4-17 Wandering NPCs
前一章我們完成了簡單的NPC系統,但是NPC在場景中亂跑,這樣的感覺很奇怪對吧?所以本章要教大家製作走路的NPC。
首先,請大家看看NPC使用的ThirdPersonAnimatorControl。
在Animator視窗點擊Grounded狀態。
從Inspector視窗會看見Motion的部分使用的Blend Tree,請點兩下開啟。
開啟後會看見如下圖的視窗,當紅點在中央下方時,角色的狀態是不會動的Idle狀態。
當紅點調整到最上面,就會發現角色開始跑起來了。
如果將紅點慢慢移到下方,角色的動作又會變得緩慢,像是在走路一樣。
所以,根據以上介紹的機制,透過調整Forward數值,可以控制角色的動作是偏向走路或者跑步。程式碼中在Character.cs中的UpdateAnimator方法,於forwardAmount中乘以一個參數,名為animatorForwardCap,這個參數會設為SerializeField。
修改後的Character.cs如下:
修改好程式碼後,大家可以參考下圖的參數進行Character的設定,我將Animator Forward Cpa設為0.3,Move Speed Multiplier設為0.3,Moving Turn Speed跟Stationary Turn Speed設為100。
執行遊戲,NPC成功在巡邏點上緩慢地行走嘍!
首先,請大家看看NPC使用的ThirdPersonAnimatorControl。
在Animator視窗點擊Grounded狀態。
從Inspector視窗會看見Motion的部分使用的Blend Tree,請點兩下開啟。
開啟後會看見如下圖的視窗,當紅點在中央下方時,角色的狀態是不會動的Idle狀態。
當紅點調整到最上面,就會發現角色開始跑起來了。
如果將紅點慢慢移到下方,角色的動作又會變得緩慢,像是在走路一樣。
所以,根據以上介紹的機制,透過調整Forward數值,可以控制角色的動作是偏向走路或者跑步。程式碼中在Character.cs中的UpdateAnimator方法,於forwardAmount中乘以一個參數,名為animatorForwardCap,這個參數會設為SerializeField。
修改後的Character.cs如下:
using System;
using UnityEngine;
using UnityEngine.AI;
using RPG.CameraUI;
namespace RPG.Character{
[SelectionBase]
public class Character : MonoBehaviour
{
[Header("Audio")]
[Range(0.0f, 1.0f)][SerializeField] float spatialBlend = 1f;
[Header("Capsule Collider")]
[SerializeField] Vector3 colliderCenter = new Vector3(0, 1, 0);
[SerializeField] float colliderRadius = 0.3f;
[SerializeField] float colliderHeight = 1.5f;
[Header("Animator")]
[SerializeField] RuntimeAnimatorController animatorController;
[SerializeField] AnimatorOverrideController animatorOverrideController;
[SerializeField] Avatar avatar;
[Range(0.0f, 1.0f)][SerializeField] float animatorForwardCap = 1f;
[Header("Movement")]
[SerializeField] float moveSpeedMultiplier = 1f;
[SerializeField] float animationSpeedMultiplier = 1f;
[SerializeField] float movingTurnSpeed = 1800;
[SerializeField] float stationaryTurnSpeed = 1800;
[SerializeField] float moveThreshold = 1f;
[Header("Nav Mesh Agent")]
[SerializeField] float steeringSpeed = 1.0f;
NavMeshAgent navMeshAgent = null;
Animator animator = null;
Rigidbody myRigidbody = null;
float turnAmount;
float forwardAmount;
bool isAlive = true;
void Awake(){
AddRequiredComponents ();
}
private void AddRequiredComponents(){
CapsuleCollider capsuleCollider = gameObject.AddComponent ();
capsuleCollider.center = colliderCenter;
capsuleCollider.radius = colliderRadius;
capsuleCollider.height = colliderHeight;
myRigidbody = gameObject.AddComponent ();
myRigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous;
myRigidbody.constraints = RigidbodyConstraints.FreezeRotation;
AudioSource audioSource = gameObject.AddComponent ();
audioSource.spatialBlend = spatialBlend;
animator = gameObject.AddComponent ();
animator.runtimeAnimatorController = animatorController;
animator.avatar = avatar;
navMeshAgent = gameObject.AddComponent ();
navMeshAgent.updateRotation = false;
navMeshAgent.updatePosition = true;
navMeshAgent.speed = steeringSpeed;
}
void Update(){
if (navMeshAgent.remainingDistance > navMeshAgent.stoppingDistance && isAlive) {
Move(navMeshAgent.desiredVelocity);
} else {
Move (Vector3.zero);
}
}
void Move(Vector3 movement)
{
SetForwardAndTurn (movement);
ApplyExtraTurnRotation();
UpdateAnimator();
}
public float GetAnimSpeedMultiplier(){
return animator.speed;
}
public void Kill(){
isAlive = false;
}
public void SetDestination(Vector3 worldPos){
navMeshAgent.SetDestination(worldPos);
}
public void SetStopDistance(float stoppingDistance){
navMeshAgent.stoppingDistance = stoppingDistance;
}
public AnimatorOverrideController GetOverrideController(){
return animatorOverrideController;
}
private void SetForwardAndTurn(Vector3 movement){
// 將Wolrd相對的向量轉換成Local相對的向量
if (movement.magnitude > moveThreshold) {
movement.Normalize ();
}
// 將Worldspace的方向轉換成LocalSpace
Vector3 localMovement = transform.InverseTransformDirection(movement);
turnAmount = Mathf.Atan2(localMovement.x, localMovement.z);
forwardAmount = localMovement.z;
}
void UpdateAnimator()
{
// update the animator parameters
animator.SetFloat("Forward", forwardAmount * animatorForwardCap, 0.1f, Time.deltaTime);
animator.SetFloat("Turn", turnAmount, 0.1f, Time.deltaTime);
animator.speed = animationSpeedMultiplier;
}
void ApplyExtraTurnRotation()
{
// help the character turn faster (this is in addition to root rotation in the animation)
float turnSpeed = Mathf.Lerp(stationaryTurnSpeed, movingTurnSpeed, forwardAmount);
transform.Rotate(0, turnAmount * turnSpeed * Time.deltaTime, 0);
}
void OnAnimatorMove(){
if (Time.deltaTime > 0) {
Vector3 velocity = (animator.deltaPosition * moveSpeedMultiplier) / Time.deltaTime;
// 保持y軸的速度不變
velocity.y = myRigidbody.velocity.y;
myRigidbody.velocity = velocity;
}
}
}
}
修改好程式碼後,大家可以參考下圖的參數進行Character的設定,我將Animator Forward Cpa設為0.3,Move Speed Multiplier設為0.3,Moving Turn Speed跟Stationary Turn Speed設為100。










留言
張貼留言