Unity - 技能框架

使用场景:有大量的方法,方法中有大量相似的效果与功能。

需要脚本:ArrayHelper,ResourcesManager,Factory类

主要思想:用SkillStatus存放技能的数据;角色释放者身上挂一个SkillManager的脚本,用于管理释放者身上所能释放的所有技能数据,并对外提供受否能释放技能的方法和释放技能的方法。释放技能通过调用SkillDeployer脚本来实现。在Skill'Deployer脚本中根据技能数据实现范围的选择和调用各类技能效果,从而实现技能释放。

1、SkillStatus,技能数据存放,可在编辑器面板配置

        这个只是一个数据的存放器,所以没有必要让它继承MonoBehaviour变成unity的脚本,但是为了配置方便可以将其序列化,实现可以在编辑器面板配置数据。

[Serializable]

        可以设两个全局枚举变量

        1)攻击类型:是范围还是单攻

 public enum SkillAttackType
 {
     Single,
     Group
 }

        2)范围选择类型:矩形还是扇形

public enum SelectorType
{
    Sector,//扇形
    Rectangle//矩形
}
[Serializable]
public class SkillStatus
{
    //技能ID
    public int skillID;
    //技能名称
    public string skillName;
    //技能描述
    public string skillDescription;
    //冷却时间
    public int coolTime;
    //冷却剩余
    public int coolRemain;
    //魔法消耗
    public int costSP;
    //攻击距离
    public float attackDistance;
    //攻击角度
    public float attackAngle;
    //攻击目标
    public string[] attackTargetTags = { "Enemy" };
    //攻击对象数组
    [HideInInspector]
    public Transform[] attackTargets;
    //技能影响类型
    public string[] impactType = { "CostSP", "Damage" };
    //连击的下一个技能编号
    public int nextBatterId;
    //伤害比率
    public float atkRatio;
    //持续时间
    public float durationTime;
    //伤害间隔
    public float atkIntervel;
    [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;
    //选择攻击类型,扇形,矩形
    public SelectorType selectorType;
}

 

 2、SkillManager脚本:挂在技能释放者身上,管理技能数据,对外提供方法。

public class CharacterSkillManage : MonoBehaviour
{
    //技能列表
    public SkillStatus[] skillStatus;
    //角色基本数据
    private CharacterBasic characterBasic;

    private void Start ()
    {
        for (int i = 0; i < skillStatus.Length; i++)
        {
            //初始化技能列表中的技能
            InitSkill(skillStatus[i]);
        }
        characterBasic = GetComponent<CharacterBasic>();
    }

    //初始化
    private void InitSkill(SkillStatus skill)
    {
        //存放技能的预制体准备
        //这里要用到ResourceManager可以实现用名字直接load资源
        skill.skillPrefab = ResourceManager.Load<GameObject>(skill.prefabName);
        skill.owner = gameObject;
    }

    //对外提供技能准备的方法用于判断技能是否可以释放
    //技能释放条件:冷却,法力
    public SkillStatus PrepareSkill(int id)
    {
        //这里用了一个自己写的便于列表操作的静态方法
        SkillStatus skill = skillStatus.ArrayFind(s => s.skillID == id);
        if (skill != default(SkillStatus) && skill.coolRemain<=0&& skill.costSP <= characterBasic.characterSP)
        {
            return skill;
        }
        else
        {
            return null;
        }
    }

    //对外提供的释放技能的方法
    //生成技能
    public void GenerateSkill(SkillStatus skill)
    {
       //这里用了对象池,管理技能对象
        GameObject skillGo = GameObjectPool.Instance.CreateObject(skill.prefabName, skill.skillPrefab, transform.position, transform.rotation);

        //调用技能释放器释放技能
        SkillDeployer skillDeployer = skillGo.GetComponent<SkillDeployer>();
        skillDeployer.SkillStatus = skill;
        skillDeployer.DeployerSkill();

        //利用对象池回收技能对象
        GameObjectPool.Instance.CollectObject(skillGo,skill.durationTime);
        //技能冷却
        StartCoroutine(CoolTimeDown(skill));
    }
    //技能冷却
    private IEnumerator CoolTimeDown(SkillStatus skill)
    {
        skill.coolRemain = skill.coolTime;
        while (skill.coolRemain > 0)
        {
            yield return new WaitForSeconds(1);
            skill.coolRemain--;
        }
    }
}

3、SkillDeployer:技能释放器,用于确定释放范围和技能的效果释放

        1)范围选择,创建一个父类,通过技能数据中的范围类型,选择子类,在通过父类释放,实现变化的隔离。因为这个类只需要提供方法,所以可以用接口作为父类

public interface IAttackSelector
{
    /// <summary>
    /// 搜索目标
    /// </summary>
    /// <param name="skill">技能数据</param>
    /// <param name="skillTF">技能所在的物体的transform</param>
    /// <returns></returns>
    public Transform[] SelectTarget(SkillStatus skill,Transform skillTF);
}

        返回的是范围内的攻击目标的Transform

        举一个扇形范围选择的例子

//继承接口
public class SectorAttackSelector : IAttackSelector
{
    public Transform[] SelectTarget(SkillStatus skill, Transform skillTF)
    {
        List<Transform> targets = new List<Transform>();
        foreach (string tag in skill.attackTargetTags)
        {
            GameObject[] enemies =GameObject.FindGameObjectsWithTag(tag);
            Transform[] targetEnemies = enemies.Select(t => t.transform);
            targets.AddRange(targetEnemies);
        }
        targets =targets.FindAll(t => 
        Vector3.Distance(t.position, skillTF.position) <= skill.attackDistance &&
        //二维计算角度自己写的不一定对
        Vector2.Angle(Vector2.right,t.position-skillTF.position)<= skill.attackAngle/2
        );
        targets = targets.FindAll(t => t.GetComponent<EnemyAI>().enemy.blood > 0);
        Transform[] result = targets.ToArray();
        if (skill.attackType == SkillAttackType.Group||result.Length==0)
        {
            return result;
        }
        else
        {
            Transform min =result.GetMin(t=>Vector3.Distance( t.position,skillTF.position));
            return new Transform[] { min };
        }
    }
}

         2)效果接口:与范围选择的接口类似,只不过效果有比较多在释放器的脚本调用时,需要用数组来存放所有需要用的效果接口。

public interface IImpactEffect
{
    void Execute(SkillDeployer deployer);
}

        对外提供执行效果的方法

        这里传入释放器的脚本是为了便于子类使用协程

        举一个消耗法力的例子

public class CostSPImpact : IImpactEffect
{
    public void Execute(SkillDeployer deployer)
    {
        var status =deployer.SkillStatus.owner.GetComponent<CharacterBasic>();
        if (status == null)
        {
            Debug.LogError("未找到技能释放者");
        }
        else
        {
            status.characterSP -= deployer.SkillStatus.costSP;
        }
    }
}

释放器也可以作为父类,子类可以有更大的空间做每个技能的个性化

public abstract class SkillDeployer : MonoBehaviour
{
    //用于接收技能数据
    private SkillStatus skillStatus;
    //接收到技能数据后立刻初始化
    public SkillStatus SkillStatus
    {
        get
        {
            return skillStatus;
        }
        set
        {
            skillStatus = value;
            InitDeployer();
        }
    }
    //用于存放攻击范围和技能效果的接口
    private IAttackSelector selector;
    private IImpactEffect[] impactArray;

    //初始化
    private void InitDeployer()
    {
        //通过工厂类的方法根据技能数据,生成对应脚本放入接口的变量内
        selector = DeployerConfigFactory.CreateIAttackSelector(skillStatus);
        impactArray = DeployerConfigFactory.CreateIImpactEffect(skillStatus);
    }

    //调用范围选择的接口方法进行选择,将对象存入skillStatus中
    public void CalculateTargets()
    {
       skillStatus.attackTargets = selector.SelectTarget(skillStatus, transform);
    }

    //效果释放
    public void ImpactTargets()
    {
        for (int i = 0; i < impactArray.Length; i++)
        {
            impactArray[i].Execute(this);
        }
    }
    public abstract void DeployerSkill();
}

举一个单攻近战的例子(其实也看不出什么)

 public class CloseSkillDeployer : SkillDeployer
 {
     public override void DeployerSkill()
     {
         CalculateTargets();
         ImpactTargets();
     }
 }

  • 12
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值