各位看官老爷们,这里是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

看官有何见解,有何指点,欢迎留言,也欢迎私聊~

Unity2D项目-平台、解谜、战斗! 1.2战斗组件Defence、Attack的更多相关文章

  1. Unity2D项目-平台、解谜、战斗! 0.1 序言:团队、项目提出、初步设计、剧情大纲

    各位看官老爷们,这里是RuaiRuai工作室(以下简称RR社),一个做单机游戏的兴趣作坊. 本文跟大家聊一下社团内第一个游戏项目.算是从萌新项目组长的角度,从第一个里程碑的结点处,往前看总结一下项目之 ...

  2. Unity2D项目-平台、解谜、战斗! 1.3移动组件

    各位看官老爷们,这里是RuaiRuai工作室,一个做单机游戏的兴趣作坊. 在这一篇中,我们将会自顶向下地讨论本2D游戏中主角不可或缺的一个功能--移动控制. 首先我们简单分析一下2D游戏中主角与移动相 ...

  3. Unity2D项目-平台、解谜、战斗! 1.1战斗底层组件CanFight-CanBeFighted

    各位看官老爷们,这里是RuaiRuai工作室,一个做单机游戏的兴趣作坊. 本文对该2D项目中战斗底层组件的开发及设计思路做一个总结,希望各路同行多多交流,各路大佬多多指点. 实例特征分析 首先对于各个 ...

  4. Unity2D项目-平台、解谜、战斗! 0.2 序言:团队在线协作方案、基线控制

    各位看官老爷们,这里是RuaiRuai工作室,一个做单机游戏的兴趣作坊. 本文跟大家聊一下笔者团队中所使用的在线协作的诸多工具,以及使用这些工具的目的和所记录的内容,希望这些内容在大家团队工作中有所帮 ...

  5. Unity2D项目-平台、解谜、战斗! 1.5 Player框架、技能管理组件

    各位看官老爷们,这里是RuaiRuai工作室,一个做单机游戏的兴趣作坊. 前文提到,凡是有"攻击"语义的对象,在游戏中,我们给予其一个"CanFight"组件予 ...

  6. eclipse里面构建maven项目详解(转载)

    本文来源于:http://my.oschina.net/u/1540325/blog/548530 eclipse里面构建maven项目详解 1       环境安装及分配 Maven是基于项目对象模 ...

  7. [转帖](整理)GNU Hurd项目详解

    (整理)GNU Hurd项目详解 http://www.ha97.com/3188.html 发表于: 开源世界 | 作者: 博客教主 标签: GNU,Hurd,详解,项目 Hurd原本是要成为GNU ...

  8. 这些.NET开源项目你知道吗?.NET平台开源文档与报表处理组件集合(三)

    在前2篇文章这些.NET开源项目你知道吗?让.NET开源来得更加猛烈些吧 和这些.NET开源项目你知道吗?让.NET开源来得更加猛烈些吧!(第二辑)中,大伙热情高涨.再次拿出自己的私货,在.NET平台 ...

  9. .NET平台开源项目速览(8)Expression Evaluator表达式计算组件使用

    在文章:这些.NET开源项目你知道吗?让.NET开源来得更加猛烈些吧!(第二辑)中,给大家初步介绍了一下Expression Evaluator验证组件.那里只是概述了一下,并没有对其使用和强大功能做 ...

随机推荐

  1. Nodejs 使用 bcrypt 库加密和验证密码

    bcrypt install λ cnpm i bcrypt -S λ cnpm install --save @types/bcrypt example import * as bcrypt fro ...

  2. 揭秘高倍矿币 Baccarat BGV,为何NGK DeFi的财富效应如此神奇?

    作为区块链4.0代表的NGK公链,这次也将借助它自己的DeFi版块NGK Baccarat,开启属于它自己的千倍财富之旅. 如果说,比特币能让没有银行账户的人,可以在全球任何时间.地点都能自由进行交易 ...

  3. 教你玩转CSS border(边框)

    边框样式 边框样式属性指定要显示什么样的边界. border-style属性用来定义边框的样式 border-style的值 代码演示: <!DOCTYPE html> <html ...

  4. Unity安卓apk打包过程

    前言:对于Unity开发小白来说,Android打包无疑是个头痛的问题,所以我总结了 Unity安卓APK的打包过程 第一步:下载对应版本的Android Platform 第二步:安装JDK并配置J ...

  5. django学习-17.如何提供一个规范的接口返回值

    目录结构 1.前言 2.进行实际的一个完整流程操作 2.1.第一步:编写一个用于查询用户数据的视图函数 2.2.第二步:编写对应的一个url匹配规则 2.3.第三步:启动django项目[hellow ...

  6. Linux零拷贝技术

    本文转载自Linux零拷贝技术 导语 本文讲解 Linux 的零拷贝技术,云计算是一门很庞大的技术学科,融合了很多技术,Linux 算是比较基础的技术,所以,学好 Linux 对于云计算的学习会有比较 ...

  7. c#初体验

    虚方法.抽象类.接口区别:虚方法:父类可能需要实例化,父类方法需要方法体,可以找到一个父类 抽象类:抽象方法,父类不能实例化,且父类方法不能实现方法体,不可以找出一个父类,需要抽象 接口:多继承 le ...

  8. LDAP启动TLS 完整操作流程

    配置LDAP启动TLS 阅读本文之前,建议初学的小伙伴先看一下上一篇:完整的 LDAP + phpLDAPadmin安装部署流程 (ubuntu18.04) 以下正文: 接下来的操作承接上文,还是在同 ...

  9. DRF 三大认证之身份认证

    目录 路由组件补充 三大认证 一.身份认证 1.如何进行身份认证 2.jwt认证规则原理 3.jwt的组成 4.jwt的使用方法 4.1 签发算法 4.2 校验算法 4.3 刷新算法 二.权限认证 三 ...

  10. 区分函数防抖&函数节流

    1. 概念区分 函数防抖:触发事件后,在n秒内函数只能执行一次,如果触发事件后在n秒内又触发了事件,则会重新计算函数延执行时间. 简单说: 频繁触发, 但只在特定的时间内才执行一次代码,如果特定时间内 ...