fruit.vue 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389
  1. <template>
  2. <div class="game-container">
  3. <div id="game"></div>
  4. </div>
  5. </template>
  6. <script setup name="Fruit">
  7. import { onMounted, ref } from 'vue';
  8. import Phaser from 'phaser';
  9. const { proxy } = getCurrentInstance();
  10. const router = useRouter();
  11. // 游戏容器和尺寸相关
  12. const width = document.documentElement.clientWidth;
  13. const height = document.documentElement.clientHeight;
  14. const wRatio = document.documentElement.clientWidth / 640;
  15. const hRatio = document.documentElement.clientHeight / 480;
  16. const gameContainer = ref(null);
  17. let game = null;
  18. // 工具类
  19. const mathTool = {
  20. init() {
  21. },
  22. // 计算延长线,p2往p1延长
  23. calcParallel(p1, p2, L) {
  24. const p = {};
  25. if (p1.x === p2.x) {
  26. if (p1.y - p2.y > 0) {
  27. p.x = p1.x;
  28. p.y = p1.y + L;
  29. } else {
  30. p.x = p1.x;
  31. p.y = p1.y - L;
  32. }
  33. } else {
  34. const k = (p2.y - p1.y) / (p2.x - p1.x);
  35. if (p2.x - p1.x < 0) {
  36. p.x = p1.x + L / Math.sqrt(1 + k * k);
  37. p.y = p1.y + L * k / Math.sqrt(1 + k * k);
  38. } else {
  39. p.x = p1.x - L / Math.sqrt(1 + k * k);
  40. p.y = p1.y - L * k / Math.sqrt(1 + k * k);
  41. }
  42. }
  43. p.x = Math.round(p.x);
  44. p.y = Math.round(p.y);
  45. return new Phaser.Math.Vector2(p.x, p.y);
  46. },
  47. // 计算垂直线,p2点开始垂直
  48. calcVertical(p1, p2, L, isLeft) {
  49. const p = {};
  50. if (p1.y === p2.y) {
  51. p.x = p2.x;
  52. if (isLeft) {
  53. if (p2.x - p1.x > 0) {
  54. p.y = p2.y - L;
  55. } else {
  56. p.y = p2.y + L;
  57. }
  58. } else {
  59. if (p2.x - p1.x > 0) {
  60. p.y = p2.y + L;
  61. } else {
  62. p.y = p2.y - L;
  63. }
  64. }
  65. } else {
  66. const k = -(p2.x - p1.x) / (p2.y - p1.y);
  67. if (isLeft) {
  68. if (p2.y - p1.y > 0) {
  69. p.x = p2.x + L / Math.sqrt(1 + k * k);
  70. p.y = p2.y + L * k / Math.sqrt(1 + k * k);
  71. } else {
  72. p.x = p2.x - L / Math.sqrt(1 + k * k);
  73. p.y = p2.y - L * k / Math.sqrt(1 + k * k);
  74. }
  75. } else {
  76. if (p2.y - p1.y > 0) {
  77. p.x = p2.x - L / Math.sqrt(1 + k * k);
  78. p.y = p2.y - L * k / Math.sqrt(1 + k * k);
  79. } else {
  80. p.x = p2.x + L / Math.sqrt(1 + k * k);
  81. p.y = p2.y + L * k / Math.sqrt(1 + k * k);
  82. }
  83. }
  84. }
  85. p.x = Math.round(p.x);
  86. p.y = Math.round(p.y);
  87. return new Phaser.Math.Vector2(p.x, p.y);
  88. },
  89. // 形成刀光点
  90. generateBlade(points) {
  91. const res = [];
  92. if (points.length <= 0) {
  93. return res;
  94. } else if (points.length === 1) {
  95. const oneLength = 6;
  96. res.push(new Phaser.Math.Vector2(points[0].x - oneLength, points[0].y));
  97. res.push(new Phaser.Math.Vector2(points[0].x, points[0].y - oneLength));
  98. res.push(new Phaser.Math.Vector2(points[0].x + oneLength, points[0].y));
  99. res.push(new Phaser.Math.Vector2(points[0].x, points[0].y + oneLength));
  100. } else {
  101. const tailLength = 10;
  102. const headLength = 20;
  103. const tailWidth = 1;
  104. const headWidth = 6;
  105. res.push(this.calcParallel(points[0], points[1], tailLength));
  106. for (let i = 0; i < points.length - 1; i++) {
  107. res.push(this.calcVertical(
  108. points[i + 1],
  109. points[i],
  110. Math.round((headWidth - tailWidth) * i / (points.length - 1) + tailWidth),
  111. true
  112. ));
  113. }
  114. res.push(this.calcVertical(
  115. points[points.length - 2],
  116. points[points.length - 1],
  117. headWidth,
  118. false
  119. ));
  120. res.push(this.calcParallel(
  121. points[points.length - 1],
  122. points[points.length - 2],
  123. headLength
  124. ));
  125. res.push(this.calcVertical(
  126. points[points.length - 2],
  127. points[points.length - 1],
  128. headWidth,
  129. true
  130. ));
  131. for (let i = points.length - 1; i > 0; i--) {
  132. res.push(this.calcVertical(
  133. points[i],
  134. points[i - 1],
  135. Math.round((headWidth - tailWidth) * (i - 1) / (points.length - 1) + tailWidth),
  136. false
  137. ));
  138. }
  139. }
  140. return res;
  141. },
  142. randomMinMax(min, max) {
  143. return Math.random() * (max - min) + min;
  144. },
  145. randomPosX() {
  146. return this.randomMinMax(-100, width + 100);
  147. },
  148. randomPosY() {
  149. //return this.randomMinMax(100, 200) + height;
  150. return this.randomMinMax(height - 100, height);
  151. },
  152. randomVelocityX(posX) {
  153. if (posX < 0) {
  154. return this.randomMinMax(100, 500);
  155. } else if (posX >= 0 && posX < width / 2) {
  156. return this.randomMinMax(0, 500);
  157. } else if (posX >= width / 2 && posX < width) {
  158. return this.randomMinMax(-500, 0);
  159. } else {
  160. return this.randomMinMax(-500, -100);
  161. }
  162. },
  163. randomVelocityY() {
  164. const myH = height - 480;
  165. return this.randomMinMax(-900 - myH, -850 - myH);
  166. },
  167. degCos(deg) {
  168. return Math.cos(deg * Math.PI / 180);
  169. },
  170. degSin(deg) {
  171. return Math.sin(deg * Math.PI / 180);
  172. },
  173. shuffle(o) {
  174. for (let j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
  175. return o;
  176. }
  177. };
  178. // 炸弹类
  179. class Bomb {
  180. constructor(envConfig) {
  181. this.env = envConfig;
  182. this.game = envConfig.scene;
  183. this.sprite = null;
  184. this.bombImage = null;
  185. this.bombSmoke = null;
  186. this.bombEmit = null;
  187. this.init();
  188. }
  189. init() {
  190. // 创建容器
  191. this.sprite = this.game.add.container(
  192. this.env.x || 0,
  193. this.env.y || 0
  194. );
  195. // 炸弹图像
  196. this.bombImage = this.game.add.sprite(0, 0, 'bomb');
  197. this.bombImage.setOrigin(0.5, 0.5);
  198. // 烟雾
  199. this.bombSmoke = this.game.add.sprite(-55, -55, 'smoke');
  200. // 创建粒子纹理
  201. const bitmap = this.game.make.graphics({ x: 0, y: 0, add: false });
  202. this.generateFlame(bitmap);
  203. const texture = bitmap.generateTexture('flameParticle', 50, 50);
  204. // 粒子发射器
  205. this.bombEmit = this.game.add.particles(0, 0, texture.key, {
  206. x: -30,
  207. y: -30,
  208. speed: { min: -100, max: 100 },
  209. scale: { start: 1, end: 0.8 },
  210. alpha: { start: 1, end: 0.1 },
  211. lifespan: 1500,
  212. frequency: 50,
  213. maxParticles: 20
  214. });
  215. // 添加到容器
  216. this.sprite.add([this.bombImage, this.bombEmit, this.bombSmoke]);
  217. // 物理属性
  218. this.game.physics.add.existing(this.sprite);
  219. this.sprite.body.setCollideWorldBounds(false);
  220. }
  221. generateFlame(bitmap) {
  222. const len = 5;
  223. bitmap.fillStyle(0xffffff);
  224. bitmap.beginPath();
  225. bitmap.moveTo(25 + len, 25 - len);
  226. bitmap.lineTo(25 + len, 25 + len);
  227. bitmap.lineTo(25 - len, 25 + len);
  228. bitmap.lineTo(25 - len, 25 - len);
  229. bitmap.closePath();
  230. bitmap.fill();
  231. }
  232. explode(onWhite, onComplete) {
  233. const lights = [];
  234. const startDeg = Math.floor(Math.random() * 360);
  235. // 保存外部this上下文
  236. const self = this;
  237. // 创建直射光芒效果
  238. const maxRays = 25; // 光芒数量
  239. const rayLength = 400; // 光芒长度
  240. const rayWidth = 200; // 光芒宽度
  241. for (let i = 0; i < maxRays; i++) {
  242. const angle = (i / maxRays) * Math.PI * 2; // 计算每个光芒的角度
  243. const light = self.game.add.graphics({
  244. x: self.sprite.x,
  245. y: self.sprite.y
  246. });
  247. // 设置初始透明度和颜色
  248. const alpha = 0.8 - (Math.random() * 0.3); // 随机透明度,增加自然感
  249. const hue = 45 - (Math.random() * 20); // 随机色调,从黄色到白色
  250. const color = Phaser.Display.Color.HSVToRGB(hue / 360, 0.8, 1).color;
  251. light.alpha = alpha;
  252. light.fillStyle(color, 1);
  253. // 创建细长三角形
  254. light.beginPath();
  255. light.moveTo(0, 0); // 起点在中心
  256. light.lineTo(
  257. Math.cos(angle) * rayLength,
  258. Math.sin(angle) * rayLength
  259. ); // 终点在外围
  260. light.lineTo(
  261. Math.cos(angle + Math.PI + 0.1) * (rayWidth / 2),
  262. Math.sin(angle + Math.PI + 0.1) * (rayWidth / 2)
  263. ); // 底部左点
  264. light.closePath();
  265. light.fill();
  266. light.setDepth(2000);
  267. lights.push(light);
  268. // 添加脉动动画效果
  269. self.game.tweens.add({
  270. targets: light,
  271. alpha: alpha * 0.5,
  272. duration: 800 + Math.random() * 400,
  273. yoyo: true,
  274. repeat: -1,
  275. ease: 'Sine.easeInOut'
  276. });
  277. }
  278. // 2. 打乱灯光顺序
  279. mathTool.shuffle(lights);
  280. // 3. 创建白屏元素
  281. const whiteScreen = self.game.add.graphics({
  282. x: 0,
  283. y: 0
  284. });
  285. whiteScreen.fillStyle(0xffffff, 0);
  286. whiteScreen.fillRect(0, 0, width, height);
  287. whiteScreen.setDepth(3000);
  288. whiteScreen.alpha = 0;
  289. // 4. 按顺序执行灯光动画
  290. function playChainAnimations(index) {
  291. if (index >= lights.length) {
  292. playWhiteScreenAnimation();
  293. return;
  294. }
  295. const light = lights[index];
  296. self.game.tweens.add({
  297. targets: light,
  298. alpha: { from: 0, to: 1, from: 0 },
  299. scale: { from: 1, to: 2 },
  300. duration: 50,
  301. onComplete: () => {
  302. light.destroy();
  303. playChainAnimations(index + 1);
  304. }
  305. });
  306. }
  307. function playWhiteScreenAnimation() {
  308. self.game.tweens.add({
  309. targets: whiteScreen,
  310. alpha: { from: 0, to: 1, to: 0 },
  311. duration: 50,
  312. onUpdate: (tween) => {
  313. if (tween.progress >= 0.25 && tween.progress <= 0.3) {
  314. onWhite();
  315. }
  316. },
  317. onComplete: () => {
  318. whiteScreen.destroy();
  319. if (typeof onComplete === 'function') {
  320. onComplete();
  321. }
  322. }
  323. });
  324. }
  325. // 开始执行第一个灯光动画
  326. playChainAnimations(0);
  327. }
  328. getSprite() {
  329. return this.sprite;
  330. }
  331. }
  332. // 水果类
  333. class Fruit {
  334. constructor(envConfig) {
  335. this.env = envConfig;
  336. this.game = envConfig.scene;
  337. this.sprite = null;
  338. this.emitterMap = {
  339. "apple": 0xFFC3E925,
  340. "banana": 0xFFFFE337,
  341. "basaha": 0xFFEB2D13,
  342. "peach": 0xFFF8C928,
  343. "sandia": 0xFF739E0F
  344. };
  345. this.bitmap = null;
  346. this.emitter = null;
  347. this.halfOne = null;
  348. this.halfTwo = null;
  349. this.init();
  350. }
  351. init() {
  352. this.sprite = this.game.add.sprite(
  353. this.env.x || 0,
  354. this.env.y || 0,
  355. this.env.key
  356. );
  357. this.sprite.setOrigin(0.5, 0.5);
  358. // 物理属性
  359. this.game.physics.add.existing(this.sprite);
  360. this.sprite.body.setCollideWorldBounds(false);
  361. this.sprite.body.onWorldBounds = true;
  362. // 创建粒子纹理
  363. this.bitmap = this.game.make.graphics({ x: 0, y: 0, add: false });
  364. // 粒子发射器
  365. this.emitter = this.game.add.particles(0, 0, 'flameParticle', {
  366. visible: false // 初始隐藏
  367. });
  368. }
  369. // 水果类中的half方法
  370. half(deg, shouldEmit = false) {
  371. // 计算世界坐标
  372. const transform = this.sprite.getWorldTransformMatrix();
  373. const worldPos = new Phaser.Math.Vector2(
  374. transform.getX(0, 0),
  375. transform.getY(0, 0)
  376. );
  377. // 1. 计算切割方向的垂直向量(用于分离力)
  378. // 刀刃角度转弧度
  379. const rad = Phaser.Math.DegToRad(deg);
  380. // 垂直于刀刃的方向向量(单位向量)
  381. const sepX = Math.sin(rad); // 垂直方向X分量
  382. const sepY = -Math.cos(rad); // 垂直方向Y分量
  383. // 分离力度(可根据水果大小调整)
  384. const sepForce = 300;
  385. // 2. 创建第一半水果
  386. this.halfOne = this.game.add.sprite(worldPos.x, worldPos.y, this.sprite.texture.key + '-1');
  387. this.halfOne.setOrigin(0.5, 0.5);
  388. this.halfOne.rotation = Phaser.Math.DegToRad(deg + 45); // 初始角度与刀刃匹配
  389. this.game.physics.add.existing(this.halfOne);
  390. // 速度 = 原速度 + 分离速度(向一侧)
  391. this.halfOne.body.velocity.x = this.sprite.body.velocity.x + sepX * sepForce;
  392. this.halfOne.body.velocity.y = this.sprite.body.velocity.y + sepY * sepForce;
  393. this.halfOne.body.gravity.y = 2000;
  394. // 增加旋转(顺时针)
  395. this.halfOne.body.angularVelocity = 500; // 旋转速度(度/秒)
  396. this.halfOne.body.setCollideWorldBounds(false);
  397. this.halfOne.checkWorldBounds = true;
  398. this.halfOne.outOfBoundsKill = true;
  399. // 3. 创建第二半水果(分离方向相反)
  400. this.halfTwo = this.game.add.sprite(worldPos.x, worldPos.y, this.sprite.texture.key + '-2');
  401. this.halfTwo.setOrigin(0.5, 0.5);
  402. this.halfTwo.rotation = Phaser.Math.DegToRad(deg + 45);
  403. this.game.physics.add.existing(this.halfTwo);
  404. // 速度 = 原速度 - 分离速度(向另一侧)
  405. this.halfTwo.body.velocity.x = this.sprite.body.velocity.x - sepX * sepForce;
  406. this.halfTwo.body.velocity.y = this.sprite.body.velocity.y - sepY * sepForce;
  407. this.halfTwo.body.gravity.y = 2000;
  408. // 增加旋转(逆时针,与第一半相反)
  409. this.halfTwo.body.angularVelocity = -500;
  410. this.halfTwo.body.setCollideWorldBounds(false);
  411. this.halfTwo.checkWorldBounds = true;
  412. this.halfTwo.outOfBoundsKill = true;
  413. // 4. 原水果透明度渐变消失(替代直接销毁)
  414. this.game.tweens.add({
  415. targets: this.sprite,
  416. alpha: 0, // 透明度从1→0
  417. duration: 100, // 100ms内消失
  418. onComplete: () => {
  419. this.sprite.destroy();
  420. }
  421. });
  422. // 5. 优化粒子效果(沿分离方向飞溅)
  423. if (shouldEmit) {
  424. const emitColor = this.emitterMap[this.sprite.texture.key];
  425. this.generateFlame(this.bitmap, emitColor);
  426. const texture = this.bitmap.generateTexture('fruitParticle', 60, 60);
  427. // 粒子发射器:沿分离方向扩散
  428. this.emitter = this.game.add.particles(0, 0, texture.key, {
  429. x: worldPos.x,
  430. y: worldPos.y,
  431. // 速度方向:以分离方向为中心,±30度范围
  432. angle: {
  433. min: deg - 30,
  434. max: deg + 30
  435. },
  436. speed: { min: 100, max: 300 }, // 速度与分离力度匹配
  437. scale: { start: 0.8, end: 0.1 },
  438. alpha: { start: 1, end: 0.1 },
  439. lifespan: 600, // 延长粒子生命周期,增强视觉效果
  440. maxParticles: 15 // 增加粒子数量
  441. });
  442. }
  443. }
  444. generateFlame(bitmap, color) {
  445. // const len = 30;
  446. // bitmap.clear();
  447. // const rgb = Phaser.Display.Color.IntegerToRGB(color);
  448. // const radgrad = bitmap.context.createRadialGradient(len, len, 4, len, len, len);
  449. // radgrad.addColorStop(0, `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 1)`);
  450. // radgrad.addColorStop(1, `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0)`);
  451. // bitmap.fillStyle(radgrad);
  452. // bitmap.fillRect(0, 0, 2 * len, 2 * len);
  453. const len = 30;
  454. bitmap.clear();
  455. // 将 16 进制颜色转换为 RGB 值(0-255 范围)
  456. const rgb = Phaser.Display.Color.IntegerToRGB(color);
  457. const r = rgb.r / 255; // 转换为 0-1 范围
  458. const g = rgb.g / 255;
  459. const b = rgb.b / 255;
  460. // 使用 Phaser 的径向渐变 API:fillGradientStyle
  461. // 参数说明:
  462. // 1. 渐变类型:1 表示径向渐变
  463. // 2-5. 内圆中心坐标 (x1, y1) 和半径 (r1)
  464. // 6-9. 外圆中心坐标 (x2, y2) 和半径 (r2)
  465. // 10-13. 内圆颜色(r, g, b, a)
  466. // 14-17. 外圆颜色(r, g, b, a)
  467. bitmap.fillGradientStyle(
  468. 1, // 径向渐变
  469. len, len, 4, // 内圆:中心 (len, len),半径 4
  470. len, len, len, // 外圆:中心 (len, len),半径 len
  471. r, g, b, 1, // 内圆颜色(不透明)
  472. r, g, b, 0 // 外圆颜色(透明)
  473. );
  474. // 绘制矩形作为粒子纹理
  475. bitmap.fillRect(0, 0, 2 * len, 2 * len);
  476. }
  477. getSprite() {
  478. return this.sprite;
  479. }
  480. }
  481. // 刀身类
  482. class Blade {
  483. constructor(envConfig) {
  484. this.env = envConfig;
  485. this.game = envConfig.scene;
  486. this.points = []; // 记录鼠标轨迹点
  487. this.graphics = null;
  488. this.POINTLIFETIME = 200; // 轨迹点的生命周期(毫秒)
  489. this.allowBlade = false;
  490. this.lastPoint = null; // 上一个记录的点
  491. this.moveThreshold = 5; // 鼠标移动超过这个距离才记录新点(避免密集冗余)
  492. this.init();
  493. }
  494. init() {
  495. this.graphics = this.game.add.graphics({
  496. x: 0,
  497. y: 0
  498. });
  499. // 监听鼠标移动事件(可选,也可在update中处理)
  500. this.game.input.on('pointermove', (pointer) => {
  501. console.log("11111",pointer)
  502. if (this.allowBlade) {
  503. this.handleMouseMove(pointer);
  504. }
  505. });
  506. }
  507. // 处理鼠标移动:记录轨迹点
  508. handleMouseMove(pointer) {
  509. const point = {
  510. x: pointer.x,
  511. y: pointer.y,
  512. time: Date.now()
  513. };
  514. if (!this.lastPoint) {
  515. // 首次记录点
  516. this.lastPoint = point;
  517. this.points.push(point);
  518. } else {
  519. // 计算与上一个点的距离,超过阈值才记录新点
  520. const dis = Math.hypot(
  521. point.x - this.lastPoint.x,
  522. point.y - this.lastPoint.y
  523. );
  524. if (dis > this.moveThreshold) {
  525. this.lastPoint = point;
  526. this.points.push(point);
  527. }
  528. }
  529. }
  530. update() {
  531. if (this.allowBlade) {
  532. this.graphics.clear();
  533. // 清理过期的轨迹点(超过生命周期的点)
  534. const now = Date.now();
  535. this.points = this.points.filter(point => now - point.time < this.POINTLIFETIME);
  536. // 生成刀光图形(基于现有轨迹点)
  537. if (this.points.length > 0) {
  538. const bladePoints = mathTool.generateBlade(this.points);
  539. if (bladePoints.length > 0) {
  540. this.graphics.fillStyle(0xffffff, 0.8);
  541. this.graphics.beginPath();
  542. this.graphics.moveTo(bladePoints[0].x, bladePoints[0].y);
  543. bladePoints.forEach((point, i) => {
  544. if (i > 0) {
  545. this.graphics.lineTo(point.x, point.y);
  546. }
  547. });
  548. this.graphics.closePath();
  549. this.graphics.fill();
  550. }
  551. }
  552. }
  553. }
  554. // 碰撞检测:去掉“鼠标按下”的限制,只要有轨迹就检测
  555. checkCollide(sprite, onCollide) {
  556. if (this.allowBlade && this.points.length > 2) { // 仅保留轨迹点数量的判断
  557. const bounds = sprite.getBounds();
  558. for (const point of this.points) {
  559. if (Phaser.Geom.Rectangle.Contains(bounds, point.x, point.y)) {
  560. onCollide();
  561. break;
  562. }
  563. }
  564. }
  565. }
  566. collideDeg() {
  567. let deg = 0;
  568. const len = this.points.length;
  569. if (len >= 2) {
  570. const p0 = this.points[0];
  571. const p1 = this.points[len - 1];
  572. if (p0.x === p1.x) {
  573. deg = 90;
  574. } else {
  575. const val = (p0.y - p1.y) / (p0.x - p1.x);
  576. deg = Math.round(Math.atan(val) * 180 / Math.PI);
  577. }
  578. if (deg < 0) {
  579. deg += 180;
  580. }
  581. }
  582. return deg;
  583. }
  584. enable() {
  585. this.allowBlade = true;
  586. }
  587. }
  588. // 启动场景
  589. class BootScene extends Phaser.Scene {
  590. constructor() {
  591. super('boot');
  592. }
  593. preload() {
  594. this.load.image('loading', 'static/images/fruit/preloader.gif');
  595. }
  596. create() {
  597. this.scene.start('preload');
  598. }
  599. }
  600. // 预加载场景
  601. class PreloadScene extends Phaser.Scene {
  602. constructor() {
  603. super('preload');
  604. }
  605. preload() {
  606. this.add.sprite(10, height / 2, 'loading').setPosition((width) / 2, height / 2);
  607. this.load.on('progress', (value) => {
  608. console.log("进度", value)
  609. });
  610. // 加载游戏资源
  611. this.load.image('apple', 'static/images/fruit/apple.png');
  612. this.load.image('apple-1', 'static/images/fruit/apple-1.png');
  613. this.load.image('apple-2', 'static/images/fruit/apple-2.png');
  614. this.load.image('background', 'static/images/fruit/background.jpg');
  615. this.load.image('banana', 'static/images/fruit/banana.png');
  616. this.load.image('banana-1', 'static/images/fruit/banana-1.png');
  617. this.load.image('banana-2', 'static/images/fruit/banana-2.png');
  618. this.load.image('basaha', 'static/images/fruit/basaha.png');
  619. this.load.image('basaha-1', 'static/images/fruit/basaha-1.png');
  620. this.load.image('basaha-2', 'static/images/fruit/basaha-2.png');
  621. this.load.image('best', 'static/images/fruit/best.png');
  622. this.load.image('bomb', 'static/images/fruit/bomb.png');
  623. this.load.image('dojo', 'static/images/fruit/dojo.png');
  624. this.load.image('game-over', 'static/images/fruit/game-over.png');
  625. this.load.image('home-desc', 'static/images/fruit/home-desc.png');
  626. this.load.image('home-mask', 'static/images/fruit/home-mask.png');
  627. this.load.image('logo', 'static/images/fruit/logo.png');
  628. this.load.image('lose', 'static/images/fruit/lose.png');
  629. this.load.image('new-game', 'static/images/fruit/new-game.png');
  630. this.load.image('peach', 'static/images/fruit/peach.png');
  631. this.load.image('peach-1', 'static/images/fruit/peach-1.png');
  632. this.load.image('peach-2', 'static/images/fruit/peach-2.png');
  633. this.load.image('quit', 'static/images/fruit/quit.png');
  634. this.load.image('sandia', 'static/images/fruit/sandia.png');
  635. this.load.image('sandia-1', 'static/images/fruit/sandia-1.png');
  636. this.load.image('sandia-2', 'static/images/fruit/sandia-2.png');
  637. this.load.image('score', 'static/images/fruit/score.png');
  638. this.load.image('shadow', 'static/images/fruit/shadow.png');
  639. this.load.image('smoke', 'static/images/fruit/smoke.png');
  640. this.load.image('x', 'static/images/fruit/x.png');
  641. this.load.image('xf', 'static/images/fruit/xf.png');
  642. this.load.image('xx', 'static/images/fruit/xx.png');
  643. this.load.image('xxf', 'static/images/fruit/xxf.png');
  644. this.load.image('xxx', 'static/images/fruit/xxx.png');
  645. this.load.image('xxxf', 'static/images/fruit/xxxf.png');
  646. this.load.bitmapFont('number', 'static/images/fruit/bitmapFont.png', 'static/images/fruit/bitmapFont.xml');
  647. }
  648. create() {
  649. this.scene.start('main');
  650. }
  651. }
  652. // 主场景
  653. class MainScene extends Phaser.Scene {
  654. constructor() {
  655. super('main');
  656. this.bg = null;
  657. this.blade = null;
  658. this.homeGroup = null;
  659. this.home_mask = null;
  660. this.logo = null;
  661. this.home_desc = null;
  662. this.sandiaGroup = null;
  663. this.new_game = null;
  664. this.sandia = null;
  665. this.lose = null;
  666. this.start = false;
  667. this.sandiaRotateSpeed = 0.9;
  668. this.newGameRotateSpeed = -0.3;
  669. }
  670. create() {
  671. // 初始化物理系统
  672. this.physics.world.gravity.y = 0;
  673. // 背景
  674. this.bg = this.add.image(0, 0, "background");
  675. this.bg.setScale(wRatio, hRatio);
  676. this.bg.setPosition(width / 2, height / 2);
  677. this.bg.setOrigin(0.5, 0.5);
  678. // 刀光
  679. this.blade = new Blade({
  680. scene: this
  681. });
  682. // 开始动画
  683. this.homeGroupAnim();
  684. }
  685. update() {
  686. this.updateRotate();
  687. // 检查是否该跳转到游戏场景
  688. // if (this.start) {
  689. // this.gotoNextScene();
  690. // }
  691. // 更新刀光
  692. this.blade.update();
  693. // 检查水果碰撞
  694. if (this.sandia && this.sandia.getSprite() && this.sandia.getSprite().active && !this.start) {
  695. this.blade.checkCollide(
  696. this.sandia.getSprite(),
  697. () => {
  698. this.startGame();
  699. }
  700. );
  701. }
  702. }
  703. homeGroupAnim() {
  704. //创建组合默认先隐藏
  705. this.homeGroup = this.add.container(0, -height);
  706. //背景蒙版
  707. // this.home_mask = this.add.image(0, 0, "home-mask");
  708. // this.home_mask.setOrigin(0, 0);
  709. // this.home_mask.setScale(wRatio);
  710. // this.home_mask.y = -200;
  711. //logo
  712. this.logo = this.add.image(20, 50, "logo");
  713. this.logo.setOrigin(0, 0);
  714. //提示语
  715. this.home_desc = this.add.image(0, 0, "home-desc");
  716. this.home_desc.setPosition((width - this.home_desc.width / 2) - 20, 70);
  717. //合并图层
  718. //this.homeGroup.add([this.home_mask, this.logo, this.home_desc]);
  719. this.homeGroup.add([this.logo, this.home_desc]);
  720. // 退出按钮
  721. this.lose = this.add.image(0, 0, "lose");
  722. this.lose.setPosition(width - this.lose.width - 30, height - this.lose.height - 30);
  723. this.lose.setInteractive();
  724. // 绑定点击事件
  725. this.lose.on('pointerdown', () => this.getExit());
  726. //动画效果,接着展示西瓜
  727. this.tweens.add({
  728. targets: this.homeGroup,
  729. y: 0,
  730. duration: 400,
  731. ease: 'Sine.InOut',
  732. onComplete: () => this.fruitAnim()
  733. });
  734. }
  735. fruitAnim() {
  736. // 每次创建全新的容器,避免复用旧实例
  737. this.sandiaGroup = this.add.container(0, 0); // 先置0,后续重新计算
  738. // 西瓜组初始位置:基于当前窗口尺寸动态计算(核心修复)
  739. this.sandiaGroup.setPosition(width / 2, height / 2); // 强制设置位置
  740. //圆圈
  741. this.new_game = this.add.sprite(0, 0, "new-game");
  742. this.new_game.setOrigin(0.5, 0.5);
  743. //西瓜
  744. this.sandia = new Fruit({ scene: this, key: "sandia" });
  745. this.sandiaGroup.add([this.new_game, this.sandia.getSprite()]);
  746. //动画效果,接着开放鼠标事件
  747. this.tweens.add({
  748. targets: this.sandiaGroup,
  749. scale: 1,
  750. duration: 500,
  751. ease: 'Linear.None',
  752. onComplete: () => this.allowBlade()
  753. });
  754. }
  755. updateRotate() {
  756. //西瓜外框圆圈图片旋转
  757. if (this.new_game) {
  758. this.new_game.rotation += this.newGameRotateSpeed * 0.016;
  759. }
  760. if (this.sandia && this.sandia.getSprite()) {
  761. this.sandia.getSprite().rotation += this.sandiaRotateSpeed * 0.016;
  762. }
  763. }
  764. allowBlade() {
  765. this.blade.enable();
  766. }
  767. startGame() {
  768. // this.start = true;
  769. // // 隐藏主界面元素
  770. // this.tweens.add({
  771. // targets: this.homeGroup,
  772. // y: -height,
  773. // duration: 200,
  774. // ease: 'Sine.InOut'
  775. // });
  776. // // 隐藏按钮
  777. // this.new_game.destroy();
  778. // this.lose.destroy();
  779. // // 切开西瓜
  780. // const deg = this.blade.collideDeg();
  781. // this.sandia.half(deg);
  782. this.start = true; // 先禁用立即切换
  783. const deg = this.blade.collideDeg();
  784. this.sandia.half(deg); // 切开西瓜,生成两半
  785. // 延迟1秒(1000ms)后再切换场景,等待动画展示
  786. setTimeout(() => {
  787. this.gotoNextScene();
  788. }, 1000);
  789. }
  790. gotoNextScene() {
  791. this.resetScene();
  792. this.scene.start("play");
  793. }
  794. getExit() {
  795. console.log("退出");
  796. router.push({ path: '/game' });
  797. }
  798. resetScene() {
  799. this.sandia = null;
  800. this.start = false;
  801. // 新增:销毁西瓜容器,避免残留
  802. if (this.sandiaGroup) {
  803. this.sandiaGroup.destroy(); // 销毁容器及其子元素
  804. this.sandiaGroup = null; // 置空引用
  805. }
  806. }
  807. }
  808. // 游戏场景
  809. class PlayScene extends Phaser.Scene {
  810. constructor() {
  811. super('play');
  812. this.bg = null;
  813. this.blade = null;
  814. this.fruits = [];
  815. this.score = 0;
  816. this.playing = false; // 改为false,在初始化方法中设置为true
  817. this.bombExplode = false;
  818. this.lostCount = 0;
  819. this.scoreImage = null;
  820. this.best = null;
  821. this.scoreText = null;
  822. this.xxxGroup = null;
  823. this.x = null;
  824. this.xx = null;
  825. this.xxx = null;
  826. this.gravity = 600;
  827. }
  828. create() {
  829. // 物理系统
  830. this.physics.world.gravity.y = this.gravity;
  831. // 背景
  832. // this.bg = this.add.image(0, 0, 'background');
  833. // this.bg.setScale(wRatio, hRatio);
  834. // this.bg.setPosition(width / 2, height / 2);
  835. // this.bg.setOrigin(0, 0);
  836. this.bg = this.add.image(0, 0, "background");
  837. // 设置背景图铺满整个游戏区域
  838. this.bg.displayWidth = width;
  839. this.bg.displayHeight = height;
  840. // 设置背景图原点为左上角
  841. this.bg.setOrigin(0, 0);
  842. // 刀光
  843. this.blade = new Blade({
  844. scene: this
  845. });
  846. this.blade.enable();
  847. // 初始化UI
  848. this.scoreAnim();
  849. this.scoreTextAnim();
  850. this.bestAnim();
  851. this.xxxAnim();
  852. // 添加调试信息
  853. console.log("PlayScene created");
  854. // 调用初始化方法,而不是直接开始生成水果
  855. this.initGame();
  856. }
  857. initGame() {
  858. // 重置游戏状态
  859. this.fruits = [];
  860. this.score = 0;
  861. this.lostCount = 0;
  862. this.bombExplode = false;
  863. this.scoreText.setText(this.score.toString());
  864. // 重置失去计数UI
  865. if (this.xxxGroup) {
  866. this.xxxGroup.removeAll(true);
  867. this.x = this.add.image(0, 0, 'x');
  868. this.xx = this.add.image(22, 0, 'xx');
  869. this.xxx = this.add.image(49, 0, 'xxx');
  870. this.x.setOrigin(0, 0);
  871. this.xx.setOrigin(0, 0);
  872. this.xxx.setOrigin(0, 0);
  873. this.xxxGroup.add([this.x, this.xx, this.xxx]);
  874. }
  875. // 开始游戏
  876. this.playing = true;
  877. console.log("Game initialized and started");
  878. // 延迟一点时间再生成第一个水果,让玩家有准备
  879. this.time.delayedCall(1000, () => {
  880. this.startFruit();
  881. console.log("First fruit spawned");
  882. });
  883. }
  884. update() {
  885. // 如果游戏未开始,不执行任何操作
  886. if (!this.playing) return;
  887. // 检查是否有水果出界
  888. if (!this.bombExplode) {
  889. for (let i = this.fruits.length - 1; i >= 0; i--) {
  890. const fruit = this.fruits[i];
  891. const sprite = fruit.getSprite();
  892. if (sprite && !sprite.active) continue;
  893. if (sprite && (
  894. sprite.y > height + 100 ||
  895. sprite.x < -100 ||
  896. sprite.x > width + 100
  897. )) {
  898. if (fruit.isFruit) {
  899. this.onOut(fruit);
  900. }
  901. sprite.destroy();
  902. this.fruits.splice(i, 1);
  903. }
  904. }
  905. }
  906. // 如果没有水果且游戏进行中,生成新水果
  907. if (this.playing && this.fruits.length === 0 && !this.bombExplode) {
  908. this.startFruit();
  909. }
  910. // 更新刀光
  911. this.blade.update();
  912. // 检查碰撞
  913. if (!this.bombExplode) {
  914. this.fruits.forEach((fruit, i) => {
  915. if (fruit.getSprite() && fruit.getSprite().active) {
  916. this.blade.checkCollide(
  917. fruit.getSprite(),
  918. () => {
  919. if (fruit.isFruit) {
  920. this.onKill(fruit);
  921. this.fruits.splice(i, 1);
  922. } else {
  923. this.onBomb(fruit);
  924. }
  925. }
  926. );
  927. }
  928. });
  929. }
  930. }
  931. scoreAnim() {
  932. this.scoreImage = this.add.image(-100, 8, 'score');
  933. this.scoreImage.setOrigin(0, 0);
  934. this.tweens.add({
  935. targets: this.scoreImage,
  936. x: 8,
  937. duration: 300,
  938. ease: 'Sine.InOut'
  939. });
  940. }
  941. bestAnim() {
  942. this.best = this.add.image(-100, 52, 'best');
  943. this.best.setOrigin(0, 0);
  944. this.tweens.add({
  945. targets: this.best,
  946. x: 5,
  947. duration: 300,
  948. ease: 'Sine.InOut'
  949. });
  950. }
  951. scoreTextAnim() {
  952. this.scoreText = this.add.bitmapText(-100, 40, 'number', this.score.toString(), 32);
  953. this.scoreText.setOrigin(0, 0);
  954. this.tweens.add({
  955. targets: this.scoreText,
  956. x: 75,
  957. duration: 300,
  958. ease: 'Sine.InOut'
  959. });
  960. }
  961. xxxAnim() {
  962. this.xxxGroup = this.add.container(width + 100, 5);
  963. this.x = this.add.image(0, 0, 'x');
  964. this.xx = this.add.image(22, 0, 'xx');
  965. this.xxx = this.add.image(49, 0, 'xxx');
  966. this.xxxGroup.add([this.x, this.xx, this.xxx]);
  967. this.tweens.add({
  968. targets: this.xxxGroup,
  969. x: width - 86,
  970. duration: 300,
  971. ease: 'Sine.InOut'
  972. });
  973. }
  974. startFruit() {
  975. const number = Math.floor(mathTool.randomMinMax(1, 3));
  976. const hasBomb = Math.random() > 0.9; // 炸弹概率
  977. const bombIndex = hasBomb ? Math.floor(Math.random() * number) : -1;
  978. for (let i = 0; i < number; i++) {
  979. if (i === bombIndex) {
  980. this.fruits.push(this.randomFruit(false));
  981. } else {
  982. this.fruits.push(this.randomFruit(true));
  983. }
  984. }
  985. }
  986. randomFruit(isFruit) {
  987. const fruitArray = ["apple", "banana", "basaha", "peach", "sandia"];
  988. const index = Math.floor(Math.random() * fruitArray.length);
  989. const x = mathTool.randomPosX();
  990. const y = mathTool.randomPosY();
  991. const vx = mathTool.randomVelocityX(x);
  992. const vy = mathTool.randomVelocityY();
  993. let fruit;
  994. if (isFruit) {
  995. fruit = new Fruit({
  996. scene: this,
  997. key: fruitArray[index],
  998. x: x,
  999. y: y
  1000. });
  1001. } else {
  1002. fruit = new Bomb({
  1003. scene: this,
  1004. x: x,
  1005. y: y
  1006. });
  1007. }
  1008. console.log("isFruitisFruitisFruit", isFruit)
  1009. fruit.isFruit = isFruit;
  1010. const sprite = fruit.getSprite();
  1011. if (sprite.body) {
  1012. sprite.body.velocity.x = vx;
  1013. sprite.body.velocity.y = vy;
  1014. sprite.body.gravity.y = this.gravity;
  1015. }
  1016. return fruit;
  1017. }
  1018. onOut(fruit) {
  1019. const sprite = fruit.getSprite();
  1020. let x, y;
  1021. // 确定失去标记的位置
  1022. if (sprite.y > height) {
  1023. x = sprite.x;
  1024. y = height - 30;
  1025. } else if (sprite.x < 0) {
  1026. x = 30;
  1027. y = sprite.y;
  1028. } else {
  1029. x = width - 30;
  1030. y = sprite.y;
  1031. }
  1032. // 创建失去标记动画
  1033. const lose = this.add.sprite(x, y, 'lose');
  1034. lose.setOrigin(0.5, 0.5);
  1035. lose.setScale(0);
  1036. const tweenShow = this.tweens.add({
  1037. targets: lose,
  1038. scale: 1,
  1039. duration: 300,
  1040. ease: 'Sine.InOut',
  1041. paused: true
  1042. });
  1043. const tweenHide = this.tweens.add({
  1044. targets: lose,
  1045. scale: 0,
  1046. duration: 300,
  1047. ease: 'Sine.InOut',
  1048. paused: true,
  1049. delay: 1000
  1050. });
  1051. this.tweens.chain({
  1052. targets: lose,
  1053. tweens: [
  1054. {
  1055. scale: 1,
  1056. duration: 300,
  1057. ease: 'Sine.InOut'
  1058. },
  1059. {
  1060. scale: 0,
  1061. duration: 300,
  1062. ease: 'Sine.InOut',
  1063. delay: 1000,
  1064. onComplete: () => {
  1065. lose.destroy();
  1066. }
  1067. }
  1068. ]
  1069. });
  1070. tweenShow.play();
  1071. tweenHide.on('complete', () => {
  1072. lose.destroy();
  1073. });
  1074. this.lostCount++;
  1075. this.loseCount();
  1076. }
  1077. onKill(fruit) {
  1078. const deg = this.blade.collideDeg();
  1079. fruit.half(deg, true);
  1080. this.score++;
  1081. this.scoreText.setText(this.score.toString());
  1082. }
  1083. onBomb(bomb) {
  1084. this.bombExplode = true;
  1085. // 屏幕震动效果
  1086. this.shakeScreen();
  1087. // 停止所有水果的物理运动
  1088. this.fruits.forEach(fruit => {
  1089. if (fruit.getSprite() && fruit.getSprite().body) {
  1090. fruit.getSprite().body.setVelocity(0);
  1091. fruit.getSprite().body.setGravity(0);
  1092. }
  1093. });
  1094. // 炸弹爆炸
  1095. bomb.explode(
  1096. () => {
  1097. // 白屏显示时的回调:销毁所有水果
  1098. this.fruits.forEach(fruit => {
  1099. if (fruit.getSprite()) {
  1100. fruit.getSprite().destroy();
  1101. }
  1102. });
  1103. this.fruits = [];
  1104. },
  1105. () => {
  1106. // 爆炸完成后的回调
  1107. this.gameOver();
  1108. }
  1109. );
  1110. }
  1111. // 添加屏幕震动方法
  1112. shakeScreen() {
  1113. // 获取主相机
  1114. const camera = this.cameras.main;
  1115. // 保存相机初始位置
  1116. const startX = camera.x;
  1117. const startY = camera.y;
  1118. // 震动持续时间(ms)
  1119. const duration = 500;
  1120. // 震动强度
  1121. const intensity = 8;
  1122. // 震动频率控制
  1123. const frequency = 20;
  1124. // 计算震动次数
  1125. const shakes = duration / frequency;
  1126. let shakeCount = 0;
  1127. // 创建震动定时器
  1128. const shakeInterval = setInterval(() => {
  1129. if (shakeCount < shakes) {
  1130. // 随机生成震动偏移量
  1131. const offsetX = Phaser.Math.Between(-intensity, intensity);
  1132. const offsetY = Phaser.Math.Between(-intensity, intensity);
  1133. // 应用震动
  1134. camera.setPosition(startX + offsetX, startY + offsetY);
  1135. shakeCount++;
  1136. } else {
  1137. // 震动结束,恢复相机位置
  1138. clearInterval(shakeInterval);
  1139. camera.setPosition(startX, startY);
  1140. }
  1141. }, frequency);
  1142. }
  1143. loseCount() {
  1144. if (this.lostCount === 1) {
  1145. this.lostAnim(this.x, 'xf');
  1146. } else if (this.lostCount === 2) {
  1147. this.lostAnim(this.xx, 'xxf');
  1148. } else if (this.lostCount >= 3) {
  1149. this.lostAnim(this.xxx, 'xxxf');
  1150. this.gameOver();
  1151. }
  1152. }
  1153. lostAnim(removeObj, addKey) {
  1154. removeObj.destroy();
  1155. const newObj = this.add.sprite(removeObj.x, removeObj.y, addKey);
  1156. newObj.setOrigin(0, 0);
  1157. newObj.setScale(0);
  1158. this.xxxGroup.add(newObj);
  1159. this.tweens.add({
  1160. targets: newObj,
  1161. scale: 1,
  1162. duration: 300,
  1163. ease: 'Sine.InOut'
  1164. });
  1165. }
  1166. gameOver() {
  1167. this.playing = false;
  1168. // 1. 确保所有其他元素停止更新,避免干扰
  1169. this.blade.allowBlade = false; // 禁用刀光
  1170. // 2. 创建game-over图片,并设置最高层级
  1171. const gameOverSprite = this.add.sprite(width / 2, height / 2, 'game-over');
  1172. gameOverSprite.setOrigin(0.5, 0.5);
  1173. gameOverSprite.setScale(0);
  1174. gameOverSprite.setDepth(1000); // 设置最高层级,确保不被覆盖
  1175. // 3. 优化入场动画,确保平滑显示
  1176. this.tweens.add({
  1177. targets: gameOverSprite,
  1178. scale: 1,
  1179. duration: 500, // 延长动画时间,确保可见
  1180. ease: 'Elastic.Out', // 更明显的弹性动画,增强视觉效果
  1181. onComplete: () => {
  1182. // 动画完成后再设置自动返回,确保用户有足够时间看到画面
  1183. setTimeout(() => {
  1184. console.log('游戏结束,返回首页');
  1185. this.scene.start('main');
  1186. }, 1000); // 延长至3秒,给用户足够时间观察
  1187. }
  1188. });
  1189. // 4. 支持点击立即返回,提升交互体验
  1190. gameOverSprite.setInteractive();
  1191. gameOverSprite.on('pointerdown', () => {
  1192. console.log('点击返回首页');
  1193. this.scene.start('main');
  1194. });
  1195. }
  1196. }
  1197. // 初始化游戏
  1198. onMounted(() => {
  1199. // 获取容器尺寸
  1200. const container = document.getElementById('game');
  1201. // 初始化工具类
  1202. mathTool.init();
  1203. // 创建游戏实例
  1204. game = new Phaser.Game({
  1205. type: Phaser.CANVAS,
  1206. width: width,
  1207. height: height,
  1208. parent: 'game',
  1209. scene: [BootScene, PreloadScene, MainScene, PlayScene],
  1210. physics: {
  1211. default: 'arcade',
  1212. arcade: {
  1213. debug: false
  1214. }
  1215. }
  1216. });
  1217. });
  1218. </script>
  1219. <style lang="scss" scoped></style>