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

117 lines
5.7 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.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;
using Yarn.Unity;
namespace Ichni.Story
{
/// <summary>
/// 单个章节的故事树数据,以 ScriptableObject 形式存储所有 block 定义与关联资产引用。
/// 布局完全由每个 block 的 (gridColumn, gridRow) 静态决定,运行时全量展示。
/// </summary>
[CreateAssetMenu(fileName = "StoryData", menuName = "Ichni/Story/New/StoryData")]
public partial class StoryData : SerializedScriptableObject
{
[TitleGroup("Chapter Settings", Alignment = TitleAlignments.Centered)]
[LabelText("Chapter Index")]
[Tooltip("章节的稳定索引。StorySave、StoryManager 与 ChapterSelection 都以该值定位当前章节。")]
public string chapterIndex;
[TitleGroup("Chapter Settings")]
[LabelText("Yarn Project")]
[Tooltip("该章节对应的 YarnProject 编译资产。TextBlock 会在运行时从此项目中执行配置的 Yarn Node。")]
public YarnProject yarnProject;
/// <summary>
/// 本章节永久剧情变量的默认值。它们只属于本章节,且不会直接写入存档;
/// 玩家第一次通过 Yarn 或其它剧情入口写入同名变量后,存档值才会覆盖这里的默认值。
/// </summary>
[LabelText("Initial Variables")]
[ListDrawerSettings(ShowFoldout = true, DefaultExpandedState = false, DraggableItems = true, ShowItemCount = true)]
[Tooltip("变量 Key 使用小写英文、数字和下划线。Yarn 请通过 <<set_*>> 与 get_*() 访问,不使用 <<declare>>、<<set>> 或 $variable。")]
public List<StoryVariableDefinition> initialVariables = new List<StoryVariableDefinition>();
[LabelText("Blocks")]
[ListDrawerSettings(ShowFoldout = true, DefaultExpandedState = false, DraggableItems = true, ShowItemCount = true)]
[Tooltip("章节的完整剧情树定义。列表排序不决定运行时流程;连接关系、条件与网格坐标才是唯一依据。")]
public List<StoryBlockDefinition> blocks = new List<StoryBlockDefinition>();
/// <summary>
/// 章节顶部 Timeline 的静态标记定义。
/// 标记的横向位置始终由 <see cref="StoryTimelineMarkerDefinition.anchorBlockId"/> 指向的
/// Block 的 <c>gridColumn</c> 推导,不在这里重复保存坐标,避免剧情树与 Timeline 脱节。
/// </summary>
[LabelText("Timeline Markers")]
[ListDrawerSettings(ShowFoldout = true, DefaultExpandedState = false, DraggableItems = true, ShowItemCount = true)]
[Tooltip("每个标记锚定到一个 TextBlock运行时使用该 Block 的 Grid Column 对齐 Timeline。TextBlock 为 Current / Completed 时显示Locked / Forbidden 时隐藏。")]
public List<StoryTimelineMarkerDefinition> timelineMarkers = new List<StoryTimelineMarkerDefinition>();
/// <summary>
/// 仅供编辑器自检使用的互斥结局分组,不参与运行时的解锁、禁用或存档逻辑。
/// 实际路线禁用仍完全通过各 Block 的 Forbidden Condition 表达,以保留剧情变量组合的灵活性。
/// </summary>
[LabelText("Exclusive Route Groups")]
[ListDrawerSettings(ShowFoldout = true, DefaultExpandedState = false, DraggableItems = true, ShowItemCount = true)]
[Tooltip("把同一章节中只能同时达成一个的结局 Block 放入同一组。Validate Story Data 会检查引用、终点形态与 Forbidden Condition 配置;不会改变运行时逻辑。")]
public List<StoryRouteValidationGroup> exclusiveRouteGroups = new List<StoryRouteValidationGroup>();
// ── Queries ─────────────────────────────────────────────────────────────
/// <summary>
/// 根据 blockId 获取 block 定义,未找到时返回 null。
/// </summary>
public StoryBlockDefinition GetBlock(string blockId)
{
foreach (StoryBlockDefinition block in blocks)
{
if (block != null && block.blockId == blockId)
return block;
}
return null;
}
/// <summary>
/// 尝试获取一个章节变量的静态默认定义。重复 Key 取列表中第一项,
/// 因此配置时应保持 Key 唯一,以免在 Inspector 中产生歧义。
/// </summary>
public bool TryGetInitialVariable(string key, out StoryVariableDefinition definition)
{
if (!string.IsNullOrWhiteSpace(key) && initialVariables != null)
{
foreach (StoryVariableDefinition item in initialVariables)
{
if (item != null && item.key == key)
{
definition = item;
return true;
}
}
}
definition = null;
return false;
}
/// <summary>
/// 获取指定 blockId 的所有直接后继 block 定义。
/// </summary>
public IEnumerable<StoryBlockDefinition> GetNextBlocks(string blockId)
{
StoryBlockDefinition source = GetBlock(blockId);
if (source == null) yield break;
if (source.nextBlockIds == null)
yield break;
foreach (string nextId in source.nextBlockIds)
{
StoryBlockDefinition next = GetBlock(nextId);
if (next != null)
yield return next;
}
}
}
}