123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722 |
- <template>
- <div class="game-container">
- <div id="game"></div>
- <canvas ref="canvasRef" :width="clientObj.width" :height="clientObj.height"
- style="position:fixed;left: 0; top: 0;"></canvas>
- </div>
- </template>
- <script setup name="Fruit" lang="ts">
- import { onMounted, ref } from 'vue';
- import Phaser from 'phaser';
- import { initSpeech, speckText, playMusic, controlMusic, speckCancel, chineseNumber } from '@/utils/speech';
- const { proxy } = getCurrentInstance() as any;
- const router = useRouter();
- const emit = defineEmits(['confirmExit']);
- const gameRef = ref(null); // 用于保存游戏实例的ref
- const canvasRef = ref(null);
- const data = reactive<any>({
- bodyposeData: {},//姿态信息
- clientObj: {},//浏览器对象
- boxes: [],//四个点坐标
- proportion: null,//人框和屏幕比例
- scaleRatio: 2.2,//素材比例
- });
- const { bodyposeData, clientObj, boxes, proportion, scaleRatio } = toRefs(data);
- /**
- * 初始化
- */
- const getInit = (e: any) => {
- let arr = e.data.result.keypoints;
- let result = [];
- for (let i = 0; i < arr.length; i += 3) {
- result.push(arr.slice(i, i + 2));
- }
- // console.log("result", result)
- bodyposeData.value = result;
- if (boxes.value.length == 0) {
- speckText("识别成功");
- proxy?.$modal.msgWarning(`识别成功`);
- let arr = e.data.result.boxes;
- boxes.value = [{ x: arr[0], y: arr[3] }, { x: arr[0], y: arr[1] }, { x: arr[2], y: arr[1] }, { x: arr[2], y: arr[3] }]
- proportion.value = (clientObj.value.height / (arr[3] - arr[1])).toFixed(2);
- }
- getCanvas();
- };
- /**
- * 绘图
- */
- const getCanvas = () => {
- const canvas: any = canvasRef.value;
- const ctx = canvas.getContext('2d');
- // 清空整个画布
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- // 保存当前状态
- ctx.save();
- function calculateOffset(a: any, b: any) {
- return {
- x: b.x - a.x,
- y: b.y - a.y
- };
- }
- const pointA = { x: clientObj.value.width / 2, y: clientObj.value.height / 2 };
- const pointB = { x: (boxes.value[2].x + boxes.value[0].x) / 2, y: (boxes.value[3].y + boxes.value[1].y) / 2 };
- const offset = calculateOffset(pointA, pointB);
- ctx.translate(-offset.x, -offset.y);
- // console.log("Canvas分辨率", clientObj.value);
- // console.log("人体图片四点坐标", boxes.value)
- // console.log("Canvas中心", pointA);
- // console.log("人体中心", pointB);
- // console.log("offset", offset)
- // console.log("proportion.value",proportion.value)
- const originalPoints = bodyposeData.value;
- // 计算缩放后坐标
- const postData = originalPoints.map((point: any) => {
- const newX = (point[0] - pointB.x) * proportion.value + pointB.x;
- const newY = (point[1] - pointB.y) * proportion.value + pointB.y;
- return [newX, newY];
- });
- // console.log("原始坐标:", originalPoints);
- // console.log("缩放后坐标:", postData);
- //externalMethod(postData[10][0] - offset.x, postData[10][1] - offset.y)
- externalRightHandMethod(postData[10][0] - offset.x, postData[10][1] - offset.y)
- externalLeftHandMethod(postData[9][0] - offset.x, postData[9][1] - offset.y)
- //绘制头部
- const point1 = { x: postData[4][0], y: postData[4][1] };
- const point2 = { x: postData[3][0], y: postData[3][1] };
- // 计算椭圆参数
- const centerX = (point1.x + point2.x) / 2; // 椭圆中心X
- const centerY = (point1.y + point2.y) / 2; // 椭圆中心Y
- const distance = Math.sqrt(
- Math.pow(point2.x - point1.x, 2) +
- Math.pow(point2.y - point1.y, 2)
- ); // 两个焦点之间的距离
- const radiusX = distance * 0.5; // 水平半径(可调整)
- const radiusY = distance * 0.6; // 垂直半径(可调整)
- // 1. 绘制填充椭圆
- ctx.beginPath();
- ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, 0, Math.PI * 2);
- ctx.fillStyle = 'red'; // 填充颜色
- ctx.fill(); // 填充
- // 2. 绘制边框
- ctx.strokeStyle = 'red';
- ctx.lineWidth = 5;
- ctx.stroke();
- // 绘制每个点
- postData.forEach((point: any, index: number) => {
- //眼睛鼻子不显示
- if (![0, 1, 2].includes(index)) {
- const [x, y] = point;
- ctx.beginPath();
- ctx.arc(x, y, 5, 0, Math.PI * 2); // 绘制半径为5的圆点
- ctx.fillStyle = 'red';
- ctx.fill();
- ctx.lineWidth = 1;
- ctx.stroke();
- }
- });
- // 根据点关系连线
- const arr = [[10, 8], [8, 6], [6, 5], [5, 7], [7, 9], [6, 12], [5, 11], [12, 11], [12, 14], [14, 16], [11, 13], [13, 15]]
- arr.forEach((point: any) => {
- let index1 = point[0];
- let index2 = point[1];
- //连线
- const dian1 = { x: postData[index1][0], y: postData[index1][1] };
- const dian2 = { x: postData[index2][0], y: postData[index2][1] };
- // 绘制连线
- ctx.beginPath();
- ctx.moveTo(dian1.x, dian1.y); // 起点
- ctx.lineTo(dian2.x, dian2.y); // 终点
- ctx.strokeStyle = 'red'; // 线条颜色
- ctx.lineWidth = 3; // 线条宽度
- ctx.stroke(); // 描边
- });
- ctx.restore(); // 恢复状态
- };
- // 游戏容器和尺寸相关
- 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, 400);
- } else if (posX >= 0 && posX < width / 2) {
- return this.randomMinMax(0, 400);
- } else if (posX >= width / 2 && posX < width) {
- return this.randomMinMax(-400, 0);
- } else {
- return this.randomMinMax(-400, -100);
- }
- },
- randomVelocityY() {
- const myH = height - 600;
- // 调整范围为原速度的70%左右(根据需要微调)
- return this.randomMinMax(-630 - myH * 0.5, -595 - myH * 0.5);
- },
- 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 boomSound = this.game.sound.add('boom');
- boomSound.play({
- volume: 0.7 // 炸弹音效可稍大,增强冲击力
- });
- const lights = [];
- const startDeg = Math.floor(Math.random() * 360);
- // 保存外部this上下文
- const self = this;
- // 创建直射光芒效果
- const maxRays = 25; // 光芒数量
- const rayLength = 400; // 光芒长度
- const rayWidth = 200; // 光芒宽度
- for (let i = 0; i < maxRays; i++) {
- const angle = (i / maxRays) * Math.PI * 2; // 计算每个光芒的角度
- const light = self.game.add.graphics({
- x: self.sprite.x,
- y: self.sprite.y
- });
- // 设置初始透明度和颜色
- const alpha = 0.8 - (Math.random() * 0.3); // 随机透明度,增加自然感
- const hue = 45 - (Math.random() * 20); // 随机色调,从黄色到白色
- const color = Phaser.Display.Color.HSVToRGB(hue / 360, 0.8, 1).color;
- light.alpha = alpha;
- light.fillStyle(color, 1);
- // 创建细长三角形
- light.beginPath();
- light.moveTo(0, 0); // 起点在中心
- light.lineTo(
- Math.cos(angle) * rayLength,
- Math.sin(angle) * rayLength
- ); // 终点在外围
- light.lineTo(
- Math.cos(angle + Math.PI + 0.1) * (rayWidth / 2),
- Math.sin(angle + Math.PI + 0.1) * (rayWidth / 2)
- ); // 底部左点
- light.closePath();
- light.fill();
- light.setDepth(2000);
- lights.push(light);
- // 添加脉动动画效果
- self.game.tweens.add({
- targets: light,
- alpha: alpha * 0.5,
- duration: 800 + Math.random() * 400,
- yoyo: true,
- repeat: -1,
- ease: 'Sine.easeInOut'
- });
- }
- // 2. 打乱灯光顺序
- mathTool.shuffle(lights);
- // 3. 创建白屏元素
- const whiteScreen = self.game.add.graphics({
- x: 0,
- y: 0
- });
- whiteScreen.fillStyle(0xffffff, 0);
- whiteScreen.fillRect(0, 0, width, height);
- whiteScreen.setDepth(3000);
- whiteScreen.alpha = 0;
- // 4. 按顺序执行灯光动画
- function playChainAnimations(index) {
- if (index >= lights.length) {
- playWhiteScreenAnimation();
- return;
- }
- const light = lights[index];
- self.game.tweens.add({
- targets: light,
- alpha: { from: 0, to: 1, from: 0 },
- scale: { from: 1, to: 2 },
- duration: 100,
- onComplete: () => {
- light.destroy();
- playChainAnimations(index + 1);
- }
- });
- }
- function playWhiteScreenAnimation() {
- self.game.tweens.add({
- targets: whiteScreen,
- alpha: { from: 0, to: 1, to: 0 },
- duration: 50,
- onUpdate: (tween) => {
- if (tween.progress >= 0.25 && tween.progress <= 0.3) {
- onWhite();
- }
- },
- onComplete: () => {
- whiteScreen.destroy();
- if (typeof onComplete === 'function') {
- onComplete();
- }
- }
- });
- }
- // 开始执行第一个灯光动画
- playChainAnimations(0);
- // 新增:在爆炸动画开始后立即隐藏炸弹,动画结束后销毁
- this.sprite.visible = false; // 先隐藏视觉元素
- // 在爆炸完成回调中销毁炸弹
- const originalOnComplete = onComplete;
- onComplete = () => {
- // 销毁炸弹容器及其内部所有元素
- if (this.sprite) {
- this.sprite.destroy();
- }
- if (this.bombEmit) {
- this.bombEmit.destroy();
- }
- if (this.bombSmoke) {
- this.bombSmoke.destroy();
- }
- // 执行原有的完成回调
- if (typeof originalOnComplete === 'function') {
- originalOnComplete();
- }
- };
- }
- 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.sprite.setScale(scaleRatio.value);
- // 物理属性
- 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', {
- visible: false // 初始隐藏
- });
- }
- // 水果类中的half方法
- half(deg, shouldEmit = false) {
- // 计算世界坐标
- const transform = this.sprite.getWorldTransformMatrix();
- const worldPos = new Phaser.Math.Vector2(
- transform.getX(0, 0),
- transform.getY(0, 0)
- );
- // 播放切割音效
- if (shouldEmit) { // 仅在需要发射粒子时播放(即有效切割时)
- const splatterSound = this.game.sound.add('splatter');
- splatterSound.play({
- volume: 0.5 // 可调节音量,范围0-1
- });
- }
- // 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.setScale(scaleRatio.value);
- 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.setScale(scaleRatio.value);
- 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, handType) {// 新增handType区分左右手
- this.env = envConfig;
- this.game = envConfig.scene;
- this.points = []; // 记录鼠标轨迹点
- this.graphics = null;
- this.POINTLIFETIME = 200; // 轨迹点的生命周期(毫秒)
- this.allowBlade = false;
- this.lastPoint = null; // 上一个记录的点
- this.moveThreshold = 5; // 鼠标移动超过这个距离才记录新点(避免密集冗余)
- this.handType = handType; // 存储"left"或"right"
- this.color = handType === 'left' ? 0x00FFFF : 0xFFFF00; // 左手蓝色,右手黄色(示例)
- this.init();
- }
- init() {
- this.graphics = this.game.add.graphics({
- x: 0,
- y: 0
- });
- // 根据手型设置绘图层级(可选)
- this.graphics.setDepth(this.handType === 'left' ? 1001 : 1002);
- // 监听鼠标移动事件(可选,也可在update中处理)
- this.game.input.on('pointermove', (pointer) => {
- //console.log("11111", pointer)
- if (this.allowBlade) {
- this.handleMouseMove(pointer);
- }
- });
- }
- // 处理鼠标移动:记录轨迹点
- handleMouseMove(pointer) {
- if (!this.allowBlade) return;
- const point = {
- x: pointer.x,
- y: pointer.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 > this.moveThreshold) {
- this.lastPoint = point;
- this.points.push(point);
- }
- }
- }
- update() {
- if (!this.allowBlade) return;
- this.graphics.clear();
- const now = Date.now();
- this.points = this.points.filter(point => now - point.time < this.POINTLIFETIME);
- if (this.points.length > 0) {
- const bladePoints = mathTool.generateBlade(this.points);
- if (bladePoints.length > 0) {
- this.graphics.fillStyle(this.color, 0.8); // 使用当前刀光的颜色
- this.graphics.beginPath();
- this.graphics.moveTo(bladePoints[0].x, bladePoints[0].y);
- bladePoints.forEach((point, i) => i > 0 && this.graphics.lineTo(point.x, point.y));
- this.graphics.closePath();
- this.graphics.fill();
- }
- }
- }
- // 碰撞检测:去掉“鼠标按下”的限制,只要有轨迹就检测
- checkCollide(sprite, onCollide) {
- if (this.allowBlade && 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('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');
- this.load.audio('splatter', 'static/images/fruit/splatter.mp3');
- this.load.audio('boom', 'static/images/fruit/boom.mp3');
- this.load.audio('throw', 'static/images/fruit/throw.mp3');
- }
- 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;
- this.leftBlade = null; // 左手刀光
- this.rightBlade = null; // 右手刀光
- }
- 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();
- // 初始化左右手刀光
- this.leftBlade = new Blade({ scene: this }, 'left');
- this.rightBlade = new Blade({ scene: this }, 'right');
- }
- 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();
- }
- );
- }
- // 分别更新左右手刀光
- this.leftBlade.update();
- this.rightBlade.update();
- // 分别检测左右手刀光与水果的碰撞
- if (this.sandia && this.sandia.getSprite() && !this.start) {
- // 右手碰撞检测
- this.rightBlade.checkCollide(
- this.sandia.getSprite(),
- () => this.startGame()
- );
- // 左手碰撞检测(可选:允许左手也能启动游戏)
- this.leftBlade.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.homeGroup.add([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,后续重新计算
- // 西瓜组初始位置:基于当前窗口尺寸动态计算(核心修复)
- this.sandiaGroup.setPosition(width / 2, height / 2); // 强制设置位置
- //圆圈
- this.new_game = this.add.sprite(0, 0, "new-game");
- this.new_game.setOrigin(0.5, 0.5);
- this.new_game.setScale(scaleRatio.value);
- //西瓜
- 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();
- // 同时启用左右手刀光
- this.leftBlade.enable();
- this.rightBlade.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();
- // 播放初始西瓜切割音效
- const splatterSound = this.sound.add('splatter');
- splatterSound.play({
- volume: 0.5 // 与普通水果切割音量保持一致
- });
- this.sandia.half(deg); // 切开西瓜,生成两半
- // 延迟1秒(1000ms)后再切换场景,等待动画展示
- setTimeout(() => {
- this.gotoNextScene();
- }, 1000);
- }
- gotoNextScene() {
- this.resetScene();
- this.scene.start("play");
- }
- getExit() {
- console.log("退出");
- // router.push({ path: '/game' });
- emit('confirmExit', {});
- }
- 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 = 200;
- this.leftBlade = null;
- this.rightBlade = null;
- }
- 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();
- // 初始化左右手刀光
- this.leftBlade = new Blade({ scene: this }, 'left');
- this.rightBlade = new Blade({ scene: this }, 'right');
- this.leftBlade.enable();
- this.rightBlade.enable();
- }
- 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();
- // 分别更新左右手刀光
- this.leftBlade.update();
- this.rightBlade.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);
- }
- }
- );
- // 右手碰撞
- this.rightBlade.checkCollide(
- fruit.getSprite(),
- () => this.handleCollision(fruit, i)
- );
- // 左手碰撞
- this.leftBlade.checkCollide(
- fruit.getSprite(),
- () => this.handleCollision(fruit, i)
- );
- }
- });
- }
- }
- // 统一处理碰撞逻辑(提取重复代码)
- handleCollision(fruit, index) {
- if (fruit.isFruit) {
- this.onKill(fruit);
- this.fruits.splice(index, 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() {
- // 根据分数动态调整数量:分数越高,生成越多水果
- let min = 1;
- let max = 1;
- if (this.score >= 0 && this.score < 30) {
- min = 1;
- max = 1;
- } else if (this.score >= 30 && this.score < 60) {
- min = 1;
- max = 2;
- } else {
- min = 2;
- max = 3;
- }
- const number = Math.floor(mathTool.randomMinMax(min, max + 1)); // +1是因为randomMinMax的max是 exclusive
- const hasBomb = Math.random() > 0.9;
- 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.shakeScreen();
- // 停止所有水果的物理运动
- 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();
- }
- );
- }
- // 添加屏幕震动方法
- shakeScreen() {
- // 获取主相机
- const camera = this.cameras.main;
- // 保存相机初始位置
- const startX = camera.x;
- const startY = camera.y;
- // 震动持续时间(ms)
- const duration = 2000;
- // 震动强度
- const intensity = 8;
- // 震动频率控制
- const frequency = 20;
- // 计算震动次数
- const shakes = duration / frequency;
- let shakeCount = 0;
- // 创建震动定时器
- const shakeInterval = setInterval(() => {
- if (shakeCount < shakes) {
- // 随机生成震动偏移量
- const offsetX = Phaser.Math.Between(-intensity, intensity);
- const offsetY = Phaser.Math.Between(-intensity, intensity);
- // 应用震动
- camera.setPosition(startX + offsetX, startY + offsetY);
- shakeCount++;
- } else {
- // 震动结束,恢复相机位置
- clearInterval(shakeInterval);
- camera.setPosition(startX, startY);
- }
- }, frequency);
- }
- 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');
- }, 1000); // 延长至3秒,给用户足够时间观察
- }
- });
- // 4. 支持点击立即返回,提升交互体验
- gameOverSprite.setInteractive();
- gameOverSprite.on('pointerdown', () => {
- console.log('点击返回首页');
- this.scene.start('main');
- });
- }
- }
- // 外部方法(如Vue组件中的某个按钮点击事件)
- const externalMethod = (autoX, autoY) => {
- // 1. 获取游戏实例(从之前保存的gameRef中)
- const game = gameRef.value;
- if (!game) {
- console.error("游戏未初始化");
- return;
- }
- // 2. 获取目标场景(根据当前活跃场景选择'main'或'play')
- // 例如:获取PlayScene(场景key为'play')
- const currentScene = getActiveScene();
- const targetScene = game.scene.getScene(currentScene);
- // 若当前在主场景,可改为 game.scene.getScene('main')
- // 3. 检查场景中的Blade实例是否存在且已启用
- if (!targetScene || !targetScene.blade || !targetScene.blade.allowBlade) {
- console.error("Blade实例未初始化或未启用");
- return;
- }
- // 4. 构造模拟的pointer参数(包含x和y)
- const mockPointer = {
- x: autoX, // 外部传入的X坐标
- y: autoY // 外部传入的Y坐标
- };
- // 5. 调用Blade的handleMouseMove方法
- targetScene.blade.handleMouseMove(mockPointer);
- };
- // 新增:区分左右手的输入方法
- const externalLeftHandMethod = (x, y) => {
- const game = gameRef.value;
- if (!game) return;
- const currentScene = getActiveScene();
- const targetScene = game.scene.getScene(currentScene);
- if (targetScene && targetScene.leftBlade && targetScene.leftBlade.allowBlade) {
- targetScene.leftBlade.handleMouseMove({ x, y });
- }
- };
- const externalRightHandMethod = (x, y) => {
- const game = gameRef.value;
- if (!game) return;
- const currentScene = getActiveScene();
- const targetScene = game.scene.getScene(currentScene);
- if (targetScene && targetScene.rightBlade && targetScene.rightBlade.allowBlade) {
- targetScene.rightBlade.handleMouseMove({ x, y });
- }
- };
- const getActiveScene = () => {
- const game = gameRef.value; // 获取游戏实例
- if (!game) return null;
- // 获取所有活跃场景(通常只有一个)
- const activeScenes = game.scene.getScenes(true);
- // 返回第一个活跃场景(如果有)
- return activeScenes.length > 0 ? activeScenes[0].scene.key : null;
- };
- onBeforeMount(() => {
- clientObj.value = {
- width: document.documentElement.clientWidth,
- height: document.documentElement.clientHeight,
- }
- scaleRatio.value = clientObj.value.height / 480;
- });
- // 初始化游戏
- 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
- }
- }
- });
- gameRef.value = game;
- });
- onBeforeUnmount(() => {
- });
- //暴露给父组件用
- defineExpose({
- getInit
- })
- </script>
- <style lang="scss" scoped></style>
|