新年新氣象,最近事情很少,繼續閒暇學習記點隨筆,歡迎拍磚。以前的〈簡單遊戲學編程語言python篇〉寫的比較幼稚和粗糙,且告一段落。開啓新的一篇關於javascript+html5的從零開始的學習。仍然以我們有興趣寫的小遊戲開始,〈flappy bird〉最近真是火的離譜,我也是昨天才開始找這個遊戲試玩一下,果真難度不小,只能玩到33分了 ,哈哈。這遊戲的評論網上已經鋪天蓋地了,這裏不作過多評論,畢竟我的屬於這個移動遊戲圈子以外的。不過仍是忍不住說一下,這遊戲創意已經不算新穎,像素級的入門遊戲精美度更是差上不少,開發難度也就是入門級的水平(相對來講)。不過做爲菜鳥的門外漢來講,這遊戲的設計思路和開發細節仍是比較值得新手去研究和做爲練手的案例研究一下。因而找到網上牛人放出的山寨版《flappy bird》之clumsy-bird,來簡單研究一下源碼吧,順便從零學習一下canvas和Js一些東西,做爲記錄。javascript
clumsy-bird的github地址爲:https://github.com/ellisonleao/clumsy-bird html
在線試玩地址:http://ellisonleao.github.io/clumsy-bird/(保證你的瀏覽器支持html5喲)html5
1、源碼框架介紹java
這個遊戲呢,採用開源的html5遊戲引擎melonJS做爲框架,這個引擎比較輕量級,比較簡單易懂。瞭解完源碼總體框架就明白了整個引擎的框架了。python
首先看一下游戲入口這裏(game.js):大部分是框架相關的,非框架代碼則是data的補充定義,用戶按鍵事件綁定這些。git
var game = { data : { score : 0, timer: 0, start: false }, "onload" : function () { if (!me.video.init("screen", 900, 600, true, 'auto')) { alert("Your browser does not support HTML5 canvas."); return; } me.audio.init("mp3,ogg"); me.loader.onload = this.loaded.bind(this); me.loader.preload(game.resources); me.state.change(me.state.LOADING); }, "loaded" : function () { me.state.set(me.state.MENU, new game.TitleScreen()); me.state.set(me.state.PLAY, new game.PlayScreen()); me.state.set(me.state.GAME_OVER, new game.GameOverScreen()); me.state.transition("fade", "#000", 100); me.input.bindKey(me.input.KEY.SPACE, "fly", true); me.input.bindTouch(me.input.KEY.SPACE); me.state.change(me.state.MENU); } };
onload 預加載的game.resources主要是圖片以下的一些素材。github
從界面加載完"loaded"函數看起,有三個狀態 Menu 對應game.TitleScreen()是咱們的標題界面處理,PLAY是咱們的game.PlayScreen(),這個就是遊戲開始的相關部分,即咱們主要研究的部分--screens/play.js編程
這裏面主要繼承重寫了ScreenObject的init初始化函數onResetEvent狀態改變函數 及刷新界面函數update。canvas
init定義了主要的變量管道長度this.pipeHoleSize = 1240;左右相鄰管道出現的間隔時間this.pipeFrequency = 92;瀏覽器
update函數中處理邏輯即每隔pipeFrequency生成上下兩個管道和碰撞體(這個實際並不渲染,後面代碼中實體的alpha渲染作透明出現,只做爲碰撞檢測用),兩個管道的位置簡單畫一下應該可求出(pipe1是下管道,保證中間距離是100,且最短管道要保證有100)
if (this.generate++ % this.pipeFrequency == 0){ var posY = this.getRandomInt( me.video.getHeight() - 100, 200 ); var posY2 = posY - me.video.getHeight() - this.pipeHoleSize; var pipe1 = new me.entityPool.newInstanceOf("pipe", this.posX, posY); var pipe2 = new me.entityPool.newInstanceOf("pipe", this.posX, posY2); var hitPos = posY - 100; var hit = new me.entityPool.newInstanceOf("hit", this.posX, hitPos); pipe1.renderable.flipY(); me.game.add(pipe1, 10); me.game.add(pipe2, 10); me.game.add(hit, 11); }
接下來是遊戲界面狀態處理函數onResetEvent
1 me.input.bindKey(me.input.KEY.SPACE, "fly", true); 2 //this.start = false; 3 game.data.score = 0; 4 game.data.timer = 0; 5 game.data.start = false; 6 7 me.game.add(new BackgroundLayer('bg', 1)); 8 9 var groundImage = me.loader.getImage('ground'); 10 11 this.ground = new me.SpriteObject( 12 0, 13 me.video.getHeight() - groundImage.height, 14 groundImage 15 ); 16 me.game.add(this.ground, 11); 17 18 this.HUD = new game.HUD.Container(); 19 me.game.world.addChild(this.HUD); 20 21 me.entityPool.add("clumsy", BirdEntity); 22 me.entityPool.add("pipe", PipeEntity, true); 23 me.entityPool.add("hit", HitEntity, true); 24 25 this.bird = me.entityPool.newInstanceOf("clumsy", 60, 26 me.game.viewport.height/2 - 100); 27 me.game.add(this.bird, 10); 28 this.posX = me.game.viewport.width; 29 30 //inputs 31 me.input.bindMouse(me.input.mouse.LEFT, me.input.KEY.SPACE); 32 me.state.transition("fade", "#fff", 100); 33 34 this.getReady = new me.SpriteObject( 35 me.video.getWidth()/2 - 200, 36 me.video.getHeight()/2 - 100, 37 me.loader.getImage('getready') 38 ); 39 me.game.add(this.getReady, 11); 40 var popOut = new me.Tween(this.getReady.pos).to({y: -132}, 2000) 41 .easing(me.Tween.Easing.Linear.None) 42 .onComplete(function(){ game.data.start = true;}).start(); 43 },
這裏面主要完成界面背景層的加載,HUd做爲分數顯示,及重要遊戲對象生成。
me.entityPool.add("clumsy", BirdEntity); 小鳥實體類
me.entityPool.add("pipe", PipeEntity, true); 管道實體類
me.entityPool.add("hit", HitEntity, true); 碰撞體類
this.bird = me.entityPool.newInstanceOf("clumsy", 60,
me.game.viewport.height/2 - 100);
me.game.add(this.bird, 10); 首先只有小鳥新實例對象生成,遊戲正式開始纔有管道等。EntityPool 就是做爲引擎管理遊戲中實例化對象而存在的。
2、遊戲對象類的實現
這塊者重介紹主重要的上面提到的那三個遊戲對象類。(entities.js)
實現仍是比較簡單的,重寫ObjectEntity重要的幾個函數就好了。就是 init 和 update這兩個函數,分別完成對象初始化和每幀的刷新。主要學習的是update裏面邏輯的處理。這裏主要介紹小鳥的處理,那兩個基本上沒多少代碼處理。
1 var BirdEntity = me.ObjectEntity.extend({ 2 init: function(x, y){ 3 var settings = {}; 4 settings.image = me.loader.getImage('clumsy'); 5 settings.spritewidth = 85; 6 settings.spriteheight= 60; 7 8 this.parent(x, y, settings); 9 this.alwaysUpdate = true; 10 this.gravity = 0.2; 11 this.gravityForce = 0.01; 12 this.maxAngleRotation = Number.prototype.degToRad(30); 13 this.maxAngleRotationDown = Number.prototype.degToRad(90); 14 this.renderable.addAnimation("flying", [0, 1, 2]); 15 this.renderable.addAnimation("idle", [0]); 16 this.renderable.setCurrentAnimation("flying"); 17 this.animationController = 0; 18 this.updateColRect(10, 70, 2, 58); 19 }, 20 21 update: function(x, y){ 22 // mechanics 23 if (game.data.start) { 24 if (me.input.isKeyPressed('fly')){ 25 this.gravityForce = 0.01; 26 27 var currentPos = this.pos.y; 28 tween = new me.Tween(this.pos).to({y: currentPos - 72}, 100); 29 tween.easing(me.Tween.Easing.Exponential.InOut); 30 tween.start(); 31 32 this.renderable.angle = -this.maxAngleRotation; 33 }else{ 34 this.renderable.setCurrentAnimation("flying"); 35 this.gravityForce += 0.2; 36 this.pos.add(new me.Vector2d(0, me.timer.tick * this.gravityForce)); 37 this.renderable.angle += Number.prototype.degToRad(3) * me.timer.tick; 38 if (this.renderable.angle > this.maxAngleRotationDown) 39 this.renderable.angle = this.maxAngleRotationDown; 40 } 41 } 42 //manual animation 43 var actual = this.renderable.getCurrentAnimationFrame(); 44 if (this.animationController++ % 2){ 45 actual++; 46 this.renderable.setAnimationFrame(actual); 47 } 48 49 res = this.collide(); 50 var hitGround = me.game.viewport.height - (96 + 60); 51 var hitSky = -80; // bird height + 20px 52 if (res) { 53 if (res.obj.type != 'hit'){ 54 me.state.change(me.state.GAME_OVER); 55 return false; 56 } 57 me.game.remove(res.obj); 58 game.data.timer++; 59 return true; 60 }else if (this.pos.y >= hitGround || this.pos.y <= hitSky){ 61 me.state.change(me.state.GAME_OVER); 62 return false; 63 } 64 65 var updated = (this.vel.x != 0 || this.vel.y != 0); 66 if (updated){ 67 this.parent(); 68 return true; 69 } 70 return false; 71 }, 72 73 });
在小鳥初始化函數完成了動畫幀的加載 三個序列動畫[0,1,2]做爲一次動畫過程,
this.renderable.addAnimation("flying", [0, 1, 2]);this.renderable.addAnimation("idle", [0]);this.renderable.setCurrentAnimation("flying");
this.gravity = 0.2;this.gravityForce = 0.01;完成重力設定。
update函數中小鳥動畫實現爲
//manual animation
var actual = this.renderable.getCurrentAnimationFrame();
if (this.animationController++ % 2){
actual++;
this.renderable.setAnimationFrame(actual);
} 如上變實現小鳥的動畫。
update函數主要功能除了上面,還主要負責完成用戶按鍵處理和碰撞檢測等處理邏輯:
當按下space鍵或者點擊鼠標執行以下動做 (好像是利用tween加強了物理效果,具體沒深究這裏。)
this.gravityForce = 0.01;var currentPos = this.pos.y;
tween = new me.Tween(this.pos).to({y: currentPos - 72}, 100);
tween.easing(me.Tween.Easing.Exponential.InOut);
tween.start();
this.renderable.angle = -this.maxAngleRotation;
當沒有按下相應鍵處理爲
this.renderable.setCurrentAnimation("flying");
this.gravityForce += 0.2;
this.pos.add(new me.Vector2d(0, me.timer.tick * this.gravityForce));
this.renderable.angle += Number.prototype.degToRad(3) * me.timer.tick; 每幀增長0.2重力加速度值,而且y值每秒增長g*t位移,角度加大,就是實現小鳥下墜效果。
1 res = this.collide(); 2 var hitGround = me.game.viewport.height - (96 + 60); 3 var hitSky = -80; // bird height + 20px 4 if (res) { 5 if (res.obj.type != 'hit'){ 6 me.state.change(me.state.GAME_OVER); 7 return false; 8 } 9 me.game.remove(res.obj); 10 game.data.timer++; 11 return true; 12 }else if (this.pos.y >= hitGround || this.pos.y <= hitSky){ 13 me.state.change(me.state.GAME_OVER); 14 return false; 15 }
判斷當前發生碰撞的對象是否是‘hit’,當不是以前的"hit"類型時說明發生碰撞對象改變,即小鳥撞管子上了 ,報遊戲結束,並清理對象。
上面就是主要的代碼邏輯了,感興趣的話能夠從git上fork一份本身研究一下,這裏不作過多探討了,閒扯之餘順便學習一下Js和html5也是收穫。
以上素材及代碼均來自網絡,僅供學習研究,轉載請註明出處,謝謝支持。