使用场景:有大量的方法,方法中有大量相似的效果与功能。
需要脚本: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();
}
}