架构大更

This commit is contained in:
SoulliesOfficial
2026-03-20 11:56:50 -04:00
parent e60ef64d01
commit d09b58fd80
3663 changed files with 15232012 additions and 105579 deletions

View File

@@ -0,0 +1,139 @@
using System;
using System.Collections.Generic;
using Continentis.MainGame.Character;
using Cysharp.Threading.Tasks;
namespace SLSFramework.General
{
/// <summary>
/// 命令静态工厂。
/// Mod 制作者通过此类创建常用命令,无需直接实例化具体命令类。
/// </summary>
public static class Cmd
{
// ── 基础工厂 ─────────────────────────────────────────────────────
/// <summary>创建一条等待指定秒数后完成的命令。</summary>
public static CommandBase Wait(float seconds)
{
return new WaitCommand(seconds);
}
/// <summary>创建一条执行同步 Action 的命令。</summary>
public static CommandBase Do(Action action)
{
return new ActionCommand(action);
}
/// <summary>创建一条执行异步 UniTask 的命令。</summary>
public static CommandBase Do(Func<UniTask> asyncAction)
{
return new AsyncActionCommand(asyncAction);
}
/// <summary>创建一条延迟指定秒数后执行同步 Action 的命令。</summary>
public static CommandBase After(float seconds, Action action)
{
return new ActionCommand(action).SetDelay(seconds);
}
// ── 组合工厂 ─────────────────────────────────────────────────────
/// <summary>创建一个顺序执行的命令组。</summary>
public static CommandGroup Sequential(params CommandBase[] cmds)
{
return new CommandGroup(ExecutionMode.Sequential, cmds);
}
/// <summary>创建一个并行执行的命令组。</summary>
public static CommandGroup Parallel(params CommandBase[] cmds)
{
return new CommandGroup(ExecutionMode.Parallel, cmds);
}
// ── Target 遍历工厂 ───────────────────────────────────────────────
/// <summary>
/// 对 <paramref name="targets"/> 列表中的每个目标调用 <paramref name="factory"/>
/// 将结果按顺序组合为一个 Sequential <see cref="CommandGroup"/>。
/// </summary>
public static CommandGroup ForEachTarget(
List<CharacterBase> targets,
Func<CharacterBase, CommandBase> factory)
{
var group = new CommandGroup(ExecutionMode.Sequential);
foreach (CharacterBase target in targets)
{
CharacterBase captured = target;
group.AddCommand(factory(captured));
}
return group;
}
/// <summary>
/// 对 <paramref name="targets"/> 列表中的每个目标调用 <paramref name="factory"/>
/// 将结果按顺序组合为一个 Sequential <see cref="CommandGroup"/>。
/// </summary>
public static CommandGroup ForEachTarget(
List<CharacterBase> targets,
Func<CharacterBase, CommandGroup> factory)
{
var group = new CommandGroup(ExecutionMode.Sequential);
foreach (CharacterBase target in targets)
{
CharacterBase captured = target;
group.AddCommand(factory(captured));
}
return group;
}
// ── 内部实现类 ────────────────────────────────────────────────────
private sealed class WaitCommand : CommandBase
{
private readonly float seconds;
public WaitCommand(float seconds)
{
this.seconds = seconds;
}
protected override async UniTask ExecuteAsync(CommandContext outerContext)
{
await UniTask.Delay(TimeSpan.FromSeconds(seconds));
}
}
private sealed class ActionCommand : CommandBase
{
private readonly Action action;
public ActionCommand(Action action)
{
this.action = action;
}
protected override UniTask ExecuteAsync(CommandContext outerContext)
{
action?.Invoke();
return UniTask.CompletedTask;
}
}
private sealed class AsyncActionCommand : CommandBase
{
private readonly Func<UniTask> asyncAction;
public AsyncActionCommand(Func<UniTask> asyncAction)
{
this.asyncAction = asyncAction;
}
protected override async UniTask ExecuteAsync(CommandContext outerContext)
{
if (asyncAction != null)
await asyncAction();
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e2394fde2169fba46b02ebba695e5210

View File

@@ -0,0 +1,55 @@
using System;
using Cysharp.Threading.Tasks;
namespace SLSFramework.General
{
/// <summary>
/// 命令基类。子类重写 <see cref="ExecuteAsync"/> 实现具体逻辑。
/// 框架通过 <see cref="RunAsync"/> 调用,先处理可选延迟再执行核心逻辑。
/// </summary>
public abstract class CommandBase
{
/// <summary>命令自身的私有上下文,可携带局部参数。</summary>
public CommandContext selfContext;
private float delayBeforeExecute;
public CommandBase(CommandContext selfContext = null)
{
this.selfContext = selfContext ?? new CommandContext();
}
/// <summary>设置命令执行前的延迟秒数,返回自身支持链式调用。</summary>
public CommandBase SetDelay(float seconds)
{
delayBeforeExecute = seconds;
return this;
}
/// <summary>
/// 框架调用入口:先执行可选延迟,再调用 <see cref="ExecuteAsync"/>。
/// 子类不应重写此方法。
/// </summary>
public async UniTask RunAsync(CommandContext outerContext)
{
if (delayBeforeExecute > 0f)
await UniTask.Delay(TimeSpan.FromSeconds(delayBeforeExecute));
await ExecuteAsync(outerContext);
}
/// <summary>子类重写此方法以实现命令的核心逻辑。</summary>
protected virtual async UniTask ExecuteAsync(CommandContext outerContext)
{
await UniTask.CompletedTask;
}
/// <summary>浅拷贝当前命令selfContext 也会被拷贝一份新实例。</summary>
public virtual CommandBase Clone()
{
CommandBase clone = (CommandBase)MemberwiseClone();
clone.selfContext = selfContext.Clone();
return clone;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: cf83d22138b5b29449dd0334aff4a3f0

View File

@@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
namespace SLSFramework.General
{
/// <summary>
/// 指令上下文 (Command Context)
/// 以强类型键值对存储指令执行时传递的游戏状态信息。
/// outerContext 由 CommandQueueManager 创建并传递给每条顶层指令;
/// selfContext 是每条指令自身私有的上下文,可携带局部参数。
/// </summary>
public class CommandContext
{
// 保留公开字段以兼容尚未迁移的调用点,后续步骤中逐一移除直接访问
public readonly Dictionary<string, object> context;
public CommandContext()
{
context = new Dictionary<string, object>();
}
public CommandContext((string, object)[] pairs)
{
context = new Dictionary<string, object>();
foreach ((string key, object value) in pairs)
context[key] = value;
}
public CommandContext(Dictionary<string, object> initialInfo)
{
context = new Dictionary<string, object>();
foreach (var pair in initialInfo)
context[pair.Key] = pair.Value;
}
// ── 强类型访问 API ────────────────────────────────────────────────
/// <summary>以强类型写入一个值。</summary>
public void Set<T>(string key, T value)
{
context[key] = value;
}
/// <summary>
/// 以强类型读取一个值。
/// 找不到 key 或类型不匹配时抛出 <see cref="KeyNotFoundException"/>。
/// </summary>
public T Get<T>(string key)
{
if (context.TryGetValue(key, out object raw) && raw is T typed)
return typed;
throw new KeyNotFoundException(
$"CommandContext 中不存在键 '{key}',或其类型与 {typeof(T).Name} 不匹配。");
}
/// <summary>
/// 以强类型尝试读取一个值。
/// 找不到 key 或类型不匹配时返回 falseresult 为默认值。
/// </summary>
public bool TryGet<T>(string key, out T result)
{
if (context.TryGetValue(key, out object raw) && raw is T typed)
{
result = typed;
return true;
}
result = default;
return false;
}
// ── 旧 API向后兼容后续步骤迁移完成后移除 ──────────────────
/// <summary>
/// 以强类型读取一个值。找不到或类型不匹配时返回默认值并写日志警告。
/// 新代码请改用 <see cref="Get{T}"/> 或 <see cref="TryGet{T}"/>。
/// </summary>
[Obsolete("请改用 Get<T>(key) 或 TryGet<T>(key, out result)。")]
public T GetInfo<T>(string key)
{
if (TryGet<T>(key, out T result))
return result;
UnityEngine.Debug.LogWarning(
$"CommandContext 中不存在键 '{key}',或其类型不匹配。返回默认值。");
return default;
}
// ── 工具方法 ──────────────────────────────────────────────────────
/// <summary>返回一个拥有相同键值对副本的新上下文(浅拷贝)。</summary>
public CommandContext Clone()
{
var copy = new CommandContext();
foreach (var pair in context)
copy.context[pair.Key] = pair.Value;
return copy;
}
/// <summary>将 other 的所有键值合并到当前上下文(同键覆盖),返回自身以支持链式调用。</summary>
public CommandContext Merge(CommandContext other)
{
foreach (var pair in other.context)
context[pair.Key] = pair.Value;
return this;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 120104451e143f74b82c3d8c9046fcb4

View File

@@ -0,0 +1,27 @@
namespace SLSFramework.General
{
/// <summary>
/// 框架预定义的 CommandContext 键常量。
/// 所有 Command 内部对 context 的读写均应引用此处的常量,消灭魔法字符串。
/// </summary>
public static class CommandContextKeys
{
/// <summary>当前命令作用的目标角色CharacterBase。</summary>
public const string Target = "Target";
/// <summary>本次抽牌操作实际抽到的牌列表List&lt;CardLogicBase&gt;)。</summary>
public const string DrawnCards = "DrawnCards";
/// <summary>本次弃牌操作弃掉的牌列表List&lt;CardLogicBase&gt;)。</summary>
public const string DiscardedCards = "DiscardedCards";
/// <summary>本次消耗操作消耗的牌列表List&lt;CardLogicBase&gt;)。</summary>
public const string ExhaustedCards = "ExhaustedCards";
/// <summary>本次伤害命令实际造成的伤害量int。</summary>
public const string DamageDealt = "DamageDealt";
/// <summary>本次治疗命令实际恢复的血量int。</summary>
public const string HealingDone = "HealingDone";
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7ed0836069916dd4abc695e149f30c93

View File

@@ -0,0 +1,140 @@
using System.Collections.Generic;
using System.Linq;
using Cysharp.Threading.Tasks;
namespace SLSFramework.General
{
/// <summary>命令组的执行模式。</summary>
public enum ExecutionMode
{
/// <summary>子命令逐条顺序执行。</summary>
Sequential,
/// <summary>子命令同时并行执行。</summary>
Parallel
}
/// <summary>
/// 命令组,将多条 <see cref="CommandBase"/> 按 Sequential 或 Parallel 模式执行。
/// 自身也是 <see cref="CommandBase"/>,可嵌套组合。
/// </summary>
public class CommandGroup : CommandBase
{
/// <summary>子命令列表。</summary>
public readonly List<CommandBase> commands = new List<CommandBase>();
/// <summary>本组的执行模式。</summary>
public readonly ExecutionMode mode;
/// <summary>
/// 组级别上下文,执行过程中会合并每条子命令的 outerContext 与 selfContext。
/// 可在组执行完成后读取聚合数据(如 DrawnCards
/// </summary>
public readonly CommandContext groupContext;
public CommandGroup(ExecutionMode mode = ExecutionMode.Sequential)
{
this.mode = mode;
groupContext = new CommandContext();
}
public CommandGroup(ExecutionMode mode, params CommandBase[] commands)
{
this.mode = mode;
groupContext = new CommandContext();
this.commands.AddRange(commands);
}
public CommandGroup(params CommandBase[] commands)
{
mode = ExecutionMode.Sequential;
groupContext = new CommandContext();
this.commands.AddRange(commands);
}
/// <summary>添加一条子命令,返回自身支持链式调用。</summary>
public CommandGroup AddCommand(CommandBase command)
{
commands.Add(command);
return this;
}
/// <summary>
/// 获取所有子命令。设 <paramref name="alsoIncludeGroups"/> 为 true 时,
/// 返回结果中也包含 <see cref="CommandGroup"/> 自身节点。
/// </summary>
public List<CommandBase> GetAllCommands(bool alsoIncludeGroups = false)
{
//CommandBase可能也是CommandGroup,需要递归获取
var allCommands = new List<CommandBase>();
if (alsoIncludeGroups)
{
allCommands.Add(this);
}
foreach (CommandBase cmd in commands)
{
if (cmd is CommandGroup group)
{
if (alsoIncludeGroups)
{
allCommands.Add(group);
}
allCommands.AddRange(group.GetAllCommands(alsoIncludeGroups));
}
else
{
allCommands.Add(cmd);
}
}
return allCommands;
}
/// <summary>获取所有指定类型的子命令(递归,包含组节点)。</summary>
public List<T> GetAllCommands<T>() where T : CommandBase
{
return GetAllCommands(true).OfType<T>().ToList();
}
/// <summary>深拷贝整个命令组及其子命令。</summary>
public override CommandBase Clone()
{
var cloned = new CommandGroup(mode);
foreach (var cmd in commands)
cloned.AddCommand(cmd.Clone());
return cloned;
}
protected override async UniTask ExecuteAsync(CommandContext outerContext)
{
if (mode == ExecutionMode.Sequential)
{
foreach (var cmd in commands)
{
await cmd.RunAsync(outerContext);
MergeContexts(cmd, outerContext);
}
}
else
{
await UniTask.WhenAll(commands.Select(cmd =>
ExecuteAndMergeAsync(cmd, outerContext)));
}
}
/// <summary>执行单条子命令并将上下文合并到 groupContext。</summary>
private async UniTask ExecuteAndMergeAsync(CommandBase cmd, CommandContext outerContext)
{
await cmd.RunAsync(outerContext);
MergeContexts(cmd, outerContext);
}
private void MergeContexts(CommandBase cmd, CommandContext outerContext)
{
groupContext.Merge(outerContext);
groupContext.Merge(cmd.selfContext);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f117f21c0bf77fb499c14f6bbedec76c

View File

@@ -0,0 +1,18 @@
namespace SLSFramework.General
{
/// <summary>
/// 命令队列的优先级分桶。数值越小优先级越高。
/// Mod 制作者只需使用 <see cref="Main"/>,其余供框架内部使用。
/// </summary>
public enum CommandLane
{
/// <summary>最高优先级:死亡判定、强制打断。</summary>
Interrupt = 0,
/// <summary>Buff 触发链、被动响应。</summary>
Reaction = 10,
/// <summary>主流程:出牌、抽牌、弃牌(默认)。</summary>
Main = 20,
/// <summary>最低优先级UI 刷新、VFX 清理。</summary>
Cleanup = 30,
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3a4efa5c124233847b2921f8a3bf1f59

View File

@@ -0,0 +1,134 @@
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace SLSFramework.General
{
/// <summary>
/// 全局命令队列管理器。
/// 内部按 <see cref="CommandLane"/> 优先级分桶,同桶内保持 FIFO 顺序。
/// 使用 <see cref="AddCommand"/> 将命令加入队列;队列空闲时自动休眠,有新命令时唤醒。
/// </summary>
public class CommandQueueManager : Singleton<CommandQueueManager>
{
// 每个 Lane 独立维护一个 FIFO 队列key 数值越小优先级越高
private readonly SortedDictionary<CommandLane, Queue<CommandBase>> lanes =
new SortedDictionary<CommandLane, Queue<CommandBase>>();
// 全局 outerContext在整个战斗生命周期内共享
private readonly CommandContext globalContext = new CommandContext();
private bool isLoopRunning = false;
/// <summary>当前是否有命令正在执行或等待执行。</summary>
public bool IsBusy => HasPending || isProcessing;
private bool isProcessing = false;
private bool HasPending
{
get
{
foreach (var queue in lanes.Values)
if (queue.Count > 0) return true;
return false;
}
}
protected override void Awake()
{
base.Awake();
// 预建所有 Lane 的队列,避免运行时动态创建
foreach (CommandLane lane in System.Enum.GetValues(typeof(CommandLane)))
lanes[lane] = new Queue<CommandBase>();
StartProcessLoop().Forget();
}
// ── 公开 API ─────────────────────────────────────────────────────
/// <summary>
/// 将命令追加到指定 Lane 的队列末尾。
/// 默认 Lane 为 <see cref="CommandLane.Main"/>Mod 制作者通常不需要指定 Lane。
/// </summary>
public CommandBase AddCommand(CommandBase command, CommandLane lane = CommandLane.Main)
{
if (command == null) return null;
lanes[lane].Enqueue(command);
return command;
}
/// <summary>
/// 将多条命令批量追加到指定 Lane。
/// </summary>
public void AddCommands(IEnumerable<CommandBase> commands, CommandLane lane = CommandLane.Main)
{
if (commands == null) return;
foreach (var cmd in commands)
AddCommand(cmd, lane);
}
/// <summary>
/// 将命令插入到 <see cref="CommandLane.Interrupt"/> Lane 的队首(最高优先级)。
/// 仅供框架内部使用(死亡判定、强制打断等)。
/// </summary>
public CommandBase AddCommandNext(CommandBase command)
{
if (command == null) return null;
// Interrupt Lane 的队列本身是 FIFO但它的整体优先级最高
// 因此直接 Enqueue 到 Interrupt 即可保证在 Main 之前执行。
lanes[CommandLane.Interrupt].Enqueue(command);
return command;
}
/// <summary>等待直到队列完全空闲(所有 Lane 均无待执行命令)。</summary>
public async UniTask WaitUntilIdle()
{
await UniTask.WaitUntil(() => !IsBusy);
}
// ── 内部处理循环 ─────────────────────────────────────────────────
private async UniTaskVoid StartProcessLoop()
{
if (isLoopRunning) return;
isLoopRunning = true;
while (true)
{
// 队列为空时休眠,避免空转
await UniTask.WaitUntil(() => HasPending);
CommandBase next = DequeueHighestPriority();
if (next == null) continue;
isProcessing = true;
try
{
await next.RunAsync(globalContext);
}
catch (System.Exception ex)
{
Debug.LogError($"[CommandQueue] 命令 {next.GetType().Name} 执行出错:{ex}");
}
finally
{
isProcessing = false;
}
}
}
/// <summary>从优先级最高的非空 Lane 取出队首命令。</summary>
private CommandBase DequeueHighestPriority()
{
foreach (var queue in lanes.Values)
{
if (queue.Count > 0)
return queue.Dequeue();
}
return null;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cb973b62a3a5b6f47bf4fcc4bf05a7e6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: -50
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 43903b94b47a0984a8b9645b80ee4d3c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,27 @@
using Cysharp.Threading.Tasks;
using SLSFramework.General;
using UnityEngine;
namespace SLSFramework.General
{
/// <summary>从 outerContext 中读取一个变量并输出到控制台。</summary>
public class Cmd_GetAndLogVariable : CommandBase
{
private readonly string variableName;
public Cmd_GetAndLogVariable(string variableName)
{
this.variableName = variableName;
}
protected override UniTask ExecuteAsync(CommandContext outerContext)
{
if (outerContext.context.TryGetValue(variableName, out object value))
Debug.Log($"[Cmd_GetAndLogVariable] 获取变量 '{variableName}',值为: '{value?.ToString() ?? "null"}'");
else
Debug.LogWarning($"[Cmd_GetAndLogVariable] 变量 '{variableName}' 未在上下文中定义。");
return UniTask.CompletedTask;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9f69b73b7e4bbb84bb29e4d5000b1292

View File

@@ -0,0 +1,26 @@
using Cysharp.Threading.Tasks;
using SLSFramework.General;
using UnityEngine;
namespace SLSFramework.General
{
/// <summary>在 outerContext 中设置一个键值对。</summary>
public class Cmd_SetVariable : CommandBase
{
private readonly string variableName;
private readonly object value;
public Cmd_SetVariable(string variableName, object value)
{
this.variableName = variableName;
this.value = value;
}
protected override UniTask ExecuteAsync(CommandContext outerContext)
{
Debug.Log($"[Cmd_SetVariable] 设置变量 '{variableName}',值为: '{value}'");
outerContext.context[variableName] = value;
return UniTask.CompletedTask;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 16d73f0ca2725b6459957343e231bc32

View File

@@ -0,0 +1,26 @@
using System;
using Cysharp.Threading.Tasks;
using SLSFramework.General;
using UnityEngine;
namespace SLSFramework.General
{
/// <summary>等待指定秒数后在控制台输出一条信息。</summary>
public class Cmd_WaitAndLog : CommandBase
{
private readonly float duration;
private readonly string message;
public Cmd_WaitAndLog(float duration, string message)
{
this.duration = duration;
this.message = message;
}
protected override async UniTask ExecuteAsync(CommandContext outerContext)
{
await UniTask.Delay(TimeSpan.FromSeconds(duration));
Debug.Log($"[Cmd_WaitAndLog] 等待 {duration} 秒后... 输出: '{message}'");
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 25d431de73c653d42a6950339d22aa16