各位看官老爷们,这里是RuaiRuai工作室,一个做单机游戏的兴趣作坊。

本文对该2D项目中战斗底层组件的开发及设计思路做一个总结,希望各路同行多多交流,各路大佬多多指点。

实例特征分析

首先对于各个能够参加战斗的实例来说,比如能主动攻击的主角、能够和主角战斗的怪物,都需要一种手段(component in unity)来施加"攻击"这个语义的动作,以及受到来自攻击者的"被攻击"的动作。故笔者初步设计一个组件来表达这两种语义。

接着,一个显然的问题出现了,单一职责原则,战斗组件承担了攻击函数的调用和受伤函数的调用两种逻辑语义,那有没有一种需求,只需要攻击需求而不需要受击需求呢,显然,比如只能挨打的宝箱、只能攻击的机关等等。于是,设计变成了这样:

攻击组件提供对单体伤害、对范围内伤害、击退、施加异常效果、施加元素攻击等基本攻击接口。

受击组件拥有一个记录当前帧所有攻击信息的栈,以供高层组件调用,同时提供受击、以及栈操作等接口。

类图如下

小作坊,意思到了就行2333

这样,在敌人的AI脚本中,或者是主角的技能脚本中,就可以调用相应的函数来执行不同的技能逻辑伤害效果。

最后给出代码:

using UnityEngine;

public class CanFight : MonoBehaviour
{
/// <summary>
/// CanFight类中范围攻击最多一次可以攻击多少个目标
/// </summary>
public const int ENMEIES_MAX_NUM_ONEATTACK = 32;
/// <summary>
/// 检测敌人碰撞体的筛选器,作为Collider2D.OverlapLayer()的第一个参数。具体作用见Unity API。
/// </summary>
private ContactFilter2D filter = new ContactFilter2D(); private bool isInitialized = false; /// <summary>
/// 构造函数,初始化筛选器的配置
/// </summary>
public void Initiailize(string[] layerNames)
{
filter.useNormalAngle = false;
filter.useDepth = false;
filter.useOutsideDepth = false;
filter.useOutsideNormalAngle = false;
filter.useTriggers = false; filter.useLayerMask = true; LayerMask targetLayer = 0;
foreach(string layername in layerNames)
{
targetLayer ^= 1 << LayerMask.NameToLayer(layername);
}
//Debug.Log("在" + gameObject.name + "中,可攻击到的层为" + System.Convert.ToString(targetLayer,2)); filter.layerMask = targetLayer;
//32个bit表示32个层,左移表示筛选需要哪个层 isInitialized = true;
} /// <summary>
/// 对一个能够战斗的目标造成伤害,作为底层私有函数被调用
/// </summary>
/// <param name="target">造成伤害的目标</param>
/// <param name="damage">造成输入数值的伤害</param>
/// <param name="interruptType">攻击打断类型,默认为无打断</param>
/// <returns>返回造成了多少伤害,具体用法有待进一步讨论</returns>
public int Attack(CanBeFighted target, int damage, AttackInterruptType interruptType = AttackInterruptType.NONE, ElementAbilityManager.Element element = ElementAbilityManager.Element.NULL)
{
if(!isInitialized)
{
Debug.LogError("在" + gameObject.name + "物体中,CanFight组件未初始化!");
}
return target.BeAttacked(gameObject, damage, interruptType, element);
} /// <summary>
/// 范围性攻击,实现方法为输入表示范围的Collier2D,检测范围内的每个拥有CanBeFighted的敌人,调用BeAttacked
/// </summary>
/// <param name="area">表示攻击范围的collier2d, 应该为trigger态</param>
/// <param name="damage">该次范围攻击造成了多少伤害</param>
/// <param name="interruptType">该次攻击为何种打断类型</param>
/// <returns>返回攻击到的敌人对象的CanBeFighted组件的数组</returns>
public CanBeFighted[] AttackArea(Collider2D area, int damage, AttackInterruptType interruptType = AttackInterruptType.NONE, ElementAbilityManager.Element element = ElementAbilityManager.Element.NULL)
{
//输入范围需要Trigger才行
if(!area.isTrigger)
{
Debug.LogError("在" + gameObject.name + "释放范围攻击时,输入的collider2d并不是trigger态");
return null;
} Collider2D[] enemies = new Collider2D[ENMEIES_MAX_NUM_ONEATTACK];
int enemiesNumber = area.OverlapCollider(filter, enemies); if(enemiesNumber != 0)
{
Debug.Log("攻击碰到敌人"); CanBeFighted[] enemiesAttacked = new CanBeFighted[enemiesNumber];
CanBeFighted enemyBody;
//对碰到的敌人进行以下操作,如果敌人有CanBeFighted组件,则施加攻击,否则报错 for(int i = 0; i < enemiesNumber; i++)
{ if (enemies[i].TryGetComponent<CanBeFighted>(out enemyBody))
{
Attack(enemyBody, damage, AttackInterruptType.NONE, element);
enemiesAttacked[i] = enemyBody;
}
else
{
Debug.LogError("在" + gameObject.name +
"释放范围攻击时,这些物体被检测为敌人,但是没有CanBeFighted组件" + enemies[i].gameObject.name);
}
}
return enemiesAttacked;
} return null;
} public CanBeFighted[] AttackArea(Collider2D area, int damage, CanBeFighted[] hasAttacked, AttackInterruptType interruptType = AttackInterruptType.NONE, ElementAbilityManager.Element element = ElementAbilityManager.Element.NULL)
{
//输入范围需要Trigger才行
if (!area.isTrigger)
{
Debug.LogError("在" + gameObject.name + "释放范围攻击时,输入的collider2d并不是trigger态");
return null;
} Collider2D[] enemies = new Collider2D[ENMEIES_MAX_NUM_ONEATTACK];
int enemiesNumber = area.OverlapCollider(filter, enemies); if (enemiesNumber != 0)
{
Debug.Log("攻击碰到敌人"); CanBeFighted[] enemiesAttacked = new CanBeFighted[enemiesNumber];
CanBeFighted enemyBody;
//对碰到的敌人进行以下操作,如果敌人有CanBeFighted组件,则施加攻击,否则报错 for (int i = 0; i < enemiesNumber; i++)
{
if (enemies[i].TryGetComponent<CanBeFighted>(out enemyBody))
{
Attack(enemyBody, damage, AttackInterruptType.NONE, element);
enemiesAttacked[i] = enemyBody;
}
else
{
Debug.LogError("在" + gameObject.name +
"释放范围攻击时,这些物体被检测为敌人,但是没有CanBeFighted组件" + enemies[i].gameObject.name);
}
}
return enemiesAttacked;
} return null;
}

整个项目原型github地址:

www.gitHub.com/yunshiyue/elementgame

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

Unity2D项目-平台、解谜、战斗! 1.1战斗底层组件CanFight-CanBeFighted的更多相关文章

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

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

  2. Unity2D项目-平台、解谜、战斗! 1.2战斗组件Defence、Attack

    各位看官老爷们,这里是RuaiRuai工作室,一个做单机游戏的兴趣作坊. 接上文,我们定义了两个分别具有"攻击"和"被攻击"语义的组件CanFight和CanB ...

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

    各位看官老爷们,这里是RuaiRuai工作室,一个做单机游戏的兴趣作坊. 在这一篇中,我们将会自顶向下地讨论本2D游戏中主角不可或缺的一个功能--移动控制. 首先我们简单分析一下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平台开源项目速览(5)深入使用与扩展SharpConfig组件

    上个月在文章:这些.NET开源项目你知道吗?让.NET开源来得更加猛烈些吧  和 .NET平台开源项目速览(1)SharpConfig配置文件读写组件 中都提到了SharpConfig组件,简单轻量级 ...

  9. .NET平台开源项目速览(2)Compare .NET Objects对象比较组件

    .NET平台开源项目速览今天介绍一款小巧强大的对象比较组件.可以更详细的获取2个对象的差别,并记录具体差别,比较过程和要求可以灵活配置. .NET开源目录:[目录]本博客其他.NET开源项目文章目录 ...

随机推荐

  1. 【Android初级】如何实现一个有动画效果的自定义下拉菜单

    我们在购物APP里面设置收货地址时,都会有让我们选择省份及城市的下拉菜单项.今天我将使用Android原生的 Spinner 控件来实现一个自定义的下拉菜单功能,并配上一个透明渐变动画效果. 要实现的 ...

  2. Java 队列同步器 AQS

    本文部分摘自<Java 并发编程的艺术> 概述 队列同步器 AbstractQueuedSynchronize(以下简称同步器),是用来构建锁(Lock)或者其他同步组件(JUC 并发包) ...

  3. const成员函数可以将非const指针作为返回值吗?

    先给出一段代码 class A { int *x; public: int *f() const { return x; } }; 成员函数f返回指向私有成员 x 的非常量指针,我认为这会修改成员x ...

  4. Docker 下Elasticsearch 的安装 和ik分词器

    (1)docker镜像下载 docker pull elasticsearch:5.6.8 (2)安装es容器 docker run -di --name=changgou_elasticsearch ...

  5. 基于Hi3559AV100 RFCN实现细节解析-(1)VGS初介绍

    下面随笔系列将对Hi3559AV100 RFCN实现细节进行解析,因为RFCN用到了VGS加框,因此本篇随笔将给出VGS视频图像子系统的具体说明,便于后面RFCN的细节实现说明. VGS 是视频图形子 ...

  6. Java语言的魅力

    Java语言的简介 Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承.指针等概念,因此Java语言具有功能强大和简单易用两个特征.Java语言作为静态面 ...

  7. HDOJ-1160(最长上升子序列变形)

    FatMouse's Speed HDOJ-1160 注意输出长度的时候不是输出dp[n] #include<bits/stdc++.h> using namespace std; con ...

  8. ES系列(一):编译准备与server启动过程解析

    ES作为强大的和流行的搜索引擎服务组件,为我们提供了方便的和高性能的搜索服务.在实际应用中也是用得比较爽,但如果能够更深入一点.虽然网上有许多的文章已经完整说明,ES是如何如何做到高性能,如何做到高可 ...

  9. JVM 中的异常

    StackOverflowError 在 JVM 的栈中,如果线程要创建的栈帧大小大于栈容量的大小时,就会抛出 java.lang.StackOverflowError.比如下面的代码 public ...

  10. 在C++中实现aligned_malloc

    malloc的默认行为 大家都知道C++中可以直接调用malloc请求内存被返回分配成功的内存指针,该指针指向的地址就是分配得到的内存的起始地址.比如下面的代码 int main() { void * ...