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 ();
}
}
}






留言
張貼留言