在3D遊戲中如果涉及到射擊類遊戲那麼就會有飛行類的指令碼比如直線飛行,導彈追蹤飛行,拋物線飛行,鐳射飛行,等等。。。
1.這些飛行的指令碼有一個共同的特點都需要有目標,和方向, 那麼先寫一個基類指令碼先上乾貨
//這個指令碼基本是供外面調(賦值什麼的)
using System.Collections; using System.Collections.Generic; using UnityEngine; public class FlyAI : MonoBehaviour { private static int MYUID=0; protected int m_MyUID=0; public const int EVENT_IDLE = 0;//空閒 public const int EVENT_START = 1;//開始飛行 public const int EVENT_FLYING = 2;//飛行中 public const int EVENT_ARRIVE = 3;//到達目標 public const int EVENT_END = 4;//到達最大航程,飛行結束 public delegate void OnFlyEvent(int nEvent); protected OnFlyEvent onFlyEvent = null; protected string m_Myname = ""; protected bool m_bActive = false; protected int m_CurFlyEvent = EVENT_IDLE; protected GameObject m_MyGo = null; //自己的GameObject protected Vector3 m_MeStartPos = new Vector3(0, 0, 0);//自己的初始位置 protected Vector3 m_MeCurrPos = new Vector3(0, 0, 0);//自己的當前位置 protected float m_fTurnRate = 1f; //自己的轉灣倍率(標量) protected float m_fSpeedRate = 1f; //自己的飛行速度(單位米/秒) protected Vector3 m_Velocity = new Vector3(1, 0, 1);//自己的速度方向 protected float m_fMaxRange = 10000f; //飛行最大飛行距離 private FightTarg m_ArmTarg = null;//目標 private GameObject m_TargGo = null; //目標的GameObject protected Vector3 m_TargStartPos = new Vector3(0, 0, 0);//目標的初始位置 protected Vector3 m_TargCurrPos = new Vector3(0, 0, 0);//目標的當前位置 protected float m_fArriveRange = 0.1f;//達到距離(離目標距離多少為到達目標,單位:米) private bool m_bTargIsAlive = false; protected float m_FlyLife = 0; //已飛行時間 protected float m_FlyDistance = 0; //累計飛行距離 protected bool m_bTimeFreeze = false; protected virtual void Awake() { MYUID++; m_MyUID=MYUID; m_CurFlyEvent = EVENT_IDLE; m_Myname = "未命名"; } protected virtual void Start() { m_bActive = true; m_bTargIsAlive = true; } protected virtual void Update() { if (m_bTimeFreeze) return; if (m_bActive) { if (m_CurFlyEvent == EVENT_IDLE) { DoEvent(EVENT_START);//開始飛行事件 } else { DoFly(); } } } protected virtual void OnDestroy() { m_bActive = false; m_TargGo = null; m_MyGo = null; } public void EnableTimeFreeze(bool bEnable) { m_bTimeFreeze = bEnable; OnTimeFreeze(); } protected virtual void OnTimeFreeze() { //子類實現其自定義功能 } protected virtual void DoFly() { m_FlyLife = m_FlyLife + Time.deltaTime; if (m_TargGo == null || m_bTargIsAlive == false) return; if (m_ArmTarg != null && m_ArmTarg.IsDie()) {//如果目標死亡,則不復制位置了 m_TargGo = null; m_ArmTarg = null; m_bTargIsAlive = false; return; } //複製目標GameObject當前位置 DefendTool.CopyVector3(m_TargGo.transform.position, out m_TargCurrPos); return; } public void SetName(string flyname) { m_Myname = flyname; } public void AppendName(string flyname) { m_Myname = m_Myname + ":" + flyname; } public void Pause() { m_bActive = false; } public void Resume() { m_bActive = true; } public void EnableFly(bool bEnable) { m_bActive = bEnable; } protected float SinProjectToVector(Vector3 from, Vector3 to) { float result = Vector3.Dot(from.normalized, to.normalized);//向量的點積 result = Mathf.Acos(result);//弧度 return Mathf.Abs(from.magnitude * Mathf.Sin(result));//點到直線距離 } public void RegisterEvent(OnFlyEvent _callback) { onFlyEvent -= _callback; onFlyEvent += _callback; } public void UnRegisterEvent(OnFlyEvent _callback) { onFlyEvent -= _callback; } protected void DoEvent(int Evnet) { if (Evnet == m_CurFlyEvent) return; m_CurFlyEvent = Evnet; if (onFlyEvent == null) return; //try {//遮蔽回撥產生的錯誤導致關聯錯誤 onFlyEvent(m_CurFlyEvent); } //catch (System.Exception e) //{ // Debug.LogError("FlyAI::DoEvent() name:" + m_Myname + ",回撥處理中發生錯誤:" + e.StackTrace); //} return; } /// <summary> /// 設定飛行器的引數 /// </summary> /// <param name="meGameobject">飛行器GameObject</param> /// <param name="startDir">飛行器初始速度方向</param> /// <param name="fSpeedRate">飛行速度(單位米/秒)</param> /// <param name="fTurnRate">轉彎倍率(標量,大於0)</param> /// <param name="fMaxRange">飛行器最大的飛行距離</param> public virtual void InitMe(GameObject meGameobject, Vector3 startDir, float fSpeedRate, float fTurnRate, float fMaxRange=1000000) { m_MyGo = meGameobject; DefendTool.CopyVector3(startDir, out m_Velocity); m_fSpeedRate = fSpeedRate; m_fTurnRate = fTurnRate; m_fMaxRange = fMaxRange; m_FlyDistance = 0; DefendTool.CopyVector3(m_MyGo.transform.position, out m_MeStartPos); DefendTool.CopyVector3(m_MyGo.transform.position, out m_MeCurrPos); } /// <summary> /// 設定飛行器追蹤目標引數 /// </summary> /// <param name="ITarg">目標ITarg</param> /// <param name="targGameobject">目標GameObject</param> /// <param name="fArriveRange">達到距離(離目標距離多少為到達目標)</param> public virtual void InitTarg(FightTarg targ, GameObject targGameobject, float fArriveRange) { m_ArmTarg = targ; m_TargGo = targGameobject; DefendTool.CopyVector3(m_TargGo.transform.position, out m_TargStartPos); DefendTool.CopyVector3(m_TargGo.transform.position, out m_TargCurrPos); m_fArriveRange = fArriveRange; } /// <summary> /// 設定飛行器追蹤目標引數 /// </summary> /// <param name="ITarg">目標ITarg</param> /// <param name="targPosition">目標靜態位置</param> /// <param name="fArriveRange">達到距離(離目標距離多少為到達目標)</param> public void InitTarg(FightTarg targ, Vector3 targPosition, float fArriveRange) { m_ArmTarg = targ; m_TargGo = null; DefendTool.CopyVector3(targPosition, out m_TargStartPos); DefendTool.CopyVector3(targPosition, out m_TargCurrPos); m_fArriveRange = fArriveRange; } }
2.上面的基類已經為我提供了資料接下來就是關於子彈飛行的時間以及事件基類
using System.Collections; using System.Collections.Generic; using UnityEngine; public class FlyWeapon : FlyAI {//武器飛行器基類(子彈,導彈,炸彈,鐳射等飛行器基類) protected float m_MaxLife = 0; //最大生命週期 protected override void Awake() { base.Awake(); SetName("FlyWeapon"); } protected override void Start() { base.Start(); } protected override void Update() { base.Update(); } protected override void OnDestroy() { base.OnDestroy(); } protected override void DoFly() { base.DoFly(); } protected void DestroyBullet() { EnableFly(false); Destroy(this);//消除子彈指令碼 } }
3_1.終於可以寫子彈的本體指令碼了(先寫一個直線飛行的子彈)
using System.Collections; using System.Collections.Generic; using UnityEngine; public class FlyWeaponLine : FlyWeapon {//子彈類飛行方式,直線飛行追蹤目標,沒有轉彎半徑,直接轉向 protected override void Awake() { base.Awake(); SetName("FlyWeaponLine"); } protected override void Start() { base.Start(); } protected override void Update() { base.Update(); } protected override void OnDestroy() { base.OnDestroy(); } protected override void DoFly() { base.DoFly(); //本時間飛行距離 bool bThrough = false; float flyDistance=Time.deltaTime*m_fSpeedRate; Vector3 remainpath = m_TargStartPos - transform.position; float remainDistance = remainpath.magnitude; if (remainDistance <= flyDistance) { flyDistance = remainDistance; bThrough = true; } m_FlyDistance = m_FlyDistance + flyDistance; remainpath = remainpath.normalized * flyDistance; transform.position = transform.position + remainpath; DefendTool.CopyVector3(transform.position, out m_MeCurrPos);//儲存新位置 //調整子彈朝向 transform.LookAt(m_TargStartPos); //判斷此次飛行是否傳過目標物體 if (remainDistance < m_fArriveRange || bThrough) { DoEvent(EVENT_ARRIVE); DestroyBullet();//結束子彈 } else { if (m_FlyDistance >= m_fMaxRange) { DoEvent(EVENT_END); //飛行結束 DestroyBullet();//結束子彈 } else { DoEvent(EVENT_FLYING); } } return; } }
3_2:導彈追蹤
using System.Collections; using System.Collections.Generic; using UnityEngine; public class FlyWeaponNavi : FlyWeapon {//導彈類飛行方式,自動追蹤目標,有轉彎半徑 protected Vector3 planeangle = new Vector3(1, 0, 1); protected float m_fCurrSpeed = 0; protected override void Awake() { base.Awake(); SetName("FlyWeaponNavi"); } protected override void Start() { base.Start(); transform.Rotate(m_Velocity); m_fCurrSpeed = 0; } protected override void Update() { base.Update(); } protected override void OnDestroy() { base.OnDestroy(); } protected override void DoFly() { base.DoFly(); //轉向處理 float per = m_FlyLife/2; float rate = per * per; if (rate >= 1) rate = 1; Vector3 v1 = m_TargCurrPos - transform.position; m_fTurnRate = Mathf.Lerp(0, 0.8f, rate); transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(v1), m_fTurnRate ); //求運動距離 per = m_FlyLife / 1.4f; rate = per * per; if (rate >= 1) rate = 1; bool bArrived = false; m_fCurrSpeed = Mathf.Lerp(m_fSpeedRate/3, m_fSpeedRate, rate); float flylength = m_fCurrSpeed * Time.deltaTime; Vector3 v0_me = transform.forward * flylength; //執行方向向量 Vector3 v1_totarg = m_TargCurrPos - transform.position;//子彈指向目標的向量 Vector3 v2_project = Vector3.Project(v1_totarg,v0_me.normalized);//向量v1在v0上的投影向量 float flyDistance = v0_me.magnitude; //本次飛行距離(米) float totargDistance = v1_totarg.magnitude;//和目標的距離(米) float projectDistance = v2_project.magnitude;//投影距離 float sinDistance = SinProjectToVector(v1_totarg, v0_me);//目標點到執行方向的距離 m_fArriveRange = 1f; if(projectDistance<=flyDistance) {//穿越 if (sinDistance<=m_fArriveRange) {//到達 bArrived = true; v0_me = v0_me.normalized * projectDistance / flyDistance * flylength; } } transform.position = transform.position + v0_me; DefendTool.CopyVector3(transform.position, out m_MeCurrPos);//儲存新位置 if (bArrived == true) { DoEvent(EVENT_ARRIVE); DestroyBullet();//結束子彈 //Debug.LogError("FlyWeaponNavi::DoFly()導彈。。。到達目標物體"); } else { if (m_FlyDistance >= m_fMaxRange) { DoEvent(EVENT_END); //飛行結束 DestroyBullet();//結束子彈 //Debug.LogError("FlyWeaponNavi::DoFly()導彈。。。EVENT_END"); } else { DoEvent(EVENT_FLYING); } } return; } }
3_3.1拋物線飛行方式(下面的邏輯是向下或者向上都可以以拋物線的方式飛行)前方高能
using System.Collections; using System.Collections.Generic; using UnityEngine; public class FlyWeaponParabola : FlyWeapon {//炸彈類飛行方式,按拋物線方式進行飛行到原始目標,不能追蹤目標新位置 private const float m_G = -9.8f; //重力加速度,相對y軸是負的 private float m_SpeedHorizontal=0;//水平初速度 private float m_SpeedVertical=0;//垂直初速度 private float m_D0=0;//水平距離 private float m_H0=0;//垂直距離 private float m_theta=0; //初速與水平面的夾角 private float m_dx=0; private float m_dy=0; private float m_dz=0; protected override void Awake() { base.Awake(); SetName("FlyWeaponParabola"); } protected override void Start() { base.Start(); //求出水平和垂直初速和夾角 int n = 0; bool bFind = false; while(n<16) { bFind=FindThetaAndSubSpeed(); if (bFind) break; n++; m_fSpeedRate = m_fSpeedRate * 1.5f; } if (bFind==false) { Debug.LogError("FlyWeaponParabola::Start() 拋物線子彈無法拋到位!"); } } protected override void Update() { base.Update(); } protected override void OnDestroy() { base.OnDestroy(); } protected override void DoFly() { base.DoFly(); //計算執行位置 bool bEnd = false; float t = m_FlyLife; if (t >= m_MaxLife) { t = m_MaxLife; bEnd = true; } float y = m_G / 2 * t * t + m_SpeedVertical * t; float d = m_SpeedHorizontal * t; float x = m_dx * d / m_D0; float z = m_dz * d / m_D0; Vector3 nextpos = new Vector3(m_MeStartPos.x + x, m_MeStartPos.y + y, m_MeStartPos.z + z); m_MyGo.transform.position = nextpos; DefendTool.CopyVector3(m_MyGo.transform.position, out m_MeCurrPos);//儲存新位置 //調整子彈朝向 m_MyGo.transform.LookAt(m_TargStartPos); //判斷此次飛行是否傳過目標物體 if (bEnd) { Vector3 distance = m_TargStartPos - m_MyGo.transform.position; if (distance.magnitude < m_fArriveRange) { DoEvent(EVENT_ARRIVE); } else { DoEvent(EVENT_END); } DestroyBullet();//結束子彈 } else { DoEvent(EVENT_FLYING); } return; } /// <summary> /// 求角度和水平和垂直速度 /// </summary> /// <returns></returns> private bool FindThetaAndSubSpeed() { //求出初速與水平面的夾角 m_dx = m_TargStartPos.x - m_MeStartPos.x; m_dy = m_TargStartPos.y - m_MeStartPos.y; m_dz = m_TargStartPos.z - m_MeStartPos.z; m_H0 = m_dy; m_D0 = Mathf.Sqrt(m_dx * m_dx + m_dz * m_dz); if (m_D0 < 0.1) {//同點投放 m_D0 = 0.1f; } m_theta = m_dy / m_D0; m_theta = Mathf.Atan(m_theta);//反正切,返回值介於-pi/2 與 pi/2 之間 float LimtAngle = Mathf.PI / 2; //限制90度 float inc = Mathf.PI * (0.1f / 360);//每次增量為0.1度 //初始角度 m_SpeedHorizontal=m_fSpeedRate * Mathf.Cos(m_theta);//水平初速度 m_SpeedVertical=m_fSpeedRate * Mathf.Sin(m_theta);//垂直初速度 m_MaxLife = m_D0 / m_SpeedHorizontal; float h1 = m_G * m_MaxLife * m_MaxLife / 2 + m_SpeedVertical * m_MaxLife; float h2 = h1; bool bFind = false; while(true) { if (Mathf.Abs(m_H0 - h1) < 0.1f) { bFind = true; break;//找到了 } if (h2 < h1) { bFind = false; break;//未找到了 } m_theta = m_theta + inc; if (m_theta >= LimtAngle) {//超出角度,未找到了 bFind = false; m_theta = m_theta -inc; return true; } h1 = h2; m_SpeedHorizontal = m_fSpeedRate * Mathf.Cos(m_theta);//水平初速度 m_SpeedVertical = m_fSpeedRate * Mathf.Sin(m_theta);//垂直初速度 m_MaxLife = m_D0 / m_SpeedHorizontal; h2 = m_G * m_MaxLife * m_MaxLife / 2 + m_SpeedVertical * m_MaxLife; } //如果無法拋物到位 if (bFind==false) { float dh = 0; if (m_H0<0) { dh = m_H0; } //////////////////////////////////////// //ax^2+bx+c=0; //G/2*t^2+vVertical*t-dh=0; /////////////////////////////////////// float a = m_G / 2; float b = m_SpeedVertical; float c = 0 - dh; float d = b * b - 4 * a * c; if (d>=0) { b = 0 - b; float t1 = (b - Mathf.Sqrt(d)) / (2 * a); float t2 = (b + Mathf.Sqrt(d)) / (2 * a); //取正的小的值 d = t1 < t2 ? t1 : t2; if (d < 0) d = t1 > t2 ? t1 : t2; } if (d <0) { m_MaxLife = 1.0f; } else { m_MaxLife = d; } } //Debug.LogError("FlyWeaponParabola::FindThetaAndSubSpeed() m_MaxLife:" + m_MaxLife); return bFind; } ///// <summary> ///// 求角度和水平和垂直速度 ///// </summary> ///// <returns></returns> //private bool FindThetaAndSubSpeed() //{ // //求出初速與水平面的夾角 // //求出出發點和目標點連線與水平面的夾角theta // //當目標點在出發點水平面之下則theta小於0 // //當目標點和出發點在同水平面則theta為0 // //當目標點在出發點水平面之上則theta大於0 // m_dx = m_TargStartPos.x - m_MeStartPos.x; // m_dy = m_TargStartPos.y - m_MeStartPos.y; // m_dz = m_TargStartPos.z - m_MeStartPos.z; // m_H0 = m_dy; // m_D0 = Mathf.Sqrt(m_dx * m_dx + m_dz * m_dz); // if (m_D0 < 0.05) // {//同點投放 // m_D0 = 0.01f; // } // float theta0 = m_dy / m_D0; // theta0 = Mathf.Atan(theta0);//反正切,返回值介於-pi/2 與 pi/2 之間 // float LimtAngle = Mathf.PI / 2; //限制90度 // float inc = 2 * Mathf.PI * (0.1f / 360);//每次增量為0.1度 // float theta1 = theta0; // float hLow = 0; // float hNow = 0; // float hNew = 0; // float vHorizontal = 0; // float vVertical = 0; // float life = 0; // bool bFindTheta = false; // //查詢合適角度 // CalThisTheta(theta1, out vHorizontal, out vVertical, out hNew, out life); // hLow = hNew; // hNow = hNew; // while (true) // { // if (hNew <= hNow) // { // //到達頂點,但無法靠近目標,也使用此角度 // break; // } // hNow = hNew; // if (Mathf.Abs(hNow - m_H0) < 0.1) // { // //距離靠近目標,使用此角度 // bFindTheta = true; // break; // } // if (hNow > m_H0 || hNow < hLow) // { // //跨越目標,使用此角度 // bFindTheta = true; // break; // } // if ((theta0 + inc) > LimtAngle) // { // //跨越垂直角度,使用此角度 // bFindTheta = true; // break; // } // if (hNow <= m_H0) // { // hLow = hNow; // } // theta0 = theta0 + inc; // CalThisTheta(theta0, out vHorizontal, out vVertical, out hNew, out life); // } // if (bFindTheta == false) // { // Debug.LogError("FlyWeaponParabola::FindThetaAndSubSpeed() 敵人在射程之外!!!"); // } // m_SpeedHorizontal = vHorizontal;//水平初速度 // m_SpeedVertical = vVertical;//垂直初速度 // m_theta = theta0; // m_MaxLife = life; // return bFindTheta; //} //private void CalThisTheta(float theta0, out float vHorizontal, out float vVertical, out float h, out float t) //{ // vHorizontal = m_fSpeedRate * Mathf.Cos(theta0);//水平初速度 // vVertical = m_fSpeedRate * Mathf.Sin(theta0);//垂直初速度 // if (vHorizontal < 0.001) // {//垂直投放 // vHorizontal = 0; // vVertical = m_fSpeedRate; // h = m_H0; // //////////////////////////////////////// // //ax^2+bx+c=0; // //G/2*t^2+vVertical*t-m_H0=0; // /////////////////////////////////////// // float a = m_G / 2; // float b = vVertical; // float c = 0 - m_H0; // t = b * b - 4 * a * c; // if (t >= 0) // { // b = 0 - b; // float t1 = (b - Mathf.Sqrt(t)) / (2 * a); // float t2 = (b + Mathf.Sqrt(t)) / (2 * a); // //取正的小的值 // t = t1 < t2 ? t1 : t2; // if (t < 0) // t = t1 > t2 ? t1 : t2; // } // if (t < 0) // {//垂直無解情況,無法達到高點 vt+gt^2/2=0; v=gt/2 ,totla time = 2t=-4v/g; // t = Mathf.Abs(-4 * vVertical / m_G); // } // } // else // {//不是垂直情況 // t = m_D0 / vHorizontal;//水平執行時間 // h = vVertical * t + m_G / 2 * t * t; // } // return; //} }
3_3.2拋物線飛行方式(只能向上拋,算出和物件的距離,高度與速度大小有關,當高度和自己的高度一樣的時候會刪除自己或者做相應的事件)這個和上面的基類完全沒關係,是拋物線的一個擴充元件)
using System.Collections; using System.Collections.Generic; using UnityEngine; public class ParabolaBullet : MonoBehaviour { private const float g = 9.8f; //重力加速度 private float m_Speed = 10;//水平飛行速度(米/秒) private float m_verticalSpeed=0;//垂直速度 private Vector3 m_moveDirection;//水平移動方向 public Vector3 m_TargPos;//目標位置 //private Vector3 m_FromPos;//開始位置 private float angleSpeed; private float angle; private float m_FlyTime = 0;//飛行時間 private bool m_IsArrived = false; private float m_distance = 0f; void Start() { //m_FromPos = new Vector3(transform.position.x,transform.position.y,transform.position.z); m_distance = Vector3.Distance(transform.position, m_TargPos);//總水平飛行距離 m_Speed = m_distance; if (m_Speed < 0.5f) m_Speed = 0.5f; m_Speed = m_Speed / 1.1f;//10f; float tempTime = m_distance / m_Speed;//總水平飛行時間 float riseTime;//上升的時間 riseTime = tempTime / 2;//上升的時間等於1半水平飛行時間 m_verticalSpeed = g * riseTime; transform.LookAt(m_TargPos); float tempTan = m_verticalSpeed / m_Speed; double hu = Mathf.Atan(tempTan); angle = (float)(180 / Mathf.PI * hu); transform.eulerAngles = new Vector3(-angle, transform.eulerAngles.y, transform.eulerAngles.z); angleSpeed = angle / riseTime; m_moveDirection = m_TargPos - transform.position; } void Update() { if (transform.position.y < m_TargPos.y) { //finish m_IsArrived = true; return; } try { m_FlyTime += Time.deltaTime; float currVerticalSpeed = m_verticalSpeed - g * m_FlyTime;//當前垂直方向速度 transform.Translate(m_moveDirection.normalized * m_Speed * Time.deltaTime, Space.World);//水平方向移動 transform.Translate(Vector3.up * currVerticalSpeed * Time.deltaTime, Space.World);//垂直方向移動 float testAngle = -angle + angleSpeed * m_FlyTime; transform.eulerAngles = new Vector3(testAngle, transform.eulerAngles.y, transform.eulerAngles.z); } catch (System.Exception e) { Debug.LogError("ParabolaBullet::Update() err:" + e.Message); } } public bool IsArrived() { return m_IsArrived; } }