气泡组件和配置项

This commit is contained in:
2024-09-13 19:39:22 +00:00
parent 4d9de0712e
commit 7adffa4372
73 changed files with 5671 additions and 2047 deletions
+17
View File
@@ -0,0 +1,17 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace AibisDream
{
public class DialogBox : DialogBoxBase
{
[SerializeField] protected GameObject choiceButton;
protected override void LoadChoiceButton()
{
choiceButtonPrefab = choiceButton;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 51f2233614aba944f9b2dab3d5af433b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+204
View File
@@ -0,0 +1,204 @@
using System;
using AibisDream.Config;
using AibisDream.Utility;
using Febucci.UI;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using Yarn.Unity;
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组件
protected TypewriterByCharacter dialogText;
protected TextMeshProUGUI actorName;
protected GameObject nextArray;
protected GameObject choiceBox;
#endregion
private bool _skipLineDelay;
[Header("跳过阻塞延迟时间/ms")]
[SerializeField] private int delayTime = 300;
protected GameObject choiceButtonPrefab;
protected virtual void Awake()
{
LoadChoiceButton();
InitComponents();
AddSomeListener();
}
private void AddSomeListener()
{
// 对话显示后有一段延时不可跳过
dialogText.onTypewriterStart.AddListener(() =>
{
_skipLineDelay = true;
CommonUtil.Delay(delayTime, () => { _skipLineDelay = false; });
});
}
#region
protected virtual void LoadChoiceButton()
{
Debug.Log("无选项对话框");
}
private void InitComponents()
{
actorName = transform.Find(ActorName)?.gameObject.GetComponent<TextMeshProUGUI>();
dialogText = transform.Find(DialogText)?.gameObject.GetComponent<TypewriterByCharacter>();
nextArray = transform.Find(NextArray)?.gameObject;
choiceBox = transform.Find(ChoiceBox)?.gameObject;
}
#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="isAutoSkip">是否自动跳过,默认为false</param>
public virtual void ShowLine(string dialogLine, CharacterVo character, Action nextStep, bool isAutoSkip = false)
{
gameObject.SetActive(true);
// 没有dialogText说明不能显示对话,直接跳过
if (!dialogText)
{
return;
}
// 把原有对话清掉
ClearBox();
dialogText.gameObject.SetActive(true);
choiceBox?.SetActive(false);
// 显示内容
ShowActorName(character);
dialogText.ShowText(dialogLine);
// TODO 如果有头像的对话框要加入头像处理
// 自动跳过的回调很难独立Remove,只能全部清空再注册,下策
dialogText.onTextShowed.RemoveAllListeners();
dialogText.onTextShowed.AddListener(() => nextArray.gameObject.SetActive(true));
// 执行自动跳过结局
if (isAutoSkip)
{
dialogText.onTextShowed.AddListener(() => CommonUtil.Delay(200, nextStep));
}
}
/// <summary>
/// 展示选项
/// </summary>
/// <param name="dialogueOptions">对话选项</param>
/// <param name="onOptionSelected">选项选择</param>
public virtual void ShowOptions(DialogueOption[] dialogueOptions, Action<int> onOptionSelected)
{
gameObject.SetActive(true);
// 没有选择框直接不显示选项
if (!choiceBox)
{
return;
}
// 把原有对话清掉
ClearBox();
choiceBox.SetActive(true);
dialogText.gameObject.SetActive(false);
// 销毁原来的按钮
foreach (Transform child in choiceBox.transform)
{
Destroy(child.gameObject);
}
// 生成新按钮
foreach (DialogueOption option in dialogueOptions)
{
if (!option.IsAvailable) continue;
var choiceInstance = Instantiate(choiceButtonPrefab, choiceBox.transform);
choiceInstance.GetComponentInChildren<TextMeshProUGUI>().text =
option.Line.TextWithoutCharacterName.Text;
choiceInstance.GetComponent<Button>().onClick
.AddListener(() => onOptionSelected.Invoke(option.DialogueOptionID));
}
}
/// <summary>
/// 尝试跳过对话
/// true说明成功快进了对话显示
/// false说明对话已经显示完成了,可以跳过
/// </summary>
/// <returns></returns>
public virtual bool TrySkipLine()
{
if (_skipLineDelay)
{
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.IsNullOrEmpty(character.nameColor))
{
actorName.text = character.cn;
}
else
{
var showStr = $"<color={character.nameColor}>{character.cn}</color>";
actorName.text = showStr;
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8a4b8d325dce1b64980f8fd2ff77c571
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,12 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace AibisDream
{
public class DialogBubble : DialogBoxBase
{
// 气泡功能比较简单似乎不需要加别的
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d43fa24173f81ea4ea102be91896826e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using AibisDream.Config;
using UnityEngine;
using Yarn.Unity;
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 Dictionary<string, IDialogView> _customBubblePool = new();
private List<IDialogView> _dialogViews;
private IDialogView _currentView;
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(DialogueOption[] dialogueOptions, Action<int> onOptionSelected)
{
gameObject.SetActive(true);
_currentView = _normalBox;
// 目前只有正常Box可以展示选项
// 处理其他泡泡
ProcessOtherBox();
// 展示选项
_normalBox.ShowOptions(dialogueOptions, onOptionSelected);
}
public void HideDialog()
{
gameObject.SetActive(false);
// 隐藏整个对话组件
_dialogViews?.ForEach(item => item.HideDialog());
}
public bool TrySkipLine()
{
return _currentView == null || _currentView.TrySkipLine();
}
private IDialogView SelectBubbleByCharacter(CharacterVo characterVo)
{
return characterVo.role switch
{
ActorRole.Player => _playerBubble,
ActorRole.MainActor => _actorBubble,
ActorRole.Aside => _normalBox,
_ => _normalBox
};
}
private void ProcessOtherBox()
{
if (_currentView == _actorBubble || _currentView == _playerBubble)
{
_normalBox.HideDialog();
}
if (_currentView == _normalBox)
{
_actorBubble.HideDialog();
_playerBubble.HideDialog();
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 98b5d94e03211a94bba6de692a1d405a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,118 @@
using System;
using UnityEngine;
using UnityEngine.UI;
namespace AibisDream
{
public class DialogCanvasManager : Singleton<DialogCanvasManager>
{
private const string BubbleView = "Dialog Bubble View";
private const string OldBoxView = "Dialog Box";
private const string QuickTalk = "Quick Talk";
private IDialogView _bubbleView;
private IDialogView _oldBoxView;
private IDialogView _curView;
private GameObject _quickTalkButton;
protected override void Awake()
{
base.Awake();
InitDialogViews();
InitQuickTalkButton();
// 默认为旧Box
_curView = _bubbleView;
}
#region
private void OnEnable()
{
DialogController.OnDialogueComplete += HideDialog;
}
private void OnDisable()
{
DialogController.OnDialogueComplete -= HideDialog;
}
private void HideDialog()
{
_curView.HideDialog();
}
#endregion
private void Update()
{
if (Input.GetKeyDown(KeyCode.Q) && Input.GetKey(KeyCode.P) )
{
_quickTalkButton.SetActive(!_quickTalkButton.activeSelf);
}
}
private void InitDialogViews()
{
_bubbleView = transform.Find(BubbleView).GetComponent<IDialogView>();
_oldBoxView = transform.Find(OldBoxView).GetComponent<IDialogView>();
_bubbleView.HideDialog();
_oldBoxView.HideDialog();
}
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>
/// 切换对话UI
/// </summary>
/// <param name="viewType">UI类型</param>
/// <returns></returns>
public IDialogView SwitchDialogView(DialogViewType viewType)
{
var newDlg = viewType switch
{
DialogViewType.Bubble => _bubbleView,
DialogViewType.OldBox => _oldBoxView,
_ => _oldBoxView
};
if (_curView != newDlg)
{
_curView.HideDialog();
_curView = newDlg;
}
return _curView;
}
/// <summary>
/// 获取当前正在使用的Dialog View
/// </summary>
/// <returns>当前正在使用的Dialog View</returns>
/// <exception cref="Exception">未找到Canvas</exception>
public static IDialogView GetCurView()
{
return Instance._curView;
}
}
public enum DialogViewType
{
Bubble, OldBox
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c14c52217dac70244b02fe48d9462bae
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
namespace AibisDream
{
public class DialogOldBox : DialogBoxBase
{
private void OnEnable()
{
DialogController.OnDialogueComplete += HideDialog;
}
private void OnDisable()
{
DialogController.OnDialogueComplete -= HideDialog;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2f063fd2d0404e04a809d7614c5b7532
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,187 @@
using Febucci.UI;
using System;
using AibisDream.Utility;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using Yarn.Unity;
namespace AibisDream
{
public class DialogUiViewer : Singleton<DialogUiViewer>
{
#region
private const string DialogBox = "Dialog Box";
private const string ActorName = "Actor Name";
private const string DialogText = "Dialog Text";
private const string NextButton = "Next Button";
private const string ChoiceBox = "Choice Box";
private const string QuickTalk = "Quick Talk";
#endregion
#region UI
private GameObject dialogBox;
private TypewriterByCharacter dialogText;
private TextMeshProUGUI actorName;
private Button nextButton;
private GameObject choiceBox;
private GameObject choiceButtonPrefab;
private GameObject quickTalkButton;
#endregion
private bool _skipLineDelay;
protected override void Awake()
{
base.Awake();
// 获取预制体
choiceButtonPrefab = Resources.Load<GameObject>("Prefabs/UI/Choice Button");
// 获取子物体
dialogBox = transform.Find(DialogBox).gameObject;
GetDialogComponentInBox(dialogBox);
}
private void OnEnable()
{
DialogController.OnDialogueComplete += HideDialog;
}
private void OnDisable()
{
DialogController.OnDialogueComplete -= HideDialog;
}
/// <summary>
/// 显示文字
/// </summary>
/// <param name="dialogLine">对话内容</param>
/// <param name="actor">名称</param>
/// <param name="nextStep">下一步</param>
public void ShowLine(string dialogLine, string actor, Action nextStep)
{
dialogBox.SetActive(true);
dialogText.ShowText("");
dialogText.gameObject.SetActive(true);
choiceBox.SetActive(false);
actorName.text = actor;
dialogText.ShowText(dialogLine);
// 显示完成后处理
dialogText.onTextShowed.RemoveAllListeners();
dialogText.onTextShowed.AddListener(() => nextButton.gameObject.SetActive(true));
nextButton.onClick.RemoveAllListeners();
}
/// <summary>
/// 显示文字,显示完成自动跳过
/// </summary>
/// <param name="dialogLine"></param>
/// <param name="actor"></param>
/// <param name="nextStep"></param>
public void ShowAutoLine(string dialogLine, string actor, Action nextStep)
{
dialogBox.SetActive(true);
dialogText.ShowText("");
dialogText.gameObject.SetActive(true);
choiceBox.SetActive(false);
actorName.text = actor;
nextButton.onClick.RemoveAllListeners();
dialogText.ShowText(dialogLine);
// 对话显示完成后自动跳过
dialogText.onTextShowed.RemoveAllListeners();
dialogText.onTextShowed.AddListener(() => CommonUtil.Delay(200, nextStep));
}
/// <summary>
/// 显示选项
/// </summary>
/// <param name="dialogueOptions">选项</param>
/// <param name="onOptionSelected"></param>
public void ShowOptions(DialogueOption[] dialogueOptions, Action<int> onOptionSelected)
{
dialogBox.SetActive(true);
choiceBox.SetActive(true);
dialogText.gameObject.SetActive(false);
dialogText.ShowText("");
actorName.text = "";
// 销毁原来的按钮
foreach (Transform child in choiceBox.transform)
{
Destroy(child.gameObject);
}
// 生成新按钮
foreach (DialogueOption option in dialogueOptions)
{
if (option.IsAvailable)
{
var choiceInstance = Instantiate(choiceButtonPrefab, Vector3.zero, Quaternion.identity);
choiceInstance.transform.SetParent(choiceBox.transform);
choiceInstance.GetComponentInChildren<TextMeshProUGUI>().text =
option.Line.TextWithoutCharacterName.Text;
choiceInstance.GetComponent<Button>().onClick
.AddListener(() => onOptionSelected.Invoke(option.DialogueOptionID));
}
}
}
public void HideDialog()
{
dialogBox?.SetActive(false);
}
public void SwitchQuickTalkMode()
{
DialogController.Instance.quickRunMode = !DialogController.Instance.quickRunMode;
quickTalkButton.GetComponent<Image>().color =
DialogController.Instance.quickRunMode ? Color.gray : Color.white;
}
public bool TrySkipLine()
{
// 开始后0.5秒不给跳过
if (_skipLineDelay) return true;
if (dialogText.isShowingText)
{
dialogText.SkipTypewriter();
return true;
}
else
{
return false;
}
}
public void AfterEndLine()
{
nextButton.gameObject.SetActive(false);
}
private void GetDialogComponentInBox(GameObject box)
{
actorName = box.transform.Find(ActorName).gameObject.GetComponent<TextMeshProUGUI>();
dialogText = box.transform.Find(DialogText).gameObject.GetComponent<TypewriterByCharacter>();
nextButton = box.transform.Find(NextButton).GetComponent<Button>();
choiceBox = box.transform.Find(ChoiceBox).gameObject;
quickTalkButton = box.transform.Find(QuickTalk).gameObject;
quickTalkButton.GetComponent<Button>().onClick.AddListener(SwitchQuickTalkMode);
dialogText.onTypewriterStart.AddListener(() =>
{
_skipLineDelay = true;
CommonUtil.Delay(300, () => { _skipLineDelay = false; });
});
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6689e1db404e4a340bec4e540aea09d5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+18
View File
@@ -0,0 +1,18 @@
using System;
using AibisDream.Config;
using Yarn.Unity;
namespace AibisDream
{
public interface IDialogView
{
public void ShowLine(string dialogLine, CharacterVo character, Action nextStep, bool isAutoSkip = false);
public void ShowOptions(DialogueOption[] dialogueOptions, Action<int> onOptionSelected);
public void HideDialog();
public bool TrySkipLine();
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d9f2abb67889d3247a1314a7788f8554
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: