本系列文章對應遊戲代碼已開源 Sinuous gamejavascript
上一節介紹了canvas的基礎用法,瞭解了遊戲開發所要用到的API。這篇文章開始,我將介紹怎麼運用這些API來完成各類各樣的遊戲效果。這個過程更重要的是參透一些遊戲開發的思路和想法,而不是僅僅知道怎麼寫代碼來完成這個遊戲。java
先用一張圖來了解一下整個遊戲的構成。git
Map表示整個背景地圖,做用很簡單,就是渲染黑色背景。
Player 表示玩家粒子,它尾巴中帶有生命點,咱們用Life類來表示。
Enemy爲紅色的敵人粒子,由於技能粒子和Enemy粒子具備不少共性,因此Skill粒子繼承自Enemy粒子。
粒子之間撞擊後有爆炸效果,用Paticle來表示爆炸粒子。github
簡單來講,遊戲就是一幀一幀圖像的疊加播放,並經過捕獲用戶反饋來實現遊戲中的人機交互。web
圖像的逐幀播放能夠類比爲放映電影,經過在熒幕上連續投放圖像來產生動做的效果。canvas
首先要建立這樣一個熒幕, 並設置銀幕的大小。segmentfault
//index.js const canvas = document.getElementById('world'); canvas.width = window.innerWidth > 1000 ? 1000 : window.innerWidth; canvas.height = window.innerHeight;
在遊戲中,熒幕對應一個地圖,咱們將這個地圖抽象爲一個類,並提供基本的渲染方法。瀏覽器
//Map.js /** * 地圖類 */ class Map { init(options) { this.canvas = options.canvas; this.ctx = this.canvas.getContext('2d'); this.width = options.width; this.height = options.height; } clear() { this.ctx.clearRect(0, 0, this.width, this.height); } render() { this.clear(); this.ctx.fillStyle = "black"; this.ctx.fillRect(0, 0, this.width, this.height); } } export default new Map();
在入口處初始化地圖函數
map.init({ canvas, width: window.innerWidth > 1000 ? 1000 : window.innerWidth, height: window.innerHeight });
熒幕準備好後,怎麼放映圖像,對應於遊戲中的放映機是什麼呢?動畫
想一想在js中用於定時執行的方法有哪些,setInterval, setTimeout, requestAnimationFrame?
setInterval這個方法在遊戲中是不能用的。因爲js是單線程,setInterval開啓的定時循環間隔會受到CPU使用狀況的影響,同時電腦對setInterval的最短間隔也有不一樣的要求。因爲遊戲對幀率的要求比較高,因此在遊戲中應該避免使用setInterval來執行定時任務。因爲沒法把握每幀執行的具體時間,setTimeout也有會遇到相似的問題。
懂的人已經懂了,現代的H5遊戲開發都是經過requestAnimationFrame來執行循環播放的。它的優點就是能根據瀏覽器的實時渲染幀率來執行函數,使的動畫播放比較流暢。而不會由於函數的執行時間跟定時器時間不一樣致使的播放卡頓現象。
通常requestAnimationFrame每幀的繪製時間是1000/60 ms。也就是每秒能繪製60幀。好就好在時間不須要咱們本身設置,而是瀏覽器的內在機制。在不一樣的瀏覽器中方法名會有所不一樣,咱們經過下面的方法來定義一個requestAnimationFrame函數
const raf = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60); //每幀1000/60ms };
有個這個方法,咱們若有神助。只須要在一個動畫方法中使用raf調用自身方法。就能實現循環調用的功能,而且如絲般順滑。使用以下:
(function animate() { map.render(); raf(animate); })();
這樣就會不斷調用map的render方法,實現逐幀播放。只不過map的render方法只是把畫布塗黑,因此看起來並無什麼變化。
咱們的遊戲中有玩家粒子,敵人粒子,還有技能粒子,撞擊爆破等效果。咱們的遊戲就是不斷地往animate這個方法中添加內容,在每一幀中渲染多個不一樣東西,看起來就是整個遊戲畫面了。咱們能夠想象一下將來啊animate方法是這樣的。
(function animate() { map.render(); player.render(); enemy.render(); skill.render(); effect.render(); raf(animate); })();
咱們須要擴展player, enemy...等等的render方法。讓它們表現出不一樣的效果。
這樣渲染出來的畫面仍是死的,怎樣讓每一幀渲染出來的圖像有所不一樣,實現動畫的效果呢?
在每一個物體類中,都有一個update方法,該方法用於改變物體的位移形狀等,因此每一幀渲染出來的畫面都會不同。
//經過update方法來控制物體位移或形態變化 update() { this.x += 1; this.y += 1; } render() { cxt.fillRect(this.x, this.y, 10, 10); }
在animate中,咱們須要在每次render後調用update方法
(function animate() { map.render(); player.render(); player.update(); raf(animate); })();
這樣,藉助於遊戲的發條,player就動起來了!咱們前面所過,遊戲就是逐幀播放和人機交互。那怎樣來處理玩家反饋呢?
在PC和手機中的所謂玩家反饋一般是鼠標的點擊滑動以及手勢等動做。經過監聽鼠標或手勢事件來改變物體的屬性,達到控制物體變化的目的。例如讓player跟隨鼠標移動。
window.addEventListener('mousemove', (e) => { self.x = e.clientX; self.y = e.clientY; });
達到的效果跟update方法本質上是一致的。
至此整個遊戲基本原理已經講得差很少了,下一節要講的是如何建立各類粒子,還有player那條會動的尾巴。敬請期待《從零開始開發一款H5小遊戲(三) 攻守陣營,賦予粒子新的生命》