123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830 |
- <template>
- <div class="game-container">
- <div id="gameCanvas" class="game-canvas"></div>
- <canvas ref="canvasRef" :width="clientObj.width" :height="clientObj.height"
- style="position:fixed;left: 0; top: 0; z-index: 999;"></canvas>
- <!-- 游戏启动界面 -->
- <div v-if="currentScene === 'start'" class="gamestart">
- <img v-if="currentScene === 'start'" src="/static/images/football/game_start.jpg" class="start_bg" />
- <div class="btn rule" @click="showRules = true">查看规则</div>
- <div class="btn start" @click="startGame">踢腿开始游戏</div>
- <!-- 规则弹窗 -->
- <div v-if="showRules" class="ruleshadow">
- <div class="rulebox">
- <span class="x_rulebox" @click="showRules = false"></span>
- <h3>操作方法:</h3>
- <p>手指拖动控制人物带球奔跑的方向和速度。</p>
- <h3>道具说明:</h3>
- <div class="daoju">
- <div class="daoju_item">
- <span class="daoju_1"></span>
- <p>每收集一个,生命值+1;触碰障碍物,生命可再复活。</p>
- </div>
- <div class="daoju_item">
- <span class="daoju_2"></span>
- <p>每收集一个,能力值+1;扫除面前一切障碍,加速前进。</p>
- </div>
- <div class="daoju_item">
- <span class="daoju_3"></span>
- <p>触碰锥桶,生命值-1;成功绕过一次分数+1。</p>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 规则说明 -->
- <div v-if="currentScene === 'rule'" class="zhiyin">
- <div class="btn start" @click="continueGame">继续游戏</div>
- </div>
- <!-- 倒计时 -->
- <div v-if="currentScene === 'countdown'" class="daoju">
- <div class="daojishibox">
- <div class="daojishinum">{{ countdownNum }}</div>
- </div>
- </div>
- <!-- 游戏结束 -->
- <div v-if="currentScene === 'gameover'" class="gameend">
- <div v-if="currentScene === 'gameover'" class="game_fenshu">
- <span>{{ score }}</span>分
- </div>
- <div class="chenghao">恭喜你获得称号<p>{{ getTitle(score) }}</p>
- </div>
- <!-- <div class="game_ogain" @click="restartGame">返回游戏</div> -->
- </div>
- </div>
- </template>
- <script setup name="Football" lang="ts">
- import Phaser from 'phaser';
- import { onMounted, ref, reactive, onBeforeUnmount, watch } from 'vue';
- import { initSpeech, speckText, playMusic, controlMusic, speckCancel, chineseNumber } from '@/utils/speech';
- import { useWebSocket } from '@/utils/bodyposeWs';
- const { proxy } = getCurrentInstance() as any;
- const router = useRouter();
- const { bodyposeWs, startDevice, checkBodypose, openBodypose, terminateBodypose, suspendBodypose, resumeBodypose, getBodyposeState, closeWS } = useWebSocket();
- const canvasRef = ref(null);
- const data = reactive<any>({
- bodyposeData: {},//姿态信息
- bodyposeState: false,//姿态识别窗口状态
- parameter: {},//参数
- deviceInfo: {},//设备信息
- againNum: 0,//再次启动次数
- againTimer: null,//定时状态
- wsState: false,//WS状态
- clientObj: {},//浏览器对象
- boxes: [],//四个点坐标
- proportion: null,//人框和屏幕比例
- myThrow: 0,//0踢腿 1收腿
- myTimer: null,
- direction: null,//跑动
- });
- const { bodyposeData, bodyposeState, parameter, deviceInfo, againNum, againTimer, wsState, clientObj, boxes, proportion, myThrow, myTimer, direction } = toRefs(data);
- // 游戏状态管理
- const currentScene = ref('start');
- const showRules = ref(false);
- const countdownNum = ref(3);
- const score = ref(0);
- const game = ref(null);
- const gameConfig = ref(null);
- let scoreListener = null; // 存储事件监听器引用
- // 游戏常量
- const width = document.documentElement.clientWidth;
- const height = document.documentElement.clientHeight;
- const GAME_WIDTH = width;
- const GAME_HEIGHT = height;
- // 游戏资源
- const gameAssets = {
- images: [
- { key: 'gameStart', url: 'static/images/football/game_start.jpg' },
- { key: 'grass', url: 'static/images/football/Caopi.png' },
- { key: 'playerShoot', url: 'static/images/football/QiuyuanShooting.png' },
- { key: 'pile', url: 'static/images/football/Pile.png' },
- { key: 'jersey', url: 'static/images/football/Jersey.png' },
- { key: 'broom', url: 'static/images/football/Broom.png' },
- { key: 'goalkeeper', url: 'static/images/football/Goalkeeper.png' },
- { key: 'goal', url: 'static/images/football/Goal.png' },
- { key: 'ball', url: 'static/images/football/Ball.png' },
- { key: 'line', url: 'static/images/football/Line.png' },
- { key: 'line2', url: 'static/images/football/Line2.png' },
- { key: 'goalBackground', url: 'static/images/football/GoalBackGround.png' },
- { key: 'gameOver', url: 'static/images/football/gameover.png' }
- ],
- spritesheets: [
- {
- key: 'playerAnim',
- url: 'static/images/football/Qiuyuan.png',
- frameWidth: 92.5,
- frameHeight: 92.5
- },
- {
- key: 'ballAnim',
- url: 'static/images/football/Ball.png',
- frameWidth: 31,
- frameHeight: 31
- },
- {
- key: 'goalkeeperAnim',
- url: 'static/images/football/Goalkeeper.png',
- frameWidth: 55,
- frameHeight: 56
- },
- {
- key: 'playerCollision',
- url: 'static/images/football/QiuyuanCollision.png',
- frameWidth: 92.5,
- frameHeight: 92.5
- },
- {
- key: 'playerSuper',
- url: 'static/images/football/QiuyuanSuper.png',
- frameWidth: 92.5,
- frameHeight: 92.5
- },
- ]
- };
- // 初始化Phaser游戏
- const initGame = () => {
- // 游戏配置
- gameConfig.value = {
- type: Phaser.AUTO,
- width: GAME_WIDTH,
- height: GAME_HEIGHT,
- parent: 'gameCanvas',
- physics: {
- default: 'arcade',
- arcade: {
- gravity: { y: 0 },
- debug: false
- }
- },
- scene: [
- PreloaderScene,
- GameScene
- ]
- };
- game.value = new Phaser.Game(gameConfig.value);
- // 关键:使用局部变量存储事件监听器,便于后续清理
- scoreListener = (newScore) => {
- score.value = newScore;
- };
- // 添加事件监听
- if (game.value && game.value.events) {
- game.value.events.on('scoreChanged', scoreListener);
- }
- game.value.events.on('gameOver', () => {
- currentScene.value = 'gameover';
- setTimeout(() => {
- if (currentScene.value == 'gameover') {
- restartGame();
- }
- }, 2000)
- });
- };
- // 预加载场景
- class PreloaderScene extends Phaser.Scene {
- constructor() {
- super('PreloaderScene');
- }
- preload() {
- // 加载图片资源
- gameAssets.images.forEach(asset => {
- this.load.image(asset.key, asset.url);
- });
- // 加载精灵表
- gameAssets.spritesheets.forEach(asset => {
- this.load.spritesheet(asset.key, asset.url, {
- frameWidth: asset.frameWidth,
- frameHeight: asset.frameHeight
- });
- });
- // 显示加载进度
- const progressBar = this.add.graphics();
- const progressBox = this.add.graphics();
- progressBox.fillStyle(0x222222, 0.8);
- progressBox.fillRect(GAME_WIDTH / 2 - 160, GAME_HEIGHT / 2 - 25, 320, 50);
- const loadingText = this.make.text({
- x: GAME_WIDTH / 2,
- y: GAME_HEIGHT / 2 - 50,
- text: 'Loading...',
- style: {
- font: '20px monospace',
- fill: '#ffffff'
- }
- }).setOrigin(0.5, 0.5);
- const percentText = this.make.text({
- x: GAME_WIDTH / 2,
- y: GAME_HEIGHT / 2,
- text: '0%',
- style: {
- font: '18px monospace',
- fill: '#ffffff'
- }
- }).setOrigin(0.5, 0.5);
- this.load.on('progress', (value) => {
- percentText.setText(`${Math.round(value * 100)}%`);
- progressBar.clear();
- progressBar.fillStyle(0xffffff, 1);
- progressBar.fillRect(GAME_WIDTH / 2 - 150, GAME_HEIGHT / 2 - 15, 300 * value, 30);
- });
- this.load.on('complete', () => {
- progressBar.destroy();
- progressBox.destroy();
- loadingText.destroy();
- percentText.destroy();
- });
- }
- create() {
- // 初始化动画
- this.initAnimations();
- // 切换到游戏场景
- this.scene.start('GameScene');
- }
- initAnimations() {
- // 球员动画
- this.anims.create({
- key: 'playerLeft',
- frames: [
- { key: 'playerAnim', frame: 0 },
- { key: 'playerAnim', frame: 2 },
- { key: 'playerAnim', frame: 4 },
- { key: 'playerAnim', frame: 6 }
- ],
- frameRate: 10,
- repeat: -1
- });
- this.anims.create({
- key: 'playerRight',
- frames: [
- { key: 'playerAnim', frame: 1 },
- { key: 'playerAnim', frame: 3 },
- { key: 'playerAnim', frame: 5 },
- { key: 'playerAnim', frame: 7 }
- ],
- frameRate: 10,
- repeat: -1
- });
- // 足球动画
- this.anims.create({
- key: 'ballAnim',
- frames: this.anims.generateFrameNumbers('ballAnim', { start: 0, end: 1 }),
- frameRate: 5,
- repeat: -1
- });
- // 守门员动画
- this.anims.create({
- key: 'goalkeeperAnim',
- frames: this.anims.generateFrameNumbers('goalkeeperAnim', { start: 0, end: 1 }),
- frameRate: 3,
- repeat: -1
- });
- }
- }
- // 游戏主场景
- class GameScene extends Phaser.Scene {
- constructor() {
- super('GameScene');
- this.player = null;
- this.score = 0;
- this.lives = 3;
- this.maxLives = 5;
- this.speed = 6;
- this.acceleration = 1.9;
- this.level = 1;
- this.obstacles = [];
- this.powerUps = [];
- this.isSuper = false;
- this.gameActive = false;
- this.timer = null;
- this.isShooting = false; // 新增:初始化射门状态
- this.obstacleTimer = null; // 新增:初始化障碍物计时器
- this.obstacleEvent = null;
- this.jerseyEvent = null;
- this.broomEvent = null;
- this.shootTimeout = null; // 射门超时计时器
- this.shootTimeLeft = 15; // 剩余射门时间
- this.shootTimerText = null; // 倒计时显示文本
- }
- create() {
- // 创建背景
- this.createBackground();
- // 创建玩家
- this.createPlayer();
- // 创建计分板
- this.createHUD();
- // 输入控制
- this.initControls();
- // 游戏事件
- this.events.on('resume', () => {
- this.gameActive = true;
- });
- // 等待开始游戏信号
- this.gameActive = false;
- }
- createBackground() {
- // 创建滚动背景
- this.background = this.add.tileSprite(0, 0, GAME_WIDTH, GAME_HEIGHT, 'grass');
- this.background.setOrigin(0, 0);
- }
- createPlayer() {
- // 创建玩家
- //this.player = this.physics.add.sprite(GAME_WIDTH / 2, GAME_HEIGHT - 100, 'playerAnim');
- this.player = this.physics.add.sprite(direction.value + 46, GAME_HEIGHT - 100, 'playerAnim');
- this.player.setCollideWorldBounds(true);
- this.player.setScale(0.8);
- this.player.lives = this.lives;
- this.player.setDepth(9);
- }
- createHUD() {
- // 分数显示
- this.scoreText = this.add.text(10, 10, `分数: ${this.score}`, {
- fontSize: '16px',
- fill: '#ffffff',
- backgroundColor: 'rgba(0,0,0,0.5)',
- padding: { x: 5, y: 2 }
- });
- this.scoreText.setDepth(10);
- // 生命值显示
- this.livesText = this.add.text(GAME_WIDTH - 80, 10, `生命: ${this.lives}`, {
- fontSize: '16px',
- fill: '#ffffff',
- backgroundColor: 'rgba(0,0,0,0.5)',
- padding: { x: 5, y: 2 }
- });
- this.livesText.setDepth(10);
- }
- initControls() {
- // 键盘控制
- this.cursors = this.input.keyboard.createCursorKeys();
- // 触摸控制(添加垂直移动)
- this.input.on('pointermove', (pointer) => {
- if (this.gameActive && pointer.isDown && !this.isShooting) {
- // 水平移动
- this.player.x = Phaser.Math.Clamp(pointer.x, this.player.width / 2, GAME_WIDTH - this.player.width / 2);
- // 垂直移动(限制范围)
- this.player.y = Phaser.Math.Clamp(
- pointer.y,
- 0, // 最小Y值
- GAME_HEIGHT// 最大Y值
- );
- }
- });
- }
- startGame() {
- // 重置核心状态
- this.gameActive = true;
- this.score = 0;
- this.lives = 3; // 重置生命值
- this.maxLives = 5;
- this.speed = 6; // 重置基础速度
- this.acceleration = 1.9; // 重置加速度
- this.level = 1; // 重置等级
- this.isSuper = false;
- this.isShooting = false;
- this.obstacles = [];
- this.powerUps = [];
- // 更新UI显示
- this.livesText.setText(`生命: ${this.lives}`);
- this.updateScore();
- // 清理旧定时器(双重保险)
- if (this.timer) this.timer.remove();
- if (this.obstacleEvent) this.obstacleEvent.remove();
- if (this.jerseyEvent) this.jerseyEvent.remove();
- if (this.broomEvent) this.broomEvent.remove();
- // 重新开始生成障碍物和计时
- this.startSpawning();
- this.timer = this.time.addEvent({
- delay: 200,
- callback: () => {
- this.score++;
- this.updateScore();
- if (this.score % 200 === 0) {
- this.levelUp();
- }
- },
- loop: true
- });
- }
- startSpawning() {
- // 先移除旧事件(强化清除逻辑)
- if (this.obstacleEvent) {
- this.obstacleEvent.remove();
- this.obstacleEvent = null; // 置空引用
- }
- if (this.jerseyEvent) {
- this.jerseyEvent.remove();
- this.jerseyEvent = null; // 置空引用
- }
- if (this.broomEvent) {
- this.broomEvent.remove();
- this.broomEvent = null; // 置空引用
- }
- // 重新创建事件(使用安全的场景上下文)
- this.obstacleEvent = this.time.addEvent({
- delay: 1000 / this.level,
- callback: () => { if (this.gameActive) this.spawnObstacle(); },
- loop: true
- });
- this.jerseyEvent = this.time.addEvent({
- delay: 5000,
- callback: () => { if (this.gameActive) this.spawnPowerUp('jersey'); },
- loop: true
- });
- this.broomEvent = this.time.addEvent({
- delay: 8000,
- callback: () => { if (this.gameActive) this.spawnPowerUp('broom'); },
- loop: true
- });
- // 新增:立即生成第一个障碍物,确保倒计时结束后立即出现
- this.spawnObstacle();
- }
- spawnObstacle() {
- const x = Phaser.Math.Between(30, GAME_WIDTH - 30);
- const obstacle = this.physics.add.sprite(x, -50, 'pile');
- obstacle.setScale(0.7);
- // 降低速度(从原来的this.speed * this.acceleration * 10调整为)
- obstacle.setVelocityY(this.speed * this.acceleration * 5); // 速度减半
- obstacle.setDepth(8);
- obstacle.type = 'pile';
- // 碰撞检测
- this.physics.add.overlap(this.player, obstacle, this.handleObstacleCollision, null, this);
- this.obstacles.push(obstacle);
- // 替换原计时器代码
- const event = this.time.addEvent({ // 用变量接收事件引用
- delay: 100,
- loop: true,
- callback: () => {
- if (obstacle.active) {
- if (obstacle.y > GAME_HEIGHT + obstacle.height) {
- obstacle.destroy();
- this.obstacles = this.obstacles.filter(o => o !== obstacle);
- this.score++;
- this.updateScore();
- this.time.removeEvent(event); // 移除当前事件
- }
- } else {
- this.time.removeEvent(event);
- }
- }
- });
- }
- spawnPowerUp(type) {
- const x = Phaser.Math.Between(30, GAME_WIDTH - 30);
- let powerUp;
- if (type === 'jersey') {
- powerUp = this.physics.add.sprite(x, -50, 'jersey');
- powerUp.type = 'jersey';
- } else if (type === 'broom') {
- powerUp = this.physics.add.sprite(x, -50, 'broom');
- powerUp.type = 'broom';
- }
- powerUp.setScale(0.7);
- // 降低道具速度(从原来的this.speed * this.acceleration * 8调整为)
- powerUp.setVelocityY(this.speed * this.acceleration * 4); // 速度降低
- powerUp.setDepth(8);
- // 碰撞检测
- this.physics.add.overlap(this.player, powerUp, this.handlePowerUpCollision, null, this);
- this.powerUps.push(powerUp);
- // 延长道具生命周期
- this.time.addEvent({
- delay: 15000,
- callback: () => {
- if (powerUp.active) {
- powerUp.destroy();
- this.powerUps = this.powerUps.filter(p => p !== powerUp);
- }
- }
- });
- }
- handleObstacleCollision(player, obstacle) {
- if (this.isSuper) {
- // 超级状态下直接摧毁障碍物
- obstacle.destroy();
- this.obstacles = this.obstacles.filter(o => o !== obstacle);
- return;
- }
- // 扣除生命值
- player.lives--;
- this.lives = player.lives;
- this.livesText.setText(`生命: ${this.lives}`);
- // 显示受伤效果
- player.setTexture('playerCollision');
- this.time.addEvent({
- delay: 618,
- callback: () => {
- player.setTexture('playerAnim');
- }
- });
- // 销毁障碍物
- obstacle.destroy();
- this.obstacles = this.obstacles.filter(o => o !== obstacle);
- // 检查游戏是否结束
- if (player.lives <= 0) {
- this.gameOver();
- }
- }
- handlePowerUpCollision(player, powerUp) {
- if (powerUp.type === 'jersey') {
- // 增加生命值
- if (player.lives < this.maxLives) {
- player.lives++;
- this.lives = player.lives;
- this.livesText.setText(`生命: ${this.lives}`);
- }
- } else if (powerUp.type === 'broom') {
- // 激活超级状态
- this.activateSuperMode();
- }
- // 销毁道具
- powerUp.destroy();
- this.powerUps = this.powerUps.filter(p => p !== powerUp);
- }
- activateSuperMode() {
- this.isSuper = true;
- this.player.setTexture('playerSuper');
- this.acceleration = 10;
- // 3秒后结束超级状态
- this.time.addEvent({
- delay: 3000,
- callback: () => {
- this.isSuper = false;
- this.player.setTexture('playerAnim');
- this.acceleration = 1.9 + (this.level - 1) * 0.5;
- }
- });
- }
- levelUp() {
- this.level++;
- this.acceleration += 0.5;
- // 显示升级提示
- const levelUpText = this.add.text(GAME_WIDTH / 2, GAME_HEIGHT / 2, `Level ${this.level}!`, {
- fontSize: '32px',
- fill: '#ffff00',
- stroke: '#000000',
- strokeThickness: 2
- }).setOrigin(0.5);
- levelUpText.setDepth(10);
- this.time.addEvent({
- delay: 1000,
- callback: () => {
- levelUpText.destroy();
- }
- });
- }
- updateScore() {
- // 新增:检查游戏是否处于活跃状态
- if (!this.gameActive) return;
- try {
- if (!this.scoreText || !this.scene) return;
- this.scoreText.setText(`分数: ${this.score}`);
- if (this.game && this.game.events) {
- this.game.events.emit('scoreChanged', this.score);
- }
- if (this.score % 200 === 0 && this.score > 0 && !this.isShooting) {
- this.enterShootingMode();
- }
- } catch (error) {
- console.error('updateScore 错误:', error);
- }
- }
- enterShootingMode() {
- this.gameActive = false;
- this.isShooting = true;
- this.shootTimeLeft = 15; // 重置剩余时间
-
- // 暂停计分定时器
- if (this.timer) {
- this.timer.paused = true;
- }
-
- // 重置键盘状态
- this.input.keyboard.resetKeys();
- this.cursors.left.isDown = false;
- this.cursors.right.isDown = false;
- this.cursors.up.isDown = false;
- this.cursors.down.isDown = false;
- // 停止所有生成事件
- if (this.obstacleEvent) this.obstacleEvent.remove();
- if (this.jerseyEvent) this.jerseyEvent.remove();
- if (this.broomEvent) this.broomEvent.remove();
- // 清除现有障碍物和道具
- this.obstacles.forEach(obs => obs.destroy());
- this.obstacles = [];
- this.powerUps.forEach(p => p.destroy());
- this.powerUps = [];
- // 创建射门场景背景
- this.background.setTexture('goalBackground');
- // 创建球门
- this.goal = this.physics.add.sprite(GAME_WIDTH / 2, 100, 'goal');
- this.goal.setScale(0.8);
- this.goal.setImmovable(true);
- this.goal.setDepth(5);
- // 创建守门员
- this.goalkeeper = this.physics.add.sprite(GAME_WIDTH / 2, 150, 'goalkeeperAnim');
- this.goalkeeper.setScale(0.9);
- this.goalkeeper.setImmovable(true);
- this.goalkeeper.anims.play('goalkeeperAnim', true);
- this.goalkeeper.setDepth(7);
- // 让守门员左右移动
- this.tweens.add({
- targets: this.goalkeeper,
- x: [GAME_WIDTH / 2 - 50, GAME_WIDTH / 2 + 50],
- duration: 4000,
- ease: 'Sine.inOut',
- repeat: -1,
- yoyo: true
- });
- // 调整球员位置(准备射门)
- this.player.setPosition(GAME_WIDTH / 2, GAME_HEIGHT - 100);
- this.player.setTexture('playerShoot');
- this.player.setVelocity(0);
- // 创建足球
- this.ball = this.physics.add.sprite(this.player.x, this.player.y - 50, 'ballAnim');
- this.ball.setScale(0.8);
- this.ball.anims.play('ballAnim', true);
- this.ball.setDepth(8);
- // 显示射门提示和倒计时
- // this.shootHint = this.add.text(GAME_WIDTH / 2, GAME_HEIGHT - 80, '点击或按空格键射门', {
- // fontSize: '16px',
- // fill: '#ffffff',
- // backgroundColor: 'rgba(0,0,0,0.5)',
- // padding: { x: 5, y: 2 }
- // }).setOrigin(0.5);
- // this.shootHint.setDepth(10);
- // 添加射门倒计时显示
- this.shootTimerText = this.add.text(GAME_WIDTH / 2, GAME_HEIGHT - 50, `剩余时间: ${this.shootTimeLeft}秒`, {
- fontSize: '16px',
- fill: '#ffffff',
- backgroundColor: 'rgba(0,0,0,0.5)',
- padding: { x: 5, y: 2 }
- }).setOrigin(0.5);
- this.shootTimerText.setDepth(10);
- // 设置射门超时计时器
- this.shootTimeout = this.time.addEvent({
- delay: 1000, // 每秒触发一次
- callback: () => {
- this.shootTimeLeft--;
- this.shootTimerText.setText(`剩余时间: ${this.shootTimeLeft}秒`);
-
- // 时间到未射门,判定失败
- if (this.shootTimeLeft <= 0) {
- this.handleShootTimeout();
- }
- },
- loop: true
- });
- // 射门控制
- this.input.keyboard.on('keydown-SPACE', this.shootBall, this);
- this.input.on('pointerdown', this.shootBall, this);
- }
- handleShootTimeout() {
- // 清除超时计时器
- if (this.shootTimeout) {
- this.shootTimeout.remove();
- this.shootTimeout = null;
- }
- // 禁用输入
- this.input.keyboard.off('keydown-SPACE', this.shootBall, this);
- this.input.off('pointerdown', this.shootBall, this);
- // 移除提示文本
- if (this.shootHint) {
- this.shootHint.destroy();
- this.shootHint = null;
- }
- if (this.shootTimerText) {
- this.shootTimerText.destroy();
- this.shootTimerText = null;
- }
- // 显示超时提示
- const timeoutText = this.add.text(GAME_WIDTH / 2, GAME_HEIGHT / 2, '射门超时!', {
- fontSize: '24px',
- fill: '#f00',
- stroke: '#000000',
- strokeThickness: 2
- }).setOrigin(0.5);
- timeoutText.setDepth(10);
- // 2秒后返回奔跑场景
- this.time.addEvent({
- delay: 2000,
- callback: () => {
- timeoutText.destroy();
- this.ball.destroy();
- this.resetToRunningScene();
- // 恢复计分定时器
- if (this.timer) {
- this.timer.paused = false;
- }
- },
- callbackScope: this
- });
- }
- shootBall() {
- if (!this.isShooting) return;
- // 清除超时计时器
- if (this.shootTimeout) {
- this.shootTimeout.remove();
- this.shootTimeout = null;
- }
- // 移除时间显示文本
- if (this.shootTimerText) {
- this.shootTimerText.destroy();
- this.shootTimerText = null;
- }
- // 禁用输入
- this.input.keyboard.off('keydown-SPACE', this.shootBall, this);
- this.input.off('pointerdown', this.shootBall, this);
- this.input.keyboard.off('keydown-LEFT');
- this.input.keyboard.off('keydown-RIGHT');
- if (this.shootHint) {
- this.shootHint.destroy();
- this.shootHint = null;
- }
- // 计算龙门网内的目标位置
- const goalNetY = this.goal.y + 30;
- const goalNetX = this.player.x;
- // 创建射门动画
- const ballTween = this.tweens.add({
- targets: this.ball,
- x: goalNetX,
- y: goalNetY,
- duration: 1000,
- ease: 'Power1',
- onComplete: () => {
- if (!this.ball || !this.ball.active) return;
- // 判断是否成功
- const isSuccess = Phaser.Math.Between(0, 1) === 1;
- if (isSuccess) {
- this.score += 3;
- this.updateScore();
- this.ball.setDepth(6.5); // 成功:足球在守门员后方(网内)
- this.successShoot();
- } else {
- this.ball.setDepth(7.5); // 失败:足球在守门员前方
- this.failShoot();
- }
- ballTween.remove();
- }
- });
- }
- // 射门成功处理
- successShoot() {
- this.isShooting = false;
- // 显示成功提示
- const successText = this.add.text(GAME_WIDTH / 2, GAME_HEIGHT / 2, '射门成功!+3分', {
- fontSize: '24px',
- fill: '#0f0',
- stroke: '#000000',
- strokeThickness: 2
- }).setOrigin(0.5);
- successText.setDepth(10);
- // 2秒后返回奔跑场景
- this.time.addEvent({
- delay: 2000,
- callback: () => {
- if (successText && successText.active) {
- successText.destroy();
- }
- this.ball.destroy();
- this.resetToRunningScene();
- // 恢复计分定时器
- if (this.timer) {
- this.timer.paused = false;
- }
- },
- callbackScope: this
- });
- }
- // 射门失败处理
- failShoot() {
- this.isShooting = false;
- // 关键修复1:解除足球的物理控制
- this.ball.setVelocity(0);
- this.ball.setImmovable(true);
- this.physics.world.disable(this.ball); // 完全禁用物理引擎对足球的控制
- // 关键修复2:调整足球位置到守门员前方固定位置,不跟随移动
- this.ball.setPosition(
- this.goalkeeper.x,
- this.goalkeeper.y + 20 // 固定在守门员前方20px处
- );
- // 显示失败提示
- const failText = this.add.text(GAME_WIDTH / 2, GAME_HEIGHT / 2, '射门失败!', {
- fontSize: '24px',
- fill: '#f00',
- stroke: '#000000',
- strokeThickness: 2
- }).setOrigin(0.5);
- failText.setDepth(10);
- // 2秒后返回奔跑场景
- this.time.addEvent({
- delay: 2000,
- callback: () => {
- if (failText && failText.active) {
- failText.destroy();
- }
- this.ball.destroy(); // 确保足球被销毁
- this.resetToRunningScene();
- // 恢复计分定时器
- if (this.timer) {
- this.timer.paused = false;
- }
- },
- callbackScope: this
- });
- }
- // 重置为奔跑场景
- resetToRunningScene() {
- // 确保清除超时计时器
- if (this.shootTimeout) {
- this.shootTimeout.remove();
- this.shootTimeout = null;
- }
-
- // 清除时间显示文本
- if (this.shootTimerText) {
- this.shootTimerText.destroy();
- this.shootTimerText = null;
- }
- this.clearScene();
- // 重置玩家状态
- if (this.player) {
- this.player.setTexture('playerAnim');
- this.player.setPosition(GAME_WIDTH / 2, GAME_HEIGHT - 100);
- this.player.setVelocity(0);
- }
- // 恢复背景和游戏状态
- if (this.background) this.background.setTexture('grass');
- this.gameActive = true;
- this.isShooting = false;
- // 确保足球被销毁
- if (this.ball) {
- this.ball.destroy();
- this.ball = null;
- }
- // 重新开始生成障碍物
- this.startSpawning();
- }
- gameOver() {
- this.gameActive = false;
- // 清理所有定时事件(增强版)
- // 1. 清除计分定时器
- if (this.timer) {
- this.timer.remove();
- this.timer.destroy(); // 彻底销毁计时器
- this.timer = null; // 置空引用
- }
- // 2. 清除障碍物生成计时器
- if (this.obstacleEvent) {
- this.obstacleEvent.remove();
- this.obstacleEvent.destroy();
- this.obstacleEvent = null;
- }
- // 3. 清除道具生成计时器
- if (this.jerseyEvent) {
- this.jerseyEvent.remove();
- this.jerseyEvent.destroy();
- this.jerseyEvent = null;
- }
- if (this.broomEvent) {
- this.broomEvent.remove();
- this.broomEvent.destroy();
- this.broomEvent = null;
- }
- // 4. 清除所有可能残留的计时器
- this.time.removeAllEvents();
- // 5. 停止所有动画和物理运动
- this.physics.pause();
- // 显示游戏结束画面
- this.add.image(GAME_WIDTH / 2, GAME_HEIGHT / 2, 'gameOver').setOrigin(0.5);
- // 触发游戏结束事件
- this.time.addEvent({
- delay: 2000,
- callback: () => {
- this.game.events.emit('gameOver');
- },
- loop: false // 确保这是一次性事件
- });
- }
- update() {
- if (!this.gameActive) return;
- // 关键修复:射门模式下直接返回,不执行任何移动逻辑
- if (this.isShooting) return;
- // 玩家移动逻辑(原有代码保持不变)
- if (this.player && this.player.active) {
- if (this.cursors.left.isDown) {
- //this.player.setVelocityX(-200);
- this.player.setX(direction.value + 46);
- if (this.player.anims.currentAnim?.key !== 'playerLeft' && !this.player.anims.paused) {
- this.player.anims.play('playerLeft', true);
- }
- } else if (this.cursors.right.isDown) {
- //this.player.setVelocityX(200);
- this.player.setX(direction.value + 46);
- if (this.player.anims.currentAnim?.key !== 'playerRight' && !this.player.anims.paused) {
- this.player.anims.play('playerRight', true);
- }
- } else if (this.cursors.up.isDown) {
- this.player.setVelocityY(-200);
- if (this.player.anims.currentAnim?.key === 'playerLeft' && !this.player.anims.paused) {
- this.player.anims.play('playerLeft', true);
- } else if (this.player.anims.currentAnim?.key === 'playerRight' && !this.player.anims.paused) {
- this.player.anims.play('playerRight', true);
- } else {
- this.player.anims.play('playerLeft', true);
- }
- } else if (this.cursors.down.isDown) {
- this.player.setVelocityY(200);
- if (this.player.anims.currentAnim?.key === 'playerLeft' && !this.player.anims.paused) {
- this.player.anims.play('playerLeft', true);
- } else if (this.player.anims.currentAnim?.key === 'playerRight' && !this.player.anims.paused) {
- this.player.anims.play('playerRight', true);
- } else {
- this.player.anims.play('playerLeft', true);
- }
- } else {
- this.player.setVelocityX(0);
- this.player.setVelocityY(0);
- this.player.anims.stop();
- }
- }
- }
- // 添加 clearScene 方法
- clearScene() {
- // 清理射门场景的元素
- if (this.goal) this.goal.destroy();
- if (this.goalkeeper) this.goalkeeper.destroy();
- if (this.ball) {
- this.ball.destroy();
- this.ball = null; // 显式置空
- }
- if (this.shootHint) this.shootHint.destroy();
- // 清理所有障碍物和道具
- this.obstacles.forEach(obs => obs.destroy());
- this.powerUps.forEach(p => p.destroy());
- this.obstacles = [];
- this.powerUps = [];
- // 停止所有动画
- this.tweens.killAll();
- }
- // 新增:场景销毁时清理资源
- destroy() {
- // 清理所有定时器
- if (this.timer) this.timer.remove();
- if (this.obstacleEvent) this.obstacleEvent.remove();
- if (this.jerseyEvent) this.jerseyEvent.remove();
- if (this.broomEvent) this.broomEvent.remove();
- // 移除输入监听
- this.input.keyboard.off('keydown-SPACE', this.shootBall, this);
- this.input.off('pointerdown', this.shootBall, this);
- // 清理所有游戏对象
- this.obstacles.forEach(obs => obs.destroy());
- this.powerUps.forEach(p => p.destroy());
- if (this.goal) this.goal.destroy();
- if (this.goalkeeper) this.goalkeeper.destroy();
- if (this.ball) this.ball.destroy();
- // 调用父类销毁方法
- super.destroy();
- }
- }
- // 游戏控制函数
- const startGame = () => {
- currentScene.value = 'countdown';
- // 倒计时
- currentScene.value = 'countdown';
- let count = 3;
- const countdownInterval = setInterval(() => {
- count--;
- countdownNum.value = count;
- if (count <= 0) {
- clearInterval(countdownInterval);
- currentScene.value = 'game';
- // 通知游戏开始
- if (game.value) {
- const gameScene = game.value.scene.getScene('GameScene');
- if (gameScene) {
- gameScene.startGame();
- }
- }
- }
- }, 1000);
- };
- const continueGame = () => {
- startGame();
- };
- const restartGame = () => {
- // 1. 彻底销毁旧游戏实例
- if (game.value) {
- // 获取当前场景并停止所有活动
- const gameScene = game.value.scene.getScene('GameScene');
- if (gameScene) {
- gameScene.gameActive = false;
- gameScene.time.removeAllEvents(); // 清除所有事件
- gameScene.physics.pause(); // 暂停物理引擎
- }
- // 销毁游戏实例
- game.value.destroy(true);
- game.value = null;
- }
- // 2. 重置全局状态
- score.value = 0;
- currentScene.value = 'start';
- countdownNum.value = 3;
- // 3. 初始化新游戏
- setTimeout(() => {
- initGame();
- }, 100);
- };
- const getTitle = (score) => {
- if (score < 50) return '足球新手';
- if (score < 100) return '业余球员';
- if (score < 200) return '专业选手';
- if (score < 300) return '足球明星';
- return '足球传奇';
- };
- // 组件挂载时初始化游戏
- onMounted(() => {
- initGame();
- });
- // 监听场景变化
- watch(currentScene, (newVal) => {
- if (newVal === 'game' && game.value) {
- const gameScene = game.value.scene.getScene('GameScene');
- if (gameScene) {
- gameScene.scene.resume();
- }
- }
- });
- /**
- * 监听投篮
- */
- watch(
- () => myThrow.value,
- (newData, oldData) => {
- if (oldData == undefined || myTimer.value) {
- return false;
- }
- console.log("ppp", oldData, newData)
- if (newData == 1) {
- console.log("收腿准备")
- } else {
- if (currentScene.value === 'start') {
- startGame();
- }
- const gameScene = game.value.scene.getScene('GameScene');
- gameScene.shootBall();
- console.log("踢出去了")
- //加个时间以免踢出去然后收腿也算了
- myTimer.value = setTimeout(() => {
- clearTimeout(myTimer.value);
- myTimer.value = null;
- }, 500)
- }
- },
- { immediate: true }
- );
- /**
- * 监听跑动
- */
- watch(
- () => direction.value,
- (newData, oldData) => {
- nextTick(() => {
- if (!game.value) {
- return false;
- }
- const gameScene = game.value.scene.getScene('GameScene');
- if (newData > oldData) {
- gameScene.cursors.left.isDown = false;
- gameScene.cursors.right.isDown = true;
- } else {
- gameScene.cursors.left.isDown = true;
- gameScene.cursors.right.isDown = false;
- }
- });
- },
- { immediate: true }
- );
- /**
- * 初始化
- */
- const getInit = async () => {
- console.log("触发姿态识别")
- let deviceid = localStorage.getItem('deviceid') || '';
- if (!deviceid) {
- proxy?.$modal.msgError(`请重新登录绑定设备号后使用`);
- return false;
- }
- bodyposeState.value = true;
- if (wsState.value) {
- proxy?.$modal.msgWarning(`操作过快,请稍后重试`);
- setTimeout(() => {
- bodyposeState.value = false;
- }, 1000)
- return false;
- }
- speckText("正在姿态识别");
- proxy?.$modal.msgWarning(`正在姿态识别`);
- bodyposeWs((e: any) => {
- //console.log("bodyposeWS", e)
- if (e?.wksid) {
- wsState.value = true;
- //获取设备信息
- startDevice({ deviceid: deviceid });
- console.log("获取设备信息")
- }
- if (e?.type == 'fe_device_init_result') {
- //接收设备信息并发送请求
- if (e?.device_info) {
- deviceInfo.value = e.device_info;
- getCheckBodypose();
- console.log("返回设备信息,检查是否支持姿态识别")
- } else {
- proxy?.$modal.msgError(`设备信息缺失,请重新登录绑定设备号后使用`);
- }
- }
- if (e?.cmd == 'check_bodyposecontroller_available') {
- let handcontroller_id = deviceInfo.value.handcontroller_id;
- if (e?.code == 0) {
- //查看姿态识别状态,如果不处于关闭就先关闭再重新启动(可能会APP退出然后工作站还在运行的可能性)
- getBodyposeState(handcontroller_id);
- againNum.value = 0;
- againTimer.value = null;
- clearTimeout(againTimer.value);
- console.log("查看姿态识别状态")
- } else {
- //尝试多次查询姿态识别状态
- if (againNum.value <= 2) {
- againTimer.value = setTimeout(() => {
- getCheckBodypose();
- }, 500)
- againNum.value++;
- } else {
- let msg = "";
- if (e.code == 102402) {
- msg = `多次连接失败请重试,姿态识别模块被占用`;
- } else {
- msg = `多次连接失败请重试,姿态识别模块不可用,code:${e.code}`;
- }
- proxy?.$modal.msgWarning(msg);
- againNum.value = 0;
- againTimer.value = null;
- clearTimeout(againTimer.value);
- getCloseBodypose();//直接关闭
- }
- }
- }
- if (e?.cmd == 'get_bodyposecontroller_state') {
- let handcontroller_id = deviceInfo.value.handcontroller_id;
- //state说明: 0:关闭,3:空闲,36:工作中
- if ([3, 36].includes(e.state)) {
- getCloseBodypose();
- proxy?.$modal.msgWarning(`请重新姿态识别`);
- } else {
- openBodypose(handcontroller_id);
- }
- }
- if (e?.type == 'bodyposecontroller_result') {
- 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();
- }
- if (e?.cmd == 'terminate_bodyposecontroller') {
- if (e?.code == 0) {
- closeWS();
- bodyposeState.value = false;
- }
- }
- if (e?.type == 'disconnect') {
- wsState.value = false;
- }
- });
- };
- /**
- * 查询姿态识别状态
- */
- const getCheckBodypose = () => {
- let handcontroller_id = deviceInfo.value.handcontroller_id;
- //检查是否支持姿态识别
- checkBodypose(handcontroller_id);
- };
- /**
- * 关闭姿态识别
- */
- const getCloseBodypose = () => {
- let handcontroller_id = deviceInfo.value.handcontroller_id;
- terminateBodypose(handcontroller_id);
- bodyposeState.value = false;
- speckCancel(); //停止播报
- setTimeout(() => {
- if (wsState.value) {
- closeWS();
- }
- }, 3000)
- };
- const getCanvas = () => {
- let leftA = { x: bodyposeData.value[12][0], y: bodyposeData.value[12][1] };//大腿
- let leftB = { x: bodyposeData.value[14][0], y: bodyposeData.value[14][1] };//膝盖
- let leftC = { x: bodyposeData.value[16][0], y: bodyposeData.value[16][1] };//脚
- let rightA = { x: bodyposeData.value[11][0], y: bodyposeData.value[11][1] };//大腿
- let rightB = { x: bodyposeData.value[13][0], y: bodyposeData.value[13][1] };//膝盖
- let rightC = { x: bodyposeData.value[15][0], y: bodyposeData.value[15][1] };//脚
- let jiaodu1 = calculateAngleAtB(leftA, leftB, leftC)
- let jiaodu2 = calculateAngleAtB(rightA, rightB, rightC)
- // console.log("jiaodu1",jiaodu1)
- // console.log("jiaodu2",jiaodu2)
- if (jiaodu1 <= 80 && jiaodu2 >= 120 || jiaodu2 <= 80 && jiaodu2 >= 120) {
- myThrow.value = 1;
- } else {
- myThrow.value = 0;
- }
- 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);
- direction.value = postData[0][0] - offset.x - (94 / 2);//鼻子X
- //绘制头部
- 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(); // 恢复状态
- };
- /**
- * 计算B点的夹角度数(∠ABC)
- * @param {Object} pointA - A点坐标 {x, y, z可选}
- * @param {Object} pointB - B点坐标 {x, y, z可选}
- * @param {Object} pointC - C点坐标 {x, y, z可选}
- * @returns {number} B点的夹角度数(保留两位小数)
- */
- function calculateAngleAtB(pointA, pointB, pointC) {
- // 计算向量BA和向量BC
- const vectorBA = {
- x: pointA.x - pointB.x,
- y: pointA.y - pointB.y,
- z: (pointA.z || 0) - (pointB.z || 0)
- };
- const vectorBC = {
- x: pointC.x - pointB.x,
- y: pointC.y - pointB.y,
- z: (pointC.z || 0) - (pointB.z || 0)
- };
- // 计算点积
- const dotProduct =
- vectorBA.x * vectorBC.x +
- vectorBA.y * vectorBC.y +
- vectorBA.z * vectorBC.z;
- // 计算向量BA的模长
- const lengthBA = Math.sqrt(
- vectorBA.x ** 2 +
- vectorBA.y ** 2 +
- vectorBA.z ** 2
- );
- // 计算向量BC的模长
- const lengthBC = Math.sqrt(
- vectorBC.x ** 2 +
- vectorBC.y ** 2 +
- vectorBC.z ** 2
- );
- // 防止除以零的情况
- if (lengthBA === 0 || lengthBC === 0) {
- throw new Error("点A、B、C不能重合");
- }
- // 计算余弦值
- const cosine = dotProduct / (lengthBA * lengthBC);
- // 由于计算误差可能导致cosine略超出[-1, 1]范围,需要修正
- const clampedCosine = Math.max(Math.min(cosine, 1), -1);
- // 计算弧度并转换为角度
- const angleRadians = Math.acos(clampedCosine);
- const angleDegrees = angleRadians * (180 / Math.PI);
- // 保留两位小数并返回
- return parseFloat(angleDegrees.toFixed(2));
- }
- onBeforeMount(() => {
- clientObj.value = {
- width: document.documentElement.clientWidth,
- height: document.documentElement.clientHeight,
- }
- getInit();
- });
- // 组件卸载时清理事件监听
- onUnmounted(() => {
- if (game.value && game.value.events && scoreListener) {
- game.value.events.off('scoreChanged', scoreListener);
- }
- });
- </script>
- <style scoped>
- .game-container {
- position: relative;
- width: 100%;
- height: 100vh;
- margin: 0 auto;
- overflow: hidden;
- }
- .game-canvas {
- width: 100%;
- height: 100%;
- background-color: #000;
- }
- .gamestart {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- z-index: 100;
- }
- .start_bg {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- .btn {
- width: 80%;
- height: 40px;
- border-radius: 5px;
- text-align: center;
- line-height: 40px;
- color: #333;
- box-shadow: 0 5px 0px #07942c;
- box-shadow: 0 5px 0px rgba(0, 0, 0, 0.17);
- position: absolute;
- left: 50%;
- margin: 0 0 0 -40%;
- font-size: 18px;
- }
- .rule {
- background: #fff;
- bottom: 100px;
- }
- .start {
- background: #ffff00;
- bottom: 40px;
- }
- .ruleshadow {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(0, 0, 0, 0.7);
- z-index: 102;
- display: flex;
- justify-content: center;
- align-items: center;
- font-size: 18px;
- }
- .rulebox {
- width: 280px;
- background-color: white;
- border-radius: 10px;
- padding: 20px;
- position: relative;
- opacity: 0;
- animation: fadeIn 0.5s forwards;
- }
- @keyframes fadeIn {
- to {
- opacity: 1;
- }
- }
- .x_rulebox {
- position: absolute;
- top: 10px;
- right: 10px;
- width: 20px;
- height: 20px;
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000'%3E%3Cpath d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E");
- cursor: pointer;
- }
- .daoju {
- margin-top: 15px;
- }
- .daoju_item {
- display: flex;
- align-items: center;
- margin-bottom: 10px;
- }
- .daoju_1,
- .daoju_2,
- .daoju_3 {
- display: inline-block;
- width: 60px;
- height: 60px;
- margin-right: 10px;
- background-size: contain;
- background-repeat: no-repeat;
- flex-shrink: 0;
- }
- .daoju_1 {
- background-image: url("/static/images/football/Jersey.png");
- }
- .daoju_2 {
- background-image: url("/static/images/football/Broom.png");
- }
- .daoju_3 {
- background-image: url("/static/images/football/Pile.png");
- }
- .zhiyin {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(0, 0, 0, 0.7);
- z-index: 100;
- display: flex;
- justify-content: center;
- align-items: center;
- }
- .daojishi {
- position: absolute;
- width: 100%;
- top: 0;
- bottom: 0;
- display: none;
- z-index: 11;
- background: url(daojishu_bg.png) repeat left top;
- background-size: 100%;
- }
- .daojishiline {
- width: 100%;
- height: 95px;
- position: absolute;
- top: 109px;
- left: 0;
- background: #ffe400;
- z-index: 1;
- }
- .daojishibox {
- width: 180px;
- height: 180px;
- position: absolute;
- left: 50%;
- top: 71px;
- margin: 0 0 0 -90px;
- background: url(daojishibox.png) no-repeat left top;
- background-size: 100%;
- z-index: 2;
- }
- .daojishipangzi {
- width: 111px;
- height: 102px;
- position: absolute;
- left: 50%;
- bottom: 50px;
- margin: 0 0 0 -55px;
- background: url(daojishipangzi.png) no-repeat left top;
- background-size: 100%;
- z-index: -1;
- }
- .daojishinum {
- width: 100%;
- text-align: center;
- font-size: 80px;
- color: #FFFFFF;
- padding: 70px 0 0 0;
- line-height: normal;
- }
- @keyframes countDown {
- 0% {
- transform: scale(3);
- opacity: 0;
- }
- 50% {
- transform: scale(1.2);
- opacity: 1;
- }
- 100% {
- transform: scale(1);
- opacity: 1;
- }
- }
- .gameend {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(0, 0, 0, 0.7);
- z-index: 100;
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- color: white;
- }
- .game_fenshu {
- font-size: 36px;
- margin-bottom: 20px;
- }
- .chenghao {
- font-size: 24px;
- margin-bottom: 30px;
- text-align: center;
- }
- .game_ogain,
- .game_chakan,
- .game_share {
- width: 150px;
- height: 40px;
- line-height: 40px;
- text-align: center;
- background-color: #f00;
- border-radius: 20px;
- margin-bottom: 10px;
- cursor: pointer;
- font-size: 18px;
- }
- .game_chakan {
- background-color: #666;
- }
- .game_share {
- background-color: #008000;
- }
- </style>
|