- 在准备好背景和地面之后,接下来就可以实现游戏的准备场景,还有控制小鸟的飞行
准备场景
- 准备场景由一个
Get Ready!文字图片和TAP提示图片组成 - 创建一个
ReadyScene.js文件,实现ReadyScene类,由于其只作显示,所以只需继承容器Container类,最后把这两个精灵对象添加到容器,并将整个容器放置在适当的位置即可 - 其所需的纹理是在
Assets类中预加载好的,需要在主模块中传进来
import { Container, Sprite } from "pixi.js";
class ReadyScene extends Container {
constructor(options) {
super();
this.tip = options.texture.tip;
this.tap = options.texture.tap;
this.gameWidth = options.width; // 需要得知游戏的宽度
this.visible = true; // 控制场景的可见性
this.initReadyScene(); // 初始化准备场景
}
}
- 声明
initReadyScene,用于初始化准备场景的两个精灵
// 加载场景显示的元素
initReadyScene() {
this.initSceneEle(this.tip, 400);
this.initSceneEle(this.tap, 530);
}
initSceneEle(texture, h) {
const sprite = new Sprite(texture);
sprite.scale.set(0.7, 0.7);
sprite.position.set((this.gameWidth - sprite.width) / 2, h);
this.addChild(sprite);
}
- 最后完善
Game类中的initScene方法,将初始化场景实例化并添加到容器中,最后在舞台上就可以看到
// 初始化游戏准备场景
initScene() {
this.readyScene = new ReadyScene({
width: this.width,
texture: this.assets.ready,
});
this.stage.addChild(this.readyScene);
}
创建小鸟
-
小鸟是游戏的主角,并且小鸟和其他显示对象不同,它会扇动翅膀飞行,因此需要使用动画精灵类
AnimatedSprite来创建小鸟AnimatedSprite类需要一个纹理数组作为基础纹理,而这个纹理数组就是精灵动画的每一帧
-
创建
Bird.js文件,实现一个Bird类,继承自AnimatedSprite,小鸟需要一些初始状态,比如开始的x、y坐标,飞行起始时间,扇动翅膀的频率等 -
由于小鸟在准备状态和飞行状态下,会以嘴巴附近作为基准点旋转,所以一开始还需要设置小鸟的基准点
import { AnimatedSprite } from "pixi.js";
class Bird extends AnimatedSprite {
constructor(options) {
super(options.texture);
this.gameHeight = options.height; // 记录游戏的高度
this.groundY = options.groundY; // 存储地面的y坐标
// 设置小鸟基准点
this.anchor.set(1, 0.5);
// 设置小鸟的大小
this.scale.set(0.8, 0.8);
// 设置小鸟的位置
this.position.set(0, (this.gameHeight - this.height) / 2);
this.startX = 150; // 小鸟的起始x坐标
this.startY = (this.gameHeight - this.height) / 2; // 小鸟的起始y坐标
this.flyStartTime = 0; // 小鸟飞行起始时间
this.animationSpeed = 0.2; // 设置扇动翅膀的频率
this.isDead = true; // 小鸟是否已死亡
this.isUp = false; // 小鸟是否正在上升
}
-
在游戏中小鸟一共有以下三个状态:
- 准备状态: 此时小鸟没有飞行,只是轻微的上下漂浮
- 开始飞行: 每次点击画面时,小鸟就会往上飞一段距离然后下坠
- 飞行过程: 在整个飞行过程中,只需要控制小鸟的姿态和
y轴坐标(x轴不变)
准备状态
-
先声明准备状态的
birdReady方法,该方法中设置小鸟的x、y的位置,并重置小鸟的rotation为0 -
调用
AnimatedSprite上的play()方法,让小鸟煽动翅膀 -
最后使用
pixi的pixi-ease缓动库,让小鸟上下漂浮- 安装
pixi-ease:npm run pixi-ease
- 安装
import { Ease } from "pixi-ease";
// 初始化小鸟的准备状态
birdReady() {
//设置起始坐标
this.x = this.startX;
this.y = this.startY;
this.rotation = 0; // 旋转角度重置
this.play(); // 小鸟扇动翅膀
// 让小鸟上下浮动
this.ease = new Ease();
this.ease.add(
this,
{ rotation: -0.15, y: this.y + 4 },
{ duration: 400, reverse: true, repeat: true }
);
}
- 接着在
Game.js中,实例化Bird类
// 初始化小鸟
initBird() {
this.bird = new Bird({
height: this.height,
groundY: this.ground.y,
texture: this.assets.bird,
});
this.bird.zIndex = 1; // 设置小鸟的层级
this.stage.addChild(this.bird);
}
- 然后再
Game.js声明一个gameReady方法,初始化游戏的准备状态
// 游戏准备状态
gameReady() {
// 准备场景可见
this.readyScene.visible = true;
// 设置游戏状态为ready
this.state = "ready";
// 初始化小鸟为准备状态
this.bird.birdReady();
}
- 当
gameReady方法在createGame中调用之后,游戏的准备状态就会呈现
开始飞行
-
当玩家点击屏幕后,小鸟开始往上飞行,往上一段距离后,接着开始往下掉落,之后玩家每点击一次屏幕,小鸟都会重复这个过程
-
这个过程就看作玩家点击一次,小鸟做一次竖直上抛运动,接着自由落体,这样就需要声明三个因素:
- 小鸟的每次往上飞的距离
FLY_HEIGHT - 小鸟每次往上飞的初速度
INIT_VELOCITY - 小鸟的重力加速度
GY
- 小鸟的每次往上飞的距离
-
于是在
Bird类中,定义小鸟的三个因素- 重力加速度
GY取10m/s²,在游戏中以毫秒为单位,所以要除以1000,游戏中的重力加速度不需要和现实中这么大,最后取3/10就行 - 每次点击屏幕往上飞的距离是
50 - 知道加速度和飞行距离,那么根据速度位移公式
初速度² - 末速度² = 2 * 加速度 * 位移,竖直上抛末速度为0,这样可求得 ,初速度 =(2 * 加速度 * 位移)^1/2
- 重力加速度
// 小鸟的重力加速度
static GY = (10 / 1000) * 0.3;
// 小鸟每次往上飞的距离
static FLY_HEIGHT = 50;
// 小鸟往上飞的初速度
static INIT_VELOCITY = Math.sqrt(2 * Bird.FLY_HEIGHT * Bird.GY);
- 在
Bird.js中声明birdStartFly方法,对小鸟飞行的状态进行初始化,然后使用Ticker和Ease缓动,循环小鸟的飞行过程
// 小鸟开始飞
birdStartFly() {
this.isDead = false; // 小鸟状态为未死亡
this.animationSpeed = 0.4; // 飞行时翅膀扇快一点,提高动画帧速度
this.flyStartY = this.y; // 记录小鸟起始的飞行位置
this.flyStartTime = +new Date(); // 记录小鸟开始飞行时间
this.ease.destroy(); // 销毁所有的缓动动画
// 飞行循环
Ticker.shared.add(() => this.birdFlying());
}
飞行过程
- 声明一个
birdFlying方法,通过Ticker来不断执行该方法,进而实现小鸟的飞行过程 - 该方法首先判断小鸟是否已经死亡,如果死亡则直接
return - 首先算出小鸟的飞行时间,然后可以通过时间不断更新,结合时间位移公式
位移 = 初速度 * 时间 - 1/2 * 加速度 * 时间² - 那么小鸟的起始飞行位置
flyStartY与飞行距离distance的差值,就是小鸟当前所处的高度 - 并且玩家每点击一次,开始飞行时间
flyStartTime都会在birdStartFly方法中更新为当前时刻,那么飞行距离就会重新开始计算
birdFlying() {
if (this.isDead) return;
// 飞行时间
const time = +new Date() - this.flyStartTime;
// 垂直飞行距离
const distance = Bird.INIT_VELOCITY * time - 0.5 * Bird.GY * time * time;
// 小鸟当前所处的高度
const birdY = this.flyStartY - distance;
// 判断小鸟是否调到地面了
// ...
}
-
接着在
birdFlying方法内判断,小鸟当前所处的高度birdY是否到达了地面- 若没有到达地面,则更新小鸟的
y坐标,并且当小鸟的飞行距离大于0时,则小鸟此时往上飞,改变isUp为true,并让小鸟角度上仰;飞行距离小于0则是往下掉,改变isUp为false,并让小鸟角度过渡为向下俯冲 - 若小鸟到达了地面,则小鸟已经死亡,直接让小鸟的
y坐标与地面相等,最后停止翅膀扇动和销毁缓动
- 若没有到达地面,则更新小鸟的
-
在
birdFlying方法中接着完善以下内容
if (birdY <= this.groundY) {
// 小鸟未落地
this.y = birdY;
if (distance > 0 && !this.isUp) {
this.isUp = true;
this.ease.add(this, { rotation: -0.3 }, { duration: 200 });
} else if (distance < 0 && this.isUp) {
this.isUp = false;
this.ease.add(this, { rotation: 1.3 }, { duration: this.groundY - this.y });
}
} else {
// 小鸟已经落地,即死亡
this.y = this.groundY;
this.isDead = true; // 改变小鸟死亡状态
this.animationSpeed = 0; // 动画速度为0
this.stop(); // 销毁翅膀扇动动画
this.ease.destroy(); // 销毁缓动动画
}
- 接下来可以给全局舞台
stage绑定一个click(浏览器)或tap(手机模式),点玩家点击后开始游戏
this.app.stage.interactive = true;
this.app.stage.on("tap", this.onUserClick);
- 声明
onUserClick,让游戏开启起来,并且点击一次调用小鸟飞行方法一次,小鸟就会不断重复做竖直上抛和自由落体运动
onUserClick() {
if (this.game.state === "over") return; // 如果游戏结束,直接return
if (this.game.state !== "running") this.game.gameStart(); // 更改游戏为开启状态
this.game.bird.birdStartFly();
},
- 在
Game.js中,更改游戏当前状态
gameStart() {
// 隐藏准备场景
this.readyScene.visible = false;
// 游戏状态为running
this.state = "running";
}
- 至此,点击屏幕后,游戏开始,之后的每次点击屏幕,小鸟都会从当前位置重新开始做竖直上抛运动