快到年終的時候作了一個以遊戲形式展現的h5活動頁,第一次嘗試使用js寫小遊戲,頗有趣的過程,很寶貴的經驗。css
直接上個效果的gif圖,遊戲的一小部分效果,錄出來有點卡
canvas
結果頁:
微信
產品妹子忽然給我拉進來一個羣,跟咱們講作了這麼久的製做平臺(用戶製做手機主題的平臺),咱們是否是應該反饋給用戶點什麼東西,就像以前特別火的微信年終總結那樣。總之就是要打動用戶,要特別酷。說特別酷的時候她回頭朝我微微一笑,微笑中帶着一點點,嗯,殺意。
活動形式,展示方式,什麼數據反正就是通通都沒想好,整個過程當中你們討論的熱火朝天。當時不知道我爲啥腦子一熱,跟她說了一句:「沒事兒,搞吧,你能想出來我就給你作出來。」而我也由於這句話把本身置身於水深火熱之中。。
討論的結果就是你們的idea感受都不是特別酷,又很差玩兒,乾脆就作個遊戲形式的吧!全部人都轉頭看向我,我想了想以前說的話,只吐出來一個字,「搞」。而心裏中五味雜陳,「遊戲?有意思啊?搞!沒搞過啊?能搞定嗎?搞!」。最終敲定,兩週時間,遊戲方式,展示用戶在魔秀的點點滴滴。架構
遊戲的形式大概相似一個滑雪大冒險和賽車的結合,以賽車的形式進行僞3d效果的展示,滑雪大冒險的樣式做爲咱們的主題,同時你們還給咱們的遊戲起了個酷炫的名字----魔秀時光道。app
遊戲的展示形式肯定後,直覺告訴我,想要將遊戲快速穩定的呈現,免去圖片加載控制,動畫控制之類的複雜處理,我須要一個JS遊戲引擎。最終在EgretPhaserPixiJS中選定了PixiJS,雖然不像Egret同樣有完善的中文文檔,可是它提供了清晰易懂的examples可快速上手,沒有複雜的生態,簡單的幾行代碼就能夠用js實現我想要如下幾點功能:ide
我須要定製整個畫布的大小和背景,我須要使用不一樣的容器來承接不一樣的內容,而且靈活控制每一個容器的屬性:函數
//畫布 var app = new PIXI.Application(800, 600, {backgroundColor : 0x1099bb}); document.body.appendChild(app.view); //定製container var container = new PIXI.Container(); container.x = (app.renderer.width - container.width) / 2; container.y = (app.renderer.height - container.height) / 2; app.stage.addChild(container);
你們都知道,使用canvas進行圖片繪製的時候,須要肯定圖片已經成功加載,而遊戲中有着大量的圖片資源須要去維護,PixiJS已經爲咱們提供此項服務:工具
var bunny = PIXI.Sprite.fromImage('required/assets/basics/bunny.png’); bunny.x = app.renderer.width / 2; bunny.y = app.renderer.height / 2; app.stage.addChild(bunny);
同時,咱們須要一個動畫控制器,來控制各Sprite的運動和重繪,而不是生硬的對各項屬性進行從新修改:學習
app.ticker.add(function(delta) { bunny.rotation += 0.1 * delta; });
須要注意的是,咱們會發現,此處的Sprite動畫控制,至關於添加了運動的動畫隊列,而且實現了相似transformjs的效果,可直接對實例的屬性進行操做。而我在寫項目的時候官方的例子是經過統一animate函數進行操做,經過requestAnimationFrame進行幀動畫控制,更推薦新的方式而不是以下:動畫
function animate () { requestAnimationFrame(animate); bunny.rotation += 1; renderer.render(stage); }
遊戲最重要的部分至關於用戶的交互了,也就是所謂的事件處理,爲Sprite添加事件監聽,很簡單,以下所示:
//元素可點擊 sprite.interactive = true; //鼠標移入cursor sprite.buttonMode = true; sprite.on('click', onClick); // mouse-only prite.on('tap', onClick); // touch-only function onClick () { sprite.scale.x *= 1.25; sprite.scale.y *= 1.25; }
設計圖固然也是很重要的,決定了咱們如何去實現這個遊戲,當我拿到設計圖的時候,他是長成這樣的,個人心裏是崩潰的。我能怎麼樣,我也很無奈呀~ 開搞吧!
根據以上,PixiJS已經基本知足咱們的需求,也就是說,工具準備和素材準備已經都完成了。在動手書寫以前,咱們須要把實現思路想好,才能保證書寫過程的清晰,避免沒必要要的麻煩。
就像咱們平時玩兒賽車遊戲同樣,咱們感受賽車在跑道上進行比賽,實際上賽車只進行左右移動而已,而運動的則是背景,如何規劃好路線,讓背景按照既定的場景去運動,並展示不一樣的視角,特地向央美的同胞諮詢了下,他們是用一個叫「攝像機」的東西實現的。對於咱們來講,不須要那麼複雜的場景,只需讓背景像前規律的「平移」,形成「樹動我不動」的視覺效果,同時咱們利用「透視」的原理,讓背景以「近大遠小」的方式進行變化,就會產生一種low low的立體效果。
關鍵詞:透視 近大遠小(偏移,大小,速度)
對於背景及物體的運動,大概路線規劃以下:
肯定視覺焦點後,咱們只需隨機生成物體出現的位置,計算出a,b相對固定,使其y進行相應速度的增長,x根據運動軌跡進行對應偏移,則可實現往近跑的效果。針對運動軌跡, 假設物體向下偏移距離爲N,則對應水平針對中軸線的偏移爲:
同時,咱們還需對物體進行近大遠小的顯示,這個比較簡單,以焦點爲0,頁面最底端爲1,進行對應比例放大便可:
scale = (curY - startY) / ( endY - startY);
針對物體的運動速度,也應在遠近有不一樣的體現。
針對背景樹,咱們需在最初對全部的樹進行展示,鋪滿兩邊背景。每列樹對應的運動路線一致,可直接讓其進行循環展現,當樹運動到最底時,讓其出如今最頂點。所以只需肯定一共有幾行幾列樹,並設定其邊界,根據行列肯定初始惟一併對其進行運動。同時,可讓樹進行小範圍的隨機偏移,使樹錯落有致。以下所示:
export default function Tree (row, col, direction) { this.cfg = { direction: direction, //方向 col: col, //第幾列 row: row, //第幾行 MaxX: 440, minY: 210, maxY: 500, range: 10 //座標浮動範圍 } };
而針對物體,則須要隨機生成它的初始x座標,並計算出其對於的路線進行運動,在運動過程當中,進行碰撞檢測,檢測是否與人物進行相撞。
人物滑動的操做,用了最簡單的實現方式:按鈕。當用戶點擊不一樣方向時,讓人物向對應方向進行偏移。同時,爲了讓人物滑動不僵硬,在左右滑動過程當中,人也應該隨着運動有對應角度的傾斜,就像咱們平時玩兒滑雪拐彎時,會改變中心同樣。思路以下:
點擊按鈕時,改變方向
運動時檢測方向,若向左,則x減少,向右,則增長
向右(左)運動時,人物對應rotation也進行增長(減少)
鬆開手時,人物對應rotation慢慢恢復成0;
因爲人物有吃東西的環節(否則這還叫什麼遊戲呢),所以碰撞檢測確定是必須的啦。咱們能夠經過兩種方式進行碰撞檢測
人物檢測碰撞物體,需實時遍歷物體座標列表,進行檢測
每一個物體自身進行碰撞檢測,檢測自身與人物位置的對應差
我很機智的選擇了第二個,畢竟每一個物體的位置都是實時變更的,而每次碰撞檢測都進行一次循環的方法,太笨重啦。在這裏咱們設置碰撞檢測的區域(寬高),在物體運動時,針對人物的x,y座標,與自身的x,y座標加減造成的四條邊界進行比較便可,若進行碰撞,則進行對應的操做便可,如播放音頻,得分+1等。
思路理清楚以後,後面的路就很明朗啦。接下來咱們就能夠着手設計下如何實現這個東西了,很顯然,遊戲中咱們擁有許許多多的「角色」,使用「面向對象」的方式再好不過了。大概的劃分以下
stage //舞臺,進行基本場景渲染,遊戲總體控制(開始,中止)等
player //玩兒家,也就是對應的人物
sprite //出現的物體,如蛋糕等,提供玩兒家吃。 包含碰撞檢測等,會本身運動
tree //由於tree自身會運動,因此每一個tree爲一個類
score //進行分數控制及顯示
cfg.js //包含總體遊戲配置
每一個對象包含如下幾點屬性及功能:
每一個對象包含其內部自身基本配置,包括位置,邊界,圖片等。直觀,便於調試
export default function People (stage) { this.cfg = { img: require('./img/people.png'), anchor: { x: 0.5, y: 0.5, }, position: { x: cfg.width / 2, y: 500 }, speed: 5, } }
每一個對象都包含其自身方法,以下所示:
render //進行圖片等渲染
animate //動畫function
init //一些初始化配置
經過以上思路的設計和結構的設計,我很快的將這個遊戲實現了。。。沒錯,理清思路和結構的重要性就是這樣。固然,在實現過程當中,也有一些小的點能夠記錄下:
爲了遊戲的進行效果,仍是決定在加載完全部資源(尤爲是圖片資源)後,才中止loading頁面。如何判斷全部內容都加載完畢了呢?寫了個小loader
var pics = [ require('./img/bg-start.png'), require('./img/btn-start.png'), ... ]; function loadImages (pics, callback) { if(pics.length) { var img = new Image(), pic = pics.shift(); img.onload = callback; img.src = pic; loadImages(pics, callback); } else{ return; } } $(function() { loadImages(pics, function () { if (!pics.length) { $('.loading').hide(); }; }) });
遊戲是橫屏展現的,那就強制橫屏好啦。這個當時還糾結挺久,仍是本身功底不紮實腦子走私了,還在想是監聽resize事件仍是旋轉屏幕事件,都沒有這些事兒啊好嗎!直接讓它旋轉就好。
if(window.orientation==180||window.orientation==0) { $('#main').height(winW); $('#main').width(winH); $(‘#main’).css({ 'transform': 'rotate(90deg)', }); } else{ $(‘#main’).css('transform', 'rotate(0)'); }
理清思路後,最亂的仍是各類定時器啦。 爲了實現物體隨機出現的效果,讓每一個物體隨機多少秒後開始出現;最後一個物體出現完,多少秒後出現結束畫面等等,須要理清楚各個定時器的關係,並對其添加語義化良好的標記,及時對未完結的定時器進行清除,防止定時器帶來的意想不到的問題。
最終遊戲的效果基本讓你們滿意啦,也是第一次嘗試這方面的開發,周圍也徹底沒有作過這東西的人。從開始的忐忑和一無所措,到過程當中理清思路和結構,到書寫中的各類未知的坑,本身在這兩週感受經歷了很充實的一件事情。同時也對後續進行一些未知事物的探索和學習有了更豐富的經驗,找對路子纔是王道呀!