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); HidePlacedPair(slot, part); _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; _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)); } 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 /// /// 绑定运行时上下文根节点。 /// /// 拼装根节点。 /// 拖拽根节点。 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.gameObject.SetActive(true); 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.gameObject.SetActive(true); 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 } }