原文首發于微信公眾號:MinProgram
原作者:花叔
Cocos 已獲得轉載授權
前陣子過春節,在家看小孩沒法出去玩,于是就碼起來了,心血來潮想做個基于物理引擎的小游戲,于是愉快地打開了 Cocos Creator。

然后到這個周末,一個算是較為完整的游戲 demo 上線了。

負責的工作
1年多前,我開始學習小游戲開發,并嘗試按全棧的方式去獨立開發小游戲。由于做小游戲都抱著學習的心態,所以什么不擅長就臨時學一下。最后整個游戲的策劃、交互、視覺設計、音效設計、前端開發、后臺開發、性能調優、功能測試等工作都一人包攬了。
所用到的 IDE 包括(僅供參考,其他同行有更好的工具推薦的話,記得留言哦):
- Photoshop,主要用于交互和視覺稿的設計。
- Cocos Creator,主要用于視覺還原、主邏輯開發、跨端調試和編譯。個人覺得這是超給力的游戲開發工具,把 component 的機制直接可視化,集成各種物理引擎、粒子引擎、UI 組件等功能,大大節省了游戲UI以及部分特效邏輯的研發成本。但小游戲版本的編譯速度還是很慢啊。
- Visual Stuido Code,用于代碼開發。這好像是最近最流行的代碼撰寫工具了,免費且穩定??!
- 微信開發者工具,用于微信私有功能開發,云開發,小游戲提審。相比早期的版本微信開發者工具的體驗優化了很多,但在文件監聽側還是有點問題,跟 Cocos Creator 的聯動,經常會出現 Cocos Creator 編譯好文件,微信開發者工具會報錯的問題。
- Cool Edit Pro,音頻資源裁切。
- TexturePacker,sprite資源集自動構建。
- Google Chrome,web版游戲調試。有些時候一些性能調優可以放在 chrome 上面,它有非常專業的調試工具。
從涉及的 IDE 看出來,其實開發一個游戲需要兼顧好多內容,盡管游戲提供的功能并不多,但實際研發時間卻不短,花了整個春節假期(WTF,明明就幾天)
實際上,游戲開發相比頁面開發,我認為前者更難,它更像一門綜合技術,世界觀構建、內容建設、美術構建、音效設計、程序算法等等,很多方面都需要涉獵,這里面的每項內容都夠我鉆研很長一段時間了。
題外話,請善待獨立做游戲的人,他們都在以高負荷的工作壓力在研發和學習。
(文章篇幅略長,想看雞湯,不想看技術細節,可直接跳到最后)
門外漢的游戲策劃
雖然自身策劃能力很業余,但在動手開發前,再簡陋的策劃步驟還是省不了的,預先策劃有助于快速開發。
1.游戲的主線
早前預想的故事場景是開卡車在林中運輸木材,核心玩法很簡單:把木箱子運到指定的區域。
整個游戲基于仿真物理場景,唯一一點超現實的功能是用藍墨水畫的線條會被實體化,會成為物理碰撞體,玩家可借此搭建橋梁、容器、障礙物等方式來協助搬運。
2.內容建設的手段
而為了解決內容構建的問題,我為游戲提供了一個開放的模式,那就是:玩家可自主創建關卡。


這樣的話,關卡是能動態新增的,任何人都可以隨時隨地在手機上去為本游戲構建關卡,我也可以脫離電腦很方便地為游戲設計初始關卡。

這需要我在游戲開發中應用到原本網頁設計的組件化思維,要把游戲里可能會出現的元素抽象為通用組件,然后構建一個編輯模式,讓玩家拖動放置,進而設計出不同的關卡。
3.虛擬貨幣的換算機制
游戲引進了金幣的概念,它是整個游戲世界里的通用貨幣,目前構思中給金幣賦予的作用有兩個:a. 以 20 比 1 的方式兌換提示機會;b. 以 2000 比 1 的方式購買卡車皮膚(暫未實現)

而金幣的獲取方式比較簡單粗暴:簽到、分享和看視頻(還沒到 1000 累計注冊用戶,所以沒放開功能)

![15]](http://www.cocoscvp.com/wp-content/uploads/2019/03/15-1.jpg)
Cocos Creator 開發經驗
鑒于內容太多,總的開發邏輯就不細說了,這里給一下開發過程中一些有用的經驗。
關于布局
Cocos Creator 為本游戲中提供了兩種實用布局組件:
1.Widget,這能讓某個元素自適應到任意位置。這是相當實用的控件了。使用方法非常簡單,各種機型的自適應布局一下子就被兼顧了。

2.Layout,這能讓游戲中的 Node 具備如網頁 dom 一般的流行布局特性。它提供了常用的三種流體布局方式:橫向、縱向以及網格。該組件對于本游戲的控件容器特別合適。

關于動畫
Cocos Creator 提供的動畫開發套件非常強悍。

這有兩個點可以提一下:
1.動畫定義,Cocos Creator 里為某個 Node 追加動畫時,只要給它加 cc.Animation 組件,然后建立一個 animation-clip,針對它去可視化編輯動畫的屬性和幀狀態,就能快速做出一個動畫。

2.動畫事件監聽,Cocos Creator 的動畫控件里有一個我覺得非常實用的功能,那就是可以為動畫的某一幀定義自定義監聽事件(該事件代碼體可定義在 Node 對應的用戶腳本組件中),例如物理的游戲里過關時會有個彈框動畫,在動畫播放到差不多的時候,會播放一個音效,利用的就是動畫的自定義事件。

關于物理引擎
Cocos Creator 的物理引擎相當強悍,只要在程序開端執行:
cc.director.getPhysicsManager().enabled = true;
整個游戲世界就會進入物理監聽狀態,所有被定義成剛體(追加了 cc.RigidBody 組件)的 Node 將直接具備物理性質,通過追加 PhysicsCollider 控件可讓 Node 具備指定熱區的物理碰撞特性。

碰撞體有一個挺有用的方法,getAABB,這是獲取碰撞體包圍盒的方法,可以用其結合 rect 的 containsRect 方法來實現對某個矩形區域里是否包含某碰撞體的功能,在物理的游戲中箱子與目標區域的監聽功能就是依靠它來實現的:
具體代碼:
//獲取自身包圍盒 var selfAABB = this.node.getComponent (cc.PhysicsPolygonCollider). getAABB() if (otherAABB.containsRect(selfAABB)) { }
此外,物理引擎里還提供了一種比較實用的組件:關節組件。
它可以定義一些常用的物理場景,比如本游戲里,汽車輪子上用的就是物理引擎的 WheelJoint 控件:

該控件可以模擬機車輪子的物理效果,讓輪子跟某個剛體保持一定距離,并能自轉。

其實除了這類型的關節組件外,官方還提供了很多別的關節組件,具體的用法可以參考 Cocos Creator 的官方開發文檔
對了,還要提一下:
cc.director.getPhysicsManager()
這段代碼能返回全局的物理管理對象,本游戲用了該對象下面的 2 個方法:
1.testPoint 方法,該方法可以檢測某個坐標點下是否存在物理碰撞體,物理的游戲中在某個剛體上禁止畫線的功能就是依靠它來實現的:
2.rayCast 方法,該方法可以獲取指定出發點到終點間射線所經過的剛體集合,物理的游戲中畫線遇到剛體后禁止繼續的功能就是依靠它來實現的:
這個方法的原生實現邏輯相當復雜,各種幾何算法什么的,反正幾何數學沒學好的花叔如果要以原生方式實現,也只能勉強實現很挫的效果,但 Cocos Creator 直接就封裝好供大家調用了,非常方便。
關于預制對象
預制對象是 Cocos Creator 中很重要的節點處理機制,https://docs.cocos.com/creator/manual/zh/asset-workflow/prefab.html,它可以把某個節點像場景那樣單獨存為一個文件,然后在不同場景中引用,并通過:
cc.instantiate()
方法進行預制對象節點的復制,這樣可以實現節點邏輯的復用,用來做節點組件最合適不過了。
物理的游戲中”創作模式”下的所有地圖元素其實就是基于同一個預制對象。

不管是”創作模式”還是“闖關模式”里的地圖元素的基礎數據模型均來自該預制對象,如果需要新增地圖元素,只要修改這個預制對象即可全局生效,可以說非常方便了。
但大家要注意,預制對象對性能有一定的反向作用,具體可以翻翻 Cocos Creator 的論壇,可以說它是雙刃劍。
關于 camera
聽說 2.0 版本的 Cocos Creator 對 camera 的邏輯進行了優化,我試用了一下真好用,目前 camera 是跟 group 綁定的,在游戲中可以定義多個 camera 來處理 node 的Color/Depth/Stencel,物理的游戲中用 camera 實現了各種元素的層級處理(給 camera 定義 Depth,然后指定不同 group 到 cullingMask 中)

此外依靠 camera,可以快速實現截圖或放大鏡的效果:

touchmove 回調方法中的相關代碼:
var camera = this. assistCamera. getComponent ( cc.Camera ); // 新建一個 RenderTexture,并且設置 camera 的 targetTexture 為新建的 RenderTexture,這樣 camera 的內容將會渲染到新建的 RenderTexture 中。 let texture = new cc.RenderTexture(); let gl = cc.game._renderContext; // 如果截圖內容中不包含 Mask 組件,可以不用傳遞第三個參數 texture.initWithSize(100, 100, gl.STENCIL_INDEX8); camera.targetTexture = texture; // 渲染一次攝像機,即更新一次內容到 RenderTexture 中 this.assistCamera.x = touchLoc.x this.assistCamera.y = touchLoc.y camera.render(); var sf = new cc.SpriteFrame(texture) this.node.targetAssist. getComponent(cc.Sprite). spriteFrame = sf
想要了解 camera 的用法,還能研究一下官網提供的 demo,這里就不展開細說了:https://github.com/cocos-creator/demo-camera
關于性能調優
講真,畢竟我也不是太深入地去了解過 Cocos Creator 的底層原理,所以性能優化這塊就只能從自己這個項目來給點小技巧,以下稍微講講:
物理的游戲剛有初版的時候,性能特別糟糕,后來做了三項優化工作。
1.自動合圖
降低 DrawCall 是提升游戲渲染效率一個非常直接有效的辦法,而兩個 DrawCall 是否可以合并為一個 DrawCall 的一個非常重要的因素就是這兩個 DrawCall 是否使用了同一張貼圖,所以官方是建議合圖的。
但我項目中用了那么多碎圖,這時候讓我去合圖,豈不搞死我!苦惱之際以外發現 Cocos Creator 提供了一個強大的功能“自動合圖(Auto Atlas)”

不用不知道,一用嚇一跳,這功能可以把當前目錄以及子目錄下所有的圖片文件以指定的算法去合并成 sprite 圖,并且自動更新原有 spriteFrame 的引用,一下子就把全部碎圖合成大圖來按需調用了。

網絡請求直接從幾十個變成一兩個。
然而….我發現 drawcall 也沒降低多少。沒花太多時間去想為什么,這個方式我就先不關注了。
2.減少節點
節點如果沒來得及釋放,那么一定會導致 drawcall 上升,往這個方向想,我就想到 cc.instantiate(),在物理的游戲里只有這個方法會主動去新增節點,那么只要產生的臨時節點及時銷毀就行,但發現其實在復制完都調用了 removeFromParent() 方法,邏輯好像是對的。
但后來查了一下資料,原來 removeFromParent 方法執行后,節點并不會自動銷毀,真正能讓它銷毀的是 node 的 destroy 方法。囧大了。應該換成 destroy。于是就全局搜索 removeFromParent,逐一替換,drawcall 就順利降下來了。
3.優化代碼邏輯
除上述常規手段外,針對自己的代碼也需要做一下優化,本游戲的代碼邏輯中最有可能優化的地方是“畫線”部分:

畫線的主要邏輯是:
- 當前場景 instantiate 一個用于畫線的全屏尺寸的預制對象
- 監聽節點上 touchmove,每次移動的時候對它上面的 cc.Graphics 組件進行 lineTo 的畫線處理,同時存儲每個移動點
- 用所有收集的移動點根據一個算法去構建 node 的物理碰撞區域
所以移動點越少越好,這樣的話,優化的手段就有兩個:
1.當前移動點與上一個移動點的直線距離少于一個限定值,就認為當前移動點無效。(相當于強制移動點的距離)
相應的判斷代碼很簡單,利用 Cocos Creator 提供的向量求距的方法即可:
return lastPoint.sub(nowPoint).mag() >= 5;
2.“當前移動點跟上一個移動點的移動方向”如果跟“上一個移動點跟上上個移動點的移動方向”一樣的話,那么上一個移動點即可銷毀不做記錄。(相當于把直線部分的移動點壓減為兩端點)
touchmove 回調中相應代碼如下:
//獲得增長向量 var addVe = thisPos.sub( thisPos.prevPos ) //如果有前一個增長變量 if(thisPos.prevPos.prevPos){ //獲取上一個點的增加向量 var lastAddVe = thisPos.prevPos.sub(thisPos.prevPos.prevPos) //如果兩個增長向量相等,則這次的點替換前一個點 if(Math.abs( addVe.signAngle( cc.v2(1,0) ) - lastAddVe.signAngle( cc.v2(1,0)) )<0.1){ this.points[this.points.length-1]=thisPos this.physicsLinePoints[this.physicsLinePoints.length-1]=thisPos }else{ this.points.push(thisPos); this.physicsLinePoints.push(thisPos); } }else{ this.points.push(thisPos); this.physicsLinePoints.push(thisPos); }
依靠上述 3 種方式,我大概能把 drawcall 控制在 50 左右,但其實效果還是能優化。
Cocos Creator 就性能優化來說,還提供了“節點池”和”動態合圖”的優化方式,本游戲目前還沒有應用上,未來也許可以試試。
最后
終于把要講的講完了,最后感慨一下。
花叔怎么說也從事互聯網快 10 年了,現在對于平時的工作也不需要單純的執行,也許從旁指導一下就算完事了。所以有人會問:你怎么還孜孜不倦地畫圖、寫代碼、做 demo 呀?還需要嗎?
我就給他說了一件事,從前有一天,我指導另一個同事的項目,振振有詞有理有據,但同事反問我一句:你多久沒寫代碼了?這里面是 xxxxx 這樣的。
我瞬間懵了一下。雖然還是答上去了,但后來我就在想,幸好他這個項目我以前研究過,不然就憑那句“你多久沒寫代碼了”,我就直接把主導權送出去了。
企業獲利大多是基于信息不對稱,誰是信息上游,誰就更有可能控制局面;管理者、策劃者給的是方向,固然重要,但信息不對稱的事情大多在于技術層。
我見過 cp 怎樣用技術忽悠需求方,特別是高新技術層面的事情,忽悠起來簡直輕而易舉。
有時候,我們轉型、通道轉通道、技術轉管理,以為是找了個更好更適合的方向。
其實我看到更多可能的:1.專業到了瓶頸,對瓶頸逃避而選擇轉型罷了;2.專業能力突出被授予管理權力,然后就以為要轉型了;3.真的不適合本專業
第三點且不說,前兩點,何曾想過你想轉向的領域,缺你嗎?你苦心經營的技術不時而鍛煉就會淡忘,就愿意因為一點點小瓶頸而舍棄嗎?
技術和管理本不沖突,但為何要因管理而丟棄技術,失去對技術創造力的追求。
還能不能好好定義自己是什么人哦?
最后,引用一前輩的話:“我這人習慣自己先搞清楚怎么實現再指導?!?/p>
誰不想當信息的上游啊。
你以為我平時指導多內部小游戲的研發,靠的是吹水咩….
花叔,是一名騰訊的 UI 工程師,同時也是一名游戲開發狂熱愛好者,歡迎各位開發者點擊【閱讀原文】與作者直接交流,也歡迎關注他的微信公眾號:MinProgram,小小程序,大大思路!