Unity官方教程 2D Roguelike(5):敵人移動(dòng)

2D Roguelike 最終效果

前言

Unity官方教程 2D Roguelike(4):角色移動(dòng)中,我們完成了游戲最主要的功能——角色移動(dòng)相關(guān)邏輯,接下來只要完成怪物的移動(dòng),整個(gè)游戲的底層架構(gòu)就差不多完整了,除了UI和音樂音效等。這一節(jié)我們主要完成以下內(nèi)容:

  • 怪物移動(dòng)邏輯
  • 怪物動(dòng)畫設(shè)置

本節(jié)你將學(xué)會(huì)什么?

  • 無新知識(shí)點(diǎn),強(qiáng)化前面所掌握的內(nèi)容

一、編輯Enemy Script

2D Roguelike是個(gè)回合制游戲,我動(dòng)敵靜,敵動(dòng)我靜。接下來創(chuàng)建一個(gè)Script,命名為Enemy,雙擊打開編輯怪物敵動(dòng)代碼吧!

第1步:MoveEnemy()

MoveEnemy()

代碼簡讀:

  • Enemy類必須繼承MovingObject類,所以冒號(hào)后面記得修改。
  • 新增私有成員變量target并在Start()進(jìn)行初始化賦值,代表Player的位置,怪物移動(dòng)是根據(jù)Player的位置來決定方向。
  • 對(duì)Start()進(jìn)行了重寫,所以需要添加修飾關(guān)鍵詞override,然后通過base調(diào)用了父類的Start()方法。
  • 新增公共方法MoveEnemy(),定義了int類型變量xDir、yDir初始化為0,代表怪物移動(dòng)方向向量。
    當(dāng)Player和Enemy在同一個(gè)X坐標(biāo),則對(duì)兩個(gè)物體的Y坐標(biāo)進(jìn)行高低判斷,如果target(Player)的y值高,則移動(dòng)方向是向上移動(dòng),yDir為1,否則為-1向下移動(dòng)。
    當(dāng)Player和Enemy不在同一個(gè)X坐標(biāo),則直接判斷X高低,target高則xDir為1向右移動(dòng),否則為-1向左移動(dòng)。

游戲管理器GameController會(huì)調(diào)用MoveEnemy()方法進(jìn)行指揮怪物隊(duì)列移動(dòng),因此關(guān)鍵詞為public。

Mathf.Abs指的是絕對(duì)值。float.Epsilon是最小浮點(diǎn)值,接近0。
條件?結(jié)果A:結(jié)果B,是三元運(yùn)算符的表達(dá)式,條件結(jié)果為true則是表達(dá)式結(jié)果是A,否則是B。

從上面的代碼解析可以看出,MoveEnemy()干的活就是根據(jù)Player坐標(biāo)確定怪物移動(dòng)方向,然后調(diào)用AttempMove<Player>()進(jìn)行真正移動(dòng)。

第2步:AttempMove<T>()

AttempMove<T>()

代碼簡讀:

  • 這游戲并非是Player走一回合,怪物走一回合。而是Player走兩回合,怪物才能走一回合。帶著這個(gè)認(rèn)知去看這一段代碼就很好理解。
  • 新增布爾值類型的私有成員變量skipMove,用它來控制怪物是否跳過這回合。
  • AttempMove<T>(),判斷skipMove是否為true,如果是的話這回合怪物要跳過不進(jìn)行移動(dòng),就return跳出這個(gè)方法不執(zhí)行后續(xù)代碼。如果是false,則調(diào)用了父類MovingObject的AttempMove<T>()方法進(jìn)行移動(dòng),最后重新把skipMove賦值為true,保證下一回合怪物不能移動(dòng)。

從上面的代碼解析可以看出,AttempMove<T>()干的活就是接收到MoveEnemy()的信息通知往哪邊移動(dòng)的時(shí)候,判斷下這回合要不要跳過,然后再進(jìn)行移動(dòng)。
OK,怪物開始移動(dòng)了!噠噠噠,噠噠噠,誒?遇見Player了!好家伙,要打的就是你!

第3步:OnCantMove<T>()

OnCantMove<T>()

代碼簡讀:

  • 我們還記得父類里有個(gè)泛型方法OnCantMove<T>()吧?因?yàn)樗浅橄蠓椒ǎ枰宇惾ソo出具體實(shí)現(xiàn),因此我們?cè)诜椒ㄇ凹由狭?strong>override修飾符。
  • 在父類MovingObject我們可以看到,OnCantMove<T>()這里的泛型參數(shù)T的類型是組件Component,而Player組件也是Component的一種,所以傳入的參數(shù)可以在方法內(nèi)轉(zhuǎn)化為Player類型,并且調(diào)用Player的LoseFood方法來扣除角色生命。playerDamage指的是怪物攻擊角色造成的傷害,也就是每次被打角色生命扣除數(shù)值。

二、編輯GameController

在上面的Enemy Script里,我們實(shí)現(xiàn)了怪物基本移動(dòng)邏輯(根據(jù)Player坐標(biāo)確定方向進(jìn)行移動(dòng),與Player碰撞的時(shí)候調(diào)用LoseFood()方法實(shí)現(xiàn)攻擊Player效果)。作為游戲管理器的GameController,則負(fù)責(zé)調(diào)控全局,指揮多個(gè)怪物在一定的條件下依次調(diào)用Enemy Script進(jìn)行移動(dòng)。

第1步:增加怪物隊(duì)列集合

打開GameController,增加以下代碼。

enemies

代碼簡讀:

  • 新建List類型的私有變量成員enemies并在Awake()內(nèi)初始化,集合里面存放的是Enemy類型的數(shù)據(jù),也就是把關(guān)卡內(nèi)的所有怪物都放進(jìn)去。
  • 添加AddEnemyToList()方法,通過Add()方法把傳入的Enemy類型的參數(shù)都添加到enemies。
  • 因?yàn)橹匦律申P(guān)卡的時(shí)候,enemies數(shù)據(jù)會(huì)被保留,所以需要在InitGame()初始化關(guān)卡時(shí)用Clear()方法清除上一個(gè)關(guān)卡的敵人數(shù)組。
    切回到Enemy Script,在Start()方法內(nèi)增加一句代碼。

這句代碼的意思是調(diào)用GameController的公有方法AddEnemyToList(),把自己(當(dāng)前實(shí)例)當(dāng)成參數(shù)傳入進(jìn)去,也就是把當(dāng)前怪物加進(jìn)集合enemies。正因?yàn)樾枰谶@里進(jìn)行調(diào)用,所以AddEnemyToList()方法的關(guān)鍵詞是public哦~

第2步:指揮怪物們依次移動(dòng)

關(guān)卡內(nèi)的怪物都被添加進(jìn)集合enemies內(nèi)了,接下來就是在恰當(dāng)?shù)臅r(shí)機(jī)指揮這些怪物一個(gè)個(gè)移動(dòng)啦!

指揮移動(dòng)

代碼簡讀:

  • 新增浮點(diǎn)值變量turnDelay并賦值,代表回合等待時(shí)間,單位為s。
  • 新增布爾值類型變量enemyMoving,代表怪物們是不是正在移動(dòng)中,正在移動(dòng)則為true,其他情況則為false。
  • Update()方法,判斷當(dāng)playerTrun和enemyMoving均為false的情況下(是怪物回合并且怪物沒有在移動(dòng)中),使用StartCoroutine函數(shù)開啟協(xié)同程序MoveEnemys()指揮怪物開始一個(gè)個(gè)進(jìn)行移動(dòng);

協(xié)程是分步驟執(zhí)行代碼的程序,遇到條件(yield return語句)會(huì)掛起暫停退出,直到條件滿足才會(huì)被喚醒繼續(xù)執(zhí)行后面的代碼。

  • MoveEnemys()方法,把enemyMoving賦值為true確保Update()不再執(zhí)行開啟協(xié)程的代碼,等待turnDelay時(shí)長之后(為了讓Player走完),判斷如果沒有怪物的時(shí)候再等待turnDelay時(shí)長(讓回合感更明顯,Player不能一直不停地移動(dòng));如果有怪物的話,則開始for循環(huán)敵人數(shù)組enemies,調(diào)用MoveEnemy()方法指揮他們一個(gè)個(gè)移動(dòng)。為了實(shí)現(xiàn)依次的效果而不是同時(shí)移動(dòng),加了間隔時(shí)長moveTime。
  • 所有敵人移動(dòng)完畢之后,把人物回合開關(guān)開起來(playerTurn為true),敵人移動(dòng)中開關(guān)關(guān)掉(enemyMoving為false),重新把回合權(quán)交給了Player。

第3步:填坑-playerTurn開關(guān)

移動(dòng)邏輯章節(jié)的第三節(jié)內(nèi)的第3步,當(dāng)時(shí)為了修正按一次方向鍵而Player移動(dòng)了多次的問題,我們臨時(shí)增加了一些代碼。現(xiàn)在已完成怪物移動(dòng)代碼,可以正常地進(jìn)行人物怪物交換來回移動(dòng),因此我們需要把之前臨時(shí)增加的三句代碼刪除。

Player Script的Update()
MovingObject Script

然后我們?cè)赑layer Script的AttempMove()方法內(nèi)對(duì)playerTurn進(jìn)行賦值改動(dòng)。

playerTurn改成false

也就是說,人物開始移動(dòng)之后立刻把playerTurn賦值為false,這樣游戲管理器的Update()判斷playerTurn和enemyMoving都是false的時(shí)候,它開始指揮怪物們進(jìn)行移動(dòng)。怪物移動(dòng)完畢之后,把playerTurn賦值為true,Player的Update()即可執(zhí)行后續(xù)代碼讓Player開始第二次移動(dòng)。
當(dāng)然,我們也不要忘了即使輪到怪物回合了,它也有選擇不走的權(quán)利!太懶惰了,怪物利用skipMove這個(gè)開關(guān),成功實(shí)現(xiàn)人物走兩次,它才動(dòng)一次。(和我一樣懶( >﹏<。)~)

第4步:執(zhí)行移動(dòng)

保存腳本,切回到Unity編輯器。打開Prefabs文件夾,同時(shí)選中Enemy1和Enemy2預(yù)制件,再點(diǎn)擊菜單欄的Component-Scripts-Enemy,把Enemy腳本掛載到這兩個(gè)預(yù)制件上。

掛載Enemy腳本

在右側(cè)Inspector內(nèi)的Enemy組件里,Blocking Layer選擇BlockingLayer層。

設(shè)置Layer層

單獨(dú)選擇Enemy1預(yù)制件,把它的Player Damage設(shè)置為10,而Enemy2的為20。(這里可以自由發(fā)揮設(shè)置傷害為多少,但是注意不要過高一招就把Player秒了……)

設(shè)置Player Damage數(shù)值

最后一步,開測!

勝利就在前方,大家沖鴨!

運(yùn)行游戲,我們按鍵盤的方向鍵讓小人走起來。


可以看到游戲正常耍起了:

  • 小人和怪物都可以正常移動(dòng)
  • 小人走兩次,怪物才走一次
  • 怪物之間移動(dòng)有先后次序
  • 小人可以正常拾取地上的食物
  • 小人可以正常劈砍障礙墻直到消失開辟路徑

但其實(shí)我們會(huì)發(fā)現(xiàn)這個(gè)游戲的怪物移動(dòng)邏輯會(huì)有一個(gè)缺點(diǎn):怪物如果被障礙墻擋住了,會(huì)一直卡著不動(dòng),直到小人移動(dòng)變換左右或者上下,它才有可能再動(dòng)起來。這個(gè)是由于Enemy腳本的MoveEnemy()方法里面獲取移動(dòng)方向的設(shè)定上不夠靈活。感興趣的童鞋可以想想如何優(yōu)化這個(gè)怪物AI~

雖然看起來除了音樂和UI,其他邏輯都做完了。但是細(xì)心的小強(qiáng)同學(xué)卻發(fā)現(xiàn)了:“老師,怪物攻擊Player的時(shí)候沒有動(dòng)作展示!”

真聰明!

那接下來我們就把動(dòng)畫補(bǔ)上吧!

三、實(shí)現(xiàn)怪物動(dòng)畫添加

怪物動(dòng)畫的轉(zhuǎn)換只有一種情況:怪物遇到Player并且進(jìn)行攻擊,這時(shí)候怪物的動(dòng)畫從idle切換到attack,并且在attack動(dòng)畫結(jié)束之后切換回idle。
其實(shí)在上一節(jié)我們已經(jīng)介紹過角色動(dòng)畫的轉(zhuǎn)換是如何設(shè)置的,而怪物的動(dòng)畫設(shè)置上基本上是一致的,所以很多細(xì)節(jié)和這樣設(shè)置的原因是什么我就不再贅述了。

  1. 雙擊Enemy1動(dòng)畫控制機(jī)打開Animator面板,在Parameters里增加Trigger名為enemyAttack
動(dòng)畫觸發(fā)器
  1. 通過右鍵的Make Transition在Enemy1Idle和Enemy1Attack之間創(chuàng)建連接。
動(dòng)畫切換關(guān)聯(lián)
  1. 選中高亮從Enemy1Idle出發(fā)到Enemy1Attack的線,對(duì)動(dòng)畫轉(zhuǎn)換進(jìn)行設(shè)置。
Idle到Attack
  1. 選中高亮從Enemy1IAttack出發(fā)到Enemy1Idle的線,對(duì)動(dòng)畫轉(zhuǎn)換進(jìn)行設(shè)置。
Attack結(jié)束回到Idle

如此就完成了Enemy1Idle和Enemy1IAttack之間的互相轉(zhuǎn)換的設(shè)置。由于Enemy2控制器是重寫控制器,自動(dòng)繼承Enemy1的設(shè)置,所以不需要再去編輯Enemy2的兩種動(dòng)畫狀態(tài)之間的切換了。
怪物動(dòng)畫的切換設(shè)置完畢,我們需要在Enemy腳本里添加觸發(fā)動(dòng)畫的代碼。

編寫Enemy動(dòng)畫觸發(fā)代碼

代碼簡析:

  • 新增一個(gè)私有成員animator,代表掛載在Enemy物體上的Animator組件,并且在Start()方法內(nèi)進(jìn)行初始化賦值。
  • 在OnCantMove()方法內(nèi),遇到Player進(jìn)行攻擊的時(shí)候,調(diào)用animator的SetTrigger()方法來激活enemyAttack觸發(fā)器,這樣就會(huì)播放對(duì)應(yīng)的attack動(dòng)畫。

可以看到怪物攻擊小人的時(shí)候有對(duì)應(yīng)的攻擊動(dòng)畫出來啦!
ヾ(???ゞ)快接近尾聲了。接下來只剩下音樂音效、UI、切換關(guān)卡處理等部分了!等我一篇搞定~

上一章傳送門:角色移動(dòng)
下一章傳送門:音樂音效、UI

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評(píng)論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評(píng)論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評(píng)論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,778評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,218評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,436評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,969評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,795評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,993評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,229評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評(píng)論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,687評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,990評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容