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 来开发更多有趣的游戏和应用程序!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PID算法入门

文章目录 122.12.22.3 344.14.24.3 1 e(t) 是偏差 实 和 目u(t) 是运算结果 2 层层叠加 得出完整的离散公式 2.1 kp 越大 系统偏差 减小的越快kp大的时候 会出现过冲现象&#xff1f; 0.5 那个会快他解释过冲 &#xff1a; 0.2的 5分钟正好到了 那0.5的五分钟 升的就比20多 就…

springboot 自带的定时任务

启用springboot 定时任务 在springboot 启动类上增加EnableScheduling 注解 如下 SpringBootApplication EnableScheduling public class SpringApplication {public static void main(String[] args) {SpringApplication.run(SpringApplication.class, args);} }编写定时逻辑…

WHAT - 容器化系列(三)- Kubernetes - k8s

目录 一、前言二、Kubernetes 架构图三、KubernetesKubernetes和Docker的关系最小调度单元Pod 四、基本概念容器生态和标准化资源Workload资源&#xff1a;控制器对象服务担保Service&Ingress1. 两者的介绍以及与 OSI 七层模型关系2. 常见的 Service 类型3. Ingress 和 Ing…

自然资源-农村土地流转知识全解

自然资源-农村土地流转知识全解 随着农村经济的发展和城市化进程的加快&#xff0c;农村土地面临着多方面的压力&#xff0c;如人口增长、城市扩张、环境恶化等。这些压力导致了农村土地利用率低、经济效益差、农民收入水平低、农村社会经济不发达等问题。因此&#xff0c;改变…

Rust 第三方库创建和导入(cargo --lib)

前言 日常开发过程中&#xff0c;难免会有一些工具方法&#xff0c;多个项目之间可能会重复使用。 所以将这些方法集成到一个第三方包中方便后期维护和管理&#xff0c; 比如工具函数如果需要修改&#xff0c;多个项目可能每个都需要改代码&#xff0c; 抽离到单独的包中只需要…

AI办公自动化:用kimi批量提取音频中的标题并重命名

很多音频文件&#xff0c;文件名很乱&#xff0c;需要根据音频信息中的标题聪明吗 在kimi中输入提示词&#xff1a; 你是一个Python编程专家&#xff0c;一步步的思考&#xff0c;完成以下脚本的撰写&#xff1a; 打开文件夹&#xff1a;E:\有声\a16z播客 读取里面所有的mp3格…

关于vlookup的第一个参数的个人理解

VLOOKUP&#xff08;查阅值&#xff0c;包含查阅值和返回值的查找区域&#xff0c;查找区域中返回值的列号&#xff0c;精确查找或近似查找&#xff09; 我个人理解&#xff0c;第一个参数应该叫线索值&#xff0c;因为我们要通过它去找与其对应的&#xff08;也就是与其同行的…

Soulmask灵魂面甲服务器一键开服联机教程

1、购买后登录服务器&#xff08;百度莱卡云&#xff09; 进入控制面板后会出现正在安装的界面&#xff0c;安装大约5分钟&#xff08;如长时间处于安装中请联系我们的客服人员&#xff09; 2、创建端口 点击网络➡创建新的网络设置 需要创建两个端口&#xff0c;一个 查询端口…

SAP_SD模块-销售交货并开票后发现物料没维护价格的完整处理方法(含POD功能)

销售流程完结后&#xff0c;发现物料价格没维护时&#xff0c;如何处理 一、业务背景&#xff1a; 1、问题发现时间&#xff1a;2024年6月2日&#xff1b; 2、问题描述&#xff1a; 2024年5月份的单据业务存在交货成本和开票成本为0的单据&#x…

【Hive SQL 每日一题】统计指定范围内的有效下单用户

文章目录 测试数据需求说明需求实现 前言&#xff1a;本题制作参考牛客网进阶题目 —— SQL128 未完成试卷数大于1的有效用户 测试数据 -- 创建用户表 DROP TABLE IF EXISTS users; CREATE TABLE users (user_id INT,name STRING,age INT,gender STRING,register_date STRING…

cocos入门5:编辑器界面介绍

Cocos Creator是一款功能强大的跨平台游戏开发工具&#xff0c;其编辑器界面设计直观易用&#xff0c;提供了从资源管理、场景编辑到脚本编写等一站式解决方案。下面是对Cocos Creator编辑器界面的详细介绍&#xff1a; 一、界面布局 Cocos Creator编辑器界面通常包含以下几个…

【计算机毕设】基于SpringBoot的医院管理系统设计与实现 - 源码免费(私信领取)

免费领取源码 &#xff5c; 项目完整可运行 &#xff5c; v&#xff1a;chengn7890 诚招源码校园代理&#xff01; 1. 研究目的 本项目旨在设计并实现一个基于SpringBoot的医院管理系统&#xff0c;以提高医院管理效率&#xff0c;优化医疗服务流程&#xff0c;提升患者就诊体验…

FL Studio Producer Edition 21.2.2.3914 所有插件版安装教程指南

FL Studio Producer Edition 21.2.2.3914 所有插件版是一款功能强大的软件音乐制作环境或数字音频工作站&#xff08;DAW&#xff09;。FL Studio 中文学习版可以帮助你制作出色的音乐&#xff0c;为您提供了一个集成的开发环境&#xff0c;使用起来非常简单有效&#xff0c;您…

2.4 操作系统死锁(死锁的概念、产生、防止、预防、避免)

文章目录 一、死锁的概念1.1 死锁、饥饿、死循环对比1.1.1 死锁&#xff08;Deadlock&#xff09;1.1.2 饥饿&#xff08;Starvation&#xff09;1.1.3 死循环&#xff08;Infinite Loop&#xff09; 1.2 死锁产生的条件 二、预防死锁三、避免死锁四、死锁的检测和解除4.1 资源…

Arduino 按钮及弹跳

所需元件 可插入面包板的按钮1个 220Ω电阻1个 10kΩ电阻1个 3mm或5mm LED 1个 面包板1块 Arduino Uno开发板1块 面包板连接线数条 使用外接电阻 将5V接到按钮&#xff0c;按钮的另一端串联1个10kΩ电阻再接地&#xff0c;这样的接法被称为下拉电阻(pull-down resistor)。若测…

Vue01-vue的简介

一、Vue是什么&#xff1f; 一套用于构建用户界面的渐进式javaScript框架。 构建用户界面&#xff1a; 渐进式&#xff1a; 目前Vue的地位&#xff1a;生态完善&#xff0c;国内前端工程师必备技能。 二、Vue的特点 一个XXX.vue就是一个组件&#xff0c;封装的概念&#xff0c…

智慧校园有哪些特征

随着科技的飞速进步&#xff0c;教育领域正经历着一场深刻的变革。智慧校园&#xff0c;作为这场变革的前沿代表&#xff0c;正在逐步重塑我们的教育理念和实践方式。它不仅仅是一个概念&#xff0c;而是一个集成了物联网、大数据、人工智能等先进技术的综合生态系统&#xff0…

QT 信号和槽 一对多关联示例,一个信号,多个槽函数响应,一个信号源如何绑定多个槽函数

在窗体里放置一个单行文本编辑控件&#xff08;QLineEdit&#xff09;、一个标签控件&#xff08;QLabel&#xff09;和一个文本浏览控件&#xff08;QTextBrowser&#xff09;&#xff0c;在单行文 本编辑控件里的文本被编辑时&#xff0c;标签控件和文本浏览控件都会同步显示…

codefun的蓝桥杯国赛之旅

前言 好久没有刷算法了&#xff0c;今天完成了我的蓝桥杯国赛之旅&#xff01; 总的来说&#xff0c;比赛的过程不是很顺利&#xff0c;只能ac两道题目&#xff0c;好多题都是有思路&#xff0c;但是要么是写不出来&#xff0c;要么是debug不出来&#xff0c;多重背包&#xf…

C++——输入输出、基本变量类型

目录 一、输入输出 1、标准输出流&#xff08;cout&#xff09; 2、标准输入流&#xff08;cin&#xff09; 3、标准错误流&#xff08;cerr&#xff09;和标准日志流&#xff08;clog&#xff09; 4、示例代码 二、基本数据类型 1、宽字符的用法 2、如何使用 3、示例…