Unity官方案例研究(第三人稱控制器)
阿新 • • 發佈:2019-01-07
因為在網上不容易找到解析官方案例的文章,最近也在研究第三人稱控制器,所以把我所知道的東西給大家分享一下.
主要的程式碼就這兩個指令碼,我已經大量註釋過了,現在給大家貼出來.
ThirdPersonUserControl指令碼
using System; using UnityEngine; using UnityStandardAssets.CrossPlatformInput; namespace UnityStandardAssets.Characters.ThirdPerson { [RequireComponent(typeof (ThirdPersonCharacter))] public class ThirdPersonUserControl : MonoBehaviour { private ThirdPersonCharacter m_Character; //ThirdPersonCharacter的物件的引用 private Transform m_Cam; // 主攝像機的位置 private Vector3 m_CamForward; // 當前相機的正前方 private Vector3 m_Move; //根據相機的正前方和使用者的輸入,計算世界座標相關的移動方向 private bool m_Jump; private void Start() { //獲取主相機,這邊的實現跟求控制器是一樣的 if (Camera.main != null) { m_Cam = Camera.main.transform; } else { // 在這個例子中我們使用世界座標來控制,也許這不是他們做希望的,不過我們至少警告一下他們! Debug.LogWarning("不存在主攝像機,需要有一個MinCamera來跟隨Player", gameObject); } // 獲取第三人稱的移動指令碼,這個不能為空 m_Character = GetComponent<ThirdPersonCharacter>(); } private void Update() { if (!m_Jump)//不在跳躍狀態下,如果讀到跳躍則更新變數 { m_Jump = Input.GetButtonDown("Jump"); } } // 固定幀數,用於物理的同步 private void FixedUpdate() { // 獲取使用者的輸入 //CrossPlatformInputManager是用來跨平臺使用的,input在windows平臺沒問題,在其他品臺可能就會出問題 float h = CrossPlatformInputManager.GetAxis("Horizontal"); float v = CrossPlatformInputManager.GetAxis("Vertical"); bool crouch = Input.GetKey(KeyCode.C); // 計算移動方向,並傳遞給角色 if (m_Cam != null) { // 計算相機關聯方向,這邊同樣強調了前方向 //吧攝像機.forword的Y軸設為0並規範化 m_CamForward = Vector3.Scale(m_Cam.forward, new Vector3(1, 0, 1)).normalized; // print("m_Cam.forward = :" + m_Cam.forward); // print("m_CamForward = "+m_CamForward); // print("H = "+h); // print("V = " + v); //v*攝像機的forWord //h*攝像機的right m_Move = v*m_CamForward + h*m_Cam.right; // print("m_move = " + m_Move); } else { // 當沒有相機時,直接以世界座標軸作為參考 m_Move = v*Vector3.forward + h*Vector3.right; } #if !MOBILE_INPUT //走路速度減半 if (Input.GetKey(KeyCode.LeftShift)) m_Move *= 0.5f; #endif // 將所有的引數傳遞給移動類 m_Character.Move(m_Move, crouch, m_Jump); m_Jump = false;//跳躍是個衝力,只要傳一次就夠了 } } }
ThirdPersonCharacter指令碼
using UnityEngine; namespace UnityStandardAssets.Characters.ThirdPerson { // 第三人稱移動類,這邊沒有相機層,並且使用的是剛體和膠囊碰撞的組合,在使用者控制的指令碼ThirdPersonUserControl中只用到了Move方法來控制角色 [RequireComponent(typeof(Rigidbody))] [RequireComponent(typeof(CapsuleCollider))] [RequireComponent(typeof(Animator))] public class ThirdPersonCharacter : MonoBehaviour { /// <summary> /// 移動中轉向的速度 /// </summary> [SerializeField] float m_MovingTurnSpeed = 360;//移動中轉向的速度 /// <summary> /// 站立中轉向的速度 /// </summary> [SerializeField] float m_StationaryTurnSpeed = 180;//站立中轉向的速度 /// <summary> /// 跳躍產生的力量 /// </summary> [SerializeField] float m_JumpPower = 12f;//跳躍產生的力量 /// <summary> /// 重力 /// </summary> [Range(1f, 4f)][SerializeField] float m_GravityMultiplier = 2f;//重力 /// <summary> /// 腿偏移值 /// </summary> [SerializeField] float m_RunCycleLegOffset = 0.2f; //腿偏移值 /// <summary> /// 移動速度 /// </summary> [SerializeField]float m_MoveSpeedMultiplier = 1f;//移動速度 /// <summary> /// 動畫播放速度 /// </summary> [SerializeField] float m_AnimSpeedMultiplier = 1f;//動畫播放速度 /// <summary> /// 地面檢查的距離 /// </summary> [SerializeField] float m_GroundCheckDistance = 0.1f;//地面檢查的距離 /// <summary> /// 剛體 /// </summary> Rigidbody m_Rigidbody;//剛體 /// <summary> /// 動畫狀態機 /// </summary> Animator m_Animator;//動畫狀態機 /// <summary> /// 是否在地面上 /// </summary> bool m_IsGrounded;//是否在地面上 /// <summary> /// 地面距離檢測的起始值 /// </summary> float m_OrigGroundCheckDistance;//地面距離檢測的起始值 /// <summary> /// 一半 /// </summary> const float k_Half = 0.5f;//一半 /// <summary> /// 轉向值 /// </summary> float m_TurnAmount;//轉向值 /// <summary> /// 前進值 /// </summary> float m_ForwardAmount;//前進值 /// <summary> /// 地面法向量 /// </summary> Vector3 m_GroundNormal;//地面法向量 /// <summary> /// 膠囊高度 /// </summary> float m_CapsuleHeight;//膠囊高度 /// <summary> /// 膠囊的中心 /// </summary> Vector3 m_CapsuleCenter;//膠囊的中心 /// <summary> /// 膠囊體 /// </summary> CapsuleCollider m_Capsule;//膠囊體 /// <summary> /// 是否是蹲伏狀態 /// </summary> bool m_Crouching;//是否是蹲伏狀態 //初始化動畫\剛體和膠囊體 void Start() { m_Animator = GetComponent<Animator>(); m_Rigidbody = GetComponent<Rigidbody>(); m_Capsule = GetComponent<CapsuleCollider>(); m_CapsuleHeight = m_Capsule.height;//膠囊高度 m_CapsuleCenter = m_Capsule.center; //膠囊的中心 //鎖定剛體的 XYZ軸的旋轉 m_Rigidbody.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationY | RigidbodyConstraints.FreezeRotationZ; m_OrigGroundCheckDistance = m_GroundCheckDistance;//儲存一下地面檢查值 } // 這是在FixedUpdate中呼叫的Move方法 /// <summary> /// 移動方法 /// </summary> /// <param name="move">移動的方向</param> /// <param name="crouch">是否蹲伏</param> /// <param name="jump">是否跳躍</param> public void Move(Vector3 move, bool crouch, bool jump) { // 將一個世界座標的輸入轉換為本地相關的轉向和前進速度,需要考慮到角色頭部的方向 if (move.magnitude > 1f) move.Normalize(); //向量大於1,則變為單位向量 move = transform.InverseTransformDirection(move);//將世界座標系的方向和位置轉換為自身座標系 //判斷是否離開地面, //並修改:m_GroundNormal:地面法向量; // m_IsGrounded //是否在地面上 // m_Animator.applyRootMotion//動畫是否影響實際位置 CheckGroundStatus(); // [Vector3.ProjectOnPlane]聖典:投影向量到一個平面上,該平面由垂直到該法線的平面定義。 move = Vector3.ProjectOnPlane(move, m_GroundNormal);//根據地面的法向量,產生一個對應平面的速度方向. print("Move:" + move); //[Mathf.Atan2] :以弧度為單位計算並返回 y/x 的反正切值。返回值表示相對直角三角形對角的角,其中 x 是臨邊邊長,而 y 是對邊邊長。 //返回值為x軸和一個零點起始在(x,y)結束的2D向量的之間夾角。 m_TurnAmount = Mathf.Atan2(move.x, move.z);//產生一個方位角,即與z軸的夾角,用於人物轉向 print("Mathf.Atan2(move.x, move.z)用於人物轉向" + m_TurnAmount); m_ForwardAmount = move.z;//人物前進的數值 //申請額外的旋轉轉 ApplyExtraTurnRotation();//應用附加轉彎 // 控制和速度處理,在地上和空中是不一樣的 if (m_IsGrounded)//如果在地面上 { //檢測是否能跳 HandleGroundedMovement(crouch, jump); } else { HandleAirborneMovement(); } //縮小膠囊體,並判斷是否可以站立 ScaleCapsuleForCrouching(crouch); //在只能下蹲的區域保持下蹲 PreventStandingInLowHeadroom(); // 將輸入和其他狀態傳遞給動畫元件 UpdateAnimator(move); } /// <summary> /// 縮小膠囊碰撞體 /// </summary> /// <param name="crouch">是否蹲伏</param> void ScaleCapsuleForCrouching(bool crouch) { //蹲下的一瞬間把膠囊高度和中心高度減半 if (m_IsGrounded && crouch) { if (m_Crouching) return; m_Capsule.height = m_Capsule.height / 2f; m_Capsule.center = m_Capsule.center / 2f; m_Crouching = true;//吧正在蹲下設定為true,保證上面程式碼只執行一次 } else { //創造一條剛體位置增加半徑一半的位置,向上發射 Ray crouchRay = new Ray(m_Rigidbody.position + Vector3.up * m_Capsule.radius * k_Half, Vector3.up); //射線長度,膠囊原高度減少半徑一半的位置, float crouchRayLength = m_CapsuleHeight - m_Capsule.radius * k_Half; if (Physics.SphereCast(crouchRay, m_Capsule.radius * k_Half, crouchRayLength, Physics.AllLayers, QueryTriggerInteraction.Ignore)) { //這邊的意思是從角色底部向上丟一個球,然後那個k_Half相關的引數是為了放置在丟的時候就碰到了地面,而做的向上偏移 m_Crouching = true; //碰到了,說明角色無法回到站立狀態 return; } // 沒有碰到,回到初始的狀態 m_Capsule.height = m_CapsuleHeight; m_Capsule.center = m_CapsuleCenter; m_Crouching = false; } } /// <summary> /// 在只能下蹲的區域保持下蹲 /// </summary> void PreventStandingInLowHeadroom() { // 在只能下蹲的區域保持下蹲 if (!m_Crouching) { //radius半徑 Ray crouchRay = new Ray(m_Rigidbody.position + Vector3.up * m_Capsule.radius * k_Half, Vector3.up); float crouchRayLength = m_CapsuleHeight - m_Capsule.radius * k_Half;//掃描的長度 //當球形掃描與任意碰撞器相交,返回true;否則返回false。 QueryTriggerInteraction:指定是否查詢碰到觸發器 if (Physics.SphereCast(crouchRay, m_Capsule.radius * k_Half, crouchRayLength, Physics.AllLayers, QueryTriggerInteraction.Ignore)) { m_Crouching = true;//下蹲 } } } /// <summary> /// 用來更新動畫狀態機裡的值 /// </summary> /// <param name="move">移動引數</param> void UpdateAnimator(Vector3 move) { //更新動畫引數 m_Animator.SetFloat("Forward", m_ForwardAmount, 0.1f, Time.deltaTime); m_Animator.SetFloat("Turn", m_TurnAmount, 0.1f, Time.deltaTime); m_Animator.SetBool("Crouch", m_Crouching); m_Animator.SetBool("OnGround", m_IsGrounded); if (!m_IsGrounded) { m_Animator.SetFloat("Jump", m_Rigidbody.velocity.y); } // 計算哪隻腳是在後面的,所以可以判斷跳躍動畫中哪隻腳先離開地面 // 這裡的程式碼依賴於特殊的跑步迴圈,假設某隻腳會在未來的0到0.5秒內超越另一隻腳 float runCycle = Mathf.Repeat(//獲取當前是在哪個腳,Repeat相當於取模 m_Animator.GetCurrentAnimatorStateInfo(0).normalizedTime + m_RunCycleLegOffset, 1); float jumpLeg = (runCycle < k_Half ? 1 : -1) * m_ForwardAmount; if (m_IsGrounded) { m_Animator.SetFloat("JumpLeg", jumpLeg); } // 這邊的方法允許我們在inspector檢視中調整動畫的速率,他會因為根運動影響移動的速度 if (m_IsGrounded && move.magnitude > 0) { m_Animator.speed = m_AnimSpeedMultiplier; } else { // 在空中的時候不用 m_Animator.speed = 1; } } /// <summary> /// 空中的處理,注意,空中跳躍和下蹲時不起作用的 /// </summary> void HandleAirborneMovement() { //根據乘子引用額外的重力 Vector3 extraGravityForce = (Physics.gravity * m_GravityMultiplier) - Physics.gravity; m_Rigidbody.AddForce(extraGravityForce); m_GroundCheckDistance = m_Rigidbody.velocity.y < 0 ? m_OrigGroundCheckDistance : 0.01f;//上升的時候不判斷是否在地面上 } /// <summary> /// 跳躍方法,檢測是否能跳 /// </summary> /// <param name="crouch">是否蹲伏</param> /// <param name="jump">是否跳躍</param> void HandleGroundedMovement(bool crouch, bool jump) { //檢查是否允許跳條件是正確的 //m_Animator.GetCurrentAnimatorStateInfo(0)獲取當前動畫狀態資訊 if (jump && !crouch && m_Animator.GetCurrentAnimatorStateInfo(0).IsName("Grounded")) { // 跳! m_Rigidbody.velocity = new Vector3(m_Rigidbody.velocity.x, m_JumpPower, m_Rigidbody.velocity.z);// //儲存x、z軸速度,並給以y軸向上的速度 m_IsGrounded = false;//是否跳躍為false m_Animator.applyRootMotion = false;//設定動畫不影響實際位置 m_GroundCheckDistance = 0.1f;//檢測地面距離0.1 print(m_GroundCheckDistance); } } /// <summary> /// 幫助角色快速轉向,這是動畫中根旋轉的附加項 /// </summary> void ApplyExtraTurnRotation() { //幫助這個角色將更快(這是除了根旋轉的動畫) // 幫助角色快速轉向,這是動畫中根旋轉的附加項 float turnSpeed = Mathf.Lerp(m_StationaryTurnSpeed, m_MovingTurnSpeed, m_ForwardAmount); transform.Rotate(0, m_TurnAmount * turnSpeed * Time.deltaTime, 0);//轉向. } /// <summary> /// 這個方法來覆蓋預設的根運動,這個方法允許我們移除位置的速度 /// </summary> public void OnAnimatorMove() { // 我們實現了使用這個方法來代替基礎的移動,這個方法允許我們移除位置的速度 //我們實現這個函式來覆蓋預設的根運動。 //這允許我們修改之前位置速度的應用。 if (m_IsGrounded && Time.deltaTime > 0) { Vector3 v = (m_Animator.deltaPosition * m_MoveSpeedMultiplier) / Time.deltaTime; // 保護一下y軸的移動速度 v.y = m_Rigidbody.velocity.y; m_Rigidbody.velocity = v; } } /// <summary> /// 檢測是否離開地面 /// </summary> void CheckGroundStatus() { RaycastHit hitInfo; #if UNITY_EDITOR // 用來在場景檢視中輔助想象地面檢查射線 //在場景中顯示地面檢查線,從腳上0.1米處往下射m_GroundCheckDistance的距離,預製體預設是0.3 Debug.DrawLine(transform.position + (Vector3.up * 0.1f), transform.position + (Vector3.up * 0.1f) + (Vector3.down * m_GroundCheckDistance)); #endif // 0.1的射線是比較小的,基礎包中預製體所設定的0.3是比較好的 if (Physics.Raycast(transform.position + (Vector3.up * 0.1f), Vector3.down, out hitInfo, m_GroundCheckDistance)) {//射到了,儲存法向量,改變變數,將動畫的applyRootMotion置為true,true的含義是應用骨骼節點的位移 //就是說動畫的運動會對實際角色座標產生影響,用於精確的播放動畫 m_GroundNormal = hitInfo.normal;//將射線觸碰到的物體的法向量賦值給m_GroundNormal m_IsGrounded = true;//是否在地面上 m_Animator.applyRootMotion = true;//動畫影響實際位置 } else { m_IsGrounded = false;//是否在地面上為false m_GroundNormal = Vector3.up; m_Animator.applyRootMotion = false;//動畫不影響實際位置,不過我感覺這邊不設為false也是可以的 } } } }