4-10 A SetDestination Movement API
這次重構要將原先撰寫滑鼠點擊地板跟點擊敵人的功能從Character移出去,改放到PlayerMovement裡面。因為往後要讓Enemy也使用Character,所以Player專用的功能必須移走,讓Character更單純化。
首先,請大家先將Player改名成PlayerMovement。
改名會導致原本在Player中的組件消失,出現Nothing Selected,記得刪除。
並接著將PlayerMovement新增回來。
如下圖,本次重構主要針對OnMouseOverEnemy跟OnMouseOverWalkable這兩個方法,由於Character類別要給Enemy使用,所以這兩個給Player專用的方法便須變更到PlayerMovement中。
以下提供PlayerMovement跟Character的完整程式碼:
PlayerMovement:
Character:
改完後,請記得檢查Player Movement的參數是否有缺失,並執行遊戲看看,是否能讓角色移動。
首先,請大家先將Player改名成PlayerMovement。
改名會導致原本在Player中的組件消失,出現Nothing Selected,記得刪除。
並接著將PlayerMovement新增回來。
如下圖,本次重構主要針對OnMouseOverEnemy跟OnMouseOverWalkable這兩個方法,由於Character類別要給Enemy使用,所以這兩個給Player專用的方法便須變更到PlayerMovement中。
以下提供PlayerMovement跟Character的完整程式碼:
PlayerMovement:
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 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Assertions; using UnityEngine.SceneManagement; using RPG.CameraUI; using RPG.Core; using RPG.Weapons; namespace RPG.Character{ public class PlayerMovement : MonoBehaviour { [SerializeField] float walkMoveStopRadius = 0.2f; [SerializeField] float attackMoveStopRadius = 5f; [SerializeField] float baseDamage = 50f; [SerializeField] AnimatorOverrideController animatorOverrideController; [SerializeField] Weapon currentWeaponConfig; [Range(.1f, 1.0f)] [SerializeField] float criticalHitChance = 0.1f; [SerializeField] float criticalHitMultiplier = 2.5f; [SerializeField] ParticleSystem criticalHitParticle; // 定義Trigger的字串常數 const string ATTACK_TRIGGER = "Attack" ; const string DEFAULT_ATTACK = "DEFAULT ATTACK" ; float lastHitTime; float currentHealthPoint; Enemy currentEnemy; Character character; Animator animator; SpecialAbilities abilities; GameObject weaponInUse; void Start(){ abilities = GetComponent<specialabilities> (); character = GetComponent<character> (); RegisterForMouseClcik (); PutWeaponInHand (currentWeaponConfig); OverrideAnimatorController (); } void Update(){ float healthAsPercentage = GetComponent<healthsystem> ().healthAsPercentage; if (healthAsPercentage > Mathf.Epsilon) { ScanForAbilityKeyDown (); } } private void ScanForAbilityKeyDown(){ for ( int keyIndex = 1; keyIndex <= abilities.GetNumberOfAbilities(); keyIndex++) { if (Input.GetKeyDown (keyIndex.ToString())) { if (currentEnemy) { abilities.AttemptSpecialAbility (keyIndex, currentEnemy.gameObject); } else { abilities.AttemptSpecialAbility (keyIndex); } } } } private void OverrideAnimatorController(){ // 取得Animator物件 animator = GetComponent<animator> (); // 將人物的Animator取代為我們剛剛新增的Player Animator Override animator.runtimeAnimatorController = animatorOverrideController; // Player Animator Override中有一預設的DEFAULT ATTACK,將其取代成Weapon物件中的Animation // 亦即人物的Animation將會依據不同的Weapon物件,而使用有不同的動畫 animatorOverrideController [DEFAULT_ATTACK] = currentWeaponConfig.GetAnimClip (); } public void PutWeaponInHand(Weapon weaponToUse){ currentWeaponConfig = weaponToUse; // 先移除之前手持的武器 Destroy(weaponInUse); GameObject weaponPrefab = weaponToUse.GetWeaponPrefab (); // 生成武器時,將會放置到指定的GameObject之下,此處為WeaponSocket之下 // 故可將WeaponSocket設置成玩家的右手或左手 GameObject weaponSocket = RequestDominantHand(); weaponInUse = Instantiate (weaponPrefab, weaponSocket.transform); // 將武器放置到正確的位置,並有正確的方向 weaponInUse.transform.localPosition = currentWeaponConfig.gripTransform.localPosition; weaponInUse.transform.localRotation = currentWeaponConfig.gripTransform.localRotation; } private GameObject RequestDominantHand(){ // 從Children中尋找包含DominantHand的物件 DominantHand[] dominantHand = GetComponentsInChildren<dominanthand> (); // 計算取得有DominantHand的物件的數量 int numberOfDominantHands = dominantHand.Length; // 確保該數量不為0 Assert.AreNotEqual (numberOfDominantHands, 0, "No DominantHand found on player, please add one" ); // 確保該數量不要大於1 Assert.IsFalse (numberOfDominantHands > 1, "Multiple Dominant script on Player, please remove one" ); // 傳回屬於該DominantHand的GameObject return dominantHand [0].gameObject; } private void RegisterForMouseClcik(){ CameraRaycaster cameraRaycaster = FindObjectOfType<cameraraycaster> (); cameraRaycaster.onMouseOverEnemy += OnMouseOverEnemy; cameraRaycaster.onMouseOverWalkable += OnMouseOverWalkable; } void OnMouseOverEnemy(Enemy enemy){ currentEnemy = enemy; if (Input.GetMouseButton (0)) { // 設置地點為Enemy的位置 character.SetDestination(enemy.transform.position); character.SetStopDistance(attackMoveStopRadius); if (IsTargetInRange (enemy.gameObject)) { AttackTarget (); } } else if (Input.GetMouseButtonDown (1)) { // 0為使用第一個技能 abilities.AttemptSpecialAbility(0, currentEnemy.gameObject); } } void OnMouseOverWalkable(Vector3 destination){ if (Input.GetMouseButton (0)) { // 設置地點為滑鼠點擊的位置 character.SetDestination(destination); character.SetStopDistance (walkMoveStopRadius); } } private void AttackTarget(){ // 確認本次攻擊時間離上一次攻擊時間須大於minTimeBetweenHits,相當於技能冷卻時間 if ( (Time.time - lastHitTime > currentWeaponConfig.GetMinTimeBetweenHits())) { // 每次攻擊前都重設武器的攻擊動畫 OverrideAnimatorController(); // 呼叫Trigger啟動攻擊動畫 animator.SetTrigger(ATTACK_TRIGGER); // 呼叫攻擊Target的方法 currentEnemy.GetComponent<healthsystem>().TakeDamage(CalculateDamage()); // 紀錄目前攻擊的時間 lastHitTime = Time.time; } } private float CalculateDamage(){ // 產生隨機值,比較該值是否小於致命一擊的機率 bool isCriticalHit = Random.Range (0f, 1f) <= criticalHitChance; float damageBeforeCritical = baseDamage + currentWeaponConfig.GetAdditionalDamage (); if (isCriticalHit) { // 播放致命一擊的特效 criticalHitParticle.Play (); // 回傳攻擊力乘上致命一擊的乘率 return damageBeforeCritical * criticalHitMultiplier; } else { return damageBeforeCritical; } } // 確認敵人是否在範圍技的攻擊範圍內 private bool IsTargetInRange(GameObject target){ float distanceToTarget = (target.transform.position - transform.position).magnitude; return distanceToTarget <= currentWeaponConfig.GetMaxAttackRange(); } void OnDrawGizmos(){ //繪製攻擊範圍 Gizmos.color = new Color(255f, 0f, 0f, 0.5f); Gizmos.DrawWireSphere (transform.position, attackMoveStopRadius); } } } </healthsystem></cameraraycaster></dominanthand></animator></healthsystem></character></specialabilities> |
Character:
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 | 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 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 void Kill(){ // TODO 需要禁止角色移動 isAlive = false ; } public void SetDestination(Vector3 worldPos){ navMeshAgent.SetDestination(worldPos); } public void SetStopDistance( float stoppingDistance){ navMeshAgent.stoppingDistance = stoppingDistance; } 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 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> |
改完後,請記得檢查Player Movement的參數是否有缺失,並執行遊戲看看,是否能讓角色移動。
留言
張貼留言