最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

【Unity俯視角射擊】我們來做一個(gè)《元?dú)怛T士》的完整Demo(二)

2020-09-28 19:58 作者:皮皮關(guān)做游戲  | 我要投稿

作者:Yumir


hi~我是Yumir。

今天要分享的內(nèi)容是進(jìn)階版的AI尋路,之前分享過使用A*Pathfinding官方提供的AI腳本進(jìn)行移動(dòng),但是要做一個(gè)完整的游戲使用該腳本有諸多不便,于是決定還是利用官方提供的尋路接口自己寫一個(gè)AI移動(dòng)腳本。

在線游戲鏈接:https://connect.unity.com/mg/other/untitled-9864


怪物設(shè)計(jì)

目前我用到的怪物角色有下圖三種,其中三號(hào)怪物放大一倍之后作為Boss使用于Boss房。

①野豬怪

一號(hào)怪物的狀態(tài)我用了待機(jī)、巡邏(實(shí)際上就是亂跑)還有死亡等三個(gè)狀態(tài),當(dāng)野豬怪在奔跑途中撞到玩家會(huì)對(duì)玩家造成傷害。

②吐泡泡的花

二號(hào)怪物是在出生點(diǎn)不會(huì)亂動(dòng)的怪物,所以只有待機(jī)、攻擊、死亡三個(gè)狀態(tài),當(dāng)玩家進(jìn)入二號(hào)怪物的攻擊范圍時(shí)二號(hào)怪物會(huì)向四周發(fā)射泡泡保護(hù)自己。

③戴面具的人形生物

三號(hào)怪是等級(jí)比較高的怪物,可以和玩家一樣使用武器,所以我給他配了一把和玩家一樣的玩具槍,狀態(tài)機(jī)相對(duì)也更復(fù)雜,有待機(jī)、巡邏、追逐、攻擊、死亡等五個(gè)狀態(tài)。

④Boss

關(guān)于四號(hào)怪其實(shí)我一開始不打算這樣設(shè)計(jì)的,后來發(fā)現(xiàn)元?dú)怛T士的新手教程也是把三號(hào)怪放大當(dāng)Boss用,而且在彈幕設(shè)計(jì)方面我確實(shí)并不擅長,所以暫時(shí)先這樣設(shè)計(jì)了,可以以后再優(yōu)化。

Boss使用的武器是權(quán)杖,由于本身傷害比較大范圍比較廣,還進(jìn)行追逐的話游戲體驗(yàn)就基本沒有了,所以Boss的狀態(tài)有:待機(jī)、巡邏、攻擊、死亡。在Boss進(jìn)行攻擊之后在“巡邏”和“攻擊”中隨機(jī)一個(gè)作為下一個(gè)狀態(tài)。


設(shè)置A*尋路

①設(shè)置尋路網(wǎng)格

之前的文章已經(jīng)寫過這部分內(nèi)容,簡略的說一下:

1.在官網(wǎng):https://arongranberg.com/astar/? ?

點(diǎn)擊Download選項(xiàng),在跳轉(zhuǎn)到的頁面上選擇下載”Free“版本,將下載下來的文件導(dǎo)入到unity中。

2.新建一個(gè)空物體,點(diǎn)擊”AddComponent“搜索”Pathfinder“添加該組件。添加組件之后面板顯示如下,點(diǎn)擊圖中框選按鈕添加Grid Graph(Graphs>Grid Graph)。

3.將”2D“和”Use 2D Physic“勾選,再在第三個(gè)紅框的位置選擇一個(gè)Layer,同時(shí)將場(chǎng)景中的障礙物的Layer都設(shè)置為對(duì)應(yīng)的Layer。

4.點(diǎn)擊Scan按鈕,生成尋路網(wǎng)格。

②編寫尋路腳本

之前的文章用了官方提供的尋路腳本,其實(shí)使用官方的提供的路徑計(jì)算接口自己編寫一個(gè)尋路腳本也是很簡單的。官方也提供了范例腳本,文末會(huì)貼上知乎大佬的翻譯貼。

尋路這件事其實(shí)可以拆解成兩個(gè)步驟,現(xiàn)代人準(zhǔn)備去一個(gè)不認(rèn)識(shí)路的目的地的情況下會(huì)怎么做?

首先,打開地圖進(jìn)行路線搜索,其實(shí)在沒有地圖app的時(shí)代問路的原理也是一樣,說白了就是規(guī)劃路線,然后就是沿著路線向目的地出發(fā)了。

AI尋路的道理也一樣,先計(jì)算路徑,再沿路徑移動(dòng):

1.計(jì)算路徑

在A*Pathfinding中只要調(diào)用Seeker組件里的StartPath方法就可以進(jìn)行路徑的計(jì)算。

由于需要使用Seeker組件所以需要先在需要尋路的角色(就是游戲中的怪物)身上添加Seeker組件,然后新建一個(gè)腳本AstarAI編寫如下代碼:

using Pathfinding;

?

public class AstarAI : MonoBehaviour {

??? public Transform targetPosition;

?

??? public void Start () {

??????? //獲取到A*Pathfinding提供的路徑計(jì)算接口所在的腳本。

??????? Seeker seeker = GetComponent<Seeker>();

??????? //調(diào)用路徑計(jì)算方法,這里需要一個(gè)回調(diào)方法,但是是測(cè)試代碼所以沒有寫內(nèi)容。

??????? seeker.StartPath(transform.position, targetPosition.position, OnPathComplete);

??? }

?

??? public void OnPathComplete (Path p) {

??????? Debug.Log("Yay, we got a path back. Did it have an error? " + p.error);

??? }

}

?

2.沿路徑移動(dòng)

當(dāng)尋路成功的時(shí)候OnPathComplete方法會(huì)被調(diào)用,我們將獲得一個(gè)Path,在將他裝到兜里之前需要先確認(rèn)下他是不是尋路成功了。

public void OnPathComplete (Path p) {

?if (!p.error) {

??????????? path = p;

??????????? currentWaypoint = 0;

?}


?

獲取到的Path中有一個(gè)Vector3類型的列表,這是我們需要的路徑點(diǎn)集合,接下來只需要讓角色從第一個(gè)路徑點(diǎn)開始不斷朝著下一個(gè)路徑點(diǎn)移動(dòng)就可以了,案例代碼比較長,直接把我最終的代碼放到下文。

由于上面的方法只是在Start中調(diào)用StartPath進(jìn)行一次尋路,目標(biāo)點(diǎn)是固定不變的。

而游戲中玩家不是站著等怪物找的,也就需要每隔一段時(shí)間更新目標(biāo)位置重新規(guī)劃路線,考慮到AI狀態(tài)機(jī)的需求,我將上面兩個(gè)操作分別封裝成了方法。

由于玩家可能動(dòng)也可能不動(dòng),所以添加了一個(gè)目標(biāo)點(diǎn)移動(dòng)距離超過1的條件,這樣就不會(huì)重復(fù)沒有必要的路徑計(jì)算。

public void OnPathComplete(Path p){

??? if (!p.error){

??????? path = p;

??????? currentWaypoint = 0;

??? }

}

public void UpdatePath(Vector2 targetPosition){

??? if (Vector2.Distance(targetPosition, targetLastPosition) > 1){

??????? targetLastPosition = targetPosition;

??????? seeker.StartPath(transform.position, targetPosition, OnPathComplete);

??? }

}

?

控制角色向下一個(gè)目標(biāo)點(diǎn)前進(jìn)的方法:

public void NextTarget(){

??? if (path == null){ return; }

??? reachedEndOfPath = false;//標(biāo)記是否已經(jīng)到達(dá)目標(biāo)點(diǎn)

??? float distanceToWaypoint;

??? while (true){

??????? distanceToWaypoint = Vector3.Distance(transform.position, path.vectorPath[currentWaypoint]);

??????? if (distanceToWaypoint < nextWaypointDistance){

??????????? if (currentWaypoint + 1 < path.vectorPath.Count){ currentWaypoint++; }

??????????? else{ reachedEndOfPath = true; break; }

??????? }

??????? else{ break; }

??? }

??? var speedFactor = reachedEndOfPath ? Mathf.Sqrt(distanceToWaypoint / nextWaypointDistance) : 1f;

??? if (!reachedEndOfPath){

??????? nextTargetPosition = path.vectorPath[currentWaypoint];

??????? Vector3 dir = (nextTargetPosition - transform.position).normalized;

??????? Vector3 velocity = dir * speed * speedFactor;

??????? transform.position += velocity * Time.deltaTime;

??? }

??? else{ path = null; }

}

?

怪物AI邏輯實(shí)現(xiàn)

文章篇幅有限,所以只舉例三號(hào)怪物的實(shí)現(xiàn)過程,因?yàn)槠渌值腁I邏輯都相對(duì)簡單,相信看這個(gè)文章的朋友看完三號(hào)怪物的AI功能實(shí)現(xiàn)過程就可以自己做出其他的AI效果了。我在游戲設(shè)計(jì)上并沒有做的很好建議自己擴(kuò)展設(shè)計(jì)。

①待機(jī)

游戲中怪物是在玩家進(jìn)入怪物所在的房間才需要開始計(jì)算路徑的,所以在玩家進(jìn)入房間前需要一個(gè)待機(jī)狀態(tài),在該狀態(tài)下怪物的動(dòng)畫狀態(tài)機(jī)播放idle動(dòng)畫,不需要額外設(shè)置。

當(dāng)玩家進(jìn)入房間時(shí),系統(tǒng)將該房間所有的怪物的標(biāo)記位“isStart”設(shè)置為true,怪物由待機(jī)轉(zhuǎn)為巡邏。

void Idle()

{

??? if (isStart)

??? {

??????? monsterState = MonsterState.Stroll;

??????? animator.SetBool("run", true);

??? }

}

?

②巡邏

設(shè)置這個(gè)狀態(tài)主要是希望怪物有“視野”這個(gè)設(shè)定,如果沒有視野的話最后就會(huì)變成一堆怪追著玩家和玩家一起繞柱走的狀態(tài),游戲體驗(yàn)極差,可以說是最基本的AI需求了。

顯然我不可能每個(gè)怪物去設(shè)置巡邏點(diǎn),隨機(jī)尋路目標(biāo)位置有一個(gè)很簡單的算法——在圓里隨機(jī)一個(gè)點(diǎn)(然后計(jì)算新的路徑)。

public void RandomPath(){

??? var point = Random.insideUnitSphere * randomRadius;

??? point += transform.position;

??? UpdatePath(point);

}

?

同時(shí)需要檢測(cè)怪物是否可以看到玩家:

public void RaycastDetection(){

??? hit = Physics2D.Raycast(transform.position + Vector3.up, (targetPosition.position - (transform.position + Vector3.up)).normalized, trackingRange, layerMask);

??? if (hit.transform != null && hit.transform == targetPosition){

??????? seeTarget = true;

??????? Debug.DrawLine(transform.position + Vector3.up, hit.transform.position, Color.red);

??? }

??? else{ seeTarget = false; }

}

?

在巡邏方法中只需要調(diào)用控制角色向下一個(gè)目標(biāo)點(diǎn)前進(jìn)的方法,并且使怪物看向下一個(gè)目標(biāo)點(diǎn)。每隔一段時(shí)間隨機(jī)一個(gè)新的目標(biāo)點(diǎn)并計(jì)算路徑,當(dāng)怪物看到玩家(seeTarget==true)時(shí)切換到追逐狀態(tài)。

void Stroll()

{

??? RaycastDetection();

??? if (seeTarget){ monsterState = MonsterState.Tracking; }

??? UpdateLookAt(myAI.nextTargetPosition);

??? if (Time.time - strolltiming >= strollCD){

??????????? strolltiming = Time.time;

??????????? myAI.RandomPath();

??? }

??? myAI.NextTarget();

}

?

怪物巡邏時(shí)的路徑

③追逐

追逐狀態(tài)下無非就是追到和沒追到,這個(gè)需要用距離和視線判斷,該狀態(tài)下需要一直調(diào)用計(jì)算路徑方法和往下一個(gè)路徑點(diǎn)移動(dòng)的方法,如果和玩家距離小于等于攻擊距離就進(jìn)入攻擊狀態(tài),如果脫離了視野就回到巡邏狀態(tài)。

void Tracking(){

??? myAI.UpdatePath(targetPosition.position);

??? myAI.NextTarget();

??? if (Vector2.Distance(transform.position, targetPosition.position) <= attackRange){

??????? monsterState = MonsterState.Attack;

??????? animator.SetBool("run", false);

??? }

??? RaycastDetection();

??? if (!seeTarget){ monsterState = MonsterState.Stroll; }

??? UpdateLookAt(myAI.nextTargetPosition);

}

?

④攻擊

我在攻擊狀態(tài)下設(shè)置了攻擊計(jì)時(shí)器,主要是因?yàn)楣治锕籼斓脑捨掖虿贿^,如果你打得過你可以不設(shè)(\doge)然后就是根據(jù)距離切換回追逐狀態(tài),和面向玩家,沒啥可說的。

void Attack(){

??? if (Vector2.Distance(transform.position, targetPosition.position) > attackRange){

??????? monsterState = MonsterState.Tracking;

??? }

??? UpdateLookAt(targetPosition.position);

??? if (Time.time - attacktiming >= attackCD){

??????? attacktiming = Time.time;

??????? weapon.ShootButtonDown();

??? }

}

?

⑤死亡

在任何狀態(tài)下,只要血量為0就會(huì)死亡,所以該狀態(tài)的入口是寫在被攻擊的方法里面的,三號(hào)怪物死亡時(shí)會(huì)向后彈飛一段距離,這個(gè)我也是用尋路實(shí)現(xiàn)了:

public override void BeAttack(float data){

??? base.BeAttack(data);

??? if (hp <= 0){

??????? myAI.UpdatePath((transform.position - targetPosition.position).normalized * 2 + transform.position);

??????? weapon.gameObject.SetActive(false);

??? }

}

void Die(){ myAI.NextTarget(); }

?

可以看到我的BeAttack是Override,因?yàn)檫@個(gè)血量為0的狀態(tài)轉(zhuǎn)換是所有的怪通用的,所以我將通用邏輯都寫在了Monster父類中(包括死亡動(dòng)畫以及生成怪物掉落獎(jiǎng)勵(lì)等功能),再在子類中分別處理特殊要求。

public virtual void BeAttack(float data){

??? hp -= data;

??? if (hp <= 0)

??? {

??????? monsterState = MonsterState.Die;

??????? GetComponent<Animator>().SetBool("die", true);

??????? GetComponent<Collider2D>().enabled = false;

??????? room.MonsterDie(this);

??????? for (int i = 0; i < coin; i++){//掉落金幣

??????????? Instantiate(GameManager.instance.coinPre, transform.position, Quaternion.identity);

??????? }

??????? for (int i = 0; i < magic; i++){//掉落魔晶石

??????????? Instantiate(GameManager.instance.mpPre, transform.position, Quaternion.identity);

??????? }

??? }

??? else{

??????? GetComponent<Animator>().Play("BeAttack");

??????? GameManager.instance.ShowAttack(data, Camera.main.WorldToScreenPoint(transform.position + new Vector3(0, 1, 0)));

??? }

}

?

關(guān)于怪物AI我已經(jīng)傾囊相授啦~接下來是游戲的小地圖繪制分享,游戲項(xiàng)目以及資源我會(huì)在游戲完成之后整理(放在第四篇文章)需要的同學(xué)可以關(guān)注一下。

知乎上有大佬翻譯了官方教程,下面這篇是本文提到的AI移動(dòng)腳本,我認(rèn)為比起去官網(wǎng)翻看相對(duì)來說更方便一些,有興趣可以看看:

https://zhuanlan.zhihu.com/p/69703555



歡迎加入游戲開發(fā)群歡樂攪基:1082025059

對(duì)游戲開發(fā)感興趣的童鞋可戳這里進(jìn)一步了解:levelpp.com/


【Unity俯視角射擊】我們來做一個(gè)《元?dú)怛T士》的完整Demo(二)的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國家法律
邢台市| 夹江县| 根河市| 隆德县| 尚志市| 乐清市| 宾川县| 南汇区| 通渭县| 方城县| 东光县| 抚顺市| 潜江市| 民勤县| 蚌埠市| 杭锦后旗| 苍梧县| 晋江市| 济南市| 沂南县| 桂平市| 巴塘县| 甘谷县| 乌拉特前旗| 永春县| 巧家县| 商洛市| 敦化市| 宜春市| 深泽县| 奎屯市| 屯留县| 普兰店市| 张家口市| 阿瓦提县| 临颍县| 西乌| 葫芦岛市| 奉化市| 鹰潭市| 鹤岗市|