123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296 |
- <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) {
- // 计算世界坐标
- const transform = this.sprite.getWorldTransformMatrix();
- const worldPos = new Phaser.Math.Vector2(
- transform.getX(0, 0),
- transform.getY(0, 0)
- );
- // 1. 计算切割方向的垂直向量(用于分离力)
- // 刀刃角度转弧度
- const rad = Phaser.Math.DegToRad(deg);
- // 垂直于刀刃的方向向量(单位向量)
- const sepX = Math.sin(rad); // 垂直方向X分量
- const sepY = -Math.cos(rad); // 垂直方向Y分量
- // 分离力度(可根据水果大小调整)
- const sepForce = 300;
- // 2. 创建第一半水果
- this.halfOne = this.game.add.sprite(worldPos.x, worldPos.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 + sepX * sepForce;
- this.halfOne.body.velocity.y = this.sprite.body.velocity.y + sepY * sepForce;
- this.halfOne.body.gravity.y = 2000;
- // 增加旋转(顺时针)
- this.halfOne.body.angularVelocity = 500; // 旋转速度(度/秒)
- this.halfOne.body.setCollideWorldBounds(false);
- this.halfOne.checkWorldBounds = true;
- this.halfOne.outOfBoundsKill = true;
- // 3. 创建第二半水果(分离方向相反)
- this.halfTwo = this.game.add.sprite(worldPos.x, worldPos.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 - sepX * sepForce;
- this.halfTwo.body.velocity.y = this.sprite.body.velocity.y - sepY * sepForce;
- this.halfTwo.body.gravity.y = 2000;
- // 增加旋转(逆时针,与第一半相反)
- this.halfTwo.body.angularVelocity = -500;
- this.halfTwo.body.setCollideWorldBounds(false);
- this.halfTwo.checkWorldBounds = true;
- this.halfTwo.outOfBoundsKill = true;
- // 4. 原水果透明度渐变消失(替代直接销毁)
- this.game.tweens.add({
- targets: this.sprite,
- alpha: 0, // 透明度从1→0
- duration: 100, // 100ms内消失
- onComplete: () => {
- this.sprite.destroy();
- }
- });
- // 5. 优化粒子效果(沿分离方向飞溅)
- 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,
- // 速度方向:以分离方向为中心,±30度范围
- angle: {
- min: deg - 30,
- max: deg + 30
- },
- speed: { min: 100, max: 300 }, // 速度与分离力度匹配
- scale: { start: 0.8, end: 0.1 },
- alpha: { start: 1, end: 0.1 },
- lifespan: 600, // 延长粒子生命周期,增强视觉效果
- maxParticles: 15 // 增加粒子数量
- });
- }
- }
- 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(0, 0); // 先置0,后续重新计算
- // 西瓜组初始位置:基于当前窗口尺寸动态计算(核心修复)
- const initX = 323 * wRatio;
- const initY = 373 * hRatio;
- this.sandiaGroup.setPosition(initX, initY); // 强制设置位置
- //圆圈
- 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 = true; // 先禁用立即切换
- 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;
- // 新增:销毁西瓜容器,避免残留
- if (this.sandiaGroup) {
- this.sandiaGroup.destroy(); // 销毁容器及其子元素
- this.sandiaGroup = null; // 置空引用
- }
- }
- }
- // 游戏场景
- 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.3; // 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
- });
- }
- console.log("isFruitisFruitisFruit",isFruit)
- 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>
|