一、實現功能如下
- 以下 1、2 功能均在同一個腳本當中,在說明功能如何實現時,只貼上部分代碼,若是想要查看完整代碼,可到底部下載代碼文件,無需積分
1、隨機生成敵人
創建三個空物體,當作敵人生成地點,注意:三個空物體必須要在NavMeshAgent尋路系統烘培的網格路面內,否則即使創建了敵人,敵人也無法自動尋路進行巡邏。(這裏就只製作一波怪了,若是要製作三波怪,可使用協程使創建敵人的方法在調用一次後,等待n秒後再次執行,血量的改變可用傳參的方式。)
代碼如下(GenerateEnemy腳本,僅供參考):
private void CreateEnemy()
{
int index = 0;
Vector3 vec = new Vector3(2, 2, 2);
enemy = new GameObject[3];
nav = new NavMeshAgent[3];
ani = new Animator[3];
enemyLoc = new Vector3[3];
for (int i = 0; i < 3; i++)
{
enemy[i] = Instantiate(EnemyType[Random.Range(0,3)], EnemyBirthLocal[i].position, Quaternion.identity);
nav[i] = enemy[i].AddComponent<NavMeshAgent>();
enemy[i].transform.localScale = vec;
enemyLoc[i] = enemy[i].transform.position;
//獲取角色的Animator組件
ani[i] = enemy[i].GetComponent<Animator>();
}
}
2、給敵人添加尋路和動畫
- 在遊戲場景中再次創建三個空物體,各自與敵人出生點連成一條線,使敵人在此之間來回巡邏,直到在視角內發現角色位置,將尋路目標更改爲角色,並在距離角色n米的時候進行攻擊。
- 如何來回巡邏和播放:創建一個Vector3變量用於存儲當前遊戲物體的位置,在下一幀時,用此變量和當前遊戲物體的位置進行Distance距離判斷,若是距離爲0,說明位置是沒有進行變換的,此時讓遊戲物體循環播放待機動畫;當距離發生不等於0,則說明遊戲物體是在運動當中,此時播放行走動畫。巡邏;讓遊戲物體的position分別與角色出生點和巡邏終點進行比較,若是距離小於0.5f,則讓角色物體朝向另一個點尋路。既然是巡邏,那麼遊戲物體到達一個點之後是會等待一段時間再回到原來的點等待一段時間,如此反覆,也爲了減少性能的消耗(不讓距離的判斷每幀都在做);我們使用協程進行等待。
代碼如下(GenerateEnemy腳本;僅供參考):
private IEnumerator NavFindRoad()
{
int num = IsFindRole();
bool jude = false;
for (int x = 0; x < 3; x++)
{
if (findNum[x] == num)
{
jude = true;
}
}
if (!jude && num != -1)
{
findNum[num] = num;
jude = false;
}
for (int i = 0; i < nav.Count; i++)
{
if (findNum[i] != i)
{
//Debug.Log(Vector3.Distance(enemy[i].transform.position, EnemyBirthLocal[i].position));
if (Vector3.Distance(enemy[i].transform.position, EnemyBirthLocal[i].position) < 0.5f)
{
nav[i].SetDestination(Target[i].position);
}
yield return new WaitForSeconds(3);
if (Vector3.Distance(enemy[i].transform.position, Target[i].position) < 0.5f)
{
nav[i].SetDestination(EnemyBirthLocal[i].position);
}
}
else nav[i].SetDestination(role.transform.position-new Vector3(0,0,3));
}
}
- 在查找角色是否進入攻擊範圍時,我們需要使用到點乘求角度。
- 以下圖片是敵人的攻擊範圍
private int IsFindRole()
{
Vector3 target = role.transform.position;
Vector3 enemyPos;
for (int i = 0; i < enemy.Count; i++)
{
enemyPos = enemy[i].transform.position;
target -= enemyPos;
float angle = Vector3.Dot(enemy[i].transform.forward.normalized, target.normalized) * Mathf.Rad2Deg;
//如果角度小於60度,那麼就進行距離的判斷
if (angle < 60)
{
float distance = Vector3.Distance(enemyPos, role.transform.position);
if (distance < 3)
{
//所有條件都滿足,將敵人的尋路目標更改爲角色
return i;
}
}
}
return -1;
}
3、血量面板的設置
- 思路:給角色和敵人分別添加靜態屬性的血量,然後在血量設置的腳本當中設置公共的血量屬性,這樣就可以在屬性面板當中直接調整角色和敵人的初始血量,在進行扣血時,我們只需要取出靜態屬性進行更改即可。
- 血條顯示:在這裏我們使用OGUI的組件進行調整,先添加Canvas畫布,再添加UI組件Sliter,將Sliter下的Handle Slide Area組件刪除,然後將Fill組件裏的顏色調整爲紅色即可。如圖
在生成角色腳本(CreateRole)當中添加一個靜態屬性血量
public static int Blood { get; set; }
在生成敵人腳本(GenerateEnemy)當中添加一個靜態屬性血量
public static int[] Blood { get; set; }
- 敵人和角色血量的設置如下代碼(SetBlood腳本;僅供參考):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SetBlood : MonoBehaviour
{
//角色血量
public int RoleBlood;
private int nowBlood;
//敵人血量,用於外部設置
public int[] EnemyBlood;
//敵人血量,用於傳遞給RoleCollier腳本,設置血條
public static int[] defEnemyBlood;
private int[] nowEnemyBlood;
private Slider roleSlider;
//當設置爲不啓用時,Find方法是無法查找的,所以添加一個靜態方法返回
private static Slider enemySlider;
/// <summary>
/// 獲取默認血量,即初始最大血量
/// </summary>
/// <param name="Bloodsize">血量數量,即敵人數量</param>
/// <returns></returns>
private static void SetdefEnemyBlood(int Bloodsize,int[] Blood)
{
defEnemyBlood = new int[Bloodsize];
for (int i=0;i<Bloodsize;i++)
{
defEnemyBlood[i] = Blood[i];
}
}
public static int[] GetdefEnemyBlood()
{
return defEnemyBlood;
}
public static Slider GetEnemySlider()
{
return enemySlider;
}
private void Start()
{
roleSlider = GameObject.Find("RoleBlood").GetComponent<Slider>();
//只有當角色和敵人發生碰撞,血量才顯示,所以獲取組件設置爲不啓用
enemySlider = GameObject.Find("EnemyBlood").GetComponent<Slider>();
enemySlider.gameObject.SetActive(false);
SetdefEnemyBlood(EnemyBlood.Length,EnemyBlood);
SetRoleBlood();
}
private void Update()
{
SetNowBlood();
}
//設置初始血量
private void SetRoleBlood()
{
//設置角色血量
CreateRole.Blood = RoleBlood;
roleSlider.maxValue = RoleBlood;
roleSlider.value = RoleBlood;
//設置敵人血量
// yield return new WaitForSeconds(2);
for (int i=0;i<EnemyBlood.Length;i++)
{
GenerateEnemy.Blood[i] = EnemyBlood[i];
}
}
//設置目前血量,受到攻擊開始減血
private void SetNowBlood()
{
//設置目前角色血量
nowBlood = CreateRole.Blood;
roleSlider.value = nowBlood;
//設置目前敵人血量(被攻擊的敵人)
//當角色與敵人發生碰撞時,纔會顯示出此敵人的血量
//所以敵人血量的設置,我們將它放在碰撞接觸方法當中
}
}
4、攻擊動作添加和減血
- 思路:需要給在創建敵人時和生成角色時,添加剛體和碰撞器組件,這樣我們就可以來判斷角色是否進攻擊了,當碰撞發生,且角色攻擊動畫在播放的時候,我們就判定爲角色在對骷髏進行攻擊,骷髏也可藉此來進行播放抵抗或攻擊動畫動畫。(抵抗動畫的播放概率爲50%,當是抵抗動畫播放時,傷害減半;當碰撞發生,而角色沒有進行攻擊,那麼骷髏就進行攻擊動畫的播放。
- 如何扣血:在角色和敵人發生碰撞時,我們可以拿到敵人的名字,這樣我們只需要在創建敵人的時候對他的名字進行一下標記,那麼就能使血量數組的一一對應,而不會混亂。例如:在創建敵人時,我們將它的名字後面加上一個數字,然後在扣血時,取出這個數字,將它安排在數組上就行了。
在創建敵人時,添加如下代碼
enemy[i].name = enemy[i].name + i;
- 在攻擊動畫播放完後,進行扣血。碰撞發生觸發的事件條件是兩物體都有碰撞器,並且運動的物體帶有剛體,而我們角色和敵人都是要運動的,所以都添加上剛體,且將isKinematic設置爲false,此時你會發現你的角色可能會直接昇天,具體原因不太清楚,調試後猜測是 Chareter Controller組件和Rigidbody組件有衝突,所以會這樣。那怎麼辦呢,我們可以將剛體裏的約束屬性全部勾上,就是使其發生碰撞但是不會發生物體特性的效果。
//給敵人添加剛體和碰撞器組件,並勾選isKinematic屬性
//在GenerateEnemy腳本中的生成敵人方法中添加
box = enemy[i].AddComponent<Rigidbody>();
enemy[i].AddComponent<BoxCollider>();
box.isKinematic = false;
box = enemy[i].AddComponent<Rigidbody>();
BoxCollider b= enemy[i].AddComponent<BoxCollider>();
//調整碰撞體位置,防止和地面接觸
b.center = new Vector3(0, 0.6f, 0);
box.isKinematic = false;
//不約束的話,敵人會被你直接撞飛
box.constraints = RigidbodyConstraints.FreeAll;
//在CreateRole腳本中的生成角色方法中添加
//給創建的角色添加碰撞器組件,剛體組件
ro.AddComponent<BoxCollider>();
Rigidbody rig=ro.AddComponent<Rigidbody>();
rig.useGravity = false;
rig.isKinematic = false;
rig.constraints = RigidbodyConstraints.FreezeAll;
//調整碰撞體位置,避免與地面發生碰撞
ro.GetComponent<BoxCollider>().center = new Vector3(0,1.5f,0);
- 在編寫扣血功能時,發現角色還會與周圍環境當中的物體進程碰撞,那麼我們就需要判斷進行碰撞的物體一定是敵人,那麼我們可以給敵人名字套一個統一的馬甲,那麼在碰撞發生時,獲取敵人名字進行字符串的包含判斷即可。
RoleCollier腳本,僅供參考
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class RoleCollier : MonoBehaviour
{
//角色攻擊力
private int attack = 1;
//敵人名字
private string enemyName;
//獲取敵人血量組件Slider
private Slider enemySlider;
//用於存儲敵人默認血量
private int[] defaultEnemyBlood;
private Animation ani;
//骷髏動畫組件
private Animator ator;
//當角色與骷髏發生碰撞時,並且角色在播放攻擊動畫,那麼骷髏扣血
private void OnCollisionEnter(Collision collision)
{
//獲取敵人名字的最後一個字符,並將其轉換爲int型
enemyName = collision.gameObject.name;
ator = collision.gameObject.GetComponent<Animator>();
int enemyNum;
//只有當播放攻擊動畫時,才能夠進行扣血,所以要獲取動畫組件
if (enemyName.Contains("skelet")&&ani.IsPlaying("Attack01"))
{
enemyNum=int.Parse(enemyName.Substring(enemyName.Length - 1));
Debug.Log(GenerateEnemy.Blood[enemyNum]);
GenerateEnemy.Blood[enemyNum] -= attack;
Debug.Log(GenerateEnemy.Blood[enemyNum]);
enemySlider.maxValue = defaultEnemyBlood[enemyNum];
enemySlider.value = GenerateEnemy.Blood[enemyNum];
enemySlider.gameObject.SetActive(true);
//當骷髏血量爲0時,播放死亡動畫,且銷燬骷髏
if (GenerateEnemy.Blood[enemyNum]<=0)
{
ator.SetBool("died",true);
AnimatorStateInfo animatorInfo;
animatorInfo = ator.GetCurrentAnimatorStateInfo(0);
if (animatorInfo.normalizedTime>=1.0f)
{
//在銷燬敵人物體後,可以添加掉落物品加buff功能(未實現)
//有興趣的可以自行實現
Destroy(collision.gameObject);
}
}
}
}
private void Start()
{
enemySlider = SetBlood.GetEnemySlider();
defaultEnemyBlood = new int[GenerateEnemy.Blood.Length];
ani = this.GetComponent<Animation>();
//設置敵人默認血量
int[] Blood = SetBlood.GetdefEnemyBlood();
for (int i=0;i<defaultEnemyBlood.Length;i++)
{
defaultEnemyBlood[i] = Blood[i];
}
}
}
EnemyCollier腳本,僅供參考
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyCollier : MonoBehaviour
{
private Animator ani;
private int attack = 1;
private void Start()
{
ani = GetComponent<Animator>();
}
private void OnCollisionEnter(Collision collision)
{
//自身位置與角色距離小於2進行攻擊
float distance = Vector3.Distance(transform.position,collision.transform.position);
if (distance<2)
{
ani.SetLayerWeight(1,1.0f);
CreateRole.Blood -= attack;
}
}
}