Related Posts Plugin for WordPress, Blogger...

4-9 Having Character Build Themselves

這次重構要來修剪Player擁有太多的Component,概念是為了讓遊戲設計師方便調整Player的參數。畢竟Player擁有的Component有Nav Mesh Agent、Capsule Collider、Rigidbody...等,且這些Component又都有自帶的參數需要設定,某些設定不需要開放讓設計師調整。

所以本次要讓Character.cs產生大多數的Component。首先將CharacterMovement改名為Character。

在Player新增Character。

以下提供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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
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;
 
  [Header("Movement")]
  [SerializeField] float walkMoveStopRadius = 0.2f;
  [SerializeField] float attackMoveStopRadius = 5f;
  [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;
 
  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 Start()
  {
   CameraRaycaster cameraRaycaster = Camera.main.GetComponent<cameraraycaster>();
   cameraRaycaster.onMouseOverWalkable += OnMouseOverWalkable;
   cameraRaycaster.onMouseOverEnemy += OnMouseOverEnemy;
  }
 
  void Update(){
   if (navMeshAgent.remainingDistance > navMeshAgent.stoppingDistance) {
    Move(navMeshAgent.desiredVelocity);
   } else {
    Move (Vector3.zero);
   }
  }
 
  public void Move(Vector3 movement)
  {
   SetForwardAndTurn (movement);
   ApplyExtraTurnRotation();
   UpdateAnimator();
  }
 
  public void Kill(){
   // TODO 需要禁止角色移動
  }
 
  public void SetDestinaition(Vector3 worldPos){
   navMeshAgent.SetDestination = worldPos;
  }
 
  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, 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 OnMouseOverWalkable(Vector3 destination){
   if (Input.GetMouseButton (0)) {
    // 設置地點為滑鼠點擊的位置
    navMeshAgent.SetDestination(destination);
    navMeshAgent.stoppingDistance = walkMoveStopRadius;
   }
  }
 
  void OnMouseOverEnemy(Enemy enemy){
   if (Input.GetMouseButton (0)) {
    // 設置地點為Enemy的位置
    navMeshAgent.SetDestination(enemy.transform.position);
    navMeshAgent.stoppingDistance = attackMoveStopRadius;
   }
  }
 
  void OnAnimatorMove(){
   if (Time.deltaTime > 0) {
    Vector3 velocity = (animator.deltaPosition * moveSpeedMultiplier) / Time.deltaTime;
    // 保持y軸的速度不變
    velocity.y = myRigidbody.velocity.y;
    myRigidbody.velocity = velocity;
   }
  }
 
  void OnDrawGizmos(){
   //繪製攻擊範圍
   Gizmos.color = new Color(255f, 0f, 0f, 0.5f);
   Gizmos.DrawWireSphere (transform.position, attackMoveStopRadius);
  }
 }
}
</cameraraycaster></navmeshagent></animator></audiosource></rigidbody></capsulecollider>

修改完成後,便可以將Player其他的組件都刪除啦!如下圖這樣刪除Component。


最後讓Player保持乾淨,只剩下Transform跟我們撰寫的Script。後面的重構會再將Movement的部分移出來,往後不管是Player、Enemy、NPC等角色,只要依據需求引入我們撰寫的Script即可,比方說NPC不需要技能也不需要血量,就引入Character、NPCAI、Movement。Enemy也可以視需求選擇引入Special Abilities等等。

所以,將這些功能解構出來的好處顯而易見!

Character打開後的屬性設定視窗如下圖。

執行遊戲以後,Character便會在Player底下自動新增組件。

留言