Vue3集成Phaser-飞机大战游戏(设计与源码)

在这里插入图片描述

文章目录

  • 引言
  • 项目初始化
  • 游戏设计和结构
  • 游戏程序实现
    • Vue页面嵌入Phaser
    • Preloader 场景加载
    • 游戏场景功能实现
    • 功能类定义
      • Boom爆炸类
      • Bullet子弹类
      • Enemy敌军类
      • Player玩家类
      • End游戏结束类
  • 总结

更多相关内容可查看

引言

飞机大战(也被称为射击游戏或空战游戏)是一种非常受欢迎的休闲游戏类型。在这个博客中,我们将探讨如何使用 Vue.js 框架来构建一个简单的飞机大战游戏。我们将从基本的游戏逻辑开始,逐步增加游戏元素和交互性,代码详解可参考注释,最终展示画面在文章底部

项目初始化

git地址:https://gitee.com/its-a-little-bad/vue-project—aircraft-battle.git
node版本:20.8.1

游戏设计和结构

在 Vue.js 中,我们通常将游戏的各个部分分解为不同的场景。

主场景

Game.vue:游戏的主界面,包含背景、飞机、敌机、子弹等。
Plane.vue:玩家的飞机,可以移动和发射子弹。
Enemy.vue:敌机,从屏幕上方随机出现并向下移动。
Bullet.vue:子弹,从玩家飞机发射并向上移动。

游戏场景
在这里插入图片描述

游戏程序实现

Vue页面嵌入Phaser

在 Vue 应用中嵌入一个 Phaser 游戏

<template><!-- Phaser 游戏的容器 --><div id="container"></div>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted } from "vue";
import { Game, AUTO, Scale } from "phaser";
import { Preloader } from "./game/Preloader";
import { Main } from "./game/Main";
import { End } from "./game/End";// 使用正则表达式检测当前设备是否为移动设备
let isMobile = /(iPhone|iPad|Android)/i.test(navigator.userAgent);// 定义了一个 game 变量来存储 Phaser 游戏实例
let game: Game;
onMounted(() => {game = new Game({parent: "container",type: AUTO,width: 375,//游戏的大小根据设备类型进行调整。如果设备是移动设备,则高度会根据设备的纵横比计算得出。height: isMobile ? (window.innerHeight / window.innerWidth) * 375 : 667,//游戏的缩放模式也根据设备类型进行设置。移动设备使用 Scale.FIT,这意味着游戏将尽可能地适应屏幕大小,//而不会保持其原始纵横比。非移动设备则使用 Scale.NONE,这意味着游戏将保持其原始大小。scale: {mode: isMobile ? Scale.FIT : Scale.NONE,},physics: {default: "arcade",arcade: {debug: false,},},scene: [Preloader, Main, End],});
});onUnmounted(() => {game.destroy(true);
});
</script><style>
body {margin: 0;
}
#app {height: 100%;
}
</style>

Preloader 场景加载

创建一个 Preloader 场景来加载游戏所需的资源和设置一些基本的游戏元素,示例如下
在这里插入图片描述
程序实现:

import { Scene } from "phaser";  
import backgroundImg from "../assets/images/background.jpg";  
import enemyImg from "../assets/images/enemy.png";  
import playerImg from "../assets/images/player.png";  
import bulletImg from "../assets/images/bullet.png";  
import boomImg from "../assets/images/boom.png";  
import spritesImg from "../assets/images/sprites.png";  
import spritesJson from "../assets/json/sprites.json?url";  
import bgmAudio from "../assets/audio/bgm.mp3";  
import boomAudio from "../assets/audio/boom.mp3";  
import bulletAudio from "../assets/audio/bullet.mp3";  export class Preloader extends Scene {  // 构造函数,定义场景名称为 "Preloader"  constructor() {  super("Preloader");  }  // 预加载资源的方法  preload() {  // 加载背景图片  this.load.image("background", backgroundImg);  // 加载敌人图片  this.load.image("enemy", enemyImg);  // 加载玩家图片  this.load.image("player", playerImg);  // 加载子弹图片  this.load.image("bullet", bulletImg);  // 加载爆炸动画的精灵表(spritesheet)  this.load.spritesheet("boom", boomImg, {  frameWidth: 64,  frameHeight: 48,  });  // 加载精灵图集(atlas)  this.load.atlas("sprites", spritesImg, spritesJson);  // 加载背景音乐  this.load.audio("bgm", bgmAudio);  // 加载爆炸音效  this.load.audio("boom", boomAudio);  // 加载子弹音效  this.load.audio("bullet", bulletAudio);  }  // 创建场景的方法  create() {  const { width, height } = this.cameras.main;  // 显示背景(通常在Preloader场景中不展示实际游戏内容,这里仅为示例)  this.add.tileSprite(0, 0, width, height, "background").setOrigin(0, 0);  // 播放背景音乐(在Preloader场景中播放通常是为了给玩家一个等待的反馈)  this.sound.play("bgm", { loop: true }); // 循环播放背景音乐  // 添加标题(通常也不在Preloader场景中,但可以作为加载提示)  this.add  .text(width / 2, height / 4, "飞机大战", {  fontFamily: "Arial",  fontSize: 60,  color: "#e3f2ed",  stroke: "#203c5b",  strokeThickness: 6,  })  .setOrigin(0.5);  // 添加开始按钮(通常用于在加载完成后切换到主场景)  let button = this.add  .image(width / 2, (height / 4) * 3, "sprites", "button") // 假设"sprites"图集中有名为"button"的帧  .setScale(3, 2)  .setInteractive()  .on("pointerdown", () => {  // 当按钮被点击时,切换到主场景(这里主场景名为'Main')  this.scene.start('Main');  });  // 按钮文案this.add.text(button.x, button.y, "开始游戏", {fontFamily: "Arial",fontSize: 20,color: "#e3f2ed",}).setOrigin(0.5); }  // 创建动画,命名为 boom,后面使用this.anims.create({key: "boom",frames: this.anims.generateFrameNumbers("boom", { start: 0, end: 18 }),repeat: 0,});
}

在Phaser 3框架中,从一个场景(如Preloader)切换到另一个场景(如Main)通常使用this.scene.start(‘Main’)这样的代码来实现。这是Phaser场景管理系统的一部分,它允许你动态地加载、创建、运行和销毁游戏的不同部分。

游戏场景功能实现

在这里插入图片描述

程序实现

// 定义 Main 场景类,继承自 Phaser 的 Scene 类  
import { Scene, Physics, GameObjects } from "phaser";
import { Player } from "./Player";
import { Bullet } from "./Bullet";
import { Enemy } from "./Enemy";
import { Boom } from "./Boom";// 场景元素
let background: GameObjects.TileSprite;
let player: Player;
let enemys: Physics.Arcade.Group;
let bullets: Physics.Arcade.Group;
let booms: GameObjects.Group;
let scoreText: GameObjects.Text;// 场景数据
let score: number;export class Main extends Scene {constructor() {super("Main");}create() {let { width, height } = this.cameras.main;// 创建背景background = this.add.tileSprite(0, 0, width, height, "background").setOrigin(0, 0);// 创建玩家,调用Player类player = new Player(this);// 创建敌军组// 注解:enemys 是一个 Phaser 的物理组,用于存储和管理多个 Enemy 对象// frameQuantity 表示从 enemy 纹理集中加载的帧数,key 是纹理集的名称// enable, active, visible 分别是启用物理、激活和可见性标志// classType 指示组中新创建对象的类型enemys = this.physics.add.group({frameQuantity: 30,key: "enemy",enable: false,// 在此初始状态下不启用物理 active: false,// 在此初始状态下不激活  visible: false,// 在此初始状态下不可见classType: Enemy,// 当组中添加新对象时使用的类});// 创建子弹// 注解:与敌军组类似,但用于存储和管理多个 Bullet 对象 bullets = this.physics.add.group({frameQuantity: 15,key: "bullet",enable: false,active: false,visible: false,classType: Bullet,});// 创建爆炸// 注解:booms 组用于存储和管理多个 Boom 对象,可能是用于显示爆炸动画booms = this.add.group({frameQuantity: 30,key: "boom",active: false,visible: false,classType: Boom,});// 分数// 注解:score 变量用于跟踪玩家的分数,scoreText 是显示分数的文本对象 score = 0;scoreText = this.add.text(10, 10, "0", {fontFamily: "Arial",fontSize: 20,});// 注册事件this.addEvent();}// 注册事件addEvent() {// 定时器// 注解:此定时器每 400 毫秒触发一次回调,生成敌军和发射子弹 this.time.addEvent({delay: 400,callback: () => {// 生成2个敌军for (let i = 0; i < 2; i++) {enemys.getFirstDead()?.born();}// 发射1颗子弹bullets.getFirstDead()?.fire(player.x, player.y - 32);},callbackScope: this,repeat: -1,});// 子弹和敌军碰撞,会调用 hit 方法this.physics.add.overlap(bullets, enemys, this.hit, null, this);// 玩家和敌军碰撞,会调用 gameOver 方法this.physics.add.overlap(player, enemys, this.gameOver, null, this);}// 子弹击中敌军hit(bullet, enemy) {// 子弹和敌军隐藏enemy.disableBody(true, true);bullet.disableBody(true, true);// 显示爆炸booms.getFirstDead()?.show(enemy.x, enemy.y);// 分数增加scoreText.text = String(++score);}// 游戏结束gameOver() {// 暂停当前场景,并没有销毁this.sys.pause();// 保存分数this.registry.set("score", score);// 打开结束场景this.game.scene.start("End");}update() {// 设置背景瓦片不断移动background.tilePositionY -= 1;}
}

功能类定义

Boom爆炸类

import { GameObjects, Scene } from "phaser";export class Boom extends GameObjects.Sprite {constructor(scene: Scene, x: number, y: number, texture: string) {// 创建对象super(scene, x, y, texture);// 爆炸动画播放结束事件this.on("animationcomplete-boom", this.hide, this);}/*** 显示爆炸* @param x 爆炸x坐标* @param y 爆炸y坐标*/show(x: number, y: number) {this.x = x;this.y = y;this.setActive(true);this.setVisible(true);// 爆炸动画this.play("boom");// 爆炸音效this.scene.sound.play("boom");}/*** 隐藏爆炸*/hide() {this.setActive(false);this.setVisible(false);}
}

Bullet子弹类

import { Physics, Scene } from "phaser";export class Bullet extends Physics.Arcade.Sprite {constructor(scene: Scene, x: number, y: number, texture: string) {super(scene, x, y, texture);// 设置属性this.setScale(0.25);}/*** 发射子弹* @param x 子弹x坐标* @param y 子弹y坐标*/fire(x: number, y: number) {this.enableBody(true, x, y, true, true);this.setVelocityY(-300);this.scene.sound.play("bullet");}preUpdate(time: number, delta: number) {super.preUpdate(time, delta);// 子弹走到头,销毁if (this.y <= -14) {this.disableBody(true, true);}}
}

Enemy敌军类

import { Physics, Math, Scene } from "phaser";export class Enemy extends Physics.Arcade.Sprite {constructor(scene: Scene, x: number, y: number, texture: string) {// 创建对象super(scene, x, y, texture);scene.add.existing(this);scene.physics.add.existing(this);// 设置属性this.setScale(0.5);this.body.setSize(100, 60);}/*** 生成敌军*/born() {let x = Math.Between(30, 345);let y = Math.Between(-20, -40);this.enableBody(true, x, y, true, true);this.setVelocityY(Math.Between(150, 300));}preUpdate(time: number, delta: number) {super.preUpdate(time, delta);let { height } = this.scene.cameras.main;// 敌军走到头,销毁if (this.y >= height + 20) {this.disableBody(true, true)}}
}

Player玩家类

import { Physics, Scene } from "phaser";export class Player extends Physics.Arcade.Sprite {isDown: boolean = false;downX: number;downY: number;constructor(scene: Scene) {// 创建对象let { width, height } = scene.cameras.main;super(scene, width / 2, height - 80, "player");scene.add.existing(this);scene.physics.add.existing(this);// 设置属性this.setInteractive();this.setScale(0.5);this.setCollideWorldBounds(true);this.body.setSize(120, 120);// 注册事件this.addEvent();}/*** 注册事件*/addEvent() {// 手指按下我方飞机this.on("pointerdown", () => {this.isDown = true;this.downX = this.x;this.downY = this.y;});// 手指抬起this.scene.input.on("pointerup", () => {this.isDown = false;});// 手指移动this.scene.input.on("pointermove", (pointer) => {if (this.isDown) {this.x = this.downX + pointer.x - pointer.downX;this.y = this.downY + pointer.y - pointer.downY;}});}
}

End游戏结束类

import { Scene } from "phaser";export class End extends Scene {constructor() {super("End");}create() {let { width, height } = this.cameras.main;// 结束面板this.add.image(width / 2, height / 2, "sprites", "result").setScale(2.5);// 标题this.add.text(width / 2, height / 2 - 85, "游戏结束", {fontFamily: "Arial",fontSize: 24,}).setOrigin(0.5);// 当前得分let score = this.registry.get("score");this.add.text(width / 2, height / 2 - 10, `当前得分:${score}`, {fontFamily: "Arial",fontSize: 20,}).setOrigin(0.5);// 重新开始按钮let button = this.add.image(width / 2, height / 2 + 50, "sprites", "button").setScale(3, 2).setInteractive().on("pointerdown", () => {// 点击事件:关闭当前场景,打开Main场景this.scene.start("Main");});// 按钮文案this.add.text(button.x, button.y, "重新开始", {fontFamily: "Arial",fontSize: 20,}).setOrigin(0.5);}
}

总结

通过使用 Vue.js 框架,我们可以轻松地构建出一个简单而有趣的飞机大战游戏。从基本的游戏逻辑开始,逐步增加游戏元素和交互性,最终得到一个完整且吸引人的游戏作品。希望这个博客能对你有所启发,并鼓励你尝试使用 Vue.js 来开发更多有趣的游戏和应用程序!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/21079.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

轻松上手MYSQL:优化MySQL慢查询,让数据库起飞

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》《MYSQL应用》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 ✨欢迎加入探索MYSQL慢查询之旅✨ &#x1f44b; 大家好&#xff01;我是你们的…

如何优雅简洁的使用YOLOv8

如何优雅简洁的使用YOLOv8 目录训练调用代码如何一键训练多个yamlexport模型测试多个yaml是否运行正常predict本文提供了 如何优雅简洁的使用YOLOv8 🗝️YOLOv8实战宝典--星级指南:从入门到精通,您不可错过的技巧   -- 聚焦于YOLO的 最新版本, 对颈部网络改进、添加局…

Crosslink-NX器件应用连载(11): 图像(数据)远程传输

作者&#xff1a;Hello&#xff0c;Panda 大家下午好&#xff0c;晚上好。这里分享一个Lattice Crosslink-NX器件实现图像或数据&#xff08;卫星数据、雷达数据、ToF传感器数据等&#xff09;远程传输的案例&#xff08;因为所描述的内容颇杂&#xff0c;晒图不好晒&#xff…

文件批量改后缀名,轻松实现TXT到DOCX格式转换,高效管理您的文件库!

文件处理与管理已成为我们日常生活和工作中不可或缺的一环。然而&#xff0c;面对海量的文件&#xff0c;如何高效地进行格式转换和管理&#xff0c;却成为了一道难题。今天&#xff0c;我们将为您揭晓一个神奇的解决方案——文件批量改后缀名功能&#xff0c;让您轻松实现TXT到…

【docker】docker的安装

如果之前安装了旧版本的docker我们需要进行卸载&#xff1a; 卸载之前的旧版本 卸载 # 卸载旧版本 sudo apt-get remove docker docker-engine docker.io containerd runc # 卸载历史版本 apt-get purge docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker…

linux文件共享之samba

1.介绍 Samba是一个开源文件共享服务&#xff0c;可以使linux与windows之间进行文件共享&#xff0c;可以根据不同人员调整共享设置以及权限管理。 2.安装 一个命令就OK了&#xff1a;yum install -y samba [rootansible01 ~]# yum install -y samba 已加载插件&#xff1a;l…

Python爬虫之简单学习BeautifulSoup库,学习获取的对象常用方法,实战豆瓣Top250

BeautifulSoup是一个非常流行的Python库&#xff0c;广泛应用于网络爬虫开发中&#xff0c;用于解析HTML和XML文档&#xff0c;以便于从中提取所需数据。它是进行网页内容抓取和数据挖掘的强大工具。 功能特性 易于使用: 提供简洁的API&#xff0c;使得即使是对网页结构不熟悉…

【一刷《剑指Offer》】面试题 31:连续子数组的最大和

牛客对应题目链接&#xff1a;连续子数组最大和_牛客题霸_牛客网 (nowcoder.com) 力扣对应题目链接&#xff1a;53. 最大子数组和 - 力扣&#xff08;LeetCode&#xff09; 核心考点 &#xff1a;简单动归问题。 一、《剑指Offer》对应内容 二、分析题目 1、贪心 从前往后迭…

关于Posix标准接口和Nuttx操作系统

基本介绍 主要参考&#xff1a; Linux 系统中的 POSIX 接口详细介绍_linux posix-CSDN博客 POSIX&#xff08;Portable Operating System Interface&#xff0c;可移植操作系统接口&#xff09;是由 IEEE&#xff08;Institute of Electrical and Electronics Engineers&#x…

大模型对齐方法笔记四:针对领域问答来进行知识对齐方法KnowPAT

KnowPAT KnowPAT(Knowledgeable Preference AlignmenT) 出自2023年11月的论文《Knowledgeable Preference Alignment for LLMs in Domain-specific Question Answering》&#xff0c;主要针对领域问答来进行知识对齐。 在领域问答有两个挑战&#xff1a;希望输出满足用户的要…

Notepad++ 常用

File Edit search view Encoding Language Settings Tools Macro Run Plugins Window 文件 编辑 搜索 视图 编码 语言 设置 工具 宏 运行 插件 窗口 快捷方式 定位行 &#xff1a;CTRL g查找&#xff1a; CTRL F替换&am…

小白也能看得懂的基于HTML+CSS+JS实现的五子棋小游戏

五子棋是一种起源于中国的传统棋类游戏&#xff0c;具有悠久的历史。 基本规则 棋盘&#xff1a; 五子棋通常在一个 15x15 的棋盘上进行&#xff0c;但也可以在更大的棋盘上进行。棋盘上的每个交叉点称为一个“点”。 棋子&#xff1a; 五子棋使用黑白两色的棋子。两名玩家分别…

【竞技宝】欧冠:多特抢开局失败,皇马展示顶级防守反击

本赛季欧冠决赛结束,皇马在上半场被压制的情况下,2比0击败多特蒙德夺得队史第15座欧冠冠军奖杯。比赛中多特蒙德已经展现出了不俗的状态,可是面对老辣的皇马他们还是败下阵来,皇马用顶级的防守反击给多特上了一课。通过这场比赛,相信球迷们也清楚当今足坛硬实力不可或缺。 在许…

7-18 对象关系映射(orm_name)---PTA实验C++

一、题目描述 一开始看到对象关系映射&#xff0c;其实我是拒绝的。这三个词凑一块&#xff0c;能是给C初学者的题吗&#xff1f; 再仔细读需求&#xff0c;才发现在课设项目已经用过这功能。Object Relational Mapping&#xff08;ORM&#xff09;就是面向对象&#xff08;O…

《平渊》· 柒 —— 大道至简?真传一句话,假传万卷书!

《平渊》 柒 "真传一句话, 假传万卷书" 对于 "大道至简"&#xff0c;不少专家可能会说出一大堆乱七八糟的名词, 比如这样&#xff1a; 所谓 "大道" 即支撑天地运转的 "系统自动力"&#xff0c;更具体地来说&#xff0c;即是天地人以…

快手游戏《无尽梦回》官宣开测:热血动作肉鸽来袭

易采游戏网最新消息&#xff1a;5月30日11:00&#xff0c;快手自研的梦境主题动作冒险手游《无尽梦回》正式宣布开启测试。此次测试名为“肉鸽进化实验”&#xff0c;旨在测试多角色技能交会的玩法。游戏将开放32人同局竞技&#xff0c;让玩家在激烈的战斗中角逐出唯一的胜利者…

HTML如何让文字底部线条不紧贴在文字下面(既在内容下方又超出内容区域)

hello&#xff0c;大家好&#xff0c;星途星途今天给大家带来的内容是如何让文字底部线条不紧贴在文字下面。 话不多说&#xff0c;先上效果图 简单来说就是padding和margin的区别。 在网页设计中&#xff0c;有时我们想要给某个元素添加一个装饰性的线条&#xff0c;比如底部…

过滤器、监听器、拦截器的区别

过滤器、监听器、拦截器的区别 过滤器&#xff08;filter&#xff09;、监听器&#xff08;Listener&#xff09;是JavaWeb的三大组件。而拦截器&#xff08;Interceptor&#xff09;是Spring框架中的。 我们主要是要分清除过滤器和拦截器的区别&#xff1a; 实现原理&#…

overleaf 写参考文献引用

目录 1、 新建.bib 文件 2、导入引用 3、在文档中引用参考文献 4、生成参考文献列表 1、 新建.bib 文件 在Overleaf项目中&#xff0c;你可以选择导入现有的 .bib 文件或在项目中创建一个新的 .bib 文件来管理你的参考文献。 导入.bib 文件&#xff1a; 在项目文件树中点击…

11. RBAC权限管理从零到一实现(二)

前端页面已提交至git https://github.com/SJshenjian/cloud-web默认用户名密码admin 1