前言
上期,我們介紹了 canvas
, GameManager
模塊,這期我們實現(xiàn)一下上次拆分的 canvas
和 Background
模塊爸邢。
canvas
由于畫布在全局是單一的科乎,所以在實現(xiàn) canvas
模塊時密强,我們采用單例模式宴杀。
新建 src/FlappyBird/canvas.js
:
// 定義 Canvas 類
class Canvas {
constructor() {
// 使用選擇器選擇 canvas DOM 節(jié)點
const canvas = document.querySelector('canvas');
// 根據(jù)設(shè)備分辨率設(shè)置寬高
canvas.height = window.innerHeight * window.devicePixelRatio;
canvas.width = window.innerWidth * window.devicePixelRatio;
this.canvas = canvas;
}
// 提供獲取 context 的方法
getCtx() {
return this.canvas.getContext('2d');
}
// 提供清空屏幕展示內(nèi)容的方法
clear() {
const ctx = this.getCtx();
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
// 提供獲取 canvas 實際寬高的方法
getSize() {
return { width: this.canvas.width, height: this.canvas.height };
}
}
// 實例化 Canvas 類
const canvas = new Canvas();
// 暴露單例
export default canvas;
// 將 ctx 暴露出去,為了方便使用
export const ctx = canvas.getCtx();
為了讓 canvas
占滿整個屏幕拾因,我們需要設(shè)置一些 css 樣式旺罢。在 src/FlappyBird/index.pcss
中添加:
/* 讓 html 占滿整個頁面的高度,默認 html 占了頁面的全寬绢记,因此不需要額外設(shè)置 */
html {
height: 100%;
}
/* 去掉 body 上默認的 margin扁达,讓 body 的高度也占滿整個頁面 */
body {
margin: 0;
height: 100%;
}
/* 讓 canvas 元素變成塊級,占滿整個頁面 */
canvas {
display: block;
width: 100%;
height: 100%;
}
Movable
在上次提到的模塊拆分時蠢熄,我們設(shè)想了 StickManager
跪解,Input
,canvas
签孔,Bird
叉讥,Background
這幾個模塊。其中 StickManager
饥追,Bird
图仓,Background
三個模塊都需要移動,因此我們提取了公用的移動類 Movable
但绕,這三個模塊繼承后可以共享移動的屬性和方法救崔。
// 定義基礎(chǔ)類
export default class Movable {
constructor() {
// 構(gòu)造函數(shù)中保存這個元素的橫向坐標(biāo)和縱向坐標(biāo) x 和 y,和橫向移動速度和縱向移動速度 vx 和 vy捏顺。
// 這里都設(shè)置成 `0`六孵,也就是默認不移動
this.x = 0;
this.y = 0;
this.vx = 0;
this.vy = 0;
}
move() {
const diff = 10;
this.x += this.vx * diff / 1000;
this.y += this.vy * diff / 1000;
return { diff };
}
}
其中橫向坐標(biāo)和縱向坐標(biāo)都是 canvas 坐標(biāo)系下的坐標(biāo),也就是從左到右 x 增加幅骄,從上到下 y 增加劫窒。
移動函數(shù)會在每次被調(diào)用的時候得到一個時間差。一種做法是時間差是真實時間的函數(shù)昌执,也就需要每次移動時需要記錄下當(dāng)前的時間烛亦,然后和上次的時間相減诈泼,得到的差值做一次比例運算:const diff = (currentTime - prevTime) * ratio;
。第二種做法是固定差值煤禽,也就是認為每次 move
函數(shù)被調(diào)用和上次被調(diào)用之間的時間差固定:const diff = fixedValue
铐达。這里我們簡單地采用第二種方式,并且讓 fixedValue
等于 10
檬果。
拿到時間差后瓮孙,計算下一個位置的坐標(biāo),并且更新到自身的屬性上选脊,這樣在具體實例調(diào)用 paint
函數(shù)繪制圖像的時候杭抠,直接讀取坐標(biāo)畫到 canvas
上。這里的物體運動有兩種方式恳啥,勻速運動和加速度運動偏灿。運動的微分公式是 (delta s) = v * (delta t)
。在加速度運動情況下钝的,只要每次修改速度就可以了 (delta v) = a * (delta t)
翁垂。因此這里只要把位置的變化累加到原先的位置上 x += vx * diff
。這里的速度的單位是像素每秒硝桩,diff
的單位是毫秒沿猜,所以需要除以 1000
。最后把 diff
值返回出去碗脊,以便在外部計算速度差啼肩。
Background
有了 Movable
后,就可以實現(xiàn)背景了衙伶。
// 引入 canvas 和 ctx
import canvas, { ctx } from './canvas';
// 背景圖
import image from './background.png';
// 引入 `Movable
import Movable from './Movable';
// 聲明 `Background` 類繼承 `Movable` 類
export default class Background extends Movable {
constructor() {
// 調(diào)用父類的構(gòu)造函數(shù)
super();
// 加載背景圖資源
this.image = new Image();
this.image.src = image;
const { width, height } = canvas.getSize();
// 背景圖的寬高是按照整個屏幕的寬高設(shè)計的
this.width = width;
this.height = height;
// 覆蓋掉默認的橫向移動速度祈坠,這里的背景圖需要向左移動,所以 `vx` 是負值
this.vx = -100;
}
move() {
// 調(diào)用父類的移動函數(shù)
super.move();
if (this.x < -this.width) {
this.x += this.width;
}
}
paint() {
ctx.drawImage(this.image, this.x, 0, this.width, this.height);
ctx.drawImage(this.image, this.x + this.width, 0, this.width, this.height);
}
}
畫背景圖時痕支,我們用兩個圖片首位相連的方式颁虐。當(dāng)一張背景圖移出了屏幕,就讓它變成下一個背景圖:x = width
卧须。
在 src/FlappyBird/GameManager.js
中引入 Background
另绩,看下效果:
import canvas from './canvas';
import Background from './Background';
export default class GameManager {
constructor() {
// 初始化背景
this.background = new Background();
}
paint() {
// 每次重繪整個畫布。先清空畫布的所有東西花嘶,再移動背景笋籽,最后畫背景
canvas.clear();
this.background.move();
this.background.paint();
}
loop() {
this.paint();
requestAnimationFrame(() => {
this.loop();
});
}
start() {
this.loop();
}
}
最后在 src/FlappyBird/index.js
中,調(diào)用 GameManager
初始化一個游戲:
// 基礎(chǔ)樣式
import './index.pcss';
import GameManager from './GameManager';
let gm = new GameManager();
// 開始游戲
gm.start();
可以看到背景一直向左移動椭员。
TBC