学习3 二维游戏动画合成(侠客行)
說明:
<學習>系列所有的源代碼均由《計算機游戲程序設計》提供。本人會在這些代碼中融入自己的想法,對其進行迭代優化,旨在個人學習。
?
教材源代碼示例:
示例代碼的迷之bug賊多,怪物AI也只有一套固定的行動模式
優化后的效果:
?
學習目標:
1.了解二維游戲動畫合成原理。
2.熟悉Cocos2d-x中的用戶交互、觸摸事件、碰撞檢測機制。
3.熟悉CocoStudio動畫編輯器的使用,了解骨骼動畫。
主要修改內容:
1.利用cocostudio修改人物骨骼,并將修改結果在游戲中讀取,從而改變人物外形,動作等,實現自定義人物骨骼動畫效果。
2.增加英雄defend動作,記錄成功/失敗次數,增加計分板功能。
3.修復bug,使游戲能正常運行。
4.怪物AI設計。
?
步驟與過程:
1.利用cocostudio修改人物骨骼,并將修改結果在游戲中讀取,從而改變人物外形,動作等,實現自定義人物骨骼動畫效果。
?
CocoStudio Animation導入新的部位的圖片,如下圖的帽子和斧子:
圖 1
圖 2
?
創建新骨骼并對圖片進行綁定,再綁定到相應父骨骼上,得到新的角色外形:
圖 3
之后更新英雄角色每個動作的角色外貌,如下圖奔跑動畫:
圖 4
?
添加自制防御動畫,如下圖所示:
圖 5
最后給每個動作的最后一幀中的某個部位層添加一個“動作名_end”的幀事件。如下圖添加攻擊動作的幀事件:
圖 6
?
導出文件后替換掉源代碼中英雄文件,運行程序,發現英雄外貌成功改變了:
圖 7
?
2.增加英雄defend動作,記錄成功/失敗次數,增加計分板功能。
?
在cocostudio中已制作了“防御”defend的動畫,接下來只要將防御按鈕和動作加到游戲中去就行。如下為AnimationScene.cpp文件中按鈕代碼:
圖 9
圖 10
為區分攻擊和防御鍵,另其分別使用兩張不同的按鈕照片。其中,攻擊按鈕為紅色,防御按鈕為藍色:
圖 11
枚舉類型中添加了防御DEFEND后,英雄類文件Hero中,簡單模仿攻擊方式的代碼寫一個防御方式,使點擊防御按鈕后實現防御功能,如下圖所示:
圖 12
關于防御功能的代碼及其相關完善,我將在后文的第5部分再詳細說明。
?
增加計分板:
重新新建一個文件SaveScore (.h和.cpp):
SavaScore.h
圖 13
SavaScore.cpp
圖 14
?
把SaveScore.h頭文件導入AnimationScene.h文件中被其引用。 SaveScore文件的功能為存儲英雄和怪物的比分,heroScore為英雄得分,enemyScore為敵人得分,剛開始都為0。這種做法好處在于,可在AnimationScene文件中直接對heroScore和enemyScore進行操作,在AnimationScene場景被刷新后,計分值也不會改變,直接調用即可。
計分板代碼:
圖 15
update函數中不斷調用judge函數,根據角色的血量來判斷輸贏,代碼為:
圖 16
圖 17
當每輪游戲結束時(還沒到最終輸贏),需要刷新當前場景,這里用scheduleOnce的方式調用了restart函數,該函數里執行的代碼可刷新場景。由于一輪游戲結束后場景不會被馬上刷新,而是在等待幾秒中后才刷新,所以這里的scheduleOnce對選擇器選擇的函數的執行是在3秒后。
restart代碼:
圖 18
其中,場景過渡使用了部落格特效,持續1.2s 。
?
3.云朵移動
尋找或自行扣掉一張沒有背景的云朵的png圖片,替換掉資源原有的cloud.png圖片 。
圖 19
AnimationScene.cpp初始化時創建3朵云,位置設置為不同,代碼較簡單,不顯示。
再在update函數中不斷修正其x軸方向位置,每朵云的位移距離不同。當云朵移動到鏡頭的一端看不見時,在修正其位置到屏幕另一端,這樣就能得到循環播放的3朵云。
以1號云朵的位移代碼為例,其余類似:
圖 20
運行效果圖:
圖 21
?
4.BUG修正
教材提供的示例代碼有許許多多的bug,主要原因是代碼邏輯寫得不好,這一部分花了大量的時間去Debug來修正。終于解決了游戲中存在的目前能找到的所有Bug,令游戲能正確且流暢的運行起來。
由于游戲bug太多,下面根據解決方法的不同,總結為4類Bug:
- 按攻擊鍵除了能控制英雄的攻擊動作,也能控制怪物的攻擊動作。
- SMITTEN顫抖/硬直動作沒有執行、ATTACK動作的動畫可被打斷、角色被擊中后會原地卡在STAND或RUN動作中執行不了任何操作 ?等一系列稀奇古怪的動作。
- 莫名其妙連續多次扣血(不是指暴擊),在解決②的bug后,容易發現當角色砍中另一角色時,會繼續不斷使用attack。
- 角色在奇怪的地方被砍中,或砍不到角色。
?
下面修正上述bug:
- 攻擊鍵也能控制怪物攻擊。
????注釋掉或刪掉AnimationScene.cpp文件中的attackCallback函數中的紅框中代碼即可:
圖 22
?
- SMITTEN動作不執行,被砍后不能操作,attack動作被打斷 等奇怪舉動的bug。
這個bug為該程序中最主要的bug,以英雄Hero為例,問題主要出現在下面三個地方:
圖 23
圖 24
圖 25
圖 26
?
分析bug原因:
可見,1)根據搖桿的操作情況,調用2)的Hero中play函數,而3)能檢測Hero的狀態,執行動作。
因為1)在update函數中;2)被1)調用;3)是update函數。 所以理論上1)2)3)都是一直在運作中的,沒有固定的先后順序。因此,由于不能確定運作順序(除了 2)會在1)后運行),導致程序容易出錯。
?
舉例:
當hero被攻擊,Hero的play函數會被傳入枚舉類型SMITTEN作為參數,根據play代碼可知,此時受傷狀態變量m_ishurt會變為true,當前角色狀態m_state會被賦值為SMITTEN。 ?
理論上來說,下一步該執行Hero中的update函數,判斷并執行SMITTEN動作了才對。 然而,這里也有可能在執行Hero的update函數之前,先執行了AnimationScene的update函數。
如果先執行了AnimationScene的update函數,那么1)的“控制角色移動”的代碼段就先被執行了,如果此時沒有動搖桿,那么枚舉類型STAND將作為Hero的play函數的參數傳進去,之后就會重新賦值給m_state,SMITTEN狀態就會被STAND狀態給覆蓋掉了。
此時,m_state的值為STAND,而m_ishurt的狀態依然是true(因為SMITTEN動作沒有被執行)。 再進入Hero的update函數時,由于m_ishurt也是各個動作是否該執行的判斷依據,當m_ishurt == true時,這些動作都不會被執行。
因此,當hero被攻擊后,hero的操作都將失效。
?
?
根據上面分析,想要解決這個bug必須要保證兩個前提(暫不討論DEFEND防御):
解決上述1)和2):
????1)在Hero.h中聲明新的布爾類型私有變量actionFlag,其作用為 ?當m_state被賦值為ATTACK或SMITTEN時,actionFlag被賦值為true,當其為true時,m_state不能被再改變,只有在ATTACK和SMITTEN動畫運行到最后一幀時,actionFlag變為false,此時m_state允許被賦值。
修改相關代碼:
圖 27
圖 28
?
????2)研究源代碼中attack相關函數,發現Hero中的m_isAttack的作用為判斷hero是否“正在攻擊”,這里指的是“動作”而不是“狀態”。通過這一變量,在動作執行時(動畫播放時)才賦值為true,在最后一幀播放完了再賦值為false。把該變量作為動作執行的判斷依據,能有效地控制并防止動作的被打斷以及持續進行(譬如一直點擊攻擊鍵,攻擊動作不斷被打斷并重新執行,只播放前幾幀);
模仿m_isAttack變量,把m_ishurt變量的意義從原來的“受傷狀態”更改為“受傷動作”。
同時,在監聽幀事件的函數中,要在動作執行完后加入play(STAND)的代碼,防止其帶著原來的m_state先執行Hero的update函數又引起什么奇怪的操作。
模仿著更改代碼:
圖 29
圖 30
?
????同理修改Enemy的代碼即可。
?
?
- 莫名其妙連續多次扣血(不是指暴擊),在解決②的bug后,容易發現當角色砍中另一角色時,會繼續不斷使用attack。
查看碰撞檢測文件MyContactListener.cpp,查看其update函數:
圖 31
以敵人攻擊英雄為例,關鍵代碼部分放大↓:
圖 32
分析:
由上面代碼可知,當其他條件滿足的前提下,Enemy的m_isAttack變量為true時,表示此時敵人正在執行攻擊動作,if滿足條件,執行Hero的hurt函數,hero受傷掉血。然后Enemy執行setAttack(false)把其m_isAttack置為false。
然而,當m_isAttack置為false后,在Enemy中,會把其視為攻擊動作已經結束,在m_state還是ATTACK時,會把m_isAttack==false作為再次執行攻擊動作的判斷依據。而檢測碰撞文件的update函數又會很快的被再次執行,m_enemy->isAttack()又會被視為true……如此地連續執行,可能會造成角色的連續多次掉血,或者角色一旦攻擊到另一角色時,會不斷地執行攻擊動作。
?
解決:
通過上述分析,我們了解到,解決問題的關鍵點在于不能在檢測碰撞中執行m_enemy->setAttack(false)來改變破壞Enemy的攻擊動作。
綜上,我們保留其思想,但是不改變m_isAttack的值,為角色引入一個新的私有變量attackHurtFlag,表示被攻擊傷到傷害的標志,增加set和get方法。以enemy攻擊hero為例,關鍵代碼為:
圖 33
???
?Hero的update函數中:
圖 34
?
- 角色在奇怪的地方被砍中,或砍不到角色。
????觀察碰撞檢測文件MyContactListener.cpp,查看其update函數中enemy攻擊hero部分:
圖 35
分析:
發現其碰撞檢測的基本原理為:為enemy的ax層(即enemy的斧子部件)添加2個檢測點,再根據hero的位置創建一個矩形。當enemy為攻擊狀態,并且其斧子的2個檢測點在hero的矩形范圍內時,即為實現碰撞。
因此,這里該如何創建矩形成為關鍵。
分析Rect方法的參數,其第1個參數為矩形左下角的x坐標,第2個參數為矩形左下角的y坐標,第3個參數為矩形的寬,第4個參數為矩形的高。
結合游戲運行圖來分析:
假設在使用cocoStudio Animation時,角色的中心點在身體的中心點,隨意創建一個矩形,則有:
圖 36
假設還是同一程序,當hero轉身后,其矩形不會根據角色的轉身而左右顛倒,如下圖所示:
圖 37
由上面兩張圖可知道,創建矩形時,寬(即x軸)的中間位置的x坐標最好落在角色中心點的x坐標上。只有這樣,hero無論轉身與否,其前后的被攻擊的判定范圍都是一樣的,這樣才不會出現奇怪的“有時能砍到,有時又砍不到”的奇怪現象。
最后只要不斷調整矩形的寬度即可(即調整第1個參數和第3個參數)。而矩形的高度只要足以涵蓋住角色即可(即調整第2個參數和第4個參數)。
?
最終矩形參數修改為:
圖 38
圖 39
?
5.防御機制
防御機制設定為:
- 點擊藍色按鈕進入防御狀態
- 防御狀態下,角色最后會保持防御動畫的最后一幀
- 防御狀態下能減少一段暴擊及暴擊傷害,受到的傷害值以藍色數
值顯示
- 防御狀態下,操作搖桿,點擊攻擊按鈕,能打斷防御狀態,并執行其他相應動作
- 防御狀態下,再點擊一次防御按鈕可以取消防御狀態
?
根據以上設定,編寫代碼:
Hero中的update:
圖 40
并為每個動作的執行加上m_isDefend=false,以STAND站立動作為例:
圖 41
Hero的showBloodTips函數 “減少暴擊數和暴擊傷害”以及“防御狀態下傷害值為藍色”:
圖 42
圖 43
運行圖:
圖 44
6.AI設計
觀察AI文件AIManager的原代碼,發現怪物AI僅僅是根據一套固有的動作反復執行而已,關鍵代碼為:
圖 45
分析上面代碼,可知這個AI并不智能。并且根據上面的執行結果,可知moveLeft的動作持續最久,因此游戲中的后半段,敵人emeny會一直往左邊界“推墻”,moveRight的持續時間太短,因此無法往右半邊回來。
?
因此,重新編寫一個AI代碼文件,使其能夠根據hero的位置,實現自動跟蹤,在適宜的位置進行攻擊的功能。
編寫后的關鍵代碼為(以hero在enemy的左方為例):
圖 46
hero在enemy的右方時也同理可得,而當hero和enemy位置相同時,enemy直接攻擊即可。
?
由上述得到了敵人AI的最佳方案,但是如果直接把bestAI函數放到update函數不斷調用的話,會發現游戲會變得非常困難,幾乎沒有贏的可能性。并且,敵人enemy的行動模式不夠隨機也反而顯得不是那么的“智能”。因此,在此基礎上減少bestAI的執行次數,插入隨機行動模式,并適當地減少攻擊頻率,讓游戲變得更簡單,令AI變得更隨機些。
修改后的代碼為:
圖 47
bestAI()中以hero在enemy的左方為例:
圖 48
AI的隨機方案:
圖 49
通過上述操作后,敵人AI能夠保持在最佳行動方案的基礎上,也進行些許隨機行動了。
7.其余Label顯示細節
???① 每輪游戲開始時都會出現“Round X”,X表示游戲的第幾輪,1.5s后消失(移除)
圖 50
???② 每一小輪游戲結束后,都會在對應角色的血條下方顯示“win”字樣
圖 51
③ 場景有部落格特效過渡
④ 游戲結束后會顯示玩家的輸贏,贏了顯示“YOU WIN!”;輸了顯示“YOU LOSE!”
圖 52
總結
以上是生活随笔為你收集整理的学习3 二维游戏动画合成(侠客行)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux移动文件 rf参数_linux
- 下一篇: Concurrent HTTP conn