1. 程式人生 > >我的遊戲開發筆記(五):Animator的運用二

我的遊戲開發筆記(五):Animator的運用二

好了好了,上篇說到如何用程式碼來控制Animator裡的條件引數,話不多說,直接開始。

Animator m_Animator;
m_Animator = GetComponent<Animator>();
m_Animator.SetBool("OnGround", true);

很簡單地幾行程式碼就可以給引數賦值了,通過查API我們可以知道SetBool的第一個引數是Animator的引數名字,第二個引數就是給它賦值。同理,要給Forward賦值就要用到SetFloat。

我根據我的需求把我的Animator做成這樣:


Unity有一個自帶的ThirdPersonCharacter.cs來控制Animator,不過要在5.0以上的版本才有。4.X版本要用的話直接複製過來也可以用。一開始看到這個程式碼我真的是一頭霧水,硬著頭皮看下去還是大概看懂了,我根據我自己製作的Animator大概改了一下,然後給出了標註。

using UnityEngine;

namespace UnityStandardAssets.Characters.ThirdPerson
{
	[RequireComponent(typeof(Rigidbody))]
	[RequireComponent(typeof(CapsuleCollider))]
	[RequireComponent(typeof(Animator))]
	public class ThirdPersonCharacter : MonoBehaviour
	{
		[SerializeField] float m_MovingTurnSpeed = 360;
		[SerializeField] float m_StationaryTurnSpeed = 180;
		[SerializeField] float m_JumpPower = 12f;
		[Range(1f, 4f)][SerializeField] float m_GravityMultiplier = 2f;
		[SerializeField] float m_RunCycleLegOffset = 0.2f; //specific to the character in sample assets, will need to be modified to work with others
		[SerializeField] float m_MoveSpeedMultiplier = 1f;
		[SerializeField] float m_AnimSpeedMultiplier = 1f;
		[SerializeField] float m_GroundCheckDistance = 0.1f;

		Rigidbody m_Rigidbody;
		Animator m_Animator;
		bool m_IsGrounded;
		float m_OrigGroundCheckDistance;
		const float k_Half = 0.5f;
		float m_TurnAmount;
		float m_ForwardAmount;
		Vector3 m_GroundNormal;
		float m_CapsuleHeight;
		Vector3 m_CapsuleCenter;
		CapsuleCollider m_Capsule;
		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;

			m_Rigidbody.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationY | RigidbodyConstraints.FreezeRotationZ;
			m_OrigGroundCheckDistance = m_GroundCheckDistance;
		}


		public void Move(Vector3 move, bool crouch, bool jump,bool kick,bool ultimate,bool Throw)
		{

			// convert the world relative moveInput vector into a local-relative
			// turn amount and forward amount required to head in the desired
			// direction.
            //從ThirdPersonUserControl進行呼叫
            //若移動向量的長度>1f,則先把向量標準化
			if (move.magnitude > 1f) move.Normalize();
            //讓向量move變為反向
			move = transform.InverseTransformDirection(move);
            //看是否在地上
			CheckGroundStatus();
            //讓前進反向與所接觸表面的法線形成直角,舉個例子,就是說當你站在地面上時,地面的法線是垂直向上,你的前進反向與它垂直,也就是向前。
			move = Vector3.ProjectOnPlane(move, m_GroundNormal);
            //計算turn的角度,arctan(x/z)
			m_TurnAmount = Mathf.Atan2(move.x, move.z);
            //給m_ForwardAmount賦值,這裡為move的z軸移動向量
			m_ForwardAmount = move.z;

            //執行旋轉
			ApplyExtraTurnRotation();

			// control and velocity handling is different when grounded and airborne:
            //若在地上
			if (m_IsGrounded)
			{
                //能跳,跳
				HandleGroundedMovement(crouch, jump);
			}
			else
			{
                //若不在地上,給角色下落加速度
				HandleAirborneMovement();
			}

            //若角色處於蹲著的狀態
			ScaleCapsuleForCrouching(crouch);
            //若角色處於一個矮屋,此時放手C鍵(蹲下鍵),則讓角色保持一個蹲著的狀態
			PreventStandingInLowHeadroom();

			// send input and other state parameters to the animator
            //更新動作
			UpdateAnimator(move);
		}


		void ScaleCapsuleForCrouching(bool crouch)
		{
            //若角色在地且準備要蹲下
			if (m_IsGrounded && crouch)
			{
                //若現在的狀態就是蹲下,返回
				if (m_Crouching) return;
                //m_Capsule的高度和中心都/2,蹲下模型要變矮
				m_Capsule.height = m_Capsule.height / 2f;
				m_Capsule.center = m_Capsule.center / 2f;
                //狀態改為蹲下
				m_Crouching = true;
			}
			else
			{
                //生成一個ray物件,起點為角色位置+(0,1,0)*m_Capsule.radius * k_Half,方向為向上
				Ray crouchRay = new Ray(m_Rigidbody.position + Vector3.up * m_Capsule.radius * k_Half, Vector3.up);
                //設定ray的長度,長度為 m_CapsuleHeight - m_Capsule.radius * k_Half,腦補一下,就是把Capsule.的上半圓減去
				float crouchRayLength = m_CapsuleHeight - m_Capsule.radius * k_Half;
                //以crouchRay為投射射線,m_Capsule.radius * k_Half為弧度,crouchRayLength為距離,掃描,若掃到東西,則返回真。也就是說,如果頭上有東西,則角色只能蹲著,不能站起來。
				if (Physics.SphereCast(crouchRay, m_Capsule.radius * k_Half, crouchRayLength))
				{
					m_Crouching = true;
					return;
				}
                //若頭上沒東西,則重置m_Capsule.height、m_Capsule.center,意味著現在不是處於蹲狀態。
				m_Capsule.height = m_CapsuleHeight;
				m_Capsule.center = m_CapsuleCenter;
				m_Crouching = false;
			}
		}

		void PreventStandingInLowHeadroom()
		{
			// prevent standing up in crouch-only zones
            //如果不處於蹲著狀態
			if (!m_Crouching)
			{
                //若頭上有東西,則一直蹲著吧。
				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))
				{
					m_Crouching = true;
				}
			}
		}


		void UpdateAnimator(Vector3 move)
		{
			// update the animator parameters
            //以下就是動作了,原裝大概有Forward,Turn,Crouch,OnGround,Jump,JumpLeg這6個引數。
            //SetFloat(name,value,damptime,deltatime),name就是引數名字,value是該引數的新值,damptime是到達value的時間,deltatime就是當前deltatime
			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);
                //Debug.Log(m_Rigidbody.velocity.y);
			}

			// calculate which leg is behind, so as to leave that leg trailing in the jump animation
			// (This code is reliant on the specific run cycle offset in our animations,
			// and assumes one leg passes the other at the normalized clip times of 0.0 and 0.5)
			float runCycle =
				Mathf.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);
			}

			// the anim speed multiplier allows the overall speed of walking/running to be tweaked in the inspector,
			// which affects the movement speed because of the root motion.
			if (m_IsGrounded && move.magnitude > 0)
			{
				m_Animator.speed = m_AnimSpeedMultiplier;
			}
			else
			{
				// don't use that while airborne
				m_Animator.speed = 1;
			}
		}


		void HandleAirborneMovement()
		{
			// apply extra gravity from multiplier:
            //現在在空中,要有加速度,牽引力為系統重力gravity*重力倍數-gravity
			Vector3 extraGravityForce = (Physics.gravity * m_GravityMultiplier) - Physics.gravity;
            //給角色加一個牽引力
			m_Rigidbody.AddForce(extraGravityForce);

            //給m_GroundCheckDistance賦值,若y軸上的力量<0,即代表沒有失重狀態,那麼m_GroundCheckDistance重置;若y軸力量>0,則是失重狀態,人飛起來,m_GroundCheckDistance置為0.01,意思就是不會著地。
			m_GroundCheckDistance = m_Rigidbody.velocity.y < 0 ? m_OrigGroundCheckDistance : 0.01f;
		}


		void HandleGroundedMovement(bool crouch, bool jump)
		{
			// check whether conditions are right to allow a jump:
            //若跳為真蹲為假,看看現在的AnimatorController的state是不是Grounded(在製作AnimatorController時必須要有個名叫Grounded的state才能執行)
            if (jump && !crouch && m_IsGrounded)
			{
				// jump!
                //給角色多加一個向上的力,值為m_JumpPower,可以隨意賦值
                Debug.Log("RunJump!");
				m_Rigidbody.velocity = new Vector3(m_Rigidbody.velocity.x, m_JumpPower, m_Rigidbody.velocity.z);
                //不在地上了
				m_IsGrounded = false;
                //不能執行在地上的動作
				m_Animator.applyRootMotion = false;
                //m_GroundCheckDistance重置為0.1
				m_GroundCheckDistance = 0.1f;
			}
		}

		void ApplyExtraTurnRotation()
		{
			// help the character turn faster (this is in addition to root rotation in the animation)
            //求出旋轉速度,Lerp(from,to,t),基於t返回from到to之間的插值,0<t<1
            //當t=0時,返回from;當t=1時,返回to,當t=0.5時返回平均值,也就是說t越大,越接近to
			float turnSpeed = Mathf.Lerp(m_StationaryTurnSpeed, m_MovingTurnSpeed, m_ForwardAmount);
            //角色旋轉
			transform.Rotate(0, m_TurnAmount * turnSpeed * Time.deltaTime, 0);
		}


		public void OnAnimatorMove()
		{
			// we implement this function to override the default root motion.
			// this allows us to modify the positional speed before it's applied.
			if (m_IsGrounded && Time.deltaTime > 0)
			{
				Vector3 v = (m_Animator.deltaPosition * m_MoveSpeedMultiplier) / Time.deltaTime;

				// we preserve the existing y part of the current velocity.
				v.y = m_Rigidbody.velocity.y;
				m_Rigidbody.velocity = v;
			}
		}


		void CheckGroundStatus()
		{
            //射線
			RaycastHit hitInfo;
#if UNITY_EDITOR
			// helper to visualise the ground check ray in the scene view
            //畫一條線,起點是當前角色的position+0.1*(0,1,0),終點是角色position+0.1*(0,1,0)+m_GroundCheckDistance*(0,-1,0)
			Debug.DrawLine(transform.position + (Vector3.up * 0.1f), transform.position + (Vector3.up * 0.1f) + (Vector3.down * m_GroundCheckDistance));
#endif
			// 0.1f is a small offset to start the ray from inside the character
			// it is also good to note that the transform position in the sample assets is at the base of the character
            //發射射線,起點是...,方向是向下,out的射線hitInfo 即有碰撞發生返回true,長度為m_GroundCheckDistance
			if (Physics.Raycast(transform.position + (Vector3.up * 0.1f), Vector3.down, out hitInfo, m_GroundCheckDistance))
			{
                //當射線發生碰撞,m_GroundNormal等於碰撞表面的發現
				m_GroundNormal = hitInfo.normal;
                //說明此刻在地上
				m_IsGrounded = true;
                //角色可以執行相應運動
				m_Animator.applyRootMotion = true;
			}
			else
			{
                //若射線沒有發生碰撞,所有置為與上面相反
				m_IsGrounded = false;
				m_GroundNormal = Vector3.up;
				m_Animator.applyRootMotion = false;
			}
		}
	}
}

除此之外還有一個ThirdPerControl類,如果說上一個類是用來對角色狀態的定義和改變,那麼這個類就是對角色的控制了。
using System;
using UnityEngine;
//using UnityStandardAssets.CrossPlatformInput;

namespace UnityStandardAssets.Characters.ThirdPerson
{
    [RequireComponent(typeof (ThirdPersonCharacter))]
    public class ThirdPersonUserControl : MonoBehaviour
    {
        private ThirdPersonCharacter m_Character; // A reference to the ThirdPersonCharacter on the object
        private Transform m_Cam;                  // A reference to the main camera in the scenes transform
        private Vector3 m_CamForward;             // The current forward direction of the camera
        private Vector3 m_Move;
        private bool m_Jump;                      // the world-relative desired move direction, calculated from the camForward and user input.
        private bool m_Kick;
        private bool m_Ultimate;
        private bool m_Throw;
        
        private void Start()
        {
            // get the transform of the main camera
            if (Camera.main != null)
            {
                m_Cam = Camera.main.transform;
            }
            else
            {
                Debug.LogWarning(
                    "Warning: no main camera found. Third person character needs a Camera tagged \"MainCamera\", for camera-relative controls.");
                // we use self-relative controls in this case, which probably isn't what the user wants, but hey, we warned them!
            }

            // get the third person character ( this should never be null due to require component )
            m_Character = GetComponent<ThirdPersonCharacter>();
        }


        private void Update()//update是會隨著幀數的變化時間間隔會變化的。
        {
            if (!m_Jump)
            {
                m_Jump = Input.GetButtonDown("Jump");
            }
            if (!m_Kick)
            {
                m_Kick = Input.GetKeyDown(KeyCode.J);
            }
            if (!m_Ultimate)
            {
                m_Ultimate = Input.GetKeyDown(KeyCode.K);
            }
            if (!m_Throw)
            {
                m_Throw = Input.GetKeyDown(KeyCode.I);
            }
        }


        // Fixed update is called in sync with physics
        private void FixedUpdate()//fixedupdate的時間間隔固定,並不會變化
        {
            // read inputs
            //獲得水平以及垂直移動的變數
            float h = Input.GetAxis("Horizontal");
            float v = Input.GetAxis("Vertical");
            //這是蹲下
            bool crouch = Input.GetKey(KeyCode.C);

            // calculate move direction to pass to character
            if (m_Cam != null)
            {
                // calculate camera relative direction to move:
                //對攝像機向前的向量進行縮放,對應的座標進行相乘即可,因為是前進向量,y為0,最後讓向量標準化。
                m_CamForward = Vector3.Scale(m_Cam.forward, new Vector3(1, 0, 1)).normalized;
                //給移動向量m_Move賦值
                m_Move = v*m_CamForward + h*m_Cam.right;
            }
            else
            {
                // we use world-relative directions in the case of no main camera
                m_Move = v*Vector3.forward + h*Vector3.right;
            }
#if !MOBILE_INPUT
			// walk speed multiplier
            //若按住左側上檔鍵,m_Move*0.5
	        if (Input.GetKey(KeyCode.LeftShift)) m_Move *= 0.5f;
#endif

            // pass all parameters to the character control script
            //呼叫ThirdPersonCharacter的Move函式進行移動
            m_Character.Move(m_Move, crouch, m_Jump,m_Kick,m_Throw,m_Ultimate);
            //角色置為沒有跳躍狀態
            m_Jump = false;
        }
    }
}

由於我用的是4.6版本,並沒有UnityStandardAssets.CrossPlatformInput這個類(也許是我不會引用←_←),所以我就把它改成Input了,5.0以上可以按原版使用。

我自己加的動作程式碼還沒有寫全,之後會慢慢完善。

至此,把程式碼拖到wukong上wukong就會按照我的條件改變自己的動作了。這幾天我初步學習Animator就到這裡,以後再慢慢深入學習。