'canvas飛機大戰遊戲案例'

面向對象程序編程 程序員Doctor 2019-07-19
"

引言

本項目使用H5 canvas繪製飛機大戰遊戲,使用ES6 class面向對象開發,目的在於練習js面向對象開發的能力,熟練使用canvas api。

一、案例演示

飛機大戰遊戲,使用canvas繪製,程序僅作練習用,實現主要功能,沒有實現遊戲開始界面、結束判斷等,效果如下圖

"

引言

本項目使用H5 canvas繪製飛機大戰遊戲,使用ES6 class面向對象開發,目的在於練習js面向對象開發的能力,熟練使用canvas api。

一、案例演示

飛機大戰遊戲,使用canvas繪製,程序僅作練習用,實現主要功能,沒有實現遊戲開始界面、結束判斷等,效果如下圖

canvas飛機大戰遊戲案例


二、知識點

  • canvas的基本使用
  • 動畫requestAnimationFrame的基本使用
  • 碰撞、邊界檢測
  • js面向對象編程(OOP)
  • 遊戲按鍵事件處理


三、案例中的對象

  • Stage 舞臺
  • SceneBase 場景,場景中的基類
  • GameScene 遊戲場景
  • Sprite 精靈類,遊戲中的基類
  • Enemy 敵機類
  • Player 玩家戰機類
  • Bullet 子彈類
  • Particle 粒子類


四、實現思路

1. Sprite類

遊戲所有實體的基類(Player | Enemy | Bullet | Particle均繼承此類)

抽象出公共的屬性方法,如下:




constructor() {
this.unit = 10; // 最小單元
this.vx = 0.2; // x方向速度
this.vy = 0.2; // y方向速度
this.sizeX = 2;
this.sizeY = 2;
this.posX = 0; // x座標
this.posY = 0; // y座標
this.color = '#369';
this.alive = true; // 是否存活
this.birth = new Date();
}
getWidth() {...} // 獲取對象真實大小
render() {...} // 把此對象繪製在canvas上

這裡用unit定義了遊戲的最小單元,因為這個遊戲都是正方形,我不想每次都寫20、50、100像素,這樣看著數字太大了,定義一個最小單元,用getWidth()通過計算獲取實際的大小,即unit * size

2. Particle類

用於控制遊戲中的粒子效果,繼承自Sprite類,擴展方法:


this.life // 對象生命,用於控制何時銷燬對象
update() { // 更新對象狀態並調用父類的render方法
if (new Date() - this.birth > this.life) {
this.alive = false;
}
this.posX += this.vx;
this.posY += this.vy;
this.render();
}


3. Bullet類

遊戲中的子彈類,負責渲染遊戲中的子彈,繼承自Sprite類,擴展方法:


constructor(scene) {
super();
this.scene = scene;
this.vx = 0;
this.vy = -1;
this.state = new Date();
this.particleCB = 500;
this.type = 0; // 區分子彈類型 0 enemy, 1 player
}
collide() {...} // 碰撞檢測
createParticle() {...} // 子彈播放的例子效果
update() {...} // 更新對象狀態並調用父類的render方法

子彈類中的碰撞檢測僅負責判斷與場景的碰撞


// 傳入場景寬高

collide(w, h) {

if (this.posX < 0 - this.getWidth() ||

this.posX > w + this.getWidth() ||

this.posY < 0 - this.getWidth() ||

this.posY > h + this.getWidth()) {

this.alive = false;

}

}

子彈運行過程會有粒子效果,這個功能在createParticle()實現




let p = new Particle({...}); // new一個實例
this.scene.particles.push(p); // 把粒子加入場景,統一處理
update() { // 更新對象狀態
if (new Date() - this.state >= this.particleCB) {
this.createParticle();
this.state = new Date();
}
this.posX += this.vx;
this.posY += this.vy;
this.render();
}

4. Enemy類

遊戲中的敵機類,負責渲染遊戲中的敵機,繼承自Sprite類,擴展方法:




constructor(scene) {

super()

this.scene = scene

this.dx = .005 // x方向的加速度

this.dy = 0 // y方向的加速度

this.changeCB = 500 // 敵機改變方向的CB

this.canChange = true

this.init()

}

init() {

this.bulletCB = 2000 // 敵機發射子彈的CB

this.attackTime = -1 // 可以攻擊的時間,小於0即可攻擊

this.boomConfig = new Map([ // 配置敵機被擊中後的爆炸效果

[2, [1.5, 1, 0.5]], // 敵機大小為2,爆炸後分成1.5 1 0.5粒子大小

[3, [2, 2, 1]] // 敵機大小為3,爆炸後分成3 3 1粒子大小

])

}

collide() {...} // 碰撞檢測

attack() {...} // 發射子彈

boom() {...} // 敵機中彈

changeX() {...} // 敵機AI

changeY() {...} // 敵機AI

change() {...} // 敵機AI

update() {...} // 更新對象狀態並調用父類的render方法

複製代碼boomConfig中之所以那樣配置,是因為在場景類中初始化敵機只有三種大小,這裡沒有考慮那麼靈活,可以在類中加入一個minSize和maxSize控制生成敵機答大小,這裡就不做那麼複雜了。

// GameScene -> createEnemy()

e.sizeX = Math.random() * 3 + 1 | 0;

5. Player類

Player類則相對複雜很多,有Enemy類的屬性方法外,還需註冊鍵盤事件,(Enemy和Player類這裡還可以抽出一個基類,這裡就不搞的那麼複雜了)


 init() {
this.keydowns = {};
this.actions = {};
this.bulletCB = 800;
this.attackTime = -1;
this.registAction('a', this.moveLeft.bind(this));
this.registAction('d', this.moveRight.bind(this));
this.registAction('w', this.moveUp.bind(this));
this.registAction('s', this.moveDown.bind(this));
this.bindEvent();
}
registAction(key, callback) {
this.actions[key] = callback;
}
bindEvent() {
window.addEventListener('keydown', ev => {
this.keydowns[ev.key] = true;
})
window.addEventListener('keyup', ev => {
this.keydowns[ev.key] = false;
})
}
update() {
this.edge(); // 邊界檢測,限制玩家在場景內
this.damage(); // 傷害檢測,子彈、敵機
let actions = Object.keys(this.actions);
actions.forEach(k => this.keydowns[k] && this.actions[k]())
this.attack();
this.render();
}

按鍵響應通過把按鍵狀態和回調函數存在對象的方式,使用registAction註冊相應事件,這樣代碼看上去會簡潔很多,僅需在update()中遍歷即可。

而對於邊界檢測和傷害檢測則本質是一個區間判斷,不同的是,邊界檢測是限制在一個區間,也就是如果不在這個區間需要限制(或者說是更正);而傷害檢測則是如果在這個區間那麼就觸發對應的事件,如我方子彈與敵機碰撞,則敵機炸燬,子彈銷燬。

下面是圖示,其實簡單的碰撞檢測都是這樣子的,比如打磚塊

"

引言

本項目使用H5 canvas繪製飛機大戰遊戲,使用ES6 class面向對象開發,目的在於練習js面向對象開發的能力,熟練使用canvas api。

一、案例演示

飛機大戰遊戲,使用canvas繪製,程序僅作練習用,實現主要功能,沒有實現遊戲開始界面、結束判斷等,效果如下圖

canvas飛機大戰遊戲案例


二、知識點

  • canvas的基本使用
  • 動畫requestAnimationFrame的基本使用
  • 碰撞、邊界檢測
  • js面向對象編程(OOP)
  • 遊戲按鍵事件處理


三、案例中的對象

  • Stage 舞臺
  • SceneBase 場景,場景中的基類
  • GameScene 遊戲場景
  • Sprite 精靈類,遊戲中的基類
  • Enemy 敵機類
  • Player 玩家戰機類
  • Bullet 子彈類
  • Particle 粒子類


四、實現思路

1. Sprite類

遊戲所有實體的基類(Player | Enemy | Bullet | Particle均繼承此類)

抽象出公共的屬性方法,如下:




constructor() {
this.unit = 10; // 最小單元
this.vx = 0.2; // x方向速度
this.vy = 0.2; // y方向速度
this.sizeX = 2;
this.sizeY = 2;
this.posX = 0; // x座標
this.posY = 0; // y座標
this.color = '#369';
this.alive = true; // 是否存活
this.birth = new Date();
}
getWidth() {...} // 獲取對象真實大小
render() {...} // 把此對象繪製在canvas上

這裡用unit定義了遊戲的最小單元,因為這個遊戲都是正方形,我不想每次都寫20、50、100像素,這樣看著數字太大了,定義一個最小單元,用getWidth()通過計算獲取實際的大小,即unit * size

2. Particle類

用於控制遊戲中的粒子效果,繼承自Sprite類,擴展方法:


this.life // 對象生命,用於控制何時銷燬對象
update() { // 更新對象狀態並調用父類的render方法
if (new Date() - this.birth > this.life) {
this.alive = false;
}
this.posX += this.vx;
this.posY += this.vy;
this.render();
}


3. Bullet類

遊戲中的子彈類,負責渲染遊戲中的子彈,繼承自Sprite類,擴展方法:


constructor(scene) {
super();
this.scene = scene;
this.vx = 0;
this.vy = -1;
this.state = new Date();
this.particleCB = 500;
this.type = 0; // 區分子彈類型 0 enemy, 1 player
}
collide() {...} // 碰撞檢測
createParticle() {...} // 子彈播放的例子效果
update() {...} // 更新對象狀態並調用父類的render方法

子彈類中的碰撞檢測僅負責判斷與場景的碰撞


// 傳入場景寬高

collide(w, h) {

if (this.posX < 0 - this.getWidth() ||

this.posX > w + this.getWidth() ||

this.posY < 0 - this.getWidth() ||

this.posY > h + this.getWidth()) {

this.alive = false;

}

}

子彈運行過程會有粒子效果,這個功能在createParticle()實現




let p = new Particle({...}); // new一個實例
this.scene.particles.push(p); // 把粒子加入場景,統一處理
update() { // 更新對象狀態
if (new Date() - this.state >= this.particleCB) {
this.createParticle();
this.state = new Date();
}
this.posX += this.vx;
this.posY += this.vy;
this.render();
}

4. Enemy類

遊戲中的敵機類,負責渲染遊戲中的敵機,繼承自Sprite類,擴展方法:




constructor(scene) {

super()

this.scene = scene

this.dx = .005 // x方向的加速度

this.dy = 0 // y方向的加速度

this.changeCB = 500 // 敵機改變方向的CB

this.canChange = true

this.init()

}

init() {

this.bulletCB = 2000 // 敵機發射子彈的CB

this.attackTime = -1 // 可以攻擊的時間,小於0即可攻擊

this.boomConfig = new Map([ // 配置敵機被擊中後的爆炸效果

[2, [1.5, 1, 0.5]], // 敵機大小為2,爆炸後分成1.5 1 0.5粒子大小

[3, [2, 2, 1]] // 敵機大小為3,爆炸後分成3 3 1粒子大小

])

}

collide() {...} // 碰撞檢測

attack() {...} // 發射子彈

boom() {...} // 敵機中彈

changeX() {...} // 敵機AI

changeY() {...} // 敵機AI

change() {...} // 敵機AI

update() {...} // 更新對象狀態並調用父類的render方法

複製代碼boomConfig中之所以那樣配置,是因為在場景類中初始化敵機只有三種大小,這裡沒有考慮那麼靈活,可以在類中加入一個minSize和maxSize控制生成敵機答大小,這裡就不做那麼複雜了。

// GameScene -> createEnemy()

e.sizeX = Math.random() * 3 + 1 | 0;

5. Player類

Player類則相對複雜很多,有Enemy類的屬性方法外,還需註冊鍵盤事件,(Enemy和Player類這裡還可以抽出一個基類,這裡就不搞的那麼複雜了)


 init() {
this.keydowns = {};
this.actions = {};
this.bulletCB = 800;
this.attackTime = -1;
this.registAction('a', this.moveLeft.bind(this));
this.registAction('d', this.moveRight.bind(this));
this.registAction('w', this.moveUp.bind(this));
this.registAction('s', this.moveDown.bind(this));
this.bindEvent();
}
registAction(key, callback) {
this.actions[key] = callback;
}
bindEvent() {
window.addEventListener('keydown', ev => {
this.keydowns[ev.key] = true;
})
window.addEventListener('keyup', ev => {
this.keydowns[ev.key] = false;
})
}
update() {
this.edge(); // 邊界檢測,限制玩家在場景內
this.damage(); // 傷害檢測,子彈、敵機
let actions = Object.keys(this.actions);
actions.forEach(k => this.keydowns[k] && this.actions[k]())
this.attack();
this.render();
}

按鍵響應通過把按鍵狀態和回調函數存在對象的方式,使用registAction註冊相應事件,這樣代碼看上去會簡潔很多,僅需在update()中遍歷即可。

而對於邊界檢測和傷害檢測則本質是一個區間判斷,不同的是,邊界檢測是限制在一個區間,也就是如果不在這個區間需要限制(或者說是更正);而傷害檢測則是如果在這個區間那麼就觸發對應的事件,如我方子彈與敵機碰撞,則敵機炸燬,子彈銷燬。

下面是圖示,其實簡單的碰撞檢測都是這樣子的,比如打磚塊

canvas飛機大戰遊戲案例


6. Scene類

場景類這裡抽出了一個基類,SceneBase,當時考慮後期擴展會有遊戲主場景,開始場景,設置場景等,所以在這裡抽離了一層。

SceneBase類也沒定義多少內容,有兩個靜態屬性,WIDTH和HEIGHT,也就是場景大小,像這類屬性沒必要放在一個個實例中,所有實例共用一個即可,這類屬性一般可以放在靜態屬性中。

真正是GameScene類在負責整個遊戲的運作,它繼承自SceneBase類,也是遊戲的引擎。

init方法則是初始化了遊戲的各個參數,


init() {
super.init();
this.setEnemyCD = 3000; // 敵機出現的CB
this.createEnemyTime = -1
this.bullets = []; // 場景中的所有子彈
this.enemys = []; // 場景中的所有敵人
this.particles = []; // 場景中的粒子
this.initPlayer();
this.initEnemy();
this.warn = false; // 玩家是否被擊中
this.killCount = 0;
}


update方法則是統一處理場景所有實體的更新


 update() {
super.update();
this.initEnemy();
if (this.warn) {
this.warning();
this.warn = false;
} else {
ctx.clearRect(0, 0, SceneBase.WIDTH, SceneBase.HEIGHT);
}
this.player.update();
this.updateBullet();
this.updateParticle();
this.updateEnemy();
this.updateInfo();
}


7. Stage類

負責管理所有的場景,


 constructor() {
this.manager = []
this.init()
}
add() {...} // 添加場景對manager
remove() {...} // 對manager場景移除
static freeze() {...} // 對manager場景凍結
static unfreeze() {...} // 對manager場景解凍
bindEvent() {...} // 一些事件響應

這個東西在此次案例並沒太多體現,是我在層疊消融遊戲案例中抽離出來的,運用在多場景切換中,比如一個是遊戲遊玩模式,一個是遊戲自定義編輯模式,是這種情況下使用的。

您可以查看關於 層疊消融遊戲 檢測與目標圖形匹配的算法

github:canvas飛機大戰(今天github山不去,代碼沒提交上去)

最後小編整理一些編程資料希望幫助到大家

領取方法:關注小編+轉發文章——私信小編(學習)“即可免費獲得”

"

引言

本項目使用H5 canvas繪製飛機大戰遊戲,使用ES6 class面向對象開發,目的在於練習js面向對象開發的能力,熟練使用canvas api。

一、案例演示

飛機大戰遊戲,使用canvas繪製,程序僅作練習用,實現主要功能,沒有實現遊戲開始界面、結束判斷等,效果如下圖

canvas飛機大戰遊戲案例


二、知識點

  • canvas的基本使用
  • 動畫requestAnimationFrame的基本使用
  • 碰撞、邊界檢測
  • js面向對象編程(OOP)
  • 遊戲按鍵事件處理


三、案例中的對象

  • Stage 舞臺
  • SceneBase 場景,場景中的基類
  • GameScene 遊戲場景
  • Sprite 精靈類,遊戲中的基類
  • Enemy 敵機類
  • Player 玩家戰機類
  • Bullet 子彈類
  • Particle 粒子類


四、實現思路

1. Sprite類

遊戲所有實體的基類(Player | Enemy | Bullet | Particle均繼承此類)

抽象出公共的屬性方法,如下:




constructor() {
this.unit = 10; // 最小單元
this.vx = 0.2; // x方向速度
this.vy = 0.2; // y方向速度
this.sizeX = 2;
this.sizeY = 2;
this.posX = 0; // x座標
this.posY = 0; // y座標
this.color = '#369';
this.alive = true; // 是否存活
this.birth = new Date();
}
getWidth() {...} // 獲取對象真實大小
render() {...} // 把此對象繪製在canvas上

這裡用unit定義了遊戲的最小單元,因為這個遊戲都是正方形,我不想每次都寫20、50、100像素,這樣看著數字太大了,定義一個最小單元,用getWidth()通過計算獲取實際的大小,即unit * size

2. Particle類

用於控制遊戲中的粒子效果,繼承自Sprite類,擴展方法:


this.life // 對象生命,用於控制何時銷燬對象
update() { // 更新對象狀態並調用父類的render方法
if (new Date() - this.birth > this.life) {
this.alive = false;
}
this.posX += this.vx;
this.posY += this.vy;
this.render();
}


3. Bullet類

遊戲中的子彈類,負責渲染遊戲中的子彈,繼承自Sprite類,擴展方法:


constructor(scene) {
super();
this.scene = scene;
this.vx = 0;
this.vy = -1;
this.state = new Date();
this.particleCB = 500;
this.type = 0; // 區分子彈類型 0 enemy, 1 player
}
collide() {...} // 碰撞檢測
createParticle() {...} // 子彈播放的例子效果
update() {...} // 更新對象狀態並調用父類的render方法

子彈類中的碰撞檢測僅負責判斷與場景的碰撞


// 傳入場景寬高

collide(w, h) {

if (this.posX < 0 - this.getWidth() ||

this.posX > w + this.getWidth() ||

this.posY < 0 - this.getWidth() ||

this.posY > h + this.getWidth()) {

this.alive = false;

}

}

子彈運行過程會有粒子效果,這個功能在createParticle()實現




let p = new Particle({...}); // new一個實例
this.scene.particles.push(p); // 把粒子加入場景,統一處理
update() { // 更新對象狀態
if (new Date() - this.state >= this.particleCB) {
this.createParticle();
this.state = new Date();
}
this.posX += this.vx;
this.posY += this.vy;
this.render();
}

4. Enemy類

遊戲中的敵機類,負責渲染遊戲中的敵機,繼承自Sprite類,擴展方法:




constructor(scene) {

super()

this.scene = scene

this.dx = .005 // x方向的加速度

this.dy = 0 // y方向的加速度

this.changeCB = 500 // 敵機改變方向的CB

this.canChange = true

this.init()

}

init() {

this.bulletCB = 2000 // 敵機發射子彈的CB

this.attackTime = -1 // 可以攻擊的時間,小於0即可攻擊

this.boomConfig = new Map([ // 配置敵機被擊中後的爆炸效果

[2, [1.5, 1, 0.5]], // 敵機大小為2,爆炸後分成1.5 1 0.5粒子大小

[3, [2, 2, 1]] // 敵機大小為3,爆炸後分成3 3 1粒子大小

])

}

collide() {...} // 碰撞檢測

attack() {...} // 發射子彈

boom() {...} // 敵機中彈

changeX() {...} // 敵機AI

changeY() {...} // 敵機AI

change() {...} // 敵機AI

update() {...} // 更新對象狀態並調用父類的render方法

複製代碼boomConfig中之所以那樣配置,是因為在場景類中初始化敵機只有三種大小,這裡沒有考慮那麼靈活,可以在類中加入一個minSize和maxSize控制生成敵機答大小,這裡就不做那麼複雜了。

// GameScene -> createEnemy()

e.sizeX = Math.random() * 3 + 1 | 0;

5. Player類

Player類則相對複雜很多,有Enemy類的屬性方法外,還需註冊鍵盤事件,(Enemy和Player類這裡還可以抽出一個基類,這裡就不搞的那麼複雜了)


 init() {
this.keydowns = {};
this.actions = {};
this.bulletCB = 800;
this.attackTime = -1;
this.registAction('a', this.moveLeft.bind(this));
this.registAction('d', this.moveRight.bind(this));
this.registAction('w', this.moveUp.bind(this));
this.registAction('s', this.moveDown.bind(this));
this.bindEvent();
}
registAction(key, callback) {
this.actions[key] = callback;
}
bindEvent() {
window.addEventListener('keydown', ev => {
this.keydowns[ev.key] = true;
})
window.addEventListener('keyup', ev => {
this.keydowns[ev.key] = false;
})
}
update() {
this.edge(); // 邊界檢測,限制玩家在場景內
this.damage(); // 傷害檢測,子彈、敵機
let actions = Object.keys(this.actions);
actions.forEach(k => this.keydowns[k] && this.actions[k]())
this.attack();
this.render();
}

按鍵響應通過把按鍵狀態和回調函數存在對象的方式,使用registAction註冊相應事件,這樣代碼看上去會簡潔很多,僅需在update()中遍歷即可。

而對於邊界檢測和傷害檢測則本質是一個區間判斷,不同的是,邊界檢測是限制在一個區間,也就是如果不在這個區間需要限制(或者說是更正);而傷害檢測則是如果在這個區間那麼就觸發對應的事件,如我方子彈與敵機碰撞,則敵機炸燬,子彈銷燬。

下面是圖示,其實簡單的碰撞檢測都是這樣子的,比如打磚塊

canvas飛機大戰遊戲案例


6. Scene類

場景類這裡抽出了一個基類,SceneBase,當時考慮後期擴展會有遊戲主場景,開始場景,設置場景等,所以在這裡抽離了一層。

SceneBase類也沒定義多少內容,有兩個靜態屬性,WIDTH和HEIGHT,也就是場景大小,像這類屬性沒必要放在一個個實例中,所有實例共用一個即可,這類屬性一般可以放在靜態屬性中。

真正是GameScene類在負責整個遊戲的運作,它繼承自SceneBase類,也是遊戲的引擎。

init方法則是初始化了遊戲的各個參數,


init() {
super.init();
this.setEnemyCD = 3000; // 敵機出現的CB
this.createEnemyTime = -1
this.bullets = []; // 場景中的所有子彈
this.enemys = []; // 場景中的所有敵人
this.particles = []; // 場景中的粒子
this.initPlayer();
this.initEnemy();
this.warn = false; // 玩家是否被擊中
this.killCount = 0;
}


update方法則是統一處理場景所有實體的更新


 update() {
super.update();
this.initEnemy();
if (this.warn) {
this.warning();
this.warn = false;
} else {
ctx.clearRect(0, 0, SceneBase.WIDTH, SceneBase.HEIGHT);
}
this.player.update();
this.updateBullet();
this.updateParticle();
this.updateEnemy();
this.updateInfo();
}


7. Stage類

負責管理所有的場景,


 constructor() {
this.manager = []
this.init()
}
add() {...} // 添加場景對manager
remove() {...} // 對manager場景移除
static freeze() {...} // 對manager場景凍結
static unfreeze() {...} // 對manager場景解凍
bindEvent() {...} // 一些事件響應

這個東西在此次案例並沒太多體現,是我在層疊消融遊戲案例中抽離出來的,運用在多場景切換中,比如一個是遊戲遊玩模式,一個是遊戲自定義編輯模式,是這種情況下使用的。

您可以查看關於 層疊消融遊戲 檢測與目標圖形匹配的算法

github:canvas飛機大戰(今天github山不去,代碼沒提交上去)

最後小編整理一些編程資料希望幫助到大家

領取方法:關注小編+轉發文章——私信小編(學習)“即可免費獲得”

canvas飛機大戰遊戲案例

"