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修改後的程式碼:
修改完成後,便可以將Player其他的組件都刪除啦!如下圖這樣刪除Component。
最後讓Player保持乾淨,只剩下Transform跟我們撰寫的Script。後面的重構會再將Movement的部分移出來,往後不管是Player、Enemy、NPC等角色,只要依據需求引入我們撰寫的Script即可,比方說NPC不需要技能也不需要血量,就引入Character、NPCAI、Movement。Enemy也可以視需求選擇引入Special Abilities等等。
所以,將這些功能解構出來的好處顯而易見!
Character打開後的屬性設定視窗如下圖。
執行遊戲以後,Character便會在Player底下自動新增組件。
所以本次要讓Character.cs產生大多數的Component。首先將CharacterMovement改名為Character。
在Player新增Character。
以下提供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; [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.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 Start() { CameraRaycaster cameraRaycaster = Camera.main.GetComponent (); 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); } } }
修改完成後,便可以將Player其他的組件都刪除啦!如下圖這樣刪除Component。
最後讓Player保持乾淨,只剩下Transform跟我們撰寫的Script。後面的重構會再將Movement的部分移出來,往後不管是Player、Enemy、NPC等角色,只要依據需求引入我們撰寫的Script即可,比方說NPC不需要技能也不需要血量,就引入Character、NPCAI、Movement。Enemy也可以視需求選擇引入Special Abilities等等。
所以,將這些功能解構出來的好處顯而易見!
Character打開後的屬性設定視窗如下圖。
執行遊戲以後,Character便會在Player底下自動新增組件。
留言
張貼留言