1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273 |
- <template>
- <div class="game-container">
- <div id="game"></div>
- </div>
- </template>
- <script setup name="Fruit">
- import { onMounted, ref } from 'vue';
- import Phaser from 'phaser';
- const { proxy } = getCurrentInstance();
- const router = useRouter();
- // 游戏容器和尺寸相关
- const width = document.documentElement.clientWidth;
- const height = document.documentElement.clientHeight;
- const wRatio = document.documentElement.clientWidth / 640;
- const hRatio = document.documentElement.clientHeight / 480;
- const gameContainer = ref(null);
- let game = null;
- // 工具类
- const mathTool = {
- init() {
- },
- // 计算延长线,p2往p1延长
- calcParallel(p1, p2, L) {
- const p = {};
- if (p1.x === p2.x) {
- if (p1.y - p2.y > 0) {
- p.x = p1.x;
- p.y = p1.y + L;
- } else {
- p.x = p1.x;
- p.y = p1.y - L;
- }
- } else {
- const k = (p2.y - p1.y) / (p2.x - p1.x);
- if (p2.x - p1.x < 0) {
- p.x = p1.x + L / Math.sqrt(1 + k * k);
- p.y = p1.y + L * k / Math.sqrt(1 + k * k);
- } else {
- p.x = p1.x - L / Math.sqrt(1 + k * k);
- p.y = p1.y - L * k / Math.sqrt(1 + k * k);
- }
- }
- p.x = Math.round(p.x);
- p.y = Math.round(p.y);
- return new Phaser.Math.Vector2(p.x, p.y);
- },
- // 计算垂直线,p2点开始垂直
- calcVertical(p1, p2, L, isLeft) {
- const p = {};
- if (p1.y === p2.y) {
- p.x = p2.x;
- if (isLeft) {
- if (p2.x - p1.x > 0) {
- p.y = p2.y - L;
- } else {
- p.y = p2.y + L;
- }
- } else {
- if (p2.x - p1.x > 0) {
- p.y = p2.y + L;
- } else {
- p.y = p2.y - L;
- }
- }
- } else {
- const k = -(p2.x - p1.x) / (p2.y - p1.y);
- if (isLeft) {
- if (p2.y - p1.y > 0) {
- p.x = p2.x + L / Math.sqrt(1 + k * k);
- p.y = p2.y + L * k / Math.sqrt(1 + k * k);
- } else {
- p.x = p2.x - L / Math.sqrt(1 + k * k);
- p.y = p2.y - L * k / Math.sqrt(1 + k * k);
- }
- } else {
- if (p2.y - p1.y > 0) {
- p.x = p2.x - L / Math.sqrt(1 + k * k);
- p.y = p2.y - L * k / Math.sqrt(1 + k * k);
- } else {
- p.x = p2.x + L / Math.sqrt(1 + k * k);
- p.y = p2.y + L * k / Math.sqrt(1 + k * k);
- }
- }
- }
- p.x = Math.round(p.x);
- p.y = Math.round(p.y);
- return new Phaser.Math.Vector2(p.x, p.y);
- },
- // 形成刀光点
- generateBlade(points) {
- const res = [];
- if (points.length <= 0) {
- return res;
- } else if (points.length === 1) {
- const oneLength = 6;
- res.push(new Phaser.Math.Vector2(points[0].x - oneLength, points[0].y));
- res.push(new Phaser.Math.Vector2(points[0].x, points[0].y - oneLength));
- res.push(new Phaser.Math.Vector2(points[0].x + oneLength, points[0].y));
- res.push(new Phaser.Math.Vector2(points[0].x, points[0].y + oneLength));
- } else {
- const tailLength = 10;
- const headLength = 20;
- const tailWidth = 1;
- const headWidth = 6;
- res.push(this.calcParallel(points[0], points[1], tailLength));
- for (let i = 0; i < points.length - 1; i++) {
- res.push(this.calcVertical(
- points[i + 1],
- points[i],
- Math.round((headWidth - tailWidth) * i / (points.length - 1) + tailWidth),
- true
- ));
- }
- res.push(this.calcVertical(
- points[points.length - 2],
- points[points.length - 1],
- headWidth,
- false
- ));
- res.push(this.calcParallel(
- points[points.length - 1],
- points[points.length - 2],
- headLength
- ));
- res.push(this.calcVertical(
- points[points.length - 2],
- points[points.length - 1],
- headWidth,
- true
- ));
- for (let i = points.length - 1; i > 0; i--) {
- res.push(this.calcVertical(
- points[i],
- points[i - 1],
- Math.round((headWidth - tailWidth) * (i - 1) / (points.length - 1) + tailWidth),
- false
- ));
- }
- }
- return res;
- },
- randomMinMax(min, max) {
- return Math.random() * (max - min) + min;
- },
- randomPosX() {
- return this.randomMinMax(-100, width + 100);
- },
- randomPosY() {
- //return this.randomMinMax(100, 200) + height;
- return this.randomMinMax(height - 100, height);
- },
- randomVelocityX(posX) {
- if (posX < 0) {
- return this.randomMinMax(100, 500);
- } else if (posX >= 0 && posX < width / 2) {
- return this.randomMinMax(0, 500);
- } else if (posX >= width / 2 && posX < width) {
- return this.randomMinMax(-500, 0);
- } else {
- return this.randomMinMax(-500, -100);
- }
- },
- randomVelocityY() {
- const myH = height - 480;
- return this.randomMinMax(-1000 - myH, -950 - myH);
- },
- degCos(deg) {
- return Math.cos(deg * Math.PI / 180);
- },
- degSin(deg) {
- return Math.sin(deg * Math.PI / 180);
- },
- shuffle(o) {
- for (let j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
- return o;
- }
- };
- // 炸弹类
- class Bomb {
- constructor(envConfig) {
- this.env = envConfig;
- this.game = envConfig.scene;
- this.sprite = null;
- this.bombImage = null;
- this.bombSmoke = null;
- this.bombEmit = null;
- this.init();
- }
- init() {
- // 创建容器
- this.sprite = this.game.add.container(
- this.env.x || 0,
- this.env.y || 0
- );
- // 炸弹图像
- this.bombImage = this.game.add.sprite(0, 0, 'bomb');
- this.bombImage.setOrigin(0.5, 0.5);
- // 烟雾
- this.bombSmoke = this.game.add.sprite(-55, -55, 'smoke');
- // 创建粒子纹理
- const bitmap = this.game.make.graphics({ x: 0, y: 0, add: false });
- this.generateFlame(bitmap);
- const texture = bitmap.generateTexture('flameParticle', 50, 50);
- // 粒子发射器
- this.bombEmit = this.game.add.particles(0, 0, texture.key, {
- x: -30,
- y: -30,
- speed: { min: -100, max: 100 },
- scale: { start: 1, end: 0.8 },
- alpha: { start: 1, end: 0.1 },
- lifespan: 1500,
- frequency: 50,
- maxParticles: 20
- });
- // 添加到容器
- this.sprite.add([this.bombImage, this.bombEmit, this.bombSmoke]);
- // 物理属性
- this.game.physics.add.existing(this.sprite);
- this.sprite.body.setCollideWorldBounds(false);
- }
- generateFlame(bitmap) {
- const len = 5;
- bitmap.fillStyle(0xffffff);
- bitmap.beginPath();
- bitmap.moveTo(25 + len, 25 - len);
- bitmap.lineTo(25 + len, 25 + len);
- bitmap.lineTo(25 - len, 25 + len);
- bitmap.lineTo(25 - len, 25 - len);
- bitmap.closePath();
- bitmap.fill();
- }
- explode(onWhite, onComplete) {
- const lights = [];
- const startDeg = Math.floor(Math.random() * 360); // 随机初始角度(备用)
- // 1. 创建8个灯光图形(爆炸扩散效果)
- for (let i = 0; i < 8; i++) {
- const light = this.game.add.graphics({
- x: this.sprite.x, // 基于炸弹位置定位
- y: this.sprite.y
- });
- light.fillStyle(0xffffff, 0); // 初始透明
- light.beginPath();
- light.arc(0, 0, 15 + i * 10, 0, Math.PI * 2); // 半径递增的圆形灯光
- light.closePath();
- light.fill();
- light.setDepth(2000); // 确保在其他元素上方
- lights.push(light);
- }
- // 2. 打乱灯光顺序(与动画顺序匹配)
- mathTool.shuffle(lights);
- // 3. 构建链式动画配置(每个灯光的闪烁动画)
- const tweenConfigs = lights.map(light => ({
- targets: light,
- alpha: { value: [0, 1, 0], duration: 500 }, // 淡入淡出
- scale: { value: 2, duration: 500 }, // 缩放扩散
- onComplete: () => {
- light.destroy(); // 单个灯光动画结束后销毁
- }
- }));
- // 4. 执行链式动画(按打乱后的顺序播放灯光效果)
- this.game.tweens.chain({
- tweens: tweenConfigs,
- onComplete: () => {
- // 5. 所有灯光动画结束后,执行白屏效果
- const whiteScreen = this.game.add.graphics({
- x: 0,
- y: 0,
- fillStyle: { color: 0xffffff, alpha: 0 }
- });
- whiteScreen.fillRect(0, 0, width, height); // 覆盖全屏
- whiteScreen.setDepth(3000); // 确保在最上层
- // 白屏动画:淡入后淡出
- this.game.tweens.add({
- targets: whiteScreen,
- alpha: { value: [0, 1, 0], duration: 400 }, // 100ms淡入 + 300ms淡出
- onUpdate: (tween) => {
- // 白屏峰值时触发onWhite回调(替代原分步逻辑)
- if (tween.progress >= 0.25 && tween.progress <= 0.3) {
- onWhite(); // 白屏最亮时执行(原逻辑中的"白屏显示时")
- }
- },
- onComplete: () => {
- whiteScreen.destroy();
- // 确保回调有效后执行
- if (typeof onComplete === 'function') {
- onComplete();
- }
- }
- });
- }
- });
- }
- getSprite() {
- return this.sprite;
- }
- }
- // 水果类
- class Fruit {
- constructor(envConfig) {
- this.env = envConfig;
- this.game = envConfig.scene;
- this.sprite = null;
- this.emitterMap = {
- "apple": 0xFFC3E925,
- "banana": 0xFFFFE337,
- "basaha": 0xFFEB2D13,
- "peach": 0xFFF8C928,
- "sandia": 0xFF739E0F
- };
- this.bitmap = null;
- this.emitter = null;
- this.halfOne = null;
- this.halfTwo = null;
- this.init();
- }
- init() {
- this.sprite = this.game.add.sprite(
- this.env.x || 0,
- this.env.y || 0,
- this.env.key
- );
- this.sprite.setOrigin(0.5, 0.5);
- // 物理属性
- this.game.physics.add.existing(this.sprite);
- this.sprite.body.setCollideWorldBounds(false);
- this.sprite.body.onWorldBounds = true;
- // 创建粒子纹理
- this.bitmap = this.game.make.graphics({ x: 0, y: 0, add: false });
- // 粒子发射器
- this.emitter = this.game.add.particles(0, 0, 'flameParticle', {
- });
- }
- // 水果类中的half方法
- half(deg, shouldEmit = false) {
- // 手动计算世界坐标 (替代getWorldPosition)
- const transform = this.sprite.getWorldTransformMatrix();
- const worldPos = new Phaser.Math.Vector2(
- transform.getX(0, 0),
- transform.getY(0, 0)
- );
- // 创建两半水果,位置严格等于原水果的世界坐标
- this.halfOne = this.game.add.sprite(
- worldPos.x, // 使用计算出的世界坐标x
- worldPos.y, // 使用计算出的世界坐标y
- this.sprite.texture.key + '-1'
- );
- this.halfOne.setOrigin(0.5, 0.5);
- this.halfOne.rotation = Phaser.Math.DegToRad(deg + 45);
- // 物理属性:初始速度归零,避免瞬间偏移
- this.game.physics.add.existing(this.halfOne);
- this.halfOne.body.velocity.x = this.sprite.body.velocity.x; // 仅继承原速度
- this.halfOne.body.velocity.y = this.sprite.body.velocity.y;
- this.halfOne.body.gravity.y = 2000;
- this.halfOne.body.setCollideWorldBounds(false);
- this.halfOne.checkWorldBounds = true;
- this.halfOne.outOfBoundsKill = true;
- // 第二半水果同理
- this.halfTwo = this.game.add.sprite(
- worldPos.x, // 使用计算出的世界坐标x
- worldPos.y, // 使用计算出的世界坐标y
- this.sprite.texture.key + '-2'
- );
- this.halfTwo.setOrigin(0.5, 0.5);
- this.halfTwo.rotation = Phaser.Math.DegToRad(deg + 45);
- this.game.physics.add.existing(this.halfTwo);
- this.halfTwo.body.velocity.x = this.sprite.body.velocity.x; // 仅继承原速度
- this.halfTwo.body.velocity.y = this.sprite.body.velocity.y;
- this.halfTwo.body.gravity.y = 2000;
- this.halfTwo.body.setCollideWorldBounds(false);
- this.halfTwo.checkWorldBounds = true;
- this.halfTwo.outOfBoundsKill = true;
- // 延迟销毁原水果,确保视觉上"替换"而非"突然消失"
- setTimeout(() => {
- this.sprite.destroy();
- }, 50);
- // 粒子效果
- if (shouldEmit) {
- const emitColor = this.emitterMap[this.sprite.texture.key];
- this.generateFlame(this.bitmap, emitColor);
- const texture = this.bitmap.generateTexture('fruitParticle', 60, 60);
- this.emitter = this.game.add.particles(0, 0, texture.key, {
- x: worldPos.x,
- y: worldPos.y,
- speed: { min: -400, max: 400 },
- scale: { start: 1, end: 0.1 },
- alpha: { start: 1, end: 0.1 },
- lifespan: 4000,
- maxParticles: 10
- });
- }
- }
- generateFlame(bitmap, color) {
- // const len = 30;
- // bitmap.clear();
- // const rgb = Phaser.Display.Color.IntegerToRGB(color);
- // const radgrad = bitmap.context.createRadialGradient(len, len, 4, len, len, len);
- // radgrad.addColorStop(0, `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 1)`);
- // radgrad.addColorStop(1, `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0)`);
- // bitmap.fillStyle(radgrad);
- // bitmap.fillRect(0, 0, 2 * len, 2 * len);
- const len = 30;
- bitmap.clear();
- // 将 16 进制颜色转换为 RGB 值(0-255 范围)
- const rgb = Phaser.Display.Color.IntegerToRGB(color);
- const r = rgb.r / 255; // 转换为 0-1 范围
- const g = rgb.g / 255;
- const b = rgb.b / 255;
- // 使用 Phaser 的径向渐变 API:fillGradientStyle
- // 参数说明:
- // 1. 渐变类型:1 表示径向渐变
- // 2-5. 内圆中心坐标 (x1, y1) 和半径 (r1)
- // 6-9. 外圆中心坐标 (x2, y2) 和半径 (r2)
- // 10-13. 内圆颜色(r, g, b, a)
- // 14-17. 外圆颜色(r, g, b, a)
- bitmap.fillGradientStyle(
- 1, // 径向渐变
- len, len, 4, // 内圆:中心 (len, len),半径 4
- len, len, len, // 外圆:中心 (len, len),半径 len
- r, g, b, 1, // 内圆颜色(不透明)
- r, g, b, 0 // 外圆颜色(透明)
- );
- // 绘制矩形作为粒子纹理
- bitmap.fillRect(0, 0, 2 * len, 2 * len);
- }
- getSprite() {
- return this.sprite;
- }
- }
- // 刀身类
- class Blade {
- constructor(envConfig) {
- this.env = envConfig;
- this.game = envConfig.scene;
- this.points = [];
- this.graphics = null;
- this.POINTLIFETIME = 200;
- this.allowBlade = false;
- this.lastPoint = null;
- this.init();
- }
- init() {
- this.graphics = this.game.add.graphics({
- x: 0,
- y: 0
- });
- }
- update() {
- if (this.allowBlade) {
- this.graphics.clear();
- // 清理过期点
- const now = Date.now();
- this.points = this.points.filter(point => now - point.time < this.POINTLIFETIME);
- if (this.game.input.activePointer.isDown) {
- const point = {
- x: this.game.input.activePointer.x,
- y: this.game.input.activePointer.y,
- time: Date.now()
- };
- if (!this.lastPoint) {
- this.lastPoint = point;
- this.points.push(point);
- } else {
- const dis = Math.hypot(
- point.x - this.lastPoint.x,
- point.y - this.lastPoint.y
- );
- if (dis > Math.sqrt(300)) { // 相当于距离平方>300
- this.lastPoint = point;
- this.points.push(point);
- }
- }
- }
- if (this.points.length > 0) {
- const bladePoints = mathTool.generateBlade(this.points);
- if (bladePoints.length > 0) {
- this.graphics.fillStyle(0xffffff, 0.8);
- this.graphics.beginPath();
- this.graphics.moveTo(bladePoints[0].x, bladePoints[0].y);
- bladePoints.forEach((point, i) => {
- if (i > 0) {
- this.graphics.lineTo(point.x, point.y);
- }
- });
- this.graphics.closePath();
- this.graphics.fill();
- }
- }
- }
- }
- checkCollide(sprite, onCollide) {
- if (this.allowBlade && this.game.input.activePointer.isDown && this.points.length > 2) {
- const bounds = sprite.getBounds();
- for (const point of this.points) {
- if (Phaser.Geom.Rectangle.Contains(bounds, point.x, point.y)) {
- onCollide();
- break;
- }
- }
- }
- }
- collideDeg() {
- let deg = 0;
- const len = this.points.length;
- if (len >= 2) {
- const p0 = this.points[0];
- const p1 = this.points[len - 1];
- if (p0.x === p1.x) {
- deg = 90;
- } else {
- const val = (p0.y - p1.y) / (p0.x - p1.x);
- deg = Math.round(Math.atan(val) * 180 / Math.PI);
- }
- if (deg < 0) {
- deg += 180;
- }
- }
- return deg;
- }
- enable() {
- this.allowBlade = true;
- }
- }
- // 启动场景
- class BootScene extends Phaser.Scene {
- constructor() {
- super('boot');
- }
- preload() {
- this.load.image('loading', 'static/images/fruit/preloader.gif');
- }
- create() {
- this.scene.start('preload');
- }
- }
- // 预加载场景
- class PreloadScene extends Phaser.Scene {
- constructor() {
- super('preload');
- }
- preload() {
- this.add.sprite(10, height / 2, 'loading').setPosition((width) / 2, height / 2);
- this.load.on('progress', (value) => {
- console.log("进度", value)
- });
- // 加载游戏资源
- this.load.image('apple', 'static/images/fruit/apple.png');
- this.load.image('apple-1', 'static/images/fruit/apple-1.png');
- this.load.image('apple-2', 'static/images/fruit/apple-2.png');
- this.load.image('background', 'static/images/fruit/background.jpg');
- this.load.image('banana', 'static/images/fruit/banana.png');
- this.load.image('banana-1', 'static/images/fruit/banana-1.png');
- this.load.image('banana-2', 'static/images/fruit/banana-2.png');
- this.load.image('basaha', 'static/images/fruit/basaha.png');
- this.load.image('basaha-1', 'static/images/fruit/basaha-1.png');
- this.load.image('basaha-2', 'static/images/fruit/basaha-2.png');
- this.load.image('best', 'static/images/fruit/best.png');
- this.load.image('bomb', 'static/images/fruit/bomb.png');
- this.load.image('dojo', 'static/images/fruit/dojo.png');
- this.load.image('game-over', 'static/images/fruit/game-over.png');
- this.load.image('home-desc', 'static/images/fruit/home-desc.png');
- this.load.image('home-mask', 'static/images/fruit/home-mask.png');
- this.load.image('logo', 'static/images/fruit/logo.png');
- this.load.image('lose', 'static/images/fruit/lose.png');
- this.load.image('new-game', 'static/images/fruit/new-game.png');
- this.load.image('peach', 'static/images/fruit/peach.png');
- this.load.image('peach-1', 'static/images/fruit/peach-1.png');
- this.load.image('peach-2', 'static/images/fruit/peach-2.png');
- this.load.image('quit', 'static/images/fruit/quit.png');
- this.load.image('sandia', 'static/images/fruit/sandia.png');
- this.load.image('sandia-1', 'static/images/fruit/sandia-1.png');
- this.load.image('sandia-2', 'static/images/fruit/sandia-2.png');
- this.load.image('score', 'static/images/fruit/score.png');
- this.load.image('shadow', 'static/images/fruit/shadow.png');
- this.load.image('smoke', 'static/images/fruit/smoke.png');
- this.load.image('x', 'static/images/fruit/x.png');
- this.load.image('xf', 'static/images/fruit/xf.png');
- this.load.image('xx', 'static/images/fruit/xx.png');
- this.load.image('xxf', 'static/images/fruit/xxf.png');
- this.load.image('xxx', 'static/images/fruit/xxx.png');
- this.load.image('xxxf', 'static/images/fruit/xxxf.png');
- this.load.bitmapFont('number', 'static/images/fruit/bitmapFont.png', 'static/images/fruit/bitmapFont.xml');
- }
- create() {
- this.scene.start('main');
- }
- }
- // 主场景
- class MainScene extends Phaser.Scene {
- constructor() {
- super('main');
- this.bg = null;
- this.blade = null;
- this.homeGroup = null;
- this.home_mask = null;
- this.logo = null;
- this.home_desc = null;
- this.sandiaGroup = null;
- this.new_game = null;
- this.sandia = null;
- this.lose = null;
- this.start = false;
- this.sandiaRotateSpeed = 0.9;
- this.newGameRotateSpeed = -0.3;
- }
- create() {
- // 初始化物理系统
- this.physics.world.gravity.y = 0;
- // 背景
- this.bg = this.add.image(0, 0, "background");
- this.bg.setScale(wRatio, hRatio);
- this.bg.setPosition(width / 2, height / 2);
- this.bg.setOrigin(0.5, 0.5);
- // 刀光
- this.blade = new Blade({
- scene: this
- });
- // 开始动画
- this.homeGroupAnim();
- }
- update() {
- this.updateRotate();
- // 检查是否该跳转到游戏场景
- // if (this.start) {
- // this.gotoNextScene();
- // }
- // 更新刀光
- this.blade.update();
- // 检查水果碰撞
- if (this.sandia && this.sandia.getSprite() && this.sandia.getSprite().active && !this.start) {
- this.blade.checkCollide(
- this.sandia.getSprite(),
- () => {
- this.startGame();
- }
- );
- }
- }
- homeGroupAnim() {
- //创建组合默认先隐藏
- this.homeGroup = this.add.container(0, -height);
- //背景蒙版
- this.home_mask = this.add.image(0, 0, "home-mask");
- this.home_mask.setOrigin(0, 0);
- this.home_mask.setScale(wRatio);
- this.home_mask.y = -200;
- //logo
- this.logo = this.add.image(20, 50, "logo");
- this.logo.setOrigin(0, 0);
- //提示语
- this.home_desc = this.add.image(0, 0, "home-desc");
- this.home_desc.setPosition((width - this.home_desc.width / 2) - 20, 70);
- //合并图层
- this.homeGroup.add([this.home_mask, this.logo, this.home_desc]);
- // 退出按钮
- this.lose = this.add.image(0, 0, "lose");
- this.lose.setPosition(width - this.lose.width - 30, height - this.lose.height - 30);
- this.lose.setInteractive();
- // 绑定点击事件
- this.lose.on('pointerdown', () => this.getExit());
- //动画效果,接着展示西瓜
- this.tweens.add({
- targets: this.homeGroup,
- y: 0,
- duration: 400,
- ease: 'Sine.InOut',
- onComplete: () => this.fruitAnim()
- });
- }
- fruitAnim() {
- // 西瓜组
- this.sandiaGroup = this.add.container(323 * wRatio, 373 * hRatio);
- this.sandiaGroup.setScale(0);
- //圆圈
- this.new_game = this.add.sprite(0, 0, "new-game");
- this.new_game.setOrigin(0.5, 0.5);
- //西瓜
- this.sandia = new Fruit({ scene: this, key: "sandia" });
- this.sandiaGroup.add([this.new_game, this.sandia.getSprite()]);
- //动画效果,接着开放鼠标事件
- this.tweens.add({
- targets: this.sandiaGroup,
- scale: 1,
- duration: 500,
- ease: 'Linear.None',
- onComplete: () => this.allowBlade()
- });
- }
- updateRotate() {
- //西瓜外框圆圈图片旋转
- if (this.new_game) {
- this.new_game.rotation += this.newGameRotateSpeed * 0.016;
- }
- if (this.sandia && this.sandia.getSprite()) {
- this.sandia.getSprite().rotation += this.sandiaRotateSpeed * 0.016;
- }
- }
- allowBlade() {
- this.blade.enable();
- }
- startGame() {
- // this.start = true;
- // // 隐藏主界面元素
- // this.tweens.add({
- // targets: this.homeGroup,
- // y: -height,
- // duration: 200,
- // ease: 'Sine.InOut'
- // });
- // // 隐藏按钮
- // this.new_game.destroy();
- // this.lose.destroy();
- // // 切开西瓜
- // const deg = this.blade.collideDeg();
- // this.sandia.half(deg);
- this.start = false; // 先禁用立即切换
- const deg = this.blade.collideDeg();
- this.sandia.half(deg); // 切开西瓜,生成两半
- // 延迟1秒(1000ms)后再切换场景,等待动画展示
- setTimeout(() => {
- this.gotoNextScene();
- }, 1000);
- }
- gotoNextScene() {
- this.resetScene();
- this.scene.start("play");
- }
- getExit() {
- console.log("退出");
- router.push({ path: '/game' });
- }
- resetScene() {
- this.sandia = null;
- this.start = false;
- }
- }
- // 游戏场景
- class PlayScene extends Phaser.Scene {
- constructor() {
- super('play');
- this.bg = null;
- this.blade = null;
- this.fruits = [];
- this.score = 0;
- this.playing = false; // 改为false,在初始化方法中设置为true
- this.bombExplode = false;
- this.lostCount = 0;
- this.scoreImage = null;
- this.best = null;
- this.scoreText = null;
- this.xxxGroup = null;
- this.x = null;
- this.xx = null;
- this.xxx = null;
- this.gravity = 1000;
- }
- create() {
- // 物理系统
- this.physics.world.gravity.y = this.gravity;
- // 背景
- // this.bg = this.add.image(0, 0, 'background');
- // this.bg.setScale(wRatio, hRatio);
- // this.bg.setPosition(width / 2, height / 2);
- // this.bg.setOrigin(0, 0);
- this.bg = this.add.image(0, 0, "background");
- // 设置背景图铺满整个游戏区域
- this.bg.displayWidth = width;
- this.bg.displayHeight = height;
- // 设置背景图原点为左上角
- this.bg.setOrigin(0, 0);
- // 刀光
- this.blade = new Blade({
- scene: this
- });
- this.blade.enable();
- // 初始化UI
- this.scoreAnim();
- this.scoreTextAnim();
- this.bestAnim();
- this.xxxAnim();
- // 添加调试信息
- console.log("PlayScene created");
- // 调用初始化方法,而不是直接开始生成水果
- this.initGame();
- }
- initGame() {
- // 重置游戏状态
- this.fruits = [];
- this.score = 0;
- this.lostCount = 0;
- this.bombExplode = false;
- this.scoreText.setText(this.score.toString());
- // 重置失去计数UI
- if (this.xxxGroup) {
- this.xxxGroup.removeAll(true);
- this.x = this.add.image(0, 0, 'x');
- this.xx = this.add.image(22, 0, 'xx');
- this.xxx = this.add.image(49, 0, 'xxx');
- this.x.setOrigin(0, 0);
- this.xx.setOrigin(0, 0);
- this.xxx.setOrigin(0, 0);
- this.xxxGroup.add([this.x, this.xx, this.xxx]);
- }
- // 开始游戏
- this.playing = true;
- console.log("Game initialized and started");
- // 延迟一点时间再生成第一个水果,让玩家有准备
- this.time.delayedCall(1000, () => {
- this.startFruit();
- console.log("First fruit spawned");
- });
- }
- update() {
- // 如果游戏未开始,不执行任何操作
- if (!this.playing) return;
- // 检查是否有水果出界
- if (!this.bombExplode) {
- for (let i = this.fruits.length - 1; i >= 0; i--) {
- const fruit = this.fruits[i];
- const sprite = fruit.getSprite();
- if (sprite && !sprite.active) continue;
- if (sprite && (
- sprite.y > height + 100 ||
- sprite.x < -100 ||
- sprite.x > width + 100
- )) {
- if (fruit.isFruit) {
- this.onOut(fruit);
- }
- sprite.destroy();
- this.fruits.splice(i, 1);
- }
- }
- }
- // 如果没有水果且游戏进行中,生成新水果
- if (this.playing && this.fruits.length === 0 && !this.bombExplode) {
- this.startFruit();
- }
- // 更新刀光
- this.blade.update();
- // 检查碰撞
- if (!this.bombExplode) {
- this.fruits.forEach((fruit, i) => {
- if (fruit.getSprite() && fruit.getSprite().active) {
- this.blade.checkCollide(
- fruit.getSprite(),
- () => {
- if (fruit.isFruit) {
- this.onKill(fruit);
- this.fruits.splice(i, 1);
- } else {
- this.onBomb(fruit);
- }
- }
- );
- }
- });
- }
- }
- scoreAnim() {
- this.scoreImage = this.add.image(-100, 8, 'score');
- this.scoreImage.setOrigin(0, 0);
- this.tweens.add({
- targets: this.scoreImage,
- x: 8,
- duration: 300,
- ease: 'Sine.InOut'
- });
- }
- bestAnim() {
- this.best = this.add.image(-100, 52, 'best');
- this.best.setOrigin(0, 0);
- this.tweens.add({
- targets: this.best,
- x: 5,
- duration: 300,
- ease: 'Sine.InOut'
- });
- }
- scoreTextAnim() {
- this.scoreText = this.add.bitmapText(-100, 40, 'number', this.score.toString(), 32);
- this.scoreText.setOrigin(0, 0);
- this.tweens.add({
- targets: this.scoreText,
- x: 75,
- duration: 300,
- ease: 'Sine.InOut'
- });
- }
- xxxAnim() {
- this.xxxGroup = this.add.container(width + 100, 5);
- this.x = this.add.image(0, 0, 'x');
- this.xx = this.add.image(22, 0, 'xx');
- this.xxx = this.add.image(49, 0, 'xxx');
- this.xxxGroup.add([this.x, this.xx, this.xxx]);
- this.tweens.add({
- targets: this.xxxGroup,
- x: width - 86,
- duration: 300,
- ease: 'Sine.InOut'
- });
- }
- startFruit() {
- const number = Math.floor(mathTool.randomMinMax(1, 5));
- const hasBomb = Math.random() > 0.7; // 30%概率有炸弹
- const bombIndex = hasBomb ? Math.floor(Math.random() * number) : -1;
- for (let i = 0; i < number; i++) {
- if (i === bombIndex) {
- this.fruits.push(this.randomFruit(false));
- } else {
- this.fruits.push(this.randomFruit(true));
- }
- }
- }
- randomFruit(isFruit) {
- const fruitArray = ["apple", "banana", "basaha", "peach", "sandia"];
- const index = Math.floor(Math.random() * fruitArray.length);
- const x = mathTool.randomPosX();
- const y = mathTool.randomPosY();
- const vx = mathTool.randomVelocityX(x);
- const vy = mathTool.randomVelocityY();
- let fruit;
- if (isFruit) {
- fruit = new Fruit({
- scene: this,
- key: fruitArray[index],
- x: x,
- y: y
- });
- } else {
- fruit = new Bomb({
- scene: this,
- x: x,
- y: y
- });
- }
- fruit.isFruit = isFruit;
- const sprite = fruit.getSprite();
- if (sprite.body) {
- sprite.body.velocity.x = vx;
- sprite.body.velocity.y = vy;
- sprite.body.gravity.y = this.gravity;
- }
- return fruit;
- }
- onOut(fruit) {
- const sprite = fruit.getSprite();
- let x, y;
- // 确定失去标记的位置
- if (sprite.y > height) {
- x = sprite.x;
- y = height - 30;
- } else if (sprite.x < 0) {
- x = 30;
- y = sprite.y;
- } else {
- x = width - 30;
- y = sprite.y;
- }
- // 创建失去标记动画
- const lose = this.add.sprite(x, y, 'lose');
- lose.setOrigin(0.5, 0.5);
- lose.setScale(0);
- const tweenShow = this.tweens.add({
- targets: lose,
- scale: 1,
- duration: 300,
- ease: 'Sine.InOut',
- paused: true
- });
- const tweenHide = this.tweens.add({
- targets: lose,
- scale: 0,
- duration: 300,
- ease: 'Sine.InOut',
- paused: true,
- delay: 1000
- });
- this.tweens.chain({
- targets: lose,
- tweens: [
- {
- scale: 1,
- duration: 300,
- ease: 'Sine.InOut'
- },
- {
- scale: 0,
- duration: 300,
- ease: 'Sine.InOut',
- delay: 1000,
- onComplete: () => {
- lose.destroy();
- }
- }
- ]
- });
- tweenShow.play();
- tweenHide.on('complete', () => {
- lose.destroy();
- });
- this.lostCount++;
- this.loseCount();
- }
- onKill(fruit) {
- const deg = this.blade.collideDeg();
- fruit.half(deg, true);
- this.score++;
- this.scoreText.setText(this.score.toString());
- }
- onBomb(bomb) {
- this.bombExplode = true;
- // 停止所有水果的物理运动
- this.fruits.forEach(fruit => {
- if (fruit.getSprite() && fruit.getSprite().body) {
- fruit.getSprite().body.setVelocity(0);
- fruit.getSprite().body.setGravity(0);
- }
- });
- // 炸弹爆炸
- bomb.explode(
- () => {
- // 白屏显示时的回调:销毁所有水果
- this.fruits.forEach(fruit => {
- if (fruit.getSprite()) {
- fruit.getSprite().destroy();
- }
- });
- this.fruits = [];
- },
- () => {
- // 爆炸完成后的回调
- this.gameOver();
- }
- );
- }
- loseCount() {
- if (this.lostCount === 1) {
- this.lostAnim(this.x, 'xf');
- } else if (this.lostCount === 2) {
- this.lostAnim(this.xx, 'xxf');
- } else if (this.lostCount >= 3) {
- this.lostAnim(this.xxx, 'xxxf');
- this.gameOver();
- }
- }
- lostAnim(removeObj, addKey) {
- removeObj.destroy();
- const newObj = this.add.sprite(removeObj.x, removeObj.y, addKey);
- newObj.setOrigin(0, 0);
- newObj.setScale(0);
- this.xxxGroup.add(newObj);
- this.tweens.add({
- targets: newObj,
- scale: 1,
- duration: 300,
- ease: 'Sine.InOut'
- });
- }
- gameOver() {
- this.playing = false;
- // 1. 确保所有其他元素停止更新,避免干扰
- this.blade.allowBlade = false; // 禁用刀光
- // 2. 创建game-over图片,并设置最高层级
- const gameOverSprite = this.add.sprite(width / 2, height / 2, 'game-over');
- gameOverSprite.setOrigin(0.5, 0.5);
- gameOverSprite.setScale(0);
- gameOverSprite.setDepth(1000); // 设置最高层级,确保不被覆盖
- // 3. 优化入场动画,确保平滑显示
- this.tweens.add({
- targets: gameOverSprite,
- scale: 1,
- duration: 500, // 延长动画时间,确保可见
- ease: 'Elastic.Out', // 更明显的弹性动画,增强视觉效果
- onComplete: () => {
- // 动画完成后再设置自动返回,确保用户有足够时间看到画面
- setTimeout(() => {
- console.log('游戏结束,返回首页');
- this.scene.start('main');
- }, 3000); // 延长至3秒,给用户足够时间观察
- }
- });
- // 4. 支持点击立即返回,提升交互体验
- gameOverSprite.setInteractive();
- gameOverSprite.on('pointerdown', () => {
- console.log('点击返回首页');
- this.scene.start('main');
- });
- }
- }
- // 初始化游戏
- onMounted(() => {
- // 获取容器尺寸
- const container = document.getElementById('game');
- // 初始化工具类
- mathTool.init();
- // 创建游戏实例
- game = new Phaser.Game({
- type: Phaser.CANVAS,
- width: width,
- height: height,
- parent: 'game',
- scene: [BootScene, PreloadScene, MainScene, PlayScene],
- physics: {
- default: 'arcade',
- arcade: {
- debug: false
- }
- }
- });
- });
- </script>
- <style lang="scss" scoped></style>
|