我的遊戲開發筆記(五):Animator的運用二
阿新 • • 發佈:2019-02-17
好了好了,上篇說到如何用程式碼來控制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就到這裡,以後再慢慢深入學習。