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