Unity——技能系统(一)
技能系统(一)
一.Demo展示
二.功能介绍
集成了技能,冷却,buff,UI显示,倒计时,动画等;
技能类型:弹道技能,动画事件根据帧数采用延迟调用技能,自定义释放位置(偏移,发射点两种),buff类型技能(自身增益buff,敌人减益buff,比如加防御和毒);
技能伤害判定:碰撞判定,圆形判定(自定义圆心和半径),扇形(角度和半径),线性(长宽),选中目标才可释放;
技能伤害支持多段;
Buff类型:燃烧,减速,感电,眩晕,中毒,击退,击飞,拉拽;增益:回血,加防御;
三.工具类介绍
CollectionHelper——数组工具,泛型,可以传入数组和条件委托,返回数组中符合条件的所有对象,以及排序功能;
TransformHelper——递归查找指定父节点下所有子节点,返回找到的目标;
SingletonMono——继承了MonoBehaviour的单例;
四.基类
1.Skill
技能数据类,所有可以外部导入的技能数据都放在这个类中,以便于可以外部导入数据;
由于测试demo,我另外写了一个SkillTemp类,继承了ScriptaleObject,方便填写测试数据;
/// <summary>
/// 技能类型,可叠加
/// </summary>
public enum DamageType
{
Bullet = 4, //特效粒子碰撞伤害
None = 8, //无伤害,未使用,为none可以不选
Buff = 32, //buff技能
//二选一
FirePos = 128, //有发射位置点
FxOffset = 256, //发射偏移,无偏移偏移量为0
//四选一
Circle = 512, //圈判定
Sector = 1024, //扇形判定
Line = 4096, //线性判定
Select = 8192, //选中才可释放
}
DamageType用来确定技能的行为,赋值都是2的倍数,可以使用与或非来减少变量个数;
后来发现直接用List好像也行,后面的技能就使用了List来存储叠加的情况;
[CreateAssetMenu(menuName="Create SkillTemp")]
public class SkillTemp : ScriptableObject
{
public Skill skill = new Skill();
/// <summary>技能类型,可用 | 拼接</summary>>
public DamageType[] damageType;
}
继承了ScriptableObject可以右键创建技能模板,直接在inspector界面编辑;

2.SkillData
组合了Skill类,在Skill类的基础上,添加了更多的不可外部传参的数据;
比如技能特效的引用,技能所有者引用,存储技能攻击目标对象用来在技能模块之间传递,以及技能等级冷却等动态变化的数据;
public class SkillData
{
[HideInInspector] public GameObject Owner;
/// <summary>技能数据</summary>
[SerializeField]
public Skill skill;
/// <summary>技能等级</summary>
public int level;
/// <summary>冷却剩余</summary>
[HideInInspector]
public float coolRemain;
/// <summary>攻击目标</summary>
[HideInInspector] public GameObject[] attackTargets;
/// <summary>是否激活</summary>
[HideInInspector]
public bool Activated;
/// <summary>技能预制对象</summary>
[HideInInspector]
public GameObject skillPrefab;
[HideInInspector]
public GameObject hitFxPrefab;
}
3.CharacterStatus
准确来说这个类不属于技能系统,他用来几率人物属性数据,以及提供受伤,刷新UI条等接口;
同时这个类存储着技能系统必须用到的受击特效挂载点HitFxPos,发射点FirePos,选中Mesh或特效物体selected,伤害数值出现点hudPos,自身头像血条UI物体uiPortrait;
最好是英雄和敌人单独写一个类继承这个基类,但是测试的话这个类就够用了;
public class CharacterStatus : MonoBehaviour
{
/// <summary>生命 </summary>
public float HP = 100;
/// <summary>生命 </summary>
public float MaxHP=100;
/// <summary>当前魔法 </summary>
public float SP = 100;
/// <summary>最大魔法 </summary>
public float MaxSP =100;
/// <summary>伤害基数</summary>
public float damage = 100;
///<summary>命中</summary>
public float hitRate = 1;
///<summary>闪避</summary>
public float dodgeRate = 1;
/// <summary>防御</summary>
public float defence = 10f;
/// <summary>主技能攻击距离 ,用于设置AI的攻击范围,与目标距离此范围内发起攻击</summary>
public float attackDistance = 2;
/// <summary>受击特效挂点 挂点名为HitFxPos </summary>
[HideInInspector]
public Transform HitFxPos;
[HideInInspector]
public Transform FirePos;
public GameObject selected;
private GameObject damagePopup;
private Transform hudPos;
public UIPortrait uiPortrait;
public virtual void Start()
{
if (CompareTag("Player"))
{
uiPortrait = GameObject.FindGameObjectWithTag("HeroHead").GetComponent<UIPortrait>();
}
else if (CompareTag("Enemy"))
{
Transform canvas = GameObject.FindGameObjectWithTag("Canvas").transform;
uiPortrait = Instantiate(Resources.Load<GameObject>("UIEnemyPortrait"), canvas).GetComponent<UIPortrait>();
uiPortrait.gameObject.SetActive(false);
//存储所有的uiPortarit在单例中
MonsterMgr.I.AddEnemyPortraits(uiPortrait);
}
uiPortrait.cstatus = this;
//更新血蓝条
uiPortrait.RefreshHpMp();
damagePopup = Resources.Load<GameObject>("HUD");
//初始化数据
selected = TransformHelper.FindChild(transform, "Selected").gameObject;
HitFxPos = TransformHelper.FindChild(transform, "HitFxPos");
FirePos = TransformHelper.FindChild(transform, "FirePos");
hudPos = TransformHelper.FindChild(transform, "HUDPos");
}
/// <summary>受击 模板方法</summary>
public virtual void OnDamage(float damage, GameObject killer,bool isBuff = false)
{
//应用伤害
var damageVal = ApplyDamage(damage, killer);
//应用PopDamage
DamagePopup pop = Instantiate(damagePopup).GetComponent<DamagePopup>();
pop.target = hudPos;
pop.transform.rotation = Quaternion.identity;
pop.Value = damageVal.ToString();
//ApplyUI画像
if (!isBuff)
{
uiPortrait.gameObject.SetActive(true);
uiPortrait.transform.SetAsLastSibling();
uiPortrait.RefreshHpMp();
}
}
/// <summary>应用伤害</summary>
public virtual float ApplyDamage(float damage, GameObject killer)
{
HP -= damage;
//应用死亡
if (HP <= 0)
{
HP = 0;
Destroy(killer, 5f);
}
return damage;
}
}
4.IAttackSelector
目标选择器接口,只定义了一个方法,选择符合条件的目标并返回;
//策略模式 将选择算法进行抽象
/// <summary>攻击目标选择算法</summary>
public interface IAttackSelector
{
///<summary>目标选择算法</summary>
GameObject[] SelectTarget(SkillData skillData, Transform skillTransform);
}
LineAttackSelector,CircleAttackSelector,SectorAttackSelector线性,圆形,扇形目标选择器,继承该接口;
就只展示一个了CircleAttackSelector;
class CircleAttackSelector : IAttackSelector
{
public GameObject[] SelectTarget(SkillData skillData, Transform skillTransform)
{
//发一个球形射线,找出所有碰撞体
var colliders = Physics.OverlapSphere(skillTransform.position, skillData.skill.attackDisntance);
if (colliders == null || colliders.Length == 0) return null;
//通过碰撞体拿到所有的gameobject对象
String[] attTags = skillData.skill.attckTargetTags;
var array = CollectionHelper.Select<Collider, GameObject>(colliders, p => p.gameObject);
//挑选出对象中能攻击的,血量大于0的
array = CollectionHelper.FindAll<GameObject>(array,
p => Array.IndexOf(attTags, p.tag) >= 0
&& p.GetComponent<CharacterStatus>().HP > 0);
if (array == null || array.Length == 0) return null;
GameObject[] targets = null;
//根据技能是单体还是群攻,决定返回多少敌人对象
if (skillData.skill.attackNum == 1)
{
//将所有的敌人,按与技能的发出者之间的距离升序排列,
CollectionHelper.OrderBy<GameObject, float>(array,
p => Vector3.Distance(skillData.Owner.transform.position, p.transform.position));
targets = new GameObject[] {array[0]};
}
else
{
int attNum = skillData.skill.attackNum;
if (attNum >= array.Length)
targets = array;
else
{
for (int i = 0; i < attNum; i++)
{
targets[i] = array[i];
}
}
}
return targets;
}
}
这里有个问题,技能的目标选择器每次释放技能都会调用,因此会重复频繁的创建,但其实这只是提供方法而已;
解决:使用工厂来缓存目标选择器;
//简单工厂
//创建敌人选择器
public class SelectorFactory
{
//攻击目标选择器缓存
private static Dictionary<string, IAttackSelector> cache = new Dictionary<string, IAttackSelector>();
public static IAttackSelector CreateSelector(DamageMode mode)
{
//没有缓存则创建
if (!cache.ContainsKey(mode.ToString()))
{
var nameSpace = typeof(SelectorFactory).Namespace;
string classFullName = string.Format("{0}AttackSelector", mode.ToString());
if (!String.IsNullOrEmpty(nameSpace))
classFullName = nameSpace + "." + classFullName;
Type type = Type.GetType(classFullName);
cache.Add(mode.ToString(), Activator.CreateInstance(type) as IAttackSelector);
}
//从缓存中取得创建好的选择器对象
return cache[mode.ToString()];
}
}
小结
所有基类,前期准备数据只有这些,另外想Demo更有体验感,还需要有角色控制,相机跟随脚本;
之后就是技能管理系统,技能释放器等;
Unity——技能系统(一)的更多相关文章
- Unity——技能系统(二)
Unity技能系统(二) Unity技能系统(一) Demo展示: 五.技能管理和释放 1.CharacterSkillSystem 技能系统类,给外部(技能按钮,按键)提供技能释放方法: 技能释放逻 ...
- Unity——技能系统(三)
Unity技能系统(三) Unity技能系统(一) Unity技能系统(二) Demo展示 六.Buff系统 buff分为增益和减益buff,应该区分开来: /// <summary> / ...
- Unity——射线系统
Unity射线系统 Demo展示 UI+Physical射线测试: FPS自定义射线测试: UGUI射线工具 实现功能,鼠标点击UI,返回鼠标点击的UI对象: 需要使用到鼠标点击事件-PointerE ...
- 三维软件转Unity的系统单位设置研究
Unity的系统单位为米,其他3D软件的模型导入,而保持和Unity的比例一致是非常重要的,下面对各软件进行测试: ㈠. 3dsmax 转 Unity的比例为100:1:也就是说Unity单位是3ds ...
- 一个MMORPG的常规技能系统
广义的的说,和战斗结算相关的内容都算技能系统,包括技能信息管理.技能调用接口.技能目标查找.技能表现.技能结算.技能创生体(buff/法术场/弹道)管理,此外还涉及的模块包括:AI模块(技能调用者). ...
- MMO技能系统的同步机制分析
转自:http://www.gameres.com/729629.html 此篇文章基于之前文章介绍的技能系统,主要介绍了如何实现MMO中的技能系统的同步.阅读此文章之前,推荐首先阅读前一篇文章:一个 ...
- Unity3D手游开发日记(2) - 技能系统架构设计
我想把技能做的比较牛逼,所以项目一开始我就在思考,是否需要一个灵活自由的技能系统架构设计,传统的技能设计,做法都是填excel表,技能需要什么,都填表里,很死板,比如有的技能只需要1个特效,有的要10 ...
- Dota2技能系统设计分析
http://blog.csdn.net/langresser_king/article/details/46776701 前两周写完了新游戏的技能系统.虽然也算灵活,但是跟Dota2的技能系统设计比 ...
- python基础----以面向对象的思想编写游戏技能系统
1. 许多程序员对面向对象的思想都很了解,并且也能说得头头是道,但是在工作运用中却用的并不顺手. 当然,我也是其中之一. 不过最近我听了我们老师的讲课,对于面向对象的思想有了更深的理解,今天决定用一个 ...
随机推荐
- 在反序列化数据的时候报错raise JSONDecodeError("Expecting value", s, err.value) from None json.decode
今天在爬取某网站数据内容适合,通过正则匹配拿到了需要的内容字符串,但是在反序列化的时候竟然报错,大概意思知道他不是json的期望值,那么我就会像是不是数据内有一些内容是由于编码的问题导致的呢?因为之前 ...
- 鸿蒙内核源码分析(寄存器篇) | 小强乃宇宙最忙存储器 | 百篇博客分析OpenHarmony源码 | v38.02
百篇博客系列篇.本篇为: v38.xx 鸿蒙内核源码分析(寄存器篇) | 小强乃宇宙最忙存储器 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪 ...
- P4451-[国家集训队]整数的lqp拆分【生成函数,特征方程】
正题 题目链接:https://www.luogu.com.cn/problem/P4451 题目大意 给出\(n\),对于所有满足\(\sum_{i=1}^ma_i=n\)且\(\forall a_ ...
- MacOS下terminal防止ssh自动断开的方法和自动断开的原因
之前换了个工作环境,用terminal连接远程服务器的时候老是出现自动断开的情况,搞得我很是郁闷.因为之前在家的时候,并没有出现过类似情况.后来在网上找了很久,发现国外网站上有个大神说应该是有些路由器 ...
- 一、Ansible基础之入门篇
目录 1. Ansible基础 1.1 介绍 1.2 工作原理 1.3 如何安装 1.3.1 先决条件 1.3.2 安装Ansible 1.4 管理节点与被管理节点建立SSH信任关系 1.5 快速入门 ...
- 初探webpack之编写plugin
初探webpack之编写plugin webpack通过plugin机制让其使用更加灵活,以适应各种应用场景,当然也大大增加了webpack的复杂性,在webpack运行的生命周期中会广播出许多事件, ...
- IIS部署WCF详细教程
前言: 前段时间接手了公司一个十几年前的老项目,该项目对外提供的服务使用的是WCF进行通信的.因为需要其他项目需要频繁的使用该WCF服务,所以我决定把这个WCF部署到IIS中避免每次调试运行查看效果. ...
- Linux Bash命令杂记(tr col join paste expand)
Linux Bash命令杂记(tr col join paste expand) tr命令 tr命令可以将输入的数据中的某些字符做替换或者是作删除 tr [-ds] STR d: 删除输入数据的中的S ...
- Java初步学习——2021.09.24每日总结,第三周周五
(1)今天做了什么: (2)明天准备做什么? (3)遇到的问题,如何解决? 今天学了将数组传递给方法和方法返回数组,其中传递的是数组的引用. 明天把例子做了,尽量把查找也学习了. 遇到了两个问题: 1 ...
- 题解 [ZJOI2019]语言
题目传送门 题目大意 给出一个 \(n\) 个点的树,现在有 \(m\) 次操作,每次可以选择一个链 \(s,t\),,然后这条链上每个点都会增加一个相同属性,问对于每一个点有与它相同属性的有多少个点 ...