Unity2D项目-平台、解谜、战斗! 1.2战斗组件Defence、Attack
各位看官老爷们,这里是RuaiRuai工作室,一个做单机游戏的兴趣作坊。
接上文,我们定义了两个分别具有“攻击”和"被攻击"语义的组件CanFight和CanBeFighted。对于CanFight我们的设计意图是任何对单个敌人,亦或是范围中的一些敌人进行攻击的函数调用都能够通过这个组件于以实现;对于CanBeFighted,它通过维护一个受击信息的队列,保存任何攻击他的攻击信息,比如攻击的施加者、攻击的伤害值、攻击的类型等等。
很明显,CanFight组件拥有独立存在的语义和逻辑,比如我有一个"地刺"陷阱,只需要将CanFight组件挂在地刺物体上,每一帧调用对范围进行攻击的函数就可以实现地刺的效果,或者在OnXXXEnter中对主角进行针对性的伤害也是可选实现方式。但是,对于CanBeFighted,它只提供了一个受击队列,并提供了一些统计数据的方法,比如求伤害和、求最大伤害值等等,并没有定义游戏中常见的"血量""护甲""死亡"等语义,而这些逻辑都和受击组件密不可分。
所以我的设计初衷是对于不同类型的"可能被攻击到的对象",他们的共同特点是能够被攻击,也就是CanBeFighted所提供的功能。而被攻击后具体作何反应,有何逻辑,都不是底层CanBeFighted组件所关心的。
这就引出了本篇的中心——针对不同类型的对象设计不同的CanBeFighted的上层组件,用来处理不同受击后的逻辑效果。我们姑且叫他Defence组件——所有Defence子类的公共父类。
攻击-防御组件的设计逻辑
由于每次攻击到对象,该对象的CanBeFighted类就会记录下攻击信息,并改变栈顶指针,我们想要实时对于攻击做出反馈,就需要在Update函数中每一帧检测CanBeFighted的受击列表,并对每一个攻击信息做出反馈。所以基本框架差不多是这样的——
受击信息统计与响应逻辑
不同的Defence子类只是在受击响应部分逻辑有所不同而已,比如宝箱——受击后自身消失,并掉落物品,敌人——受击后进入受击状态,并生命值减少。
从设计模式上看,整个框架类似于监听者模式,受击列表或者CanBeFighted组件扮演了事件队列的角色,Defence组件扮演了对事件队列的访问器以及响应函数的角色。
下面,我们给出Defence父类和"主角"两个主要的Defence子类代码,供各位看官参考:
using System.Collections; using System.Collections.Generic; using UnityEngine; //Defence组件需要一个CanBeFighted组件,作为底层操作对象 [RequireComponent(typeof(CanBeFighted))] abstract public class Defence : MonoBehaviour { protected CanBeFighted attackedCheck; protected int hpMax = 5; protected int hp = 0; protected bool isDead = false; //保证该组件被初始化过,否则在调用方法时候报错 private bool hasBeenInitialized = false; //统计相关 protected bool hasSetStatistic = false; //受击次数 protected int attackNum = 0; //毛伤害总和 protected int damageSum = 0; //最大打断类型 protected AttackInterruptType maxInterrupt = AttackInterruptType.NONE; //净伤害总和 protected int realDamage = 0; //血量下降值 protected int hpReduction = 0; protected virtual void Awake() { attackedCheck = GetComponent<CanBeFighted>(); if (attackedCheck == null) { Debug.LogError("在" + gameObject.name + "中,Defence组件没有找到所依赖的CanBeFighted组件"); } } public void SetImmune(bool isImmune) { attackedCheck.SetImmune(isImmune); } public virtual void Initialize(int hpMax) { hasBeenInitialized = true; this.hpMax = hpMax; this.hp = hpMax; } //子类中应该在这个方法中包含你想通过这个组件做到的全部事情,除了clear abstract public void AttackCheck(); public int getHpMax() { return hpMax; } public int getHp() { return hp; } public bool getIsDead() { return isDead; } public int getAttackNum() { return attackNum; } public int getDamageSum() { return damageSum; } public AttackInterruptType getMaxInterrupt() { return maxInterrupt; } public int getRealDamage() { return realDamage; } public int getHpReduction() { return hpReduction; } //将下一帧的hasSetStatistic设置为false,并清除CanBeFighted的数据 public virtual void Clear() { hasSetStatistic = false; attackedCheck.Clear(); } //统计CanBeFighted中的信息,包括统计相关、受击次数、毛伤害总和、最大打断类型 protected virtual void SetStatistic() { hasSetStatistic = true; if(!hasBeenInitialized) { Debug.LogError("在Defence中,没有初始化该组件!"); } AttackInterruptType localMaxInterrupt = AttackInterruptType.NONE; int localDamageSum = 0; if(attackedCheck.hasBeenAttacked()) { foreach (AttackContent attack in attackedCheck.GetAttackedList()) { if ((int)localMaxInterrupt < (int)attack.interruptType) { localMaxInterrupt = attack.interruptType; } localDamageSum += attack.damage; } } maxInterrupt = localMaxInterrupt; damageSum = localDamageSum; attackNum = attackedCheck.getAttackNum(); } //定义了伤害计算方法,通过计算出的realDamage,SetHealthStatus方法对生命值进行扣除,并计算出hpReduction virtual protected void Damage() { if(hasSetStatistic) { realDamage = damageSum; SetHealthStatus(); } else { Debug.LogError("在" + gameObject.name + "中,setHealthStatus发生在了setStatistic之前"); } } //对生命值进行扣除,并计算出hpReduction;如果生命值为0,则isDead设置为true protected virtual void SetHealthStatus() { if (hp < realDamage) { hpReduction = hp; hp = 0; } else { hpReduction = realDamage; hp -= realDamage; } if (hp == 0) { isDead = true; } } }
1 public class DefencePlayer : Defence, ClassSaver 2 { 3 private int recoveredHp = 0; 4 private MovementPlayer movementComponent; 5 6 //挨打无敌参数 7 private float immuneTotalTime = 2f; 8 private float immuneCurTime = 0f; 9 private bool isAttackedImmune = false; 10 11 private AttackAnime attackAnime; 12 protected override void Awake() 13 { 14 base.Awake(); 15 movementComponent = GetComponent<MovementPlayer>(); 16 attackAnime = GetComponent<AttackAnime>(); 17 } 18 19 20 21 22 public void Heal(int healPoint) 23 { 24 //hp += healPoint; 25 //hp = hp > hpMax ? hpMax : hp; 26 if ((hp+healPoint)> hpMax) 27 { 28 recoveredHp = hpMax - hp; 29 hp = hpMax; 30 } 31 else 32 { 33 recoveredHp = healPoint; 34 hp += healPoint; 35 } 36 } 37 38 public override void Clear() 39 { 40 base.Clear(); 41 recoveredHp = 0; 42 } 43 44 //实现了抽象方法,在这个方法中包含你想通过这个组件做到的全部事情,在Player控制脚本中逐帧调用该方法 45 public override void AttackCheck() 46 { 47 SetStatistic(); 48 AttackImmuneCheck(); 49 Damage(); 50 GetStatisticCollector(); 51 } 52 53 public override void Initialize(int hpMax) 54 { 55 base.Initialize(hpMax); 56 } 57 58 //改写父类的伤害计算方法,每次伤害都会减少1点护甲值的伤害。 59 protected override void Damage() 60 { 61 if (hasSetStatistic) 62 { 63 realDamage = damageSum - attackNum * armor; 64 SetHealthStatus(); 65 } 66 else 67 { 68 Debug.LogError("在" + gameObject.name + "中,setHealthStatus发生在了setStatistic之前"); 69 } 70 71 if (realDamage > 0) 72 { 73 Debug.Log("player受到了" + realDamage + "点伤害"); 74 } 75 } 76 77 78 public int getArmor() { return armor; } 79 public int getRecoverdHp() { return recoveredHp; } 80 }
那么今天的分享就是这些,欢迎访问:
整个项目原型github地址:
www.gitHub.com/yunshiyue/elementgame
看官有何见解,有何指点,欢迎留言,也欢迎私聊~