Related Posts Plugin for WordPress, Blogger...

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如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
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> ();
   capsuleCollider.center = colliderCenter;
   capsuleCollider.radius = colliderRadius;
   capsuleCollider.height = colliderHeight;
 
   myRigidbody = gameObject.AddComponent<rigidbody> ();
   myRigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous;
   myRigidbody.constraints = RigidbodyConstraints.FreezeRotation;
 
   AudioSource audioSource = gameObject.AddComponent<audiosource> ();
   audioSource.spatialBlend = spatialBlend;
 
   animator = gameObject.AddComponent<animator> ();
   animator.runtimeAnimatorController = animatorController;
   animator.avatar = avatar;
 
   navMeshAgent = gameObject.AddComponent<navmeshagent> ();
   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;
   }
  }
 }
}
</navmeshagent></animator></audiosource></rigidbody></capsulecollider>

修改好程式碼後,大家可以參考下圖的參數進行Character的設定,我將Animator Forward Cpa設為0.3,Move Speed Multiplier設為0.3,Moving Turn Speed跟Stationary Turn Speed設為100。

 執行遊戲,NPC成功在巡邏點上緩慢地行走嘍!

留言