Ver.0.3.0.33
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b4d826b979c4f20acfd79e163a5d425
|
||||
timeCreated: 1748337675
|
||||
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class BaseButton : Button
|
||||
{
|
||||
public SelectionType CurSelectionState => TransStateToType(currentSelectionState);
|
||||
|
||||
public UnityEvent<SelectionType, bool> onStateTransition = new();
|
||||
|
||||
protected override void DoStateTransition(SelectionState state, bool instant)
|
||||
{
|
||||
if (!gameObject.activeInHierarchy)
|
||||
return;
|
||||
|
||||
onStateTransition?.Invoke(TransStateToType(state), instant);
|
||||
}
|
||||
|
||||
protected static SelectionType TransStateToType(SelectionState state)
|
||||
{
|
||||
return Enum.Parse<SelectionType>(state.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 因为作用域问题把枚举转出来用
|
||||
/// </summary>
|
||||
public enum SelectionType
|
||||
{
|
||||
/// <summary>
|
||||
/// The UI object can be selected.
|
||||
/// </summary>
|
||||
Normal,
|
||||
|
||||
/// <summary>
|
||||
/// The UI object is highlighted.
|
||||
/// </summary>
|
||||
Highlighted,
|
||||
|
||||
/// <summary>
|
||||
/// The UI object is pressed.
|
||||
/// </summary>
|
||||
Pressed,
|
||||
|
||||
/// <summary>
|
||||
/// The UI object is selected
|
||||
/// </summary>
|
||||
Selected,
|
||||
|
||||
/// <summary>
|
||||
/// The UI object cannot be selected.
|
||||
/// </summary>
|
||||
Disabled
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f5702aa38bfd4746aecc799b153aab91
|
||||
timeCreated: 1748343214
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using AibisDream.Utility;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.Localization.Components;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
[RequireComponent(typeof(Button))]
|
||||
public class BoolButton : MonoBehaviour
|
||||
{
|
||||
private Image _buttonImage;
|
||||
private LocalizeStringEvent _stringEvent;
|
||||
private Button _button;
|
||||
public BoolButtonData data;
|
||||
public Func<bool> getBool;
|
||||
|
||||
public UnityEvent OnClick
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_button == null) _button = GetComponent<Button>();
|
||||
return _button.onClick;
|
||||
}
|
||||
}
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
_stringEvent = GetComponentInChildren<LocalizeStringEvent>();
|
||||
_buttonImage = GetComponentInChildren<Image>();
|
||||
// 监听按钮点击
|
||||
_button = GetComponent<Button>();
|
||||
OnClick.AddListener(() => _ = CommonUtil.Delay(100, AfterClick));
|
||||
}
|
||||
|
||||
private void AfterClick()
|
||||
{
|
||||
if (_buttonImage)
|
||||
{
|
||||
_buttonImage.sprite = getBool.Invoke() ? data.trueSprite : data.falseSprite;
|
||||
}
|
||||
|
||||
if (_stringEvent)
|
||||
{
|
||||
_stringEvent.SetEntry(getBool.Invoke() ? data.trueKey : data.falseKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct BoolButtonData
|
||||
{
|
||||
public string trueKey;
|
||||
public Sprite trueSprite;
|
||||
public string falseKey;
|
||||
public Sprite falseSprite;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9ccf36d0564d46b98327fbcbfb03096b
|
||||
timeCreated: 1749474015
|
||||
@@ -0,0 +1,55 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class ColorTintButton : Button
|
||||
{
|
||||
[SerializeField] private Graphic[] insideGraphics;
|
||||
[SerializeField] private ColorBlock insideColorConfig = ColorBlock.defaultColorBlock;
|
||||
|
||||
[SerializeField] private Graphic[] backGraphics;
|
||||
[SerializeField] private ColorBlock backColorConfig = ColorBlock.defaultColorBlock;
|
||||
|
||||
protected override void DoStateTransition(SelectionState state, bool instant)
|
||||
{
|
||||
if (!gameObject.activeInHierarchy)
|
||||
return;
|
||||
|
||||
var insideColor = state switch
|
||||
{
|
||||
SelectionState.Normal => insideColorConfig.normalColor,
|
||||
SelectionState.Highlighted => insideColorConfig.highlightedColor,
|
||||
SelectionState.Pressed => insideColorConfig.pressedColor,
|
||||
SelectionState.Selected => insideColorConfig.selectedColor,
|
||||
SelectionState.Disabled => insideColorConfig.disabledColor,
|
||||
_ => Color.black
|
||||
};
|
||||
|
||||
StartColorTween(insideGraphics, insideColor * insideColorConfig.colorMultiplier, instant);
|
||||
|
||||
var backColor = state switch
|
||||
{
|
||||
SelectionState.Normal => backColorConfig.normalColor,
|
||||
SelectionState.Highlighted => backColorConfig.highlightedColor,
|
||||
SelectionState.Pressed => backColorConfig.pressedColor,
|
||||
SelectionState.Selected => backColorConfig.selectedColor,
|
||||
SelectionState.Disabled => backColorConfig.disabledColor,
|
||||
_ => Color.black
|
||||
};
|
||||
|
||||
StartColorTween(backGraphics, backColor * insideColorConfig.colorMultiplier, instant);
|
||||
}
|
||||
|
||||
private void StartColorTween(Graphic[] graphics, Color targetColor, bool instant)
|
||||
{
|
||||
if (graphics is { Length: > 0 })
|
||||
{
|
||||
foreach (var graphic in graphics)
|
||||
{
|
||||
graphic?.CrossFadeColor(targetColor, instant ? 0f : insideColorConfig.fadeDuration, true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 566501c4f7a049a5af0114ca8bab5c72
|
||||
timeCreated: 1748344650
|
||||
@@ -1,34 +1,41 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class CursorManager : MonoBehaviour
|
||||
public class CursorManager : Singleton<CursorManager>
|
||||
{
|
||||
private const float DelayTime = 0.1f;
|
||||
|
||||
[Header("指针图片")] public Sprite defaultCursor;
|
||||
public Sprite nextTalk, talking, option, interactive, holding, disable, eye;
|
||||
public Sprite nextTalk, talking, option, interactive, holding, disable;
|
||||
|
||||
private Image _cursor;
|
||||
private GraphicRaycaster _raycaster;
|
||||
|
||||
private Sprite _curCursor;
|
||||
private CursorState _stateCache;
|
||||
|
||||
#region 状态相关
|
||||
|
||||
private Coroutine _switchCoroutine;
|
||||
private CursorState _curState = CursorState.Default;
|
||||
private bool _isTalking;
|
||||
[HideInInspector] public bool isInMenu;
|
||||
private bool _isInGame;
|
||||
|
||||
#endregion
|
||||
|
||||
// 卸载时注销事件
|
||||
private readonly List<IUnRegister> _unRegisters = new();
|
||||
|
||||
public void Awake()
|
||||
private void Start()
|
||||
{
|
||||
InitRefs();
|
||||
InitCursor();
|
||||
@@ -59,14 +66,54 @@ namespace AibisDream
|
||||
private void InitRefs()
|
||||
{
|
||||
_cursor = transform.Find("Cursor").GetComponent<Image>();
|
||||
_raycaster = UIManager.Instance.Canvas.GetComponent<GraphicRaycaster>();
|
||||
}
|
||||
|
||||
private void RegisterEvent()
|
||||
{
|
||||
// 此处监听所有可能对鼠标状态产生影响的事件
|
||||
RegisterGameEvent();
|
||||
RegisterDialogEvent();
|
||||
RegisterTriggerEvent();
|
||||
RegisterMenu();
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
_curCursor = defaultCursor;
|
||||
|
||||
_curState = CursorState.Default;
|
||||
_stateCache = CursorState.Default;
|
||||
|
||||
_isInGame = false;
|
||||
}
|
||||
|
||||
private void RegisterDialogEvent()
|
||||
@@ -98,22 +145,48 @@ namespace AibisDream
|
||||
EnumEventSystem.Global.Register<TriggerEnum, IInteraction>(TriggerEnum.PointerUp, OnPointerUp));
|
||||
}
|
||||
|
||||
private void RegisterMenu()
|
||||
{
|
||||
_unRegisters.Add(EnumEventSystem.Global.Register<EventEnum, bool>(EventEnum.Menu, OnMenuChange));
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Input.GetMouseButtonDown(0) || Input.GetKeyDown(KeyCode.Space))
|
||||
UpdateCursor();
|
||||
HandleDialog();
|
||||
}
|
||||
|
||||
private void UpdateCursor()
|
||||
{
|
||||
// 再判断是不是浮在可交互物上
|
||||
_cursor.sprite = !_isInGame || IsOverSelected() ? defaultCursor : _curCursor;
|
||||
}
|
||||
|
||||
private void HandleDialog()
|
||||
{
|
||||
if (!_isInGame) return;
|
||||
if (!Input.GetMouseButtonDown(0) && !Input.GetKeyDown(KeyCode.Space)) return;
|
||||
|
||||
// 控制对话
|
||||
if (_isTalking)
|
||||
{
|
||||
if (_isTalking && !isInMenu)
|
||||
{
|
||||
DialogController.Instance.JumpLine();
|
||||
}
|
||||
DialogController.Instance.JumpLine();
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
// // 将虚拟指针与鼠标位置同步
|
||||
@@ -139,7 +212,7 @@ namespace AibisDream
|
||||
_switchCoroutine = StartCoroutine(DelayAction(() =>
|
||||
{
|
||||
_isTalking = false;
|
||||
_cursor.sprite = defaultCursor;
|
||||
_curCursor = defaultCursor;
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -155,7 +228,7 @@ namespace AibisDream
|
||||
_switchCoroutine = StartCoroutine(DelayAction(() =>
|
||||
{
|
||||
_isTalking = false;
|
||||
_cursor.sprite = defaultCursor;
|
||||
_curCursor = defaultCursor;
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -212,17 +285,6 @@ namespace AibisDream
|
||||
SwitchCursor(EventSystemEx.Instance.holdingObj == target.GetGameObject() ? interactive : defaultCursor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region 菜单打开
|
||||
|
||||
private void OnMenuChange(bool isMenuOpen)
|
||||
{
|
||||
isInMenu = isMenuOpen;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -235,7 +297,7 @@ namespace AibisDream
|
||||
private void SwitchCursor(Sprite sprite)
|
||||
{
|
||||
StopDelayCursor();
|
||||
_cursor.sprite = sprite;
|
||||
_curCursor = sprite;
|
||||
}
|
||||
|
||||
private void StopDelayCursor()
|
||||
@@ -259,7 +321,6 @@ namespace AibisDream
|
||||
public enum CursorState
|
||||
{
|
||||
Default,
|
||||
Interactive,
|
||||
Eye
|
||||
Interactive
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d9cb39e4c7764dcf8f99fe429a8edc19
|
||||
timeCreated: 1749038925
|
||||
@@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.Utility;
|
||||
using Febucci.UI.Core;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class Bubble : MonoBehaviour
|
||||
{
|
||||
private TypewriterCore _typewriter;
|
||||
private TMP_Text _actorName;
|
||||
|
||||
private TMP_Text _lineText;
|
||||
private LayoutElement _layoutElement;
|
||||
|
||||
private LayoutGroup _layoutGroup;
|
||||
private RectTransform _rect;
|
||||
private Image _bubbleImage;
|
||||
|
||||
#region 参数
|
||||
|
||||
[SerializeField] public ActorRole role;
|
||||
[SerializeField] public int idx;
|
||||
[SerializeField] private int maxWidth;
|
||||
[SerializeField] private int minWidth;
|
||||
|
||||
#endregion
|
||||
|
||||
public event Action<Bubble> OnDisposed;
|
||||
|
||||
public bool IsShowingText => _typewriter.isShowingText;
|
||||
|
||||
public void LoadConfig(BubbleSlotData data)
|
||||
{
|
||||
InitRefs();
|
||||
|
||||
role = data.actorRole;
|
||||
idx = data.idx;
|
||||
maxWidth = data.maxWidth;
|
||||
minWidth = data.minWidth;
|
||||
var rect = transform as RectTransform;
|
||||
rect.localPosition = UIManager.GetDialogUIPos(data.slotPosition);
|
||||
// 设置style
|
||||
SetStyle(data.bubbleStyle, data.pivotType);
|
||||
}
|
||||
|
||||
private void SetStyle(BubbleStyle bubbleStyle, PivotType pivotType)
|
||||
{
|
||||
_bubbleImage.color = bubbleStyle.imageColor;
|
||||
// padding内容
|
||||
_layoutGroup.padding = bubbleStyle.BubblePadding;
|
||||
_lineText.margin = bubbleStyle.linePadding;
|
||||
_lineText.fontSize = bubbleStyle.lineFontSize;
|
||||
_actorName.margin = bubbleStyle.actorPadding;
|
||||
_actorName.fontSize = bubbleStyle.actorFontSize;
|
||||
// 是否展示名称
|
||||
_actorName.gameObject.SetActive(bubbleStyle.hasActorName);
|
||||
|
||||
// 锚点相关
|
||||
_rect.pivot = CommonUtil.GetPivotByType(pivotType);
|
||||
var spritePath = bubbleStyle.bubbleSpriteAddress.EndsWith("]")
|
||||
? $"{bubbleStyle.bubbleSpriteAddress}"
|
||||
: $"{bubbleStyle.bubbleSpriteAddress}[{pivotType.ToString()}]";
|
||||
|
||||
_bubbleImage.sprite = ResourceKit.LoadAssetSync<Sprite>(spritePath);
|
||||
}
|
||||
|
||||
private void InitRefs()
|
||||
{
|
||||
_typewriter = GetComponentInChildren<TypewriterCore>();
|
||||
_actorName = transform.Find("名称").GetComponent<TMP_Text>();
|
||||
_typewriter.onTextShowed.RemoveAllListeners();
|
||||
_typewriter.onTextShowed.AddListener(() => EnumEventSystem.Global.Send(DialogEventEnum.LineShown));
|
||||
// 获取内容文字
|
||||
_lineText = _typewriter.GetComponent<TMP_Text>();
|
||||
_layoutElement = _typewriter.GetComponent<LayoutElement>();
|
||||
// 气泡自身
|
||||
_layoutGroup = GetComponent<LayoutGroup>();
|
||||
_bubbleImage = GetComponent<Image>();
|
||||
_rect = _bubbleImage.rectTransform;
|
||||
}
|
||||
|
||||
public void ShowActorName(CharacterVo characterVo)
|
||||
{
|
||||
if (_actorName == null) return;
|
||||
|
||||
_actorName.text = string.IsNullOrEmpty(characterVo.GetActorName())
|
||||
? string.Empty
|
||||
: characterVo.GetActorName();
|
||||
}
|
||||
|
||||
public void ShowLine(string line)
|
||||
{
|
||||
_typewriter.TextAnimator.SetText("");
|
||||
gameObject.SetActive(true);
|
||||
HandleLayout(line);
|
||||
_typewriter.ShowText(line);
|
||||
}
|
||||
|
||||
public void SkipLine()
|
||||
{
|
||||
if (_typewriter.isShowingText)
|
||||
{
|
||||
_typewriter.SkipTypewriter();
|
||||
}
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Hide();
|
||||
OnDisposed?.Invoke(this);
|
||||
OnDisposed = null;
|
||||
}
|
||||
|
||||
private void HandleLayout(string line)
|
||||
{
|
||||
// 先去除富文本标签
|
||||
line = Regex.Replace(line.Replace("<br>", "\n"), "[<|{].*?[>|}]", string.Empty);
|
||||
var width = DialogCanvasManager.Instance.EstimateTextWidth(line, _lineText.fontSize);
|
||||
if (width > maxWidth)
|
||||
{
|
||||
_layoutElement.preferredWidth = maxWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
_layoutElement.preferredWidth = -1;
|
||||
}
|
||||
|
||||
_layoutElement.minWidth = minWidth;
|
||||
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(_rect);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[Header("测试参数")] public int testMinWidth;
|
||||
public int testMaxWidth;
|
||||
public BubbleStyle testBubbleStyle;
|
||||
public PivotType testPivotType;
|
||||
|
||||
public void LoadStyle()
|
||||
{
|
||||
InitRefs();
|
||||
SetStyle(testBubbleStyle, testPivotType);
|
||||
_layoutElement.minWidth = testMinWidth;
|
||||
_layoutElement.preferredWidth = testMaxWidth;
|
||||
}
|
||||
|
||||
public void SaveStyle()
|
||||
{
|
||||
if (!testBubbleStyle) return;
|
||||
|
||||
InitRefs();
|
||||
|
||||
testBubbleStyle.linePadding = _lineText.margin;
|
||||
testBubbleStyle.lineFontSize = _lineText.fontSize;
|
||||
testBubbleStyle.actorPadding = _actorName.margin;
|
||||
testBubbleStyle.actorFontSize = _actorName.fontSize;
|
||||
|
||||
testBubbleStyle.BubblePadding = _layoutGroup.padding;
|
||||
|
||||
testBubbleStyle.hasActorName = _actorName.gameObject.activeSelf;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6adb9d7cc075457a8fece1b14ca4c429
|
||||
timeCreated: 1749022700
|
||||
@@ -0,0 +1,37 @@
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class BubbleFactory
|
||||
{
|
||||
private readonly SimpleObjectPool<Bubble> _pool;
|
||||
private readonly GameObject _bubblePrefab;
|
||||
private readonly Transform _bubbleRoot;
|
||||
|
||||
public BubbleFactory(Transform bubbleRoot)
|
||||
{
|
||||
_bubbleRoot = bubbleRoot;
|
||||
_bubblePrefab = ResourceKit.LoadAssetSync<GameObject>(ConstRef.BubblePrefab);
|
||||
_pool = new SimpleObjectPool<Bubble>(InstantiateBubble);
|
||||
}
|
||||
|
||||
public Bubble Create(BubbleSlotData data)
|
||||
{
|
||||
var bubble = _pool.Allocate();
|
||||
bubble.OnDisposed += o => _pool.Recycle(o);
|
||||
|
||||
bubble.LoadConfig(data);
|
||||
bubble.Hide();
|
||||
|
||||
return bubble;
|
||||
}
|
||||
|
||||
private Bubble InstantiateBubble()
|
||||
{
|
||||
return Object.Instantiate(_bubblePrefab, _bubbleRoot).GetComponent<Bubble>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: da7e7fe867834c0aa077caf6aba8fd93
|
||||
timeCreated: 1749123699
|
||||
@@ -0,0 +1,215 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class BubbleGroup : Singleton<BubbleGroup>, IDialogView
|
||||
{
|
||||
private BubbleOption[] _options;
|
||||
private Dictionary<ActorRole, Bubble[]> _bubbleDict;
|
||||
public DialogViewType CurDialogType { get; private set; }
|
||||
|
||||
private BubbleFactory _bubbleFactory;
|
||||
|
||||
private SimpleObjectPool<Bubble> _bubblePool;
|
||||
private SimpleObjectPool<BubbleOption> _optionPool;
|
||||
|
||||
#region 一些索引
|
||||
|
||||
private Action _nextStep;
|
||||
private Bubble _curBubble;
|
||||
|
||||
private IUnRegister _unRegister;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 生命周期
|
||||
|
||||
public override void OnSingletonInit()
|
||||
{
|
||||
// 注册泡泡
|
||||
_unRegister =
|
||||
EnumEventSystem.Global.Register<DialogEventEnum, DialogText>(DialogEventEnum.OptionSelected,
|
||||
_ => HideOptions());
|
||||
// 工厂
|
||||
_bubbleFactory = new BubbleFactory(transform.Find("Bubbles"));
|
||||
// 加载选项
|
||||
InitOptions();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
HideDialog();
|
||||
}
|
||||
|
||||
public override void OnSingletonDestroy()
|
||||
{
|
||||
_unRegister.UnRegister();
|
||||
}
|
||||
|
||||
public void LoadBubbles(BubbleSlotGroupData groupData)
|
||||
{
|
||||
CurDialogType = groupData.dialogViewType;
|
||||
// 回收旧泡泡
|
||||
foreach (var oldBubble in _bubbleDict.Values.SelectMany(item => item))
|
||||
{
|
||||
oldBubble.Dispose();
|
||||
}
|
||||
|
||||
// 加载新泡泡
|
||||
_bubbleDict = groupData.bubbleSlots
|
||||
.Select(item => _bubbleFactory.Create(item))
|
||||
.GroupBy(item => item.role)
|
||||
.ToDictionary(group => group.Key, group => group.ToArray());
|
||||
|
||||
// 更新选项的位置
|
||||
if (groupData.bubbleOptionSlots is { Length: 0 })
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var optionSlotData in groupData.bubbleOptionSlots)
|
||||
{
|
||||
if (optionSlotData.optionIdx >= _options.Length) continue;
|
||||
_options[optionSlotData.optionIdx].LoadData(optionSlotData);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitOptions()
|
||||
{
|
||||
_options = transform.Find("Options")
|
||||
.GetComponentsInChildren<BubbleOption>(true)
|
||||
.OrderBy(item => item.optionIdx)
|
||||
.ToArray();
|
||||
|
||||
_bubbleDict = new Dictionary<ActorRole, Bubble[]>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 对话框功能实现
|
||||
|
||||
public void ShowLine(string dialogLine, CharacterVo character, Action nextStep, string lineId,
|
||||
bool isAutoSkip = false)
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
|
||||
if (TryGetBubble(character.role, character.bubbleIdx, out var bubble))
|
||||
{
|
||||
bubble.ShowActorName(character);
|
||||
bubble.ShowLine(dialogLine);
|
||||
// 将非选定的Bubble隐藏
|
||||
HideDialog(bubble);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"{character.GetActorName()}的Role没有对应Bubble");
|
||||
return;
|
||||
}
|
||||
|
||||
// 保留信息
|
||||
_nextStep = nextStep;
|
||||
_curBubble = bubble;
|
||||
}
|
||||
|
||||
public void ShowOptions(DialogOption[] dialogueOptions)
|
||||
{
|
||||
HideDialog();
|
||||
// 如果仅由一个选项
|
||||
if (dialogueOptions.Length == 1)
|
||||
{
|
||||
_options[0].ShowOption(dialogueOptions[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < dialogueOptions.Length; i++)
|
||||
{
|
||||
_options[i + 1].ShowOption(dialogueOptions[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void HideDialog()
|
||||
{
|
||||
foreach (var textBubble in _bubbleDict.Values.SelectMany(inner => inner))
|
||||
{
|
||||
textBubble.Hide();
|
||||
}
|
||||
|
||||
HideOptions();
|
||||
}
|
||||
|
||||
public bool TrySkipLine(bool isForceSkip = false)
|
||||
{
|
||||
if (!_curBubble)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_curBubble && _curBubble.IsShowingText)
|
||||
{
|
||||
_curBubble.SkipLine();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void NextStep()
|
||||
{
|
||||
if (_nextStep == null) return;
|
||||
|
||||
EnumEventSystem.Global.Send(DialogEventEnum.LineEnd);
|
||||
var next = _nextStep;
|
||||
_nextStep = null;
|
||||
|
||||
_curBubble?.Hide();
|
||||
_curBubble = null;
|
||||
|
||||
next?.Invoke();
|
||||
}
|
||||
|
||||
public void OnLocalizationChanged(string value)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void HideDialog(Bubble activeBubble)
|
||||
{
|
||||
foreach (var textBubble in _bubbleDict.Values.SelectMany(inner => inner))
|
||||
{
|
||||
if (activeBubble != textBubble)
|
||||
{
|
||||
textBubble.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
HideOptions();
|
||||
}
|
||||
|
||||
private bool TryGetBubble(ActorRole role, int idx, out Bubble bubble)
|
||||
{
|
||||
if (_bubbleDict.TryGetValue(role, out var bubbles))
|
||||
{
|
||||
bubble = bubbles.FirstOrDefault(item => item.idx == idx);
|
||||
return bubble != null;
|
||||
}
|
||||
|
||||
bubble = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void HideOptions()
|
||||
{
|
||||
foreach (var option in _options)
|
||||
{
|
||||
option.Hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a76234ef7ee34400b306a4823081ba62
|
||||
timeCreated: 1749022020
|
||||
@@ -0,0 +1,44 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class BubbleOption : MonoBehaviour
|
||||
{
|
||||
public int optionIdx;
|
||||
private TMP_Text _text;
|
||||
private DialogOption _option;
|
||||
private Button _button;
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
_text = GetComponentInChildren<TMP_Text>();
|
||||
_button = GetComponent<Button>();
|
||||
}
|
||||
|
||||
public void LoadData(BubbleOptionSlotData data)
|
||||
{
|
||||
((RectTransform)transform).localPosition = UIManager.GetDialogUIPos(data.slotPosition);
|
||||
}
|
||||
|
||||
public void ShowOption(DialogOption dialogOption)
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
_button.onClick.RemoveAllListeners();
|
||||
_text.text = "";
|
||||
// 清理旧数据
|
||||
gameObject.SetActive(true);
|
||||
_text.text = dialogOption.Line;
|
||||
_button.onClick.AddListener(() =>
|
||||
{
|
||||
dialogOption.Select();
|
||||
});
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 468ec4615ee64b2cb5ef8b1a66339dac
|
||||
timeCreated: 1749024963
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using AibisDream.Framework;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class BubbleOptionSlot : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private BubbleOptionSlotData data;
|
||||
|
||||
public BubbleOptionSlotData Data
|
||||
{
|
||||
get
|
||||
{
|
||||
data.slotPosition = CameraKit.GetScreenPos(transform.position);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct BubbleOptionSlotData
|
||||
{
|
||||
[HideInInspector] public Vector3 slotPosition;
|
||||
public int optionIdx;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 98b5d94e03211a94bba6de692a1d405a
|
||||
guid: c1a72c4bf33546244b7030c41b1b2b05
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class BubbleSlot : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private BubbleSlotData data;
|
||||
|
||||
public BubbleSlotData Data
|
||||
{
|
||||
get
|
||||
{
|
||||
data.slotPosition = CameraKit.GetScreenPos(transform.position);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
// 画一个矩形
|
||||
Gizmos.color = Color.white;
|
||||
// 计算长和宽
|
||||
var width = data.maxWidth / 108f;
|
||||
var height = 0.5f * width;
|
||||
// 根据PivotType确定矩形位置
|
||||
var centerNormal = GetSquareCenterOffset(data.pivotType);
|
||||
// Calculate the corners of the square
|
||||
var topLeftNormal = centerNormal + new Vector3(-0.5f, 0.5f, 0);
|
||||
var topRightNormal = centerNormal + new Vector3(0.5f, 0.5f, 0);
|
||||
var bottomLeftNormal = centerNormal + new Vector3(-0.5f, -0.5f, 0);
|
||||
var bottomRightNormal = centerNormal + new Vector3(0.5f, -0.5f, 0);
|
||||
|
||||
// 计算实际位置
|
||||
var topLeft = new Vector3(topLeftNormal.x * width, topLeftNormal.y * height, 0) + transform.position;
|
||||
var topRight = new Vector3(topRightNormal.x * width, topRightNormal.y * height, 0) + transform.position;
|
||||
var bottomLeft = new Vector3(bottomLeftNormal.x * width, bottomLeftNormal.y * height, 0) + transform.position;
|
||||
var bottomRight = new Vector3(bottomRightNormal.x * width, bottomRightNormal.y * height, 0) + transform.position;
|
||||
|
||||
// Draw the square
|
||||
Gizmos.DrawLine(topLeft, topRight);
|
||||
Gizmos.DrawLine(topRight, bottomRight);
|
||||
Gizmos.DrawLine(bottomRight, bottomLeft);
|
||||
Gizmos.DrawLine(bottomLeft, topLeft);
|
||||
|
||||
// pivot画圆
|
||||
Gizmos.color = Color.green;
|
||||
Gizmos.DrawWireSphere(transform.position, 0.05f);
|
||||
}
|
||||
|
||||
private static Vector3 GetSquareCenterOffset(PivotType pivotType)
|
||||
{
|
||||
return pivotType switch
|
||||
{
|
||||
PivotType.Top => new Vector3(0, -0.5f, 0),
|
||||
PivotType.Bottom => new Vector3(0, 0.5f, 0),
|
||||
PivotType.Left => new Vector3(0.5f, 0, 0),
|
||||
PivotType.Right => new Vector3(-0.5f, 0, 0),
|
||||
PivotType.TopLeft => new Vector3(0.5f, -0.5f, 0),
|
||||
PivotType.TopRight => new Vector3(-0.5f, -0.5f, 0),
|
||||
PivotType.BottomLeft => new Vector3(0.5f, 0.5f, 0),
|
||||
PivotType.BottomRight => new Vector3(-0.5f, 0.5f, 0),
|
||||
PivotType.Center => Vector3.zero,
|
||||
_ => Vector3.zero
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct BubbleSlotData
|
||||
{
|
||||
[HideInInspector] public Vector3 slotPosition;
|
||||
public ActorRole actorRole;
|
||||
public int idx;
|
||||
public int maxWidth;
|
||||
public int minWidth;
|
||||
public BubbleStyle bubbleStyle;
|
||||
|
||||
public PivotType pivotType;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8a4b8d325dce1b64980f8fd2ff77c571
|
||||
guid: bbc0dde0e8b8a9c498545e1b5b508241
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class BubbleSlotGroup : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private BubbleSlotGroupData data;
|
||||
|
||||
public BubbleSlotGroupData CollectData()
|
||||
{
|
||||
// 获取子类
|
||||
var bubbleSlots = GetComponentsInChildren<BubbleSlot>();
|
||||
var optionSlots = GetComponentsInChildren<BubbleOptionSlot>();
|
||||
// 抽取数据
|
||||
data.bubbleSlots = bubbleSlots.Select(item => item.Data).ToArray();
|
||||
if (optionSlots is { Length: 0 })
|
||||
{
|
||||
data.bubbleOptionSlots = optionSlots.Select(item => item.Data).ToArray();
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct BubbleSlotGroupData
|
||||
{
|
||||
public DialogViewType dialogViewType;
|
||||
[HideInInspector] public BubbleSlotData[] bubbleSlots;
|
||||
[HideInInspector] public BubbleOptionSlotData[] bubbleOptionSlots;
|
||||
}
|
||||
|
||||
public enum PivotType {
|
||||
Top,
|
||||
Bottom,
|
||||
Left,
|
||||
Right,
|
||||
TopLeft,
|
||||
TopRight,
|
||||
BottomLeft,
|
||||
BottomRight,
|
||||
Center
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8225446392f64688813d628ffb56cc7c
|
||||
timeCreated: 1749042853
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.Utility;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
@@ -14,6 +15,8 @@ namespace AibisDream
|
||||
public GameObject choiceButtonPrefab;
|
||||
|
||||
#endregion
|
||||
|
||||
private TextParam _textParam;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
@@ -21,7 +24,7 @@ namespace AibisDream
|
||||
_ => HideDialog());
|
||||
}
|
||||
|
||||
public void ShowLine(string dialogLine, CharacterVo character, Action nextStep, bool isAutoSkip = false)
|
||||
public void ShowLine(string dialogLine, CharacterVo character, Action nextStep, string lineId, bool isAutoSkip = false)
|
||||
{
|
||||
Debug.Log("CenterOption只显示选项");
|
||||
}
|
||||
@@ -42,8 +45,14 @@ namespace AibisDream
|
||||
if (!option.IsAvailable) continue;
|
||||
|
||||
var choiceInstance = Instantiate(choiceButtonPrefab, transform);
|
||||
choiceInstance.GetComponentInChildren<TMP_Text>().text = option.Line;
|
||||
choiceInstance.GetComponent<Button>().onClick.AddListener(option.Select);
|
||||
var optionText = choiceInstance.GetComponentInChildren<TextMeshProUGUI>();
|
||||
optionText.text = option.Line;
|
||||
optionText.characterSpacing = _textParam.charSpace;
|
||||
choiceInstance.GetComponent<Button>().onClick.AddListener(() =>
|
||||
{
|
||||
HideDialog();
|
||||
option.Select();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,5 +71,10 @@ namespace AibisDream
|
||||
{
|
||||
// 无法继续
|
||||
}
|
||||
|
||||
public void OnLocalizationChanged(string value)
|
||||
{
|
||||
_textParam = JsonUtil.ReadBeanByText<TextParam>(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.Utility;
|
||||
using Febucci.UI;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
@@ -17,16 +18,12 @@ namespace AibisDream
|
||||
private Action _nextStep;
|
||||
|
||||
#endregion
|
||||
|
||||
private bool _skipLineDelay;
|
||||
|
||||
[Header("跳过阻塞延迟时间/ms")] [SerializeField]
|
||||
private int delayTime = 300;
|
||||
|
||||
private TextParam _textParam;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
InitComponents();
|
||||
AddSomeListener();
|
||||
}
|
||||
|
||||
private void InitComponents()
|
||||
@@ -34,17 +31,7 @@ namespace AibisDream
|
||||
_dialogText = transform.Find("Dialog Text")?.gameObject.GetComponent<TypewriterByCharacter>();
|
||||
}
|
||||
|
||||
private void AddSomeListener()
|
||||
{
|
||||
// 对话显示后有一段延时不可跳过
|
||||
_dialogText.onTypewriterStart.AddListener(() =>
|
||||
{
|
||||
_skipLineDelay = true;
|
||||
_ = CommonUtil.Delay(delayTime, () => { _skipLineDelay = false; });
|
||||
});
|
||||
}
|
||||
|
||||
public void ShowLine(string dialogLine, CharacterVo character, Action nextStep, bool isAutoSkip = false)
|
||||
public void ShowLine(string dialogLine, CharacterVo character, Action nextStep, string lineId, bool isAutoSkip = false)
|
||||
{
|
||||
// 把原有对话清掉
|
||||
ClearBox();
|
||||
@@ -58,12 +45,6 @@ namespace AibisDream
|
||||
_dialogText.onTextShowed.AddListener(() => EnumEventSystem.Global.Send(DialogEventEnum.LineShown));
|
||||
|
||||
_nextStep = nextStep;
|
||||
|
||||
// 执行自动跳过结局
|
||||
if (isAutoSkip)
|
||||
{
|
||||
_dialogText.onTextShowed.AddListener(() => _ = CommonUtil.Delay(200, NextStep));
|
||||
}
|
||||
}
|
||||
|
||||
public void ShowOptions(DialogOption[] dialogueOptions)
|
||||
@@ -78,11 +59,6 @@ namespace AibisDream
|
||||
|
||||
public bool TrySkipLine(bool isForceSkip)
|
||||
{
|
||||
if (_skipLineDelay && !isForceSkip)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_dialogText.isShowingText)
|
||||
{
|
||||
_dialogText.SkipTypewriter();
|
||||
@@ -102,7 +78,16 @@ namespace AibisDream
|
||||
_nextStep = null;
|
||||
next.Invoke();
|
||||
}
|
||||
|
||||
|
||||
public void OnLocalizationChanged(string value)
|
||||
{
|
||||
_textParam = JsonUtil.ReadBeanByText<TextParam>(value);
|
||||
var tmp = _dialogText.GetComponent<TMP_Text>();
|
||||
// 修改对话文本
|
||||
|
||||
tmp.characterSpacing = _textParam.charSpace;
|
||||
}
|
||||
|
||||
private void ClearBox()
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
|
||||
@@ -1,16 +1,212 @@
|
||||
using System;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.Utility;
|
||||
using Febucci.UI;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class DialogBox : DialogBoxBase
|
||||
public class DialogBox : MonoBehaviour, IDialogView
|
||||
{
|
||||
[SerializeField] protected GameObject choiceButton;
|
||||
|
||||
protected override void OnAwake()
|
||||
#region box组件名称
|
||||
|
||||
private const string ActorName = "Actor Name";
|
||||
private const string DialogText = "Dialog Text";
|
||||
private const string NextArray = "Next Array";
|
||||
private const string ChoiceBox = "Choice Box";
|
||||
|
||||
#endregion
|
||||
|
||||
#region box组件
|
||||
|
||||
private TypewriterByCharacter _dialogText;
|
||||
private TextMeshProUGUI _actorName;
|
||||
private GameObject _nextArray;
|
||||
private GameObject _choiceBox;
|
||||
|
||||
#endregion
|
||||
|
||||
private TextParam _textParam;
|
||||
|
||||
private Action _nextStep;
|
||||
|
||||
[SerializeReference] private GameObject choiceButtonPrefab;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// 初始化选择按钮
|
||||
choiceButtonPrefab = choiceButton;
|
||||
InitComponents();
|
||||
RegisterEvents();
|
||||
}
|
||||
|
||||
#region 初始化
|
||||
|
||||
private void InitComponents()
|
||||
{
|
||||
_actorName = transform.Find(ActorName)?.gameObject.GetComponentInChildren<TextMeshProUGUI>();
|
||||
_dialogText = transform.Find(DialogText)?.gameObject.GetComponent<TypewriterByCharacter>();
|
||||
_nextArray = transform.Find(NextArray)?.gameObject;
|
||||
_choiceBox = transform.Find(ChoiceBox)?.gameObject;
|
||||
}
|
||||
|
||||
private void RegisterEvents()
|
||||
{
|
||||
_dialogText.onTextShowed.AddListener(OnTextShowed);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏对话盒
|
||||
/// </summary>
|
||||
public void HideDialog()
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示对话
|
||||
/// </summary>
|
||||
/// <param name="dialogLine">对话内容</param>
|
||||
/// <param name="character">角色内容</param>
|
||||
/// <param name="nextStep">下一步</param>
|
||||
/// <param name="lineId">行号</param>
|
||||
/// <param name="isAutoSkip">是否自动跳过,默认为false</param>
|
||||
public void ShowLine(string dialogLine, CharacterVo character, Action nextStep, string lineId,
|
||||
bool isAutoSkip = false)
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
|
||||
// 把原有对话清掉
|
||||
ClearBox();
|
||||
_dialogText.gameObject.SetActive(true);
|
||||
_choiceBox?.SetActive(false);
|
||||
|
||||
// 显示内容
|
||||
ShowActorName(character);
|
||||
_dialogText.ShowText(dialogLine);
|
||||
|
||||
_nextStep = nextStep;
|
||||
}
|
||||
|
||||
private void OnTextShowed()
|
||||
{
|
||||
_nextArray.gameObject.SetActive(true);
|
||||
EnumEventSystem.Global.Send(DialogEventEnum.LineShown);
|
||||
}
|
||||
|
||||
public void NextStep()
|
||||
{
|
||||
if (_nextStep == null) return;
|
||||
|
||||
EnumEventSystem.Global.Send(DialogEventEnum.LineEnd);
|
||||
// 关闭对话
|
||||
var next = _nextStep;
|
||||
_nextStep = null;
|
||||
next.Invoke();
|
||||
}
|
||||
|
||||
public void OnLocalizationChanged(string value)
|
||||
{
|
||||
_textParam = JsonUtil.ReadBeanByText<TextParam>(value);
|
||||
|
||||
var tmp = _dialogText.GetComponent<TMP_Text>();
|
||||
// 修改对话文本
|
||||
tmp.characterSpacing = _textParam.charSpace;
|
||||
_actorName.characterSpacing = _textParam.charSpace;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 展示选项
|
||||
/// </summary>
|
||||
/// <param name="dialogueOptions">对话选项</param>
|
||||
public void ShowOptions(DialogOption[] dialogueOptions)
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
// 没有选择框直接不显示选项
|
||||
if (!_choiceBox)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 把原有对话清掉
|
||||
ClearBox();
|
||||
_choiceBox.SetActive(true);
|
||||
_dialogText.gameObject.SetActive(false);
|
||||
HideActorName();
|
||||
|
||||
// 销毁原来的按钮
|
||||
foreach (Transform child in _choiceBox.transform)
|
||||
{
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
|
||||
// 生成新按钮
|
||||
foreach (var option in dialogueOptions)
|
||||
{
|
||||
if (!option.IsAvailable) continue;
|
||||
|
||||
var choiceInstance = Instantiate(choiceButtonPrefab, _choiceBox.transform);
|
||||
var optionText = choiceInstance.GetComponentInChildren<TextMeshProUGUI>();
|
||||
optionText.text = option.Line;
|
||||
optionText.characterSpacing = _textParam.charSpace;
|
||||
choiceInstance.GetComponent<Button>().onClick.AddListener(() =>
|
||||
{
|
||||
HideDialog();
|
||||
option.Select();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试跳过对话
|
||||
/// true说明成功快进了对话显示
|
||||
/// false说明对话已经显示完成了,可以跳过
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool TrySkipLine(bool isForceSkip)
|
||||
{
|
||||
if (_dialogText.isShowingText)
|
||||
{
|
||||
_dialogText.SkipTypewriter();
|
||||
return true;
|
||||
}
|
||||
|
||||
_nextArray.gameObject.SetActive(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ClearBox()
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
_dialogText?.ShowText("");
|
||||
_nextArray?.SetActive(false);
|
||||
if (!_actorName) _actorName.text = "";
|
||||
}
|
||||
|
||||
private void ShowActorName(CharacterVo character)
|
||||
{
|
||||
if (!_actorName) return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(character.key))
|
||||
{
|
||||
_actorName.transform.parent.gameObject.SetActive(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_actorName.transform.parent.localPosition =
|
||||
character.key == "me" ? new Vector3(310, 95, 0) : new Vector3(-458, 95, 0);
|
||||
|
||||
_actorName.transform.parent.gameObject.SetActive(true);
|
||||
_actorName.text = character.GetActorName();
|
||||
}
|
||||
}
|
||||
|
||||
private void HideActorName()
|
||||
{
|
||||
_actorName.transform.parent.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,213 +0,0 @@
|
||||
using System;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.Utility;
|
||||
using Febucci.UI;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public abstract class DialogBoxBase : MonoBehaviour, IDialogView
|
||||
{
|
||||
#region box组件名称
|
||||
|
||||
private const string ActorName = "Actor Name";
|
||||
private const string DialogText = "Dialog Text";
|
||||
private const string NextArray = "Next Array";
|
||||
private const string ChoiceBox = "Choice Box";
|
||||
|
||||
#endregion
|
||||
|
||||
#region box组件
|
||||
|
||||
private TypewriterByCharacter _dialogText;
|
||||
private TextMeshProUGUI _actorName;
|
||||
private GameObject _nextArray;
|
||||
private GameObject _choiceBox;
|
||||
|
||||
#endregion
|
||||
|
||||
private bool _skipLineDelay;
|
||||
private Action _nextStep;
|
||||
|
||||
[Header("跳过阻塞延迟时间/ms")] [SerializeField]
|
||||
private int delayTime = 300;
|
||||
|
||||
protected GameObject choiceButtonPrefab;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
InitComponents();
|
||||
AddSomeListener();
|
||||
}
|
||||
|
||||
private void AddSomeListener()
|
||||
{
|
||||
// 对话显示后有一段延时不可跳过
|
||||
_dialogText.onTypewriterStart.AddListener(() =>
|
||||
{
|
||||
_skipLineDelay = true;
|
||||
_ = CommonUtil.Delay(delayTime, () => { _skipLineDelay = false; });
|
||||
});
|
||||
}
|
||||
|
||||
#region 初始化
|
||||
|
||||
private void InitComponents()
|
||||
{
|
||||
_actorName = transform.Find(ActorName)?.gameObject.GetComponentInChildren<TextMeshProUGUI>();
|
||||
_dialogText = transform.Find(DialogText)?.gameObject.GetComponent<TypewriterByCharacter>();
|
||||
_nextArray = transform.Find(NextArray)?.gameObject;
|
||||
_choiceBox = transform.Find(ChoiceBox)?.gameObject;
|
||||
|
||||
OnAwake();
|
||||
}
|
||||
|
||||
protected virtual void OnAwake()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏对话盒
|
||||
/// </summary>
|
||||
public void HideDialog()
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示对话
|
||||
/// </summary>
|
||||
/// <param name="dialogLine">对话内容</param>
|
||||
/// <param name="character">角色内容</param>
|
||||
/// <param name="nextStep">下一步</param>
|
||||
/// <param name="onTextShowed">文字展示结束后触发</param>
|
||||
/// <param name="isAutoSkip">是否自动跳过,默认为false</param>
|
||||
public virtual void ShowLine(string dialogLine, CharacterVo character, Action nextStep, bool isAutoSkip = false)
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
|
||||
// 把原有对话清掉
|
||||
ClearBox();
|
||||
_dialogText.gameObject.SetActive(true);
|
||||
_choiceBox?.SetActive(false);
|
||||
|
||||
// 显示内容
|
||||
ShowActorName(character);
|
||||
_dialogText.ShowText(dialogLine);
|
||||
|
||||
// 自动跳过的回调很难独立Remove,只能全部清空再注册,下策
|
||||
_dialogText.onTextShowed.RemoveAllListeners();
|
||||
_dialogText.onTextShowed.AddListener(() => _nextArray.gameObject.SetActive(true));
|
||||
_dialogText.onTextShowed.AddListener(() => EnumEventSystem.Global.Send(DialogEventEnum.LineShown));
|
||||
|
||||
_nextStep = nextStep;
|
||||
|
||||
// 执行自动跳过结局
|
||||
if (isAutoSkip)
|
||||
{
|
||||
_dialogText.onTextShowed.AddListener(() => _ = CommonUtil.Delay(200, NextStep));
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void NextStep()
|
||||
{
|
||||
if (_nextStep == null) return;
|
||||
|
||||
EnumEventSystem.Global.Send(DialogEventEnum.LineEnd);
|
||||
var next = _nextStep;
|
||||
_nextStep = null;
|
||||
next.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 展示选项
|
||||
/// </summary>
|
||||
/// <param name="dialogueOptions">对话选项</param>
|
||||
public virtual void ShowOptions(DialogOption[] dialogueOptions)
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
// 没有选择框直接不显示选项
|
||||
if (!_choiceBox)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 把原有对话清掉
|
||||
ClearBox();
|
||||
_choiceBox.SetActive(true);
|
||||
_dialogText.gameObject.SetActive(false);
|
||||
|
||||
// 销毁原来的按钮
|
||||
foreach (Transform child in _choiceBox.transform)
|
||||
{
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
|
||||
// 生成新按钮
|
||||
foreach (var option in dialogueOptions)
|
||||
{
|
||||
if (!option.IsAvailable) continue;
|
||||
|
||||
var choiceInstance = Instantiate(choiceButtonPrefab, _choiceBox.transform);
|
||||
choiceInstance.GetComponentInChildren<TextMeshProUGUI>().text = option.Line;
|
||||
choiceInstance.GetComponent<Button>().onClick.AddListener(option.Select);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试跳过对话
|
||||
/// true说明成功快进了对话显示
|
||||
/// false说明对话已经显示完成了,可以跳过
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual bool TrySkipLine(bool isForceSkip)
|
||||
{
|
||||
if (_skipLineDelay && !isForceSkip)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_dialogText.isShowingText)
|
||||
{
|
||||
_dialogText.SkipTypewriter();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_nextArray.gameObject.SetActive(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearBox()
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
_dialogText?.ShowText("");
|
||||
_nextArray?.SetActive(false);
|
||||
if (!_actorName) _actorName.text = "";
|
||||
}
|
||||
|
||||
private void ShowActorName(CharacterVo character)
|
||||
{
|
||||
if (!_actorName) return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(character.GetActorName()))
|
||||
{
|
||||
_actorName.transform.parent.gameObject.SetActive(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_actorName.transform.parent.localPosition =
|
||||
character.key == "me" ? new Vector3(310, 95, 0) : new Vector3(-458, 95, 0);
|
||||
|
||||
_actorName.transform.parent.gameObject.SetActive(true);
|
||||
_actorName.text = character.GetActorName();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class DialogBubble : DialogBoxBase
|
||||
{
|
||||
// 气泡功能比较简单似乎不需要加别的
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AibisDream.Kit;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class DialogBubbleViewer : MonoBehaviour, IDialogView
|
||||
{
|
||||
private const string PlayerBubble = "Player Bubble";
|
||||
private const string ActorBubble = "Actor Bubble";
|
||||
private const string NormalBox = "Normal Box";
|
||||
|
||||
private IDialogView _normalBox;
|
||||
private IDialogView _playerBubble;
|
||||
|
||||
private IDialogView _actorBubble;
|
||||
|
||||
private List<IDialogView> _dialogViews;
|
||||
|
||||
private IDialogView _currentView;
|
||||
|
||||
private Action _nextStep;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
private void Init()
|
||||
{
|
||||
_normalBox = transform.Find(NormalBox).GetComponent<IDialogView>();
|
||||
_playerBubble = transform.Find(PlayerBubble).GetComponent<IDialogView>();
|
||||
_actorBubble = transform.Find(ActorBubble).GetComponent<IDialogView>();
|
||||
|
||||
_dialogViews = new List<IDialogView> { _normalBox, _playerBubble, _actorBubble };
|
||||
_dialogViews.ForEach(item => item.HideDialog());
|
||||
}
|
||||
|
||||
public void ShowLine(string dialogLine, CharacterVo character, Action nextStep, bool isAutoSkip = false)
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
// 根据角色信息气泡
|
||||
_currentView = SelectBubbleByCharacter(character);
|
||||
// 处理其他泡泡
|
||||
ProcessOtherBox();
|
||||
// 显示对话
|
||||
_currentView.ShowLine(dialogLine, character, nextStep, isAutoSkip);
|
||||
}
|
||||
|
||||
public void ShowOptions(DialogOption[] options)
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
_currentView = _normalBox;
|
||||
// 目前只有正常Box可以展示选项
|
||||
// 处理其他泡泡
|
||||
ProcessOtherBox();
|
||||
|
||||
// 展示选项
|
||||
_normalBox.ShowOptions(options);
|
||||
}
|
||||
|
||||
public void HideDialog()
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
// 隐藏整个对话组件
|
||||
_dialogViews?.ForEach(item => item.HideDialog());
|
||||
}
|
||||
|
||||
public bool TrySkipLine(bool isForceSkip = false)
|
||||
{
|
||||
return _currentView == null || _currentView.TrySkipLine(isForceSkip);
|
||||
}
|
||||
|
||||
public void NextStep()
|
||||
{
|
||||
_currentView.NextStep();
|
||||
}
|
||||
|
||||
private IDialogView SelectBubbleByCharacter(CharacterVo characterVo)
|
||||
{
|
||||
var targetBubble = characterVo.role switch
|
||||
{
|
||||
ActorRole.Player => _playerBubble,
|
||||
ActorRole.MainActor => _actorBubble,
|
||||
ActorRole.Aside => _normalBox,
|
||||
_ => _normalBox
|
||||
};
|
||||
|
||||
foreach (var bubble in _dialogViews.Where(bubble => bubble != targetBubble))
|
||||
{
|
||||
bubble.HideDialog();
|
||||
}
|
||||
|
||||
return targetBubble;
|
||||
}
|
||||
|
||||
private void ProcessOtherBox()
|
||||
{
|
||||
if (_currentView == _actorBubble || _currentView == _playerBubble)
|
||||
{
|
||||
_normalBox.HideDialog();
|
||||
}
|
||||
|
||||
if (_currentView == _normalBox)
|
||||
{
|
||||
_actorBubble.HideDialog();
|
||||
_playerBubble.HideDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class DialogCanvasManager : Singleton<DialogCanvasManager>, IDialogView
|
||||
{
|
||||
private const string QuickTalk = "Quick Talk";
|
||||
|
||||
#region 引用
|
||||
|
||||
private readonly Dictionary<DialogViewType, IDialogView> _dialogViewDict = new();
|
||||
private IDialogView _curView;
|
||||
private IDialogView _lastShowView;
|
||||
private GameObject _quickTalkButton;
|
||||
private IDialogView _defaultView;
|
||||
private IDialogView _showingView;
|
||||
|
||||
private IActionController _autoNext;
|
||||
private IActionController _block;
|
||||
private IActionController _hideDialog;
|
||||
|
||||
public TMP_Text widthEstimation;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 参数
|
||||
|
||||
[SerializeField] private float blockDuration;
|
||||
[SerializeField] private float autoSkipDelay;
|
||||
[SerializeField] private float autoHideDuration;
|
||||
|
||||
private bool _isNextAutoSkip;
|
||||
private bool _isNextBlocked;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -25,25 +39,27 @@ namespace AibisDream
|
||||
|
||||
public override void OnSingletonInit()
|
||||
{
|
||||
InitQuickTalkButton();
|
||||
InitEvent();
|
||||
|
||||
// 默认对话框直接加载
|
||||
var box = transform.Find("Dialog Box").GetComponent<IDialogView>();
|
||||
RegisterDialogView(DialogViewType.OldBox, box);
|
||||
var centerText = transform.Find("Center Text").GetComponent<IDialogView>();
|
||||
RegisterDialogView(DialogViewType.CenterText, centerText);
|
||||
var centerOption = transform.Find("Center Option").GetComponent<IDialogView>();
|
||||
RegisterDialogView(DialogViewType.CenterOption, centerOption);
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_curView = _dialogViewDict[DialogViewType.OldBox];
|
||||
// 默认对话框直接加载
|
||||
// var box = transform.Find("Dialog Box").GetComponent<IDialogView>();
|
||||
// RegisterDialogView(DialogViewType.OldBox, box);
|
||||
var centerText = transform.Find("Center Text").GetComponent<IDialogView>();
|
||||
RegisterDialogView(DialogViewType.CenterText, centerText);
|
||||
var centerOption = transform.Find("Center Option").GetComponent<IDialogView>();
|
||||
RegisterDialogView(DialogViewType.CenterOption, centerOption);
|
||||
var bubbleGroup = transform.Find("Bubble Group").GetComponent<IDialogView>();
|
||||
RegisterDialogView(DialogViewType.StandardBubble, bubbleGroup);
|
||||
|
||||
_defaultView = _dialogViewDict[DialogViewType.StandardBubble];
|
||||
}
|
||||
|
||||
private void InitEvent()
|
||||
{
|
||||
// 记录相关
|
||||
_rmvList.Add(
|
||||
EnumEventSystem.Global.Register<DialogEventEnum, DialogText>(DialogEventEnum.LineStart,
|
||||
AddDialogueLine));
|
||||
@@ -51,6 +67,45 @@ namespace AibisDream
|
||||
EnumEventSystem.Global.Register<DialogEventEnum, DialogText>(DialogEventEnum.OptionSelected,
|
||||
AddDialogueLine));
|
||||
_rmvList.Add(EnumEventSystem.Global.Register(EventEnum.NextYarn, CleanDialogHistory));
|
||||
|
||||
// 文本显示事件
|
||||
_rmvList.Add(EnumEventSystem.Global.Register(DialogEventEnum.LineShown, OnTextShown));
|
||||
_rmvList.Add(EnumEventSystem.Global.Register(DialogEventEnum.LineEnd, OnTextEnd));
|
||||
}
|
||||
|
||||
private void OnTextShown()
|
||||
{
|
||||
// 阻塞部分
|
||||
_isNextBlocked = true;
|
||||
_block = ActionKit.Delay(blockDuration, () =>
|
||||
{
|
||||
_isNextBlocked = false;
|
||||
_block = null;
|
||||
}
|
||||
).Start(this);
|
||||
|
||||
// 自动跳过部分
|
||||
if (DialogController.Instance.quickRunMode) return;
|
||||
|
||||
if (DialogController.Instance.autoRunMode || _isNextAutoSkip)
|
||||
{
|
||||
_autoNext = ActionKit.Delay(autoSkipDelay, () =>
|
||||
{
|
||||
_autoNext = null;
|
||||
NextStep();
|
||||
}).Start(this);
|
||||
_isNextAutoSkip = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTextEnd()
|
||||
{
|
||||
_block?.Deinit();
|
||||
_block = null;
|
||||
_isNextBlocked = false;
|
||||
|
||||
_autoNext?.Deinit();
|
||||
_autoNext = null;
|
||||
}
|
||||
|
||||
public override void OnSingletonDestroy()
|
||||
@@ -73,29 +128,8 @@ namespace AibisDream
|
||||
|
||||
#endregion
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.Q) && Input.GetKey(KeyCode.P))
|
||||
{
|
||||
_quickTalkButton.SetActive(!_quickTalkButton.activeSelf);
|
||||
}
|
||||
}
|
||||
|
||||
#region 对话框相关处理
|
||||
|
||||
private void InitQuickTalkButton()
|
||||
{
|
||||
_quickTalkButton = transform.Find(QuickTalk).gameObject;
|
||||
_quickTalkButton.GetComponent<Button>().onClick.AddListener(SwitchQuickTalkMode);
|
||||
}
|
||||
|
||||
private void SwitchQuickTalkMode()
|
||||
{
|
||||
DialogController.Instance.quickRunMode = !DialogController.Instance.quickRunMode;
|
||||
_quickTalkButton.GetComponent<Image>().color =
|
||||
DialogController.Instance.quickRunMode ? Color.gray : Color.white;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册对话框
|
||||
/// </summary>
|
||||
@@ -119,30 +153,43 @@ namespace AibisDream
|
||||
|
||||
#region 对话框接口
|
||||
|
||||
public void ShowLine(string dialogLine, CharacterVo character, Action nextStep, bool isAutoSkip = false)
|
||||
public void ShowLine(string dialogLine, CharacterVo character, Action nextStep, string lineId,
|
||||
bool isAutoSkip = false)
|
||||
{
|
||||
_isNextAutoSkip = isAutoSkip;
|
||||
// 根据DialogViewType确定在哪个对话框显示
|
||||
if (character.dialogViewType == DialogViewType.Default)
|
||||
{
|
||||
if (_lastShowView != _curView)
|
||||
if (_showingView != _defaultView)
|
||||
{
|
||||
_lastShowView?.HideDialog();
|
||||
_showingView?.HideDialog();
|
||||
}
|
||||
else
|
||||
{
|
||||
_hideDialog?.Deinit();
|
||||
_hideDialog = null;
|
||||
}
|
||||
|
||||
_lastShowView = _curView;
|
||||
// 没有指定对话框,就用当前激活的
|
||||
_curView.ShowLine(dialogLine, character, nextStep, isAutoSkip);
|
||||
_showingView = _defaultView;
|
||||
|
||||
_showingView.ShowLine(dialogLine, character, nextStep, lineId, isAutoSkip);
|
||||
}
|
||||
else if (_dialogViewDict.TryGetValue(character.dialogViewType, out var view))
|
||||
{
|
||||
if (_lastShowView != view)
|
||||
if (_showingView != view)
|
||||
{
|
||||
_lastShowView?.HideDialog();
|
||||
_showingView?.HideDialog();
|
||||
}
|
||||
else
|
||||
{
|
||||
_hideDialog?.Deinit();
|
||||
_hideDialog = null;
|
||||
}
|
||||
|
||||
_lastShowView = view;
|
||||
_showingView = view;
|
||||
|
||||
// 对于指定了对话框的,就在指定对话框显示
|
||||
view.ShowLine(dialogLine, character, nextStep, isAutoSkip);
|
||||
view.ShowLine(dialogLine, character, nextStep, lineId, isAutoSkip);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -156,37 +203,72 @@ namespace AibisDream
|
||||
// 根据状况选择不同的选择框
|
||||
if (viewType == DialogViewType.Default)
|
||||
{
|
||||
_dialogViewDict[DialogViewType.OldBox].ShowOptions(dialogueOptions);
|
||||
if (_showingView != _defaultView)
|
||||
{
|
||||
_showingView?.HideDialog();
|
||||
}
|
||||
else
|
||||
{
|
||||
_hideDialog?.Deinit();
|
||||
_hideDialog = null;
|
||||
}
|
||||
_showingView = _defaultView;
|
||||
|
||||
_showingView.ShowOptions(dialogueOptions);
|
||||
}
|
||||
else
|
||||
else if(_dialogViewDict.TryGetValue(viewType, out var view))
|
||||
{
|
||||
_dialogViewDict[viewType].ShowOptions(dialogueOptions);
|
||||
if (_showingView != view)
|
||||
{
|
||||
_showingView?.HideDialog();
|
||||
}
|
||||
else
|
||||
{
|
||||
_hideDialog?.Deinit();
|
||||
_hideDialog = null;
|
||||
}
|
||||
_showingView = view;
|
||||
_showingView.ShowOptions(dialogueOptions);
|
||||
}
|
||||
}
|
||||
|
||||
public void HideDialog()
|
||||
{
|
||||
_curView.HideDialog();
|
||||
_showingView?.HideDialog();
|
||||
}
|
||||
|
||||
public bool TrySkipLine(bool isForceSkip = false)
|
||||
{
|
||||
return _lastShowView != null && _lastShowView.TrySkipLine(isForceSkip);
|
||||
if (_isNextBlocked)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return _showingView != null && _showingView.TrySkipLine(isForceSkip);
|
||||
}
|
||||
|
||||
public void NextStep()
|
||||
{
|
||||
_lastShowView?.NextStep();
|
||||
var view = _showingView;
|
||||
_hideDialog = ActionKit.Delay(autoHideDuration, () =>
|
||||
{
|
||||
_hideDialog = null;
|
||||
view.HideDialog();
|
||||
}).Start(this);
|
||||
_showingView?.NextStep();
|
||||
}
|
||||
|
||||
public void OnLocalizationChanged(string value)
|
||||
{
|
||||
}
|
||||
|
||||
public void SwitchDialogView(DialogViewType dialogViewType)
|
||||
{
|
||||
if (_dialogViewDict.TryGetValue(dialogViewType, out var view))
|
||||
{
|
||||
_curView.HideDialog();
|
||||
_lastShowView?.HideDialog();
|
||||
_lastShowView = null;
|
||||
_curView = view;
|
||||
_defaultView?.HideDialog();
|
||||
_showingView?.HideDialog();
|
||||
_defaultView = view;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -198,7 +280,7 @@ namespace AibisDream
|
||||
|
||||
#region 对话回顾
|
||||
|
||||
[Header("对话回顾组件")] [SerializeField] private TMP_Text logText;
|
||||
private StringBuilder DialogRecord => UIManager.Instance.GetPanel<RecordPanel>().recordStr;
|
||||
|
||||
/// <summary>
|
||||
/// 记录对话
|
||||
@@ -206,25 +288,33 @@ namespace AibisDream
|
||||
/// <param name="line">对话</param>
|
||||
private void AddDialogueLine(DialogText line)
|
||||
{
|
||||
logText.text += line.GetDialogText();
|
||||
DialogRecord.Append(line.GetDialogText());
|
||||
}
|
||||
|
||||
private void CleanDialogHistory()
|
||||
{
|
||||
logText.text = null;
|
||||
DialogRecord.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换对话回顾展示状态
|
||||
/// </summary>
|
||||
public void ControlDialogHistory()
|
||||
public void CloseCanvas()
|
||||
{
|
||||
var logObj = transform.Find("Dialogue Log").gameObject;
|
||||
logObj.SetActive(!logObj.activeSelf);
|
||||
EnumEventSystem.Global.Send(EventEnum.Menu, logObj.activeSelf);
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
public void OpenCanvas()
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public float EstimateTextWidth(string text, float fontSize)
|
||||
{
|
||||
widthEstimation.fontSize = fontSize;
|
||||
widthEstimation.text = text;
|
||||
widthEstimation.ForceMeshUpdate();
|
||||
return widthEstimation.preferredWidth;
|
||||
}
|
||||
}
|
||||
|
||||
public enum DialogViewType
|
||||
@@ -234,6 +324,8 @@ namespace AibisDream
|
||||
OldBox,
|
||||
Screen,
|
||||
CenterText,
|
||||
CenterOption
|
||||
CenterOption,
|
||||
Task,
|
||||
StandardBubble
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ namespace AibisDream
|
||||
{
|
||||
public interface IDialogView
|
||||
{
|
||||
public void ShowLine(string dialogLine, CharacterVo character, Action nextStep, bool isAutoSkip = false);
|
||||
public void ShowLine(string dialogLine, CharacterVo character, Action nextStep, string lineId ,bool isAutoSkip = false);
|
||||
|
||||
public void ShowOptions(DialogOption[] dialogueOptions);
|
||||
|
||||
@@ -19,6 +19,8 @@ namespace AibisDream
|
||||
public bool TrySkipLine(bool isForceSkip = false);
|
||||
|
||||
public void NextStep();
|
||||
|
||||
public void OnLocalizationChanged(string value);
|
||||
}
|
||||
|
||||
public struct DialogOption
|
||||
@@ -62,4 +64,12 @@ namespace AibisDream
|
||||
.Select(item => new DialogOption { _option = item, _onOptionSelected = onOptionSelected }).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct TextParam
|
||||
{
|
||||
public float charSpace;
|
||||
public float lineSpace;
|
||||
public int maxCharNum;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using AibisDream.Utility;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Localization.Components;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
[RequireComponent(typeof(TMP_Text))]
|
||||
[RequireComponent(typeof(LocalizeStringEvent))]
|
||||
public class LocalizedText : MonoBehaviour
|
||||
{
|
||||
private TMP_Text _text;
|
||||
private LocalizeStringEvent _localizeEvent;
|
||||
|
||||
public bool isLineText = true;
|
||||
public string template = "{0}";
|
||||
|
||||
private void OnUpdateString(string text)
|
||||
{
|
||||
if (isLineText)
|
||||
{
|
||||
text = CommonUtil.RemoveName(text);
|
||||
}
|
||||
|
||||
_text.text = string.Format(template, text);
|
||||
}
|
||||
|
||||
public void SetLocalizedText(string table, string entry)
|
||||
{
|
||||
_text = GetComponent<TMP_Text>();
|
||||
_localizeEvent = GetComponent<LocalizeStringEvent>();
|
||||
|
||||
_localizeEvent.OnUpdateString.AddListener(OnUpdateString);
|
||||
|
||||
_localizeEvent.SetTable(table);
|
||||
_localizeEvent.SetEntry(entry);
|
||||
}
|
||||
|
||||
public void SetVariables()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c9c71a7e9c9455d955ff20b5db823a1
|
||||
timeCreated: 1746702086
|
||||
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.Utility;
|
||||
using Febucci.UI;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class TempTextBox : MonoBehaviour, IDialogView
|
||||
{
|
||||
#region 索引
|
||||
|
||||
private TypewriterByCharacter _dialogText;
|
||||
private Image _panel;
|
||||
private Action _nextStep;
|
||||
|
||||
#endregion
|
||||
|
||||
private bool _skipLineDelay;
|
||||
|
||||
private TextParam _textParam;
|
||||
|
||||
[Header("跳过阻塞延迟时间/ms")] [SerializeField]
|
||||
private int delayTime = 300;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
InitComponents();
|
||||
AddSomeListener();
|
||||
}
|
||||
|
||||
private void InitComponents()
|
||||
{
|
||||
_dialogText = transform.Find("Dialog Text")?.gameObject.GetComponent<TypewriterByCharacter>();
|
||||
}
|
||||
|
||||
private void AddSomeListener()
|
||||
{
|
||||
// 对话显示后有一段延时不可跳过
|
||||
_dialogText.onTypewriterStart.AddListener(() =>
|
||||
{
|
||||
_skipLineDelay = true;
|
||||
_ = CommonUtil.Delay(delayTime, () => { _skipLineDelay = false; });
|
||||
});
|
||||
}
|
||||
|
||||
public void ShowLine(string dialogLine, CharacterVo character, Action nextStep, string lineId, bool isAutoSkip = false)
|
||||
{
|
||||
// 把原有对话清掉
|
||||
ClearBox();
|
||||
_dialogText.gameObject.SetActive(true);
|
||||
|
||||
// 显示内容
|
||||
_dialogText.ShowText(dialogLine);
|
||||
|
||||
// 自动跳过的回调很难独立Remove,只能全部清空再注册,下策
|
||||
_dialogText.onTextShowed.RemoveAllListeners();
|
||||
_dialogText.onTextShowed.AddListener(() => EnumEventSystem.Global.Send(DialogEventEnum.LineShown));
|
||||
|
||||
_nextStep = nextStep;
|
||||
}
|
||||
|
||||
public void ShowOptions(DialogOption[] dialogueOptions)
|
||||
{
|
||||
// 不能出选项
|
||||
}
|
||||
|
||||
public void HideDialog()
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
public bool TrySkipLine(bool isForceSkip)
|
||||
{
|
||||
if (_skipLineDelay && !isForceSkip)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_dialogText.isShowingText)
|
||||
{
|
||||
_dialogText.SkipTypewriter();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void NextStep()
|
||||
{
|
||||
if (_nextStep == null) return;
|
||||
|
||||
ClearBox();
|
||||
EnumEventSystem.Global.Send(DialogEventEnum.LineEnd);
|
||||
var next = _nextStep;
|
||||
_nextStep = null;
|
||||
next.Invoke();
|
||||
}
|
||||
|
||||
public void OnLocalizationChanged(string value)
|
||||
{
|
||||
_textParam = JsonUtil.ReadBeanByText<TextParam>(value);
|
||||
var tmp = _dialogText.GetComponent<TMP_Text>();
|
||||
// 修改对话文本
|
||||
tmp.characterSpacing = _textParam.charSpace;
|
||||
}
|
||||
|
||||
private void ClearBox()
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
_dialogText?.TextAnimator.SetText("");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3bee0b70ea2946b8a6c59a4bceb04342
|
||||
timeCreated: 1747292399
|
||||
@@ -2,7 +2,10 @@ using System;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.Utility;
|
||||
using Febucci.UI.Core;
|
||||
using TMPro;
|
||||
using Unity.VisualScripting;
|
||||
using UnityEngine;
|
||||
using Object = System.Object;
|
||||
|
||||
namespace AibisDream.Framework
|
||||
{
|
||||
@@ -48,5 +51,14 @@ namespace AibisDream.Framework
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
public void OnLocalizationChanged(string value)
|
||||
{
|
||||
var textParam = JsonUtil.ReadBeanByText<TextParam>(value);
|
||||
var tmp = GetComponentInChildren<TMP_Text>();
|
||||
|
||||
tmp.characterSpacing = textParam.charSpace;
|
||||
maxCharNum = textParam.maxCharNum;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.Utility;
|
||||
using Unity.VisualScripting;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream
|
||||
@@ -14,15 +14,10 @@ namespace AibisDream
|
||||
|
||||
#region 一些索引
|
||||
|
||||
private bool _skipLineDelay;
|
||||
private Action _nextStep;
|
||||
private bool _isAutoSkip;
|
||||
|
||||
private TextBubble _curBubble;
|
||||
|
||||
[Header("跳过阻塞延迟时间/ms")] [SerializeField]
|
||||
private int delayTime = 300;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 初始化
|
||||
@@ -54,16 +49,13 @@ namespace AibisDream
|
||||
|
||||
#endregion
|
||||
|
||||
public void ShowLine(string dialogLine, CharacterVo character, Action nextStep, bool isAutoSkip = false)
|
||||
public void ShowLine(string dialogLine, CharacterVo character, Action nextStep, string lineId, bool isAutoSkip = false)
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
|
||||
if (TryGetBubble(character.role, character.bubbleIdx ,out var bubble))
|
||||
{
|
||||
bubble.ShowLine(dialogLine, OnTextShowed);
|
||||
// 设置延时
|
||||
_skipLineDelay = true;
|
||||
_ = CommonUtil.Delay(delayTime, () => { _skipLineDelay = false; });
|
||||
// 将非选定的Bubble隐藏
|
||||
HideDialog(bubble);
|
||||
}
|
||||
@@ -75,7 +67,6 @@ namespace AibisDream
|
||||
|
||||
// 保留信息
|
||||
_nextStep = nextStep;
|
||||
_isAutoSkip = isAutoSkip;
|
||||
_curBubble = bubble;
|
||||
}
|
||||
|
||||
@@ -88,7 +79,10 @@ namespace AibisDream
|
||||
{
|
||||
foreach (var textBubble in _bubbleDict.Values.SelectMany(inner => inner))
|
||||
{
|
||||
textBubble.Hide();
|
||||
if (!textBubble.IsDestroyed())
|
||||
{
|
||||
textBubble.Hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,18 +92,13 @@ namespace AibisDream
|
||||
{
|
||||
if (activeBubble != textBubble)
|
||||
{
|
||||
textBubble.Hide();
|
||||
textBubble?.Hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TrySkipLine(bool isForceSkip)
|
||||
{
|
||||
if (_skipLineDelay && !isForceSkip)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_curBubble.IsShowingText)
|
||||
{
|
||||
_curBubble.SkipLine();
|
||||
@@ -132,17 +121,14 @@ namespace AibisDream
|
||||
next?.Invoke();
|
||||
}
|
||||
|
||||
public void OnLocalizationChanged(string value)
|
||||
{
|
||||
}
|
||||
|
||||
private void OnTextShowed()
|
||||
{
|
||||
// 触发事件
|
||||
EnumEventSystem.Global.Send(DialogEventEnum.LineShown);
|
||||
|
||||
// 执行自动跳过结局
|
||||
if (_isAutoSkip)
|
||||
{
|
||||
_ = CommonUtil.Delay(200, NextStep);
|
||||
_isAutoSkip = false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetBubble(ActorRole role, int idx, out TextBubble textBubble)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5010337b422cfa74ea2d2939e762693e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using AibisDream.Utility;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.Localization.Components;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
/// <summary>
|
||||
/// 表单项父类
|
||||
/// </summary>
|
||||
public abstract class AbsFormItem : MonoBehaviour
|
||||
{
|
||||
#region 数据部分
|
||||
|
||||
public string prop;
|
||||
public string label;
|
||||
public abstract FieldType FieldType { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 索引部分
|
||||
|
||||
private LocalizeStringEvent _labelRef;
|
||||
public UnityEvent<string, string> OnValueChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
InitItem();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化FormItem
|
||||
/// </summary>
|
||||
private void InitItem()
|
||||
{
|
||||
// label设置
|
||||
_labelRef = transform.Find("Label").GetComponent<LocalizeStringEvent>();
|
||||
_labelRef.SetTable(ConstRef.UITextTable);
|
||||
_labelRef.SetEntry(label);
|
||||
// field设置
|
||||
InitField();
|
||||
}
|
||||
|
||||
protected abstract void InitField();
|
||||
public abstract void SetValue(string value);
|
||||
}
|
||||
|
||||
public enum FieldType
|
||||
{
|
||||
Input,
|
||||
Slider,
|
||||
Select,
|
||||
Dropdown
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct UIFormOption
|
||||
{
|
||||
public string prop;
|
||||
public string label;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 429e0ca7a1096a1478e50195b88539b8
|
||||
guid: dd06484c1f627d249ad5a16558c9cdb9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -0,0 +1,32 @@
|
||||
using TMPro;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class DropdownItem : AbsFormItem
|
||||
{
|
||||
public override FieldType FieldType => FieldType.Dropdown;
|
||||
|
||||
private TMP_Dropdown _dropdown;
|
||||
|
||||
protected override void InitField()
|
||||
{
|
||||
// 获取索引
|
||||
_dropdown = GetComponentInChildren<TMP_Dropdown>(true);
|
||||
// 注册事件
|
||||
_dropdown.onValueChanged.AddListener(OnSelectChanged);
|
||||
}
|
||||
|
||||
public override void SetValue(string value)
|
||||
{
|
||||
var target = _dropdown.options.FindIndex(item => item.text == value);
|
||||
_dropdown.value = target;
|
||||
}
|
||||
|
||||
private void OnSelectChanged(int value)
|
||||
{
|
||||
// 序号转字符串
|
||||
var strValue = _dropdown.options[value].text;
|
||||
OnValueChanged?.Invoke(prop, strValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 39f0dc8bcf2641458cf3f8726add30f9
|
||||
timeCreated: 1744702543
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace AibisDream
|
||||
{
|
||||
/// <summary>
|
||||
/// 表单接口。
|
||||
/// 表单Root主要承担数据逻辑功能
|
||||
/// 1. 修改表单数据时对应改动
|
||||
/// 2. 从配置文件读取表单数据对应改动
|
||||
/// 3. 表单初始化
|
||||
/// </summary>
|
||||
public interface IUIForm
|
||||
{
|
||||
public void OnItemChanged(string propName, string value);
|
||||
public void UpdateForm();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e80043371ab4923944ceda3823424b9
|
||||
timeCreated: 1744619745
|
||||
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using AibisDream.Utility;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class LanguageItem : AbsFormItem
|
||||
{
|
||||
public override FieldType FieldType => FieldType.Select;
|
||||
|
||||
#region 索引
|
||||
|
||||
private TMP_Text _showText;
|
||||
private Button _leftBtn;
|
||||
private Button _rightBtn;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 数据
|
||||
|
||||
[Header("可选项")]
|
||||
public UIFormOption[] options;
|
||||
private int _curIdx;
|
||||
|
||||
#endregion
|
||||
|
||||
protected override void InitField()
|
||||
{
|
||||
var field = transform.Find("Field");
|
||||
_showText = field.Find("ShowText").GetComponent<TMP_Text>();
|
||||
_leftBtn = field.Find("Left Btn").GetComponent<Button>();
|
||||
_rightBtn = field.Find("Right Btn").GetComponent<Button>();
|
||||
|
||||
_leftBtn.onClick.AddListener(LeftRoll);
|
||||
_rightBtn.onClick.AddListener(RightRoll);
|
||||
}
|
||||
|
||||
public override void SetValue(string value)
|
||||
{
|
||||
var idx = Array.FindIndex(options, item => item.prop == value);
|
||||
if (idx < 0)
|
||||
{
|
||||
Debug.LogError($"{prop} setting value error");
|
||||
}
|
||||
|
||||
_curIdx = idx;
|
||||
_showText.text = options[idx].label;
|
||||
}
|
||||
|
||||
private void LeftRoll()
|
||||
{
|
||||
_curIdx = CommonUtil.LoopStep(_curIdx, 0, options.Length - 1);
|
||||
_showText.text = options[_curIdx].label;
|
||||
|
||||
OnValueChanged?.Invoke(prop, options[_curIdx].prop);
|
||||
}
|
||||
|
||||
private void RightRoll()
|
||||
{
|
||||
_curIdx = CommonUtil.LoopStep(_curIdx, 0, options.Length - 1, -1);
|
||||
_showText.text = options[_curIdx].label;
|
||||
|
||||
OnValueChanged?.Invoke(prop, options[_curIdx].prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae7f07f0b7da4a119b731f409ebbfeda
|
||||
timeCreated: 1745478466
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using AibisDream.Utility;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Localization.Components;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class SelectItem : AbsFormItem
|
||||
{
|
||||
public override FieldType FieldType => FieldType.Select;
|
||||
|
||||
#region 索引
|
||||
|
||||
private LocalizeStringEvent _showText;
|
||||
private Button _leftBtn;
|
||||
private Button _rightBtn;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region 数据
|
||||
|
||||
[Header("可选项")]
|
||||
public UIFormOption[] options;
|
||||
private int _curIdx;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
protected override void InitField()
|
||||
{
|
||||
var field = transform.Find("Field");
|
||||
_showText = field.Find("ShowText").GetComponent<LocalizeStringEvent>();
|
||||
_leftBtn = field.Find("Left Btn").GetComponent<Button>();
|
||||
_rightBtn = field.Find("Right Btn").GetComponent<Button>();
|
||||
|
||||
_leftBtn.onClick.AddListener(LeftRoll);
|
||||
_rightBtn.onClick.AddListener(RightRoll);
|
||||
}
|
||||
|
||||
public override void SetValue(string value)
|
||||
{
|
||||
var idx = Array.FindIndex(options, item => item.prop == value);
|
||||
if (idx < 0)
|
||||
{
|
||||
Debug.LogError($"{prop} setting value error");
|
||||
}
|
||||
|
||||
_curIdx = idx;
|
||||
_showText.SetEntry(options[idx].label);
|
||||
}
|
||||
|
||||
private void LeftRoll()
|
||||
{
|
||||
_curIdx = CommonUtil.LoopStep(_curIdx, 0, options.Length - 1);
|
||||
_showText.SetEntry(options[_curIdx].label);
|
||||
|
||||
OnValueChanged?.Invoke(prop, options[_curIdx].prop);
|
||||
}
|
||||
|
||||
private void RightRoll()
|
||||
{
|
||||
_curIdx = CommonUtil.LoopStep(_curIdx, 0, options.Length - 1, -1);
|
||||
_showText.SetEntry(options[_curIdx].label);
|
||||
|
||||
OnValueChanged?.Invoke(prop, options[_curIdx].prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ef1de16c90b849ef86a4360768fc55f6
|
||||
timeCreated: 1745473121
|
||||
@@ -0,0 +1,51 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class SettingForm : MonoBehaviour, IUIForm
|
||||
{
|
||||
#region 索引
|
||||
|
||||
private AbsFormItem[] _formItems;
|
||||
|
||||
#endregion
|
||||
|
||||
public void Init()
|
||||
{
|
||||
// 获取索引
|
||||
_formItems = GetComponentsInChildren<AbsFormItem>();
|
||||
UpdateForm();
|
||||
// 绑定变化
|
||||
foreach (var formItem in _formItems)
|
||||
{
|
||||
formItem.OnValueChanged.AddListener(OnItemChanged);
|
||||
}
|
||||
}
|
||||
|
||||
public void UnInit()
|
||||
{
|
||||
// 绑定变化
|
||||
foreach (var formItem in _formItems)
|
||||
{
|
||||
formItem.OnValueChanged.RemoveAllListeners();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnItemChanged(string propName, string value)
|
||||
{
|
||||
SettingLoader.Instance.Write(propName, value);
|
||||
}
|
||||
|
||||
public void UpdateForm()
|
||||
{
|
||||
var settingDict = SettingLoader.Instance.ReadAll();
|
||||
foreach (var formItem in _formItems)
|
||||
{
|
||||
if (settingDict.TryGetValue(formItem.prop, out var value))
|
||||
{
|
||||
formItem.SetValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6564f6671e32475c981a8c3c1ca63ffd
|
||||
timeCreated: 1744622926
|
||||
@@ -0,0 +1,42 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class SliderItem : AbsFormItem
|
||||
{
|
||||
public override FieldType FieldType => FieldType.Slider;
|
||||
|
||||
#region 索引
|
||||
|
||||
private Slider _slider;
|
||||
private TMP_Text _sliderText;
|
||||
|
||||
#endregion
|
||||
|
||||
protected override void InitField()
|
||||
{
|
||||
// 初始化索引
|
||||
_slider = GetComponentInChildren<Slider>();
|
||||
_sliderText = _slider.GetComponentInChildren<TMP_Text>(true);
|
||||
// 绑定事件
|
||||
_slider.onValueChanged.AddListener(OnSliderChanged);
|
||||
}
|
||||
|
||||
private void OnSliderChanged(float value)
|
||||
{
|
||||
// 更新数字显示
|
||||
_sliderText.text = Mathf.RoundToInt(value * 100).ToString();
|
||||
// 触发修改事件
|
||||
OnValueChanged.Invoke(prop, value.ToString("F2"));
|
||||
}
|
||||
|
||||
public override void SetValue(string value)
|
||||
{
|
||||
var floatValue = float.Parse(value);
|
||||
_slider.value = floatValue;
|
||||
_sliderText.text = Mathf.RoundToInt(floatValue * 100).ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6cb985d5e95047f88ddec878de7972a2
|
||||
timeCreated: 1744617521
|
||||
@@ -1,200 +0,0 @@
|
||||
using System.Collections;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class MainUIController : Singleton<MainUIController>
|
||||
{
|
||||
private const string TVScene = "TV Scene";
|
||||
private const string BlackImage = "Fade Image";
|
||||
private const string NewsImage = "News";
|
||||
private const string ObjPanel = "Show Obj";
|
||||
|
||||
#region ShowObj参数
|
||||
|
||||
[Header("Obj尺寸参数")] public float objWidth;
|
||||
public float objHeight;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 主场景图片
|
||||
|
||||
[SerializeField] private GameObject mainUIPanel;
|
||||
|
||||
private FadeImage _tvScene;
|
||||
private FadeImage _blackImage;
|
||||
private FadeImage _news;
|
||||
private GameObject _objPanel;
|
||||
private Image _objImage;
|
||||
private Image _fullScreen;
|
||||
|
||||
#endregion
|
||||
|
||||
public override void OnSingletonInit()
|
||||
{
|
||||
InitChildObj();
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
EnumEventSystem.Global.Register(EventEnum.SceneUnload, OnSceneUnload);
|
||||
}
|
||||
|
||||
private void OnSceneUnload()
|
||||
{
|
||||
StartCoroutine(HideFullScreen());
|
||||
StartCoroutine(HideObj());
|
||||
FadeOut(0.1f);
|
||||
}
|
||||
|
||||
#region 基本淡入淡出
|
||||
|
||||
public void FadeIn(float duration)
|
||||
{
|
||||
_blackImage.FadeIn(duration);
|
||||
}
|
||||
|
||||
public IEnumerator FadeInSync(float duration)
|
||||
{
|
||||
return _blackImage.FadeInSync(duration);
|
||||
}
|
||||
|
||||
public void FadeOut(float duration)
|
||||
{
|
||||
_blackImage.FadeOut(duration);
|
||||
}
|
||||
|
||||
public IEnumerator FadeOutSync(float duration)
|
||||
{
|
||||
return _blackImage.FadeOutSync(duration);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TV场景淡入淡出
|
||||
|
||||
public void TVFadeIn(float duration)
|
||||
{
|
||||
_tvScene.FadeIn(duration);
|
||||
_news.FadeIn(duration);
|
||||
}
|
||||
|
||||
public IEnumerator TVFadeInSync(float duration)
|
||||
{
|
||||
_news.FadeIn(duration);
|
||||
return _tvScene.FadeInSync(duration);
|
||||
}
|
||||
|
||||
public void TVFadeOut(float duration)
|
||||
{
|
||||
_tvScene.FadeOut(duration);
|
||||
_news.FadeOut(duration);
|
||||
}
|
||||
|
||||
public IEnumerator TVFadeOutSync(float duration)
|
||||
{
|
||||
_news.FadeOut(duration);
|
||||
return _tvScene.FadeOutSync(duration);
|
||||
}
|
||||
|
||||
public void SwitchTV(string newsName)
|
||||
{
|
||||
_news.SwitchImage($"Art/Background/{newsName}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏整个UI
|
||||
/// </summary>
|
||||
public void HideMainUI()
|
||||
{
|
||||
mainUIPanel.SetActive(false);
|
||||
}
|
||||
|
||||
public void ShowMainUI()
|
||||
{
|
||||
mainUIPanel.SetActive(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示物体
|
||||
/// </summary>
|
||||
/// <returns>协程</returns>
|
||||
public IEnumerator ShowObj(string picName)
|
||||
{
|
||||
_objPanel.gameObject.SetActive(true);
|
||||
|
||||
// 切换图片
|
||||
var newSprite = Resources.Load<Sprite>($"Art/Obj/{picName}");
|
||||
// 获取图片的长宽比
|
||||
var imageAspect = (float)newSprite.texture.width / newSprite.texture.height;
|
||||
|
||||
// 计算新的尺寸
|
||||
if (imageAspect > 1) // 宽图
|
||||
{
|
||||
var newHeight = objWidth / imageAspect;
|
||||
_objImage.rectTransform.sizeDelta = new Vector2(objWidth, newHeight);
|
||||
}
|
||||
else // 高图
|
||||
{
|
||||
var newWidth = objHeight * imageAspect;
|
||||
_objImage.rectTransform.sizeDelta = new Vector2(newWidth, objHeight);
|
||||
}
|
||||
|
||||
// 淡入
|
||||
_objImage.sprite = newSprite;
|
||||
_objImage.gameObject.SetActive(true);
|
||||
yield return _objImage.DOBlendableColor(Color.white, 1).WaitForCompletion();
|
||||
}
|
||||
|
||||
public IEnumerator ShowFullScreen(string picName)
|
||||
{
|
||||
_fullScreen.sprite = Resources.Load<Sprite>($"Art/Obj/{picName}");
|
||||
|
||||
// 淡入
|
||||
_fullScreen.gameObject.SetActive(true);
|
||||
yield return _objImage.DOBlendableColor(Color.white, 1).WaitForCompletion();
|
||||
}
|
||||
|
||||
public IEnumerator HideFullScreen()
|
||||
{
|
||||
_fullScreen.gameObject.SetActive(true);
|
||||
yield return _objImage.DOBlendableColor(Color.clear, 1).WaitForCompletion();
|
||||
_fullScreen.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏物体
|
||||
/// </summary>
|
||||
/// <returns>协程</returns>
|
||||
public IEnumerator HideObj()
|
||||
{
|
||||
_objImage.gameObject.SetActive(true);
|
||||
yield return _objImage.DOBlendableColor(Color.clear, 1).WaitForCompletion();
|
||||
_objImage.gameObject.SetActive(false);
|
||||
_objPanel.SetActive(false);
|
||||
}
|
||||
|
||||
private void InitChildObj()
|
||||
{
|
||||
var rawTvScene = transform.GetChild(0).Find(TVScene).gameObject.GetComponent<Image>();
|
||||
_tvScene = new FadeImage(rawTvScene);
|
||||
|
||||
var rawBlackImage = transform.GetChild(0).Find(BlackImage).gameObject.GetComponent<Image>();
|
||||
_blackImage = new FadeImage(rawBlackImage);
|
||||
|
||||
var rawNews = transform.GetChild(0).Find(NewsImage).gameObject.GetComponent<Image>();
|
||||
_news = new FadeImage(rawNews);
|
||||
|
||||
_objPanel = transform.GetChild(0).Find(ObjPanel).gameObject;
|
||||
_objImage = _objPanel.transform.GetChild(0).GetComponent<Image>();
|
||||
|
||||
_fullScreen = transform.GetChild(0).Find("Full Screen Image").GetComponent<Image>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b3c03e9f6ddaadd408fba26d40f93c26
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class DialogPanel : MonoBehaviour, IUIPanel
|
||||
{
|
||||
#region UI面板功能
|
||||
|
||||
public bool IsOpen => gameObject.activeSelf;
|
||||
public bool IsCloseable => false;
|
||||
|
||||
public void Show()
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d43fa24173f81ea4ea102be91896826e
|
||||
guid: 2ecd4df87fde33d4a8944917a863a6af
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -0,0 +1,66 @@
|
||||
using AibisDream.Utility;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class EndPanel : MonoBehaviour, IUIPanel
|
||||
{
|
||||
public bool IsOpen => gameObject.activeSelf;
|
||||
public bool IsCloseable => false;
|
||||
|
||||
#region 打开和关闭
|
||||
|
||||
public void Show()
|
||||
{
|
||||
// GameManager.Instance.PauseGame();
|
||||
gameObject.SetActive(true);
|
||||
RegisterButton();
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
UnRegisterButton();
|
||||
// GameManager.Instance.ContinueGame();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void RegisterButton()
|
||||
{
|
||||
var returnBtn = transform.Find("返回").GetComponent<Button>();
|
||||
returnBtn.onClick.AddListener(BackToMain);
|
||||
var backToMain = transform.Find("问卷").GetComponent<Button>();
|
||||
backToMain.onClick.AddListener(OpenFeedbackLink);
|
||||
var quit = transform.Find("Bug反馈").GetComponent<Button>();
|
||||
quit.onClick.AddListener(OpenBugLink);
|
||||
}
|
||||
|
||||
private void UnRegisterButton()
|
||||
{
|
||||
var returnBtn = transform.Find("返回").GetComponent<Button>();
|
||||
returnBtn.onClick.RemoveAllListeners();
|
||||
var backToMain = transform.Find("问卷").GetComponent<Button>();
|
||||
backToMain.onClick.RemoveAllListeners();
|
||||
var quit = transform.Find("Bug反馈").GetComponent<Button>();
|
||||
quit.onClick.RemoveAllListeners();
|
||||
}
|
||||
|
||||
private void BackToMain()
|
||||
{
|
||||
UIManager.Instance.HidePanel<EndPanel>();
|
||||
GameManager.Instance.QuitGame();
|
||||
}
|
||||
|
||||
private void OpenFeedbackLink()
|
||||
{
|
||||
Application.OpenURL(ConstRef.SurveyURL);
|
||||
}
|
||||
|
||||
private void OpenBugLink()
|
||||
{
|
||||
Application.OpenURL(ConstRef.BugSurveyURL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 102c329e03bc4e4882e2beb365e3ae1b
|
||||
timeCreated: 1745221838
|
||||
@@ -0,0 +1,79 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class MainPanel : MonoBehaviour, IUIPanel
|
||||
{
|
||||
public bool IsOpen => gameObject.activeSelf;
|
||||
public bool IsCloseable => false;
|
||||
|
||||
#region 面板控制
|
||||
|
||||
public void Show()
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
RegisterButton();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Input.GetKey(KeyCode.Q) && Input.GetKeyDown(KeyCode.P))
|
||||
{
|
||||
var obj = transform.Find("Top Bar").Find("Quick Forward").gameObject;
|
||||
var active = obj.activeSelf;
|
||||
obj.SetActive(!active);
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterButton()
|
||||
{
|
||||
// 为按钮注册事件
|
||||
var topBar = transform.Find("Top Bar");
|
||||
topBar.Find("Setting").GetComponent<Button>().onClick.AddListener(OpenSettingPanel);
|
||||
topBar.Find("Record").GetComponent<Button>().onClick.AddListener(OpenRecordPanel);
|
||||
|
||||
// 自动跳过和快进包括状态变换
|
||||
var auto = topBar.Find("Auto Skip").GetComponent<BoolButton>();
|
||||
auto.OnClick.AddListener(AutoSkip);
|
||||
auto.getBool = () => DialogController.Instance.autoRunMode;
|
||||
|
||||
var ffwd = topBar.Find("Quick Forward").GetComponent<BoolButton>();
|
||||
ffwd.OnClick.AddListener(QuickForward);
|
||||
ffwd.getBool = () => DialogController.Instance.quickRunMode;
|
||||
}
|
||||
|
||||
private void AutoSkip()
|
||||
{
|
||||
DialogController.Instance.autoRunMode = !DialogController.Instance.autoRunMode;
|
||||
}
|
||||
|
||||
private void QuickForward()
|
||||
{
|
||||
DialogController.Instance.quickRunMode = !DialogController.Instance.quickRunMode;
|
||||
|
||||
Time.timeScale = DialogController.Instance.quickRunMode ? 8f : 1f;
|
||||
}
|
||||
|
||||
private void OpenSettingPanel()
|
||||
{
|
||||
UIManager.Instance.ShowPanel<SettingPanel>();
|
||||
}
|
||||
|
||||
private void OpenRecordPanel()
|
||||
{
|
||||
UIManager.Instance.ShowPanel<RecordPanel>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4518044cbe67a4647af3110956c37758
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,199 @@
|
||||
using System.Collections;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using DG.Tweening;
|
||||
using RainbowArt.CleanFlatUI;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
/// <summary>
|
||||
/// 主要用来放置淡入淡出,图片展示等演出工具
|
||||
/// </summary>
|
||||
public class PlayToolPanel : MonoBehaviour, IUIPanel
|
||||
{
|
||||
public bool IsOpen => gameObject.activeSelf;
|
||||
public bool IsCloseable => false;
|
||||
|
||||
[Header("Obj尺寸参数")] public float objWidth;
|
||||
public float objHeight;
|
||||
|
||||
#region 索引
|
||||
|
||||
private Image _fadeImage;
|
||||
private GameObject _showObjPanel;
|
||||
private Image _showObjImage;
|
||||
private Image _fullScreenImage;
|
||||
private Image _redFlash;
|
||||
private ModalWindowProgressBar _window;
|
||||
private ModalWindow _warningWindow;
|
||||
|
||||
#endregion
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
InitRefs();
|
||||
EnumEventSystem.Global.Register(GameLoopEnum.GameQuit, HideAll);
|
||||
}
|
||||
|
||||
private void InitRefs()
|
||||
{
|
||||
_fadeImage = transform.Find("Fade Image").GetComponent<Image>();
|
||||
_showObjPanel = transform.Find("Show Obj").gameObject;
|
||||
_showObjImage = _showObjPanel.transform.Find("Obj").GetComponent<Image>();
|
||||
_fullScreenImage = transform.Find("Full Screen Image").GetComponent<Image>();
|
||||
_window = transform.Find("进度条窗口").GetComponent<ModalWindowProgressBar>();
|
||||
_redFlash = transform.Find("Red Flash").GetComponent<Image>();
|
||||
_warningWindow = transform.Find("警告窗口").GetComponent<ModalWindow>();
|
||||
}
|
||||
|
||||
public IEnumerator FadeInAsync(float duration = 1)
|
||||
{
|
||||
return _fadeImage.FadeInAsync(duration);
|
||||
}
|
||||
|
||||
public IEnumerator FadeOutAsync(float duration = 1)
|
||||
{
|
||||
return _fadeImage.FadeOutAsync(duration);
|
||||
}
|
||||
|
||||
public void FadeInSync(float duration = 1)
|
||||
{
|
||||
StartCoroutine(FadeInAsync(duration));
|
||||
}
|
||||
|
||||
public void FadeOutSync(float duration = 1)
|
||||
{
|
||||
StartCoroutine(FadeOutAsync(duration));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示物体
|
||||
/// </summary>
|
||||
/// <returns>协程</returns>
|
||||
public IEnumerator ShowObj(string picName)
|
||||
{
|
||||
_showObjPanel.gameObject.SetActive(true);
|
||||
|
||||
// 切换图片
|
||||
var newSprite = Resources.Load<Sprite>($"Art/Obj/{picName}");
|
||||
// 获取图片的长宽比
|
||||
var imageAspect = (float)newSprite.texture.width / newSprite.texture.height;
|
||||
|
||||
// 计算新的尺寸
|
||||
if (imageAspect > 1) // 宽图
|
||||
{
|
||||
var newHeight = objWidth / imageAspect;
|
||||
_showObjImage.rectTransform.sizeDelta = new Vector2(objWidth, newHeight);
|
||||
}
|
||||
else // 高图
|
||||
{
|
||||
var newWidth = objHeight * imageAspect;
|
||||
_showObjImage.rectTransform.sizeDelta = new Vector2(newWidth, objHeight);
|
||||
}
|
||||
|
||||
// 淡入
|
||||
_showObjImage.sprite = newSprite;
|
||||
yield return _showObjImage.FadeInAsync(1);
|
||||
}
|
||||
|
||||
public IEnumerator HideObj()
|
||||
{
|
||||
yield return _showObjImage.FadeOutAsync(1);
|
||||
_showObjPanel.SetActive(false);
|
||||
}
|
||||
|
||||
public IEnumerator ShowFullScreen(string picName)
|
||||
{
|
||||
_fullScreenImage.sprite = Resources.Load<Sprite>($"Art/Obj/{picName}");
|
||||
|
||||
// 淡入
|
||||
yield return _fullScreenImage.FadeInAsync(1);
|
||||
}
|
||||
|
||||
public IEnumerator HideFullScreen()
|
||||
{
|
||||
return _fullScreenImage.FadeOutAsync(1);
|
||||
}
|
||||
|
||||
public IEnumerator OpenProgressWindow(string title, string description, float duration)
|
||||
{
|
||||
AudioManager.Instance.PlaySfx("event:/Scriptal/Ui");
|
||||
_window.TitleValue = title;
|
||||
_window.DescriptionValue = description;
|
||||
float currentProgress = 0;
|
||||
|
||||
DOTween.To(
|
||||
() => currentProgress,
|
||||
value =>
|
||||
{
|
||||
currentProgress = value;
|
||||
_window.SetProgress(currentProgress); // 更新进度
|
||||
},
|
||||
100,
|
||||
duration
|
||||
);
|
||||
|
||||
_window.ShowModalWindow();
|
||||
|
||||
// 获取 moveText 的 RectTransform
|
||||
RectTransform moveTextRect =
|
||||
_window.transform.Find("View/mask/moveText")?.GetComponent<RectTransform>();
|
||||
if (moveTextRect != null)
|
||||
{
|
||||
moveTextRect.DOAnchorPosY(0f, duration).SetEase(Ease.Linear);
|
||||
}
|
||||
|
||||
// 等待用户确认
|
||||
yield return new WaitUntil(() => currentProgress >= 100);
|
||||
_window.HideModalWindow();
|
||||
moveTextRect.DOAnchorPosY(1326f, 0.1f).SetEase(Ease.Linear);
|
||||
}
|
||||
|
||||
// 红色闪烁效果
|
||||
public IEnumerator FlashRed(float duration)
|
||||
{
|
||||
_redFlash.gameObject.SetActive(true);
|
||||
yield return _redFlash.FadeInAsync(duration / 2);
|
||||
yield return _redFlash.FadeOutAsync(duration / 2);
|
||||
_redFlash.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
private void HideAll()
|
||||
{
|
||||
_fullScreenImage.FadeOut(0.1f);
|
||||
_showObjImage.FadeOut(0.1f);
|
||||
_showObjPanel.SetActive(false);
|
||||
}
|
||||
|
||||
#region 感觉这个面板应该常驻
|
||||
|
||||
public void Show()
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 打开警告窗口
|
||||
/// </summary>
|
||||
/// <param name="description">警告描述文本</param>
|
||||
/// <returns>等待玩家确认的协程</returns>
|
||||
public IEnumerator OpenWarningWindow(string description)
|
||||
{
|
||||
AudioManager.Instance.PlaySfx("event:/Scriptal/Ui");
|
||||
_warningWindow.DescriptionValue = description;
|
||||
_warningWindow.ShowModalWindow();
|
||||
|
||||
// 等待玩家点击确认按钮
|
||||
yield return new WaitUntil(() => !_warningWindow.gameObject.activeSelf);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae9caaab66fd459493d673ef3fd524f2
|
||||
timeCreated: 1744779595
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Text;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class RecordPanel : MonoBehaviour, IUIPanel
|
||||
{
|
||||
public bool IsOpen => gameObject.activeSelf;
|
||||
public bool IsCloseable => true;
|
||||
|
||||
public readonly StringBuilder recordStr = new();
|
||||
|
||||
public void Show()
|
||||
{
|
||||
ShowText();
|
||||
GameManager.Instance.PauseGame();
|
||||
gameObject.SetActive(true);
|
||||
|
||||
var btn = transform.Find("Return").GetComponent<Button>();
|
||||
btn.onClick.AddListener(ReturnGame);
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
GameManager.Instance.ContinueGame();
|
||||
// 关闭时停止渲染
|
||||
ClearText();
|
||||
var btn = transform.Find("Return").GetComponent<Button>();
|
||||
btn.onClick.RemoveAllListeners();
|
||||
}
|
||||
|
||||
private void ShowText()
|
||||
{
|
||||
var text = transform.Find("Log View").GetComponentInChildren<TMP_Text>();
|
||||
text.text = recordStr.ToString();
|
||||
}
|
||||
|
||||
private void ClearText()
|
||||
{
|
||||
var text = transform.Find("Log View").GetComponentInChildren<TMP_Text>();
|
||||
text.text = null;
|
||||
}
|
||||
|
||||
private void ReturnGame()
|
||||
{
|
||||
UIManager.Instance.HidePanel<RecordPanel>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dc6a1b2ccb564028b69d821ab33778f5
|
||||
timeCreated: 1744779271
|
||||
@@ -0,0 +1,77 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class SavesPanel : MonoBehaviour, IUIPanel
|
||||
{
|
||||
public bool IsOpen => gameObject.activeSelf;
|
||||
public bool IsCloseable => true;
|
||||
|
||||
private static readonly string SaveFilePath = Application.streamingAssetsPath + "/SaveFiles";
|
||||
|
||||
#region 索引
|
||||
public Button buttonPrefab;
|
||||
public Transform buttonParent;
|
||||
#endregion
|
||||
|
||||
#region 打开和关闭
|
||||
|
||||
public void Show()
|
||||
{
|
||||
InitLevelBtn();
|
||||
gameObject.SetActive(true);
|
||||
RegisterButton();
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
UnRegisterButton();
|
||||
}
|
||||
|
||||
private void RegisterButton()
|
||||
{
|
||||
var returnBtn = transform.Find("Return").GetComponent<Button>();
|
||||
returnBtn.onClick.AddListener(() => UIManager.Instance.HidePanel<SavesPanel>());
|
||||
}
|
||||
|
||||
private void UnRegisterButton()
|
||||
{
|
||||
var returnBtn = transform.Find("Return").GetComponent<Button>();
|
||||
returnBtn.onClick.RemoveAllListeners();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void InitLevelBtn()
|
||||
{
|
||||
// 清空旧按钮
|
||||
foreach (Transform child in buttonParent)
|
||||
{
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
|
||||
// 加载存档文件
|
||||
string[] saveFiles = Directory.GetFiles(SaveFilePath, "*.json");
|
||||
var orderedSaveFiles = saveFiles.OrderByDescending(item => item).ToArray();
|
||||
foreach (var saveFile in orderedSaveFiles)
|
||||
{
|
||||
var button = Instantiate(buttonPrefab, buttonParent);
|
||||
button.GetComponentInChildren<TMP_Text>().text = Path.GetFileNameWithoutExtension(saveFile);
|
||||
button.onClick.AddListener(() => OnSaveFileSelected(saveFile));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSaveFileSelected(string saveFileName)
|
||||
{
|
||||
// 隐藏UI
|
||||
gameObject.SetActive(false);
|
||||
// 开始游戏
|
||||
GameManager.Instance.StartWithSaveFile(saveFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bdd86c19be1449ca9e2783eb714fb8ff
|
||||
timeCreated: 1745572533
|
||||
@@ -0,0 +1,81 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class SettingPanel : MonoBehaviour, IUIPanel
|
||||
{
|
||||
public bool IsOpen => gameObject.activeSelf;
|
||||
public bool IsCloseable => true;
|
||||
|
||||
#region 打开和关闭
|
||||
|
||||
public void Show()
|
||||
{
|
||||
// 暂停游戏
|
||||
GameManager.Instance.PauseGame();
|
||||
|
||||
gameObject.SetActive(true);
|
||||
// 更新表单内容
|
||||
var form = GetComponentInChildren<SettingForm>();
|
||||
form.Init();
|
||||
// 注册函数
|
||||
RegisterButton();
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
// 更新表单内容
|
||||
var form = GetComponentInChildren<SettingForm>();
|
||||
form.UnInit();
|
||||
UnRegisterButton();
|
||||
|
||||
gameObject.SetActive(false);
|
||||
// 继续游戏
|
||||
GameManager.Instance.ContinueGame();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void RegisterButton()
|
||||
{
|
||||
var buttonGroup = transform.Find("Button Group");
|
||||
var continueBtn = buttonGroup.Find("Continue").GetComponent<Button>();
|
||||
continueBtn.onClick.AddListener(BackToGame);
|
||||
var backToMain = buttonGroup.Find("BackMain").GetComponent<Button>();
|
||||
backToMain.onClick.AddListener(BackToMain);
|
||||
var quit = buttonGroup.Find("Quit").GetComponent<Button>();
|
||||
quit.onClick.AddListener(Quit);
|
||||
}
|
||||
|
||||
private void UnRegisterButton()
|
||||
{
|
||||
var buttonGroup = transform.Find("Button Group");
|
||||
var continueBtn = buttonGroup.Find("Continue").GetComponent<Button>();
|
||||
continueBtn.onClick.RemoveAllListeners();
|
||||
var backToMain = buttonGroup.Find("BackMain").GetComponent<Button>();
|
||||
backToMain.onClick.RemoveAllListeners();
|
||||
var quit = buttonGroup.Find("Quit").GetComponent<Button>();
|
||||
quit.onClick.RemoveAllListeners();
|
||||
}
|
||||
|
||||
private void BackToGame()
|
||||
{
|
||||
// 关闭Setting面板
|
||||
UIManager.Instance.HidePanel<SettingPanel>();
|
||||
}
|
||||
|
||||
private void BackToMain()
|
||||
{
|
||||
// 关闭Setting面板
|
||||
UIManager.Instance.HidePanel<SettingPanel>();
|
||||
// 重启
|
||||
GameManager.Instance.QuitGame();
|
||||
}
|
||||
|
||||
private void Quit()
|
||||
{
|
||||
Application.Quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff78a14b28070864692582ecc5db824c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,75 @@
|
||||
using AibisDream.Utility;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class StartPanel : MonoBehaviour, IUIPanel
|
||||
{
|
||||
public bool IsOpen => gameObject.activeSelf;
|
||||
public bool IsCloseable => false;
|
||||
|
||||
#region 开启和关闭
|
||||
|
||||
public void Show()
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
RegisterButton();
|
||||
}
|
||||
|
||||
private void RegisterButton()
|
||||
{
|
||||
// 开始界面按钮
|
||||
var tabs = transform.Find("Tabs");
|
||||
tabs.Find("Start").GetComponent<Button>().onClick.AddListener(StartNewGame);
|
||||
tabs.Find("Select Level").GetComponent<Button>().onClick.AddListener(OpenLevelPanel);
|
||||
tabs.Find("Load Save File").GetComponent<Button>().onClick.AddListener(OpenSaveFilePanel);
|
||||
tabs.Find("Setting").GetComponent<Button>().onClick.AddListener(OpenSetting);
|
||||
tabs.Find("Quit").GetComponent<Button>().onClick.AddListener(QuitApp);
|
||||
// 打开链接
|
||||
transform.Find("问卷").GetComponent<Button>().onClick.AddListener(() => Application.OpenURL(ConstRef.SurveyURL));
|
||||
transform.Find("关注").GetComponent<Button>().onClick.AddListener(() => Application.OpenURL(ConstRef.QQURL));
|
||||
}
|
||||
|
||||
#region 按钮对应的函数
|
||||
|
||||
|
||||
private void StartNewGame()
|
||||
{
|
||||
GameManager.Instance.StartNewGame();
|
||||
}
|
||||
|
||||
private void OpenLevelPanel()
|
||||
{
|
||||
// TODO 打开选关面板
|
||||
}
|
||||
|
||||
private void OpenSaveFilePanel()
|
||||
{
|
||||
UIManager.Instance.ShowPanel<SavesPanel>();
|
||||
}
|
||||
|
||||
private void OpenSetting()
|
||||
{
|
||||
UIManager.Instance.ShowPanel<SettingPanel>();
|
||||
}
|
||||
|
||||
private void QuitApp()
|
||||
{
|
||||
GameManager.Instance.QuitApp();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9ba6256b10e589d478fc8c1910595417
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,5 +1,3 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using TMPro;
|
||||
@@ -49,10 +47,10 @@ namespace AibisDream
|
||||
// 隐藏UI
|
||||
gameObject.SetActive(false);
|
||||
// 开始游戏
|
||||
GameLoopManager.Instance.StartWithSaveFile(saveFileName);
|
||||
// GameLoopManager.Instance.StartWithSaveFile(saveFileName);
|
||||
}
|
||||
|
||||
public void HideSaveFileUI()
|
||||
private void HideSaveFileUI()
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AibisDream.Framework;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public class UIManager : Kit.Singleton<UIManager>, IUIManager
|
||||
{
|
||||
#region 索引
|
||||
|
||||
private Dictionary<Type, IUIPanel> _panelPool;
|
||||
private readonly Stack<IUIPanel> _panelStack = new();
|
||||
|
||||
public RectTransform Canvas { get; private set; }
|
||||
public Vector2 Resolution { get; set; }
|
||||
|
||||
public Camera UICamera { get; private set; }
|
||||
public RectTransform DialogCanvas { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
public override void OnSingletonInit()
|
||||
{
|
||||
InitRefs();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.Escape))
|
||||
{
|
||||
if (_panelStack.Count > 0)
|
||||
{
|
||||
var lastPanel = _panelStack.Peek();
|
||||
HidePanel(lastPanel);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowPanel<SettingPanel>();
|
||||
}
|
||||
}
|
||||
|
||||
if (Input.GetKeyDown(KeyCode.C))
|
||||
{
|
||||
if (GetPanel<MainPanel>().IsOpen)
|
||||
{
|
||||
HidePanel<MainPanel>();
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowPanel<MainPanel>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitRefs()
|
||||
{
|
||||
UICamera = transform.Find("UI Camera").GetComponent<Camera>();
|
||||
// 获取Canvas
|
||||
Canvas = transform.Find("UI Canvas").GetComponent<RectTransform>();
|
||||
DialogCanvas = transform.Find("Dialog Canvas").GetComponent<RectTransform>();
|
||||
// 获取Panel
|
||||
var panels = Canvas.GetComponentsInChildren<IUIPanel>(true);
|
||||
_panelPool = new Dictionary<Type, IUIPanel>();
|
||||
foreach (var panel in panels)
|
||||
{
|
||||
_panelPool[panel.GetType()] = panel;
|
||||
}
|
||||
|
||||
EnumEventSystem.Global.Register(GameLoopEnum.AppStart, OnAppStart);
|
||||
EnumEventSystem.Global.Register(GameLoopEnum.GameStart, OnGameStart);
|
||||
EnumEventSystem.Global.Register(GameLoopEnum.GameQuit, OnGameQuit);
|
||||
}
|
||||
|
||||
private void OnAppStart()
|
||||
{
|
||||
// 游戏打开
|
||||
ShowPanel<StartPanel>();
|
||||
ShowPanel<PlayToolPanel>();
|
||||
|
||||
HidePanel<MainPanel>();
|
||||
HidePanel<SettingPanel>();
|
||||
HidePanel<RecordPanel>();
|
||||
HidePanel<EndPanel>();
|
||||
}
|
||||
|
||||
private void OnGameStart()
|
||||
{
|
||||
// 开始游戏
|
||||
HidePanel<StartPanel>();
|
||||
ShowPanel<MainPanel>();
|
||||
}
|
||||
|
||||
private void OnGameQuit()
|
||||
{
|
||||
HidePanel<MainPanel>();
|
||||
ShowPanel<StartPanel>();
|
||||
}
|
||||
|
||||
public void ShowPanel<T>(Action<T> call = null) where T : class, IUIPanel
|
||||
{
|
||||
var type = typeof(T);
|
||||
// 从缓存中检查panel
|
||||
if (_panelPool.TryGetValue(type, out var panel))
|
||||
{
|
||||
if (panel.IsOpen) return;
|
||||
if (panel.IsCloseable) _panelStack.Push(panel);
|
||||
panel.Show();
|
||||
call?.Invoke(panel as T);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"panel {type} 未注册");
|
||||
}
|
||||
}
|
||||
|
||||
public void HidePanel<T>() where T : IUIPanel
|
||||
{
|
||||
var type = typeof(T);
|
||||
// 从缓存中检查panel
|
||||
if (_panelPool.TryGetValue(type, out var panel))
|
||||
{
|
||||
if (!panel.IsOpen) return;
|
||||
if (panel.IsCloseable) _panelStack.Pop();
|
||||
panel.Hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"panel {type} 未注册");
|
||||
}
|
||||
}
|
||||
|
||||
private void HidePanel<T>(T panel) where T : IUIPanel
|
||||
{
|
||||
if (_panelPool.ContainsValue(panel))
|
||||
{
|
||||
if (!panel.IsOpen) return;
|
||||
if (panel.IsCloseable) _panelStack.Pop();
|
||||
panel.Hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"panel {typeof(T)} 未注册");
|
||||
}
|
||||
}
|
||||
|
||||
public T GetPanel<T>() where T : class, IUIPanel
|
||||
{
|
||||
if (_panelPool.TryGetValue(typeof(T), out var panel))
|
||||
{
|
||||
return panel as T;
|
||||
}
|
||||
|
||||
Debug.Log($"没有找到{typeof(T)}");
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Vector3 GetDialogUIPos(Vector3 screenPos)
|
||||
{
|
||||
// 转换为UI坐标
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||
Instance.DialogCanvas,
|
||||
screenPos,
|
||||
Instance.UICamera,
|
||||
out Vector2 localPoint);
|
||||
|
||||
return localPoint;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IUIManager
|
||||
{
|
||||
void ShowPanel<T>(Action<T> call = null) where T : class, IUIPanel;
|
||||
void HidePanel<T>() where T : IUIPanel;
|
||||
T GetPanel<T>() where T : class, IUIPanel;
|
||||
RectTransform Canvas { get; }
|
||||
Vector2 Resolution { get; set; }
|
||||
|
||||
// 还没想好要不要用栈
|
||||
// UIPanel Pop();
|
||||
// void Push<T>(T panel) where T : UIPanel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0981e1188cb42a844b3b7b79a186bd39
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,12 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public interface IUIPanel
|
||||
{
|
||||
public virtual void Show() { }
|
||||
public virtual void Hide() { }
|
||||
public bool IsOpen { get; }
|
||||
public bool IsCloseable { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 714c1d8a65854c1fb1355f6ff2c6f670
|
||||
timeCreated: 1743669973
|
||||
Reference in New Issue
Block a user