前言
上期捷绑,我們實現(xiàn)了 canvas
, Background
模塊淳玩,這期我們將實現(xiàn) Bird
和 StickManager
模塊抵拘。
Bird
Bird
也是一個可移動的模塊陵珍,但是和之前的 Background
的移動模式不同寝杖,Bird
的移動是有加速度的。
另外的不同的一點是互纯,Bird
是一個需要考慮碰撞的模型:
- 邊界碰撞瑟幕。碰到頂部和底部,游戲結(jié)束留潦。
- 柱子碰撞只盹。碰到柱子,游戲結(jié)束兔院。
我們把 Bird
簡化成一個橢圓模型殖卑,這樣計算碰撞會簡單很多。
// 依然引入畫布
import canvas, { ctx } from './canvas';
// 向前飛的圖片坊萝,在速度為 0 的時候用
import forwardImage from './bird-forward.png';
// 向上飛的圖片孵稽,在速度小于 0 的時候用
import upImage from './bird-up.png';
// 向下飛的圖片许起,在速度大于 0 的時候用
import downImage from './bird-down.png';
// 移動模型的基礎(chǔ)類
import Movable from './Movable';
export default class Bird extends Movable {
constructor() {
// 調(diào)用父類的構(gòu)造函數(shù),初始化基礎(chǔ)的位置和速度屬性
super();
const { width, height } = canvas.getSize();
// 圓心菩鲜,我們把 `Bird` 固定在這個位置园细,移動背景來讓玩家覺得鳥在向前飛行
this.x = 200;
// 初始的高度位置在屏幕中心
this.y = height / 2;
// 圖片素材和真實展示的像素的比例
const ratio = 5;
// 鳥的模型
// 橢圓的橫軸長度
this.rx = 17 * ratio; // 17 * 12
// 橢圓的縱軸長度
this.ry = 12 * ratio;
// 初始的縱向速度,會在每次 `move` 時疊加差值
this.vy = 100;
// 重力加速度接校,不變
this.g = 2000;
// 加載圖像素材
const forward = new Image();
forward.src = forwardImage;
const up = new Image();
up.src = upImage;
const down = new Image();
down.src = downImage;
this.image = { forward, up, down };
}
move() {
// 調(diào)用父類的移動函數(shù)猛频,并且拿到時間差,用來計算新的縱向速度
const { diff } = super.move();
this.vy = this.vy + (this.g * diff / 1000);
}
moveUp() {
// 當(dāng)用戶點擊屏幕時馅笙,將鳥的縱向速度設(shè)置成向上飛的一個速度
this.vy = -500;
}
getYState() {
// 根據(jù)縱向速度伦乔,判斷這時需要使用什么圖片
if (this.vy > 0) {
return 'up';
}
if (this.vy < 0) {
return 'down';
}
return 'forward';
}
getImage() {
// 獲取展示的圖片
const state = this.getYState();
return this.image[state];
}
paint() {
// 畫個鳥厉亏,鳥的坐標(biāo) x 和 y 表示的是中心點
ctx.drawImage(this.getImage(), this.x - (this.rx / 2), this.y - (this.ry / 2), this.rx, this.ry);
}
}
修改 GameManager
的 paint
函數(shù)董习,添加 Bird
的應(yīng)用:
paint() {
canvas.clear();
this.background.move();
this.background.paint();
this.bird.move();
this.bird.paint();
}
這里需要注意繪制的順序,先畫 Background
爱只,再畫 Bird
皿淋,否則 Bird
會被 Background
覆蓋掉。
可以看到這個 Bird
直接掉了下去恬试。之后我們會添加用戶的交互窝趣,來讓它重新飛起來。
StickManager
這個類會管理所有的柱子训柴,所以我們還需要添加一個類:Stick
來表示一組柱子哑舒,包括一上一下的兩個柱子:
import canvas, { ctx } from './canvas';
// 上部分的那根柱子的圖片素材
import stickUpImageSrc from './stick-up.png';
// 下部分的那根柱子的圖片素材
import stickDownImageSrc from './stick-down.png';
import Movable from './Movable';
export default class Stick extends Movable {
constructor(x) {
// 在使用柱子的時候,我們會從外面?zhèn)魅胫拥臋M坐標(biāo)
super();
// 加載圖片素材
const stickUpImage = new Image();
stickUpImage.src = stickUpImageSrc;
const stickDownImage = new Image();
stickDownImage.src = stickDownImageSrc;
this.image = { up: stickUpImage, down: stickDownImage };
// 簡單地設(shè)置上下兩根柱子之間的空隙的高度
this.gap = 400;
// 設(shè)置柱子的橫向速度
this.vx = -500;
const { width, height } = canvas.getSize();
// 圖片素材和實際展示的像素大小的比例關(guān)系
const ratio = 5;
this.width = {
up: 26 * ratio, // 26 * 135
down: 26 * ratio, // 26 * 121
};
this.height = {
up: 135 * ratio,
down: 121 * ratio,
};
// 初始的柱子的位置幻馁。橫坐標(biāo)和縱坐標(biāo)表示上下兩根柱子之間的空隙的中心位置
this.x = x;
this.y = height / 2 + (Math.random() * 400 - 200);
}
paint() {
// 畫出上下兩根柱子
ctx.drawImage(this.image.up, this.x - this.width.up / 2, this.y - this.gap / 2 - this.height.up, this.width.up, this.height.up);
ctx.drawImage(this.image.down, this.x - this.width.down / 2, this.y + this.gap / 2, this.width.down, this.height.down);
}
isOutOfScreen() {
// 提供方法來判斷柱子是否已經(jīng)移出了屏幕
return this.x + this.width.up / 2 < 0 && this.x + this.width.down / 2 < 0;
}
}
然后在 StickManager
中使用:
import canvas from './canvas';
import Stick from './Stick';
export default class StickManager {
constructor() {
// 當(dāng)前屏幕內(nèi)的 `Stick` 列表
this.sticks = [];
const { width } = canvas.getSize();
// 初始時添加三根柱子洗鸵,分別在屏幕最右側(cè),最右側(cè)的 1.5 倍位置和最右側(cè)的 2 倍位置
this.create(width);
this.create(width + width / 2);
this.create(width * 2);
}
getSticks() {
// 對外提供獲取 sticks 的方法
return this.sticks;
}
create(x) {
// 創(chuàng)建 `Stick`仗嗦,并記錄到列表中
this.sticks.push(new Stick(x));
}
move() {
// 移動每根柱子膘滨,并且判斷是否柱子已經(jīng)移動到屏幕外面了,如果移出了屏幕稀拐,則從列表中去除火邓。這里是用了在 `Stick` 中定義的 `isOutOfScreen` 方法
this.sticks = this.sticks.filter((stick) => {
stick.move();
return !stick.isOutOfScreen();
});
// 保持游戲中有三根柱子,如果列表長度小于 3德撬,則新增一根柱子
if (this.sticks.length < 3) {
const { width } = canvas.getSize();
this.create(width * 3 / 2);
}
}
paint() {
// 畫出列表中的每根柱子
return this.sticks.map((stick) => {
return stick.paint();
});
}
}
修改 GameManager
的 paint
函數(shù)铲咨,添加 StickManager
的應(yīng)用:
paint() {
canvas.clear();
this.background.move();
this.background.paint();
this.sticks.move();
this.sticks.paint();
this.bird.move();
this.bird.paint();
}
可以看到柱子能正確地移動和生成。
TBC