游戏引擎mota-js-v3.0 施工记录
前言
mota-js是一款用于做出魔塔類型游戲的HTML5 2d游戲引擎(github項目地址),目前最新的版本是v2.66,由于原主力開發已經工作,因此很長一段時間沒有大版本的更新。
最近在用樣板做一個游戲的時候,體會到了樣板的一些限制。主要有:1. 地圖尺寸受限,超過一定尺寸(30x30)會到達性能瓶頸,尤其是在手機上會很卡頓。2. 素材尺寸受限。貼圖種類被限制在32x32或32x48,尺寸更大做起來會很麻煩,沒有一個精靈系統。3. 實現一些特效很困難。樣板中大量使用了dom來分割地圖場景與狀態欄UI,一些聯動特效難以實現,并且使用dom對做游戲開發而非前端開發的人來說是個噩夢。
去年針對這些問題改了一版pre3.0的運行時系統,但現在回看感覺問題很多,首先是設計模式選取不當,造成理解困難,其次對原樣板的耦合太多,被原有框架所限制,導致渲染引擎的能力沒有發揮出來。因此這兩天決定重啟項目,解決之前的問題。
運行時系統簡介
這里主要施工的部分是運行時,編輯器有另外一位大佬在施工。游戲引擎的運行時系統,如果是復雜的游戲,其系統規模將會是龐大的,如圖是《游戲引擎架構》一書中對現代游戲引擎的系統架構描述。
這樣的架構在unity、ue4等引擎中有完整的體現,但在我要施工的對象上不可能有這么多。
HTML5游戲是運行在瀏覽器上的,因此瀏覽器解決了A1-A3、B、C以及部分D的問題。其次因為是2d游戲,許多核心系統中的東西用不到。然后因為魔塔是棋盤類單機游戲,位置是確定的方格,因此也不需要碰撞檢測、骨骼動畫、在線多人等模塊。最后,因為是在原樣板基礎上進行的二次開發,很多游戲算法都已經實現過了。因此算下來,借助成熟的第三方庫,工作量并不大,對預期的架構圖修剪如下,預期工期在15天左右。
施工日志
4-19
調研第三方庫:
- 渲染引擎PIXI
- 動畫庫tweenjs
4-20
實現資產管理(AssetsManager)。
資產管理中存在的坑:
4-21
實現動畫管理(AnimationManager)、精靈管理(SpriteManager)的部分功能。
4-22
實現一個地圖的原型:瓦片地圖繪制與角色移動。
對系統框架的進一步總結:
4-23
- 實現一個UI的原型:對話框的繪制與控制消失
– 對話框基于窗口基類(WinBase),這是UI的基本單元,包含基本的坐標、長寬屬性,并且可以有皮膚(WinSkin),具有聚合組件的功能。
– 組件(Components)是使能窗口的重要部分,可以接收消息控制窗口屬性,如,在對話框窗體注冊一個“點擊響應”(tapScreen)組件,其功能為關閉當前窗口。
– 對話框文字格式控制。控制字符,比如"\r \n"之類的,對文字內容進行格式控制,重寫了原樣板的實現,可以通過注冊實現任意格式控制,如字體變化、透明度變化等等。 - 實現尋路與路線繪制
– 尋路使用bfs,復用原樣板的函數。路線繪制重寫,功能性與原樣板一致。
– 對控制器(Controller)概念思考:一個控制器,響應用戶操作,控制一個對象,對象可以是角色、UI,也可以沒有特定對象,表示實現某種操作(比如響應點擊操作)。當有特定對象時,被控制的對象是完全“受控”于控制器的。舉個例子:勇士在被控制移動時,使用了尋路功能,那么此時,尋路走動的過程中,會不斷調用的move,而這個move,是勇士自身的方法,還是控制器的方法?如果注意了控制器的概念,那么這里就應該是調用控制器的方法,這樣做的好處是,把操作與對象的行為分離了,便于后續實現錄像系統——操作全部由控制系統管理,對象無需關心操作。
記錄一個坑:
ES6中的箭頭函數()=>{} 與 function有一個重要區別,那就是箭頭函數中的this綁定的是函數體外的,也就是說在聲明的時候就綁定了this,而不是像function一樣在調用的時候才綁定。
這兩部分需要后續補充的內容:
4-24
今天主要實現事件系統和消息管理的部分架構。
之前的進度到了勇士能在地圖移動,但是沒辦法和地圖上的事件進行交互,主要就對這塊進行施工。
事件、角色的概念
事件,在傳統角色扮演類游戲中一般特指會產生一系列劇情、動作的觸發點或者npc,角色,通常指的是主角和npc一類會動會產生行為的對象。
在設計事件系統的架構的過程中,理論上可以把地圖上能跑能動的有屬性的對象都當成是角色,但純圖塊(block)、和角色(帶事件的塊如npc)和勇士(玩家控制的對象)顯然不屬于同一種,單明顯帶有一種遞增的關系:
block -> actor -> hero (具體的關系等之后施工完了再完善)
在實現中,如果把所有帶事件的點,都當成是一個個“角色”,那么勇士觸發事件就可以當作是與角色之間的【交互】,簡單的例子比如碰撞事件。
實現碰撞事件過程如下:
這個過程實現的是【碰撞】這類交互,但是實際游戲中,不止碰撞,比如有的點是空點,有的點是【戰后事件】、【拾取后事件】……對原樣板有的或沒有的總結有如下類型的事件:
事件類型:
其中打!的是在地圖上定義的事件,這些可能還不是全部,那么問題來了,要對所有事件單獨寫代碼去判斷嗎?原樣板是這么做的,感覺工作量會很恐怖,我怕工期趕不及,所以有了下面的消息管理。
消息管理
消息管理(MessageManager)是一個處理行為產生的消息的模塊。
這里定義一下“行為”的概念,一般來說,游戲中的對象都能產生行為,但不是所有行為都會產生消息。比如之前的控制器,是用戶行為,但由于控制器是同步的,即時控制勇士,沒有必要產生消息。但是勇士的行為會產生消息,因為勇士走出去觸碰地圖點,可能會產生諸如碰撞、到達、離開等一系列情況,這些情況是不由勇士自己處理的,必須交由消息中心處理。當勇士發送消息時,是一個生產者,消息管理中心的任務是找到一個消費者處理這個消息。
比如前面11類事件,就是11種以上的消息,這些消息會在對應的代碼執行時,如果有消費者成功消費了這條消息,就會返回消費情況,讓生產者來進行判斷下一步的情況——這個過程中,生產者不需要知道自己的消息被誰消費。這樣只需要在合適的地方加入消息生產,就可以比較快速靈活地實現以上事件的處理。
移動事件
在原樣板中,事件與地圖點是綁定的,這在寫一些劇情的時候會很頭疼,npc不能頻繁移動,否則會滿地都是事件點,真·移動事件可以解決這個問題。
在上面的事件實現中,所有事件點都會成為角色(Actor),角色可以包含數據,這個數據可以用于定位事件原點。這樣移動事件本質上是移動角色,角色移動后,原點將不存在塊,勇士不再觸發事件,但觸發移動后的角色時,就會觸發其定位到原點的事件。
這一塊剛開始做,需要對事件系統進行進一步的施工。
存在的問題
- 同類消息多次注冊導致的沖突。當一個生產者有多個消費者時會出現這種問題,同樣的問題也會出現在控制器,比如點擊綁定了屏幕響應和勇者瞬移,一旦優先級不當,就有可能出現事件觸發的瞬間就點下了響應。只能從設計上避免。
- 移動事件需要存儲數據,但只有移動過的事件才需要,如果全部成為角色,很有可能沒法區分,或者是需要一定開銷去區分誰的數據需要進存檔。(也許這不是問題,需要進一步測試)
4-25
首先處理兩個bug。
第一個,開啟新頁面時,一些sprite會加載不出來,經過調試發現是PIXI的材質緩存機制導致的,開啟strict模式即可。
第二個,在手機上出現點擊失效的情況。經過調試發現是強制橫屏時,指針對象沒有做對應的適配導致的,對tink.js的源碼進行修改后修復。
然后基本完成移動事件和一個簡單的打字機,然后做了一部分異步處理。
移動事件中移動事件點的關鍵在于對塊信息和事件信息及時修改,事件管理以及地圖管理訂閱事件移動時發出的leave和arrive消息,對事件和塊進行重定位即可。
打字機之前就留了空,做起來也簡單了,做了一個動畫管理,申請一個【等待】的動畫對象,作用于對話框繪制中的【依次繪制每個字符】,然后處理好回調即可。另外實現了一個控制字符\w,可以控制說話的速度,比如下面圖就改變了兩次語速。
動畫這塊主要的問題在于解決異步,比如,如何實現說話過程中執行下一個事件?目前還沒有好的思路。
(打字機演示圖因審核問題刪去
4-26
最近事多,估計工期又要延后了……
今日主要做了兩個部分,一個是對資產管理進行了一定的補充,之前是基于原樣板的圖集包裝的,為了測試sprite的優先級變化,就用PIXI導入了其他類型的材質,然后在編輯器里做了一些改動,通過調整數據的材質來改變貼圖
另一個是試圖解決異步的問題。據說有一種叫做promise的東西,我去找來看看大概明白了是怎么回事,但似乎原生的實現效率并不高,而且不一定適合引擎里的架構,就先在角色類和動畫類進行實驗,因為這兩部分異步最多。
比如角色類,主要是移動,涉及到發送消息、移動動畫,如果不用promise,會出現大量回調,可讀性很差。
用promise改進后移動是這樣的:
其中涉及動畫的部分使用了一個onChange,用于移動過程中的優先級變化。
這種形式相比于不停callback看上去好多了,當然仍然沒有解決多個角色同時移動異步問題,因為這個還沒有實現Promise.all的效果。這個留到明天解決。
4-27
今天打算完成異步事件執行的部分。
角色部分全部架構基本完成,目前全部寫在ActorManager文件中,后續如果有增加新的角色部分可以考慮拆分,目前沒必要。
關于多角色異步執行(即昨天提到的promise.all),本質上和原樣板沒有區別,就是用一個唯一code掛在全局,執行完畢后回調取消掉code。當所有異步code都取消時,即為完成一次all。
以一個行走事件的異步過程為例。
角色消息中心事件管理地圖管理異步移動(Async)生成并記錄uid返回uid開始移動(Leave)Leave ActorLeave Actor在此期間可以執行其他事件移動結束(Arrive)Arrive ActorArrive Actor異步結束(取消uid)撤銷uid,檢查等待事件角色消息中心事件管理地圖管理角色每次移動開始時和移動到達后都要發送消息,事件管理和地圖管理接收該消息,并對地圖和事件數據進行修改(實現事件移動的方法),此外還需要在移動開始前和結束后對異步進行記錄,如果在移動的過程中,執行了等待全部異步事件(類似promise.all),則會在消息中心掛載一個一次性的回調函數,等到全部執行完畢后進行調用。同樣也能實現競爭式如promise.race的效果。但目前暫未用到。目前有一個問題尚未解決——事件的碰撞,比如A要到B的位置,B要到C的位置,看上去能執行成功,但發往事件管理和地圖管理的消息出現了競爭——無法確定誰先到達,如果A先到,發現B處已經有一個塊,就會發生重疊的錯誤情況。預想的解決辦法是同一個點碰撞后成為一個隊列,先進先出,這樣一來,即使A到達B的時候,B還沒離開,在B離開時也能正確取出自己的事件。
最后做了一些關于地圖特效的實驗和接口,學習了一下PIXI包裝的filter,實現一個簡易的色調變化。但后來嘗試做光影但遇到了一些困難,這塊還是缺乏一些理論基礎,有空了補一下。但特效畢竟不是目前引擎的重心,明天重點還是做核心的部分,至少要能執行完一個魔塔游戲的基本流程。
明日施工計劃對象:事件系統(修bug,以及繼續做基本事件的補充),戰斗系統。
4-28
總結一下目前實現的結構。
數據庫相關
AssetsManagerSpriteManagerAnimationManagerBattleManagerAssetsManager: 資產數據庫單例,管理包括材質、敵人信息、道具信息、技能信息、事件信息、角色信息等原始靜態數據。只有加載這些數據后,才能初始化后續三部分。
SpriteManager:精靈管理單例,處理包括角色精靈、動畫精靈、窗口精靈等動態數據的基本管理。后續計劃做一個精靈緩沖池,防止進行大量增刪行為(比如瀏覽地圖)帶來的開銷。
AnimationManager:動畫管理單例,實現各種特效的地方。提供動畫執行單元實例的獲取接口。
BattleManager(施工中):戰斗管理單例。進行戰斗數據管理,主要包括獲取敵人數據、戰斗傷害的計算。由于戰斗本身是屬于事件的部分,所以這部分純粹是作為一個API接口,如,輸入勇士信息,查詢敵人、獲取敵人信息并返回,不涉及到對實際運行數據產生的影響——但實際運行中的數據會影響到這里的計算結果。
游戲性相關
分發消息extendsextendsextendsextendsControlManagerMessageManagerListenerSceneManagerMapManagerEventManagerActorManagerControlManager:控制管理單例。包括兩部分:1. 用戶的輸入指令管理 2. 輸入指令后產生消息的管理。
MessageManager:消息處理單例。負責匯總各種消息來源的消息,并分發給各個監聽者。
Listener:監聽基類,相當于是為MessageManager專門配的一個接收者。所有被動接收消息進行處理的管理器都需要繼承此類。
SceneManager:場景管理單例。負責場景繪制,包括狀態欄、菜單欄、地圖界面、UI界面等。
MapManager:地圖管理單例。負責地圖的狀態存儲,包括地圖上的角色信息,地形信息等。
EventManager:事件管理單例。負責響應事件消息,如移動事件、戰斗事件、自定義事件、轉場事件等。
今天首先做了一個Listener類,把之前的幾個消息接收者都歸總了起來,使之具有高擴展性。
使用的一個例子如下,增加一個新的戰前事件,改變角色的屬性:
理論上所有繼承了Listener的實例都能注冊接收這個消息然后進行處理,也能work,但是不應該這么做。因為在目前的實現中,如果消息接收者都進行異步處理,那么將是一種偽并發狀態,無法確定先后,而事實上,不同模塊的消息處理優先級是不同的,所以后續可能會調整模塊消息接收的優先級,部分模塊的消息處理很可能需要等其他模塊都完成后才進行。
然后是戰斗系統。借用了一些原樣板的戰斗計算內容,實現了技能的部分。
魔塔戰斗系統介紹
戰斗系統是魔塔類游戲的核心,屬于一種固定數值的回合制戰斗,即勇者、敵人每回合互相造成傷害,直到一方倒下,在沒有加特殊技能的情況下,其結果是能夠通過公式解析出來的。在傳統的三原塔里(4399的50層、新新、24層),是有戰斗動畫演示這個回合過程的,但在現代魔塔游戲里(以RM魔塔、H5魔塔為代表),這個戰斗動畫被基本取消了,玩家更多的重點關注在路線中,尤其以H5為甚,不僅取消了動畫,還引入了瞬移,以加快游戲節奏,此外,玩家還需要查看大量的傷害數據,以及更高階的數據信息,包括臨界減傷表(加x點攻擊減少x點傷害)、防御減傷表(1防減少x點傷害)等。這使得戰斗系統有一定的計算負擔,但是傳統的戰斗算法是有解析解的,所以問題不大。
但是,在一些藍海塔加入一些特殊技能后,比如“第x回合造成x點傷害”、“怪物每回合增加x點防御”、這種,很難有解析算法,再加上魔塔中勇者數據是變化很快的,到處都是引起屬性變化的寶石,使用緩存計算基本不太可能,很可能在計算一些大數據塔的過程中產生嚴重的卡頓。
因此關注戰斗系統,首先就對技能進行一定的關注。技能本質上是一個影響戰斗進程的特殊變量,正常的戰斗過程如下(感覺這個過程可以叫做戰斗管線了…):
戰斗基本信息勇者信息敵人信息傷害信息beforeBattlegetHeroInfogetEnemyInfo計算傷害afterBattle怪物技能可以繼承一個基類,基類的以上函數全部留空,即按默認來。技能覆蓋對應的函數后,可以對特定過程的數據流發生變化。
舉例來說,【硬化皮膚】技能1:怪物的防御力額外增加勇者50%攻擊的數值。那么就繼承getEnemyInfo函數,將敵人的防御數據修改即可。
但這么修改也有問題:修改不是線性的,多個技能時會發生沖突。比如有個技能2:【防御強化】怪物防御力增加20%。
因此每個函數添加一個修改單元,存放每個技能產生的修改結果,主要有兩種,一種是百分比(percentage)、一種是固定數值變化(hardchange),接下來的寫法就是:
一般來說這個模型對于大部分數值類的技能是夠用的,但對于一些特定需求可能無法滿足,一方面,比如勇者的某種屬性依賴于怪物(比如勇者防御力增加怪物的某個屬性值),另一方面比如回合類的技能,其本質是循環,需要對傷害計算進行大量修改,暫時不考慮。
此外一個優化點,現代魔塔的地圖顯傷包含了上圖過程2、3、4的計算,而且互相獨立,據鹿神介紹可以用Worker實現異步計算,明天學習一下。
4-29
今天繼續完善戰斗系統。
昨天提到的Worker去看了,發現這個東西需要獨立的上下文環境進行線程計算,和主線程之間只能通過通信進行交互,這就很麻煩了。后來想到可以用空閑計算的方法進行異步計算,但目前測試還沒有到性能瓶頸,先暫時放棄優化這塊。
戰斗部分完善傷害計算,將顯傷加入地圖場景中。效果如下:
目前已經能夠完成一個最簡單的游戲流程:打怪、撿寶物、切換地圖、對話,但還缺少一個重要的模塊:狀態管理。
這里的狀態,指的是包括勇者的數值、游戲變量、錄像等實時信息,之所以要對這塊進行管理,是因為涉及到一個重要功能:存讀檔。
SL大法玩過游戲的都知道,遇事不決就存檔是rpg中的常見操作,在魔塔中更甚,玩一座有一定難度的塔會產生大量的存檔,因為其中包含大量的路線分歧,經常需要頻繁存讀檔,再加上H5有一個【自動存檔】功能,因此對存讀檔的性能有一定的需求。
就之前的經驗看,當塔層數較低(低于一百層)時,幾乎不會有卡頓,但在層數上升到一百多以上時,由于地圖數據讀檔和存檔過程中反復刷新,會產生一定的延遲。
因此,如果要用樣板制作大型的藍海塔,有必要對存讀檔進行優化。
明天進行狀態管理部分的施工。
4-30
狀態管理參考了一些博文(存讀檔功能在unity3d的實現 ),將游戲中狀態分為如下幾個部分:
- 勇士狀態(生命、攻擊、防御、道具……)
- 進程(游戲變量、統計信息)
- 地圖(塊的設置和移除情況)
- 事件(位置、激活情況)
本質上來說,這些都可以歸為變量,但在考慮到對狀態的存檔讀檔的時候,又有一些區別,其中勇士狀態和進程在存讀檔時是需要完全存儲和加載的,不可分割,但是地圖和事件就不一定了。
舉例來說,一個游戲玩到第三關,后面還有四關沒打,這時讀取第二關的存檔,那么存檔中關于后面四關的信息是無需加載的,存儲也一樣。這可以通過臟標記來實現。但這樣還不夠。當游戲進行到中后期,已經改動了很多的地圖狀態,臟標記已經很難有優化效果了,需要另外找辦法。
通過對原樣板的觀察測試發現,此時存讀檔的主要開銷在于對全部地圖數據的記錄和讀取,測試中兩百層地圖的讀取大約需要200毫秒(5fps),這對逐幀繪制的一些特效會產生明顯卡頓,可以通過懶加載的方式避免:只對讀檔的目標地圖進行加載,其他的部分等到訪問時再進行加載。
存檔相對高效一些,但是也只有10fps,在自動存檔時,也會影響一些需要高fps的畫面,存檔的優化可以通過建立存檔樹來解決。存檔樹演示如下:
當前x存檔1存檔2存檔3假設存檔3是之前試錯的一條路,通過讀檔回存檔1后,進行另一個選擇,存儲了存檔2。注意到,無論是存檔3還是存檔2,都是在存檔1的基礎上進行的改動,那就意味著:存儲的時候不必存儲全部內容,只需要存儲相對于父存檔的改動即可。這個本質上也是一種臟標記的應用,但是會在每一次存檔的時候,清除臟標記,所以臟標記會很少。
但是如果存檔進行了這樣的改動,讀檔也必須與之匹配才行。如果只存儲相對改動,這無疑會增加讀檔的開銷:讀檔需要去找存檔樹的關系進行拼接,不斷查詢存檔,這是很費時的,因為存檔不是全部都存在內存中。
這就產生了矛盾:加速存檔,就會減慢讀檔,加快讀檔,就會減慢存檔,有沒有兩全其美的辦法,既能迅速讀檔也能迅速存檔?……很遺憾,暫時沒有,但可以優先解決自動存檔,這樣就解決了大部分可能的卡頓。
在游戲過程中,觸發最為頻繁的是自動存檔,自動存檔指的是在進行一些操作,如切換地圖、戰斗、開門等不可預知行為時進行的存檔,相當于上個保險,防止誤操作。目前的版本中支持一定步數的連續回檔,即自動存檔成一個隊列,可以回到前幾步的狀態。
自動存讀檔是典型的適合存檔樹+懶加載的優化點,原因是每次存儲和讀檔都修改極小,而且存檔都在內存中(自動存檔不會全部持久化),是一個天然的鏈式結構,因此可以對其進行著重優化。
實現上,先實現一個最簡單的緩存基類,用于優化自動存讀檔。包含方法有:
然后地圖管理包含一個繼承緩存基類的對象,切換樓層以及對圖塊增刪時進行標臟。
在戰斗后加上自動存檔,測試發現每次存儲量都很小(一張地圖),存取時間約20毫秒,基本不影響性能。
明天再考慮如何做手動存讀檔以及更復雜的樹形分支。
5-1
考慮以下三個基本功能:(規則1)
回退是一個之前沒考慮的新功能,就我個人來說,一般用于load手滑多退了一步的情況,但也有說能用來分析路線?不過這些不重要。
演示如下,狀態1~3是存儲的三個存檔,當前狀態是未保存的進度。
狀態1狀態2狀態3當前狀態往回讀(load):
Xload狀態1狀態2狀態3當前狀態此時無法使用back,原因是前一個【當前狀態】并沒有存儲,已經丟失(如果在讀取時進行了存儲則另算,先不考慮)。
再次load:
此時可以通過back回到狀態3。
為了實現手動存檔讀檔,將在這個模型基礎上對save、load、back進行第一次擴展:(規則2)
如下:
load:index=2back:index=2狀態1狀態2狀態3當前狀態這樣可以實現鏈式存檔的手動存讀。但存在一個問題:如果在讀回狀態1后,進行了新的狀態保存,就會變成:
狀態1狀態2狀態3狀態4當前狀態此時之前的模型不能適用,將再次擴充(規則3):
改動比較大的在load,演示如下,從狀態4讀到狀態3:
1.load2.back狀態1狀態2狀態3狀態4這樣實現的性能瓶頸在于查詢各個存儲的狀態然后進行合并。據鹿神說并發讀取存檔開銷并不大,試了一下localforage,讀取900個存檔只用了70ms。因此存讀這塊并不是問題,難的是如何實現這一塊。有可能并不會需要這種LCA操作,一定深度后做一次全存是一個好的方法。
明天再嘗試進行具體的實現施工…
5-2
存檔實現預期超過預期時間,放棄,改為懶加載優化。
存檔時:如果有沒有修改過的樓層,就不必重新壓縮,直接存入。
讀檔時:讀取未解壓的存檔,只有訪問目標樓層時,才解壓目標樓層。
完善材質類型,增加對tileset的支持。
調研自動元件的實現:自動元件參考文章:RMXP的自動元件繪制原理
原樣板的繪制方法不再適合PIXI的框架,需要基于ActorSprite增加一種多模態的元件,情況略有些復雜,但原理不變。
5-3
自動元件竣工。
每個自動元件圖塊包含四個小sprite,通過放置在四個角落拼湊為一個完整的圖塊,這四個圖塊的模式一共47種情況,由九宮格的邊角決定。
上面的參考博文給出了“繪制情況-小元件”的映射表,但沒有給出“邊角-繪制情況”的映射,在此記錄如下:
// javascript let edge = {}; /*** 對mask符合filter的edge填充角落* @param value* @param filter*/ function fillCorner(filter, value, mask) {mask = mask || 0xf;for(let i = 0; i < (1<<8); i++){if((i & mask) == filter){edge[i] = value;}} } // 0 邊 fillCorner(0, 47); // 1 邊 fillCorner((1<<0), 42); // 下 fillCorner((1<<1), 43); // 右 fillCorner((1<<2), 44); // 上 fillCorner((1<<3), 45); // 左 // 2. 2邊 fillCorner((1<<0) + (1<<2), 32); // 下 + 上 fillCorner((1<<1) + (1<<3), 33); // 右 + 左 —— 對角無影響fillCorner((1<<1) + (1<<0), 35, 0xf | (1<<4)); // 右下* fillCorner((1<<1) + (1<<0) + (1<<4), 34, 0xf | (1<<4)); // 右下* —— 4fillCorner((1<<1) + (1<<2), 41, 0xf | (1<<5)); // 右上* fillCorner((1<<1) + (1<<2) + (1<<5), 40, 0xf | (1<<5)); // 右上* —— 5fillCorner((1<<3) + (1<<2), 39, 0xf | (1<<6)); // 左上* fillCorner((1<<3) + (1<<2) + (1<<6), 38, 0xf | (1<<6)); // 左上* —— 6fillCorner((1<<3) + (1<<0), 37, 0xf | (1<<7)); // 左下* fillCorner((1<<3) + (1<<0) + (1<<7), 36, 0xf | (1<<7)); // 左下* —— 7// 3. 3邊// 缺左 左角無影響 // 右滿 fillCorner((1<<0) + (1<<2) + (1<<1) + (1<<4) + (1<<5), 16, 0xf | ((1<<4) + (1<<5))); // 右下 fillCorner((1<<0) + (1<<2) + (1<<1) + (1<<4), 17, 0xf | ((1<<4) + (1<<5))); // 右上 fillCorner((1<<0) + (1<<2) + (1<<1) + (1<<5), 18, 0xf | ((1<<4) + (1<<5))); // 無右 fillCorner((1<<0) + (1<<2) + (1<<1), 19, 0xf | ((1<<4) + (1<<5))); // 缺上 fillCorner((1<<0) + (1<<1) + (1<<3) + (1<<4) + (1<<7), 20, 0xf | ((1<<4) + (1<<7))); // 缺 上 + 下滿 fillCorner((1<<0) + (1<<1) + (1<<3) + (1<<7), 21, 0xf | ((1<<4) + (1<<7))); // 缺 上 + 左下 fillCorner((1<<0) + (1<<1) + (1<<3) + (1<<4), 22, 0xf | ((1<<4) + (1<<7))); // 缺 上 + 右下 fillCorner((1<<0) + (1<<1) + (1<<3), 23, 0xf | ((1<<4) + (1<<7))); // 缺 上 // 缺右 fillCorner((1<<0) + (1<<2) + (1<<3) + (1<<6) + (1<<7), 24, 0xf | ((1<<6) + (1<<7))); // 缺 右 + 左滿 fillCorner((1<<0) + (1<<2) + (1<<3) + (1<<6), 25, 0xf | ((1<<6) + (1<<7))); // 缺 右 + 左上 fillCorner((1<<0) + (1<<2) + (1<<3) + (1<<7), 26, 0xf | ((1<<6) + (1<<7))); // 缺 右 + 左下 fillCorner((1<<0) + (1<<2) + (1<<3), 27, 0xf | ((1<<6) + (1<<7))); // 缺 右fillCorner((1<<1) + (1<<2) + (1<<3) + (1<<5) + (1<<6), 28, 0xf | ((1<<5) + (1<<6))); // 缺 下 + 上滿 (存疑 26 27 44 45 ?) fillCorner((1<<1) + (1<<2) + (1<<3) + (1<<6), 30, 0xf | ((1<<5) + (1<<6))); // 缺 下 + 左上 fillCorner((1<<1) + (1<<2) + (1<<3) + (1<<5), 29, 0xf | ((1<<5) + (1<<6))); // 缺 下 + 右上 fillCorner((1<<1) + (1<<2) + (1<<3), 31, 0xf | ((1<<5) + (1<<6))); // 缺 下// 4. 4邊 let four = (1<<0) + (1<<1) + (1<<2) + (1<<3); // -------- 右下 右上 左上 左下 ----------- edge[four + (1<<4) + (1<<5) + (1<<6) + (1<<7)] = 0; edge[four + (1<<4) + (1<<5) + (0<<6) + (1<<7)] = 1; // 缺左上 edge[four + (1<<4) + (0<<5) + (1<<6) + (1<<7)] = 2; // 缺右上 edge[four + (1<<4) + (0<<5) + (0<<6) + (1<<7)] = 3; // 缺左上 右上 edge[four + (0<<4) + (1<<5) + (1<<6) + (1<<7)] = 4; // 缺右下 edge[four + (0<<4) + (1<<5) + (0<<6) + (1<<7)] = 5; // 缺右下 左上 edge[four + (0<<4) + (0<<5) + (1<<6) + (1<<7)] = 6; // 缺右下 右上 edge[four + (0<<4) + (0<<5) + (0<<6) + (1<<7)] = 7; // 缺右下 右上 左上 edge[four + (1<<4) + (1<<5) + (1<<6) + (0<<7)] = 8; // 缺左下 edge[four + (1<<4) + (1<<5) + (0<<6) + (0<<7)] = 9; // 缺左上 左下 edge[four + (1<<4) + (0<<5) + (1<<6) + (0<<7)] = 10; // 缺左下 右上 edge[four + (1<<4) + (0<<5) + (0<<6) + (0<<7)] = 11; // 缺左上 左下 右上 edge[four + (0<<4) + (1<<5) + (1<<6) + (0<<7)] = 12; // 缺左下 右下 edge[four + (0<<4) + (1<<5) + (0<<6) + (0<<7)] = 13; // 缺左下 左上 右下 edge[four + (0<<4) + (0<<5) + (1<<6) + (0<<7)] = 14; // 缺左下 右上 右下 edge[four] = 15; // 都缺最后得到的一個edge,是一個邊角情況到繪制情況256-47的映射,這個可以作為繪制依據的查詢。
攝像機(Camera)的概念
地圖的進一步完善是實現大地圖。當前的地圖只能提供一定寬度的顯示,超過則會溢出到邊界,無法正常運行。要實現更大尺寸的地圖,在此先引入攝像機的概念。
Camera一般在3d游戲中使用比較多,因為涉及到成像透視等一系列需求,需要這個概念來幫助理解。在2d游戲中因為是平面的緣故,一般都是用畫布概念,動畫和游戲過程就是不斷繪制的過程。但涉及到遮擋的時候,畫布就無法幫助理解了。
2d攝像機本質也是一個畫布,但它對應著一個游戲實體,也即照射的場景(Scene),之前做場景管理,不僅管理場景中的數據,還混入了渲染,而引入攝像機后,渲染的過程就應放到攝像機中,其邏輯為:
數據渲染場景攝像機canvas攝像機要存儲視角(viewPoint),作為場景繪制的依據,其次,還需要一個渲染區域(renderArea)作為繪制目標——即畫到窗口的何處。當繪制超出邊界時,需要進行剪裁。最后,為了讓視角跟隨主角,需要有一個綁定對象到視角的方法。
目前實現能夠在電腦上基本能夠較為流暢地繪制一個52x52的大地圖,但在低性能手機上會嚴重的幀率下降(下降至20fps),推測是由于大量的sprite更新導致的,可以考慮進行優化——使用緩沖池,只對當前畫面的一部分進行刷新。
5-4
給大地圖加了緩動后基本竣工,留下兩個問題:1 手機上的性能優化 2 讀檔優化(大地圖讀檔會導致大量sprite申請和銷毀 這是沒必要的),等之后細節優化再進行吧,今天主要進行事件部分的施工。
事件部分的改進點如下:
后記
架構的施工記錄到此基本接近尾聲,后面就是添磚加瓦充實游戲性系統、細節優化以及找bug。對其中有價值的部分會專門開文章寫,這篇不會再更新了,主要最近又忙起來了,不知道咕到什么時候才能全部做完……就這樣吧。
總結
以上是生活随笔為你收集整理的游戏引擎mota-js-v3.0 施工记录的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 调用scp命令_linux之s
- 下一篇: 百度云 x 中国联通 | 立标杆,中国联