4-11 Extracting WeaponSystem
今天的重構要來分離PlayerMovement中的武器設定到WeaponSystem,包含攻擊、致命一擊、更換武器等功能,如此一來,往後的Enemy也能夠裝上武器系統了呢,就能讓Enemy的手中套用武器跟攻擊動作了!
首先,我在Character新增了一個Base Script資料夾,往後不管是Player, Enemy, NPC等角色都能到此資料夾選擇加裝不同的系統。然後請大家新增一個Script名為WeaponSystem。
如下圖為重構完成後的WeaponSystem,大家應該能夠從參數中看出來本次要從PlayerMovement中移動哪些程式碼到WeaponSystem吧,可以先自行試試看唷。
以下提供修改好的PlayerMovement與WeaponSystem程式碼。
PlayerMovement:
WeaponSystem:
修改完成後,讓我們試看看套用到Enemy身上吧。首先我找出Enemy放置武器的位置。
接著在該位置新增Dominant Hand組件。
並替該Enemy新增Weapon System,設定好相關武器跟參數。
首先,我在Character新增了一個Base Script資料夾,往後不管是Player, Enemy, NPC等角色都能到此資料夾選擇加裝不同的系統。然後請大家新增一個Script名為WeaponSystem。
如下圖為重構完成後的WeaponSystem,大家應該能夠從參數中看出來本次要從PlayerMovement中移動哪些程式碼到WeaponSystem吧,可以先自行試試看唷。
以下提供修改好的PlayerMovement與WeaponSystem程式碼。
PlayerMovement:
using System.Collections; using System.Collections.Generic; using UnityEngine; 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; float currentHealthPoint; Enemy currentEnemy; Character character; SpecialAbilities abilities; WeaponSystem weaponSystem; void Start(){ abilities = GetComponent(); character = GetComponent (); weaponSystem = GetComponent (); RegisterForMouseClcik (); } void Update(){ float healthAsPercentage = GetComponent ().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 RegisterForMouseClcik(){ CameraRaycaster cameraRaycaster = FindObjectOfType (); 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)) { weaponSystem.AttackTarget (enemy.gameObject); } } 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 bool IsTargetInRange(GameObject target){ float distanceToTarget = (target.transform.position - transform.position).magnitude; return distanceToTarget <= weaponSystem.GetCurrentWeapon().GetMaxAttackRange(); } void OnDrawGizmos(){ //繪製攻擊範圍 Gizmos.color = new Color(255f, 0f, 0f, 0.5f); Gizmos.DrawWireSphere (transform.position, attackMoveStopRadius); } } }
WeaponSystem:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Assertions; using RPG.Weapons; namespace RPG.Character{ public class WeaponSystem : MonoBehaviour { [SerializeField] float baseDamage = 50f; [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; GameObject weaponInUse; GameObject target; Character character; Animator animator; void Start () { animator = GetComponent(); character = GetComponent (); PutWeaponInHand (currentWeaponConfig); OverrideAnimatorController (); } void Update () { } private void OverrideAnimatorController(){ AnimatorOverrideController animatorOverrideController = character.GetOverrideController (); // 將人物的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; } public void AttackTarget(GameObject targetToAttack){ target = targetToAttack; AttackTarget (); } public Weapon GetCurrentWeapon(){ return currentWeaponConfig; } private GameObject RequestDominantHand(){ // 從Children中尋找包含DominantHand的物件 DominantHand[] dominantHand = GetComponentsInChildren (); // 計算取得有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 AttackTarget(){ // 確認本次攻擊時間離上一次攻擊時間須大於minTimeBetweenHits,相當於技能冷卻時間 if ( (Time.time - lastHitTime > currentWeaponConfig.GetMinTimeBetweenHits())) { // 每次攻擊前都重設武器的攻擊動畫 OverrideAnimatorController(); // 呼叫Trigger啟動攻擊動畫 animator.SetTrigger(ATTACK_TRIGGER); // 呼叫攻擊Target的方法 target.GetComponent ().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; } } } }
修改完成後,讓我們試看看套用到Enemy身上吧。首先我找出Enemy放置武器的位置。
接著在該位置新增Dominant Hand組件。
並替該Enemy新增Weapon System,設定好相關武器跟參數。
執行遊戲看看,哇!連Enemy都拿起鋤頭了!
留言
張貼留言