在Unity中制作完整的技能系统(代码篇)
Posted 酒九5
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在Unity中制作完整的技能系统(代码篇)相关的知识,希望对你有一定的参考价值。
哈喽~又是我暴躁老哥酒九,上次我向大家介绍了一下技能系统的思路和使用方法。那么我们话不多说,这篇文章就是有关这些功能都是如何实现的,让我们开始吧。
素材准备工作做好之后我们就可以开始编写具体的脚本了。新建好我们的项目,场景搭建和角色素材这些操作就大家自己去选择准备吧。新建一个文件夹命名为SkillSystem,之后我们技能系统所有的脚本就放在这里面了(养成收拾脚本的习惯)。
PS:接下来的所有脚本我们都统一放进一个命名空间中,我这里命名为MOBASkill,后面代码中就不单独写出来。
1、系统总体规划
我们再来捋一捋我们这个技能系统的各个模块之间是怎么互相作用的。在每一拥有技能的角色身上都会有一个技能管理器负责该角色的技能数据存放和生成对应技能的预制体,在我们生成技能预制体(释放技能)的同时,对应的技能预制体身上挂载好的技能释放器就会在释放器配置工厂中找到对应的效果算法和目标选择算法,并且执行算法中的代码完成查找敌人并对敌人产生对应的效果,最后销毁预制体本身,这就是一次完整的技能释放流程。
2、技能数据类(SkillData)
我们新建脚本命名为SkillData,将我们技能中需要的数据都写在这个数据类中,这里可以大家根据自己的设计想法来进行添加,这里我就不过多啰嗦我们看着代码讲。
public enum SkillAttackType
single,
aoe,
public enum SelectorType
none,
Sector,
Rectangular,
public enum DisappearType
TimeOver,
CheckOver,
[Serializable]
public class SkillData
public int skillId;//技能ID
public string name;//技能名称
public string description;//技能描述
public int skillCd;//技能冷却时间
public int cdRemain;//技能剩余冷却时间
public int costMp;//法力值消耗
public float attackDistance;//技能距离
public float attackAngle;//技能攻击角度
public string[] attackTargetTags = "Enemy" ;//能作用的目标Tag
[HideInInspector]
public Transform[] attackTargets;//作用目标对象数组
public string[] impactType = "CostMP", "Damage" ;//技能影响类型
public int nextBatterld;//连击的技能ID
public float attackNum;//伤害数值
public float durationTime;//持续时间
public float attackInterval;//伤害间隔
[HideInInspector]
public GameObject owner;//技能所属的角色
public string prefabName;//技能预制体名称
[HideInInspector]
public GameObject skillPrefab;//预制体对象
public string animationName;//动画名称
public string hitFxName;//受击特效名称
[HideInInspector]
public GameObject hitFxPrefab;//受击特效预制体
public int level;//技能等级
public SkillAttackType attackType;//AOE或者单体
public SelectorType selectorType;//释放范围类型(圆形,扇形,矩形)
public string skillIndicator;//技能指示器名字
public string skillIconName;//技能显示图标名字
[HideInInspector]
public Image skillIcon;//技能事件图标
public DisappearType disappearType;//技能预制体消失方式
这里有几个变量的作用需要单独说一说他的作用:
1.attackTargetTags和attackTargets,看上去这两个变量的作用是一样的,但是前者是在释放技能之前该技能可以作用于那些物体上(敌人,队友,建筑等),后者是在执行技能范围选择算法之后返回的在技能范围中的敌人(实际攻击到的目标)。
2.owner在这里其实就是挂载管理器的物体自己,在后续算法中可以更方便的调用本身的数据,方法或者组件等。
3.skillIndicator是我们成功释放技能之前显示出来的辅助技能释放的工具。
PS:通常我们定义了公有变量之后就会在Inspector窗口中生成对应的输入框,而当我们在变量前加上[HideInInspector]之后就会该公有变量就不会在Inspector中显示。
3、技能管理器(SkillManager)
技能管理器的作用就是存好人物的所有技能数据,初始化技能数据中未填写的部分(owner,skillPrefab,hitFxPrefab),准备技能,生成技能等。因此首先我们需要准备一个数组来存放人物的全部技能,根据数据中的预制体名字(prefabName,hitFxName)给对应的GameObject赋值的方法,生成技能时各种内容等。那么我们来看代码。
public class CharacterSkillManager : MonoBehaviour
public SkillData[] Skills;//技能列表
private void Awake()
foreach (var s in Skills)
InitSkill(s);
//初始化技能
private void InitSkill(SkillData data)
if (data.prefabName != null)
data.skillPrefab = Resources.Load<GameObject>("SkillPrefab/"+data.prefabName);
data.owner = this.gameObject;
//技能释放条件判断
public SkillData PrepareSkill(int id)
SkillData data = new SkillData();
foreach (var s in Skills)
if (s.skillId == id)
data = s;
if (data != null && data.cdRemain <= 0)//这里还有技能消耗值的判断
return data;
else
return null;
//生成技能
public void GenerateSkill(SkillData data)
//创建技能预制体
GameObject skillgo = GameObjectPool.instance.CreateObject(data.prefabName, data.skillPrefab, transform.position, transform.rotation);
//传递技能数据给技能释放器
SkillDeployer deployer = skillgo.GetComponent<SkillDeployer>();
deployer.SkillData = data;
//释放器释放技能
deployer.DeploySkill();
StartCoroutine(CoolTimeDown(data));//开启冷却
//协程实现技能冷却
private IEnumerator CoolTimeDown(SkillData data)
data.cdRemain = data.skillCd;
while (data.cdRemain > 0)
yield return new WaitForSeconds(1f);
data.cdRemain -= 1;
这里对于能否释放技能的条件有:该技能ID是否存在,技能的是否完成冷却(利用剩余冷却时间是否为0来判断),技能所需的消耗值玩家的对应数值是否足够。大家伙可以根据自己的需求来进行条件的添加删除,举个例子:一个技能拥有三段释放,那么就可以将上一段技能的是否释放来作为条件实现。生成技能这里由于我练习了一下对象池,所以使用了对象池,大家可以改成普通的实例化就好。后面的技能冷却就是通过协程实现啦。
4、释放器配置工厂(DeployerConfigFactory)
根据之前的技能流程图发现在技能管理器中释放技能的操作其实只有实例化预制体和将技能数据传入释放器中,而实际的选取敌人,执行技能效果都是在释放器中来完成的。那技能释放 器是怎么知道要用那些算法的呢?我们可以看到技能数据中有两个变量impactType和selectorType这两个变量中都存放的是字符串内容,那么我们就通过这些字符串内容来获取到对应的算法。这些操作就由释放器配置工厂来完成。
先准备好技能范围选择算法和效果算法的接口:
public interface IImpactEffect //效果算法接口
void Execute(SkillDeployer deployer);
public interface ISkillSelector //范围选择算法接口
Transform[] SelectTarget(SkillData data, Transform skillTF);//skillTF是技能预制体
接下来我们的所有的相关算法就是通过继承这两个接口再在其中编写具体的逻辑。现在来看我们释放器配置工厂。
public class DeployerConfigFactory//反射来实现
public static ISkillSelector CreateSkillSelector(SkillData data)//范围选择算法
string className = string.Format("MOBASkill.0SkillSelector", data.selectorType);
return CreateObject<IAttackSelector>(className);
public static IImpactEffect[] CreateImpactEffects(SkillData data) //效果算法
IImpactEffect[] impacts = new IImpactEffect[data.impactType.Length];
for (int i = 0; i < data.impactType.Length; i++)
string classname = string.Format("MOBASkill.0Impact", data.impactType[i]);
impacts[i] = CreateObject<IImpactEffect>(classname);
return impacts;
private static T CreateObject<T>(string className) where T : class//创建对应算法
Type type = Type.GetType(className);
return Activator.CreateInstance(type) as T;
在类中我们通过CreateObject方法来由字符串变量找到对应的算法,主要操作就是使用泛型(T)并且将我们的泛型约束为引用类型(where T : class),根据前文提到的算法的字符串变量的Type来创建对应类型的方法。而CreateAttackSelector和CreateImpactEffects两个方法就是把算法名补全之后返回指定接口类型的方法。
PS:这里的Activator.CreatInstance的作用是使用与指定参数匹配程度最高的构造函数来创建指定类型的实例。通俗的讲就是按照参数给出方法,不过通过它返回的是object类型所以要转化成我们需要的类型(as T)。
5、技能释放器(SkillDeployer)
解决了根据数据生成算法之后,在技能释放器中就只需要初始化释放器算法并且调用相应的算法就好了,代码如下:
public abstract class SkillDeployer : MonoBehaviour//技能释放器
private SkillData skillData;
public SkillData SkillData //技能管理器提供
get return skillData;
set skillData = value; InitDeplopyer();
//范围选择算法
private IAttackSelector selector;
//效果算法对象
private IImpactEffect[] impactArray;
//初始化释放器
private void InitDeplopyer()//初始化释放器
//范围选择
selector = DeployerConfigFactory.CreateAttackSelector(skillData);
//效果
impactArray = DeployerConfigFactory.CreateImpactEffects(skillData);
//范围选择
public void CalculateTargets()
skillData.attackTargets = selector.SelectTarget(skillData, this.transform);
//效果
public void ImpactTargets()
for (int i = 0; i < impactArray.Length; i++)
impactArray[i].Execute(this);
public abstract void DeploySkill();//供技能管理器调用,由子类实现,定义具体释放策略
这里释放器就可以作为我们所有释放器的父类,如果有释放情况的不同,就通过继承他在子类中进行重写方法。比如前文提到的:近战技能释放是如何释放,远程技能释放又是怎样的等等,这里就交给大家自己完成啦。
public class MeleeSkillDeployer : SkillDeployer//近战技能释放例子
public override void DeploySkill()
//执行选区算法
CalculateTargets();
//执行影响算法
ImpactTargets();
6、技能范围选择算法(Selector)
顾名思义,这里要完成的内容就是将在规定范围中的敌人全部选择出来这里就给大家演示一下扇形范围的敌人检测。上代码。
public class SectorSkillSelector : ISkillSelector
GameObject[] tempGOArray;
public Transform[] SelectTarget(SkillData data, Transform skillTF)
//根据技能数据中得标签 获取所有目标
List<Transform> taragets = new List<Transform>();
for (int i = 0; i < data.attackTargetTags.Length; i++)
tempGOArray = GameObject.FindGameObjectsWithTag(data.attackTargetTags[i]);
for (int i = 0; i < tempGOArray.Length; i++)
taragets.Add(tempGOArray[i].GetComponent<Transform>());
//判断攻击范围
taragets = taragets.FindAll(t =>
Vector3.Distance(t.position, skillTF.position) <= data.attackDistance &&
Vector3.Angle(skillTF.forward, t.position - skillTF.position) <= data.attackAngle / 2
);
//返回目标
Transform[] result = taragets.ToArray();
if (result.Length == 0)
Debug.Log("没有敌人");
return result;
else
for (int i = 0; i < result.Length; i++)
Debug.Log(result[i].name);
return result;
说明一下整体的流程,首先我们先根据技能数据中的attackTargetTags中的Tag找到所有带有该Tag的物体,再通过一个列表的FindAll方法来找符合条件的敌人,再返回一个数组到技能数据的attackTargets中去。这样就完成了敌人的查找。
PS:1.当我们命名脚本名是一定要是与释放器配置工厂中的命名规则一致,否则无法找到,在这里也就是范围选择算法名为xxxSkillSelector,而效果算法名应该为xxxImpactEffect。2.在FindAll中使用了Lamda表达式来完成条件的限制,Vector3.Distance负责在攻击距离内,Vector3.Angle负责在攻击角度内。3.这里还需要大家自己添加上敌人是否存活的选择条件判断,技能为作用于目标单个或者目标为多个的返回情况等。
7、技能效果算法(Impact)
技能的效果就有很多了可以是回复血量,消耗法力值,减少血量等等,大家可以根据自己的需求来自行进行编写啦。这里的例子了是给敌人添加一个击飞Buff。
public class BlowFlyImpact : IImpactEffect
private SkillData data;
public void Execute(SkillDeployer deployer)
data = deployer.SkillData;
deployer.StartCoroutine(ContinuousBlowFly(deployer));
public void BlowFly(Transform transform)//给敌人添加Buff
CharacterData cd = transform.GetComponent<CharacterData>();
BuffManager.instance.AllBuffs[0].currentTarget = cd;
cd.AddBuff(BuffManager.instance.AllBuffs[0]);
IEnumerator ContinuousBlowFly(SkillDeployer deployer) //每隔0.05秒检测一次敌人
float time = 0;
do
yield return new WaitForSeconds(0.05f);
time += 0.05f;
deployer.CalculateTargets();
if (data.attackTargets.Length != 0)
foreach (var t in data.attackTargets)
BlowFly(t);
while (time < 0.4f);
为什么要用协程来持续检测敌人是因为这个技能是一个冲撞技能,在玩家向前位移的整个时间检测到的敌人都会被添加击飞的Buff。BuffManager是一个简单Buff系统中的管理器,这里不必深究。这里当我们呢释放技能时就会调用Execute中的内容啦。
好啦,本篇文章的内容就到这里啦,大家伙下篇文章见哦。拜拜~
Unity实战篇 | 教你怎样将Unity的启动Logo 设置成 自己制作的 帧动画
🎬 博客主页:https://xiaoy.blog.csdn.net
🎥 本文由 呆呆敲代码的小Y 原创,首发于 CSDN🙉
🎄 学习专栏推荐:👑Unity系统学习专栏👑
🌲 游戏制作专栏推荐:👑游戏制作👑
🏅 欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
📆 未来很长,值得我们全力奔赴更美好的生活✨
------------------❤️分割线❤️-------------------------
🎁前言
-
你在打包Unity应用的时候是不是还在用
默认启动Logo
呢? -
看完这篇文章教你自己定制一款属于自己的
Unity启动Logo
! -
之前有一篇文章我们讲了怎样在不购买专业版的情况下
自定义Unity的启动Logo
-
但是只是用了一张图片对默认Logo进行替换,并没有加自己的启动动画
-
那本篇文章就来介绍一下,怎样在
去除默认Logo的前提
下制作自己的启动动画! -
还没学会怎样
去除UnityLogo
的小伙伴可以去看这篇文章先学习一下哦:
🎅Unity实战篇 | 将Unity的启动画面 设置成 自己制作的 帧动画
🎄帧动画
先来说一下这个 帧动画
是啥意思方便大家理解
这里说的 帧动画
就是一系列的 静态图片 快速切换达到一个动图的效果
事实上我们现在的游戏也是通过一帧一帧的拼接起来,一秒60帧以上就会很流畅了
比如之前很火的火柴人系列
,就是很经典的这种帧动画
哈哈哈,是不是很有意思呢~
我们就是要搞一个这种动态图片的帧动画
来做 应用启动Logo
!
🔔帧动画制作
如果能自己画图来制作一个系列的帧动画
自然是好的。
但是可惜 小Y
不会这个技术活(懒)呀,那咋办呢~
俗话说得好,只要思想不滑坡,方法总比困难多!
这里顺道给大家推荐一个录屏工具:ScreenToGif
这个工具可以实现基本的录屏功能
和 保存视频 或者 Gif
等格式的文件
我平时就用这个录屏做一个动态图展示,还是很赞的!
废话不多说,我这里主要是用到了这个工具里面的一个功能
它可以将现有的 Gif格式图片
拆分成多帧静态图
,然后再将每一帧的图片一键保存起来
保存之后的图片示例如下:
好了,这样我们就有一个简单的帧动画素材
了
虽然看起来很Low,但是这里就是简单做个示范哈,大家可以自己定制一款属于自己的启动Logo!
接下来就是怎样在工程中使用这个帧动画了,一起来看看。
🎉将 帧动画图片 添加到Android Studio工程中
这一步很简单,继续打开我们去除Unity启动Logo的文章
中用到的工程
然后将帧动画图片
复制到 res -> drawable
文件夹下即可!
🎋代码编写帧动画
我们继续在Android Studio
写代码然后使用的 aar
的方式给Unity去除启动Logo
那么这个添加帧动画
的方法依旧是在Android Studio
中进行的
所以我们在这篇 去除Logo的文章 基础上继续添加代码
在原有的MainActivity
中继续添加一个ImageView
和一个AnimationDrawable
ImageView
和AnimationDrawable
就是我们即将给 帧动画 赋值的参数
这段代码是给帧动画赋值
,将我们自己的一套帧动画图片添加进去
然后设置动画循环播放
,还要把 帧动画的位置
和 尺寸信息
设置完毕
然后别忘了在关闭启动动画的方法
中将帧动画也关闭掉
这一步是在Unity端进行的,与之前的去除UnityLogo文中方法一致!
🎊打包aar给Unity使用
又到了打包的这一步,就说明快要结束啦!
选中这个工程,然后 Build -> Make Module
然后把aar
和AndroidManifest
都保存出来,待会给Unity使用(这一步每次都重复,大家应该都会了)
aar
复制出来之后记得把Libs文件夹
下的class包
删掉。
🌻将aar和AndroidManifest放入Unity工程中
新建Plugins/Android
文件夹
把我们在Android端保存的aar文件和AndroidManifest
都放到这个Android文件夹下
写脚本调用 Android中消除启动Logo 的动画图片,脚本跟之前的 去除Logo的文章 一样没变化
Unity打包apk应用,设置包名。File -> Build Settings -> Player Settings
这里把 公司名 和 包名 改为我们的AndroidManifest
中的一致即可
然后就是Build打包
即可!
🎈效果展示
打包完之后点击运行,Unity的Logo就会变成我们自定义的动画Logo啦!
是不是很实用呢,可以自己搞一个属于自己的Logo放上去
这样我们打包的Unity应用就会变成 自己的专属Logo
!
赶紧自己动手试试吧!
源码工程
的话可以参考下上一篇文章中的工程,只需要改动一下帧动画的代码即可!
【Unity终极奥义】Unity打包去掉启动画面Logo,无需破解,一学就会
👥总结
-
本篇文章自己制作了一个
帧动画
来替换了Unity的默认启动Logo
-
虽然这个
帧动画
看起来一般般哈哈,但是我们的功能做好了 -
有时间可以整一个好看的动画,用来做应用启动的时候显示
-
这样就可以拥有一个属于自己的应用启动动画啦!
-
那本篇文章就到这里结束啦,喜欢的小伙伴记得点个
三连
,咱们下次再见啦~
🚀往期优质文章分享
- ❤️Unity零基础到入门 | 游戏引擎 Unity 从0到1的 系统学习 路线【全面总结-建议收藏】!
- 🧡花一天时间做一个高质量飞机大战游戏,过万字Unity完整教程!漂亮学妹看了直呼666!
- 💛通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难
- 💚重回童年的经典系列☀️|【贪吃蛇小游戏】近两万字完整制作过程+解析+源码 【建议收藏学习】
- 🤍重回童年的经典系列☀️| 【皇室战争 】 的 即时战斗类 复刻游戏Demo!两万多字游戏制作过程+解析!
- 💙重回童年的经典系列☀️| 【横版街机格斗游戏】类似“恐龙快打” 该如何制作? | 一起来学习 顺便送源码【建议收藏学习】
- 💜重回童年的经典系列☀️|【炸弹人小游戏】制作过程+解析 | 收藏起来跟曾经的小伙伴一起梦回童年!
🚀 优质专栏分享 🚀 |
- 🎄如果感觉文章看完了不过瘾,可以来我的其他 专栏 看一下哦~
- 🎄比如以下几个专栏:Unity基础知识学习专栏、Unity游戏制作专栏、Unity实战类项目 和 算法学习专栏
- 🎄可以学习更多的关于Unity引擎的相关内容哦!直接点击下面颜色字体就可以跳转啦!
🚀 社区活动,重磅来袭 🚀 |
【游戏开发爱好者社区】在本周重磅新推出【每日打卡】活动
🎁 新玩法,奖励升级!游戏开发爱好者社区:https://bbs.csdn.net/forums/unitygame
社区中心思想:今天你学到了什么?
在社区你可以做些什么: 每日强化知识点,白嫖书籍礼品!
一个人可以走的很快,一群人才能走的更远!🔥爆C站的游戏开发爱好者社区欢迎您的加入!
更多白嫖活动详情:https://bbs.csdn.net/topics/603722518
温馨提示: 点击下面卡片可以获取更多编程知识,包括各种语言学习资料,上千套PPT模板和各种游戏源码素材等等资料。更多内容可自行查看哦!
以上是关于在Unity中制作完整的技能系统(代码篇)的主要内容,如果未能解决你的问题,请参考以下文章
Unity实战篇 | 教你怎样将Unity的启动Logo 设置成 自己制作的 帧动画
Unity实战篇 | 教你怎样将Unity的启动Logo 设置成 自己制作的 帧动画