当前位置: 首页 > news >正文

vue和angular实现飞机大战

说明:
vue和angular实现飞机大战
1.用表情符合代替图片资源(火箭是自己战机,黄色是子弹,其他是敌人战机)
2.用鼠标移动飞机左右方向
3.空格键发射子弹
效果图:
在这里插入图片描述

step1:C:\Users\wangrusheng\PycharmProjects\untitled3\src\views\Flay.vue

<template>
  <div class="game-container">
    <canvas ref="canvas" :width="game.SWIDTH" :height="game.SHEIGHT" @mousemove="onMouseMove"></canvas>

    <div v-if="game.gameState === 'menu'" class="menu overlay">
      <h2>飞机大战</h2>
      <button @click="game.startGame()">开始游戏</button>
      <button @click="exitGame">退出游戏</button>
    </div>

    <div v-if="game.gameState === 'gameover'" class="gameover overlay">
      <h2>游戏结束! 分数: {{ game.score }}</h2>
      <button @click="game.startGame()">重新开始</button>
    </div>
  </div>
</template>

<script setup>
import { reactive, ref, onMounted, onUnmounted } from 'vue';

// 转换GameService为响应式对象
const game = reactive({
  ENEMY_EMOJIS: ["🦄","🐺","🐗","🐴","🦋","🐌","🐞","🐝","🦀","🐡","🐬","🦑","🐊","🦓","🐇"],
  SWIDTH: 600,
  SHEIGHT: 900,
  HERO_SIZE: 40,
  ENEMY_SIZE: 30,
  BULLET_SPEED: 8,
  heroX: 300,
  heroY: 800,
  bullets: [],
  enemies: [],
  score: 0,
  gameState: 'menu',
  lastTime: 0,

  startGame() {
    this.gameState = 'playing';
    this.heroX = this.SWIDTH / 2;
    this.heroY = this.SHEIGHT - 100;
    this.bullets = [];
    this.enemies = [];
    this.score = 0;
    this.lastTime = 0;
    this.gameLoop(0);
  },

  gameLoop(timestamp) {
    if (this.gameState !== 'playing') return;

    const deltaTime = timestamp - this.lastTime;
    this.lastTime = timestamp;

    if (Math.random() < 0.05 && this.enemies.length < 10) {
      this.enemies.push(new Enemy(
        this.ENEMY_EMOJIS[Math.floor(Math.random() * this.ENEMY_EMOJIS.length)],
        Math.random() * (this.SWIDTH - this.ENEMY_SIZE),
        -this.ENEMY_SIZE
      ));
    }

    this.bullets = this.bullets.filter(bullet => {
      bullet.y -= this.BULLET_SPEED;
      return bullet.y > -this.BULLET_SPEED;
    });

    this.enemies = this.enemies.filter(enemy => {
      enemy.y += 4;

      const hit = this.bullets.some(bullet =>
        bullet.x < enemy.x + this.ENEMY_SIZE &&
        bullet.x + 10 > enemy.x &&
        bullet.y < enemy.y + this.ENEMY_SIZE &&
        bullet.y + 20 > enemy.y
      );

      if (hit) this.score++;

      const crash =
        this.heroX < enemy.x + this.ENEMY_SIZE &&
        this.heroX + this.HERO_SIZE > enemy.x &&
        this.heroY < enemy.y + this.ENEMY_SIZE &&
        this.heroY + this.HERO_SIZE > enemy.y;

      if (crash) this.gameState = 'gameover';

      return !hit && enemy.y < this.SHEIGHT && !crash;
    });

    requestAnimationFrame((t) => this.gameLoop(t));
  }
});

class Enemy {
  constructor(emoji, x, y) {
    this.emoji = emoji;
    this.x = x;
    this.y = y;
  }
}

const canvas = ref(null);
let animationFrameId;

const onMouseMove = (e) => {
  if (game.gameState === 'playing') {
    const rect = canvas.value.getBoundingClientRect();
    game.heroX = e.clientX - rect.left - game.HERO_SIZE / 2;
    game.heroY = e.clientY - rect.top - game.HERO_SIZE / 2;
    keepInBounds();
  }
};

const keepInBounds = () => {
  game.heroX = Math.max(0, Math.min(game.SWIDTH - game.HERO_SIZE, game.heroX));
  game.heroY = Math.max(0, Math.min(game.SHEIGHT - game.HERO_SIZE, game.heroY));
};

const handleKeyDown = (e) => {
  if (e.code === 'Space' && game.gameState === 'playing') {
    game.bullets.push({
      x: game.heroX + game.HERO_SIZE / 2 - 5,
      y: game.heroY - 10
    });
  }
};

const draw = () => {
  const ctx = canvas.value.getContext('2d');
  ctx.clearRect(0, 0, game.SWIDTH, game.SHEIGHT);

  ctx.font = '40px Segoe UI Emoji';
  ctx.fillText('🚀', game.heroX, game.heroY);

  ctx.fillStyle = 'gold';
  game.bullets.forEach(b => ctx.fillRect(b.x, b.y, 10, 20));

  ctx.fillStyle = 'red';
  game.enemies.forEach(enemy => {
    ctx.font = '30px Segoe UI Emoji';
    ctx.fillText(enemy.emoji, enemy.x, enemy.y);
  });

  ctx.fillStyle = 'black';
  ctx.font = '20px Arial';
  ctx.fillText(`Score: ${game.score}`, 10, 30);

  animationFrameId = requestAnimationFrame(draw);
};

const exitGame = () => {
  // 退出逻辑
};

onMounted(() => {
  document.addEventListener('keydown', handleKeyDown);
  draw();
});

onUnmounted(() => {
  document.removeEventListener('keydown', handleKeyDown);
  cancelAnimationFrame(animationFrameId);
});
</script>

<style scoped>
.game-container {
  position: relative;
  background: linear-gradient(135deg, #1a1a1a 0%, #4a4a4a 100%);
  border-radius: 15px;
  box-shadow: 0 0 20px rgba(0,0,0,0.5);
  overflow: hidden;
  margin: 20px;
}

canvas {
  display: block;
  border-radius: 10px;
  box-shadow: inset 0 0 10px rgba(255,255,255,0.1);
}

.overlay {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: rgba(0, 0, 0, 0.8);
  padding: 2rem;
  border-radius: 15px;
  text-align: center;
  box-shadow: 0 0 20px rgba(0,0,0,0.5);
  color: white;
}

button {
  background: #4CAF50;
  border: none;
  padding: 10px 20px;
  margin: 10px;
  border-radius: 5px;
  color: white;
  cursor: pointer;
  transition: background 0.3s;
}

button:hover {
  background: #45a049;
}

h2 {
  margin: 0 0 1rem 0;
  color: #fff;
}
</style>

手动分割线
以下是angular代码
step101:C:\Users\wangrusheng\PycharmProjects\untitled15\src\app\flay\flay.component.ts

import { Component, HostListener, ViewChild, ElementRef } from '@angular/core';
import { GameService } from './game.service';
import {NgIf} from '@angular/common';
@Component({
  selector: 'app-flay',
  imports: [
    NgIf
  ],
  template: `
    <canvas #canvas [width]="game.SWIDTH" [height]="game.SHEIGHT"
            (mousemove)="onMouseMove($event)"></canvas>

    <div *ngIf="game.gameState === 'menu'" class="menu">
      <h2>飞机大战</h2>
      <button (click)="game.startGame()">开始游戏</button>
      <button (click)="exitGame()">退出游戏</button>
    </div>

    <div *ngIf="game.gameState === 'gameover'" class="gameover">
      <h2>游戏结束! 分数: {{game.score}}</h2>
      <button (click)="game.startGame()">重新开始</button>
    </div>
  `,
  styles: [`
    canvas { border: 1px solid #ccc; }
    .menu, .gameover {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      text-align: center;
    }
  `]
})

export class FlayComponent {
  @ViewChild('canvas') canvas!: ElementRef<HTMLCanvasElement>;

  constructor(public game: GameService) {}

  ngAfterViewInit() {
    this.draw();
  }

  onMouseMove(e: MouseEvent) {
    if (this.game.gameState === 'playing') {
      const rect = this.canvas.nativeElement.getBoundingClientRect();
      this.game.heroX = e.clientX - rect.left - this.game.HERO_SIZE/2;
      this.game.heroY = e.clientY - rect.top - this.game.HERO_SIZE/2;
      this.keepInBounds();
    }
  }

  @HostListener('document:keydown', ['$event'])
  onKeyDown(e: KeyboardEvent) {
    if (e.code === 'Space' && this.game.gameState === 'playing') {
      this.game.bullets.push({
        x: this.game.heroX + this.game.HERO_SIZE/2 - 5,
        y: this.game.heroY - 10
      });
    }
  }

  private keepInBounds() {
    this.game.heroX = Math.max(0, Math.min(
      this.game.SWIDTH - this.game.HERO_SIZE,
      this.game.heroX
    ));
    this.game.heroY = Math.max(0, Math.min(
      this.game.SHEIGHT - this.game.HERO_SIZE,
      this.game.heroY
    ));
  }

  private draw() {
    requestAnimationFrame(() => this.draw());
    const ctx = this.canvas.nativeElement.getContext('2d')!;

    ctx.clearRect(0, 0, this.game.SWIDTH, this.game.SHEIGHT);

    // 绘制玩家
    ctx.font = '40px Segoe UI Emoji';
    ctx.fillText('🚀', this.game.heroX, this.game.heroY);

    // 绘制子弹
    ctx.fillStyle = 'gold';
    this.game.bullets.forEach(b => ctx.fillRect(b.x, b.y, 10, 20));

    // 绘制敌人
    ctx.fillStyle = 'red';
    this.game.enemies.forEach(enemy => {
      ctx.font = '30px Segoe UI Emoji';
      ctx.fillText(enemy.emoji, enemy.x, enemy.y);
    });

    // 绘制分数
    ctx.fillStyle = 'black';
    ctx.font = '20px Arial';
    ctx.fillText(`Score: ${this.game.score}`, 10, 30);
  }

  exitGame() {
    // 退出逻辑(根据实际需求实现)
  }
}

step102:C:\Users\wangrusheng\PycharmProjects\untitled15\src\app\flay\game.service.ts

// game.service.ts
import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class GameService {
  readonly ENEMY_EMOJIS = ["🦄","🐺","🐗","🐴","🦋","🐌","🐞","🐝","🦀","🐡","🐬","🦑","🐊","🦓","🐇"];
  readonly SWIDTH = 600;
  readonly SHEIGHT = 900;
  readonly HERO_SIZE = 40;
  readonly ENEMY_SIZE = 30;
  readonly BULLET_SPEED = 8;

  heroX = this.SWIDTH / 2;
  heroY = this.SHEIGHT - 100;
  bullets: { x: number, y: number }[] = [];
  enemies: Enemy[] = [];
  score = 0;
  gameState: 'menu' | 'playing' | 'gameover' = 'menu';

  private lastTime = 0;

  startGame() {
    this.gameState = 'playing';
    this.heroX = this.SWIDTH / 2;
    this.heroY = this.SHEIGHT - 100;
    this.bullets = [];
    this.enemies = [];
    this.score = 0;
    this.gameLoop(0);
  }

  private gameLoop(timestamp: number) {
    if (this.gameState !== 'playing') return;

    const deltaTime = timestamp - this.lastTime;
    this.lastTime = timestamp;

    // 生成敌人
    if (Math.random() < 0.05 && this.enemies.length < 10) {
      this.enemies.push(new Enemy(
        this.ENEMY_EMOJIS[Math.floor(Math.random() * this.ENEMY_EMOJIS.length)],
        Math.random() * (this.SWIDTH - this.ENEMY_SIZE),
        -this.ENEMY_SIZE
      ));
    }

    // 更新子弹
    this.bullets = this.bullets.filter(bullet => {
      bullet.y -= this.BULLET_SPEED;
      return bullet.y > -this.BULLET_SPEED;
    });

    // 更新敌人并检测碰撞
    this.enemies = this.enemies.filter(enemy => {
      enemy.y += 4;

      // 子弹碰撞
      const hit = this.bullets.some(bullet =>
        bullet.x < enemy.x + this.ENEMY_SIZE &&
        bullet.x + 10 > enemy.x &&
        bullet.y < enemy.y + this.ENEMY_SIZE &&
        bullet.y + 20 > enemy.y
      );

      if (hit) this.score++;

      // 玩家碰撞
      const crash =
        this.heroX < enemy.x + this.ENEMY_SIZE &&
        this.heroX + this.HERO_SIZE > enemy.x &&
        this.heroY < enemy.y + this.ENEMY_SIZE &&
        this.heroY + this.HERO_SIZE > enemy.y;

      if (crash) this.gameState = 'gameover';

      return !hit && enemy.y < this.SHEIGHT && !crash;
    });

    requestAnimationFrame((t) => this.gameLoop(t));
  }
}

class Enemy {
  constructor(
    public emoji: string,
    public x: number,
    public y: number
  ) {}
}

end

http://www.dtcms.com/a/109897.html

相关文章:

  • 彩虹表攻击
  • 52.个人健康管理系统小程序(基于springbootvue)
  • Linux 高级命令与常见操作:文本处理、系统管理与网络调试
  • 红米AC2100-刷OpenWrt系统,安装zerotier教程
  • 7-6 混合类型数据格式化输入
  • 大数据(4)Hive数仓三大核心特性解剖:面向主题性、集成性、非易失性如何重塑企业数据价值?
  • PPT助手:一款集计时、远程控制与多屏切换于一身的PPT辅助工具
  • 【备忘】在Docker中安装宝塔面板,实现环境隔离,又能快速迁移服务器环境
  • 基于Flask的MBA考生成绩查询系统设计与实现
  • Java 可变参数(Varargs)使用指南
  • Vue实例中,setTimeout()不生效怎么解决
  • 联合类型|类型断言|交叉类型
  • [CISSP] [6] 密码学和对称密钥算法
  • AI模拟了一场5亿年的进化
  • 微信小程序基于Canvas实现头像图片裁剪(上)
  • 【python】Plot a Square
  • 【SQL】取消sql某一列的唯一值key值的方法
  • XXX软件系统研发技术手册模板
  • “*(单星号)”和“**(双星号)”在Python中的灵活运用
  • PySide6控件:QFont设置、QColor调色板、QPixmap图像处理与QCursor光标自定义
  • ESP32 C3mini与ESP32-WROOM蓝牙连接问题
  • 离线模型添加联网搜索功能
  • hpu萌新训练赛(三)
  • NVR接入录像回放平台EasyCVR打造基层安防 “智慧中枢”:一站式智慧安防方案
  • 使用 Mockito 快速构建 Mock 测试环境
  • uniapp如何接入星火大模型
  • php的高速缓存
  • SparkAudio 是什么,和其他的同类 TTS 模型相比有什么优势
  • Redis缓存问题
  • Linux系统程序设计:从入门到高级Day02