初始化仓库

This commit is contained in:
SepComet
2026-02-08 17:08:52 +08:00
commit 8763955323
1132 changed files with 285326 additions and 0 deletions
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 797dee361a842bf4a826e018725f327a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,27 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace UI
{
/// <summary>
/// Open data for MVC-based GameplayA form.
/// </summary>
public sealed class CombineFormContext : UIContext
{
/// <summary>
/// Slot definitions. Position and order are provided externally.
/// </summary>
public List<CombineSlotContext> Slots;
/// <summary>
/// Optional part definitions. When null/empty, parts are derived from slots.
/// </summary>
public List<CombinePartContext> Parts;
/// <summary>
/// Auto start puzzle after runtime nodes are generated.
/// </summary>
public bool AutoStart = true;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6d8c5c616c000704fb2d7d94a8d22c84
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,21 @@
using System;
using Definition.Enum;
using UnityEngine;
namespace UI
{
/// <summary>
/// Runtime part data passed from external system.
/// </summary>
[Serializable]
public sealed class CombinePartContext
{
public CombinePartType PartType = CombinePartType.Dou;
public string PartDisplayName = "Dou";
public string MechanicsExplanation = string.Empty;
public bool LockAfterPlaced = true;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 82f303529f5b4b040845223f97a66cfa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,28 @@
using System;
using Definition.Enum;
using UnityEngine;
namespace UI
{
/// <summary>
/// Runtime slot data passed from external system.
/// </summary>
[Serializable]
public sealed class CombineSlotContext
{
public CombinePartType RequiredPartType = CombinePartType.Dou;
public int BuildOrder = 0;
public bool RequireStrictOrder = true;
public Vector2 AnchoredPosition = Vector2.zero;
public Vector2 SizeDelta = new Vector2(120f, 120f);
public string MechanicsExplanation = string.Empty;
public string MismatchHint = string.Empty;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 03894567063ea7d42a417545043e32c6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f5e3a76d06305a244a21a9ecabe635bc
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,96 @@
using CustomComponent;
using GameFramework.Event;
using UnityGameFramework.Runtime;
namespace UI
{
public class CombineFormController : IFormController<CombineFormContext>
{
private CombineComponent _controller;
private CombineForm _combineForm;
private int? _formSerialId;
private CombineFormContext _context;
public CombineFormController(CombineComponent controller)
{
_controller = controller;
GameEntry.Event.Subscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess);
GameEntry.Event.Subscribe(CloseUIFormCompleteEventArgs.EventId, CloseUIFormComplete);
}
public int? OpenUI(CombineFormContext context)
{
if (_controller == null)
{
_controller = GameEntry.Combine;
}
if (_controller == null)
{
Log.Warning("CombineFormController open failed. Controller is null.");
return null;
}
if (context != null)
{
_controller.SetFormContext(context);
}
_context = _controller.GetFormContext();
if (_context == null)
{
Log.Warning("CombineFormController open failed. Form context is null.");
return null;
}
_formSerialId = GameEntry.UI.OpenUIForm(UIFormId.CombineForm, _context);
return _formSerialId;
}
public void CloseUI()
{
if (_formSerialId.HasValue)
{
GameEntry.UI.CloseUIForm(_formSerialId.Value);
return;
}
if (_combineForm != null)
{
_combineForm.Close();
}
}
~CombineFormController()
{
GameEntry.Event.Unsubscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess);
GameEntry.Event.Unsubscribe(CloseUIFormCompleteEventArgs.EventId, CloseUIFormComplete);
}
private void OpenUIFormSuccess(object sender, GameEventArgs e)
{
if (!(e is OpenUIFormSuccessEventArgs args)) return;
if (args.UserData == _context)
{
_combineForm = args.UIForm.Logic as CombineForm;
}
}
private void CloseUIFormComplete(object sender, GameEventArgs e)
{
if (!(e is CloseUIFormCompleteEventArgs args)) return;
if (args.SerialId == _formSerialId)
{
_combineForm = null;
_formSerialId = null;
_context = null;
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ff5681c108fbc6c4680287e385ddaa12
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2bb96e9f3ec659447838d79b7b33db97
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,254 @@
using CustomComponent;
using Definition.Enum;
using UnityEngine;
using UnityEngine.EventSystems;
namespace UI
{
/// <summary>
/// 可拖拽拼装部件:负责拖拽交互、出生点回退与放置到槽位。
/// </summary>
[RequireComponent(typeof(RectTransform))]
[DisallowMultipleComponent]
public class CombineDraggablePart : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
#region Inspector Config
[SerializeField] private CombinePartType _partType = CombinePartType.Dou;
[SerializeField] private string _partDisplayName = "Dou";
[SerializeField] [TextArea(2, 4)] private string _mechanicsExplanation = string.Empty;
[SerializeField] private bool _lockAfterPlaced = true;
[SerializeField] private RectTransform _rectTransform;
[SerializeField] private CanvasGroup _canvasGroup;
#endregion
#region Spawn State
private bool _spawnStateCached;
private Transform _spawnParent;
private Vector3 _spawnWorldPosition = Vector3.zero;
private Quaternion _spawnWorldRotation = Quaternion.identity;
private Vector3 _spawnWorldScale = Vector3.one;
private int _spawnSiblingIndex;
#endregion
#region Runtime State
private CombineComponent _controller;
private CombineSlot _currentSlot;
private bool _isPlaced;
private bool _isLocked;
#endregion
#region Public Query
public CombinePartType PartType => _partType;
public string MechanicsExplanation => _mechanicsExplanation;
#endregion
#region Setup
/// <summary>
/// 绑定玩法控制器。
/// </summary>
public void BindController(CombineComponent controller)
{
_controller = controller;
}
/// <summary>
/// 使用运行时数据初始化部件配置。
/// </summary>
public void Initialize(CombinePartContext data)
{
_partType = data.PartType;
_partDisplayName = string.IsNullOrEmpty(data.PartDisplayName)
? data.PartType.ToString()
: data.PartDisplayName;
_mechanicsExplanation = data.MechanicsExplanation ?? string.Empty;
_lockAfterPlaced = data.LockAfterPlaced;
}
/// <summary>
/// 缓存出生点状态(仅首次缓存)。
/// </summary>
public void CacheSpawnState()
{
if (_spawnStateCached)
{
return;
}
_spawnStateCached = true;
_spawnParent = _rectTransform.parent;
_spawnWorldPosition = _rectTransform.position;
_spawnWorldRotation = _rectTransform.rotation;
_spawnWorldScale = _rectTransform.localScale;
_spawnSiblingIndex = _rectTransform.GetSiblingIndex();
}
/// <summary>
/// 重置部件到出生点,并清理槽位占用状态。
/// </summary>
public void ResetToSpawn()
{
ClearSlotOccupancy();
_isPlaced = false;
_isLocked = false;
ReturnToSpawn();
}
#endregion
#region Drag Flow
/// <summary>
/// 开始拖拽:关闭射线阻挡并切到拖拽层级。
/// </summary>
public void OnBeginDrag(PointerEventData eventData)
{
if (!CanStartDrag())
{
return;
}
_canvasGroup.blocksRaycasts = false;
MoveToDragRoot();
_rectTransform.SetAsLastSibling();
}
/// <summary>
/// 拖拽中:按指针增量更新锚点位置。
/// </summary>
public void OnDrag(PointerEventData eventData)
{
if (!CanDrag(eventData))
{
return;
}
_rectTransform.anchoredPosition += eventData.delta;
}
/// <summary>
/// 结束拖拽:恢复射线阻挡;未成功放置则回到出生点。
/// </summary>
public void OnEndDrag(PointerEventData eventData)
{
if (_isLocked)
{
return;
}
_canvasGroup.blocksRaycasts = true;
if (!_isPlaced)
{
ReturnToSpawn();
}
}
/// <summary>
/// 校验是否允许开始拖拽。
/// </summary>
private bool CanStartDrag()
{
return !_isLocked && !_isPlaced;
}
/// <summary>
/// 校验拖拽中是否可更新位置。
/// </summary>
private bool CanDrag(PointerEventData eventData)
{
return !_isLocked && !_isPlaced && eventData != null;
}
/// <summary>
/// 将部件切到控制器提供的拖拽根节点。
/// </summary>
private void MoveToDragRoot()
{
if (_controller == null)
{
return;
}
Transform dragRoot = _controller.GetDragRoot();
if (dragRoot != null)
{
_rectTransform.SetParent(dragRoot, true);
}
}
#endregion
#region Placement
/// <summary>
/// 将部件恢复到出生点状态。
/// </summary>
public void ReturnToSpawn()
{
if (_spawnParent == null)
{
return;
}
_rectTransform.SetParent(_spawnParent, true);
_rectTransform.position = _spawnWorldPosition;
_rectTransform.rotation = _spawnWorldRotation;
_rectTransform.localScale = _spawnWorldScale;
_rectTransform.SetSiblingIndex(_spawnSiblingIndex);
}
/// <summary>
/// 应用成功放置到槽位后的表现与状态。
/// </summary>
internal void PlaceToSlot(CombineSlot slot)
{
_currentSlot = slot;
_isPlaced = true;
_isLocked = _lockAfterPlaced;
RectTransform snapPoint = slot.SnapPoint;
_rectTransform.SetParent(snapPoint != null ? snapPoint : slot.transform, false);
_rectTransform.anchoredPosition = Vector2.zero;
_rectTransform.localRotation = Quaternion.identity;
_rectTransform.localScale = Vector3.one;
_canvasGroup.blocksRaycasts = !_isLocked;
}
/// <summary>
/// 清理当前槽位占用记录。
/// </summary>
private void ClearSlotOccupancy()
{
if (_currentSlot != null)
{
_currentSlot.ClearOccupiedPart(this);
_currentSlot = null;
}
}
#endregion
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c019085f31e649a88e99eb655ed94b15
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,247 @@
using System.Collections.Generic;
using CustomComponent;
using Event;
using GameFramework.Event;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityGameFramework.Runtime;
namespace UI
{
/// <summary>
/// MVC UI form for GameplayA. It builds slots and draggable parts from external data.
/// </summary>
[DisallowMultipleComponent]
public class CombineForm : UGuiForm
{
#region Property
[SerializeField] private CombineComponent _controller;
[SerializeField] private RectTransform _slotRoot;
[SerializeField] private RectTransform _partRoot;
[SerializeField] private CombineSlot _slotPrefab;
[SerializeField] private CombineDraggablePart _partPrefab;
[SerializeField] private TMP_Text _guideText;
[SerializeField] private TMP_Text _explanationText;
[SerializeField] private TMP_Text _progressText;
[SerializeField] private string _progressFormat = "{0}/{1}";
[SerializeField] private Vector2 _partStartAnchoredPosition = new(0f, -40f);
[SerializeField] private float _partVerticalSpacing = 120f;
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 (context.Slots.Count == 0)
{
Log.Warning("CombineMvcForm open failed. Slot model is empty.");
return;
}
Build(context);
_controller.BindRuntimeContext(_slotRoot, _partRoot);
GameEntry.Event.Subscribe(CombineGuideMessageEventArgs.EventId, OnGuideMessageChanged);
GameEntry.Event.Subscribe(CombinePartMessageEventArgs.EventId, OnPartExplained);
GameEntry.Event.Subscribe(CombineProgressEventArgs.EventId, OnProgressChanged);
GameEntry.Event.Subscribe(CombineCompletedEventArgs.EventId, OnPuzzleCompleted);
if (context.AutoStart)
{
_controller.StartPuzzle();
}
else
{
GameEntry.Event.Fire(this,
CombineProgressEventArgs.Create(_controller.CurrentStep, _controller.TotalStep));
}
}
else
{
Log.Error("CombineMvcForm open failed. userData is invalid.");
}
}
protected override void OnClose(bool isShutdown, object userData)
{
GameEntry.Event.Unsubscribe(CombineGuideMessageEventArgs.EventId, OnGuideMessageChanged);
GameEntry.Event.Unsubscribe(CombinePartMessageEventArgs.EventId, OnPartExplained);
GameEntry.Event.Unsubscribe(CombineProgressEventArgs.EventId, OnProgressChanged);
GameEntry.Event.Unsubscribe(CombineCompletedEventArgs.EventId, OnPuzzleCompleted);
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()
{
GameEntry.Event.Fire(this, CombineGuideMessageEventArgs.Create(string.Empty));
GameEntry.Event.Fire(this, CombinePartMessageEventArgs.Create(string.Empty));
GameEntry.Event.Fire(this, CombineProgressEventArgs.Create(0, 0));
}
private void Build(CombineFormContext context)
{
ClearRuntimeNodes();
foreach (var slotData in context.Slots)
{
CombineSlot slot = Instantiate(_slotPrefab, _slotRoot, false);
slot.Initialize(slotData);
slot.SetSnapPoint(slot.transform as RectTransform);
_runtimeNodes.Add(slot.gameObject);
}
foreach (var partData in context.Parts)
{
CombineDraggablePart part = Instantiate(_partPrefab, _partRoot, false);
part.Initialize(partData);
_runtimeNodes.Add(part.gameObject);
}
}
#endregion
#region Event Handlers
private void OnGuideMessageChanged(object sender, GameEventArgs e)
{
if (!(e is CombineGuideMessageEventArgs args)) return;
if (_guideText != null)
{
_guideText.text = args.Message;
}
}
private void OnPartExplained(object sender, GameEventArgs e)
{
if (!(e is CombinePartMessageEventArgs args)) return;
if (_explanationText != null)
{
_explanationText.text = args.Message;
}
}
private void OnProgressChanged(object sender, GameEventArgs e)
{
if (!(e is CombineProgressEventArgs args)) return;
if (_progressText != null)
{
_progressText.text = string.Format(_progressFormat, args.CurrentStep, args.TotalSteps);
}
}
private void OnPuzzleCompleted(object sender, GameEventArgs e)
{
if (!(e is CombineCompletedEventArgs args)) return;
if (_progressText != null)
{
_progressText.text = string.Format(_progressFormat, _controller.TotalStep, _controller.TotalStep);
}
}
#endregion
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 004eb70514fd9064ca990769ebe97043
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,195 @@
using CustomComponent;
using Definition.Enum;
using UnityEngine;
using UnityEngine.EventSystems;
namespace UI
{
/// <summary>
/// 拼装槽位:接收拖拽部件并转发给玩法控制器做规则校验。
/// </summary>
[DisallowMultipleComponent]
public class CombineSlot : MonoBehaviour, IDropHandler
{
#region Inspector Config
/// <summary>
/// 槽位要求的部件类型。
/// </summary>
[SerializeField] private CombinePartType _requiredPartType = CombinePartType.Dou;
/// <summary>
/// 槽位在拼装流程中的顺序值。
/// </summary>
[SerializeField] private int _buildOrder;
/// <summary>
/// 是否要求严格按顺序放置。
/// </summary>
[SerializeField] private bool _requireStrictOrder = true;
/// <summary>
/// 部件吸附点;为空时使用自身 RectTransform。
/// </summary>
[SerializeField] private RectTransform _snapPoint;
/// <summary>
/// 放置成功后的力学说明文案。
/// </summary>
[SerializeField] [TextArea(2, 4)] private string _mechanicsExplanation = string.Empty;
/// <summary>
/// 部件不匹配时的自定义提示文案。
/// </summary>
[SerializeField] [TextArea(2, 4)] private string _mismatchHint = string.Empty;
#endregion
#region Runtime State
/// <summary>
/// 玩法控制器引用。
/// </summary>
private CombineComponent _controller;
/// <summary>
/// 当前占用该槽位的部件。
/// </summary>
private CombineDraggablePart _occupiedPart;
#endregion
#region Public Query
/// <summary>
/// 获取槽位要求的部件类型。
/// </summary>
public CombinePartType RequiredPartType => _requiredPartType;
/// <summary>
/// 获取槽位顺序值。
/// </summary>
public int BuildOrder => _buildOrder;
/// <summary>
/// 获取槽位是否要求严格顺序。
/// </summary>
public bool RequireStrictOrder => _requireStrictOrder;
/// <summary>
/// 获取槽位是否已被占用。
/// </summary>
public bool IsOccupied => _occupiedPart != null;
/// <summary>
/// 获取槽位的力学说明文案。
/// </summary>
public string MechanicsExplanation => _mechanicsExplanation;
/// <summary>
/// 获取槽位不匹配提示文案。
/// </summary>
public string MismatchHint => _mismatchHint;
/// <summary>
/// 获取槽位吸附点。
/// </summary>
public RectTransform SnapPoint => _snapPoint != null ? _snapPoint : transform as RectTransform;
#endregion
#region Setup
/// <summary>
/// 绑定玩法控制器。
/// </summary>
public void BindController(CombineComponent controller)
{
_controller = controller;
}
/// <summary>
/// 使用运行时数据初始化槽位配置。
/// </summary>
public void Initialize(CombineSlotContext data)
{
_requiredPartType = data.RequiredPartType;
_buildOrder = data.BuildOrder;
_requireStrictOrder = data.RequireStrictOrder;
_mechanicsExplanation = data.MechanicsExplanation ?? string.Empty;
_mismatchHint = data.MismatchHint ?? string.Empty;
}
/// <summary>
/// 设置槽位吸附点,为空时回退到槽位自身。
/// </summary>
public void SetSnapPoint(RectTransform snapPoint)
{
_snapPoint = snapPoint;
}
/// <summary>
/// 重置槽位占用状态。
/// </summary>
public void ResetSlot()
{
_occupiedPart = null;
}
#endregion
#region Drop Handling
/// <summary>
/// 拖拽放下回调:识别部件并交给控制器校验放置。
/// </summary>
public void OnDrop(PointerEventData eventData)
{
if (!CanHandleDrop(eventData))
{
return;
}
CombineDraggablePart part = eventData.pointerDrag.GetComponent<CombineDraggablePart>();
if (part == null)
{
return;
}
_controller.TryPlacePart(part, this);
}
/// <summary>
/// 校验是否满足处理放下事件的前置条件。
/// </summary>
private bool CanHandleDrop(PointerEventData eventData)
{
return _controller != null && eventData != null && eventData.pointerDrag != null;
}
#endregion
#region Occupancy
/// <summary>
/// 标记当前槽位被指定部件占用。
/// </summary>
internal void SetOccupiedPart(CombineDraggablePart part)
{
_occupiedPart = part;
}
/// <summary>
/// 清理槽位占用,仅在占用者与传入部件一致时生效。
/// </summary>
internal void ClearOccupiedPart(CombineDraggablePart part)
{
if (_occupiedPart == part)
{
_occupiedPart = null;
}
}
#endregion
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5986fde954b3467b96ccc5d2d741297a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: