Unity筆記-04
練習專案腳本規劃
專案需求(部分)
開始,生成指定數量的敵人,
為每人隨機選擇一條可以使用的路線,要求:敵人型別,產生的時間隨機
當敵人死亡后再產生下一個敵人,直到生成數量達到上限為止,
需求分析
創建腳本:敵人馬達類,提供移動,旋轉,尋路等功能
創建腳本:敵人狀態資訊類,提供受傷,死亡等功能
創建腳本:敵人影片類,定義各種影片名稱,播放影片的功能
創建腳本:敵人AI類,通過判斷狀態執行尋路或者攻擊
部分代碼與改動
影片類與影片工具類
影片工具類
/// <summary>
/// 影片工具類,提供有關影片播放行為的功能
/// </summary>
public class AnimationTool
{
/// <summary>
/// 附加在敵人影片上的參考
/// </summary>
private Animation anim;
/// <summary>
/// 創建影片行為類
/// </summary>
/// <param name="animation">附在敵人影片上的參考</param>
public AnimationTool(Animation animation)
{
this.anim = animation;
}
public void Play(string animationName)
{
anim.CrossFade(animationName);
}
}
影片工具類,不繼承MonoBehaviour類,僅提供播放影片的方法,因此要寫建構式,并傳入播放組件,提供播放方法,后續有需要可添加例如:判斷影片是否在播放之類的方法
影片類
/// <summary>
/// 敵人影片類
/// </summary>
public class EnemyAnimation : MonoBehaviour
{
/// <summary>
/// 跑步影片
/// </summary>
public string runAnimation;
/// <summary>
/// 攻擊影片
/// </summary>
public string AttackAnimation;
/// <summary>
/// 攻擊后搖影片
/// </summary>
public string AttackRollBackAnimation;
/// <summary>
/// 死亡影片
/// </summary>
public string DeathAnimation;
/// <summary>
/// 影片播放工具類
/// </summary>
private AnimationTool action;
private void Awake()
{
//注意:影片腳本要掛在敵人的父空物體上,以便日后模型更換,而不需要重新除錯腳本
//action = new AnimationTool(?);
//FindObjectOfType<EnemyStatusInfo>().GetComponent<Animation>()
action = new AnimationTool(this.GetComponentInChildren<Animation>());
}
}
對于影片類的說明:
提前定義多個影片的名稱,在初始化的時候給影片名稱賦值:比如攻擊影片,移動影片,這些影片的名稱賦值給名稱變數,之后需要播放該影片的時候,只需要通過影片工具類的播放方法去播放該影片即可,這樣做的原因是保持類的功能單一性,降低耦合度,
影片類的作用就好像提供一個播放串列,根據你的需要去播放你需要播放的影片
注意:
1.影片腳本應當掛在物件物體的父空物體上,通過父空物體去呼叫物件的影片,這樣做可以方便日后的模型更換
2.這里為了方便把所有定義的影片名稱都設定為public方便除錯
路線類
/// <summary>
/// 路線類
/// </summary>
public class WayLine
{
/// <summary>
/// 當前路點坐標
/// </summary>
public WayPoints[] MyProperty;
}
/// <summary>
/// 路點類
/// </summary>
public class WayPoints
{
/// <summary>
/// 點的坐標
/// </summary>
public Vector3 position;
/// <summary>
/// 點是否可用
/// </summary>
public bool IsUseable;
}
該類沒有繼承MonoBehaviour類,作為一個工具類提供路線的存盤
position儲存點的位置,IsUseable儲存點是否可用
敵人AI類
/// <summary>
/// 敵人AI
/// </summary>
[RequireComponent(typeof(EnemyAnimation))]
[RequireComponent(typeof(EnemyMotor))]
[RequireComponent(typeof(EnemyStatusInfo))]
public class EnemyAI : MonoBehaviour
{
public enum State
{
/// <summary>
/// 攻擊狀態
/// </summary>
Attack,
/// <summary>
/// 尋路狀態
/// </summary>
PathFinding
}
/// <summary>
/// 運動類
/// </summary>
private EnemyMotor motor;
/// <summary>
/// 影片類
/// </summary>
private EnemyAnimation anim;
/// <summary>
/// 敵人當前狀態
/// </summary>
private State currentState;
/// <summary>
/// 路線
/// </summary>
private WayLine wayLine;
/// <summary>
/// 手動輸入路點集
/// </summary>
public Transform[] Points;
/// <summary>
/// 初始化
/// </summary>
private void Start()
{
#region 初始化工具
motor = this.GetComponent<EnemyMotor>();//初始化移動馬達
anim = this.GetComponentInParent<EnemyAnimation>();//獲得父空物體上掛的影片腳本
#endregion
#region 初始化路線
wayLine = new WayLine();//創建路線實體
wayLine.MyProperty = new WayPoints[Points.Length];//初始化點集長度
for (int i = 0; i < Points.Length; i++)
{
wayLine.MyProperty[i] = new WayPoints();//創建點實體
wayLine.MyProperty[i].position = Points[i].position;//初始化路點集
wayLine.MyProperty[i].IsUseable = true;
}
#endregion
#region 初始化狀態
currentState = State.PathFinding;
#endregion
}
private float AttackTime=0;
private float intervalTime=2;
/// <summary>
/// 渲染更新
/// </summary>
private void Update()
{
switch (currentState)
{
case State.Attack:
Attack();
break;
case State.PathFinding:
PathFinding();
break;
}
}
private void PathFinding()
{
//播放影片
//執行尋路
//檢查狀態
//修改狀態
if (!motor.PathFinding(wayLine))
{
currentState = State.Attack;
}
}
private void Attack()
{
if (!anim.action.isPlay(anim.AttackAnimation))
{
//播放閑置影片,暫無
}
if (AttackTime < Time.time)
{
//執行攻擊
//播放攻擊影片
anim.action.Play(anim.AttackAnimation);
AttackTime += intervalTime;//影片播放時間-間隔也就是攻擊后搖
}
//播放攻擊后腰影片
//檢查狀態
//修改狀態
}
}
敵人AI類,主要通過當前狀態來判斷當前應當執行哪種動作
這里方便除錯,手動輸入路點集,后續會修正
這里要把尋路和攻擊兩個放大獨立出來成為單獨方法,方便日后維護
敵人狀態類
/// <summary>
/// 敵人狀態資訊類,提供敵人生命值,受傷,陣亡等功能
/// </summary>
public class EnemyStatusInfo : MonoBehaviour
{
/// <summary>
/// 當前生命值
/// </summary>
public int HP;
/// <summary>
/// 最大生命值
/// </summary>
public int MaxHP;
/// <summary>
/// 受傷,減少生命值
/// </summary>
public void Damage(int attackNumber)
{
if (HP > 0)
{
HP -= attackNumber;
}
else
{
Death();
}
}
/// <summary>
/// 銷毀延遲時間
/// </summary>
private float deathDelay=5;
/// <summary>
/// 陣亡
/// </summary>
public void Death()
{
var anim = this.GetComponent<EnemyAnimation>();
//播放死亡影片
anim.action.Play(anim.DeathAnimation);
//銷毀物體
Destroy(this.gameObject, deathDelay);
}
}
銷毀物體需要等待死亡影片播放完畢,因此要設定延遲時間,
敵人馬達類
/// <summary>
/// 敵人馬達運動類,提供前進,注視旋轉,尋路功能
/// </summary>
public class EnemyMotor : MonoBehaviour
{
/// <summary>
/// 設定旋轉速度以及移動速度
/// </summary>
public float speed=2;
/// <summary>
/// 記錄位移向量差
/// </summary>
private Vector3 Relative;
/// <summary>
/// 記錄轉到下次路點的旋轉四元值
/// </summary>
private Quaternion rotation;
/// <summary>
/// 記錄當前走到的路點索引
/// </summary>
private int count = 0;
/// <summary>
/// 記錄上一個路點的索引
/// </summary>
private int lastCount;
/// <summary>
/// 獲得控制件下物件的影片組件
/// </summary>
private Animation anim;
/// <summary>
/// 前進
/// </summary>
public void MoveForward(WayPoints Point)
{
if (Point.IsUseable)//判斷該路點當前是否被占用
{
//transform.Translate(Relative.normalized*Time.deltaTime);
transform.position = Vector3.MoveTowards(transform.position, Point.position,speed*Time.deltaTime);
anim.CrossFade("EnemyRun");
//注意:如果要判斷是否到達,不要用“==”來判斷,因為計算機是離散的,精度不可能完全精準,要用兩點間的距離來判斷
if (Vector3.Distance(transform.position, Point.position)<0.01f)
{
anim.Stop("EnemyRun");
lastCount = count;//存盤上一個路點
Point.IsUseable = false;
count++;//當前路點自增
}
}
}
/// <summary>
/// 注視旋轉
/// </summary>
public void LookRotate(Quaternion rotation)
{
this.transform.rotation = Quaternion.Slerp(this.transform.rotation,rotation,3*speed*Time.deltaTime);
}
/// <summary>
/// 尋路
/// </summary>
public bool PathFinding(WayLine wayLine)
{
if (wayLine!= null && count < wayLine.MyProperty.Length)
{
WayPoints Point = wayLine.MyProperty[count];
Relative = Point.position - this.transform.position;//獲得向量差值
rotation = Quaternion.LookRotation(Relative);//獲得旋轉
if (transform.rotation != rotation)//如果方位不相同,那么進行旋轉
{
LookRotate(rotation);
}
else
{
MoveForward(Point);
wayLine.MyProperty[lastCount].IsUseable = true;
}
return true;
}
else
{
//尋路完成
return false;
}
}
/// <summary>
/// 初始化階段
/// </summary>
private void Start()
{
anim = this.GetComponentInChildren<Animation>();
}
}
敵人馬達類,提供移動,轉動,尋路等功能,該功能的基本原理為:提供實作給定的一系列點集組成一條路線,在讓敵人沿著這條路線移動,
基本思路:
首先注視旋轉將面朝方向對準下一個點,然后移動,直到移動到最后一個點,這里由于敵人生成器還未寫,對于路線能否使用的判斷暫不做解釋(可能有誤),
有關移動
主要有以下兩種方法:
一:
Translate方法,具體使用方法
transform.Translate(方向向量);
二:
Vector3.MoveTowards方法,具體使用方法
Vector3.MoveTowards(自身位置,移動的目標點,移動速度);
但是要做到持續移動,更加推薦第二種方法,第一種方法因為移動路段固定,可能在某種特定情況下出現穿墻等bug
這里通過兩點之間的向量差得到這兩點的方向向量:
Relative = Point.position - this.transform.position;
有關注視旋轉
也有兩種方法(不全):
一:
Transform.LookAt(目標點) 瞬間轉向目標點
二:
rotation = Quaternion.LookRotation(向量差);Relative該方法回傳旋轉四元值(Quaternion)
transform.rotation = Quaternion.Slerp(當前物體的四元值,rotation,旋轉速度);
可以持續的旋轉,直到正方向(默認為Z軸)朝向目標點
注意
這里的wayline在實際運行的時候會在Start方法里進行初始化賦值,尋路只需要將路線給到尋路方法即可自動尋路
一些的問題和疑問
在實際除錯程序中
- 由于影片和移動判定的沖突,我將移動腳本掛在了物件的父空物體上,通過控制父空物體的移動來控制物件的移動,而影片則單獨控制物件,這樣便避免了沖突,
- 關于第二種旋轉方法的判定問題:如果出現逆時針旋轉,通過除錯,四元數(x,y,z,w)會出現相反數的情況,而導致判定錯誤,如果都是順時針旋轉那么沒有問題,我的判定方法為:
transform.rotation != rotation也就是之間判斷二者是否相等,顯然我這樣的判定存在問題,但是是否單獨拿出四元數進行絕對值判斷就正確呢?這里存在疑問 - 由于尋路方法同時涵蓋了旋轉和移動,這導致影片類無法在敵人AI類中獨立出播放,而必須耦合進旋轉和移動的方法,否則就會導致影片錯誤例如:旋轉的程序中就播放移動影片,這顯然是錯誤的;尋路方法是否應該放到敵人AI類里會更好一些,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/299759.html
標籤:其他
上一篇:U-Boot-記憶體部分
