添加了对话UI,基本完成对话系统设计
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
//------------------------------------------------------------
|
||||
// Game Framework
|
||||
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
|
||||
// Homepage: https://gameframework.cn/
|
||||
// Feedback: mailto:ellan@gameframework.cn
|
||||
//------------------------------------------------------------
|
||||
|
||||
using GameFramework;
|
||||
using StarForce;
|
||||
using UI;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace CustomComponent
|
||||
{
|
||||
public class BuiltinDataComponent : GameFrameworkComponent
|
||||
{
|
||||
[FormerlySerializedAs("m_BuildInfoTextAsset")] [SerializeField] private TextAsset _buildInfoTextAsset = null;
|
||||
|
||||
[FormerlySerializedAs("m_DefaultDictionaryTextAsset")] [SerializeField] private TextAsset _defaultDictionaryTextAsset = null;
|
||||
|
||||
[FormerlySerializedAs("m_UpdateResourceFormTemplate")] [SerializeField] private UpdateResourceForm _updateResourceFormTemplate = null;
|
||||
|
||||
private BuildInfo _buildInfo = null;
|
||||
|
||||
public BuildInfo BuildInfo => _buildInfo;
|
||||
|
||||
public UpdateResourceForm UpdateResourceFormTemplate => _updateResourceFormTemplate;
|
||||
|
||||
public void InitBuildInfo()
|
||||
{
|
||||
if (_buildInfoTextAsset == null || string.IsNullOrEmpty(_buildInfoTextAsset.text))
|
||||
{
|
||||
Log.Info("Build info can not be found or empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
_buildInfo = Utility.Json.ToObject<BuildInfo>(_buildInfoTextAsset.text);
|
||||
if (_buildInfo == null)
|
||||
{
|
||||
Log.Warning("Parse build info failure.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void InitDefaultDictionary()
|
||||
{
|
||||
if (_defaultDictionaryTextAsset == null || string.IsNullOrEmpty(_defaultDictionaryTextAsset.text))
|
||||
{
|
||||
Log.Info("Default dictionary can not be found or empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!GameEntry.Localization.ParseData(_defaultDictionaryTextAsset.text))
|
||||
{
|
||||
Log.Warning("Parse default dictionary failure.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cfb0b001fcb30fe45ac95ddb8c28ba57
|
||||
timeCreated: 1528026146
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,651 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Definition.Enum;
|
||||
using Event;
|
||||
using UI;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace CustomComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// 核心玩法A控制器,负责拼装规则校验、进度统计与完成判定。
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class CombineComponent : GameFrameworkComponent
|
||||
{
|
||||
#region Inspector Config
|
||||
|
||||
/// <summary>
|
||||
/// 是否自动收集子节点中的槽位和部件。
|
||||
/// </summary>
|
||||
[SerializeField] private bool _autoCollectChildren = true;
|
||||
|
||||
/// <summary>
|
||||
/// 组件启用时是否自动开始拼装。
|
||||
/// </summary>
|
||||
[SerializeField] private bool _autoStartOnEnable = false;
|
||||
|
||||
/// <summary>
|
||||
/// 是否对所有槽位启用全局严格顺序。
|
||||
/// </summary>
|
||||
[SerializeField] private bool _strictGlobalOrder = true;
|
||||
|
||||
/// <summary>
|
||||
/// 拖拽时的临时父节点。
|
||||
/// </summary>
|
||||
[SerializeField] private Transform _dragRoot = null;
|
||||
|
||||
/// <summary>
|
||||
/// 拼装节点根对象。
|
||||
/// </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.";
|
||||
|
||||
/// <summary>
|
||||
/// 放置顺序错误提示文本。
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
[TextArea(2, 4)]
|
||||
private string _wrongOrderHint = "Wrong order. Place lower or inner parts first.";
|
||||
|
||||
/// <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.";
|
||||
|
||||
/// <summary>
|
||||
/// 当前参与拼装的槽位列表。
|
||||
/// </summary>
|
||||
[SerializeField] private List<CombineSlot> _slots = new List<CombineSlot>();
|
||||
|
||||
/// <summary>
|
||||
/// 当前参与拼装的可拖拽部件列表。
|
||||
/// </summary>
|
||||
[SerializeField] private List<CombineDraggablePart> _parts = new List<CombineDraggablePart>();
|
||||
|
||||
/// <summary>
|
||||
/// 拼装完成事件。
|
||||
/// </summary>
|
||||
[SerializeField] private UnityEvent _onPuzzleCompleted = new UnityEvent();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Runtime State
|
||||
|
||||
private CombineFormContext _formContext;
|
||||
private CombineFormController _formController;
|
||||
|
||||
/// <summary>
|
||||
/// 按顺序排序后的槽位缓存。
|
||||
/// </summary>
|
||||
private readonly List<CombineSlot> _orderedSlots = new List<CombineSlot>();
|
||||
|
||||
/// <summary>
|
||||
/// 当前期望放置的槽位索引。
|
||||
/// </summary>
|
||||
private int _nextOrderSlotIndex = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 当前已放置部件数量。
|
||||
/// </summary>
|
||||
private int _placedCount = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 拼装是否已开始。
|
||||
/// </summary>
|
||||
private bool _isStarted = false;
|
||||
|
||||
/// <summary>
|
||||
/// 拼装是否已完成。
|
||||
/// </summary>
|
||||
private bool _isCompleted = false;
|
||||
|
||||
/// <summary>
|
||||
/// 拼装是否处于暂停状态。
|
||||
/// </summary>
|
||||
private bool _isPaused = false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Query
|
||||
|
||||
/// <summary>
|
||||
/// 获取拼装是否完成。
|
||||
/// </summary>
|
||||
public bool IsCompleted => _isCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前步数。
|
||||
/// </summary>
|
||||
public int CurrentStep => _placedCount;
|
||||
|
||||
/// <summary>
|
||||
/// 获取总步数。
|
||||
/// </summary>
|
||||
public int TotalStep => _orderedSlots.Count;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity Lifecycle
|
||||
|
||||
/// <summary>
|
||||
/// 组件启用时可选自动开始拼装。
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
if (_autoStartOnEnable)
|
||||
{
|
||||
StartPuzzle();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Level Lifecycle
|
||||
|
||||
/// <summary>
|
||||
/// 使用关卡数据启动关卡,并打开玩法UI。
|
||||
/// </summary>
|
||||
public int? StartLevel(CombineFormContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
Log.Warning("CoreGameplayA start failed. context is null.");
|
||||
return null;
|
||||
}
|
||||
|
||||
ClearRuntimeContext();
|
||||
SetFormContext(context);
|
||||
EnsureFormController();
|
||||
|
||||
// 先关闭上一次UI,避免重复打开。
|
||||
_formController.CloseUI();
|
||||
|
||||
int? serialId = _formController.OpenUI(context);
|
||||
if (!serialId.HasValue)
|
||||
{
|
||||
Log.Warning("CoreGameplayA start failed. OpenUI returned null.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return serialId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止当前关卡并清理运行态。
|
||||
/// </summary>
|
||||
public void StopLevel()
|
||||
{
|
||||
_formController?.CloseUI();
|
||||
ClearRuntimeContext();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Puzzle Flow
|
||||
|
||||
/// <summary>
|
||||
/// 启动拼装流程。
|
||||
/// </summary>
|
||||
public void StartPuzzle()
|
||||
{
|
||||
if (_autoCollectChildren)
|
||||
{
|
||||
CollectChildren();
|
||||
}
|
||||
|
||||
BuildOrderedSlotList();
|
||||
PrepareSlots();
|
||||
PrepareParts();
|
||||
|
||||
_placedCount = 0;
|
||||
_nextOrderSlotIndex = 0;
|
||||
_isStarted = true;
|
||||
_isCompleted = false;
|
||||
_isPaused = false;
|
||||
|
||||
GameEntry.Event.Fire(this, CombineProgressEventArgs.Create(_placedCount, _orderedSlots.Count));
|
||||
|
||||
if (!string.IsNullOrEmpty(_startHint))
|
||||
{
|
||||
GameEntry.Event.Fire(this, CombineGuideMessageEventArgs.Create(_startHint));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 暂停拼装输入。
|
||||
/// </summary>
|
||||
public void PausePuzzle()
|
||||
{
|
||||
_isPaused = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 恢复拼装输入。
|
||||
/// </summary>
|
||||
public void ResumePuzzle()
|
||||
{
|
||||
_isPaused = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置并重新开始拼装。
|
||||
/// </summary>
|
||||
public void ResetPuzzle()
|
||||
{
|
||||
StartPuzzle();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Placement
|
||||
|
||||
/// <summary>
|
||||
/// 尝试将部件放置到目标槽位。
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 放置前通用状态校验。
|
||||
/// </summary>
|
||||
private bool CanPlacePart(CombineDraggablePart part, CombineSlot slot)
|
||||
{
|
||||
return _isStarted && !_isCompleted && !_isPaused && part != null && slot != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验槽位是否可放置。
|
||||
/// </summary>
|
||||
private bool ValidateSlotAvailability(CombineDraggablePart part, CombineSlot slot)
|
||||
{
|
||||
if (!slot.IsOccupied)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
RejectPlace(part, _slotOccupiedHint);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验部件类型是否匹配槽位。
|
||||
/// </summary>
|
||||
private bool ValidatePartType(CombineDraggablePart part, CombineSlot slot)
|
||||
{
|
||||
if (slot.RequiredPartType == part.PartType)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
string hint = string.IsNullOrEmpty(slot.MismatchHint) ? _wrongPartHint : slot.MismatchHint;
|
||||
RejectPlace(part, hint);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验放置顺序是否符合规则。
|
||||
/// </summary>
|
||||
private bool ValidateOrder(CombineDraggablePart part, CombineSlot slot)
|
||||
{
|
||||
if (!NeedValidateOrder(slot) || IsExpectedOrderSlot(slot))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
RejectPlace(part, _wrongOrderHint);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用成功放置结果,并推进进度。
|
||||
/// </summary>
|
||||
private void ApplyPlacement(CombineDraggablePart part, CombineSlot slot)
|
||||
{
|
||||
slot.SetOccupiedPart(part);
|
||||
part.PlaceToSlot(slot);
|
||||
|
||||
_placedCount++;
|
||||
AdvanceOrderCursor();
|
||||
PublishExplanation(part, slot);
|
||||
|
||||
GameEntry.Event.Fire(this, CombineProgressEventArgs.Create(_placedCount, _orderedSlots.Count));
|
||||
TryCompletePuzzle();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否完成全部步骤并触发完成事件。
|
||||
/// </summary>
|
||||
private void TryCompletePuzzle()
|
||||
{
|
||||
if (_placedCount < _orderedSlots.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isCompleted = true;
|
||||
|
||||
if (!string.IsNullOrEmpty(_completeHint))
|
||||
{
|
||||
GameEntry.Event.Fire(this, CombineGuideMessageEventArgs.Create(_completeHint));
|
||||
}
|
||||
|
||||
_onPuzzleCompleted.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理放置失败:部件回弹并提示。
|
||||
/// </summary>
|
||||
private void RejectPlace(CombineDraggablePart part, string hint)
|
||||
{
|
||||
part.ReturnToSpawnAnimated();
|
||||
|
||||
if (!string.IsNullOrEmpty(hint))
|
||||
{
|
||||
GameEntry.Event.Fire(this, CombineGuideMessageEventArgs.Create(hint));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Runtime Context
|
||||
|
||||
/// <summary>
|
||||
/// 绑定运行时上下文根节点。
|
||||
/// </summary>
|
||||
/// <param name="puzzleRoot">拼装根节点。</param>
|
||||
/// <param name="dragRoot">拖拽根节点。</param>
|
||||
public void BindRuntimeContext(Transform puzzleRoot, Transform dragRoot = null)
|
||||
{
|
||||
_puzzleRoot = puzzleRoot;
|
||||
if (dragRoot != null)
|
||||
{
|
||||
_dragRoot = dragRoot;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置当前UI上下文数据。
|
||||
/// </summary>
|
||||
public void SetFormContext(CombineFormContext context)
|
||||
{
|
||||
_formContext = context;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前UI上下文数据。
|
||||
/// </summary>
|
||||
public CombineFormContext GetFormContext()
|
||||
{
|
||||
return _formContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理运行时上下文与状态。
|
||||
/// </summary>
|
||||
public void ClearRuntimeContext()
|
||||
{
|
||||
_formContext = null;
|
||||
_puzzleRoot = null;
|
||||
_dragRoot = null;
|
||||
_slots.Clear();
|
||||
_parts.Clear();
|
||||
_orderedSlots.Clear();
|
||||
_nextOrderSlotIndex = 0;
|
||||
_placedCount = 0;
|
||||
_isStarted = false;
|
||||
_isCompleted = false;
|
||||
_isPaused = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前拖拽根节点。
|
||||
/// </summary>
|
||||
public Transform GetDragRoot()
|
||||
{
|
||||
return _dragRoot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确保表单控制器已创建。
|
||||
/// </summary>
|
||||
private void EnsureFormController()
|
||||
{
|
||||
if (_formController == null)
|
||||
{
|
||||
_formController = new CombineFormController(this);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Collection And Preparation
|
||||
|
||||
/// <summary>
|
||||
/// 收集拼装所需的槽位与部件。
|
||||
/// </summary>
|
||||
private void CollectChildren()
|
||||
{
|
||||
_slots.Clear();
|
||||
_parts.Clear();
|
||||
|
||||
Transform collectRoot = _puzzleRoot != null ? _puzzleRoot : transform;
|
||||
_slots = collectRoot.GetComponentsInChildren<CombineSlot>(true).ToList();
|
||||
|
||||
CollectUniqueParts(collectRoot);
|
||||
if (_dragRoot != null && !ReferenceEquals(_dragRoot, collectRoot))
|
||||
{
|
||||
CollectUniqueParts(_dragRoot);
|
||||
}
|
||||
|
||||
if (_slots.Count == 0 || _parts.Count == 0)
|
||||
{
|
||||
Log.Warning("CoreGameplayA collect failed. slots={0}, parts={1}.", _slots.Count.ToString(),
|
||||
_parts.Count.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从指定根节点收集不重复的可拖拽部件。
|
||||
/// </summary>
|
||||
private void CollectUniqueParts(Transform root)
|
||||
{
|
||||
if (root == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CombineDraggablePart[] parts = root.GetComponentsInChildren<CombineDraggablePart>(true);
|
||||
for (int i = 0; i < parts.Length; i++)
|
||||
{
|
||||
CombineDraggablePart part = parts[i];
|
||||
if (part == null || _parts.Contains(part))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_parts.Add(part);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建按 BuildOrder 排序后的槽位列表。
|
||||
/// </summary>
|
||||
private void BuildOrderedSlotList()
|
||||
{
|
||||
_orderedSlots.Clear();
|
||||
|
||||
for (int i = 0; i < _slots.Count; i++)
|
||||
{
|
||||
CombineSlot slot = _slots[i];
|
||||
if (slot != null)
|
||||
{
|
||||
_orderedSlots.Add(slot);
|
||||
}
|
||||
}
|
||||
|
||||
_orderedSlots.Sort((a, b) => a.BuildOrder.CompareTo(b.BuildOrder));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化槽位控制器绑定与占用状态。
|
||||
/// </summary>
|
||||
private void PrepareSlots()
|
||||
{
|
||||
for (int i = 0; i < _orderedSlots.Count; i++)
|
||||
{
|
||||
CombineSlot slot = _orderedSlots[i];
|
||||
slot.BindController(this);
|
||||
slot.ResetSlot();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化部件控制器绑定与出生点状态。
|
||||
/// </summary>
|
||||
private void PrepareParts()
|
||||
{
|
||||
for (int i = 0; i < _parts.Count; i++)
|
||||
{
|
||||
CombineDraggablePart part = _parts[i];
|
||||
if (part == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
part.BindController(this);
|
||||
part.CacheSpawnState();
|
||||
part.ResetToSpawn();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Rule Helpers
|
||||
|
||||
/// <summary>
|
||||
/// 是否需要校验严格顺序。
|
||||
/// </summary>
|
||||
private bool NeedValidateOrder(CombineSlot slot)
|
||||
{
|
||||
return _strictGlobalOrder || slot.RequireStrictOrder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前槽位是否是下一步期望槽位。
|
||||
/// </summary>
|
||||
private bool IsExpectedOrderSlot(CombineSlot slot)
|
||||
{
|
||||
if (_nextOrderSlotIndex < 0 || _nextOrderSlotIndex >= _orderedSlots.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return ReferenceEquals(_orderedSlots[_nextOrderSlotIndex], slot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 推进顺序游标到下一个未占用槽位。
|
||||
/// </summary>
|
||||
private void AdvanceOrderCursor()
|
||||
{
|
||||
while (_nextOrderSlotIndex < _orderedSlots.Count && _orderedSlots[_nextOrderSlotIndex].IsOccupied)
|
||||
{
|
||||
_nextOrderSlotIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cc0388c578bec9746813380eee9038d4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,407 @@
|
||||
using System.Collections.Generic;
|
||||
using DataTable;
|
||||
using Definition.Enum;
|
||||
using Event;
|
||||
using GameFramework.DataTable;
|
||||
using GameFramework.Event;
|
||||
using UI;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace CustomComponent
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public class DialogComponent : GameFrameworkComponent
|
||||
{
|
||||
#region Property
|
||||
|
||||
private const int DialogChapterDivisor = 1000;
|
||||
private const int LineChapterDivisor = 100000000;
|
||||
private const int LineDialogDivisor = 100000;
|
||||
|
||||
private readonly Dictionary<int, DRDialog> _dialogMap = new();
|
||||
|
||||
private readonly Dictionary<int, List<DRDialogLine>> _dialogLinesMap = new();
|
||||
|
||||
private readonly Dictionary<int, int> _dialogFirstLineIdMap = new();
|
||||
|
||||
private IDataTable<DRDialog> _dtDialog;
|
||||
private IDataTable<DRDialogLine> _dtDialogLine;
|
||||
private DialogFormController _formController;
|
||||
private DialogFormContext _formContext;
|
||||
|
||||
private int _currentChapterId;
|
||||
private int _currentLineIndex = -1;
|
||||
private bool _isInitialized;
|
||||
private bool _isPlaying;
|
||||
|
||||
public bool IsInitialized => _isInitialized;
|
||||
|
||||
public bool IsPlaying => _isPlaying;
|
||||
|
||||
public int CurrentChapterId => _currentChapterId;
|
||||
|
||||
public int CurrentDialogId => _formContext != null ? _formContext.DialogId : 0;
|
||||
|
||||
public int CurrentLineId => _formContext != null ? _formContext.CurrentLineId : 0;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
private void Start()
|
||||
{
|
||||
GameEntry.Event.Subscribe(DialogNextLineRequestEventArgs.EventId, OnDialogNextLineRequest);
|
||||
GameEntry.Event.Subscribe(DialogSkipRequestEventArgs.EventId, OnDialogSkipRequest);
|
||||
GameEntry.Event.Subscribe(DialogStopRequestEventArgs.EventId, OnDialogStopRequest);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
GameEntry.Event.Unsubscribe(DialogNextLineRequestEventArgs.EventId, OnDialogNextLineRequest);
|
||||
GameEntry.Event.Unsubscribe(DialogSkipRequestEventArgs.EventId, OnDialogSkipRequest);
|
||||
GameEntry.Event.Unsubscribe(DialogStopRequestEventArgs.EventId, OnDialogStopRequest);
|
||||
}
|
||||
|
||||
public bool Init(int chapterId)
|
||||
{
|
||||
if (chapterId <= 0)
|
||||
{
|
||||
Log.Warning("Dialog init failed. chapterId must be positive.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!EnsureDataTables())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
StopDialog();
|
||||
_dialogMap.Clear();
|
||||
_dialogLinesMap.Clear();
|
||||
_dialogFirstLineIdMap.Clear();
|
||||
|
||||
DRDialog[] dialogRows = _dtDialog.GetDataRows((a, b) => a.Id.CompareTo(b.Id));
|
||||
for (int i = 0; i < dialogRows.Length; i++)
|
||||
{
|
||||
DRDialog dialogRow = dialogRows[i];
|
||||
if (dialogRow == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ParseChapterIdFromDialogId(dialogRow.Id) != chapterId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_dialogMap[dialogRow.Id] = dialogRow;
|
||||
}
|
||||
|
||||
if (_dialogMap.Count == 0)
|
||||
{
|
||||
Log.Warning("Dialog init failed. No dialog rows found for chapter '{0}'.", chapterId.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
DRDialogLine[] lineRows = _dtDialogLine.GetDataRows((a, b) => a.Id.CompareTo(b.Id));
|
||||
for (int i = 0; i < lineRows.Length; i++)
|
||||
{
|
||||
DRDialogLine lineRow = lineRows[i];
|
||||
if (lineRow == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ParseChapterIdFromLineId(lineRow.Id) != chapterId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int dialogId = ParseDialogIdFromLineId(lineRow.Id);
|
||||
if (!_dialogMap.ContainsKey(dialogId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_dialogLinesMap.TryGetValue(dialogId, out List<DRDialogLine> dialogLines))
|
||||
{
|
||||
dialogLines = new List<DRDialogLine>();
|
||||
_dialogLinesMap.Add(dialogId, dialogLines);
|
||||
}
|
||||
|
||||
dialogLines.Add(lineRow);
|
||||
}
|
||||
|
||||
List<int> invalidDialogIds = new List<int>();
|
||||
foreach (KeyValuePair<int, DRDialog> dialogPair in _dialogMap)
|
||||
{
|
||||
if (!_dialogLinesMap.TryGetValue(dialogPair.Key, out List<DRDialogLine> dialogLines) ||
|
||||
dialogLines.Count == 0)
|
||||
{
|
||||
Log.Warning("Dialog init warning. Dialog '{0}' has no lines and will be ignored.",
|
||||
dialogPair.Key.ToString());
|
||||
invalidDialogIds.Add(dialogPair.Key);
|
||||
continue;
|
||||
}
|
||||
|
||||
dialogLines.Sort((a, b) => a.Id.CompareTo(b.Id));
|
||||
_dialogFirstLineIdMap[dialogPair.Key] = dialogLines[0].Id;
|
||||
}
|
||||
|
||||
for (int i = 0; i < invalidDialogIds.Count; i++)
|
||||
{
|
||||
_dialogMap.Remove(invalidDialogIds[i]);
|
||||
}
|
||||
|
||||
if (_dialogMap.Count == 0)
|
||||
{
|
||||
Log.Warning("Dialog init failed. No valid dialog remains for chapter '{0}'.", chapterId.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureFormController();
|
||||
_formContext = null;
|
||||
_currentChapterId = chapterId;
|
||||
_currentLineIndex = -1;
|
||||
_isInitialized = true;
|
||||
_isPlaying = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool StartDialog(int dialogId)
|
||||
{
|
||||
if (!_isInitialized)
|
||||
{
|
||||
Log.Warning("Start dialog failed. Dialog component is not initialized.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ParseChapterIdFromDialogId(dialogId) != _currentChapterId)
|
||||
{
|
||||
Log.Warning("Start dialog failed. Dialog '{0}' does not belong to chapter '{1}'.", dialogId.ToString(),
|
||||
_currentChapterId.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_dialogMap.TryGetValue(dialogId, out DRDialog dialogRow))
|
||||
{
|
||||
Log.Warning("Start dialog failed. Dialog '{0}' was not found in current chapter cache.",
|
||||
dialogId.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_dialogLinesMap.TryGetValue(dialogId, out List<DRDialogLine> dialogLines) || dialogLines.Count == 0)
|
||||
{
|
||||
Log.Warning("Start dialog failed. Dialog '{0}' has no playable lines.", dialogId.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_isPlaying)
|
||||
{
|
||||
StopDialog();
|
||||
}
|
||||
|
||||
EnsureFormController();
|
||||
if (_formContext == null)
|
||||
{
|
||||
_formContext = new DialogFormContext();
|
||||
}
|
||||
|
||||
_formContext.ChapterId = _currentChapterId;
|
||||
_formContext.DialogId = dialogRow.Id;
|
||||
_formContext.DialogTitle = dialogRow.Title;
|
||||
_formContext.DialogUIMode = dialogRow.UIMode;
|
||||
|
||||
_currentLineIndex = 0;
|
||||
ApplyLineToContext(dialogLines[_currentLineIndex], _currentLineIndex, dialogLines.Count);
|
||||
_isPlaying = true;
|
||||
|
||||
_formController.OpenUI(_formContext);
|
||||
_formController.OnDialogStarted(_formContext);
|
||||
_formController.OnDialogLineChanged(_formContext);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool NextLine()
|
||||
{
|
||||
if (!_isPlaying)
|
||||
{
|
||||
Log.Warning("Next line failed. No dialog is playing.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryGetCurrentDialogLines(out List<DRDialogLine> dialogLines))
|
||||
{
|
||||
StopDialog();
|
||||
return false;
|
||||
}
|
||||
|
||||
int nextLineIndex = _currentLineIndex + 1;
|
||||
if (nextLineIndex >= dialogLines.Count)
|
||||
{
|
||||
EndDialogInternal();
|
||||
return true;
|
||||
}
|
||||
|
||||
_currentLineIndex = nextLineIndex;
|
||||
ApplyLineToContext(dialogLines[_currentLineIndex], _currentLineIndex, dialogLines.Count);
|
||||
_formController.OnDialogLineChanged(_formContext);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SkipDialog()
|
||||
{
|
||||
if (!_isPlaying)
|
||||
{
|
||||
Log.Warning("Skip dialog failed. No dialog is playing.");
|
||||
return false;
|
||||
}
|
||||
|
||||
EndDialogInternal();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void StopDialog()
|
||||
{
|
||||
if (!_isPlaying)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EndDialogInternal();
|
||||
}
|
||||
|
||||
public void ClearRuntimeContext()
|
||||
{
|
||||
StopDialog();
|
||||
|
||||
_dialogMap.Clear();
|
||||
_dialogLinesMap.Clear();
|
||||
_dialogFirstLineIdMap.Clear();
|
||||
|
||||
_formContext = null;
|
||||
_currentChapterId = 0;
|
||||
_currentLineIndex = -1;
|
||||
_isInitialized = false;
|
||||
_isPlaying = false;
|
||||
}
|
||||
|
||||
private bool EnsureDataTables()
|
||||
{
|
||||
_dtDialog = GameEntry.DataTable.GetDataTable<DRDialog>();
|
||||
if (_dtDialog == null)
|
||||
{
|
||||
Log.Warning("Dialog init failed. Data table DRDialog is missing.");
|
||||
return false;
|
||||
}
|
||||
|
||||
_dtDialogLine = GameEntry.DataTable.GetDataTable<DRDialogLine>();
|
||||
if (_dtDialogLine == null)
|
||||
{
|
||||
Log.Warning("Dialog init failed. Data table DRDialogLine is missing.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void EnsureFormController()
|
||||
{
|
||||
if (_formController == null)
|
||||
{
|
||||
_formController = new DialogFormController();
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetCurrentDialogLines(out List<DRDialogLine> dialogLines)
|
||||
{
|
||||
dialogLines = null;
|
||||
if (_formContext == null)
|
||||
{
|
||||
Log.Warning("Dialog state invalid. Form context is null.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_dialogLinesMap.TryGetValue(_formContext.DialogId, out dialogLines))
|
||||
{
|
||||
Log.Warning("Dialog state invalid. Dialog lines are missing for dialog '{0}'.",
|
||||
_formContext.DialogId.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void EndDialogInternal()
|
||||
{
|
||||
_isPlaying = false;
|
||||
_currentLineIndex = -1;
|
||||
|
||||
_formController.OnDialogEnded(_formContext);
|
||||
_formController.CloseUI();
|
||||
}
|
||||
|
||||
private void ApplyLineToContext(DRDialogLine lineRow, int lineIndex, int totalLines)
|
||||
{
|
||||
_formContext.CurrentLineId = lineRow.Id;
|
||||
_formContext.SpeakerId = lineRow.SpeakerId;
|
||||
_formContext.SpeakerName = lineRow.SpeakerName;
|
||||
_formContext.Expression = lineRow.Expression;
|
||||
_formContext.Direction = lineRow.Direction;
|
||||
_formContext.Text = lineRow.Text;
|
||||
_formContext.Emphasis = lineRow.Emphasis;
|
||||
_formContext.LineIndex = lineIndex;
|
||||
_formContext.TotalLines = totalLines;
|
||||
_formContext.IsLastLine = lineIndex >= totalLines - 1;
|
||||
}
|
||||
|
||||
private static int ParseChapterIdFromDialogId(int dialogId)
|
||||
{
|
||||
return dialogId / DialogChapterDivisor;
|
||||
}
|
||||
|
||||
private static int ParseChapterIdFromLineId(int lineId)
|
||||
{
|
||||
return lineId / LineChapterDivisor;
|
||||
}
|
||||
|
||||
private static int ParseDialogIdFromLineId(int lineId)
|
||||
{
|
||||
return lineId / LineDialogDivisor;
|
||||
}
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
private void OnDialogNextLineRequest(object sender, GameEventArgs e)
|
||||
{
|
||||
if (!(e is DialogNextLineRequestEventArgs))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NextLine();
|
||||
}
|
||||
|
||||
private void OnDialogSkipRequest(object sender, GameEventArgs e)
|
||||
{
|
||||
if (!(e is DialogSkipRequestEventArgs))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SkipDialog();
|
||||
}
|
||||
|
||||
private void OnDialogStopRequest(object sender, GameEventArgs e)
|
||||
{
|
||||
if (!(e is DialogStopRequestEventArgs))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StopDialog();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d6174838c30e460429e5628757bbf015
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CustomUtility;
|
||||
using Definition;
|
||||
using GameFramework.Resource;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace CustomComponent
|
||||
{
|
||||
public class SpriteCacheComponent : GameFrameworkComponent
|
||||
{
|
||||
[SerializeField] private float _pixelsPerUnit = 100f;
|
||||
[SerializeField] private Vector2 _defaultPivot = new(0.5f, 0.5f);
|
||||
|
||||
private Dictionary<string, Sprite> _spriteCache;
|
||||
private ResourceComponent _resource;
|
||||
|
||||
void Start()
|
||||
{
|
||||
_spriteCache = new Dictionary<string, Sprite>();
|
||||
_resource = GameEntry.Resource;
|
||||
}
|
||||
|
||||
public void GetSprite(string assetName, Action<Sprite> callback)
|
||||
{
|
||||
if (_spriteCache.TryGetValue(assetName, out var sprite))
|
||||
{
|
||||
callback?.Invoke(sprite);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
_resource.LoadAsset
|
||||
(
|
||||
AssetUtility.GetUIDialogAsset(assetName),
|
||||
Constant.AssetPriority.UIFormAsset,
|
||||
new LoadAssetCallbacks(
|
||||
(resourcePath, asset, duration, userData) =>
|
||||
{
|
||||
Log.Debug(resourcePath);
|
||||
Texture2D texture = asset as Texture2D;
|
||||
if (texture != null)
|
||||
{
|
||||
Sprite newSprite = Sprite.Create(
|
||||
texture,
|
||||
new Rect(0, 0, texture.width, texture.height),
|
||||
_defaultPivot,
|
||||
_pixelsPerUnit);
|
||||
_spriteCache.Add(assetName, newSprite);
|
||||
callback?.Invoke(newSprite);
|
||||
}
|
||||
},
|
||||
(resourcePath, status, errorMessage, userData) =>
|
||||
{
|
||||
Log.Error("Can not load icon '{0}' from '{1}' with error message '{2}'.",
|
||||
assetName,
|
||||
resourcePath,
|
||||
errorMessage);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
_spriteCache.Clear();
|
||||
_resource = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 42b4751aefa33c545a86f146a48536ee
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user