4-1 From Interface To Inheritance
今天要來重構技能系統,目前的設計有太多重複的程式碼,每當要新增不同的技能時,都要複製『播放粒子系統』、『播放音效』等同樣的程式碼,今天要將重複的部分都移動父類別。如對本章重構內容有不懂之處,請先看看以前的文章唷:
3-9 Special Abilities System Overview
首先,將ISpecialAbility.cs刪除。
將MonoBehaviour跟ISpecialAbility都刪掉,統一繼承AbilityBehaviour。
在AbilityConfig中的AddComponent方法回傳ISpecialAbility,請改成AbilityBehaviour。其餘有繼承AbilityConfig的類別中,同樣實作AddComponent方法的回傳值也必須改成AbilityBehaviour。
接著,提供AbilityBehaviour的程式碼:
記得在PowerAttackBehaviour、AreaEffectBehaviour、SelfHealBehaviour中有使用Use的地方,都要用Override。
以下提供有修改到的程式碼,大家應該會發現比之前的還要簡潔很多。改完以後,請大家自行測試看看是否還能正常發動技能唷!
PowerAttackBehaviour.cs:
AreaEffectBehaviour.cs:
SelfHealBehaviour.cs:
3-9 Special Abilities System Overview
3-10 Create An Area Of Effect Ability
3-18 Spawning Particles At Runtime
3-19 Self Heal Special Ability Challenge
3-20 Triggering Special Abilities From Keys
3-21 Add An Effect Audio On Special Abilities
3-24 Finishing The AOE And Self Heal Particle Effect
將SpecialAbilityConfig.cs修改成AbilityConfig.cs。
原本寫在 SpecialAbilityConfig裡面的Class名稱,也記得要一併修改成AbilityConfig唷!用Refactor工具一併修改名稱吧。
接著,新增Script,名稱為AbilityBehaviour。我們會將播放粒子系統跟播放技能音效的方法都統一寫在AbilityBehaviour,讓繼承AbilityBehaviour的Class直接使用。
改完以後,大家應該會發現原本繼承ISpecialAbility的地方提示紅字。
將MonoBehaviour跟ISpecialAbility都刪掉,統一繼承AbilityBehaviour。
在AbilityConfig中的AddComponent方法回傳ISpecialAbility,請改成AbilityBehaviour。其餘有繼承AbilityConfig的類別中,同樣實作AddComponent方法的回傳值也必須改成AbilityBehaviour。
接著,提供AbilityBehaviour的程式碼:
using System.Collections; using System.Collections.Generic; using UnityEngine; namespace RPG.Character{ public abstract class AbilityBehaviour : MonoBehaviour { protected AbilityConfig config; const float PARTICLE_CLEAN_UP_DELAY = 20f; public abstract void Use(AbilityParams useParams); public void SetConfig(AbilityConfig config){ this.config = config; } protected void PlayEffectAudio(){ // 取得音效Component AudioSource myAudioSource = GetComponent(); // 從config取得技能音效 AudioClip audioClip = config.GetAudioClip (); // 使用PlayOnShot播放技能音效,可避免和其他技能音效重疊在一起 myAudioSource.PlayOneShot (audioClip); } protected void PlayParticleEffect(){ // 初始化粒子系統,綁定到Player身上 GameObject particlePrefab = Instantiate(config.GetParticlePrefab(), transform.position, Quaternion.identity, transform); // 啟動粒子系統 particlePrefab.GetComponent ().Play(); // 播放完後自我銷毀 StartCoroutine(DestroyParticleWhenFinished(particlePrefab)); } protected void PlayParticleEffectOnTarget(AbilityParams useParams){ // 初始化粒子系統,綁定到Enemy身上 GameObject particlePrefab = Instantiate(config.GetParticlePrefab(), useParams.target.transform.position, Quaternion.identity, useParams.target.transform); // 啟動粒子系統 particlePrefab.GetComponent ().Play(); // 播放完後自我銷毀 StartCoroutine(DestroyParticleWhenFinished(particlePrefab)); } IEnumerator DestroyParticleWhenFinished(GameObject particlePrefab){ while (particlePrefab.GetComponent ().isPlaying) { yield return new WaitForSeconds (PARTICLE_CLEAN_UP_DELAY); } Destroy (particlePrefab); yield return new WaitForEndOfFrame (); } } }
記得在PowerAttackBehaviour、AreaEffectBehaviour、SelfHealBehaviour中有使用Use的地方,都要用Override。
以下提供有修改到的程式碼,大家應該會發現比之前的還要簡潔很多。改完以後,請大家自行測試看看是否還能正常發動技能唷!
PowerAttackBehaviour.cs:
using System.Collections; using System.Collections.Generic; using UnityEngine; namespace RPG.Character{ public class PowerAttackBehaviour : AbilityBehaviour{ public override void Use(AbilityParams useParams){ DealDamage (useParams); PlayParticleEffectOnTarget (useParams); PlayEffectAudio (); } private void DealDamage(AbilityParams useParams){ float damageToDeal = (config as PowerAttackConfig).GetExtraDamage () + useParams.baseDamage; useParams.target.TakeDamage (damageToDeal); } } }
AreaEffectBehaviour.cs:
using System.Collections; using System.Collections.Generic; using UnityEngine; using RPG.Core; namespace RPG.Character{ public class AreaEffectBehaviour : AbilityBehaviour{ public override void Use(AbilityParams useParams){ DealRadiaDamage (useParams); PlayParticleEffect (); PlayEffectAudio (); } private void DealRadiaDamage(AbilityParams useParams){ RaycastHit[] hits = Physics.SphereCastAll ( transform.position, // 以Player為中心點 (config as AreaEffectConfig).GetRadius (), // 掃描的球體半徑範圍 Vector3.up, // 掃描的方向不重要,因為我們發動的是Player為原點的範圍技 (config as AreaEffectConfig).GetRadius () // 掃描距離設定成跟掃描半徑一樣即可 ); foreach (RaycastHit hit in hits) { Enemy enemy = hit.collider.gameObject.GetComponent(); if (enemy != null) { float damageToDeal = useParams.baseDamage + (config as AreaEffectConfig).GetDamageToEachTarget (); enemy.TakeDamage (damageToDeal); } } } } }
SelfHealBehaviour.cs:
using System.Collections; using System.Collections.Generic; using UnityEngine; using RPG.Core; namespace RPG.Character{ public class SelfHealBehaviour : AbilityBehaviour { Player player; void Start(){ player = GetComponent(); } public override void Use(AbilityParams useParams){ player.Heal ((config as SelfHealConfig).GetExtraHealth ()); PlayParticleEffect (); PlayEffectAudio (); } } }
留言
張貼留言