Files
SoulliesOfficial c30bb258b1 Story排版
2026-07-20 16:56:04 -04:00

137 lines
5.8 KiB
C#
Raw Permalink 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 Ichni;
using Ichni.Menu;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace Ichni.Story.UI
{
/// <summary>
/// 剧情树中的歌曲 Block 视图。
/// 它只保存 <see cref="StoryBlockDefinition.songName"/> 这一个稳定歌曲标识;歌曲名称、作曲者与插画
/// 均从当前章节的 <see cref="SongItemData"/> 读取,避免 StoryData 与选曲资料出现两份可漂移的数据。
/// 点击后切换到选曲并自动选中对应歌曲的流程将在后续阶段接入。
/// </summary>
public class SongBlockView : StoryBlockView
{
[Header("Song Block")]
[Tooltip("Mask 下的 16:9 插画 Image。Sprite 由当前章节的 SongItemData.illustration 提供。")]
public Image illustrationImage;
[Tooltip("illustrationImage 对应的 RectTransform。仅修改其 anchoredPosition.y 以完成纵向裁切。")]
public RectTransform illustrationRect;
public TMP_Text songNameText;
public TMP_Text composerText;
[Tooltip("歌曲或剧情 Block 不可进入时显示的遮罩节点。")]
public GameObject lockOverlay;
private ChapterSelectionUnit _chapter;
private SongItemData _song;
private bool _canEnterSong;
public override void Initialize(StoryBlockDefinition def, Vector2 position, StoryBlockState blockState)
{
base.Initialize(def, position, blockState);
if (TryResolveSong(out _chapter, out _song))
{
illustrationImage.sprite = _song.illustration;
songNameText.text = _song.displaySongName;
composerText.text = _song.composer;
}
else
{
// 配置错误时保留稳定 ID 以便定位问题,同时锁定 Block避免从剧情页绕过内容授权。
illustrationImage.sprite = null;
songNameText.text = def.songName;
composerText.text = string.Empty;
}
// 数据存储的是最终 local Y而非叠加在 Prefab 默认位置上的偏移量。
// Clamp 仍保留作为运行时防线:即使旧资产或外部工具写入越界值,也不会在 Mask 边缘露出空白。
Vector2 illustrationPosition = illustrationRect.anchoredPosition;
illustrationPosition.y = Mathf.Clamp(def.illustrationLocalY, -74.25f, 74.25f);
illustrationRect.anchoredPosition = illustrationPosition;
ApplyState(blockState);
}
/// <summary>
/// StoryBlock 的剧情状态和歌曲的内容解锁状态必须同时满足,才能真正点击进入歌曲。
/// <see cref="UnlockSaveModule.CanEnterSong"/> 复用选曲页面的统一授权判断,防止 Story SongBlock 成为绕过入口。
/// </summary>
public override void ApplyState(StoryBlockState newState)
{
base.ApplyState(newState);
bool storyAllowsInteraction = state != StoryBlockState.Locked && state != StoryBlockState.Forbidden;
_canEnterSong = _chapter != null && _song != null &&
GameSaveManager.instance != null &&
GameSaveManager.instance.UnlockSaveModule != null &&
GameSaveManager.instance.UnlockSaveModule.CanEnterSong(_chapter, _song);
bool canInteract = storyAllowsInteraction && _canEnterSong;
button.interactable = canInteract;
lockOverlay.SetActive(!canInteract);
}
/// <summary>
/// 根据正在构建的剧情章节索引定位歌曲,而不是盲目信任 currentChapter。
/// 这样即使未来从其它页面直接打开 StoryData也不会把同名歌曲解析到上一次残留的章节中。
/// </summary>
private bool TryResolveSong(out ChapterSelectionUnit chapter, out SongItemData song)
{
chapter = null;
song = null;
string chapterIndex = StoryManager.instance?.treeController?.ActiveChapterIndex;
ChapterSelectionManager chapterManager = ChapterSelectionManager.instance;
if (string.IsNullOrEmpty(chapterIndex) || chapterManager == null)
{
Debug.LogWarning($"[SongBlockView] 无法解析歌曲 Block '{blockId}':当前章节或 ChapterSelectionManager 不可用。");
return false;
}
foreach (ChapterSelectionUnit candidate in chapterManager.chapters)
{
if (candidate != null && candidate.chapterIndex == chapterIndex)
{
chapter = candidate;
break;
}
}
if (chapter == null)
{
Debug.LogWarning($"[SongBlockView] 无法解析歌曲 Block '{blockId}':找不到章节 '{chapterIndex}' 的 ChapterSelectionUnit。");
return false;
}
foreach (SongItemData candidate in chapter.songs)
{
if (candidate != null && candidate.songName == definition.songName)
{
song = candidate;
return true;
}
}
Debug.LogWarning($"[SongBlockView] 无法解析歌曲 Block '{blockId}':章节 '{chapterIndex}' 中不存在歌曲 ID '{definition.songName}'。");
return false;
}
protected override void OnClicked()
{
if (!_canEnterSong)
{
return;
}
// 占位:后续接入曲目选择流程
Debug.Log($"[SongBlockView] 点击音乐块 '{blockId}' (song='{definition.songName}')。占位实现,曲目选择将在后续阶段接入。");
}
}
}