'canvas飛機大戰遊戲案例'
引言
本項目使用H5 canvas繪製飛機大戰遊戲,使用ES6 class面向對象開發,目的在於練習js面向對象開發的能力,熟練使用canvas api。
一、案例演示
飛機大戰遊戲,使用canvas繪製,程序僅作練習用,實現主要功能,沒有實現遊戲開始界面、結束判斷等,效果如下圖
引言
本項目使用H5 canvas繪製飛機大戰遊戲,使用ES6 class面向對象開發,目的在於練習js面向對象開發的能力,熟練使用canvas api。
一、案例演示
飛機大戰遊戲,使用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的基本使用
- 動畫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()中遍歷即可。
而對於邊界檢測和傷害檢測則本質是一個區間判斷,不同的是,邊界檢測是限制在一個區間,也就是如果不在這個區間需要限制(或者說是更正);而傷害檢測則是如果在這個區間那麼就觸發對應的事件,如我方子彈與敵機碰撞,則敵機炸燬,子彈銷燬。
下面是圖示,其實簡單的碰撞檢測都是這樣子的,比如打磚塊
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的基本使用
- 動畫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()中遍歷即可。
而對於邊界檢測和傷害檢測則本質是一個區間判斷,不同的是,邊界檢測是限制在一個區間,也就是如果不在這個區間需要限制(或者說是更正);而傷害檢測則是如果在這個區間那麼就觸發對應的事件,如我方子彈與敵機碰撞,則敵機炸燬,子彈銷燬。
下面是圖示,其實簡單的碰撞檢測都是這樣子的,比如打磚塊
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山不去,代碼沒提交上去)