Passion & UI

This commit is contained in:
SoulliesOfficial
2026-06-12 17:11:39 -04:00
parent 7bc1e1722c
commit 6d7ebc5825
3444 changed files with 865284 additions and 463132 deletions

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Cielonos.Core.Interaction;
using Cielonos.MainGame.UI;
using SLSUtilities.UI;
using UnityEngine;
namespace Cielonos.MainGame.Characters
@@ -8,42 +9,166 @@ namespace Cielonos.MainGame.Characters
public class PlayerInteractionSubcontroller : SubcontrollerBase<Player>
{
public Player player => owner;
/// <summary>当前进入触发区的所有交互选项(由 InteractionTrigger 维护)。</summary>
public List<InteractionChoice> currentChoices = new List<InteractionChoice>();
/// <summary>
/// 当前激活的可交互对象(最近一次进入触发区的对象)。
/// MechanicalTableUI、LogisticsCenterUI 等通过此字段获取驱动它们的场景对象。
/// </summary>
/// <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 += () => ExecuteChoice(0);
player.operationSc.OnInteract += ExecuteCurrentChoice;
player.operationSc.OnNavigateInteractionChoice += NavigateChoice;
// 订阅 UIPageManager 的输入阻塞事件,联动 InteractionUIArea 的显隐
if (UIPageManager.Instance != null)
{
UIPageManager.Instance.OnInputBlockChanged += OnUIInputBlockChanged;
}
}
/// <summary>由 InteractionTrigger 在进入触发区时调用,记录当前激活对象。</summary>
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 在退出触发区时调用,清除当前激活对象。</summary>
/// <summary>由 InteractionTrigger 在退出触发区时调用,清除当前激活对象并隐藏选项 UI。</summary>
public void RemoveCurrentInteractable(InteractableObjectBase interactable)
{
if (currentInteractable == interactable) currentInteractable = null;
if (currentInteractable != interactable) return;
currentInteractable = null;
PlayerCanvas.InteractionUIArea?.Hide();
}
public void ExecuteChoice(int index = 0)
// ====================================================================
// 导航与执行
// ====================================================================
/// <summary>
/// 导航选中索引。delta = 1向下/滚轮向下delta = -1向上/滚轮向上)。
/// 会自动跳过 isInteractable = false 的选项。
/// 若处于挂起状态(有 UIPage 打开),忽略导航。
/// </summary>
public void NavigateChoice(int delta)
{
if (index < 0 || index >= currentChoices.Count)
if (_isSuspended) return;
if (currentChoices.Count == 0) return;
int startIndex = currentChoiceIndex;
int next = currentChoiceIndex;
do
{
Debug.LogWarning("Invalid interaction choice index.");
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;
}
var choice = currentChoices[index];
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;
}
}
}