Files
ichni_Official/Assets/Scripts/NewStorySystem/Data/StoryConditionNode.cs
SoulliesOfficial 810d019619 剧情+对话完善
2026-07-21 15:24:42 -04:00

239 lines
9.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;
namespace Ichni.Story
{
/// <summary>
/// 通用剧情条件树的节点基类。通过多态组合出“与 / 或 / 非 + 叶子条件”的复合表达式。
/// 由 <see cref="StoryCondition"/> 借助 <c>[SerializeReference]</c> 序列化持有,
/// Odin 会以类型下拉框展示可选的节点类型。
/// </summary>
[Serializable]
public abstract class StoryConditionNode
{
/// <summary>
/// 对该节点求值。
/// </summary>
/// <param name="getVariable">按变量名返回其整型值的委托。</param>
/// <param name="isBlockCompleted">按 blockId 判断该 Block 是否已完成的委托。</param>
public abstract bool Evaluate(Func<string, int> getVariable, Func<string, bool> isBlockCompleted);
#if UNITY_EDITOR
/// <summary>
/// 返回 Inspector 中使用的紧凑摘要。条件编辑器只读取该文本,不影响运行时求值逻辑。
/// </summary>
public virtual string GetEditorSummary() => GetType().Name;
#endif
}
/// <summary>
/// 复合节点(逻辑与 AND所有子条件均满足时才满足。子列表为空视为满足。
/// </summary>
[Serializable]
[LabelText("All Of (AND)")]
public class AllOfCondition : StoryConditionNode
{
[HideLabel]
[SerializeReference]
[ListDrawerSettings(ShowFoldout = true, DefaultExpandedState = true)]
public List<StoryConditionNode> conditions = new List<StoryConditionNode>();
public override bool Evaluate(Func<string, int> getVariable, Func<string, bool> isBlockCompleted)
{
foreach (StoryConditionNode child in conditions)
{
if (child != null && !child.Evaluate(getVariable, isBlockCompleted))
return false;
}
return true;
}
#if UNITY_EDITOR
public override string GetEditorSummary() => $"All Of ({conditions?.Count ?? 0})";
#endif
}
/// <summary>
/// 复合节点(逻辑或 OR任一子条件满足即满足。子列表为空视为不满足。
/// </summary>
[Serializable]
[LabelText("Any Of (OR)")]
public class AnyOfCondition : StoryConditionNode
{
[HideLabel]
[SerializeReference]
[ListDrawerSettings(ShowFoldout = true, DefaultExpandedState = true)]
public List<StoryConditionNode> conditions = new List<StoryConditionNode>();
public override bool Evaluate(Func<string, int> getVariable, Func<string, bool> isBlockCompleted)
{
if (conditions.Count == 0)
return false;
foreach (StoryConditionNode child in conditions)
{
if (child != null && child.Evaluate(getVariable, isBlockCompleted))
return true;
}
return false;
}
#if UNITY_EDITOR
public override string GetEditorSummary() => $"Any Of ({conditions?.Count ?? 0})";
#endif
}
/// <summary>
/// 复合节点(逻辑非 NOT对子条件取反。子条件为空视为满足等价于“非 假”)。
/// </summary>
[Serializable]
[LabelText("Not")]
public class NotCondition : StoryConditionNode
{
[HideLabel]
[SerializeReference]
public StoryConditionNode condition;
public override bool Evaluate(Func<string, int> getVariable, Func<string, bool> isBlockCompleted)
{
return condition == null || !condition.Evaluate(getVariable, isBlockCompleted);
}
#if UNITY_EDITOR
public override string GetEditorSummary() => $"Not ({condition?.GetEditorSummary() ?? "None"})";
#endif
}
/// <summary>
/// 叶子节点:指定 Block 已完成时满足。
/// </summary>
[Serializable]
[LabelText("Block Completed")]
public class BlockCompletedCondition : StoryConditionNode
{
[LabelText("Block ID")]
[LabelWidth(50)]
[Tooltip("必须已完成的 StoryBlock ID。可用于表达显式前置条件通常连接关系已由 Connected Rule 自动处理。")]
public string blockId;
public override bool Evaluate(Func<string, int> getVariable, Func<string, bool> isBlockCompleted)
{
return !string.IsNullOrEmpty(blockId) && isBlockCompleted(blockId);
}
#if UNITY_EDITOR
public override string GetEditorSummary() => string.IsNullOrWhiteSpace(blockId)
? "Block Completed: <missing ID>"
: $"Block Completed: {blockId}";
#endif
}
/// <summary>
/// 叶子节点:将指定变量的当前值与目标值按比较方式求值。
/// 既可用于解锁条件,也可用于 Forbidden Condition例如当路线变量等于另一条路线的值时禁用 Block。
/// </summary>
[Serializable]
[LabelText("Variable Compare")]
public class VariableCondition : StoryConditionNode
{
[HorizontalGroup("VariableComparison", 0.50f)]
[LabelText("Var")]
[LabelWidth(24)]
[Tooltip("章节剧情变量名。使用小写英文、数字和下划线;可由 Yarn 的 set_* 指令写入。")]
public string variableName;
[HorizontalGroup("VariableComparison", 0.22f)]
[LabelText("Op")]
[LabelWidth(20)]
[Tooltip("变量值与目标值之间的比较方式。")]
public VariableComparison comparison;
[HorizontalGroup("VariableComparison")]
[LabelText("Value")]
[LabelWidth(35)]
[Tooltip("用于比较的整数目标值。Bool 条件使用 0 / 1。")]
public int value;
public override bool Evaluate(Func<string, int> getVariable, Func<string, bool> isBlockCompleted)
{
int actual = getVariable(variableName);
return comparison switch
{
VariableComparison.Equal => actual == value,
VariableComparison.NotEqual => actual != value,
VariableComparison.Greater => actual > value,
VariableComparison.GreaterOrEqual => actual >= value,
VariableComparison.Less => actual < value,
VariableComparison.LessOrEqual => actual <= value,
_ => false
};
}
#if UNITY_EDITOR
public override string GetEditorSummary()
{
string variable = string.IsNullOrWhiteSpace(variableName) ? "<missing variable>" : variableName;
string operation = comparison switch
{
VariableComparison.Equal => "==",
VariableComparison.NotEqual => "!=",
VariableComparison.Greater => ">",
VariableComparison.GreaterOrEqual => ">=",
VariableComparison.Less => "<",
VariableComparison.LessOrEqual => "<=",
_ => "?"
};
return $"{variable} {operation} {value}";
}
#endif
}
}
#if UNITY_EDITOR
namespace Ichni.Story.Editor
{
/// <summary>
/// 通用 StoryCondition 的 Odin Inspector Drawer。
/// <para>Unlock、Forbidden 与 Helper Availability 共用同一条件模型;此 Drawer 只改变编辑器中的
/// 折叠呈现方式,不修改条件树的序列化结构或运行时求值结果。</para>
/// <para>默认仅显示可读摘要。需要调整条件时再展开,避免 SerializeReference 条件树长期占据 Block 的编辑空间。</para>
/// </summary>
public class StoryConditionDrawer : Sirenix.OdinInspector.Editor.OdinValueDrawer<StoryCondition>
{
private bool _isExpanded;
protected override void DrawPropertyLayout(UnityEngine.GUIContent label)
{
StoryCondition condition = ValueEntry.SmartValue;
string summary = condition == null ? "None" : condition.GetEditorSummary();
// 父属性若保留了简短标签(例如 Helper 的 Availability则与摘要合并显示
// StoryBlock 中使用 HideLabel 时只保留摘要,避免重复占用左侧标签列。
string prefix = label == null || string.IsNullOrWhiteSpace(label.text)
? string.Empty
: $"{label.text}: ";
UnityEngine.GUIContent foldoutLabel = new UnityEngine.GUIContent(
prefix + summary,
"留空表示未配置条件。展开后可选择 All Of (AND)、Any Of (OR)、Not、Block Completed 或 Variable Compare。"
);
_isExpanded = UnityEditor.EditorGUILayout.Foldout(_isExpanded, foldoutLabel, true);
if (!_isExpanded)
{
return;
}
UnityEditor.EditorGUI.indentLevel++;
// 继续调用 Odin 的默认 SerializeReference 绘制链,使类型选择、嵌套条件和 Undo 行为保持原样。
CallNextDrawer(UnityEngine.GUIContent.none);
UnityEditor.EditorGUI.indentLevel--;
}
}
}
#endif