using System; using System.Collections.Generic; using DG.Tweening; using UnityEngine; using UnityEngine.Localization.Settings; using UnityEngine.UI; namespace Ichni.Story { /// /// 场景中常驻 Helper 的交互控制器。 /// 点击后会按剧情条件筛选并加权随机抽取一条台词,实例化 HelperTalk Prefab 显示; /// 此过程不修改剧情变量、Block 完成状态或存档。 /// [RequireComponent(typeof(Button))] public class StoryHelperController : MonoBehaviour { [Header("References")] [SerializeField, Tooltip("承接 Helper 点击的 Button。建议挂在固定根节点,而非会移动的视觉节点上。")] private Button helperButton; [SerializeField, Tooltip("当前 Helper 的视觉 Presenter。未来可替换为 Spine 或 Live2D 对应实现。")] private StoryHelperVisualPresenter visualPresenter; [SerializeField, Tooltip("点击后实例化的 HelperTalk Prefab。它独立于 MessageBox。")] private StoryHelperTalk helperTalkPrefab; [SerializeField, Tooltip("运行时 HelperTalk 的父节点。留空时使用当前 Helper 根节点。")] private Transform helperTalkContainer; [Header("Motion Roots")] [SerializeField, Tooltip("只承担循环漂浮动画的节点。")] private RectTransform idleRoot; [SerializeField, Tooltip("只承担点击反馈的节点。它应位于 Idle Root 内部,避免与循环漂浮争夺同一属性。")] private RectTransform clickRoot; [Header("Idle Animation")] [SerializeField, Min(0f), Tooltip("循环漂浮的最大纵向偏移(Canvas 单位)。")] private float idleFloatDistance = 8f; [SerializeField, Min(0.05f), Tooltip("从基准位置移动到最高点或最低点的单程时间。")] private float idleHalfDuration = 1.35f; [Header("Click Animation")] [SerializeField, Min(0.01f), Tooltip("点击缩放与位移反馈持续时间。")] private float clickPunchDuration = 0.2f; [SerializeField, Min(0f), Tooltip("点击时的缩放强度;0.035 表示约 3.5% 的轻微放大。")] private float clickPunchScale = 0.035f; [SerializeField, Min(0f), Tooltip("点击时的纵向位移反馈(Canvas 单位)。")] private float clickPunchOffset = 5f; [SerializeField, Min(0f), Tooltip("连续点击的最小间隔,防止重复实例化大量气泡。")] private float clickCooldown = 0.3f; private readonly List _eligibleDialogues = new(); private Sequence _idleSequence; private Sequence _clickSequence; private StoryHelperTalk _activeTalk; private Vector2 _idleBasePosition; private Vector2 _clickBasePosition; private Vector3 _clickBaseScale; private string _lastDialogueId; private StoryHelperData _appliedHelperData; private StoryManager _subscribedStoryManager; private float _nextClickTime; private void Awake() { if (helperButton == null) helperButton = GetComponent