文本系统重做完成

This commit is contained in:
2025-11-12 20:00:18 +08:00
parent 845ac9cc27
commit ef260e5d43
18 changed files with 311 additions and 293 deletions
@@ -13,8 +13,6 @@ namespace AibisDream
[SerializeField] private LineAdvanceInput lineAdvanceInput;
public AdvanceMode advanceMode;
private void Start()
{
_dialogueRunner = GetComponentInChildren<DialogueRunner>();
@@ -33,11 +31,17 @@ namespace AibisDream
private void OnEnable()
{
EnumEventSystem.Global.Register(GameLoopEnum.GameStart, ResetAdvanceMode);
EnumEventSystem.Global.Register(DialogEventEnum.OptionShow, OnOptionShow);
EnumEventSystem.Global.Register<DialogEventEnum, LineInfo>(DialogEventEnum.OptionSelected, OnOptionSelected);
}
private void OnDisable()
{
EnumEventSystem.Global.UnRegister(GameLoopEnum.GameStart, ResetAdvanceMode);
EnumEventSystem.Global.UnRegister(DialogEventEnum.OptionShow, OnOptionShow);
EnumEventSystem.Global.UnRegister<DialogEventEnum, LineInfo>(DialogEventEnum.OptionSelected, OnOptionSelected);
}
private void ResetAdvanceMode()
@@ -48,6 +52,9 @@ namespace AibisDream
isQuick = false
};
isInOption = false;
lineSyncToken = null;
Time.timeScale = 1f;
lineAdvanceInput.ResetAdvanceMode();
}
@@ -113,6 +120,8 @@ namespace AibisDream
#region
public AdvanceMode advanceMode;
public void SwitchAutoMode()
{
advanceMode.isAuto = !advanceMode.isAuto;
@@ -128,8 +137,36 @@ namespace AibisDream
}
#endregion
}
public LineSyncToken lineSyncToken;
public bool isInOption;
public LineSyncState GetDialogState()
{
if (lineSyncToken != null && lineSyncToken.state != LineSyncState.Advanced)
{
return lineSyncToken.state;
}
else if (isInOption)
{
return LineSyncState.InOption;
}
else
{
return LineSyncState.Default;
}
}
private void OnOptionShow()
{
isInOption = true;
}
private void OnOptionSelected(LineInfo lineInfo)
{
isInOption = false;
}
}
public struct AdvanceMode
{
@@ -140,14 +177,14 @@ namespace AibisDream
{
get
{
if (isAuto)
{
return AdvanceModeEnum.Auto;
}
else if (isQuick)
if (isQuick)
{
return AdvanceModeEnum.Quick;
}
else if (isAuto)
{
return AdvanceModeEnum.Auto;
}
else
{
return AdvanceModeEnum.Default;
+3 -2
View File
@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AibisDream.Framework;
using AibisDream.Utility;
using UnityEngine;
using Yarn.Unity;
@@ -23,6 +21,7 @@ namespace AibisDream
{
// 自动推进视图
lineSyncEvent = LineSyncToken.CreateAuto(dialogueLine, onDialogueLineFinished);
DialogController.Instance.lineSyncToken = lineSyncEvent;
if (_screenViewDict.TryGetValue(DialogViewType.Screen, out var view))
{
view.RunLine(lineSyncEvent);
@@ -35,6 +34,7 @@ namespace AibisDream
else if (dialogueLine.GetCharacterVo().dialogViewType == DialogViewType.Task)
{
lineSyncEvent = LineSyncToken.CreateImmediate(dialogueLine, onDialogueLineFinished);
DialogController.Instance.lineSyncToken = lineSyncEvent;
if (_screenViewDict.TryGetValue(DialogViewType.Task, out var view))
{
view.RunLine(lineSyncEvent);
@@ -48,6 +48,7 @@ namespace AibisDream
{
// 手动推进视图
lineSyncEvent = LineSyncToken.CreateDefault(dialogueLine, onDialogueLineFinished);
DialogController.Instance.lineSyncToken = lineSyncEvent;
bubbleGroup.RunLine(lineSyncEvent);
}
+29 -13
View File
@@ -41,7 +41,6 @@ namespace AibisDream
{
this.lineInfo = LineInfo.Generate(lineInfo);
_nextStep = nextStep;
state = LineSyncState.PlayText;
_textShown = textShown;
}
@@ -104,7 +103,7 @@ namespace AibisDream
EnumEventSystem.Global.Send(DialogEventEnum.LineShown);
await _textShown.OnTextShown(this);
}
public void TextStart()
{
if (state != LineSyncState.Default)
@@ -149,7 +148,7 @@ namespace AibisDream
var delayTime = lineText.RemoveRichTextTags().Length * ConstRef.CharacterDelayTime;
return CommonUtil.Limit(delayTime, ConstRef.LineMaxDelay, ConstRef.LineMinDelay);
}
public int CalcDelayTime()
{
return ConstRef.FixedDelay;
@@ -165,11 +164,18 @@ namespace AibisDream
{
public async Task OnTextShown(LineSyncToken token)
{
token.state = LineSyncState.AdvanceDelay;
await Task.Delay(token.lineInfo.CalcDelayTime(), token.AdvanceDelayToken.Token);
token.state = LineSyncState.WaitForAdvance;
await Task.Delay(token.lineInfo.CalcAutoDelayTime(), token.AdvanceDelayToken.Token);
token.state = LineSyncState.WaitForAutoAdvance;
try
{
token.state = LineSyncState.AdvanceDelay;
await Task.Delay(token.lineInfo.CalcDelayTime(), token.AdvanceDelayToken.Token);
token.state = LineSyncState.WaitForAdvance;
await Task.Delay(token.lineInfo.CalcAutoDelayTime(), token.AdvanceDelayToken.Token);
token.state = LineSyncState.WaitForAutoAdvance;
}
catch (TaskCanceledException)
{
// 跳过,无事发生
}
}
}
@@ -177,10 +183,17 @@ namespace AibisDream
{
public async Task OnTextShown(LineSyncToken token)
{
token.state = LineSyncState.AdvanceDelay;
await Task.Delay(token.lineInfo.CalcDelayTime(), token.AdvanceDelayToken.Token);
token.state = LineSyncState.WaitForAutoAdvance;
token.TryAdvance();
try
{
token.state = LineSyncState.AdvanceDelay;
await Task.Delay(token.lineInfo.CalcDelayTime(), token.AdvanceDelayToken.Token);
token.state = LineSyncState.WaitForAutoAdvance;
token.TryAdvance();
}
catch (TaskCanceledException)
{
// 跳过,无事发生
}
}
}
@@ -199,6 +212,9 @@ namespace AibisDream
AdvanceDelay,
WaitForAdvance,
WaitForAutoAdvance,
Advanced
Advanced,
// 仅限DialogController使用
InOption
}
}
@@ -11,7 +11,7 @@ using STOP_MODE = FMOD.Studio.STOP_MODE;
namespace AibisDream.Kit
{
public class AudioManager : Singleton<AudioManager>, IAudioManager
public class AudioManager : Singleton<AudioManager>
{
private const string AmbStateName = "scene";
@@ -46,7 +46,7 @@ namespace AibisDream.Kit
private void RegisterEvent()
{
// 语音相关
EnumEventSystem.Global.Register<DialogEventEnum, DialogText>(DialogEventEnum.LineStart, OnLineStart);
EnumEventSystem.Global.Register<DialogEventEnum, LineInfo>(DialogEventEnum.LineStart, OnLineStart);
EnumEventSystem.Global.Register(DialogEventEnum.LineShown, OnLineShown);
// 场景切换相关
EnumEventSystem.Global.Register<EventEnum, string>(EventEnum.SceneLoad, OnSceneLoad);
@@ -235,7 +235,7 @@ namespace AibisDream.Kit
#endregion
public void OnLineStart(DialogText dialogText)
public void OnLineStart(LineInfo dialogText)
{
if (_voiceInstance.IsPlaying())
{
@@ -243,12 +243,12 @@ namespace AibisDream.Kit
_voiceInstance.release();
}
if (string.IsNullOrEmpty(dialogText.actor.voicePath) || !FModEx.IsEventExist(dialogText.actor.VoicePath))
if (string.IsNullOrEmpty(dialogText.character.voicePath) || !FModEx.IsEventExist(dialogText.character.VoicePath))
{
return;
}
_voiceInstance = RuntimeManager.CreateInstance(dialogText.actor.VoicePath);
_voiceInstance = RuntimeManager.CreateInstance(dialogText.character.VoicePath);
_voiceInstance.start();
}
@@ -1,72 +1,123 @@
using AibisDream.Kit;
using System.Collections.Generic;
using AibisDream.Kit;
using UnityEngine;
namespace AibisDream.Framework
{
public class EventSystemEx : SingletonBase<EventSystemEx>
public class EventSystemEx : Singleton<EventSystemEx>
{
#region
public bool isLocked;
#endregion
public GameObject pointerOverObj;
public GameObject draggingObj;
public GameObject holdingObj;
private List<GameObject> pointerOverObjList;
private List<GameObject> draggingObjList;
private List<GameObject> holdingObjList;
#region
public void OnPointerEnter(IInteraction interaction)
{
pointerOverObj = interaction.GetGameObject();
EnumEventSystem.Global.Send(TriggerEnum.PointerEnter, interaction);
if (!pointerOverObjList.Contains(interaction.GetGameObject()))
{
pointerOverObjList.Add(interaction.GetGameObject());
}
}
public void OnPointerExit(IInteraction interaction)
{
if (pointerOverObj == interaction.GetGameObject())
{
EnumEventSystem.Global.Send(TriggerEnum.PointerExit, interaction);
pointerOverObj = null;
}
pointerOverObjList.Remove(interaction.GetGameObject());
}
public void OnPointerDown(IInteraction interaction)
{
EnumEventSystem.Global.Send(TriggerEnum.PointerDown, interaction);
holdingObj = interaction.GetGameObject();
if (!holdingObjList.Contains(interaction.GetGameObject()))
{
holdingObjList.Add(interaction.GetGameObject());
}
}
public void OnPointerUp(IInteraction interaction)
{
if (holdingObj == interaction.GetGameObject())
holdingObjList.Remove(interaction.GetGameObject());
}
public void OnDrag(IInteraction interaction)
{
if (!draggingObjList.Contains(interaction.GetGameObject()))
{
EnumEventSystem.Global.Send(TriggerEnum.PointerUp, interaction);
holdingObj = null;
draggingObjList.Add(interaction.GetGameObject());
}
}
public void OnEndDrag(IInteraction interaction)
{
draggingObjList.Remove(interaction.GetGameObject());
}
public void OnBeginDrag(IInteraction interaction)
{
if (!draggingObjList.Contains(interaction.GetGameObject()))
{
draggingObjList.Add(interaction.GetGameObject());
}
}
public TriggerEnum GetPointerState()
{
if (holdingObjList.Count > 0 || draggingObjList.Count > 0)
{
Debug.Log($"Dragging: {draggingObjList.Count}");
Debug.Log($"Holding: {holdingObjList.Count}");
return TriggerEnum.Dragging;
}
else if (pointerOverObjList.Count > 0)
{
Debug.Log($"PointerOver: {pointerOverObjList.Count}");
return TriggerEnum.PointerOver;
}
else
{
return TriggerEnum.None;
}
}
#endregion
#region
private EventSystemEx()
{
}
public override void OnSingletonInit()
{
pointerOverObjList = new List<GameObject>();
draggingObjList = new List<GameObject>();
holdingObjList = new List<GameObject>();
EnumEventSystem.Global.Register(GameLoopEnum.GameStart, ClearAll);
EnumEventSystem.Global.Register(GameLoopEnum.GameQuit, ClearAll);
}
private void ClearAll()
{
pointerOverObjList.Clear();
draggingObjList.Clear();
holdingObjList.Clear();
}
public override void OnSingletonDestroy()
{
EnumEventSystem.Global.UnRegister(GameLoopEnum.GameStart, ClearAll);
EnumEventSystem.Global.UnRegister(GameLoopEnum.GameQuit, ClearAll);
}
#endregion
}
public enum TriggerEnum
{
PointerEnter,
PointerExit,
PointerDown,
PointerUp
None,
Dragging,
PointerOver
}
}
@@ -102,6 +102,23 @@ namespace AibisDream.Framework
base.OnPointerUp(eventData);
}
}
public override void OnDrag(PointerEventData eventData)
{
EventSystemEx.Instance.OnDrag(_interaction);
base.OnDrag(eventData);
}
public override void OnEndDrag(PointerEventData eventData)
{
EventSystemEx.Instance.OnEndDrag(_interaction);
base.OnEndDrag(eventData);
}
public override void OnBeginDrag(PointerEventData eventData)
{
base.OnBeginDrag(eventData);
}
}
public interface IInteraction
-3
View File
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 9a9cfd121f9a45c0a72e5f056e7ce308
timeCreated: 1732358305
@@ -1,16 +0,0 @@
using UnityEngine;
namespace AibisDream.Framework
{
public interface IMoveable
{
bool IsBlock { get; }
void StartMove();
void Move(Vector2 mousePos);
void StopMove();
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: c5c7b28802d266d499177b7fa8f9f2cd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,74 +0,0 @@
using UnityEngine;
namespace AibisDream.Framework
{
public class MoveableSystem : IMonoKit
{
private bool _isDragging;
private IMoveable _dragObj;
private Vector3 _offset;
private readonly Camera _camera = Camera.main;
public void OnUpdate()
{
// 鼠标按下
if (Input.GetMouseButtonDown(0))
{
OnMouseClickDown();
}
// 鼠标按住
if (Input.GetMouseButton(0) && _isDragging)
{
OnMouseHolding();
}
// 鼠标松开
if (Input.GetMouseButtonUp(0))
{
OnMouseClickUp();
}
}
private void OnMouseClickDown()
{
// 按下时发出检查射线
Ray ray = _camera.ScreenPointToRay(Input.mousePosition);
RaycastHit2D hit = Physics2D.Raycast(ray.origin, ray.direction);
// 检测命中的物体
if (hit.collider && hit.collider.CompareTag("Moveable"))
{
_dragObj = hit.collider.gameObject.GetComponent<IMoveable>();
if (_dragObj == null) return;
if (_dragObj.IsBlock) return;
_dragObj.StartMove();
_offset = hit.collider.transform.position - GetMouseAsWorldPoint();
_isDragging = true;
}
}
private void OnMouseHolding()
{
_dragObj.Move(GetMouseAsWorldPoint() + _offset);
}
private void OnMouseClickUp()
{
if (!_isDragging) return;
_dragObj?.StopMove();
_dragObj = null;
_isDragging = false;
}
private Vector3 GetMouseAsWorldPoint()
{
Vector3 mousePoint = Input.mousePosition;
mousePoint.z = _camera.nearClipPlane;
return _camera.ScreenToWorldPoint(mousePoint);
}
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 5d755460ba0142f39c5677d8f948c777
timeCreated: 1732358554
@@ -23,7 +23,6 @@ namespace AibisDream.Framework
{
_iocContainer ??= new IOCContainer();
// 在这里添加相应的工具
Register(new MoveableSystem());
Register(new TransTest());
}
+77 -100
View File
@@ -1,9 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using AibisDream.Framework;
using AibisDream.Framework;
using AibisDream.Kit;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace AibisDream
@@ -16,21 +13,6 @@ namespace AibisDream
public Sprite nextTalk, talking, option, interactive, holding, disable;
private Image _cursor;
private GraphicRaycaster _raycaster;
private CursorState _stateCache;
#region
private Coroutine _switchCoroutine;
private CursorState _curState = CursorState.Default;
private bool _isTalking;
private bool _isInGame;
#endregion
// 卸载时注销事件
private readonly List<IUnRegister> _unRegisters = new();
private void Start()
{
@@ -45,87 +27,9 @@ namespace AibisDream
_cursor.sprite = defaultCursor;
}
private void OnEnable()
{
RegisterEvent();
}
private void OnDisable()
{
// 注销所有事件
foreach (var eventUnRegister in _unRegisters)
{
eventUnRegister.UnRegister();
}
_unRegisters.Clear();
}
private void InitRefs()
{
_cursor = transform.Find("Cursor").GetComponent<Image>();
_raycaster = UIManager.Instance.Canvas.GetComponent<GraphicRaycaster>();
}
private void RegisterEvent()
{
// 此处监听所有可能对鼠标状态产生影响的事件
RegisterGameEvent();
}
private void RegisterGameEvent()
{
_unRegisters.Add(EnumEventSystem.Global.Register(GameLoopEnum.GameStart, OnGameStart));
_unRegisters.Add(EnumEventSystem.Global.Register(GameLoopEnum.GameQuit, OnGameQuit));
_unRegisters.Add(EnumEventSystem.Global.Register(GameLoopEnum.PauseGame, OnPauseGame));
_unRegisters.Add(EnumEventSystem.Global.Register(GameLoopEnum.UnPauseGame, OnUnPauseGame));
}
private void OnGameStart()
{
_isInGame = true;
}
private void OnUnPauseGame()
{
_curState = _stateCache;
_stateCache = CursorState.Default;
_isInGame = true;
}
private void OnPauseGame()
{
_stateCache = _curState;
_curState = CursorState.Default;
_isInGame = false;
}
private void OnGameQuit()
{
_curState = CursorState.Default;
_stateCache = CursorState.Default;
_isInGame = false;
}
private bool IsOverSelected()
{
if (!EventSystem.current.IsPointerOverGameObject())
{
return false;
}
var eventData = new PointerEventData(EventSystem.current)
{
position = Input.mousePosition
};
var results = new List<RaycastResult>();
_raycaster.Raycast(eventData, results);
return results.Count > 0 && results.Any(result => result.gameObject.CompareTag("Selectable"));
}
private void LateUpdate()
@@ -134,15 +38,88 @@ namespace AibisDream
// RectTransformUtility.ScreenPointToLocalPointInRectangle(_cursorCanvas, Input.mousePosition,
// CameraKit.Instance.uiCamera, out var worldPos);
// 判断当前要用什么样的指针
_cursor.rectTransform.position = Input.mousePosition;
// 在输入状态,要单独处理
var cursorState = CheckCursorState();
_cursor.sprite = GetCursorSprite(cursorState);
}
private CursorStateEnum CheckCursorState()
{
var gameState = GameManager.Instance.state;
if (!gameState.isInGame || gameState.isInPause)
{
// 非游戏中显示默认指针
return CursorStateEnum.Default;
}
else if (UIManager.Instance.IsOverRegion("MainUI"))
{
// 游戏中鼠标移至右上角Main Panel时显示默认指针
return CursorStateEnum.Default;
}
else if (gameState.isInDialog)
{
// 处理对话中的指针
var dialogState = DialogController.Instance.GetDialogState();
return HandleDialogState(dialogState);
}
else
{
// 非对话处理指针交互
var triggerEnum = EventSystemEx.Instance.GetPointerState();
return HandleDraggingState(triggerEnum);
}
}
private CursorStateEnum HandleDialogState(LineSyncState dialogState)
{
return dialogState switch
{
LineSyncState.Default => CursorStateEnum.Default,
LineSyncState.PlayText => CursorStateEnum.PlayText,
LineSyncState.AdvanceDelay => CursorStateEnum.PlayText,
LineSyncState.WaitForAdvance => CursorStateEnum.WaitForAdvance,
LineSyncState.WaitForAutoAdvance => CursorStateEnum.WaitForAdvance,
LineSyncState.Advanced => CursorStateEnum.Default,
LineSyncState.InOption => CursorStateEnum.InOption,
_ => CursorStateEnum.Default,
};
}
private CursorStateEnum HandleDraggingState(TriggerEnum triggerEnum)
{
return triggerEnum switch
{
TriggerEnum.Dragging => CursorStateEnum.Dragging,
TriggerEnum.PointerOver => CursorStateEnum.PointerOver,
_ => CursorStateEnum.Default,
};
}
private Sprite GetCursorSprite(CursorStateEnum cursorState)
{
return cursorState switch
{
CursorStateEnum.Default => defaultCursor,
CursorStateEnum.PlayText => talking,
CursorStateEnum.WaitForAdvance => nextTalk,
CursorStateEnum.InOption => option,
CursorStateEnum.Dragging => holding,
CursorStateEnum.PointerOver => interactive,
_ => defaultCursor,
};
}
}
public enum CursorState
public enum CursorStateEnum
{
Default,
Interactive
PlayText,
WaitForAdvance,
InOption,
Dragging,
PointerOver
}
}
@@ -16,19 +16,10 @@ namespace AibisDream
[SerializeField] private TMP_Text widthEstimation;
[SerializeField] private CenterBubble centerBubble;
private Dictionary<ActorRole, Bubble[]> _bubbleDict;
private Dictionary<ActorRole, Bubble[]> _bubbleDict = new();
private BubbleFactory _bubbleFactory;
private CancellationTokenSource _autoHideToken;
#region
private Action _nextStep;
private IBubble _curBubble;
private IUnRegister _unRegister;
#endregion
#region
public override void OnSingletonInit()
@@ -37,11 +28,6 @@ namespace AibisDream
_bubbleFactory = new BubbleFactory(transform);
}
public override void OnSingletonDestroy()
{
_unRegister.UnRegister();
}
public void LoadBubbles(BubbleSlotGroupData groupData)
{
// 回收旧泡泡
+37
View File
@@ -1,8 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AibisDream.Framework;
using AibisDream.UI;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace AibisDream
{
@@ -19,6 +22,8 @@ namespace AibisDream
public Camera UICamera { get; private set; }
public RectTransform DialogCanvas { get; private set; }
private GraphicRaycaster _raycaster;
#endregion
public override void OnSingletonInit()
@@ -60,6 +65,8 @@ namespace AibisDream
// 获取Canvas
Canvas = transform.Find("UI Canvas").GetComponent<RectTransform>();
DialogCanvas = transform.Find("Dialog Canvas").GetComponent<RectTransform>();
// 获取 GraphicRaycaster
_raycaster = Canvas.GetComponent<GraphicRaycaster>();
// 获取Panel
var panels = Canvas.GetComponentsInChildren<IUIPanel>(true);
_panelPool = new Dictionary<Type, IUIPanel>();
@@ -167,6 +174,36 @@ namespace AibisDream
return localPoint;
}
/// <summary>
/// 检查鼠标是否在指定标记的 UI 区域上
/// </summary>
/// <param name="regionTag">区域标识符,需要在 UIRegionMarker 组件中配置</param>
/// <returns>如果鼠标在指定区域上返回 true,否则返回 false</returns>
public bool IsOverRegion(string regionTag)
{
if (string.IsNullOrEmpty(regionTag))
{
return false;
}
// 如果鼠标不在 UI 上,直接返回 false
if (!EventSystem.current.IsPointerOverGameObject())
{
return false;
}
// 使用 GraphicRaycaster 检测鼠标下的 UI 元素
var eventData = new PointerEventData(EventSystem.current)
{
position = Input.mousePosition
};
var results = new List<RaycastResult>();
_raycaster.Raycast(eventData, results);
return results.Count > 0 && results.Any(result => result.gameObject.CompareTag(regionTag));
}
}
public interface IUIManager