biography-of-lijie/Assets/GameMain/Scripts/UI/View/CombineForm.cs

350 lines
10 KiB
C#

using System.Collections.Generic;
using CustomComponent;
using Event;
using GameFramework.Event;
using TMPro;
using UnityEngine;
using UnityEngine.Serialization;
using UnityGameFramework.Runtime;
namespace UI
{
/// <summary>
/// MVC UI form for GameplayA. It builds slots and draggable parts from external data.
/// </summary>
public class CombineForm : UGuiForm
{
#region Property
[SerializeField] private RectTransform _slotRoot;
[SerializeField] private RectTransform _partRoot;
[SerializeField] private CombineDraggablePart _partPrefab;
[FormerlySerializedAs("_progressText")] [SerializeField]
private TMP_Text _hintText;
[SerializeField] private Vector2 _partStartAnchoredPosition = new(0f, -40f);
[SerializeField] private float _partVerticalSpacing = 120f;
[SerializeField] [Range(0f, 1f)] private float _partSpawnMinXNormalized = 0.55f;
[SerializeField] [Range(0f, 1f)] private float _partSpawnMaxXNormalized = 0.95f;
[SerializeField] [Range(0f, 1f)] private float _partSpawnMinYNormalized = 0.1f;
[SerializeField] [Range(0f, 1f)] private float _partSpawnMaxYNormalized = 0.9f;
private CombineComponent _controller;
private readonly List<GameObject> _runtimeNodes = new();
#endregion
#region FSM
protected override void OnInit(object userData)
{
base.OnInit(userData);
if (_controller == null)
{
_controller = GameEntry.Combine;
}
ResetTexts();
}
protected override void OnOpen(object userData)
{
base.OnOpen(userData);
if (_controller == null)
{
_controller = GameEntry.Combine;
}
if (userData is CombineFormContext context)
{
if (!CanBuildContext(context))
{
Log.Warning("CombineMvcForm open failed. StructurePrefab/PartSprites is invalid.");
return;
}
Build(context);
_controller.BindRuntimeContext(_slotRoot, _partRoot);
GameEntry.Event.Subscribe(CombineFeedbackEventArgs.EventId, OnFeedbackChanged);
if (context.AutoStart)
{
_controller.StartPuzzle();
}
}
else
{
Log.Error("CombineMvcForm open failed. userData is invalid.");
}
}
private static bool CanBuildContext(CombineFormContext context)
{
if (context == null)
{
return false;
}
bool hasStructurePrefab = context.StructurePrefab != null;
bool hasPartSprites = context.PartSprites != null && context.PartSprites.Count > 0;
return hasStructurePrefab && hasPartSprites;
}
protected override void OnClose(bool isShutdown, object userData)
{
GameEntry.Event.Unsubscribe(CombineFeedbackEventArgs.EventId, OnFeedbackChanged);
if (_controller != null)
{
_controller.ClearRuntimeContext();
}
ClearRuntimeNodes();
ResetTexts();
base.OnClose(isShutdown, userData);
}
protected override void OnPause()
{
base.OnPause();
if (_controller != null)
{
_controller.PausePuzzle();
}
}
protected override void OnResume()
{
base.OnResume();
if (_controller != null)
{
_controller.ResumePuzzle();
}
}
protected override void OnCover()
{
base.OnCover();
if (_controller != null)
{
_controller.PausePuzzle();
}
}
protected override void OnReveal()
{
base.OnReveal();
if (_controller != null)
{
_controller.ResumePuzzle();
}
}
#endregion
#region Other Methods
private void ClearRuntimeNodes()
{
for (int i = _runtimeNodes.Count - 1; i >= 0; i--)
{
GameObject node = _runtimeNodes[i];
if (node != null)
{
GameObject.Destroy(node);
}
}
_runtimeNodes.Clear();
}
private void ResetTexts()
{
if (_hintText != null)
{
_hintText.text = string.Empty;
}
}
private void Build(CombineFormContext context)
{
ClearRuntimeNodes();
List<CombineSlot> runtimeSlots = BuildSlots(context);
if (runtimeSlots.Count == 0)
{
Log.Warning("CombineMvcForm build failed. No CombineSlot found in structure prefab.");
return;
}
List<Sprite> partSprites = ClonePartSprites(context.PartSprites);
BuildParts(partSprites, runtimeSlots);
}
private List<CombineSlot> BuildSlots(CombineFormContext context)
{
var runtimeSlots = new List<CombineSlot>();
if (context.StructurePrefab == null)
{
return runtimeSlots;
}
GameObject structureNode = Instantiate(context.StructurePrefab, _slotRoot, false);
CenterStructureNode(structureNode);
_runtimeNodes.Add(structureNode);
CombineStageOverlay stageOverlay = structureNode.GetComponentInChildren<CombineStageOverlay>(true);
if (stageOverlay != null)
{
List<CombineSlot> managedSlots = stageOverlay.GetManagedSlots();
if (managedSlots.Count > 0)
{
return managedSlots;
}
}
CombineSlot[] structureSlots = structureNode.GetComponentsInChildren<CombineSlot>(true);
for (int i = 0; i < structureSlots.Length; i++)
{
CombineSlot slot = structureSlots[i];
if (slot != null)
{
runtimeSlots.Add(slot);
}
}
return runtimeSlots;
}
private void BuildParts(List<Sprite> partSprites, List<CombineSlot> runtimeSlots)
{
if (_partPrefab == null)
{
Log.Warning("CombineMvcForm build failed. Part prefab is missing.");
return;
}
List<CombineSlot> orderedSlots = new List<CombineSlot>(runtimeSlots);
orderedSlots.Sort((a, b) => a.BuildOrder.CompareTo(b.BuildOrder));
int spawnCount = Mathf.Min(partSprites.Count, orderedSlots.Count);
if (partSprites.Count != orderedSlots.Count)
{
Log.Warning("CombineMvcForm build mismatch. partSprites={0}, slots={1}.", partSprites.Count.ToString(),
orderedSlots.Count.ToString());
}
for (int i = 0; i < spawnCount; i++)
{
CombineSlot slot = orderedSlots[i];
if (slot == null)
{
continue;
}
CombineDraggablePart part = Instantiate(_partPrefab, _partRoot, false);
part.ConfigureRuntime(slot.RequiredPartType, partSprites[i]);
SetPartSpawnPosition(part, i);
_runtimeNodes.Add(part.gameObject);
}
}
private static List<Sprite> ClonePartSprites(List<Sprite> source)
{
var result = new List<Sprite>();
if (source == null)
{
return result;
}
for (int i = 0; i < source.Count; i++)
{
result.Add(source[i]);
}
return result;
}
private static void CenterStructureNode(GameObject structureNode)
{
if (structureNode == null)
{
return;
}
RectTransform rectTransform = structureNode.transform as RectTransform;
if (rectTransform != null)
{
rectTransform.anchoredPosition = Vector2.zero;
rectTransform.localScale = Vector3.one;
return;
}
structureNode.transform.localPosition = Vector3.zero;
structureNode.transform.localScale = Vector3.one;
}
private void SetPartSpawnPosition(CombineDraggablePart part, int index)
{
RectTransform partRect = part.transform as RectTransform;
if (partRect == null || _partRoot == null)
{
return;
}
Rect rootRect = _partRoot.rect;
if (rootRect.width <= 0f || rootRect.height <= 0f)
{
partRect.anchoredPosition = _partStartAnchoredPosition + Vector2.down * (_partVerticalSpacing * index);
return;
}
float minX = Mathf.Lerp(rootRect.xMin, rootRect.xMax,
Mathf.Min(_partSpawnMinXNormalized, _partSpawnMaxXNormalized));
float maxX = Mathf.Lerp(rootRect.xMin, rootRect.xMax,
Mathf.Max(_partSpawnMinXNormalized, _partSpawnMaxXNormalized));
float minY = Mathf.Lerp(rootRect.yMin, rootRect.yMax,
Mathf.Min(_partSpawnMinYNormalized, _partSpawnMaxYNormalized));
float maxY = Mathf.Lerp(rootRect.yMin, rootRect.yMax,
Mathf.Max(_partSpawnMinYNormalized, _partSpawnMaxYNormalized));
partRect.anchoredPosition = new Vector2(
Random.Range(minX, maxX),
Random.Range(minY, maxY));
}
#endregion
#region Event Handlers
private void OnFeedbackChanged(object sender, GameEventArgs e)
{
if (!(e is CombineFeedbackEventArgs args)) return;
if (_hintText != null)
{
_hintText.text = args.Message;
}
}
#endregion
}
}