unity简单技能系统_unity skill-程序员宅基地

技术标签: Unity  

unity简单技能系统

类类型概览

  • CharacterSkillManager      角色技能管理器 挂载在角色 持有SkillData与释放器 通过释放器进行技能释放

  • SkillDeployer                       技能释放器基类 持有选取类型与影响效果两个接口 抽象函数释放方式

  • SkillData                             技能数据类 保存技能基本信息、攻击基本信息、影响效果、选择类型等

  • IImpactEffects                     影响效果接口 持有效果执行方法Execute(SkillDeployer deployer)

  • IAttackSelector                   选区算法接口 持有选区算法 SelectTarget(SkillData data)

  • DeployerConfigFactory      释放器算法工厂返回具体类方法

  • Enum SkillAttackType         攻击类型 群体/单体

  • Enum SelectorType           选择类型 扇形/矩形


  • MeleeSkillDeployer           近战释放器 继承自 SkillDeployer

  • CostSPImpact                 消耗法力值 实现IImpactEffects接口

  • HurtHPImpact                 伤害生命

  • AddSPImapact                 增加蓝耗

具体思路

  • 将技能拆分为 数据(伤害,范围) 效果 (浮空,减速) 根据数据不同例如 范围、持续时间等 根据不同效果构建不同效果 例如: 英雄联盟中 艾希的W 技能与E技能 前者向前方一定扇形的敌人造成伤害与减速 两个效果 ,后者 向释放方向发射弹体 对沿途进行视野暴露一个效果 。
  • 拿到相应技能该像哪里释放,如何获得效果对象,我们需要一个替我们处理技能释放的对象 去处理技能从初始化到释放的过程。 SkillDeployer 持有 技能数据、影响效果接口与选区算法接口 ,在具体创建时通过释放器工厂拿到相应具体实现。

代码具体流程

  • 技能管理器持有技能数据 与唯一选择器 检测到相应输入 通过唯一ID获取技能数据 根据技能数据 例如蓝耗 判断是否能够释放 达到释放条件后 创建选择器
  • 选择器根据传入技能数据 影响效果 通过工厂 拿到具体方法 进行释放

效果

释放前
释放后
结果

角色脚本1
角色脚本2
受击脚本

实现

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//人物状态
public class CharacterStateData : MonoBehaviour
{
    
     public float HP = 100;
     public float SP = 100;
     public float BeAttack = 10;//攻击力
     
    void Start()
    {
    
        
    }

    void Update()
    {
    
        
    }
}



using System.Collections;
using System.Collections.Generic;
using TestSkillSystem;
using UnityEngine;
using Debug = UnityEngine.Debug;
//技能管理器
public class CharacterSkillManager : MonoBehaviour
{
    

    
    private Transform m_Transform;

    private CharacterStateData m_PlayerData;

    private SkillData m_NowSKill;
    //技能列表
    public List<SkillData> Skills;
    private SkillDeployer m_Deployer;
    
    private void Awake()
    {
    
        m_Transform = GetComponent<Transform>();
        m_PlayerData = GetComponent<CharacterStateData>();
        m_Deployer = GetComponent<SkillDeployer>();
    }

    private void Start()
    {
    
        for (int i = 0; i < Skills.Count; i++)
        {
    
            InitSkill(Skills[i]);
        }
        
       
    }

  
    private void Update()
    {
    
        if (Input.GetKeyDown(KeyCode.Q))
        {
    
           useSkill();
        }
        
    }

    private void useSkill()
    {
    
        m_NowSKill = PrepareSkill(1001);
        if (m_NowSKill == null)
        {
    
            return;
        }
        GenerateSkill(m_NowSKill);
    }




    /// <summary>
    /// 生成技能
    /// </summary>
    public void GenerateSkill(SkillData data)
    {
    
        GameObject skill = Instantiate(data.skillPrefab, m_Transform.position, m_Transform.rotation);
        
        SkillDeployer skillDeployer = skill.GetComponent<SkillDeployer>();
        skillDeployer.skillData = data;
        skillDeployer.DeploySkill();
        
        //定时销毁
        Destroy(skill, data.durationTime);
        //开启cd
        StartCoroutine(CoolTimeDown(data));
    }
    /// <summary>
    /// 准备释放技能
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public SkillData PrepareSkill(int id)
    {
    
        SkillData data = Skills.Find(a => a.id == id);

        if (data != null && data.coolRemain <= 0 && m_PlayerData.SP >= data.costSp)
        {
    
            return data;
        }
        Debug.Log("当前技能无法释放");
        return null;
    }
    /// <summary>
    /// 初始化技能
    /// </summary>
    /// <param name="data"></param>
    private void InitSkill(SkillData data)
    {
    
       data.skillPrefab =  SkillResManager.Load<GameObject>(data.prefabName);
       
       data.owner = gameObject;
       
    }

    /// <summary>
    /// 技能冷却
    /// </summary>
    /// <param name="data"></param>
    /// <returns></returns>
    private IEnumerator CoolTimeDown(SkillData data)
    {
    
        data.coolRemain = data.coolTime;
        while (data.coolRemain > 0)
        {
    
            yield return new WaitForSeconds(1);
            data.coolRemain--;
        }
        Debug.Log("技能CD完毕over");
    }
}
using System;
using System.Collections.Generic;
using UnityEngine;
namespace TestSkillSystem
{
    
    
    /// <summary>
    /// 技能释放器
     /// </summary>
    public abstract class SkillDeployer : MonoBehaviour
    {
    
        private SkillData m_SkillData;

        public SkillData skillData
        {
    
            get {
     return m_SkillData; }
            set
            {
    
                m_SkillData = value;
                InitDeployer();
            }
        }

        private IAttackSelector m_Selector;
        private List<IImpactEffects> m_Effects;

        private void InitDeployer()
        {
    
            m_Effects=new List<IImpactEffects>();
           
            //选取类型
            m_Selector = DeployerConfigFactory.CreateAttackSelector(skillData);
            

            //影响效果
            m_Effects = DeployerConfigFactory.CreateImpactEffects(skillData);
            
            //Debug.Log("go");
        }
        
        //执行算法对象
        //选区
        public void CalculateTargets()
        {
    
            skillData.attackTargets = m_Selector.SelectTarget(skillData, transform);
            
            
        }
        //执行影响效果
        public void ImpactTargets()
        {
    
            for (int i = 0; i < m_Effects.Count; ++i)
            {
    
                m_Effects[i].Execute(this);
            }
        }
        //释放方式
        //技能管理器调用,有子类实现,定义具体释放策略
        public abstract void DeploySkill();
    }
    
}

using System;
using System.Collections.Generic;
using UnityEngine;

namespace TestSkillSystem
{
    
    /// <summary>
    /// 攻击类型 群体/单体
    /// </summary>
    public enum SkillAttackType
    {
    
        Group,
        Alone
    }

    /// <summary>
    /// 选择类型 扇形/矩形
    /// </summary>
    public enum SelectorType
    {
    
        Sector,
        Rectangle
    }
    //技能数据
   [Serializable]
    public class SkillData
    {
    
        /// <summary>技能ID</summary>
        public int id;
        /// <summary>技能名称</summary>
        public string name;
        /// <summary>技能描述</summary>
        public string description;

        /// <summary>冷却时间</summary>
        public float coolTime;

        /// <summary>冷却剩余时间</summary> 
        public float coolRemain;

        /// <summary>魔法消耗</summary>    
        public float costSp;

        /// <summary>攻击距离</summary>    
        public float attackDistance;

        /// <summary>攻击角度</summary>    
        public float attackAngle;

        /// <summary>攻击目标Tags</summary>    
        public List<string> attackTargetTags ;

        /// <summary>攻击目标对象(数组)</summary>    
        public List<Transform> attackTargets;
       [Tooltip("技能影响类型")]
        /// <summary>技能影响类型</summary>    
        public List<string> impactype ;

        /// <summary>连击的下一个技能ID</summary>
        public int nextBatterid;

        /// <summary>伤害比率</summary>    
        public float atkRatio;

        /// <summary>持续时间</summary>    
        public float durationTime;

        /// <summary>伤害间隔</summary>    
        public float atkInterval;

        /// <summary>技能所属对象(OBJ)</summary>
       [HideInInspector]  public GameObject owner;

        /// <summary>技能预制件名称</summary>
         public string prefabName;
        /// <summary>技能预制件对象</summary>
        [HideInInspector] public GameObject skillPrefab;
        /// <summary>动画名称</summary>    
        public string aniamtorName;

        /// <summary>受击特效名称</summary>    
        public string hitFxName;

        /// <summary>受击特效预制件</summary>    
        [HideInInspector] public GameObject hitFxPrefab;

        /// <summary>技能等级</summary>    
        public int level;

        /// <summary>攻击类型</summary>    
        public SkillAttackType attackType;

        /// <summary>选择类型 扇形/矩形</summary>    
        public SelectorType selectorType;

    }
    

}

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace TestSkillSystem
{
    
    //选区接口
    public interface IAttackSelector
    {
    
        /// <summary>
        /// 搜索目标
        /// </summary>
        /// <param name="data"></param>
        /// <param name="skillTF"></param>
        /// <returns></returns>
        List<Transform> SelectTarget(SkillData data, Transform skillTF);

    }


}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace TestSkillSystem
{
    
    //影响效果接口
    public interface IImpactEffects
    {
    
        
        //执行
        void Execute(SkillDeployer deployer);
    }

}


using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;

namespace TestSkillSystem
{
    
    //算法工厂
    public static class DeployerConfigFactory 
    {
    
        
        //命名规则 TestSkillSystem + 枚举名 +AttackSelector
        //例如扇形: TestSkillSystem
        /// <summary>
        /// 创建选区算法
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public static IAttackSelector CreateAttackSelector(SkillData data)
        {
    
            string name = $"TestSkillSystem.{data.selectorType}AttackSelector";
           // Debug.Log(name);
            return CreateObject<IAttackSelector>(name);
        }
        //影响效果命名规范
        //同上 TestSkillSystem. + impactype[?] +Impact 
        /// <summary>
        /// 创建影响算法
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public static List<IImpactEffects> CreateImpactEffects(SkillData data)
        {
    
            List<IImpactEffects> temp= new List<IImpactEffects>();
            for (int i = 0; i < data .impactype.Count; ++i)
            {
    
                string className = $"TestSkillSystem.{data.impactype[i]}Impact";
                //Debug.Log(className);
                temp.Add( CreateObject<IImpactEffects>(className));
            }

            return temp;
        }
        
        private static T CreateObject<T>(string className) where T : class
        {
    

            Type type = Type.GetType(className);
            if (type == null)
            {
    
                Debug.Log($"Type为空ClssName为:{className} ");
            }
            return Activator.CreateInstance(type) as T;
        }
    }

}


using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace TestSkillSystem
{
    
    
    public class SectorAttackSelector : IAttackSelector
    {
    
       /// <summary> 
       /// 扇形/圆形选区
       /// </summary>
       /// <param name="data"></param>
       /// <param name="skillTF">技能所在位置</param>
       /// <returns></returns>
        public List<Transform> SelectTarget(SkillData data, Transform skillTF)
       {
    
           //获取目标
           List<string> tags = data.attackTargetTags;
           List<Transform> resTrans = new List<Transform>();
           for (int i = 0; i < tags.Count; ++i)
           {
    
                GameObject[] tempGOArray = GameObject.FindGameObjectsWithTag(tags[i]);
                if (tempGOArray.Length == 0)
                {
    
                    Debug.Log("标签数组为空");
                }
                    
                foreach (var VARIABLE in tempGOArray)
                {
    
                    resTrans.Add(VARIABLE.transform);
                }
           }
           //判断攻击范围
           resTrans.FindAll(res => Vector3.Distance(res.position, skillTF.position) <= data.attackDistance
                                   && Vector3.Angle(skillTF.forward,res.position-skillTF.position)<=data.attackAngle/2);
           //筛选出活得角色
           resTrans.FindAll(res => res.GetComponent<CharacterStateData>().HP > 0);
           //返回目标
           //依据 单体/群体
           if (data.attackType == SkillAttackType.Group||resTrans.Count==0 )
               return resTrans;
           //huoqu 距离最近的
           int index = 0;
           float min = Vector3.Distance(resTrans[0].position, skillTF.position);
           for (int i=0;i<resTrans.Count;++i)
           {
    
               float temp = Vector3.Distance(resTrans[i].position, skillTF.position);
               if (temp < min)
               {
    
                   min = temp;
                   index = i;
               }
           }
           return new List<Transform> {
    resTrans[index]};
       }
    }
}
using System.Collections.Generic;
using UnityEngine;

namespace TestSkillSystem
{
    
    //具体影响效果
    public class CostSPImpact: IImpactEffects
    {
    
        
        public void Execute(SkillDeployer deployer)
        {
    
            Debug.Log($"消耗法力值执行:{deployer.skillData.owner.name}");
            var staus = deployer.skillData.owner.GetComponent<CharacterStateData>();
            if(staus==null)
                Debug.Log("状态为空");
            staus.SP -= deployer.skillData.costSp;
        }
    }

    public class HurtHPImpact : IImpactEffects
    {
    
        public void Execute(SkillDeployer deployer)
        {
    
            Debug.Log($"伤害生命执行:{deployer.skillData.owner.name}");
            List<Transform> Temp = deployer.skillData.attackTargets;
            for (int i = 0; i < Temp.Count; ++i)
            {
    
                Debug.Log($"{Temp[i].gameObject.name} hp执行前: {Temp[i].GetComponent<CharacterStateData>().HP}");
                Temp[i].GetComponent<CharacterStateData>().HP-=deployer.skillData.owner.GetComponent<CharacterStateData>().BeAttack;
                Debug.Log($"{Temp[i].gameObject.name} hp施行后: {Temp[i].GetComponent<CharacterStateData>().HP}");
            }


        }
    }
    public class AddSPImpact : IImpactEffects
    {
    
        public void Execute(SkillDeployer deployer)
        {
    
            Debug.Log($"回复法力值执行:{deployer.skillData.owner.name}");
            var staus = deployer.skillData.owner.GetComponent<CharacterStateData>();
            if(null==staus)
                Debug.Log("状态为空");
            staus.SP += deployer.skillData.owner.GetComponent<CharacterStateData>().BeAttack*0.5f;
        }
    }
}
using UnityEngine;

namespace TestSkillSystem
{
    
    //近战释放器 测试
    public class MeleeSkillDeployer: SkillDeployer
    {
    
        public override void DeploySkill()
        {
    
            //执行选区算法
            CalculateTargets();
            
            //执行影响算法
            ImpactTargets();
            
            //其他策略 
            skillData.owner.transform.position=new Vector3(skillData.owner.transform.position.x+1,skillData.owner.transform.position.y,skillData.owner.transform.position.z);
        }
        
    }
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_44092253/article/details/115186231

智能推荐

Docker 快速上手学习入门教程_docker菜鸟教程-程序员宅基地

文章浏览阅读2.5w次,点赞6次,收藏50次。官方解释是,docker 容器是机器上的沙盒进程,它与主机上的所有其他进程隔离。所以容器只是操作系统中被隔离开来的一个进程,所谓的容器化,其实也只是对操作系统进行欺骗的一种语法糖。_docker菜鸟教程

电脑技巧:Windows系统原版纯净软件必备的两个网站_msdn我告诉你-程序员宅基地

文章浏览阅读5.7k次,点赞3次,收藏14次。该如何避免的,今天小编给大家推荐两个下载Windows系统官方软件的资源网站,可以杜绝软件捆绑等行为。该站提供了丰富的Windows官方技术资源,比较重要的有MSDN技术资源文档库、官方工具和资源、应用程序、开发人员工具(Visual Studio 、SQLServer等等)、系统镜像、设计人员工具等。总的来说,这两个都是非常优秀的Windows系统镜像资源站,提供了丰富的Windows系统镜像资源,并且保证了资源的纯净和安全性,有需要的朋友可以去了解一下。这个非常实用的资源网站的创建者是国内的一个网友。_msdn我告诉你

vue2封装对话框el-dialog组件_<el-dialog 封装成组件 vue2-程序员宅基地

文章浏览阅读1.2k次。vue2封装对话框el-dialog组件_

MFC 文本框换行_c++ mfc同一框内输入二行怎么换行-程序员宅基地

文章浏览阅读4.7k次,点赞5次,收藏6次。MFC 文本框换行 标签: it mfc 文本框1.将Multiline属性设置为True2.换行是使用"\r\n" (宽字符串为L"\r\n")3.如果需要编辑并且按Enter键换行,还要将 Want Return 设置为 True4.如果需要垂直滚动条的话将Vertical Scroll属性设置为True,需要水平滚动条的话将Horizontal Scroll属性设_c++ mfc同一框内输入二行怎么换行

redis-desktop-manager无法连接redis-server的解决方法_redis-server doesn't support auth command or ismis-程序员宅基地

文章浏览阅读832次。检查Linux是否是否开启所需端口,默认为6379,若未打开,将其开启:以root用户执行iptables -I INPUT -p tcp --dport 6379 -j ACCEPT如果还是未能解决,修改redis.conf,修改主机地址:bind 192.168.85.**;然后使用该配置文件,重新启动Redis服务./redis-server redis.conf..._redis-server doesn't support auth command or ismisconfigured. try

实验四 数据选择器及其应用-程序员宅基地

文章浏览阅读4.9k次。济大数电实验报告_数据选择器及其应用

随便推点

灰色预测模型matlab_MATLAB实战|基于灰色预测河南省社会消费品零售总额预测-程序员宅基地

文章浏览阅读236次。1研究内容消费在生产中占据十分重要的地位,是生产的最终目的和动力,是保持省内经济稳定快速发展的核心要素。预测河南省社会消费品零售总额,是进行宏观经济调控和消费体制改变创新的基础,是河南省内人民对美好的全面和谐社会的追求的要求,保持河南省经济稳定和可持续发展具有重要意义。本文建立灰色预测模型,利用MATLAB软件,预测出2019年~2023年河南省社会消费品零售总额预测值分别为21881...._灰色预测模型用什么软件

log4qt-程序员宅基地

文章浏览阅读1.2k次。12.4-在Qt中使用Log4Qt输出Log文件,看这一篇就足够了一、为啥要使用第三方Log库,而不用平台自带的Log库二、Log4j系列库的功能介绍与基本概念三、Log4Qt库的基本介绍四、将Log4qt组装成为一个单独模块五、使用配置文件的方式配置Log4Qt六、使用代码的方式配置Log4Qt七、在Qt工程中引入Log4Qt库模块的方法八、获取示例中的源代码一、为啥要使用第三方Log库,而不用平台自带的Log库首先要说明的是,在平时开发和调试中开发平台自带的“打印输出”已经足够了。但_log4qt

100种思维模型之全局观思维模型-67_计算机中对于全局观的-程序员宅基地

文章浏览阅读786次。全局观思维模型,一个教我们由点到线,由线到面,再由面到体,不断的放大格局去思考问题的思维模型。_计算机中对于全局观的

线程间控制之CountDownLatch和CyclicBarrier使用介绍_countdownluach于cyclicbarrier的用法-程序员宅基地

文章浏览阅读330次。一、CountDownLatch介绍CountDownLatch采用减法计算;是一个同步辅助工具类和CyclicBarrier类功能类似,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。二、CountDownLatch俩种应用场景: 场景一:所有线程在等待开始信号(startSignal.await()),主流程发出开始信号通知,既执行startSignal.countDown()方法后;所有线程才开始执行;每个线程执行完发出做完信号,既执行do..._countdownluach于cyclicbarrier的用法

自动化监控系统Prometheus&Grafana_-自动化监控系统prometheus&grafana实战-程序员宅基地

文章浏览阅读508次。Prometheus 算是一个全能型选手,原生支持容器监控,当然监控传统应用也不是吃干饭的,所以就是容器和非容器他都支持,所有的监控系统都具备这个流程,_-自动化监控系统prometheus&grafana实战

React 组件封装之 Search 搜索_react search-程序员宅基地

文章浏览阅读4.7k次。输入关键字,可以通过键盘的搜索按钮完成搜索功能。_react search