迭代拼装玩法与素材

This commit is contained in:
2026-04-22 10:54:24 +08:00
parent a1b6b4a823
commit 204eb6e4d0
35 changed files with 3471 additions and 848 deletions
@@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using Definition.Enum;
using Event;
using UI;
using UnityEngine;
@@ -42,40 +41,11 @@ namespace CustomComponent
/// </summary>
[SerializeField] private Transform _puzzleRoot = null;
/// <summary>
/// 拼装开始时提示文本。
/// </summary>
[SerializeField]
[TextArea(2, 4)]
private string _startHint = "Assemble dougong from lower-to-upper and inner-to-outer.";
[SerializeField] [TextArea(1, 3)] private string _successHint = "拼接成功";
/// <summary>
/// 放置顺序错误提示文本。
/// </summary>
[SerializeField]
[TextArea(2, 4)]
private string _wrongOrderHint = "Wrong order. Place lower or inner parts first.";
[SerializeField] [TextArea(1, 3)] private string _orderErrorHint = "顺序错误";
/// <summary>
/// 部件不匹配提示文本。
/// </summary>
[SerializeField]
[TextArea(2, 4)]
private string _wrongPartHint = "Part does not match this slot.";
/// <summary>
/// 槽位已占用提示文本。
/// </summary>
[SerializeField]
[TextArea(2, 4)]
private string _slotOccupiedHint = "This slot is already occupied.";
/// <summary>
/// 拼装完成提示文本。
/// </summary>
[SerializeField]
[TextArea(2, 4)]
private string _completeHint = "Dougong assembly completed.";
[SerializeField] [TextArea(1, 3)] private string _positionErrorHint = "位置错误";
/// <summary>
/// 当前参与拼装的槽位列表。
@@ -229,11 +199,6 @@ namespace CustomComponent
_isPaused = false;
GameEntry.Event.Fire(this, CombineProgressEventArgs.Create(_placedCount, _orderedSlots.Count));
if (!string.IsNullOrEmpty(_startHint))
{
GameEntry.Event.Fire(this, CombineGuideMessageEventArgs.Create(_startHint));
}
}
/// <summary>
@@ -311,7 +276,7 @@ namespace CustomComponent
return true;
}
RejectPlace(part, _slotOccupiedHint);
RejectPlace(part, CombineFeedbackType.PositionError);
return false;
}
@@ -325,8 +290,7 @@ namespace CustomComponent
return true;
}
string hint = string.IsNullOrEmpty(slot.MismatchHint) ? _wrongPartHint : slot.MismatchHint;
RejectPlace(part, hint);
RejectPlace(part, CombineFeedbackType.PositionError);
return false;
}
@@ -340,7 +304,7 @@ namespace CustomComponent
return true;
}
RejectPlace(part, _wrongOrderHint);
RejectPlace(part, CombineFeedbackType.OrderError);
return false;
}
@@ -354,7 +318,7 @@ namespace CustomComponent
_placedCount++;
AdvanceOrderCursor();
PublishExplanation(part, slot);
PublishFeedback(CombineFeedbackType.Success);
GameEntry.Event.Fire(this, CombineProgressEventArgs.Create(_placedCount, _orderedSlots.Count));
TryCompletePuzzle();
@@ -372,11 +336,6 @@ namespace CustomComponent
_isCompleted = true;
if (!string.IsNullOrEmpty(_completeHint))
{
GameEntry.Event.Fire(this, CombineGuideMessageEventArgs.Create(_completeHint));
}
GameEntry.Event.Fire(this, CombineCompletedEventArgs.Create());
_onPuzzleCompleted.Invoke();
}
@@ -384,14 +343,29 @@ namespace CustomComponent
/// <summary>
/// 处理放置失败:部件回弹并提示。
/// </summary>
private void RejectPlace(CombineDraggablePart part, string hint)
private void RejectPlace(CombineDraggablePart part, CombineFeedbackType feedbackType)
{
part.ReturnToSpawnAnimated();
PublishFeedback(feedbackType);
}
if (!string.IsNullOrEmpty(hint))
private void PublishFeedback(CombineFeedbackType feedbackType)
{
string message = string.Empty;
switch (feedbackType)
{
GameEntry.Event.Fire(this, CombineGuideMessageEventArgs.Create(hint));
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
@@ -603,50 +577,6 @@ namespace CustomComponent
}
}
/// <summary>
/// 发布当前放置对应的力学说明文案。
/// </summary>
private void PublishExplanation(CombineDraggablePart part, CombineSlot slot)
{
string explanation = slot.MechanicsExplanation;
if (string.IsNullOrEmpty(explanation))
{
explanation = part.MechanicsExplanation;
}
if (string.IsNullOrEmpty(explanation))
{
explanation = GetDefaultExplanation(part.PartType);
}
if (!string.IsNullOrEmpty(explanation))
{
GameEntry.Event.Fire(this, CombinePartMessageEventArgs.Create(explanation));
}
}
/// <summary>
/// 兜底力学说明文案。
/// </summary>
private static string GetDefaultExplanation(CombinePartType partType)
{
switch (partType)
{
case CombinePartType.Dou:
return "Dou transfers upper load and works as the base node.";
case CombinePartType.Sheng:
return "Sheng raises layer height to form the bracket hierarchy.";
case CombinePartType.Gong:
return "Gong spreads force laterally through overhang.";
case CombinePartType.Qiao:
return "Qiao continues force transfer to the outer side.";
case CombinePartType.Ang:
return "Ang uses leverage to redirect eave load inward.";
default:
return string.Empty;
}
}
#endregion
}
}
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using UI;
using UnityEngine;
using UnityEngine.Serialization;
namespace CustomComponent
{
@@ -8,7 +9,7 @@ namespace CustomComponent
public sealed class StoryCombineConfig
{
public bool AutoStart = true;
public List<CombineSlotContext> Slots = new List<CombineSlotContext>();
public List<CombinePartContext> Parts = new List<CombinePartContext>();
public GameObject StructurePrefab;
[FormerlySerializedAs("PartPrefabs")] public List<Sprite> PartSprites = new List<Sprite>();
}
}
@@ -5,8 +5,6 @@ namespace CustomComponent
[CreateAssetMenu(menuName = "Story/Directive/Start Combine", fileName = "Directive_StartCombine")]
public sealed class StoryStartCombineDirectiveAsset : StoryDirectiveAsset
{
[SerializeField] private bool _useDefaultConfig = true;
[SerializeField] private StoryCombineConfig _combineConfig = new StoryCombineConfig();
public override string ActionName => "StartCombine";
@@ -18,8 +16,7 @@ namespace CustomComponent
return;
}
StoryCombineConfig config = _useDefaultConfig ? null : _combineConfig;
director.ExecuteStartCombine(config);
director.ExecuteStartCombine(_combineConfig);
}
}
}
@@ -1,6 +1,5 @@
using System.Collections;
using System.Collections.Generic;
using Definition.Enum;
using Event;
using UI;
using UnityEngine;
@@ -293,149 +292,33 @@ namespace CustomComponent
private static CombineFormContext BuildCombineFormContext(StoryCombineConfig config)
{
if (config != null && config.Slots != null && config.Slots.Count > 0)
if (config == null)
{
return new CombineFormContext
{
Slots = CloneSlots(config.Slots),
Parts = CloneParts(config.Parts),
AutoStart = config.AutoStart
};
return null;
}
return BuildDefaultCombineContext();
}
private static List<CombineSlotContext> CloneSlots(List<CombineSlotContext> source)
{
var result = new List<CombineSlotContext>();
if (source == null)
{
return result;
}
for (int i = 0; i < source.Count; i++)
{
CombineSlotContext slot = source[i];
if (slot == null)
{
continue;
}
result.Add(new CombineSlotContext
{
RequiredPartType = slot.RequiredPartType,
BuildOrder = slot.BuildOrder,
RequireStrictOrder = slot.RequireStrictOrder,
AnchoredPosition = slot.AnchoredPosition,
SizeDelta = slot.SizeDelta,
MechanicsExplanation = slot.MechanicsExplanation,
MismatchHint = slot.MismatchHint
});
}
return result;
}
private static List<CombinePartContext> CloneParts(List<CombinePartContext> source)
{
var result = new List<CombinePartContext>();
if (source == null)
{
return result;
}
for (int i = 0; i < source.Count; i++)
{
CombinePartContext part = source[i];
if (part == null)
{
continue;
}
result.Add(new CombinePartContext
{
PartType = part.PartType,
PartDisplayName = part.PartDisplayName,
MechanicsExplanation = part.MechanicsExplanation,
LockAfterPlaced = part.LockAfterPlaced
});
}
return result;
}
private static CombineFormContext BuildDefaultCombineContext()
{
List<CombineSlotContext> slots = new List<CombineSlotContext>
{
new CombineSlotContext
{
RequiredPartType = CombinePartType.Dou,
BuildOrder = 0,
RequireStrictOrder = true,
AnchoredPosition = new Vector2(-320f, -160f),
SizeDelta = new Vector2(120f, 120f),
MechanicsExplanation = "Dou transfers upper load and works as the base node."
},
new CombineSlotContext
{
RequiredPartType = CombinePartType.Sheng,
BuildOrder = 1,
RequireStrictOrder = true,
AnchoredPosition = new Vector2(-320f, -20f),
SizeDelta = new Vector2(120f, 120f),
MechanicsExplanation = "Sheng raises layer height to form the bracket hierarchy."
},
new CombineSlotContext
{
RequiredPartType = CombinePartType.Gong,
BuildOrder = 2,
RequireStrictOrder = true,
AnchoredPosition = new Vector2(-320f, 120f),
SizeDelta = new Vector2(120f, 120f),
MechanicsExplanation = "Gong spreads force laterally through overhang."
},
new CombineSlotContext
{
RequiredPartType = CombinePartType.Qiao,
BuildOrder = 3,
RequireStrictOrder = true,
AnchoredPosition = new Vector2(-160f, 120f),
SizeDelta = new Vector2(120f, 120f),
MechanicsExplanation = "Qiao continues force transfer to the outer side."
},
new CombineSlotContext
{
RequiredPartType = CombinePartType.Ang,
BuildOrder = 4,
RequireStrictOrder = true,
AnchoredPosition = new Vector2(0f, 120f),
SizeDelta = new Vector2(120f, 120f),
MechanicsExplanation = "Ang uses leverage to redirect eave load inward."
}
};
List<CombinePartContext> parts = new List<CombinePartContext>
{
new CombinePartContext
{ PartType = CombinePartType.Dou, PartDisplayName = "Dou", LockAfterPlaced = true },
new CombinePartContext
{ PartType = CombinePartType.Sheng, PartDisplayName = "Sheng", LockAfterPlaced = true },
new CombinePartContext
{ PartType = CombinePartType.Gong, PartDisplayName = "Gong", LockAfterPlaced = true },
new CombinePartContext
{ PartType = CombinePartType.Qiao, PartDisplayName = "Qiao", LockAfterPlaced = true },
new CombinePartContext
{ PartType = CombinePartType.Ang, PartDisplayName = "Ang", LockAfterPlaced = true }
};
return new CombineFormContext
{
Slots = slots,
Parts = parts,
AutoStart = true
StructurePrefab = config.StructurePrefab,
PartSprites = ClonePartSprites(config.PartSprites),
AutoStart = config.AutoStart
};
}
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;
}
}
}