174 lines
6.6 KiB
C#
174 lines
6.6 KiB
C#
using System.Collections.Generic;
|
||
using Cielonos.Core.Interaction;
|
||
using Cielonos.MainGame.UI;
|
||
using SLSUtilities.UI;
|
||
using UnityEngine;
|
||
|
||
namespace Cielonos.MainGame.Characters
|
||
{
|
||
public class PlayerInteractionSubcontroller : SubcontrollerBase<Player>
|
||
{
|
||
public Player player => owner;
|
||
|
||
/// <summary>当前进入触发区的所有交互选项(由 InteractionTrigger 维护)。</summary>
|
||
public List<InteractionChoice> currentChoices = new List<InteractionChoice>();
|
||
|
||
/// <summary>当前高亮(选中)的选项索引。</summary>
|
||
public int currentChoiceIndex { get; private set; }
|
||
|
||
/// <summary>当前激活的可交互对象(最近一次进入触发区的对象)。</summary>
|
||
public InteractableObjectBase currentInteractable { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 当有 UIPage 打开时为 true,此时交互 UI 被挂起、交互操作被屏蔽。
|
||
/// </summary>
|
||
private bool _isSuspended;
|
||
|
||
public override void Initialize()
|
||
{
|
||
base.Initialize();
|
||
player.operationSc.OnInteract += ExecuteCurrentChoice;
|
||
player.operationSc.OnNavigateInteractionChoice += NavigateChoice;
|
||
|
||
// 订阅 UIPageManager 的输入阻塞事件,联动 InteractionUIArea 的显隐
|
||
if (UIPageManager.Instance != null)
|
||
{
|
||
UIPageManager.Instance.OnInputBlockChanged += OnUIInputBlockChanged;
|
||
}
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
if (UIPageManager.Instance != null)
|
||
{
|
||
UIPageManager.Instance.OnInputBlockChanged -= OnUIInputBlockChanged;
|
||
}
|
||
}
|
||
|
||
// ====================================================================
|
||
// UIPage 联动
|
||
// ====================================================================
|
||
|
||
/// <summary>
|
||
/// 当任何 UIPage 打开/关闭时由 UIPageManager 触发。
|
||
/// blocked = true:有 UIPage 打开 → 挂起 InteractionUIArea(隐藏但不清除状态)。
|
||
/// blocked = false:所有 UIPage 关闭 → 若仍在交互范围内则恢复显示。
|
||
/// </summary>
|
||
private void OnUIInputBlockChanged(bool blocked)
|
||
{
|
||
_isSuspended = blocked;
|
||
|
||
if (blocked)
|
||
{
|
||
// 仅隐藏 UI,不清除 currentInteractable / currentChoices
|
||
PlayerCanvas.InteractionUIArea?.Hide();
|
||
}
|
||
else
|
||
{
|
||
// 所有页面关闭后,若仍在交互范围内,恢复 InteractionUIArea
|
||
if (currentInteractable != null && currentChoices.Count > 0)
|
||
{
|
||
PlayerCanvas.InteractionUIArea?.Show(currentChoices, currentChoiceIndex);
|
||
}
|
||
}
|
||
}
|
||
|
||
// ====================================================================
|
||
// 进入 / 离开触发区
|
||
// ====================================================================
|
||
|
||
/// <summary>由 InteractionTrigger 在进入触发区时调用,记录当前激活对象并显示选项 UI。</summary>
|
||
public void SetCurrentInteractable(InteractableObjectBase interactable)
|
||
{
|
||
currentInteractable = interactable;
|
||
currentChoiceIndex = 0;
|
||
|
||
// 自动跳到第一个可用的选项
|
||
SnapToFirstInteractable();
|
||
|
||
// 若当前没有 UIPage 打开,才显示交互选项
|
||
if (!_isSuspended)
|
||
{
|
||
PlayerCanvas.InteractionUIArea?.Show(currentChoices, currentChoiceIndex);
|
||
}
|
||
}
|
||
|
||
/// <summary>由 InteractionTrigger 在退出触发区时调用,清除当前激活对象并隐藏选项 UI。</summary>
|
||
public void RemoveCurrentInteractable(InteractableObjectBase interactable)
|
||
{
|
||
if (currentInteractable != interactable) return;
|
||
|
||
currentInteractable = null;
|
||
PlayerCanvas.InteractionUIArea?.Hide();
|
||
}
|
||
|
||
// ====================================================================
|
||
// 导航与执行
|
||
// ====================================================================
|
||
|
||
/// <summary>
|
||
/// 导航选中索引。delta = 1(向下/滚轮向下),delta = -1(向上/滚轮向上)。
|
||
/// 会自动跳过 isInteractable = false 的选项。
|
||
/// 若处于挂起状态(有 UIPage 打开),忽略导航。
|
||
/// </summary>
|
||
public void NavigateChoice(int delta)
|
||
{
|
||
if (_isSuspended) return;
|
||
if (currentChoices.Count == 0) return;
|
||
|
||
int startIndex = currentChoiceIndex;
|
||
int next = currentChoiceIndex;
|
||
|
||
do
|
||
{
|
||
next = (next + delta + currentChoices.Count) % currentChoices.Count;
|
||
if (next == startIndex) break; // 绕了一圈,无可用选项,保持原位
|
||
}
|
||
while (!currentChoices[next].isInteractable);
|
||
|
||
currentChoiceIndex = next;
|
||
PlayerCanvas.InteractionUIArea?.UpdateHighlight(currentChoiceIndex);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行当前高亮的选项。若该选项 isInteractable = false 或处于挂起状态,则什么都不做。
|
||
/// </summary>
|
||
public void ExecuteCurrentChoice()
|
||
{
|
||
if (_isSuspended) return;
|
||
if (currentChoices.Count == 0) return;
|
||
if (currentChoiceIndex < 0 || currentChoiceIndex >= currentChoices.Count) return;
|
||
|
||
InteractionChoice choice = currentChoices[currentChoiceIndex];
|
||
if (!choice.isInteractable)
|
||
{
|
||
Debug.Log($"[InteractionSc] 选项 '{choice.choiceName}' 当前不可用。");
|
||
return;
|
||
}
|
||
|
||
choice.action?.Invoke();
|
||
}
|
||
|
||
// ====================================================================
|
||
// 内部工具
|
||
// ====================================================================
|
||
|
||
/// <summary>将 currentChoiceIndex 移到第一个 isInteractable = true 的选项。</summary>
|
||
private void SnapToFirstInteractable()
|
||
{
|
||
if (currentChoices.Count == 0) return;
|
||
|
||
for (int i = 0; i < currentChoices.Count; i++)
|
||
{
|
||
int index = (currentChoiceIndex + i) % currentChoices.Count;
|
||
if (currentChoices[index].isInteractable)
|
||
{
|
||
currentChoiceIndex = index;
|
||
return;
|
||
}
|
||
}
|
||
// 所有选项均不可用时,停在 0
|
||
currentChoiceIndex = 0;
|
||
}
|
||
}
|
||
} |