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底下自動新增組件。







留言
張貼留言