好了好了,上篇說到如何用代碼來控制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就到這裡,以後再慢慢深入學習。