Files
Cielonos/Assets/Scripts/MainGame/Characters/Player/PlayerInteractionSubcontroller.cs
SoulliesOfficial 6d7ebc5825 Passion & UI
2026-06-12 17:11:39 -04:00

174 lines
6.6 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 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;
}
}
}