前端?請叫我遊戲開發

本文轉載自公衆號:流利說技術團隊

一、背景

半年前項目組決定開發一款遊戲化的 app。但是組內還沒有遊戲開發人員。作爲一個前端工程師,還算熟悉號稱“鎮後端”、“鎮客戶端”的 JavaScript。遂果斷跳入遊戲開發的坑中。這篇文章從比較 general 的方面對比了前端開發和遊戲開發的一些區別,算是這段時間工作的一個總結,希望更多前端小夥伴們也可以嘗試下游戲開發。

二、引擎選擇

市面上存在的遊戲引擎有很多,比如 Unity,Cocos2d-x,Egret 等。最終選擇了 Cocos Creator,原因主要有幾下幾點:

  • 跨平臺,平臺無關的代碼共用,可以提高開發效率
  • 使用 JavaScript/TypeScript 作爲開發語言,對於前端比較友好。對 ES201X 語法支持比較完善。
  • 1.x 版本(筆者開始做時還未發佈2.0版本)是基於 Cocos2d-x 實現的。文檔資源和社區環境都比較成熟,便於上手。
  • 編輯器提供了集成式的開發、設計環境。最大程度上提高了程序員和設計師的合作效率。
  • 支持多種平臺,可移植性比較好

本文以下部分提到的”遊戲“均指 Cocos Creator 引擎,其他遊戲引擎可能會有所不同。

三、遊戲和前端開發的區別

1、共同點

  • 主要編程語言都是 JavaScript/TypeScript,JavaScript 中常用的 deisgn pattern 都可直接使用
  • 使用 prettier 進行代碼風格管理,使用 tslint/eslint 對進行代碼質量管理
  • 使用 jest 進行單元測試
  • 基於組件。組件在各種前端框架中很常見,同樣遊戲中也是基於組件封裝隔離各個模塊的。當然遊戲中的組件跟前端框架中的組件還是有很大區別的,後面會專門介紹。
  • PM還是會不斷地改需求😂😂

2、不同點(技術方面)

資源(Asset)

遊戲的代碼倉庫裏資源佔了很大一部分比例,這些資源的組織和使用方式跟前端還是差別很大的。

  • 遊戲工程裏幾乎所有的東西(包括代碼、文件夾)都是資源。這裏的資源是面向引擎而言的,而非面向開發者的。
  • 遊戲中用的資源種類非常多,對於不同的資源的加載使用方式略有不同。前端通常使用不同的 HTML 標籤來加載對應的 DOM 來處理,遊戲裏主要是通過不同的內置組件來。
  • 遊戲中所有的./assets目錄下的資源和文件夾,編輯器都會自動創建一個對應的.meta文件。該文件定義了該資源的 uuid、版本信息等。這裏需要注意一個常見的問題,git 不會管理空文件夾,遊戲編輯器卻會爲這個空文件夾創建一個 meta 文件,此時遠端工程可能會有一個沒有資源與之對應的 meta 文件,從而導致 ci 編譯失敗。
  • 遊戲中使用資源的方式有兩種。靜態索引和動態加載。前者通過在渲染的節點組件上設置屬性,指向資源的id,在節點加載的過程中加載資源。後者是在代碼中根據資源路徑動態加載。動態加載的資源必須位於工程的resource目錄下。因爲引擎打包時默認會打包所有resource目錄下的資源和其他目錄中被靜態引用的資源。前端(web環境)的資源都是通過網絡加載的,所以不存在這個問題。

工程目錄結構

遊戲中一種比較常用的工程組織結構是按照資源的種類進行劃分。比如/imgs/audios。這種組織方式在比較小的項目中問題不大,但在大的項目中會有如下缺點:

  • 多人修改同一目錄,非代碼文件(.meta.fire.prefab)的衝突會增多,解決起來非常難受
  • 過於扁平,不能反映出項目模塊的關係,迭代幾次後項目會變得非常混亂
  • 在我們的實踐中其實借鑑了前端項目中的工程目錄方式,
  • 這裏assets相當於前端工程慣例中的src目錄;
  • foundation相當於services或者utils,放置一些公用(全局)的類庫。
  • modules相當於pages,存放的是場景(相當於前端的頁面)。
  • resources則相當於components,存放是在頁面中引用的組件。每個組件目錄下都有對應的腳本、圖片、音頻等資源。
assets
├── foundation
│   ├── common
│   ├── lib
│   ├── protos
│   └── utils
├── modules
│   ├── activity
│   └── roadmap
└── resources
    ├── activities
    ├── roadmap
    ├── sessions
    └── start

組件設計

用慣了 React、Vue 之類的前端框架之後,組件就是一個輸入數據、輸出UI的模塊,開發的過程中很少去操作 Component,component 的創建銷燬幾乎都由框架完成。開發者的工作重心更多的是如何將多個代表組件的元素(Element)拼接起來。他們之間通過 props 或者事件(Vue)來通信就好了。組件的開發和使用都是偏聲明(函數)式的,更關注數據的流動。

而在遊戲中的組件,只有在其他組件獲取到了這個組件的引用並使用組件的方法使之完成某個任務纔有意義,是偏面向對象的,更關注邏輯的依賴。

其次。前端框架的組件裏需要關注UI的渲染,即輸出的(虛擬)DOM結構。而遊戲組件只專注邏輯,渲染則需要開發者手動在編輯器中創建節點,遊戲引擎通過解析節點樹及綁定在節點上的腳本組件來完成的。

舉個🌰

遊戲腳本組件

class Prarent extends cc.Component {
    son: Son = null
    letSonRun () {
        this.son.run()
    }
}
class Son extends cc.Component {
    label: cc.Label = null
    run () {
        this.label.string = 'Running'
    }
    stop () {
        this.label.string = 'Stopped'
    }
}

React 組件

class Parent extends React.Component {
    letSonRun () {
        this.setState({ isSonRun: true })
    }
    render () {
        <Son isRun={this.state.isSonRun}>
    }
}

class Son extends React.Component {
    render () {
        return this.props.isRun ? <div>Running</div> : <div>stopped</div>
    }
}

樣式/適配

  • 前端的樣式是通過 CSS 實現的,遊戲的樣式則是建立在一套座標位置體系和一些內置的組件上的。

  • web 頁面中元素的位置在自然狀態下是流式排布,即結構中後來的元素在已有元素的後面(下面)。而遊戲中則需要每個元素都給定一個基於笛卡爾座標系的位置。默認位置是(0, 0)
  • 相對於 css 的font-屬性,遊戲提供了 Label 和 RichText 組件
  • 相對於 css 的定位屬性,遊戲提供了 Widget 組件
  • 前端針對移動端的適配方案多采用自適應彈性佈局。除了可以使用塊級組件的流體特性外,還可以使用 FlexBox、Grid,em 等實現更爲精細複雜的適配。而遊戲中的彈性是天然自帶的。在遊戲工程中需要設置頂層節點(Canvas)的設計分辨率,並指定是按照高度還是寬度適配,這裏先假定按照高度適配。設定完成後,最終 canvas 的高度就會撐滿屏幕,然後寬度按比例縮放。當然這種適配方式只能根據屏幕做等比縮放,還是很有侷限性的。比如多個子元素在固定大小父元素下的彈性佈局,遊戲提供了 Layout 組件(類似 flexBox)解決這個問題。在進行更爲精細的屏幕適配和彈性佈局,需要手動對節點進行縮放。

測試

前端的測試生態已經很完善了,從單元測試、集成測試、到 E2E 有一整套的工具可以拿來用,不同的框架也會有響應的測試工具包提升寫測試用例的效率。但是遊戲社區內幾乎未找到現成的測試框架、工具。

基於遊戲引擎跨平臺的特性,我們正在試驗一種測試方案:

  • 把測試分成兩種類型:一種是跟 UI 無關的純邏輯功能測試;另一種是需要驗證 UI 的測試。
  • 對於第一種情況。我們可以藉助前端現有的測試框架來做單元測試。
  • 對於第二種情況又可以分成兩種情況來做。
  • 對 Native 平臺沒有依賴的模塊完全可以將代碼跑在瀏覽器環境下做集成測試
  • 對 Native 平臺有依賴的的模塊可以搭建一個 Electron 環境,模擬 Native 環境,完成集成測試

Git & Code Review

  • 遊戲工程中會有很多的大體積資源文件,比如圖片、音頻甚至是視頻。這些文件的 git 變動會導致我們的遠程倉庫體積快速膨脹。所以我們使用git-lfs來管理這些大文件。它將你所標記的大文件保存至另外的倉庫,而在主倉庫僅保留其輕量級指針。 那麼在你檢出版本時,根據指針的變化情況下更新對應的大文件,而不是在本地保存所有版本的大文件。
  • 在資源部分我們提到導入資源時,會爲每個資源創建 meta 文件。同時在遊戲項目中還有場景(fire)文件、預製(prefab)文件。這些文件都是編輯器爲我們創建的,雖然都爲文本,但是可讀性比較差,在 Merge Request 中的進行 Code Review 的意義不大。這裏我們採用 @melon 同學開發的 chrome 插件 gitlab-reviewer 摺疊某些後綴的文件內容,只閱讀代碼和配置文件等有價值的部分,極大地提高了 code review 的效率。

周邊工具

遊戲的工具鏈比較長,尚未窺得全貌,這裏只說幾個平時用的比較多的吧。

  • TexturePacker Texutre 是遊戲裏面對圖片的稱謂。顧名思義,這個工具就是打圖集。圖集在遊戲裏的意義前面已經說明了。
  • Tiled Map Editor 是一款開源的地圖編輯器。主要用於規劃地圖上的點位、路徑及相關的數據。有地圖的遊戲應該都離不開
  • dragBones 一款骨骼動畫設計工具,主要供設計師使用,開發只需要瞭解即可
  • particle designer,一款粒子編輯器,主要給設計師用
  • Hiero 位圖編輯器,用於生成字體位圖
  • tiny-png CLI 圖片壓縮工具,可以批量壓縮圖片,減少包體積利器
  • ffmpeg 音視頻處理工具,主要用於壓縮音頻,減少包體積利器

3、不同點(非技術層面)

與設計師的合作

通常對於前端來說,設計師給出交互稿和視覺稿就可以動工了。但是對於遊戲來說還遠遠不夠。各種粒子特效、幀動畫都需要設計師來做。好在遊戲編輯器提供了一套對設計師友好的設計工具。設計師可以直接在編輯器中進行設計,設計輸出可以推送到倉庫中直接給開發使用,避免了開發從設計到開發的重複實現過程。

但是開發和設計師事先規定一套良好的設計規範仍然非常重要。

  • 命名規範。設計師命名都比較寫意,從開發角度 camelCase, PascalCase, kebab-case 還是要提前規範好,所有的資源文件名應能直接看出其用途
  • 組件拆分。每次新的需求需要開發和設計師共同定義要拆分的模塊的動效。避免開發過程中對設計師已經做好的特效再重新拆分重組,浪費時間。
  • git 規範。沒錯,我們的設計師會用 git,但他們可能在這個項目之前並沒有用過,所以所有 git 規範都應該提前定義好,git-flow 和 commitlint 都要提前設置好😂

四、後記

經過將近半年的迭代,我們的項目已經走過了5個版本,迭代也日趨穩定(插播一條廣告,各大應用市場搜索“ 兒流利說 ”即可看到我們的產品)。這是一個 Team 團結合作的結果,在這裏感謝所有爲遊戲項目貢獻力量的開發小夥伴們:

  • 我們的 Tech Leader 在項目初期在架構上以其深厚的項目經驗給出了很多寶貴的指導和建議,讓項目在初期少走很多彎路。在日常工作裏也負責很多跨部門的協調工作,可以讓我們更專注在開發上,感謝🙏;
  • 我們的 Team 後來也慢慢有了更多、更專業的遊戲開發工程師,給後來的遊戲開發提供了強有力的支持,也給我個人的遊戲開發技能提升提供了很多幫助,感謝🙏;
  • 我們遊戲還是運行在 Native 平臺上的,客戶端的同學在完成產品需求外,在遊戲的編譯、打包工具方面提供了大力支持。現在偶爾也來支援寫遊戲,感謝🙏;
  • 最後,還有和我一樣身爲前端工程師還奮戰在遊戲開發前線上的小夥伴,和你們一起寫代碼、code review 是一件非常開心的事,感謝🙏。

文章已於2019-03-12修改

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章