biography-of-lijie/Assets/GameMain/Scripts/CustomComponent/CombineComponent.cs

649 lines
18 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 Event;
using UI;
using UnityEngine;
using UnityEngine.Events;
using UnityGameFramework.Runtime;
namespace CustomComponent
{
/// <summary>
/// 核心玩法A控制器负责拼装规则校验、进度统计与完成判定。
/// </summary>
[DisallowMultipleComponent]
public class CombineComponent : GameFrameworkComponent
{
private const int MaxCombineRoundCount = 2;
#region Inspector Config
/// <summary>
/// 组件启用时是否自动开始拼装。
/// </summary>
[SerializeField] private bool _autoStartOnEnable = false;
/// <summary>
/// 是否对所有槽位启用全局严格顺序。
/// </summary>
[SerializeField] private bool _strictGlobalOrder = true;
/// <summary>
/// 拖拽时的临时父节点。
/// </summary>
[SerializeField] private Transform _dragRoot = null;
/// <summary>
/// 拼装节点根对象。
/// </summary>
[SerializeField] private Transform _puzzleRoot = null;
/// <summary>
/// Combine 展示关卡序列(当前固定两次,按顺序消费)。
/// </summary>
[SerializeField] private List<GameObject> _levelStructurePrefabs = new List<GameObject>();
[SerializeField] [TextArea(1, 3)] private string _successHint = "拼接成功";
[SerializeField] [TextArea(1, 3)] private string _orderErrorHint = "顺序错误";
[SerializeField] [TextArea(1, 3)] private string _positionErrorHint = "位置错误";
/// <summary>
/// 拼装完成事件。
/// </summary>
[SerializeField] private UnityEvent _onPuzzleCompleted = new UnityEvent();
#endregion
#region Runtime State
private CombineFormContext _formContext;
private CombineFormController _formController;
/// <summary>
/// 当前拼装场景中收集到的槽位(运行态)。
/// </summary>
private readonly List<CombineSlot> _runtimeSlots = new List<CombineSlot>();
/// <summary>
/// 当前拼装场景中收集到的拖拽部件(运行态)。
/// </summary>
private readonly List<CombineDraggablePart> _runtimeParts = new List<CombineDraggablePart>();
/// <summary>
/// 按顺序排序后的槽位缓存。
/// </summary>
private readonly List<CombineSlot> _orderedSlots = new List<CombineSlot>();
/// <summary>
/// 当前期望放置的槽位索引。
/// </summary>
private int _nextOrderSlotIndex = 0;
/// <summary>
/// 当前已放置部件数量。
/// </summary>
private int _placedCount = 0;
/// <summary>
/// 拼装是否已开始。
/// </summary>
private bool _isStarted = false;
/// <summary>
/// 拼装是否已完成。
/// </summary>
private bool _isCompleted = false;
/// <summary>
/// 拼装是否处于暂停状态。
/// </summary>
private bool _isPaused = false;
/// <summary>
/// 下一次启动要使用的展示关卡索引。
/// </summary>
private int _nextLevelStructureIndex = 0;
#endregion
#region Public Query
/// <summary>
/// 获取拼装是否完成。
/// </summary>
public bool IsCompleted => _isCompleted;
/// <summary>
/// 获取当前步数。
/// </summary>
public int CurrentStep => _placedCount;
/// <summary>
/// 获取总步数。
/// </summary>
public int TotalStep => _orderedSlots.Count;
#endregion
#region Unity Lifecycle
/// <summary>
/// 组件启用时可选自动开始拼装。
/// </summary>
private void OnEnable()
{
if (_autoStartOnEnable)
{
StartPuzzle();
}
}
#endregion
#region Level Lifecycle
/// <summary>
/// 使用关卡数据启动关卡并打开玩法UI。
/// </summary>
public int? StartLevel(CombineFormContext context)
{
if (!TryBuildRuntimeFormContext(context, out CombineFormContext runtimeContext))
{
return null;
}
ClearRuntimeContext();
SetFormContext(runtimeContext);
EnsureFormController();
// 先关闭上一次UI避免重复打开。
_formController.CloseUI();
int? serialId = _formController.OpenUI(runtimeContext);
if (!serialId.HasValue)
{
Log.Warning("CoreGameplayA start failed. OpenUI returned null.");
return null;
}
_nextLevelStructureIndex++;
return serialId;
}
/// <summary>
/// 停止当前关卡并清理运行态。
/// </summary>
public void StopLevel()
{
_formController?.CloseUI();
ClearRuntimeContext();
}
#endregion
#region Puzzle Flow
/// <summary>
/// 启动拼装流程。
/// </summary>
public void StartPuzzle()
{
CollectChildren();
BuildOrderedSlotList();
PrepareSlots();
PrepareParts();
_placedCount = 0;
_nextOrderSlotIndex = 0;
_isStarted = true;
_isCompleted = false;
_isPaused = false;
GameEntry.Event.Fire(this, CombineProgressEventArgs.Create(_placedCount, _orderedSlots.Count));
}
/// <summary>
/// 暂停拼装输入。
/// </summary>
public void PausePuzzle()
{
_isPaused = true;
}
/// <summary>
/// 恢复拼装输入。
/// </summary>
public void ResumePuzzle()
{
_isPaused = false;
}
/// <summary>
/// 重置并重新开始拼装。
/// </summary>
public void ResetPuzzle()
{
StartPuzzle();
}
#endregion
#region Placement
/// <summary>
/// 尝试将部件放置到目标槽位。
/// </summary>
public bool TryPlacePart(CombineDraggablePart part, CombineSlot slot)
{
if (!CanPlacePart(part, slot))
{
return false;
}
if (!ValidateSlotAvailability(part, slot))
{
return false;
}
if (!ValidatePartType(part, slot))
{
return false;
}
if (!ValidateOrder(part, slot))
{
return false;
}
ApplyPlacement(part, slot);
return true;
}
/// <summary>
/// 放置前通用状态校验。
/// </summary>
private bool CanPlacePart(CombineDraggablePart part, CombineSlot slot)
{
return _isStarted && !_isCompleted && !_isPaused && part != null && slot != null;
}
/// <summary>
/// 校验槽位是否可放置。
/// </summary>
private bool ValidateSlotAvailability(CombineDraggablePart part, CombineSlot slot)
{
if (!slot.IsOccupied)
{
return true;
}
RejectPlace(part, CombineFeedbackType.PositionError);
return false;
}
/// <summary>
/// 校验部件类型是否匹配槽位。
/// </summary>
private bool ValidatePartType(CombineDraggablePart part, CombineSlot slot)
{
if (slot.RequiredPartType == part.PartType)
{
return true;
}
RejectPlace(part, CombineFeedbackType.PositionError);
return false;
}
/// <summary>
/// 校验放置顺序是否符合规则。
/// </summary>
private bool ValidateOrder(CombineDraggablePart part, CombineSlot slot)
{
if (!NeedValidateOrder(slot) || IsExpectedOrderSlot(slot))
{
return true;
}
RejectPlace(part, CombineFeedbackType.OrderError);
return false;
}
/// <summary>
/// 应用成功放置结果,并推进进度。
/// </summary>
private void ApplyPlacement(CombineDraggablePart part, CombineSlot slot)
{
slot.SetOccupiedPart(part);
part.PlaceToSlot(slot);
HidePlacedPair(slot, part);
_placedCount++;
AdvanceOrderCursor();
PublishFeedback(CombineFeedbackType.Success);
GameEntry.Event.Fire(this, CombineProgressEventArgs.Create(_placedCount, _orderedSlots.Count));
TryCompletePuzzle();
}
/// <summary>
/// 检查是否完成全部步骤并触发完成事件。
/// </summary>
private void TryCompletePuzzle()
{
if (_placedCount < _orderedSlots.Count)
{
return;
}
_isCompleted = true;
_onPuzzleCompleted.Invoke();
}
/// <summary>
/// 处理放置失败:部件回弹并提示。
/// </summary>
private void RejectPlace(CombineDraggablePart part, CombineFeedbackType feedbackType)
{
part.ReturnToSpawnAnimated();
PublishFeedback(feedbackType);
}
private void PublishFeedback(CombineFeedbackType feedbackType)
{
string message = string.Empty;
switch (feedbackType)
{
case CombineFeedbackType.Success:
message = _successHint;
break;
case CombineFeedbackType.OrderError:
message = _orderErrorHint;
break;
case CombineFeedbackType.PositionError:
message = _positionErrorHint;
break;
}
GameEntry.Event.Fire(this, CombineFeedbackEventArgs.Create(feedbackType, message));
}
private static void HidePlacedPair(CombineSlot slot, CombineDraggablePart part)
{
if (slot != null && slot.gameObject != null)
{
slot.gameObject.SetActive(false);
}
if (part != null && part.gameObject != null)
{
part.gameObject.SetActive(false);
}
}
#endregion
#region Runtime Context
/// <summary>
/// 绑定运行时上下文根节点。
/// </summary>
/// <param name="puzzleRoot">拼装根节点。</param>
/// <param name="dragRoot">拖拽根节点。</param>
public void BindRuntimeContext(Transform puzzleRoot, Transform dragRoot = null)
{
_puzzleRoot = puzzleRoot;
if (dragRoot != null)
{
_dragRoot = dragRoot;
}
}
/// <summary>
/// 设置当前UI上下文数据。
/// </summary>
public void SetFormContext(CombineFormContext context)
{
_formContext = context;
}
/// <summary>
/// 获取当前UI上下文数据。
/// </summary>
public CombineFormContext GetFormContext()
{
return _formContext;
}
/// <summary>
/// 清理运行时上下文与状态。
/// </summary>
public void ClearRuntimeContext()
{
_formContext = null;
_puzzleRoot = null;
_dragRoot = null;
_runtimeSlots.Clear();
_runtimeParts.Clear();
_orderedSlots.Clear();
_nextOrderSlotIndex = 0;
_placedCount = 0;
_isStarted = false;
_isCompleted = false;
_isPaused = false;
}
/// <summary>
/// 获取当前拖拽根节点。
/// </summary>
public Transform GetDragRoot()
{
return _dragRoot;
}
/// <summary>
/// 确保表单控制器已创建。
/// </summary>
private void EnsureFormController()
{
if (_formController == null)
{
_formController = new CombineFormController(this);
}
}
private bool TryBuildRuntimeFormContext(CombineFormContext context, out CombineFormContext runtimeContext)
{
runtimeContext = context ?? new CombineFormContext();
if (!TryGetNextLevelStructurePrefab(out GameObject structurePrefab))
{
return false;
}
runtimeContext.StructurePrefab = structurePrefab;
return true;
}
private bool TryGetNextLevelStructurePrefab(out GameObject prefab)
{
prefab = null;
if (_levelStructurePrefabs == null || _levelStructurePrefabs.Count == 0)
{
Log.Warning("CoreGameplayA start failed. level structure list is empty.");
return false;
}
int maxPlayableCount = Mathf.Min(MaxCombineRoundCount, _levelStructurePrefabs.Count);
if (_nextLevelStructureIndex < 0 || _nextLevelStructureIndex >= maxPlayableCount)
{
Log.Warning("CoreGameplayA start skipped. No remaining level structure prefab. used={0}, total={1}.",
_nextLevelStructureIndex.ToString(), maxPlayableCount.ToString());
return false;
}
prefab = _levelStructurePrefabs[_nextLevelStructureIndex];
if (prefab != null)
{
return true;
}
Log.Warning("CoreGameplayA start failed. level structure prefab at index {0} is null.",
_nextLevelStructureIndex.ToString());
return false;
}
#endregion
#region Collection And Preparation
/// <summary>
/// 收集拼装所需的槽位与部件。
/// </summary>
private void CollectChildren()
{
_runtimeSlots.Clear();
_runtimeParts.Clear();
Transform collectRoot = _puzzleRoot != null ? _puzzleRoot : transform;
CombineSlot[] foundSlots = collectRoot.GetComponentsInChildren<CombineSlot>(true);
for (int i = 0; i < foundSlots.Length; i++)
{
CombineSlot slot = foundSlots[i];
if (slot != null)
{
_runtimeSlots.Add(slot);
}
}
CollectUniqueParts(collectRoot);
if (_dragRoot != null && !ReferenceEquals(_dragRoot, collectRoot))
{
CollectUniqueParts(_dragRoot);
}
if (_runtimeSlots.Count == 0 || _runtimeParts.Count == 0)
{
Log.Warning("CoreGameplayA collect failed. slots={0}, parts={1}.", _runtimeSlots.Count.ToString(),
_runtimeParts.Count.ToString());
}
}
/// <summary>
/// 从指定根节点收集不重复的可拖拽部件。
/// </summary>
private void CollectUniqueParts(Transform root)
{
if (root == null)
{
return;
}
CombineDraggablePart[] parts = root.GetComponentsInChildren<CombineDraggablePart>(true);
for (int i = 0; i < parts.Length; i++)
{
CombineDraggablePart part = parts[i];
if (part == null || _runtimeParts.Contains(part))
{
continue;
}
_runtimeParts.Add(part);
}
}
/// <summary>
/// 构建按 BuildOrder 排序后的槽位列表。
/// </summary>
private void BuildOrderedSlotList()
{
_orderedSlots.Clear();
for (int i = 0; i < _runtimeSlots.Count; i++)
{
CombineSlot slot = _runtimeSlots[i];
if (slot != null)
{
_orderedSlots.Add(slot);
}
}
_orderedSlots.Sort((a, b) => a.BuildOrder.CompareTo(b.BuildOrder));
}
/// <summary>
/// 初始化槽位控制器绑定与占用状态。
/// </summary>
private void PrepareSlots()
{
for (int i = 0; i < _orderedSlots.Count; i++)
{
CombineSlot slot = _orderedSlots[i];
slot.gameObject.SetActive(true);
slot.BindController(this);
slot.ResetSlot();
}
}
/// <summary>
/// 初始化部件控制器绑定与出生点状态。
/// </summary>
private void PrepareParts()
{
for (int i = 0; i < _runtimeParts.Count; i++)
{
CombineDraggablePart part = _runtimeParts[i];
if (part == null)
{
continue;
}
part.gameObject.SetActive(true);
part.BindController(this);
part.CacheSpawnState();
part.ResetToSpawn();
}
}
#endregion
#region Rule Helpers
/// <summary>
/// 是否需要校验严格顺序。
/// </summary>
private bool NeedValidateOrder(CombineSlot slot)
{
return _strictGlobalOrder || slot.RequireStrictOrder;
}
/// <summary>
/// 当前槽位是否是下一步期望槽位。
/// </summary>
private bool IsExpectedOrderSlot(CombineSlot slot)
{
if (_nextOrderSlotIndex < 0 || _nextOrderSlotIndex >= _orderedSlots.Count)
{
return false;
}
return ReferenceEquals(_orderedSlots[_nextOrderSlotIndex], slot);
}
/// <summary>
/// 推进顺序游标到下一个未占用槽位。
/// </summary>
private void AdvanceOrderCursor()
{
while (_nextOrderSlotIndex < _orderedSlots.Count && _orderedSlots[_nextOrderSlotIndex].IsOccupied)
{
_nextOrderSlotIndex++;
}
}
#endregion
}
}