400 lines
14 KiB
C#
400 lines
14 KiB
C#
|
|
using System;
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
using System.Linq;
|
|||
|
|
using System.Text.RegularExpressions;
|
|||
|
|
using Ichni.Story.UI;
|
|||
|
|
using Sirenix.OdinInspector;
|
|||
|
|
using UnityEngine;
|
|||
|
|
using UnityEngine.Serialization;
|
|||
|
|
|
|||
|
|
namespace Ichni.Story
|
|||
|
|
{
|
|||
|
|
public partial class DialogManager : SerializedMonoBehaviour
|
|||
|
|
{
|
|||
|
|
public static DialogManager instance;
|
|||
|
|
|
|||
|
|
public List<TextAsset> dialogTextAssets;
|
|||
|
|
|
|||
|
|
public bool isPlayingDialog;
|
|||
|
|
public bool isPlayingChoice;
|
|||
|
|
|
|||
|
|
public string currentDialog;
|
|||
|
|
public int currentDialogSentenceIndex;
|
|||
|
|
public string currentFinalType;
|
|||
|
|
|
|||
|
|
public Dictionary<string, List<string>> functionDictionary;
|
|||
|
|
public Dictionary<string, List<DialogSentence>> dialogDictionary;
|
|||
|
|
public Dictionary<string, List<Choice>> choiceDictionary;
|
|||
|
|
public Dictionary<string, List<Condition>> conditionDictionary;
|
|||
|
|
|
|||
|
|
private string currentLoadingDialog;
|
|||
|
|
|
|||
|
|
[Header("Test")]
|
|||
|
|
public List<TextAsset> testTextAssets;
|
|||
|
|
|
|||
|
|
public DialogUIPage dialogUIPage;
|
|||
|
|
|
|||
|
|
private void Awake()
|
|||
|
|
{
|
|||
|
|
instance = this;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void SetDialog(string dialogName)
|
|||
|
|
{
|
|||
|
|
TextAsset dialog = Resources.Load<TextAsset>("Dialogs/" + dialogName);
|
|||
|
|
SetDialog(new List<TextAsset> { dialog }, "Entry");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void SetDialog(List<TextAsset> dialogFiles, string dialogParagraphName)
|
|||
|
|
{
|
|||
|
|
dialogUIPage.FadeIn();
|
|||
|
|
|
|||
|
|
currentDialog = "NULL";
|
|||
|
|
isPlayingDialog = true;
|
|||
|
|
LoadDialog(dialogFiles);
|
|||
|
|
|
|||
|
|
if (!string.IsNullOrEmpty(dialogParagraphName))
|
|||
|
|
{
|
|||
|
|
currentDialog = dialogParagraphName;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
PlayNextDialogParagraph(currentDialog);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void PlayNextDialogParagraph(string nextDialog)
|
|||
|
|
{
|
|||
|
|
currentDialog = nextDialog;
|
|||
|
|
currentDialogSentenceIndex = 0;
|
|||
|
|
|
|||
|
|
if (functionDictionary.TryGetValue(currentDialog, out List<string> functionList))
|
|||
|
|
{
|
|||
|
|
functionList.ForEach(x => StoryInterpreters.FunctionInterpreter.Eval(x));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (choiceDictionary.ContainsKey(currentDialog))
|
|||
|
|
{
|
|||
|
|
currentFinalType = "Choice";
|
|||
|
|
}
|
|||
|
|
else if (conditionDictionary.ContainsKey(currentDialog))
|
|||
|
|
{
|
|||
|
|
currentFinalType = "Condition";
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
currentFinalType = "None";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
PlayDialog();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[Button("Test Play")]
|
|||
|
|
public void PlayDialog()
|
|||
|
|
{
|
|||
|
|
if(currentDialog == "NULL")
|
|||
|
|
{
|
|||
|
|
throw new Exception("Current dialog is NULL");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*if (dialogInterface.dialogTextFrame.isPlayingSentence)
|
|||
|
|
{
|
|||
|
|
dialogInterface.dialogTextFrame.FinishSentence();
|
|||
|
|
return;
|
|||
|
|
}*/
|
|||
|
|
|
|||
|
|
if (dialogDictionary[currentDialog].Count > 0 && currentDialogSentenceIndex < dialogDictionary[currentDialog].Count)
|
|||
|
|
{
|
|||
|
|
DialogSentence currentSentence = dialogDictionary[currentDialog][currentDialogSentenceIndex];
|
|||
|
|
|
|||
|
|
string interpretedContent = currentSentence.GetInterpretedContent();
|
|||
|
|
|
|||
|
|
dialogUIPage.textFrame.PlaySentence(currentSentence.characterName, interpretedContent);
|
|||
|
|
currentDialogSentenceIndex++;
|
|||
|
|
|
|||
|
|
if (currentDialogSentenceIndex <= dialogDictionary[currentDialog].Count)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if(currentDialogSentenceIndex >= dialogDictionary[currentDialog].Count)
|
|||
|
|
{
|
|||
|
|
if (currentFinalType == "Choice")
|
|||
|
|
{
|
|||
|
|
isPlayingChoice = true;
|
|||
|
|
dialogUIPage.choiceFrame.PlayChoice(choiceDictionary[currentDialog]);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (currentFinalType == "Condition")
|
|||
|
|
{
|
|||
|
|
foreach (var condition in conditionDictionary[currentDialog])
|
|||
|
|
{
|
|||
|
|
if (condition.GetConditionResult())
|
|||
|
|
{
|
|||
|
|
PlayNextDialogParagraph(condition.nextDialogName);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (currentFinalType == "None" && currentDialogSentenceIndex >= dialogDictionary[currentDialog].Count)
|
|||
|
|
{
|
|||
|
|
dialogUIPage.FadeOut();
|
|||
|
|
dialogUIPage.choiceFrame.gameObject.SetActive(false);
|
|||
|
|
//currentDialogNPC.priorStoryTexts.Remove(dialogTextAsset);
|
|||
|
|
//currentDialogNPC = null;
|
|||
|
|
isPlayingDialog = false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public partial class DialogManager
|
|||
|
|
{
|
|||
|
|
public void LoadDialog(List<TextAsset> dialogFiles)
|
|||
|
|
{
|
|||
|
|
ClearDictionaries();
|
|||
|
|
|
|||
|
|
dialogTextAssets = dialogFiles;
|
|||
|
|
List<string> dialogLines = new List<string>();
|
|||
|
|
|
|||
|
|
foreach (TextAsset textAsset in dialogTextAssets)
|
|||
|
|
{
|
|||
|
|
dialogLines.AddRange(ExtractValidFragments(textAsset.text));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
dialogLines.RemoveAll(line => line.Trim() == "");
|
|||
|
|
|
|||
|
|
dialogLines.ForEach(Debug.Log);
|
|||
|
|
|
|||
|
|
foreach (var line in from line in dialogLines
|
|||
|
|
where !ParseHeader(line)
|
|||
|
|
where !ParseChoiceModule(line)
|
|||
|
|
where !ParseConditionModule(line)
|
|||
|
|
where !ParseDialogSentence(line)
|
|||
|
|
select line)
|
|||
|
|
{
|
|||
|
|
throw new Exception($"Invalid dialog line: {line}"); // 抛出异常,提示不合法的对话行
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//dialogDictionary.RemoveWhere((header, sentences) => sentences == null || sentences.Count == 0);
|
|||
|
|
choiceDictionary.RemoveWhere((header, choices) => choices == null || choices.Count == 0);
|
|||
|
|
conditionDictionary.RemoveWhere((header, conditions) => conditions == null || conditions.Count == 0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 从原始大文本中提取所有以 '$' 开头的有效片段,忽略以 '#' 开头的注释片段。
|
|||
|
|
/// 拆分依据:每当遇到 '$' 或 '#' 字符,即视为一个新片段的起始。
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="inputText">未分割的完整文本(可能包含任意换行或连续内容)。</param>
|
|||
|
|
/// <returns>剥离首 '$' 后的有效文本列表。</returns>
|
|||
|
|
public static List<string> ExtractValidFragments(string inputText)
|
|||
|
|
{
|
|||
|
|
if (inputText == null)
|
|||
|
|
{
|
|||
|
|
throw new ArgumentNullException(nameof(inputText));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 正则:(?<prefix>[$#]) // 片段起始前缀
|
|||
|
|
// (?<content>.*? ) // 非贪婪捕获所有内容
|
|||
|
|
// (?=(?:[$#])|\z) // 直到下一个 '$'、'#' 或文末
|
|||
|
|
|
|||
|
|
const string pattern = @"(?<prefix>[$#])(?<content>.*?)(?=(?:[$#])|\z)";
|
|||
|
|
MatchCollection matches = Regex.Matches(inputText, pattern, RegexOptions.Singleline);
|
|||
|
|
|
|||
|
|
var result = new List<string>(matches.Count);
|
|||
|
|
foreach (Match m in matches)
|
|||
|
|
{
|
|||
|
|
char prefix = m.Groups["prefix"].Value[0];
|
|||
|
|
string content = m.Groups["content"].Value;
|
|||
|
|
|
|||
|
|
if (prefix == '$')
|
|||
|
|
{
|
|||
|
|
result.Add(content.Trim());
|
|||
|
|
}
|
|||
|
|
// prefix == '#' 时自动忽略
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public partial class DialogManager
|
|||
|
|
{
|
|||
|
|
public void ClearDictionaries()
|
|||
|
|
{
|
|||
|
|
dialogDictionary.Clear();
|
|||
|
|
choiceDictionary.Clear();
|
|||
|
|
conditionDictionary.Clear();
|
|||
|
|
functionDictionary.Clear();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool ParseHeader(string line)
|
|||
|
|
{
|
|||
|
|
//格式:[currentLoadingDialog]{Function0();Function1();Function2();}
|
|||
|
|
line = line.Trim();
|
|||
|
|
|
|||
|
|
string dialogTitle = line.Split("{")[0];
|
|||
|
|
if (dialogTitle[0] != '[' || dialogTitle[^1] != ']')
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
currentLoadingDialog = dialogTitle.Replace("[", "").Replace("]", "");
|
|||
|
|
|
|||
|
|
dialogDictionary.Add(currentLoadingDialog, new List<DialogSentence>());
|
|||
|
|
choiceDictionary.Add(currentLoadingDialog, new List<Choice>());
|
|||
|
|
conditionDictionary.Add(currentLoadingDialog, new List<Condition>());
|
|||
|
|
|
|||
|
|
if (currentDialog == "NULL")
|
|||
|
|
{
|
|||
|
|
currentDialog = currentLoadingDialog;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!line.Contains("{")) // 这个Header没有函数需要执行
|
|||
|
|
{
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
string functions = line.Split("{")[1];
|
|||
|
|
if (functions.Contains("}"))
|
|||
|
|
{
|
|||
|
|
functions = functions.Split("}")[0];
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
throw new System.Exception("Dialog header's function list must be enclosed in {}.");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
functions = functions.Replace(" ", "").Replace("\n", "").Replace("\r", "").Trim(); //忽略空格,换行
|
|||
|
|
|
|||
|
|
List<string> functionList = functions.Split(';').ToList(); //分割函数
|
|||
|
|
functionList = functionList.Where(x => !string.IsNullOrEmpty(x)).ToList(); //去除空函数
|
|||
|
|
functionDictionary.Add(currentLoadingDialog, functionList);
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool ParseDialogSentence(string line)
|
|||
|
|
{
|
|||
|
|
//speakerName(emotion):sentence
|
|||
|
|
|
|||
|
|
string[] sentenceData;
|
|||
|
|
if (line.Contains(":"))
|
|||
|
|
{
|
|||
|
|
sentenceData = line.Split(":", 2);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
string character = sentenceData[0];
|
|||
|
|
|
|||
|
|
string speakerName = character;
|
|||
|
|
string emotion = "Default";
|
|||
|
|
|
|||
|
|
|
|||
|
|
if (character.Contains("("))
|
|||
|
|
{
|
|||
|
|
emotion = character.Split("(")[1].Replace(")", "");
|
|||
|
|
speakerName = character.Split("(")[0];
|
|||
|
|
}
|
|||
|
|
else if (character.Contains("("))
|
|||
|
|
{
|
|||
|
|
emotion = character.Split("(")[1].Replace(")", "");
|
|||
|
|
speakerName = character.Split("(")[0];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
DialogSentence dialogSentence = new DialogSentence
|
|||
|
|
{
|
|||
|
|
characterName = speakerName,
|
|||
|
|
characterEmotion = emotion,
|
|||
|
|
content = sentenceData[1]
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
|
|||
|
|
dialogDictionary[currentLoadingDialog].Add(dialogSentence);
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool ParseChoiceModule(string line)
|
|||
|
|
{
|
|||
|
|
//$Choice{
|
|||
|
|
//choiceText0(Hint0)->[nextDialogName0];
|
|||
|
|
//choiceText1(Hint1)->[nextDialogName1];
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
line = line.Trim();
|
|||
|
|
|
|||
|
|
if (line.Contains("Choice"))
|
|||
|
|
{
|
|||
|
|
string[] choiceModuleData = line.Split('{');
|
|||
|
|
|
|||
|
|
List<Choice> choices = new List<Choice>();
|
|||
|
|
|
|||
|
|
string[] choiceData = choiceModuleData[1].Split(';');
|
|||
|
|
|
|||
|
|
for (var index = 0; index < choiceData.Length - 1; index++)
|
|||
|
|
{
|
|||
|
|
Choice choice = new Choice
|
|||
|
|
{
|
|||
|
|
choiceText = choiceData[index].Split("->")[0].Split("(")[0].Trim(),
|
|||
|
|
hint = choiceData[index].Split("->")[0].Split("(")[1].Replace(")", "").Trim(),
|
|||
|
|
nextDialogName = choiceData[index].Split("->[")[1].Replace("]", "").Trim(),
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
choices.Add(choice);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
choiceDictionary[currentLoadingDialog] = choices;
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 解析条件模块
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="line"></param>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
private bool ParseConditionModule(string line)
|
|||
|
|
{
|
|||
|
|
//$Condition{
|
|||
|
|
//conditionSentence0->[nextDialogName0];
|
|||
|
|
//conditionSentence1->[nextDialogName1];
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
if (line.Contains("Condition"))
|
|||
|
|
{
|
|||
|
|
string[] conditionModuleData = line.Split('{');
|
|||
|
|
|
|||
|
|
List<Condition> conditions = new List<Condition>();
|
|||
|
|
|
|||
|
|
string[] conditionData = conditionModuleData[1].Split(';');
|
|||
|
|
|
|||
|
|
for (var index = 0; index < conditionData.Length - 1; index++)
|
|||
|
|
{
|
|||
|
|
conditionData[index] = conditionData[index].Replace(" ", "").Replace("\n", "").Replace("\r", "").Trim();
|
|||
|
|
Condition condition = new Condition
|
|||
|
|
{
|
|||
|
|
conditionSentence = conditionData[index].Split("->[")[0].Trim(),
|
|||
|
|
nextDialogName = conditionData[index].Split("->[")[1].Replace("]", "").Trim(),
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
conditions.Add(condition);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
conditionDictionary[currentLoadingDialog] = conditions;
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|