我的需求是要在 FlowScript 中写技能,然后在角色中引用这个 FlowScript,当需要释放技能的时候就启用这个 FlowScript
ScriptableAbility 是用来存代表技能内容的 FlowScript 的
之后可能还会有其他功能
Assets/MeowFramework/Core/Scriptable/ScriptableAbility/ScriptableAbility.cs
// ----------------------------------------------
// 作者: 廉价喵
// 创建于: 05/04/2022 15:25
// 最后一次修改于: 11/04/2022 23:04
// 版权所有: CheapMeowStudio
// 描述:
// ----------------------------------------------
using FlowCanvas;
using Sirenix.OdinInspector;
using UnityEngine;
namespace MeowFramework.Core.Scriptable
{
///
/// 可资产化技能
///
[CreateAssetMenu(fileName = "New Scriptable Ability",
menuName = "MeowFramework/Scriptable Ability/Create Scriptable Ability")]
public class ScriptableAbility : SerializedScriptableObject
{
///
/// 技能 ID
///
[ValidateInput("IDValidate")]
[Tooltip("技能 ID")]
public int AbilityID;
///
/// 俗名
///
[Tooltip("俗名")]
public string FriendlyName;
///
/// 概述
///
[TextArea]
[Tooltip("概述")]
public string Description = "";
///
/// 技能 FlowScript
///
[Required]
[Tooltip("技能 FlowScript")]
public FlowScript AbilityFlowScript;
///
/// 验证函数:是否为非负数
///
///
///
private bool IDValidate(int value) => value >= 0;
}
}
为了让技能的 FlowScript 能够执行,需要有一个 FlowScriptController,而 FlowScriptController 又需要挂在一个物体上,因此,一个技能实体需要一个空物体,空物体上面要挂一个 FlowScriptController,这个 FlowScriptController 的 FlowScript 要赋为 ScriptableAbility 中设置好的 FlowScript
Assets/MeowFramework/Core/Entity/AbilityEntity.cs
// ----------------------------------------------
// 作者: 廉价喵
// 创建于: 11/04/2022 17:49
// 最后一次修改于: 11/04/2022 20:27
// 版权所有: CheapMeowStudio
// 描述:
// ----------------------------------------------
using FlowCanvas;
using Sirenix.OdinInspector;
namespace MeowFramework.Core.Entity
{
public class AbilityEntity : SerializedMonoBehaviour
{
///
/// FlowScript 控制器
///
private FlowScriptController flowScriptController;
private void OnEnable()
{
flowScriptController = GetComponent<FlowScriptController>();
}
///
/// 启动技能
///
public void StartAbility()
{
flowScriptController.StartBehaviour();
}
///
/// 暂停技能
///
public void PauseAbility()
{
flowScriptController.PauseBehaviour();
}
///
/// 结束技能
///
public void StopAbility()
{
flowScriptController.StopBehaviour();
}
///
/// 重置技能
///
public void ResetAbility()
{
flowScriptController.RestartBehaviour();
}
}
}
有一点很怪的是,private Dictionary 在监视器上的设置没办法保存下来。
点开挂有 ActorBase 的 GameObject,在监视器中选择 ActorBase,为暴露出来的 Dictionary 添加一项,然后离开监视器界面,再点开原来的 GameObject,监视器中 ActorBase 的 Dictionary 又变为初始化的空值了
但是 public Dictionary 就不会
所以就,Odin 很神奇
实际释放技能的时候就是生成一个技能实体,然后调用这个技能实体的方法
Assets/MeowFramework/Core/Entity/Actor/ActorBase.cs
// ----------------------------------------------
// 作者: 廉价喵
// 创建于: 28/03/2022 17:43
// 最后一次修改于: 11/04/2022 20:41
// 版权所有: CheapMeowStudio
// 描述:
// ----------------------------------------------
using System.Collections.Generic;
using System.ComponentModel;
using MeowFramework.Core.FrameworkComponent;
using MeowFramework.Core.Scriptable;
using NodeCanvas.Framework;
using Sirenix.OdinInspector;
using UnityEngine;
namespace MeowFramework.Core.Actor
{
public class ActorBase : SerializedMonoBehaviour
{
///
/// 血量
///
[BoxGroup("Attribute")]
[Tooltip("血量")]
public ActorAttribute<float> HP = new ActorAttribute<float>();
///
/// 最大血量
///
[BoxGroup("Attribute")]
[Tooltip("最大血量")]
public ActorAttribute<float> MaxHP = new ActorAttribute<float>();
///
/// 可用技能字典
///
[BoxGroup("GamePlay")]
[Tooltip("可用技能字典")]
public Dictionary<int,ScriptableAbility> availableAbilityDictionary = new Dictionary<int,ScriptableAbility>();
///
/// 出生时附加的 Buff 的字典
///
[BoxGroup("GamePlay")]
[Tooltip("出生时附加的 Buff 的字典")]
public Dictionary<int,ScriptableBuff> bornBuffDictionary = new Dictionary<int,ScriptableBuff>();
///
/// 运行时的 Buff 的列表
///
[BoxGroup("GamePlay")]
[ShowInInspector]
[Sirenix.OdinInspector.ReadOnly]
[Tooltip("运行时的 Buff 的列表")]
private List<int> aliveBuffList = new List<int>();
///
/// 尝试释放技能
///
/// 技能 ID
///
public bool TryCastAbility(int availableAbilityIndex)
{
// 如果没有这个技能,就返回假
if (!availableAbilityDictionary.ContainsKey(availableAbilityIndex))
return false;
// 如果有这个技能,获得技能数据
var scriptableAbility = availableAbilityDictionary[availableAbilityIndex];
// 生成技能实体
var abilityEntity = AbilityComponent.SpawnAbilityEntity(scriptableAbility);
// 执行技能
abilityEntity.StartAbility();
return true;
}
}
}
后来我觉得其实没有必要使用 ID 来查找 ScriptableAbility 了
因为我是直接在 ActorBase 中就已经引用了 ScriptableAbility 的
所以接下来,对于 AbilityComponent 组件,要做的就只是生成技能实体的函数而已
Assets/MeowFramework/Core/FrameworkComponent/AbilityComponent.cs
// ----------------------------------------------
// 作者: 廉价喵
// 创建于: 11/04/2022 9:29
// 最后一次修改于: 11/04/2022 20:37
// 版权所有: CheapMeowStudio
// 描述:
// ----------------------------------------------
using FlowCanvas;
using MeowFramework.Core.Entity;
using MeowFramework.Core.Scriptable;
using Sirenix.OdinInspector;
using UnityEngine;
namespace MeowFramework.Core.FrameworkComponent
{
///
/// 框架中的技能组件
///
[InlineEditor]
public class AbilityComponent : BaseComponent
{
///
/// 技能对象池的根节点
///
private static Transform AbilityPoolRoot;
private void Start()
{
// 创建技能对象池的根节点
GameObject root = new GameObject();
root.name = "AbilityPoolRoot";
root.transform.SetParent(transform);
AbilityPoolRoot = root.transform;
}
///
/// 生成一个技能实体
///
/// 可资产化技能
///
public static AbilityEntity SpawnAbilityEntity(ScriptableAbility scriptableAbility)
{
// 创建空物体作为 Ability 物体
GameObject entity = new GameObject();
// Ability 物体放在统一的 root 下
entity.transform.SetParent(AbilityPoolRoot);
// 初始化 FlowScript 控制器
FlowScriptController flowScriptController = entity.AddComponent<FlowScriptController>();
flowScriptController.StopBehaviour();
flowScriptController.behaviour = scriptableAbility.AbilityFlowScript;
// 为 Ability 物体添加 AbilityEntity,方便控制
return entity.AddComponent<AbilityEntity>();
}
}
}
实际运行的时候我发现 AbilityComponent 的 Start 可能慢于其他脚本,AbilityPoolRoot 可能来不及赋值,导致别人在 Start 的时候用到 SpawnAbilityEntity 时 AbilityPoolRoot == null
于是我就想在监视器中配置 AbilityPoolRoot
Assets/MeowFramework/Core/FrameworkComponent/AbilityComponent.cs
// ----------------------------------------------
// 作者: 廉价喵
// 创建于: 11/04/2022 9:29
// 最后一次修改于: 11/04/2022 21:50
// 版权所有: CheapMeowStudio
// 描述:
// ----------------------------------------------
using FlowCanvas;
using MeowFramework.Core.Entity;
using MeowFramework.Core.Scriptable;
using Sirenix.OdinInspector;
using UnityEngine;
namespace MeowFramework.Core.FrameworkComponent
{
///
/// 框架中的技能组件
///
[InlineEditor]
public class AbilityComponent : BaseComponent
{
///
/// 技能对象池的根节点
///
[Required]
[ShowInInspector]
[Tooltip("技能对象池的根节点")]
private Transform abilityPoolRoot;
///
/// 技能对象池的根节点
///
public static Transform AbilityPoolRoot => AbilityPoolRoot;
///
/// 生成一个技能实体
///
/// 可资产化技能
///
public static AbilityEntity SpawnAbilityEntity(ScriptableAbility scriptableAbility)
{
// 创建空物体作为 Ability 物体
GameObject entity = new GameObject();
// Ability 物体放在统一的 root 下
Debug.Log(AbilityPoolRoot);
entity.transform.SetParent(AbilityPoolRoot);
// 初始化 FlowScript 控制器
FlowScriptController flowScriptController = entity.AddComponent<FlowScriptController>();
flowScriptController.StopBehaviour();
flowScriptController.behaviour = scriptableAbility.AbilityFlowScript;
// 为 Ability 物体添加 AbilityEntity,方便控制
return entity.AddComponent<AbilityEntity>();
}
}
}
实际用的时候还是会找到 null
好吧这是之前我见到的 static 变量不能通过监视器设置的问题
那就只能惰性求值了
但是写了之后发现原来 transform 是动态的
好吧这很合理
我想写一个静态属性,但是也拿不到
好吧后面才知道是用 Awake
根本没什么机会用,都忘了hhhh
Assets/MeowFramework/Core/FrameworkComponent/AbilityComponent.cs
// ----------------------------------------------
// 作者: 廉价喵
// 创建于: 11/04/2022 9:29
// 最后一次修改于: 11/04/2022 23:04
// 版权所有: CheapMeowStudio
// 描述:
// ----------------------------------------------
using System;
using FlowCanvas;
using MeowFramework.Core.Entity;
using MeowFramework.Core.Scriptable;
using NodeCanvas.Tasks.Actions;
using Sirenix.OdinInspector;
using UnityEngine;
namespace MeowFramework.Core.FrameworkComponent
{
///
/// 框架中的技能组件
///
[InlineEditor]
public class AbilityComponent : BaseComponent
{
///
/// 技能对象池的根节点
///
private static Transform AbilityPoolRoot;
private void Awake()
{
// 创建技能对象池的根节点
GameObject root = new GameObject();
root.name = "AbilityPoolRoot";
root.transform.SetParent(transform);
AbilityPoolRoot = root.transform;
}
///
/// 生成一个技能实体
///
/// 可资产化技能
///
public static AbilityEntity SpawnAbilityEntity(ScriptableAbility scriptableAbility)
{
if(!AbilityPoolRoot)
Debug.LogError("没有技能对象池的根节点!");
// 创建空物体作为 Ability 物体
GameObject entity = new GameObject();
// Ability 物体放在统一的 root 下
entity.transform.SetParent(AbilityPoolRoot);
entity.name = scriptableAbility.FriendlyName == "" ? "NewAbilityEntity" : scriptableAbility.FriendlyName + "Entity";
// 初始化 FlowScript 控制器
FlowScriptController flowScriptController = entity.AddComponent<FlowScriptController>();
flowScriptController.StopBehaviour();
flowScriptController.behaviour = scriptableAbility.AbilityFlowScript;
// 为 Ability 物体添加 AbilityEntity,方便控制
return entity.AddComponent<AbilityEntity>();
}
}
}
现在我也终于要做射击了
我在想,射击这个东西
是做成一个可以射击的 buff,然后这个 buff 的 flowscript 中写一个绑定 inputcontroller 的事件,连一个 cooldown,再连接到射击函数,比较好
还是单独做一个技能,在主角的 flowscript 中写一个绑定 inputcontroller 的事件,连一个 cooldown,再连接到射击技能,技能里面放装有射击函数的 flowscript,比较好
果然还是应该一个 buff 比较好,毕竟执行 flowscript 是需要生成一个空物体装 flowscriptcontroller 来执行的,如果是单独做一个技能的话,技能执行完就要删除掉生成的空物体,感觉对于射击这种高频事件不友好。虽然也可以用对象池,但是感觉其实也没必要
对于一个一直存在的 buff,就只需要生成一次空物体,加一次 flowscriptcontroller 就好了
相比而言 允许射击 buff 更贴合能力一点,因为 buff 后面要做 buff 之间的检查,打断,消除,什么的……思考
其实我犹豫的就是,我就是感觉,那要是这样的话,那么所有的技能其实都可以放到 buff 里面了
角色能够使用三个可触发的技能,那么就让角色出生的时候带三个 buff,这三个 buff 的 flowscript 中就是一个可触发的技能内容
那这样的话前面做的技能框架要改成 Buff 框架了
Buff 设置需要用到的枚举
Assets/MeowFramework/Core/Scriptable/ScriptableBuff/BuffDurationType.cs
// ----------------------------------------------
// 作者: 廉价喵
// 创建于: 11/04/2022 19:28
// 最后一次修改于: 11/04/2022 20:26
// 版权所有: CheapMeowStudio
// 描述:
// ----------------------------------------------
namespace MeowFramework.Core.Scriptable
{
///
/// 可资产化 Buff 持续时间种类
///
public enum BuffDurationType
{
///
/// 瞬间
///
Instant = 0,
///
/// 持续一帧
///
OneFrame,
///
/// 持续一段时间
///
Durable,
///
/// 无限时间
///
Infinite,
}
}
ScriptableBuff 保存 Buff 的设置
Assets/MeowFramework/Core/Scriptable/ScriptableBuff/ScriptableBuff.cs
// ----------------------------------------------
// 作者: 廉价喵
// 创建于: 27/03/2022 9:51
// 最后一次修改于: 11/04/2022 23:56
// 版权所有: CheapMeowStudio
// 描述:
// ----------------------------------------------
using System.Collections.Generic;
using FlowCanvas;
using MeowFramework.Core.FrameworkComponent;
using Sirenix.OdinInspector;
using UnityEngine;
namespace MeowFramework.Core.Scriptable
{
///
/// 可资产化 Buff
///
[CreateAssetMenu(fileName = "New Scriptable Buff", menuName = "MeowFramework/Scriptable Buff/Create Scriptable Buff")]
public class ScriptableBuff : SerializedScriptableObject
{
///
/// Buff ID
///
[ValidateInput("IDValidate")]
[Tooltip("Buff ID")]
public int BuffID;
///
/// 俗名
///
[Tooltip("俗名")]
public string FriendlyName;
///
/// 概述
///
[TextArea]
[Tooltip("概述")]
public string Description = "";
///
/// 堆叠层数限制
///
[EnumToggleButtons]
[BoxGroup("Layer")]
[Tooltip("堆叠层数限制")]
public BuffLayerStackLimitType LayerStackLimit;
///
/// 最大堆叠层数
///
[ShowIf("@LayerStackLimit == BuffLayerStackLimitType.Limited")]
[BoxGroup("Layer")]
[Tooltip("最大堆叠层数")]
public int MaxLayers;
///
/// 什么时候应该重置 Command
///
[EnumToggleButtons]
[ShowIf("@LayerStackLimit == BuffLayerStackLimitType.Limited && MaxLayers == 0")]
[BoxGroup("Layer")]
[Tooltip("什么时候应该重置命令")]
public BuffResetCommandType WhenShouldResetCommand;
///
/// 指令持续时间种类
///
[EnumToggleButtons]
[BoxGroup("Time")]
[Tooltip("指令持续时间种类")]
public BuffDurationType DurationType;
///
/// 指令持续时间
///
[ShowIf("DurationType",BuffDurationType.Durable)]
[BoxGroup("Time")]
[ValidateInput("DurationValidate")]
[Tooltip("指令持续时间")]
public float Duration;
///
/// 什么时候应该重置 ElapsedTime
///
[EnumToggleButtons]
[ShowIf("@DurationType == BuffDurationType.Durable || DurationType == BuffDurationType.Infinite")]
[BoxGroup("Time")]
[Tooltip("什么时候应该重置已流逝时间")]
public BuffResetElapsedTimeType WhenShouldResetElapsedTimeType;
///
/// 是否能够被打断
///
[ShowIf("@DurationType == BuffDurationType.Durable || DurationType == BuffDurationType.Infinite")]
[BoxGroup("Time")]
[Tooltip("是否能够被打断")]
public bool CanBeInterrupted;
///
/// Buff 元素 Tag 字典
///
[Tooltip("元素标签字典")]
public Dictionary<ElementType, bool> ElementTypeTagDictionary = new Dictionary<ElementType, bool>
{
{ElementType.Pyro, false},
{ElementType.Hydro, false},
{ElementType.Anemo, false},
{ElementType.Electro, false},
{ElementType.Cryo, false},
{ElementType.Geo, false},
};
///
/// Buff FlowScript
///
[Tooltip("FlowScript")]
public FlowScript BuffFlowScript;
///
/// 初始时注册 Buff ID
///
private void OnEnable()
{
// 根据新的 ID 添加键值对
BuffComponent.ScriptableBuffDictionary[BuffID] = this;
}
///
/// 验证函数:是否为非负数
///
///
///
private bool IDValidate(int value) => value >= 0;
///
/// 验证函数:是否为非负数
///
///
///
private bool DurationValidate(float value) => value >= 0;
}
}
为了让 FlowScript 有一个挂靠的地方,所以要建一套 Entity 来承载
Assets/MeowFramework/Core/Entity/FlowScriptEntity.cs
// ----------------------------------------------
// 作者: 廉价喵
// 创建于: 11/04/2022 17:49
// 最后一次修改于: 12/04/2022 0:17
// 版权所有: CheapMeowStudio
// 描述:
// ----------------------------------------------
using FlowCanvas;
using Sirenix.OdinInspector;
namespace MeowFramework.Core.Entity
{
///
/// 挂载 FlowScript 的实体
///
public class FlowScriptEntity : SerializedMonoBehaviour
{
///
/// FlowScript 控制器
///
public FlowScriptController flowScriptController;
private void Awake()
{
flowScriptController = GetComponent<FlowScriptController>();
}
}
}
BuffEntity 是可以解析 ScriptableBuff 中的设置的实体
Assets/MeowFramework/Core/Entity/BuffEntity.cs
// ----------------------------------------------
// 作者: 廉价喵
// 创建于: 11/04/2022 23:41
// 最后一次修改于: 12/04/2022 0:17
// 版权所有: CheapMeowStudio
// 描述:
// ----------------------------------------------
using MeowFramework.Core.Scriptable;
using Sirenix.OdinInspector;
using UnityEngine;
namespace MeowFramework.Core.Entity
{
///
/// 挂载 FlowScript 的实体
///
public class BuffEntity : FlowScriptEntity
{
///
/// Buff 释放者
///
[ShowInInspector]
[Sirenix.OdinInspector.ReadOnly]
[Tooltip("Buff 释放者")]
private ActorBase caster;
///
/// Buff 接受者
///
[ShowInInspector]
[Sirenix.OdinInspector.ReadOnly]
[Tooltip("Buff 接受者")]
private ActorBase receiver;
///
/// 初始化 Buff
///
public void BuffInitialize(ActorBase caster, ActorBase receiver, ScriptableBuff scriptableBuff)
{
this.caster = caster;
this.receiver = receiver;
}
///
/// 启动 Buff
///
public void StartBuff()
{
flowScriptController.StartBehaviour();
}
///
/// 暂停 Buff
///
public void PauseBuff()
{
flowScriptController.PauseBehaviour();
}
///
/// 结束 Buff
///
public void StopBuff()
{
flowScriptController.StopBehaviour();
}
///
/// 重置 Buff
///
public void ResetBuff()
{
flowScriptController.RestartBehaviour();
}
}
}
Buff 组件的话,由于生成 Buff 的函数被分开了,所以现在唯一有的功能就是记录 Buff 字典,然后提供 Buff 实体的根节点
Assets/MeowFramework/Core/FrameworkComponent/BuffComponent.cs
// ----------------------------------------------
// 作者: 廉价喵
// 创建于: 11/04/2022 10:59
// 最后一次修改于: 11/04/2022 23:34
// 版权所有: CheapMeowStudio
// 描述:
// ----------------------------------------------
using System.Collections.Generic;
using System.ComponentModel;
using MeowFramework.Core.Scriptable;
using Sirenix.OdinInspector;
namespace MeowFramework.Core.FrameworkComponent
{
///
/// 框架中的 Buff 组件
///
[InlineEditor]
public class BuffComponent : BaseComponent
{
///
/// 技能字典
///
[Sirenix.OdinInspector.ReadOnly]
[ShowInInspector]
[Description("Buff 字典")]
public static Dictionary<int, ScriptableBuff> ScriptableBuffDictionary = new Dictionary<int, ScriptableBuff>();
}
}
本来是想把生成 Buff 这个功能放在 Component 里面的,但是我想到,如果要把它做成一个 static 的,那为什么不干脆放到一个通用类里面呢
于是就把生成 Buff 的函数放到了 BuffUtility
Assets/MeowFramework/Core/Utility/BuffUtility.cs
// ----------------------------------------------
// 作者: 廉价喵
// 创建于: 11/04/2022 23:27
// 最后一次修改于: 12/04/2022 0:19
// 版权所有: CheapMeowStudio
// 描述:
// ----------------------------------------------
using FlowCanvas;
using MeowFramework.Core.Entity;
using MeowFramework.Core.FrameworkComponent;
using UnityEngine;
namespace MeowFramework.Core.Utility
{
public static class BuffUtility
{
///
/// 尝试释放 Buff
///
/// Buff 释放者
/// Buff 接受者
/// Buff ID
/// Buff 实体
public static BuffEntity TryAddBuff(ActorBase caster, ActorBase receiver, int buffIndex)
{
// 如果没有这个 Buff,就返回空
if (!BuffComponent.ScriptableBuffDictionary.ContainsKey(buffIndex))
{
Debug.LogError($"Buff 组件的 Buff 字典中不存在键为 {buffIndex} 的条目!");
return null;
}
// 如果有这个 Buff,获得 Buff 数据
var scriptableBuff = BuffComponent.ScriptableBuffDictionary[buffIndex];
if(!BuffComponent.BuffPoolRoot)
Debug.LogError("Buff 组件下面没有对象池的根节点!");
// 创建空物体作为 Buff 物体
GameObject entity = new GameObject();
// Ability 物体放在统一的 root 下
entity.transform.SetParent(BuffComponent.BuffPoolRoot);
entity.name = scriptableBuff.FriendlyName == "" ? "NewBuffEntity" : scriptableBuff.FriendlyName + "Entity";
// 初始化 FlowScript 控制器
FlowScriptController flowScriptController = entity.AddComponent<FlowScriptController>();
flowScriptController.StopBehaviour();
flowScriptController.behaviour = scriptableBuff.BuffFlowScript;
// 为 Buff 物体添加 BuffEntity,方便控制
var buffEntity = entity.AddComponent<BuffEntity>();
buffEntity.BuffInitialize(caster, receiver, scriptableBuff);
// 执行 Buff
buffEntity.StartBuff();
return buffEntity;
}
}
}
Buff 释放的测试 FlowScript
用于测试的 Buff
用于测试的 Buff 的 FlowScript
运行结果
看上去这样应该没啥大问题了