Pixi的基本使用(5)--寻宝猎人

寻宝猎人

  • 游戏需求: 使用键盘上的箭头键帮助探险家找到宝藏并将其带到出口* 怪物在地牢壁之间上下移动,如果探险家触碰到怪物则变成半透明,并且右上角的生命值会减少* 如果所有生命值都用光了,会显示 You Lost!* 如果探险家带着宝藏到达出口,会显示 You Won!

代码结构

  • 寻宝猎人游戏共分为7个部分,分别为:初始化舞台初始化游戏游戏循环游戏结束边界检测碰撞检测按键绑定
 // 所需数据
 data() {
   return {
     app: null, // 整体舞台
     // 精灵
     explorer: null, // 探索者
     door: null, // 门
     dungeon: null, // 背景
     treasure: null, // 宝箱
     blobs: [], // 怪物集合
     
     // 容器
     gameScene: null, // 游戏场景
     gameOverScene: null, // 游戏结束场景
     healthBar: null, // 生命条
     
     // 文本
     tip: null, // 提示信息
 };
 },
 ​
 methods: {
   // 初始化舞台
 initApp() {},
   
 // 初始化游戏
 initGame(texture) {},
   
 // 游戏结束
 gameEnd() {},
  // 按键绑定
 _addKeyListener(sprite) {},
  // 碰撞检测
 _hitTestRectangle(moveSprite, otherSprite) {},
  // 移动范围检测
 _moveRangeCheck(sprite, container) {}
 } 

初始化舞台

  • initApp 中初始化舞台并加载所需资源,待资源加载完成后初始化游戏
 // 初始化舞台
 initApp() {
   this.app = new PIXI.Application({
     width: 600, //宽
     height: 600, //高
     backgroundAlpha: 1, // 背景不透明
     backgroundColor: 0xffffff,
     antialias: true, // 开启抗锯齿,使字体和图形边缘更加平滑
     resolution: 1, // 像素比
     forceCanvas: false, // 不强制使用canvas
 });
   this.$refs.pixi.appendChild(this.app.view); // 挂载生成的画布
   // 加载资源
   const sourceUrl = require("@/assets/images/treasureHunter.png");
   const loader = PIXI.Loader.shared;
   loader.reset(); // 有缓存,需要重置loader
   loader.add(sourceUrl); // 加载图片资源
   loader.load((loader, resource) => {
     PIXI.utils.clearTextureCache(); // 清除纹理缓存
     // 待资源加载完成后,初始化游戏
     this.initGame(resource[sourceUrl].texture);
 });
 } 

初始化游戏

  • 待纹理图集加载完成后,initGame 方法会立即执行,该方法里需要创建和初始化容器,精灵,游戏场景,绑定按键,制作游戏文本等* 初始化游戏场景和精灵,属于主游戏的精灵都被添加到 gameScene 容器this.gameScene = new PIXI.Container(); // 初始化游戏场景容器 this.app.stage.addChild(this.gameScene); // 将游戏场景添加到舞台 // 解析游戏场景所需精灵 mySprite.parse((resource) => { // 背景 this.dungeon = new PIXI.Sprite(resource["dungeon.png"]); this.gameScene.addChild(this.dungeon); // 将精灵添加到游戏场景容器内 ​ // 门 this.door = new PIXI.Sprite(resource["door.png"]); this.door.position.set(32, 0); this.gameScene.addChild(this.door); ​ // 探索者 this.explorer = new PIXI.Sprite(resource["explorer.png"]); this.explorer.vx = 0; this.explorer.vy = 0; this.explorer.x = 48; this.explorer.y = this.gameScene.height / 2 - this.explorer.height / 2; this.gameScene.addChild(this.explorer); ​ // 宝盒 this.treasure = new PIXI.Sprite(resource["treasure.png"]); this.treasure.x = this.gameScene.width - this.treasure.width - 48; this.treasure.y = this.gameScene.height / 2 - this.treasure.height / 2; this.gameScene.addChild(this.treasure); ​ // 生成怪物 let numberOfBlobs = 7, spacing = 60, xOffset = 90, speed = 4, direction = 1; for (let i = 0; i < numberOfBlobs; i++) { const blob = new Sprite(resource["blob.png"]); blob.x = spacing * i + xOffset; // 随机定义怪物的y坐标 blob.y = this._randomInt(0, this.gameScene.height - blob.height); blob.vy = speed * direction; this.blobs.push(blob); this.gameScene.addChild(blob); } }); * 初始化生命条容器,随后也添加到主游戏的 gameScene 容器this.healthBar = new PIXI.Container(); this.healthBar.position.set(this.app.stage.width - 170, 4); this.gameScene.addChild(this.healthBar); // 创建生命条背景矩形 const innerBar = new PIXI.Graphics(); innerBar.beginFill(0x000000); innerBar.drawRoundedRect(0, 0, 128, 8); innerBar.endFill(); this.healthBar.addChild(innerBar); // 创建生命条血条 const outerBar = new PIXI.Graphics(); outerBar.beginFill(0xff3300); outerBar.drawRoundedRect(0, 0, 128, 8); outerBar.endFill(); this.healthBar.addChild(outerBar); this.healthBar.outer = outerBar; // 将血条添加到容器上,方便操作 * 待生命条、游戏场景、精灵初始化之后,如下图所示
    • 初始化游戏结束的场景,游戏结束时显示的文本将被添加到 gameOverScene 容器中this.gameOverScene = new PIXI.Container(); // 游戏结束场景容器 this.app.stage.addChild(this.gameOverScene); const tipStyle = new PIXI.TextStyle({ fontFamily: "Futura", fontSize: 64, fill: "white", }); this.tip = new PIXI.Text("The End!", tipStyle); this.tip.x = 120; this.tip.y = this.app.stage.height / 2 - 32; this.gameOverScene.visible = false; // 在游戏开始时,gameOverScene不应该被显示出来 this.gameOverScene.addChild(this.tip);* 给移动的猎人添加按键监听事件

  • 开启游戏循环
this.app.ticker.add(() => this.gameLoop()); 
  • 以上则是初始化游戏方法 initGame 的全部内容,接下来需要完成 gameLoop 游戏循环方法* 获取随机数方法
 _randomInt(min, max) {
   return Math.floor(Math.random() * (max - min + 1)) + min;
 } 

游戏循环

  • 游戏循环开启后,利用猎人的速度让它移动
 this.explorer.x += this.explorer.vx;
 this.explorer.y += this.explorer.vy; 
  • 需要对猎人的移动范围进行检测,不能越界
 // 定义移动的范围
 const gameContainer = {
   x: 28,
   y: 10,
   width: 488,
   height: 480,
 };
 // 边界检测函数
 this._moveRangeCheck(this.explorer, gameContainer); 
  • 让怪物上下移动,同对怪物进行边界检测和碰撞检测,看看是否到达边界或者碰撞到猎人
 // 让怪物自己移动
 this.blobs.forEach((blob) => {
   blob.y += blob.vy;
   // 对怪物进行碰撞检测
   const arriveBorder = this._moveRangeCheck(blob, gameContainer);
   // 怪物抵达边界后,进行反向移动
   if (arriveBorder === "top" || arriveBorder === "bottom") {
     blob.vy *= -1;
 }
 ​
   // 检测猎人是否碰到了怪物
   const isHit = this._hitTestRectangle(this.explorer, blob);
   if (isHit) {
 // 假如碰撞到怪物,猎人呈半透明,并且血条降低 
     this.explorer.alpha = 0.5;
     this.healthBar.outer.width -= 1;
 } else {
     setTimeout(() => {
       // 猎人离开后,半透明小时
       this.explorer.alpha = 1;
   }, 200);
 }
 }); 
  • 判断血条是否为空,如果为空则游戏结束,调用 gameEnd 方法
 // 如果生命条已经为空,则游戏失败
 if (this.healthBar.outer.width < 0) {
   this.gameEnd(); // 游戏结束
   this.tip.text = "You Lose!"; // 修改提示文案
   this.healthBar.outer.width = 0; // 让血条停在0
   setTimeout(() => {
     this.app.ticker.stop(); // 1秒后停止运行游戏
 }, 1000);
 } 
  • 判断猎人有没有接触到宝盒,接触则让宝盒与猎人一起移动,看起来像是猎人带着宝盒
 // 判断探索者是否已经接触到宝盒
 const isTouch = this._hitTestRectangle(this.explorer, this.treasure);
 if (isTouch) {
   // 让宝盒跟着探索者一同移动
   this.treasure.x = this.explorer.x + 8;
   this.treasure.y = this.explorer.y + 8;
 } 
  • 游戏获胜的条件: 宝盒抵达门的地方
 // 判断宝盒子是否抵达了出口
 const isOut = this._hitTestRectangle(this.explorer, this.door);
 if (isOut) {
   this.gameEnd();
   this.tip.text = "You Win!!!";
 } 
  • 以上是游戏循环 gameLoop 方法的全部内容

边界检测

  • 自定义边界检测方法 _moveRangeCheck 保证猎人只能在墙壁内移动* 该方法需要两个参数,一个是想限制移动范围的精灵,一个是矩形区域对象,保证精灵只能在区域内移动* 该方法返回一个变量,可以判断是到达了哪个边界* 左边界: 精灵的 x 坐标小于矩形区域的 x 坐标* 右边界: 精灵的 x 坐标与精灵宽度的和大于矩形区域的宽度* 上边界: 精灵的 y 坐标小于矩形区域的 y 坐标* 下边界: 精灵的 y 坐标与精灵高的和大于矩形区域的高度
 _moveRangeCheck(sprite, container) {
   let boundary = ""; // 定义精灵抵达的边
 ​
   // 精灵抵达左边界
   if (sprite.x < container.x) {
     sprite.x = container.x;
     boundary = "left";
 }
   // 精灵抵达右边界
   if (sprite.x + sprite.width > container.width) {
     sprite.x = container.width - sprite.width;
     boundary = "right";
 }
   // 精灵抵达上边界
   if (sprite.y < container.y) {
     sprite.y = container.y;
     boundary = "top";
 }
   // 精灵抵达下编辑
   if (sprite.y + sprite.height > container.height) {
     sprite.y = container.height - sprite.height;
     boundary = "bottom";
 }
 ​
   return boundary;
 }, 

碰撞检测

  • 函数需要两个参数: 需要检测的两个精灵对象
  • 判断原理: 精灵1与精灵2之间的距离,小于精灵1与精灵2半宽和,则为碰撞
  • 如果两个精灵重叠,函数将返回 true
 _hitTestRectangle(moveSprite, otherSprite) {
   // 定义所需变量
   let hit = false; // 是否碰撞
   // 寻找每个精灵的中心点
   moveSprite.centerX = moveSprite.x + moveSprite.width / 2; // 移动精灵水平中心点
   moveSprite.centerY = moveSprite.y + moveSprite.height / 2; // 移动精灵垂直中心点
   // (32,232)
   otherSprite.centerX = otherSprite.x + otherSprite.width / 2; // 矩形水平中心点
   otherSprite.centerY = otherSprite.y + otherSprite.height / 2; // 矩形垂直中心点
   // console.log(`(${otherSprite.rectangle.x},${otherSprite.rectangle.y})`);
 ​
   // 找出每个精灵的半高和半宽
   moveSprite.halfWidth = moveSprite.width / 2; // 移动精灵半宽
   moveSprite.halfHeight = moveSprite.height / 2; // 移动精灵半高
   otherSprite.halfWidth = otherSprite.width / 2; // 矩形半宽
   otherSprite.halfHeight = otherSprite.height / 2; // 矩形半高
 ​
   // 移动精灵和矩形之间的距离
   const gapX = moveSprite.centerX - otherSprite.centerX;
   const gapY = moveSprite.centerY - otherSprite.centerY;
 ​
   // 算出移动精灵和矩形半宽半高的总和
   const combineWidth = moveSprite.halfWidth + otherSprite.halfWidth;
   const combineHeight = moveSprite.halfHeight + otherSprite.halfHeight;
   // 检查x轴上是否有碰撞
   // 判断两个精灵中心点间的距离,是否小于精灵半宽和
   if (Math.abs(gapX) < combineWidth) {
     // 检查y轴是否有碰撞
     hit = Math.abs(gapY) < combineHeight;
 } else {
     hit = false;
 }
   return hit;
 }, 

按键绑定

  • 按键绑定 _addKeyListener 方法是对操作的精灵,监听上下左右按键事件
 _addKeyListener(sprite) {
   let left = keyboard("ArrowLeft"),
     right = keyboard("ArrowRight"),
     up = keyboard("ArrowUp"),
     down = keyboard("ArrowDown");
 ​
   // 左
   left.press = () => {
     sprite.vx = -2;
     sprite.vy = 0;
 };
   left.release = () => {
     if (!right.isDown && sprite.vy === 0) sprite.vx = 0;
 };
 ​
   // 右
   right.press = () => {
     sprite.vx = 2;
     sprite.vy = 0;
 };
   right.release = () => {
     if (!left.isDown && sprite.vy === 0) sprite.vx = 0;
 };
 ​
   // 上
   up.press = () => {
     sprite.vy = -2;
     sprite.vx = 0;
 };
   up.release = () => {
     if (!down.isDown && sprite.vx === 0) sprite.vy = 0;
 };
 ​
   // 下
   down.press = () => {
     sprite.vy = 2;
     sprite.vx = 0;
 };
   down.release = () => {
     if (!up.isDown && sprite.vx === 0) sprite.vy = 0;
 };
 }, 

游戏结束

  • 游戏结束需要把主游戏场景隐藏,并将游戏结束场景显示,舞台背景色是改成黑色
 gameEnd() {
   this.gameScene.visible = false;
   this.app.renderer.backgroundColor = 0x000000;
   this.gameOverScene.visible = true;
 }, 
## 最后 整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。 **有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享**

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

tps://img-blog.csdnimg.cn/0c589e654cf64117a2573860a080e7b8.png#pic_center" style=“margin: auto” />

文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

猜你喜欢

转载自blog.csdn.net/web220507/article/details/129945445