剧情
This commit is contained in:
192
Assets/Scripts/Game/GameSaveManager.cs
Normal file
192
Assets/Scripts/Game/GameSaveManager.cs
Normal file
@@ -0,0 +1,192 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Ichni.RhythmGame;
|
||||
using Ichni.Story;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Ichni
|
||||
{
|
||||
public class GameSaveManager : SerializedMonoBehaviour
|
||||
{
|
||||
public static GameSaveManager instance;
|
||||
|
||||
public SongSaveModule SongSaveModule;
|
||||
public StorySaveModule StorySaveModule;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
instance = this;
|
||||
|
||||
SongSaveModule = new SongSaveModule();
|
||||
SongSaveModule.LoadSongStatuses();
|
||||
|
||||
StorySaveModule = new StorySaveModule();
|
||||
StorySaveModule.LoadStoryVariables();
|
||||
StorySaveModule.LoadChoices();
|
||||
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public partial class SongSaveModule
|
||||
{
|
||||
public Dictionary<string, SongStatusSave> songStatusSaves;
|
||||
private string SongSavePath => Application.streamingAssetsPath + "/GameSaves/SongSaves.json";
|
||||
|
||||
public SongSaveModule()
|
||||
{
|
||||
songStatusSaves = new Dictionary<string, SongStatusSave>();
|
||||
Debug.Log("Song save path: " + SongSavePath);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class SongSaveModule
|
||||
{
|
||||
[Button]
|
||||
public void SaveSongStatuses()
|
||||
{
|
||||
ES3.Save("SongSaves", songStatusSaves, SongSavePath);
|
||||
}
|
||||
|
||||
public void LoadSongStatuses()
|
||||
{
|
||||
if (ES3.FileExists(SongSavePath))
|
||||
{
|
||||
songStatusSaves = ES3.Load<Dictionary<string, SongStatusSave>>("SongSaves", SongSavePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
songStatusSaves = new Dictionary<string, SongStatusSave>();
|
||||
|
||||
//TODO: delete
|
||||
songStatusSaves.Add("Chaos Zone", new SongStatusSave
|
||||
{
|
||||
isCompleted = false,
|
||||
additionalInfo = "",
|
||||
beatmapSaves = new Dictionary<string, BeatmapSave>()
|
||||
{
|
||||
{ "Easy", new BeatmapSave { accuracy = 0.0f, isFullCombo = false, isAllPerfect = false } },
|
||||
{ "Hard", new BeatmapSave { accuracy = 0.0f, isFullCombo = false, isAllPerfect = false } },
|
||||
{ "Chaos", new BeatmapSave { accuracy = 0.0f, isFullCombo = false, isAllPerfect = false } }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Button]
|
||||
public void ClearBeatmapRecords()
|
||||
{
|
||||
foreach (var songStatus in songStatusSaves.Values)
|
||||
{
|
||||
foreach (var beatmapSave in songStatus.beatmapSaves.Values)
|
||||
{
|
||||
beatmapSave.accuracy = 0.0f;
|
||||
beatmapSave.isFullCombo = false;
|
||||
beatmapSave.isAllPerfect = false;
|
||||
}
|
||||
}
|
||||
|
||||
SaveSongStatuses();
|
||||
}
|
||||
}
|
||||
|
||||
public partial class StorySaveModule
|
||||
{
|
||||
public Dictionary<string, List<TutorialBlockSave>> tutorialBlockSaves;
|
||||
public Dictionary<string, List<SongBlockSave>> songBlockSaves;
|
||||
public Dictionary<string, List<DialogBlockSave>> dialogBlockSaves;
|
||||
public Dictionary<string, List<BlockConnectorSave>> connectorSaves;
|
||||
|
||||
public Dictionary<string, int> storyVariables;
|
||||
public Dictionary<string, int> selectedChoices;
|
||||
|
||||
private string GetStorySavePath(string chapterName) => Application.streamingAssetsPath + "/StorySaves/" + chapterName + ".json";
|
||||
private string StoryVariablesPath => Application.streamingAssetsPath + "/StorySaves/StoryVariables.json";
|
||||
private string ChoicesPath => Application.streamingAssetsPath + "/StorySaves/Choices.json";
|
||||
|
||||
public StorySaveModule()
|
||||
{
|
||||
tutorialBlockSaves = new Dictionary<string, List<TutorialBlockSave>>();
|
||||
songBlockSaves = new Dictionary<string, List<SongBlockSave>>();
|
||||
dialogBlockSaves = new Dictionary<string, List<DialogBlockSave>>();
|
||||
connectorSaves = new Dictionary<string, List<BlockConnectorSave>>();
|
||||
storyVariables = new Dictionary<string, int>();
|
||||
selectedChoices = new Dictionary<string, int>();
|
||||
Debug.Log("Story Variables path: " + StoryVariablesPath);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class StorySaveModule
|
||||
{
|
||||
public void LoadStoryline(string chapterName)
|
||||
{
|
||||
tutorialBlockSaves[chapterName] = ES3.Load<List<TutorialBlockSave>>("TutorialBlockSaves", GetStorySavePath(chapterName));
|
||||
songBlockSaves[chapterName] = ES3.Load<List<SongBlockSave>>("SongBlockSaves", GetStorySavePath(chapterName));
|
||||
dialogBlockSaves[chapterName] = ES3.Load<List<DialogBlockSave>>("TextBlockSaves", GetStorySavePath(chapterName));
|
||||
connectorSaves[chapterName] = ES3.Load<List<BlockConnectorSave>>("BlockConnectorSaves", GetStorySavePath(chapterName));
|
||||
}
|
||||
|
||||
public void SaveStoryline(string chapterName, List<TutorialBlockSave> tutorialBlocks,
|
||||
List<SongBlockSave> songBlocks, List<DialogBlockSave> dialogBlocks,
|
||||
List<BlockConnectorSave> connectors)
|
||||
{
|
||||
ES3.Save("TutorialBlockSaves", tutorialBlocks, GetStorySavePath(chapterName));
|
||||
ES3.Save("SongBlockSaves", songBlocks, GetStorySavePath(chapterName));
|
||||
ES3.Save("TextBlockSaves", dialogBlocks, GetStorySavePath(chapterName));
|
||||
ES3.Save("BlockConnectorSaves", connectors, GetStorySavePath(chapterName));
|
||||
|
||||
SaveStoryVariables();
|
||||
SaveChoices();
|
||||
}
|
||||
}
|
||||
|
||||
public partial class StorySaveModule
|
||||
{
|
||||
public void SaveStoryVariables()
|
||||
{
|
||||
string path = Application.streamingAssetsPath + "/StorySaves/" + "StoryVariables.json";
|
||||
ES3.Save("StoryVariables", storyVariables, path);
|
||||
}
|
||||
|
||||
public void LoadStoryVariables()
|
||||
{
|
||||
string path = Application.streamingAssetsPath + "/StorySaves/" + "StoryVariables.json";
|
||||
if (ES3.FileExists(path))
|
||||
{
|
||||
storyVariables = ES3.Load<Dictionary<string, int>>("StoryVariables", path);
|
||||
}
|
||||
else
|
||||
{
|
||||
storyVariables = new Dictionary<string, int>();
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveChoices()
|
||||
{
|
||||
string path = Application.streamingAssetsPath + "/StorySaves/" + "Choices.json";
|
||||
ES3.Save("Choices", selectedChoices, path);
|
||||
}
|
||||
|
||||
public void LoadChoices()
|
||||
{
|
||||
string path = Application.streamingAssetsPath + "/StorySaves/" + "Choices.json";
|
||||
if (ES3.FileExists(path))
|
||||
{
|
||||
selectedChoices = ES3.Load<Dictionary<string, int>>("Choices", path);
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedChoices = new Dictionary<string, int>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6be21a0f3163e154ca67d55f70a2617b
|
||||
guid: cffb558157bfb9a49a2a624f025f8958
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
13
Assets/Scripts/Menu/SongSelection/BeatmapSave.cs
Normal file
13
Assets/Scripts/Menu/SongSelection/BeatmapSave.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Ichni.RhythmGame
|
||||
{
|
||||
public class BeatmapSave
|
||||
{
|
||||
public float accuracy;
|
||||
public bool isFullCombo;
|
||||
public bool isAllPerfect;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4634e1beb428cb341974c13687d09bfa
|
||||
guid: 6b751bb37660d844780656591830afff
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -1,18 +1,26 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Ichni.RhythmGame;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
|
||||
public class SongSelectionManager : MonoBehaviour
|
||||
namespace Ichni.Menu
|
||||
{
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
public partial class SongSelectionManager : SerializedMonoBehaviour
|
||||
{
|
||||
|
||||
}
|
||||
public static SongSelectionManager instance;
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
instance = this;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public partial class SongSelectionManager
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
13
Assets/Scripts/Menu/SongSelection/SongStatusSave.cs
Normal file
13
Assets/Scripts/Menu/SongSelection/SongStatusSave.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Ichni.RhythmGame
|
||||
{
|
||||
public class SongStatusSave
|
||||
{
|
||||
public bool isCompleted;
|
||||
public string additionalInfo;
|
||||
public Dictionary<string, BeatmapSave> beatmapSaves;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c427891d163b0a5468cedefc05f055dc
|
||||
guid: 0e15d295a0ae77041ad0269ab3801bc3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -89,5 +89,15 @@ namespace Ichni
|
||||
// 最后赋值 target.anchoredPosition = localPos;
|
||||
return localPos;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取目标 RectTransform 在目标空间 RectTransform 中的本地位置
|
||||
/// </summary>
|
||||
public static Vector2 GetLocalUIPosition(RectTransform targetRect, RectTransform targetSpace)
|
||||
{
|
||||
Vector2 screenPos = RectTransformUtility.WorldToScreenPoint(null, targetRect.position);
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(targetSpace, screenPos, null, out Vector2 localPos);
|
||||
return localPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ namespace Ichni.Story
|
||||
|
||||
public Dictionary<string, List<string>> functionDictionary;
|
||||
public Dictionary<string, List<DialogSentence>> dialogDictionary;
|
||||
public Dictionary<string, List<Choice>> choiceDictionary;
|
||||
public Dictionary<string, ChoiceGroup> choiceDictionary;
|
||||
public Dictionary<string, List<Condition>> conditionDictionary;
|
||||
|
||||
private string currentLoadingDialog;
|
||||
@@ -41,32 +41,32 @@ namespace Ichni.Story
|
||||
|
||||
public void SetDialog(string dialogName)
|
||||
{
|
||||
TextAsset dialog = Resources.Load<TextAsset>("Dialogs/" + dialogName);
|
||||
SetDialog(new List<TextAsset> { dialog }, "Entry");
|
||||
string chapter = StoryManager.instance.currentChapter;
|
||||
TextAsset dialog = Resources.Load<TextAsset>("Story/" + chapter + "/Dialogs/" + dialogName);
|
||||
SetDialog(new List<TextAsset> { dialog });
|
||||
}
|
||||
|
||||
public void SetDialog(List<TextAsset> dialogFiles, string dialogParagraphName)
|
||||
public void SetDialog(List<TextAsset> dialogFiles, string dialogParagraphName = "")
|
||||
{
|
||||
dialogUIPage.FadeIn();
|
||||
|
||||
currentDialog = "NULL";
|
||||
isPlayingDialog = true;
|
||||
LoadDialog(dialogFiles);
|
||||
|
||||
if (!string.IsNullOrEmpty(dialogParagraphName))
|
||||
{
|
||||
currentDialog = dialogParagraphName;
|
||||
}
|
||||
|
||||
PlayNextDialogParagraph(currentDialog);
|
||||
currentDialog = "NULL";
|
||||
LoadDialog(dialogFiles, out string firstHeader);
|
||||
|
||||
Debug.Log($"Loaded dialog, first header: {firstHeader}");
|
||||
currentDialog = dialogParagraphName == "" ? firstHeader : dialogParagraphName;
|
||||
|
||||
Debug.Log($"Setting dialog to: {currentDialog}");
|
||||
}
|
||||
|
||||
public void PlayNextDialogParagraph(string nextDialog)
|
||||
public void PlayNextDialogParagraph(string nextDialog, bool invokeFunctions = true)
|
||||
{
|
||||
currentDialog = nextDialog;
|
||||
currentDialogSentenceIndex = 0;
|
||||
|
||||
if (functionDictionary.TryGetValue(currentDialog, out List<string> functionList))
|
||||
if (invokeFunctions && functionDictionary.TryGetValue(currentDialog, out List<string> functionList))
|
||||
{
|
||||
functionList.ForEach(x => StoryInterpreters.FunctionInterpreter.Eval(x));
|
||||
}
|
||||
@@ -83,11 +83,8 @@ namespace Ichni.Story
|
||||
{
|
||||
currentFinalType = "None";
|
||||
}
|
||||
|
||||
PlayDialog();
|
||||
}
|
||||
|
||||
[Button("Test Play")]
|
||||
public void PlayDialog()
|
||||
{
|
||||
if(currentDialog == "NULL")
|
||||
@@ -95,11 +92,10 @@ namespace Ichni.Story
|
||||
throw new Exception("Current dialog is NULL");
|
||||
}
|
||||
|
||||
/*if (dialogInterface.dialogTextFrame.isPlayingSentence)
|
||||
if (isPlayingChoice)
|
||||
{
|
||||
dialogInterface.dialogTextFrame.FinishSentence();
|
||||
return;
|
||||
}*/
|
||||
}
|
||||
|
||||
if (dialogDictionary[currentDialog].Count > 0 && currentDialogSentenceIndex < dialogDictionary[currentDialog].Count)
|
||||
{
|
||||
@@ -107,7 +103,7 @@ namespace Ichni.Story
|
||||
|
||||
string interpretedContent = currentSentence.GetInterpretedContent();
|
||||
|
||||
dialogUIPage.textFrame.PlaySentence(currentSentence.characterName, interpretedContent);
|
||||
dialogUIPage.dialogContentFrame.PlaySentence(currentSentence.characterName, interpretedContent);
|
||||
currentDialogSentenceIndex++;
|
||||
|
||||
if (currentDialogSentenceIndex <= dialogDictionary[currentDialog].Count)
|
||||
@@ -121,7 +117,7 @@ namespace Ichni.Story
|
||||
if (currentFinalType == "Choice")
|
||||
{
|
||||
isPlayingChoice = true;
|
||||
dialogUIPage.choiceFrame.PlayChoice(choiceDictionary[currentDialog]);
|
||||
dialogUIPage.dialogContentFrame.PlayChoice(choiceDictionary[currentDialog]);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -140,21 +136,65 @@ namespace Ichni.Story
|
||||
|
||||
if (currentFinalType == "None" && currentDialogSentenceIndex >= dialogDictionary[currentDialog].Count)
|
||||
{
|
||||
dialogUIPage.FadeOut();
|
||||
dialogUIPage.choiceFrame.gameObject.SetActive(false);
|
||||
//currentDialogNPC.priorStoryTexts.Remove(dialogTextAsset);
|
||||
//currentDialogNPC = null;
|
||||
StoryManager.instance.storyline.currentBlock.state = StoryBlockState.Completed;
|
||||
isPlayingDialog = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void RevealDialog()
|
||||
{
|
||||
string finalType;
|
||||
int max = 0;
|
||||
|
||||
do
|
||||
{
|
||||
finalType = currentFinalType;
|
||||
currentDialogSentenceIndex = 0;
|
||||
|
||||
foreach (DialogSentence sentence in dialogDictionary[currentDialog])
|
||||
{
|
||||
string interpretedContent = sentence.GetInterpretedContent();
|
||||
dialogUIPage.dialogContentFrame.PlaySentence(sentence.characterName, interpretedContent);
|
||||
currentDialogSentenceIndex++;
|
||||
}
|
||||
|
||||
if (finalType == "Choice")
|
||||
{
|
||||
ChoiceGroup choiceGroup = choiceDictionary[currentDialog];
|
||||
int choiceIndex = GameSaveManager.instance.StorySaveModule.selectedChoices[choiceGroup.choiceName];
|
||||
dialogUIPage.dialogContentFrame.SelectChoice(choiceGroup, choiceIndex);
|
||||
}
|
||||
|
||||
if (finalType == "Condition")
|
||||
{
|
||||
foreach (var condition in conditionDictionary[currentDialog])
|
||||
{
|
||||
if (condition.GetConditionResult())
|
||||
{
|
||||
PlayNextDialogParagraph(condition.nextDialogName, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
max++;
|
||||
|
||||
if (max > 1024)
|
||||
{
|
||||
throw new Exception("An infinite loop may detected in dialog parsing. Please check the dialog structure.");
|
||||
}
|
||||
|
||||
} while (finalType != "None");
|
||||
}
|
||||
}
|
||||
|
||||
public partial class DialogManager
|
||||
{
|
||||
public void LoadDialog(List<TextAsset> dialogFiles)
|
||||
public void LoadDialog(List<TextAsset> dialogFiles, out string firstHeader)
|
||||
{
|
||||
ClearDictionaries();
|
||||
|
||||
firstHeader = string.Empty;
|
||||
|
||||
dialogTextAssets = dialogFiles;
|
||||
List<string> dialogLines = new List<string>();
|
||||
|
||||
@@ -165,20 +205,34 @@ namespace Ichni.Story
|
||||
|
||||
dialogLines.RemoveAll(line => line.Trim() == "");
|
||||
|
||||
dialogLines.ForEach(Debug.Log);
|
||||
//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)
|
||||
foreach (string line in dialogLines)
|
||||
{
|
||||
throw new Exception($"Invalid dialog line: {line}"); // 抛出异常,提示不合法的对话行
|
||||
if (!ParseHeader(line))
|
||||
{
|
||||
if (!ParseChoiceModule(line))
|
||||
{
|
||||
if (!ParseConditionModule(line))
|
||||
{
|
||||
if (!ParseDialogSentence(line))
|
||||
{
|
||||
throw new Exception($"Invalid dialog line: {line}"); // 抛出异常,提示不合法的对话行
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (firstHeader == string.Empty)
|
||||
{
|
||||
firstHeader = currentDialog;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//dialogDictionary.RemoveWhere((header, sentences) => sentences == null || sentences.Count == 0);
|
||||
choiceDictionary.RemoveWhere((header, choices) => choices == null || choices.Count == 0);
|
||||
choiceDictionary.RemoveWhere((header, choices) => choices == null || choices.choices.Count == 0);
|
||||
conditionDictionary.RemoveWhere((header, conditions) => conditions == null || conditions.Count == 0);
|
||||
}
|
||||
|
||||
@@ -244,7 +298,7 @@ namespace Ichni.Story
|
||||
currentLoadingDialog = dialogTitle.Replace("[", "").Replace("]", "");
|
||||
|
||||
dialogDictionary.Add(currentLoadingDialog, new List<DialogSentence>());
|
||||
choiceDictionary.Add(currentLoadingDialog, new List<Choice>());
|
||||
//choiceDictionary.Add(currentLoadingDialog, new ChoiceGroup("Error"));
|
||||
conditionDictionary.Add(currentLoadingDialog, new List<Condition>());
|
||||
|
||||
if (currentDialog == "NULL")
|
||||
@@ -278,7 +332,7 @@ namespace Ichni.Story
|
||||
|
||||
public bool ParseDialogSentence(string line)
|
||||
{
|
||||
//speakerName(emotion):sentence
|
||||
//speakerName:sentence
|
||||
|
||||
string[] sentenceData;
|
||||
if (line.Contains(":"))
|
||||
@@ -291,30 +345,14 @@ namespace Ichni.Story
|
||||
}
|
||||
|
||||
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]
|
||||
characterName = speakerName.Trim(),
|
||||
content = sentenceData[1].Trim()
|
||||
};
|
||||
|
||||
|
||||
dialogDictionary[currentLoadingDialog].Add(dialogSentence);
|
||||
|
||||
return true;
|
||||
@@ -322,9 +360,9 @@ namespace Ichni.Story
|
||||
|
||||
public bool ParseChoiceModule(string line)
|
||||
{
|
||||
//$Choice{
|
||||
//choiceText0(Hint0)->[nextDialogName0];
|
||||
//choiceText1(Hint1)->[nextDialogName1];
|
||||
//$Choice(ChoiceName){
|
||||
//choiceText0->[nextDialogName0];
|
||||
//choiceText1->[nextDialogName1];
|
||||
//}
|
||||
|
||||
line = line.Trim();
|
||||
@@ -333,23 +371,21 @@ namespace Ichni.Story
|
||||
{
|
||||
string[] choiceModuleData = line.Split('{');
|
||||
|
||||
List<Choice> choices = new List<Choice>();
|
||||
string choiceName = choiceModuleData[0].Split('(')[1].Replace(")", "").Trim();
|
||||
ChoiceGroup choiceGroup = new ChoiceGroup(choiceName);
|
||||
|
||||
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(),
|
||||
};
|
||||
choiceData[index] = choiceData[index].Replace(" ", "").Replace("\n", "").Replace("\r", "").Trim();
|
||||
|
||||
string choiceText = choiceData[index].Split("->[")[0].Trim();
|
||||
string nextDialogName = choiceData[index].Split("->[")[1].Replace("]", "").Trim();
|
||||
|
||||
choices.Add(choice);
|
||||
choiceGroup.choices.Add(new Choice(choiceText, nextDialogName));
|
||||
}
|
||||
|
||||
choiceDictionary[currentLoadingDialog] = choices;
|
||||
choiceDictionary[currentLoadingDialog] = choiceGroup;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -10,19 +10,17 @@ namespace Ichni.Story
|
||||
public string audioEventName;
|
||||
|
||||
public string characterName;
|
||||
public string characterEmotion;
|
||||
|
||||
public DialogSentence()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public DialogSentence(string content, string audioEventName, string characterName, string characterEmotion)
|
||||
public DialogSentence(string content, string audioEventName, string characterName)
|
||||
{
|
||||
this.content = content;
|
||||
this.audioEventName = audioEventName;
|
||||
this.characterName = characterName;
|
||||
this.characterEmotion = characterEmotion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -62,12 +60,29 @@ namespace Ichni.Story
|
||||
return string.Join("", parts);
|
||||
}
|
||||
}
|
||||
|
||||
public class ChoiceGroup
|
||||
{
|
||||
public string choiceName;
|
||||
public List<Choice> choices;
|
||||
|
||||
public ChoiceGroup(string choiceName)
|
||||
{
|
||||
this.choiceName = choiceName;
|
||||
this.choices = new List<Choice>();
|
||||
}
|
||||
}
|
||||
|
||||
public class Choice
|
||||
{
|
||||
public string choiceText;
|
||||
public string hint;
|
||||
public string nextDialogName;
|
||||
|
||||
public Choice(string choiceText, string nextDialogName)
|
||||
{
|
||||
this.choiceText = choiceText;
|
||||
this.nextDialogName = nextDialogName;
|
||||
}
|
||||
}
|
||||
|
||||
public class Condition
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using DynamicExpresso;
|
||||
using Ichni.Story;
|
||||
using Ichni.Story.UI;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Ichni.Story
|
||||
@@ -21,23 +22,34 @@ namespace Ichni.Story
|
||||
|
||||
static void SetFunctionInterpreter()
|
||||
{
|
||||
FunctionInterpreter.SetFunction("GetGlobalVariable", new Func<string, int>(GetGlobalVariable));
|
||||
FunctionInterpreter.SetFunction("SetVariable", new Action<string, int>(SetStoryVariable));
|
||||
FunctionInterpreter.SetFunction("GetVariable", new Func<string, int>(GetStoryVariable));
|
||||
FunctionInterpreter.SetFunction("GenerateDialogBlock", new Action<string>(GenerateDialogBlock));
|
||||
FunctionInterpreter.SetFunction("GenerateSongBlock", new Action<string>(GenerateSongBlock));
|
||||
}
|
||||
|
||||
static void SetConditionInterpreter()
|
||||
{
|
||||
ConditionInterpreter.SetFunction("GetGlobalVariable", new Func<string, int>(GetGlobalVariable));
|
||||
ConditionInterpreter.SetFunction("GetVariable", new Func<string, int>(GetStoryVariable));
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class StoryInterpreters
|
||||
{
|
||||
/// <summary>
|
||||
/// 设置全局变量的值
|
||||
/// </summary>
|
||||
static void SetStoryVariable(string variableName, int value)
|
||||
{
|
||||
GameSaveManager.instance.StorySaveModule.storyVariables[variableName] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取全局变量的值
|
||||
/// </summary>
|
||||
static int GetGlobalVariable(string variableName)
|
||||
static int GetStoryVariable(string variableName)
|
||||
{
|
||||
if (StoryManager.instance.globalVariables.TryGetValue(variableName, out int value))
|
||||
if (GameSaveManager.instance.StorySaveModule.storyVariables.TryGetValue(variableName, out int value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
@@ -45,4 +57,23 @@ namespace Ichni.Story
|
||||
throw new ArgumentException($"Global variable '{variableName}' not found.");
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class StoryInterpreters
|
||||
{
|
||||
static void GenerateDialogBlock(string blockName)
|
||||
{
|
||||
StoryBlockUIBase currentBlock = StoryManager.instance.storyline.currentBlock;
|
||||
Vector2 positionOffset = new Vector2(500, 0);
|
||||
DialogBlockUI newBlock = StoryManager.instance.storyline.GenerateDialogBlock(blockName, currentBlock.blockPosition + positionOffset, StoryBlockState.Current);
|
||||
StoryManager.instance.storyline.GenerateConnector(currentBlock, newBlock);
|
||||
}
|
||||
|
||||
static void GenerateSongBlock(string blockName)
|
||||
{
|
||||
StoryBlockUIBase currentBlock = StoryManager.instance.storyline.currentBlock;
|
||||
Vector2 positionOffset = new Vector2(500, 0);
|
||||
SongBlockUI newBlock = StoryManager.instance.storyline.GenerateSongBlock(blockName, currentBlock.blockPosition + positionOffset, StoryBlockState.Current);
|
||||
StoryManager.instance.storyline.GenerateConnector(currentBlock, newBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,18 +8,32 @@ using UnityEngine.Serialization;
|
||||
|
||||
namespace Ichni.Story
|
||||
{
|
||||
public class StoryManager : SerializedMonoBehaviour
|
||||
public partial class StoryManager : SerializedMonoBehaviour
|
||||
{
|
||||
public static StoryManager instance;
|
||||
|
||||
[FormerlySerializedAs("storylineDisplay")] public Storyline storyline;
|
||||
public StoryUIPage storyUIPage;
|
||||
|
||||
public string currentChapter;
|
||||
public Dictionary<string, StoryData> storyDatas;
|
||||
|
||||
public StorylineDisplay storylineDisplay;
|
||||
[FormerlySerializedAs("StoryPage")] public StoryUIPage storyUIPage;
|
||||
|
||||
public Dictionary<string, int> globalVariables;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
instance = this;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class StoryManager
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public enum StoryBlockState
|
||||
{
|
||||
Locked,
|
||||
Current,
|
||||
Completed
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Ichni.Story
|
||||
{
|
||||
public class StoryBlock
|
||||
{
|
||||
public string blockName; // 单元格标识名
|
||||
public int rowIndex; // 剧情线编号
|
||||
public int timeColumn; // 时间列索引
|
||||
public bool isCompleted = false; // 完成状态
|
||||
public int requiredCount; // 前序节点未完成计数
|
||||
public StoryBlock nextBlock; // 下一个单元格
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Ichni.Story
|
||||
{
|
||||
public class StoryChapter : MonoBehaviour
|
||||
{
|
||||
public List<Storyline> storylines;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Ichni.Story
|
||||
{
|
||||
[CreateAssetMenu(fileName = "StoryData", menuName = "Ichni/Story/StoryData")]
|
||||
public class StoryData : SerializedScriptableObject
|
||||
{
|
||||
public List<StoryBlockData> StoryBlockDatas; // 剧情单元格名称列表
|
||||
public Dictionary<string, int> storyVariables; // 剧情变量字典,键为变量名,值为默认值,如果Save中没有该变量,则生成,并使用默认值
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class StoryBlockData
|
||||
{
|
||||
public string blockName;
|
||||
public string blockID;
|
||||
|
||||
public StoryBlockData(string blockName, string blockID)
|
||||
{
|
||||
this.blockName = blockName;
|
||||
this.blockID = blockID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7ae5431e0e2c6fe48ba5237e56a42769
|
||||
TextScriptImporter:
|
||||
guid: 31a6a9bf0919951489cacfb86fb715c9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
106
Assets/Scripts/Story/StoryData/StoryData.cs
Normal file
106
Assets/Scripts/Story/StoryData/StoryData.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Ichni.Story
|
||||
{
|
||||
[CreateAssetMenu(fileName = "StoryData", menuName = "Ichni/Story/StoryData")]
|
||||
public class StoryData : SerializedScriptableObject
|
||||
{
|
||||
public List<DialogBlockData> dialogBlockDatas; // 剧情单元格名称列表
|
||||
public List<SongBlockData> songBlockDatas; // 音乐单元格名称列表
|
||||
public List<TutorialBlockData> tutorialBlockDatas; // 教程单元格名称列表
|
||||
public List<InitialBlockData> initialBlocks; // 初始剧情单元格列表,包含所有初始剧情单元格的名称
|
||||
|
||||
public StoryBlockData GetDataByName(string blockName, out Type dataType)
|
||||
{
|
||||
foreach (var block in tutorialBlockDatas.Where(block => block.blockName == blockName))
|
||||
{
|
||||
dataType = typeof(TutorialBlockData);
|
||||
return block;
|
||||
}
|
||||
|
||||
foreach (var block in songBlockDatas.Where(block => block.blockName == blockName))
|
||||
{
|
||||
dataType = typeof(SongBlockData);
|
||||
return block;
|
||||
}
|
||||
|
||||
foreach (var block in dialogBlockDatas.Where(block => block.blockName == blockName))
|
||||
{
|
||||
dataType = typeof(DialogBlockData);
|
||||
return block;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"No block found with name: {blockName}");
|
||||
}
|
||||
}
|
||||
|
||||
[InlineProperty]
|
||||
[Serializable]
|
||||
public class InitialBlockData
|
||||
{
|
||||
public string blockName;
|
||||
public StoryBlockState initialState; // 初始状态
|
||||
public Vector2 blockPosition; // 初始位置
|
||||
public List<string> nextBlocks; // 下一步可选的剧情单元格名称列表
|
||||
}
|
||||
|
||||
[InlineProperty]
|
||||
[Serializable]
|
||||
public class StoryBlockData
|
||||
{
|
||||
[FoldoutGroup("$blockName", true)]
|
||||
public string blockName;
|
||||
[FoldoutGroup("$blockName")]
|
||||
public string blockID;
|
||||
[FoldoutGroup("$blockName")]
|
||||
public Vector2 blockSize;
|
||||
}
|
||||
|
||||
[InlineProperty]
|
||||
[Serializable]
|
||||
public class TutorialBlockData : StoryBlockData
|
||||
{
|
||||
[FoldoutGroup("$blockName")]
|
||||
public string tutorialName;
|
||||
|
||||
|
||||
public TutorialBlockData()
|
||||
{
|
||||
this.blockSize = new Vector2(400, 200);
|
||||
}
|
||||
}
|
||||
|
||||
[InlineProperty]
|
||||
[Serializable]
|
||||
public class DialogBlockData : StoryBlockData
|
||||
{
|
||||
[FoldoutGroup("$blockName")]
|
||||
public string dialogTitle;
|
||||
|
||||
|
||||
public DialogBlockData()
|
||||
{
|
||||
this.blockSize = new Vector2(400, 200);
|
||||
}
|
||||
}
|
||||
|
||||
[InlineProperty]
|
||||
[Serializable]
|
||||
public class SongBlockData : StoryBlockData
|
||||
{
|
||||
[FoldoutGroup("$blockName")]
|
||||
public string songName;
|
||||
|
||||
|
||||
public SongBlockData()
|
||||
{
|
||||
this.blockSize = new Vector2(400, 200);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 7ab917c50249812429ebd44d6574497c, type: 3}
|
||||
m_Name: StoryData_Chapter1
|
||||
m_EditorClassIdentifier:
|
||||
serializationData:
|
||||
SerializedFormat: 2
|
||||
SerializedBytes:
|
||||
ReferencedUnityObjects: []
|
||||
SerializedBytesString:
|
||||
Prefab: {fileID: 0}
|
||||
PrefabModificationsReferencedUnityObjects: []
|
||||
PrefabModifications: []
|
||||
SerializationNodes:
|
||||
- Name: storyVariables
|
||||
Entry: 7
|
||||
Data: 0|System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Int32,
|
||||
mscorlib]], mscorlib
|
||||
- Name: comparer
|
||||
Entry: 7
|
||||
Data: 1|System.Collections.Generic.GenericEqualityComparer`1[[System.String,
|
||||
mscorlib]], mscorlib
|
||||
- Name:
|
||||
Entry: 8
|
||||
Data:
|
||||
- Name:
|
||||
Entry: 12
|
||||
Data: 1
|
||||
- Name:
|
||||
Entry: 7
|
||||
Data:
|
||||
- Name: $k
|
||||
Entry: 1
|
||||
Data: TestVariable
|
||||
- Name: $v
|
||||
Entry: 3
|
||||
Data: 0
|
||||
- Name:
|
||||
Entry: 8
|
||||
Data:
|
||||
- Name:
|
||||
Entry: 13
|
||||
Data:
|
||||
- Name:
|
||||
Entry: 8
|
||||
Data:
|
||||
StoryBlockDatas:
|
||||
- blockName:
|
||||
blockID:
|
||||
blockPosition: {x: 0, y: 0}
|
||||
previousBlockIDs: []
|
||||
nextBlockIDs: []
|
||||
@@ -4,26 +4,50 @@ using UnityEngine;
|
||||
|
||||
namespace Ichni.Story
|
||||
{
|
||||
public class StorySave : MonoBehaviour
|
||||
{
|
||||
public StoryBlockSave blockSave;
|
||||
public StoryVariableSave variableSave;
|
||||
}
|
||||
|
||||
public class StoryBlockSave
|
||||
{
|
||||
public enum StoryBlockState
|
||||
public string blockName;
|
||||
public Vector2 position;
|
||||
public StoryBlockState state;
|
||||
|
||||
public StoryBlockSave(string blockName, Vector2 position, StoryBlockState state)
|
||||
{
|
||||
Locked,
|
||||
Current,
|
||||
Completed
|
||||
this.blockName = blockName;
|
||||
this.state = state;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public Dictionary<string, StoryBlockState> storyBlockStates = new Dictionary<string, StoryBlockState>();
|
||||
}
|
||||
|
||||
public class StoryVariableSave
|
||||
public class TutorialBlockSave : StoryBlockSave
|
||||
{
|
||||
public Dictionary<string, int> variables = new Dictionary<string, int>();
|
||||
public TutorialBlockSave(string blockName, Vector2 position, StoryBlockState state) : base(blockName, position, state)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class DialogBlockSave : StoryBlockSave
|
||||
{
|
||||
public DialogBlockSave(string blockName, Vector2 position, StoryBlockState state) : base(blockName, position, state)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class SongBlockSave : StoryBlockSave
|
||||
{
|
||||
public SongBlockSave(string blockName, Vector2 position, StoryBlockState state) : base(blockName, position, state)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class BlockConnectorSave
|
||||
{
|
||||
public string startBlockName;
|
||||
public string endBlockName;
|
||||
|
||||
public BlockConnectorSave(string startBlockName, string endBlockName)
|
||||
{
|
||||
this.startBlockName = startBlockName;
|
||||
this.endBlockName = endBlockName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6e95609f9d235c042a48dbabae121053
|
||||
NativeFormatImporter:
|
||||
guid: b2c23056fb0ab4b45a8db11866018794
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
18
Assets/Scripts/Story/StoryUI/Blocks/BeatmapStatusMark.cs
Normal file
18
Assets/Scripts/Story/StoryUI/Blocks/BeatmapStatusMark.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class BeatmapStatusMark : MonoBehaviour
|
||||
{
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6de7129c47df0eb47858a380f344dfe2
|
||||
guid: b547d2cc398393a46a2a4c503f128cac
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Ichni.Story.UI;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI.Extensions;
|
||||
|
||||
@@ -8,6 +9,8 @@ namespace Ichni.Story
|
||||
public class BlockConnectorUI : MonoBehaviour
|
||||
{
|
||||
public UILineRenderer curve;
|
||||
public StoryBlockUIBase startBlock;
|
||||
public StoryBlockUIBase endBlock;
|
||||
|
||||
public void SetCurve(Vector2 startPosition, Vector2 endPosition)
|
||||
{
|
||||
@@ -20,7 +23,7 @@ namespace Ichni.Story
|
||||
curve.Points = new Vector2[]
|
||||
{
|
||||
startPosition,
|
||||
mid1,
|
||||
//mid1,
|
||||
mid2,
|
||||
endPosition
|
||||
};
|
||||
51
Assets/Scripts/Story/StoryUI/Blocks/DialogBlockUI.cs
Normal file
51
Assets/Scripts/Story/StoryUI/Blocks/DialogBlockUI.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Ichni.Story.UI
|
||||
{
|
||||
public class DialogBlockUI : StoryBlockUIBase
|
||||
{
|
||||
public string blockTitle;
|
||||
public TMP_Text titleText;
|
||||
|
||||
public Button button;
|
||||
public List<ChoiceGroupUI> choiceGroups;
|
||||
|
||||
public void Initialize(string blockName, Vector2 position, Vector2 positionOffset,
|
||||
Vector2 size, StoryBlockState state, string blockTitle)
|
||||
{
|
||||
base.Initialize(blockName, position, positionOffset, size, state);
|
||||
|
||||
this.blockTitle = blockTitle;
|
||||
titleText.text = blockTitle;
|
||||
|
||||
button.onClick.AddListener(() =>
|
||||
{
|
||||
if(state == StoryBlockState.Locked) return;
|
||||
|
||||
StoryManager.instance.storyline.currentBlock = this;
|
||||
|
||||
if (state == StoryBlockState.Current)
|
||||
{
|
||||
DialogManager.instance.SetDialog(blockName);
|
||||
DialogManager.instance.PlayNextDialogParagraph(DialogManager.instance.currentDialog);
|
||||
}
|
||||
else if (state == StoryBlockState.Completed)
|
||||
{
|
||||
DialogManager.instance.SetDialog(blockName);
|
||||
DialogManager.instance.PlayNextDialogParagraph(DialogManager.instance.currentDialog, false);
|
||||
DialogManager.instance.RevealDialog();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public override StoryBlockSave GetBlockSave()
|
||||
{
|
||||
return new DialogBlockSave(blockName, blockPosition, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
78
Assets/Scripts/Story/StoryUI/Blocks/SongBlockUI.cs
Normal file
78
Assets/Scripts/Story/StoryUI/Blocks/SongBlockUI.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Ichni.Menu;
|
||||
using Ichni.RhythmGame;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Ichni.Story.UI
|
||||
{
|
||||
public class SongBlockUI : StoryBlockUIBase
|
||||
{
|
||||
public string songName;
|
||||
public Button button;
|
||||
public TMP_Text songNameText;
|
||||
public RectTransform beatmapStatusMarkContainer;
|
||||
|
||||
public GameObject beatmapStatusMarkPrefab;
|
||||
|
||||
public void Initialize(string blockName, Vector2 position, Vector2 positionOffset,
|
||||
Vector2 size, StoryBlockState state, string songName)
|
||||
{
|
||||
base.Initialize(blockName, position, positionOffset, size, state);
|
||||
|
||||
this.songName = songName;
|
||||
songNameText.text = songName;
|
||||
|
||||
button.onClick.AddListener(() =>
|
||||
{
|
||||
MenuManager.instance.prepareUIPage.FadeIn();
|
||||
});
|
||||
|
||||
SetUpBeatmapStatusMarks();
|
||||
}
|
||||
|
||||
public override StoryBlockSave GetBlockSave()
|
||||
{
|
||||
return new SongBlockSave(blockName, blockPosition, state);
|
||||
}
|
||||
|
||||
public void SetUpBeatmapStatusMarks()
|
||||
{
|
||||
SongStatusSave songStatusSave = GameSaveManager.instance.SongSaveModule.songStatusSaves[songName];
|
||||
|
||||
string chapter = StoryManager.instance.currentChapter;
|
||||
ChapterSelectionUnit cpt = ChapterSelectionManager.instance.chapters.First(c => c.chapterIndex == chapter);
|
||||
SongItemData song = cpt.songs.First(s => s.songName == this.songName);
|
||||
foreach (DifficultyData difficulty in song.difficultyDataList)
|
||||
{
|
||||
foreach (KeyValuePair<string, BeatmapSave> beatmapSave in songStatusSave.beatmapSaves)
|
||||
{
|
||||
if (beatmapSave.Key == difficulty.difficultyName)
|
||||
{
|
||||
if (beatmapSave.Value.isAllPerfect)
|
||||
{
|
||||
GameObject mark = Instantiate(beatmapStatusMarkPrefab, beatmapStatusMarkContainer);
|
||||
mark.GetComponent<Image>().color = difficulty.color;
|
||||
mark.transform.GetChild(0).GetComponent<TMP_Text>().color = difficulty.color;
|
||||
mark.transform.GetChild(0).GetComponent<TMP_Text>().text = "AP";
|
||||
break;
|
||||
}
|
||||
|
||||
if (beatmapSave.Value.isFullCombo)
|
||||
{
|
||||
GameObject mark = Instantiate(beatmapStatusMarkPrefab, beatmapStatusMarkContainer);
|
||||
mark.GetComponent<Image>().color = difficulty.color;
|
||||
mark.transform.GetChild(0).GetComponent<TMP_Text>().color = difficulty.color;
|
||||
mark.transform.GetChild(0).GetComponent<TMP_Text>().text = "FC";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Assets/Scripts/Story/StoryUI/Blocks/StoryBlockUIBase.cs
Normal file
30
Assets/Scripts/Story/StoryUI/Blocks/StoryBlockUIBase.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Ichni.Story.UI
|
||||
{
|
||||
public abstract class StoryBlockUIBase : MonoBehaviour
|
||||
{
|
||||
public string blockName;
|
||||
public Vector2 blockPosition;
|
||||
public StoryBlockState state;
|
||||
|
||||
public RectTransform blockRect;
|
||||
public RectTransform inPort;
|
||||
public RectTransform outPort;
|
||||
|
||||
protected void Initialize(string blockName, Vector2 position, Vector2 positionOffset, Vector2 size, StoryBlockState state)
|
||||
{
|
||||
this.blockName = blockName;
|
||||
this.blockPosition = position;
|
||||
this.state = state;
|
||||
|
||||
blockRect.anchoredPosition = position + positionOffset;
|
||||
blockRect.sizeDelta = size;
|
||||
}
|
||||
|
||||
public abstract StoryBlockSave GetBlockSave();
|
||||
}
|
||||
}
|
||||
34
Assets/Scripts/Story/StoryUI/Blocks/TutorialBlockUI.cs
Normal file
34
Assets/Scripts/Story/StoryUI/Blocks/TutorialBlockUI.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Ichni.Story.UI
|
||||
{
|
||||
public class TutorialBlockUI : StoryBlockUIBase
|
||||
{
|
||||
public Button button;
|
||||
public string tutorialName;
|
||||
public TMP_Text tutorialNameText;
|
||||
|
||||
public void Initialize(string blockName, Vector2 position, Vector2 positionOffset, Vector2 size, StoryBlockState state, string tutorialName)
|
||||
{
|
||||
base.Initialize(blockName, position, positionOffset, size, state);
|
||||
|
||||
this.tutorialName = tutorialName;
|
||||
tutorialNameText.text = tutorialName;
|
||||
|
||||
button.onClick.AddListener(() =>
|
||||
{
|
||||
//DialogManager.instance.SetDialog(blockName);
|
||||
});
|
||||
}
|
||||
|
||||
public override StoryBlockSave GetBlockSave()
|
||||
{
|
||||
return new TutorialBlockSave(blockName, blockPosition, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Ichni.Story
|
||||
namespace Ichni.Story.UI
|
||||
{
|
||||
public class Storyline : MonoBehaviour
|
||||
public class ChoiceButtonUI : MonoBehaviour
|
||||
{
|
||||
|
||||
}
|
||||
11
Assets/Scripts/Story/StoryUI/DialogUI/ChoiceButtonUI.cs.meta
Normal file
11
Assets/Scripts/Story/StoryUI/DialogUI/ChoiceButtonUI.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 90f34f59ff260c44796d71c51b7c0ee6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
45
Assets/Scripts/Story/StoryUI/DialogUI/ChoiceGroupUI.cs
Normal file
45
Assets/Scripts/Story/StoryUI/DialogUI/ChoiceGroupUI.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using I2.Loc;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Ichni.Story.UI
|
||||
{
|
||||
public class ChoiceGroupUI : MonoBehaviour
|
||||
{
|
||||
public GameObject choiceButtonPrefab;
|
||||
public RectTransform container;
|
||||
|
||||
public List<Button> choiceButtonList;
|
||||
|
||||
public string choiceName;
|
||||
public int choiceIndex;
|
||||
|
||||
public void Initialize(ChoiceGroup choiceGroup)
|
||||
{
|
||||
this.choiceName = choiceGroup.choiceName;
|
||||
choiceButtonList = new List<Button>();
|
||||
|
||||
for (var index = 0; index < choiceGroup.choices.Count; index++)
|
||||
{
|
||||
var choice = choiceGroup.choices[index];
|
||||
int cIndex = index; // Capture the current index for the listener
|
||||
|
||||
GameObject choiceButton = Instantiate(choiceButtonPrefab, container);
|
||||
choiceButton.GetComponentInChildren<Localize>().SetTerm(StoryManager.instance.currentChapter + "/" + choice.choiceText);
|
||||
choiceButton.GetComponent<Button>().onClick.AddListener(() =>
|
||||
{
|
||||
DialogManager.instance.PlayNextDialogParagraph(choice.nextDialogName);
|
||||
DialogManager.instance.isPlayingChoice = false;
|
||||
choiceButtonList.ForEach(b => b.interactable = false);
|
||||
DialogManager.instance.PlayDialog();
|
||||
this.choiceIndex = cIndex;
|
||||
GameSaveManager.instance.StorySaveModule.selectedChoices[choiceName] = cIndex;
|
||||
});
|
||||
choiceButtonList.Add(choiceButton.GetComponent<Button>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Story/StoryUI/DialogUI/ChoiceGroupUI.cs.meta
Normal file
11
Assets/Scripts/Story/StoryUI/DialogUI/ChoiceGroupUI.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a653477cd0de8794b810214793b04cc9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
81
Assets/Scripts/Story/StoryUI/DialogUI/DialogContentFrame.cs
Normal file
81
Assets/Scripts/Story/StoryUI/DialogUI/DialogContentFrame.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using DG.Tweening;
|
||||
using DG.Tweening.Core;
|
||||
using DG.Tweening.Plugins.Options;
|
||||
using I2.Loc;
|
||||
using Ichni.Story.UI;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.Serialization;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Ichni.Story
|
||||
{
|
||||
public class DialogContentFrame : MonoBehaviour, IPointerClickHandler
|
||||
{
|
||||
public GameObject textPrefab;
|
||||
public GameObject choiceGroupPrefab;
|
||||
|
||||
public RectTransform dialogContentContainer;
|
||||
public List<DialogTextUI> dialogTexts;
|
||||
public List<ChoiceGroupUI> choiceGroups;
|
||||
|
||||
public void PlaySentence(string speakerName, string content)
|
||||
{
|
||||
DialogTextUI dialogTextUI = Instantiate(textPrefab, dialogContentContainer).GetComponent<DialogTextUI>();
|
||||
dialogTextUI.speakerNameText.SetTerm("Characters/" + speakerName);
|
||||
dialogTextUI.contentText.SetTerm(StoryManager.instance.currentChapter +"/" +content);
|
||||
dialogTexts.Add(dialogTextUI);
|
||||
}
|
||||
|
||||
public ChoiceGroupUI PlayChoice(ChoiceGroup choiceGroup)
|
||||
{
|
||||
ChoiceGroupUI choiceGroupUI = Instantiate(choiceGroupPrefab, dialogContentContainer).GetComponent<ChoiceGroupUI>();
|
||||
choiceGroupUI.Initialize(choiceGroup);
|
||||
choiceGroups.Add(choiceGroupUI);
|
||||
|
||||
return choiceGroupUI;
|
||||
}
|
||||
|
||||
public void SelectChoice(ChoiceGroup choiceGroup, int index)
|
||||
{
|
||||
ChoiceGroupUI choiceGroupUI = PlayChoice(choiceGroup);
|
||||
for (var buttonIndex = 0; buttonIndex < choiceGroupUI.choiceButtonList.Count; buttonIndex++)
|
||||
{
|
||||
Button b = choiceGroupUI.choiceButtonList[buttonIndex];
|
||||
b.interactable = false;
|
||||
|
||||
if (buttonIndex == index)
|
||||
{
|
||||
b.image.color = Color.red;
|
||||
}
|
||||
}
|
||||
|
||||
DialogManager.instance.PlayNextDialogParagraph(choiceGroup.choices[index].nextDialogName, false);
|
||||
}
|
||||
|
||||
public void ClearAllSentences()
|
||||
{
|
||||
foreach (DialogTextUI dialogText in dialogTexts)
|
||||
{
|
||||
Destroy(dialogText.gameObject);
|
||||
}
|
||||
|
||||
foreach (ChoiceGroupUI choiceGroup in choiceGroups)
|
||||
{
|
||||
Destroy(choiceGroup.gameObject);
|
||||
}
|
||||
|
||||
dialogTexts.Clear();
|
||||
choiceGroups.Clear();
|
||||
}
|
||||
|
||||
public void OnPointerClick(PointerEventData eventData)
|
||||
{
|
||||
DialogManager.instance.PlayDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Assets/Scripts/Story/StoryUI/DialogUI/DialogTextUI.cs
Normal file
16
Assets/Scripts/Story/StoryUI/DialogUI/DialogTextUI.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using I2.Loc;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Ichni.Story.UI
|
||||
{
|
||||
public class DialogTextUI : MonoBehaviour
|
||||
{
|
||||
public Image background;
|
||||
public Localize speakerNameText;
|
||||
public Localize contentText;
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Story/StoryUI/DialogUI/DialogTextUI.cs.meta
Normal file
11
Assets/Scripts/Story/StoryUI/DialogUI/DialogTextUI.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 831ccff3dc06bfc4884663d623af866d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -10,7 +10,6 @@ namespace Ichni.Story.UI
|
||||
{
|
||||
public class DialogUIPage : UIPageBase
|
||||
{
|
||||
public TextFrame textFrame;
|
||||
public ChoiceFrame choiceFrame;
|
||||
public DialogContentFrame dialogContentFrame;
|
||||
}
|
||||
}
|
||||
279
Assets/Scripts/Story/StoryUI/Storyline.cs
Normal file
279
Assets/Scripts/Story/StoryUI/Storyline.cs
Normal file
@@ -0,0 +1,279 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Ichni.Story.UI
|
||||
{
|
||||
public partial class Storyline : MonoBehaviour
|
||||
{
|
||||
[Header("UI References")]
|
||||
public RectTransform content; // Content of ScrollRect
|
||||
[FormerlySerializedAs("textBlockPrefab")] public GameObject dialogBlockPrefab; // Prefab of UI Node
|
||||
public GameObject musicBlockPrefab;
|
||||
public GameObject tutorialBlockPrefab;
|
||||
public GameObject connectionCurvePrefab; // Prefab of connection curve
|
||||
|
||||
[Header("Layout Settings")]
|
||||
public float marginLeft = 50f; // additive space on the left
|
||||
public float marginRight = 50f; // Extra space on the right
|
||||
public float marginTop = 50f; // Extra space on the top
|
||||
public float marginBottom = 50f; // Extra space on the bottom
|
||||
public RectTransform connectionContainer;
|
||||
|
||||
public StoryBlockUIBase currentBlock;
|
||||
|
||||
public List<StoryBlockUIBase> storyBlocks;
|
||||
public List<DialogBlockUI> dialogBlocks;
|
||||
public List<SongBlockUI> songBlocks;
|
||||
public List<TutorialBlockUI> tutorialBlocks;
|
||||
public List<BlockConnectorUI> connectors;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
storyBlocks = new List<StoryBlockUIBase>();
|
||||
dialogBlocks = new List<DialogBlockUI>();
|
||||
songBlocks = new List<SongBlockUI>();
|
||||
tutorialBlocks = new List<TutorialBlockUI>();
|
||||
connectors = new List<BlockConnectorUI>();
|
||||
|
||||
//TutorialBlockUI t0 = GenerateTutorialBlock(new Vector2(200, -400), "ZakoCurse 0");
|
||||
//TextBlockUI b1 = GenerateTextBlock("Departure_P1_A", new Vector2(1000, -400), StoryBlockState.Current);
|
||||
|
||||
SetUpStoryline(StoryManager.instance.currentChapter);
|
||||
|
||||
/*GenerateTextBlock("Departure_P1_A", new Vector2(1000, -400), StoryBlockState.Current);
|
||||
GenerateTextBlock("Departure_P2_A", new Vector2(1500, -400), StoryBlockState.Current);
|
||||
GenerateConnector("Departure_P1_A", "Departure_P2_A");*/
|
||||
|
||||
SetUpBackground();
|
||||
connectionContainer.SetParent(content);
|
||||
connectionContainer.SetAsFirstSibling();
|
||||
}
|
||||
}
|
||||
|
||||
public partial class Storyline
|
||||
{
|
||||
public TutorialBlockUI GenerateTutorialBlock(string blockName, Vector2 position, StoryBlockState state)
|
||||
{
|
||||
TutorialBlockUI block = Instantiate(tutorialBlockPrefab, content).GetComponent<TutorialBlockUI>();
|
||||
StoryData storyData = StoryManager.instance.storyDatas[StoryManager.instance.currentChapter];
|
||||
TutorialBlockData blockData = storyData.tutorialBlockDatas.FirstOrDefault(data => data.blockName == blockName);
|
||||
|
||||
if (blockData == null) throw new KeyNotFoundException("There is no block with name " + blockName);
|
||||
|
||||
block.Initialize(blockData.blockName, position, new Vector2(marginLeft, 0), blockData.blockSize, state, blockData.tutorialName);
|
||||
|
||||
storyBlocks.Add(block);
|
||||
tutorialBlocks.Add(block);
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
public DialogBlockUI GenerateDialogBlock(string blockName, Vector2 position, StoryBlockState state)
|
||||
{
|
||||
DialogBlockUI block = Instantiate(dialogBlockPrefab, content).GetComponent<DialogBlockUI>();
|
||||
StoryData storyData = StoryManager.instance.storyDatas[StoryManager.instance.currentChapter];
|
||||
DialogBlockData blockData = storyData.dialogBlockDatas.FirstOrDefault(data => data.blockName == blockName);
|
||||
|
||||
if (blockData == null) throw new KeyNotFoundException("There is no block with name " + blockName);
|
||||
|
||||
block.Initialize(blockData.blockName, position, new Vector2(marginLeft, 0), blockData.blockSize, state, blockData.dialogTitle);
|
||||
|
||||
storyBlocks.Add(block);
|
||||
dialogBlocks.Add(block);
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
public SongBlockUI GenerateSongBlock(string blockName, Vector2 position, StoryBlockState state)
|
||||
{
|
||||
SongBlockUI block = Instantiate(musicBlockPrefab, content).GetComponent<SongBlockUI>();
|
||||
StoryData storyData = StoryManager.instance.storyDatas[StoryManager.instance.currentChapter];
|
||||
SongBlockData blockData = storyData.songBlockDatas.FirstOrDefault(data => data.blockName == blockName);
|
||||
|
||||
if (blockData == null) throw new KeyNotFoundException("There is no block with name " + blockName);
|
||||
|
||||
block.Initialize(blockName,position,new Vector2(marginLeft, 0), blockData.blockSize, state, blockData.songName);
|
||||
|
||||
storyBlocks.Add(block);
|
||||
songBlocks.Add(block);
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
public void GenerateConnector(StoryBlockUIBase startBlock, StoryBlockUIBase endBlock)
|
||||
{
|
||||
BlockConnectorUI connector = Instantiate(connectionCurvePrefab, connectionContainer).GetComponent<BlockConnectorUI>();
|
||||
|
||||
Vector2 startPosition = SpaceConverter.GetLocalUIPosition(startBlock.outPort, GetComponent<RectTransform>());
|
||||
Vector2 endPosition = SpaceConverter.GetLocalUIPosition(endBlock.inPort, GetComponent<RectTransform>());
|
||||
|
||||
connector.startBlock = startBlock;
|
||||
connector.endBlock = endBlock;
|
||||
|
||||
connector.SetCurve(startPosition, endPosition);
|
||||
connectors.Add(connector);
|
||||
}
|
||||
|
||||
public void GenerateConnector(string startBlockName, string endBlockName)
|
||||
{
|
||||
StoryBlockUIBase startBlock = storyBlocks.FirstOrDefault(block => block.blockName == startBlockName);
|
||||
StoryBlockUIBase endBlock = storyBlocks.FirstOrDefault(block => block.blockName == endBlockName);
|
||||
GenerateConnector(startBlock, endBlock);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class Storyline
|
||||
{
|
||||
private void ClearStoryline()
|
||||
{
|
||||
foreach (var block in storyBlocks)
|
||||
{
|
||||
Destroy(block.gameObject);
|
||||
}
|
||||
storyBlocks.Clear();
|
||||
dialogBlocks.Clear();
|
||||
songBlocks.Clear();
|
||||
tutorialBlocks.Clear();
|
||||
|
||||
foreach (var connector in connectors)
|
||||
{
|
||||
Destroy(connector.gameObject);
|
||||
}
|
||||
connectors.Clear();
|
||||
|
||||
content.sizeDelta = Vector2.zero;
|
||||
}
|
||||
|
||||
private void SetUpBackground()
|
||||
{
|
||||
float maxRight = float.MinValue;
|
||||
|
||||
foreach (var block in storyBlocks)
|
||||
{
|
||||
float rightEdge = block.blockRect.anchoredPosition.x + block.blockRect.sizeDelta.x * 0.5f;
|
||||
if (rightEdge > maxRight)
|
||||
{
|
||||
maxRight = rightEdge;
|
||||
}
|
||||
}
|
||||
|
||||
maxRight += marginRight;
|
||||
|
||||
if (maxRight < 2560f)
|
||||
{
|
||||
maxRight = 2560f;
|
||||
}
|
||||
|
||||
|
||||
float lowY = float.MaxValue;
|
||||
|
||||
foreach (var block in storyBlocks)
|
||||
{
|
||||
float bottomEdge = block.blockRect.anchoredPosition.y - block.blockRect.sizeDelta.y * 0.5f;
|
||||
if (bottomEdge < lowY)
|
||||
{
|
||||
lowY = bottomEdge;
|
||||
}
|
||||
}
|
||||
|
||||
float maxHeight = Mathf.Abs(lowY) + marginTop + marginBottom;
|
||||
|
||||
if (maxHeight < 1440f)
|
||||
{
|
||||
maxHeight = 1440f;
|
||||
}
|
||||
|
||||
content.sizeDelta = new Vector2(maxRight, maxHeight);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class Storyline
|
||||
{
|
||||
public void SetUpStoryline(string chapterName)
|
||||
{
|
||||
GameSaveManager.instance.StorySaveModule.LoadStoryline(chapterName);
|
||||
|
||||
foreach (var blockSave in GameSaveManager.instance.StorySaveModule.tutorialBlockSaves[chapterName])
|
||||
{
|
||||
GenerateTutorialBlock(blockSave.blockName, blockSave.position, blockSave.state);
|
||||
}
|
||||
|
||||
foreach (var blockSave in GameSaveManager.instance.StorySaveModule.songBlockSaves[chapterName])
|
||||
{
|
||||
GenerateSongBlock(blockSave.blockName, blockSave.position, blockSave.state);
|
||||
}
|
||||
|
||||
foreach (var blockSave in GameSaveManager.instance.StorySaveModule.dialogBlockSaves[chapterName])
|
||||
{
|
||||
GenerateDialogBlock(blockSave.blockName, blockSave.position, blockSave.state);
|
||||
}
|
||||
|
||||
foreach (var connectorSave in GameSaveManager.instance.StorySaveModule.connectorSaves[chapterName])
|
||||
{
|
||||
GenerateConnector(connectorSave.startBlockName, connectorSave.endBlockName);
|
||||
}
|
||||
}
|
||||
|
||||
[Button]
|
||||
public void SaveStoryline(string chapterName)
|
||||
{
|
||||
List<TutorialBlockSave> tutorialBlockSaves =
|
||||
tutorialBlocks.Select(block => block.GetBlockSave() as TutorialBlockSave).ToList();
|
||||
|
||||
List<SongBlockSave> songBlockSaves =
|
||||
songBlocks.Select(block => block.GetBlockSave() as SongBlockSave).ToList();
|
||||
|
||||
List<DialogBlockSave> dialogBlockSaves =
|
||||
dialogBlocks.Select(block => block.GetBlockSave() as DialogBlockSave).ToList();
|
||||
|
||||
List<BlockConnectorSave> connectorSaves =
|
||||
connectors.Select(connector => new BlockConnectorSave(connector.startBlock.blockName, connector.endBlock.blockName)).ToList();
|
||||
|
||||
GameSaveManager.instance.StorySaveModule.SaveStoryline(
|
||||
chapterName, tutorialBlockSaves, songBlockSaves, dialogBlockSaves, connectorSaves);
|
||||
}
|
||||
|
||||
[Button]
|
||||
public void ResetStory()
|
||||
{
|
||||
ClearStoryline();
|
||||
|
||||
StoryData storyData = StoryManager.instance.storyDatas[StoryManager.instance.currentChapter];
|
||||
List<InitialBlockData> initialBlocks = storyData.initialBlocks;
|
||||
|
||||
foreach (InitialBlockData blockData in initialBlocks)
|
||||
{
|
||||
storyData.GetDataByName(blockData.blockName, out Type dataType);
|
||||
|
||||
if (dataType == typeof(TutorialBlockData))
|
||||
{
|
||||
GenerateTutorialBlock(blockData.blockName, blockData.blockPosition, blockData.initialState);
|
||||
}
|
||||
else if (dataType == typeof(DialogBlockData))
|
||||
{
|
||||
GenerateDialogBlock(blockData.blockName, blockData.blockPosition, blockData.initialState);
|
||||
}
|
||||
else if (dataType == typeof(SongBlockData))
|
||||
{
|
||||
GenerateSongBlock(blockData.blockName, blockData.blockPosition, blockData.initialState);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (InitialBlockData blockData in initialBlocks)
|
||||
{
|
||||
foreach (string nextBlockName in blockData.nextBlocks)
|
||||
{
|
||||
GenerateConnector(blockData.blockName, nextBlockName);
|
||||
}
|
||||
}
|
||||
|
||||
SetUpBackground();
|
||||
SaveStoryline(StoryManager.instance.currentChapter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using CsvHelper;
|
||||
using CsvHelper.Configuration;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Ichni.Story
|
||||
{
|
||||
public class StorylineSheetReader : SerializedMonoBehaviour
|
||||
{
|
||||
public TextAsset csvFile; // 直接拖拽到 Inspector
|
||||
public List<StoryBlock> allNodes;
|
||||
|
||||
void Awake() {
|
||||
if (csvFile == null) {
|
||||
Debug.LogError("请在 Inspector 中指定 CSV TextAsset 文件。");
|
||||
return;
|
||||
}
|
||||
|
||||
allNodes = new List<StoryBlock>();
|
||||
|
||||
using (var reader = new StringReader(csvFile.text)) {
|
||||
var config = new CsvConfiguration(CultureInfo.InvariantCulture) {
|
||||
HasHeaderRecord = false,
|
||||
IgnoreBlankLines = true
|
||||
};
|
||||
|
||||
using (var csv = new CsvReader(reader, config)) {
|
||||
int rowIndex = 0;
|
||||
while (csv.Read()) {
|
||||
for (int col = 0; csv.TryGetField<string>(col, out var cell); col++) {
|
||||
if (!string.IsNullOrWhiteSpace(cell)) {
|
||||
allNodes.Add(new StoryBlock() {
|
||||
blockName = cell.Trim(),
|
||||
rowIndex = rowIndex,
|
||||
timeColumn = col
|
||||
});
|
||||
}
|
||||
}
|
||||
rowIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
BuildDependencies();
|
||||
}
|
||||
|
||||
void BuildDependencies() {
|
||||
var groups = allNodes.GroupBy(n => n.rowIndex);
|
||||
foreach (var group in groups) {
|
||||
var list = group.OrderBy(n => n.timeColumn).ToList();
|
||||
for (int i = 1; i < list.Count; i++) {
|
||||
var prev = list[i - 1];
|
||||
var curr = list[i];
|
||||
prev.nextBlock = curr; // 设置前一个单元格的下一个单元格
|
||||
curr.requiredCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
Tutorial,L1-P1,L1-P2,L1-P3,L1-P4
|
||||
,,L2-P1,L2-P2,
|
||||
,,,,L3-P1
|
||||
|
@@ -1,48 +0,0 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Michsky.MUIP;
|
||||
using Sirenix.Utilities;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Ichni.Story
|
||||
{
|
||||
public class ChoiceFrame : MonoBehaviour
|
||||
{
|
||||
public GameObject choiceButtonPrefab;
|
||||
public RectTransform choiceButtonContainer;
|
||||
public List<Button> choiceButtonList;
|
||||
public TooltipContent hint;
|
||||
|
||||
public void PlayChoice(List<Choice> choices)
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
choiceButtonContainer.GetAllChildren().ForEach(x => Destroy(x.gameObject));
|
||||
choiceButtonList.Clear();
|
||||
|
||||
/*if (choiceModule.hint != "")
|
||||
{
|
||||
hint.gameObject.SetActive(true);
|
||||
hint.description = choiceModule.hint;
|
||||
}
|
||||
else
|
||||
{
|
||||
hint.gameObject.SetActive(false);
|
||||
}*/
|
||||
|
||||
foreach (var choice in choices)
|
||||
{
|
||||
GameObject choiceButton = Instantiate(choiceButtonPrefab, choiceButtonContainer);
|
||||
choiceButton.GetComponentInChildren<TMP_Text>().text = choice.choiceText;
|
||||
choiceButton.GetComponent<Button>().onClick.AddListener(() =>
|
||||
{
|
||||
DialogManager.instance.PlayNextDialogParagraph(choice.nextDialogName);
|
||||
DialogManager.instance.isPlayingChoice = false;
|
||||
gameObject.SetActive(false);
|
||||
});
|
||||
choiceButtonList.Add(choiceButton.GetComponent<Button>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 62ce59e989ba99d4ca0407641e0ae2f2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,38 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using DG.Tweening;
|
||||
using DG.Tweening.Core;
|
||||
using DG.Tweening.Plugins.Options;
|
||||
using I2.Loc;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Ichni.Story
|
||||
{
|
||||
public class TextFrame : MonoBehaviour
|
||||
{
|
||||
public GameObject textPrefab;
|
||||
public RectTransform textContainer;
|
||||
|
||||
private TweenerCore<string, string, StringOptions> contentTween;
|
||||
public bool isPlayingSentence;
|
||||
|
||||
public void PlaySentence(string speakerName, string content)
|
||||
{
|
||||
GameObject contentText = Instantiate(textPrefab, textContainer);
|
||||
contentText.transform.GetChild(1).GetComponent<TMP_Text>().text = speakerName;
|
||||
contentText.transform.GetChild(2).GetComponent<TMP_Text>().text = content;
|
||||
//contentText.GetComponent<Localize>().OnLocalize();
|
||||
}
|
||||
|
||||
public void FinishSentence()
|
||||
{
|
||||
if (isPlayingSentence)
|
||||
{
|
||||
contentTween.Complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Ichni.Story.UI
|
||||
{
|
||||
public class SongBlockUI : StoryBlockUIBase
|
||||
{
|
||||
public string blockName;
|
||||
public string songName;
|
||||
public Button button;
|
||||
public TMP_Text musicText;
|
||||
|
||||
public void Initialize(string blockName, string songName)
|
||||
{
|
||||
this.blockName = blockName;
|
||||
this.songName = songName;
|
||||
|
||||
musicText.text = songName;
|
||||
button.onClick.AddListener(() =>
|
||||
{
|
||||
MenuManager.instance.prepareUIPage.FadeIn();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Ichni.Story.UI
|
||||
{
|
||||
public class StoryBlockUIBase : MonoBehaviour
|
||||
{
|
||||
public RectTransform blockRect;
|
||||
public RectTransform inPort;
|
||||
public RectTransform outPort;
|
||||
}
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Ichni.Story.UI
|
||||
{
|
||||
public class StorylineDisplay : MonoBehaviour
|
||||
{
|
||||
[Header("UI References")]
|
||||
public RectTransform content; // Content of ScrollRect
|
||||
public GameObject textBlockPrefab; // Prefab of UI Node
|
||||
public GameObject musicBlockPrefab;
|
||||
public GameObject tutorialBlockPrefab;
|
||||
public GameObject connectionCurvePrefab; // Prefab of connection curve
|
||||
|
||||
[Header("Layout Settings")]
|
||||
public float marginLeft = 50f; // additive space on the left
|
||||
public float marginRight = 50f; // Extra space on the right
|
||||
public RectTransform connectionContainer;
|
||||
|
||||
public List<StoryBlockUIBase> generatedBlocks;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
generatedBlocks = new List<StoryBlockUIBase>();
|
||||
TutorialBlockUI t0 = GenerateTutorialBlock(new Vector2(100, 0), "ZakoCurse 0");
|
||||
TextBlockUI b1 = GenerateTextBlock(new Vector2(600, 200), "ZakoCurse 0");
|
||||
TextBlockUI b2 = GenerateTextBlock(new Vector2(1100, -200), "ZakoCurse 0");
|
||||
SongBlockUI m0 = GenerateMusicBlock(new Vector2(1600, 0), "Chaos Zone", "Chaos Zone");
|
||||
TextBlockUI b3_1 = GenerateTextBlock(new Vector2(2100, 300), "ZakoCurse 0");
|
||||
TextBlockUI b3_2 = GenerateTextBlock(new Vector2(2600, -300), "ZakoCurse 0");
|
||||
|
||||
// Generate connections
|
||||
GenerateConnector(t0, b1);
|
||||
GenerateConnector(b1, b2);
|
||||
GenerateConnector(b2, m0);
|
||||
GenerateConnector(m0, b3_1);
|
||||
GenerateConnector(m0, b3_2);
|
||||
|
||||
SetUpBackground();
|
||||
connectionContainer.SetParent(content);
|
||||
}
|
||||
|
||||
public TextBlockUI GenerateTextBlock(Vector2 position, string blockName)
|
||||
{
|
||||
return GenerateTextBlock(position, new Vector2(300, 200), blockName);
|
||||
}
|
||||
|
||||
public TextBlockUI GenerateTextBlock(Vector2 position, Vector2 size, string blockName)
|
||||
{
|
||||
TextBlockUI block = Instantiate(textBlockPrefab, content).GetComponent<TextBlockUI>();
|
||||
|
||||
// Set position and size
|
||||
block.blockRect.anchoredPosition = position + new Vector2(marginLeft, 0);
|
||||
block.blockRect.sizeDelta = size;
|
||||
|
||||
block.Initialize(blockName, "Zako Curse 0");
|
||||
generatedBlocks.Add(block);
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
public TutorialBlockUI GenerateTutorialBlock(Vector2 position, string blockName)
|
||||
{
|
||||
return GenerateTutorialBlock(position, new Vector2(300, 200), blockName);
|
||||
}
|
||||
|
||||
public TutorialBlockUI GenerateTutorialBlock(Vector2 position, Vector2 size, string blockName)
|
||||
{
|
||||
TutorialBlockUI block = Instantiate(tutorialBlockPrefab, content).GetComponent<TutorialBlockUI>();
|
||||
|
||||
// Set position and size
|
||||
block.blockRect.anchoredPosition = position + new Vector2(marginLeft, 0);
|
||||
block.blockRect.sizeDelta = size;
|
||||
|
||||
block.Initialize(blockName);
|
||||
generatedBlocks.Add(block);
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
public SongBlockUI GenerateMusicBlock(Vector2 position, string blockName, string musicName)
|
||||
{
|
||||
return GenerateMusicBlock(position, new Vector2(200, 100), blockName, musicName);
|
||||
}
|
||||
|
||||
public SongBlockUI GenerateMusicBlock(Vector2 position, Vector2 size, string blockName, string musicName)
|
||||
{
|
||||
SongBlockUI block = Instantiate(musicBlockPrefab, content).GetComponent<SongBlockUI>();
|
||||
|
||||
// Set position and size
|
||||
block.blockRect.anchoredPosition = position + new Vector2(marginLeft, 0);
|
||||
block.blockRect.sizeDelta = size;
|
||||
|
||||
block.Initialize(blockName, musicName);
|
||||
generatedBlocks.Add(block);
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
public void GenerateConnector(StoryBlockUIBase outBlock, StoryBlockUIBase inBlock)
|
||||
{
|
||||
BlockConnectorUI connector = Instantiate(connectionCurvePrefab, connectionContainer).GetComponent<BlockConnectorUI>();
|
||||
Vector2 startPosition = GetLocalUIPosition(outBlock.outPort, GetComponent<RectTransform>());
|
||||
Vector2 endPosition = GetLocalUIPosition(inBlock.inPort, GetComponent<RectTransform>());
|
||||
connector.SetCurve(startPosition, endPosition);
|
||||
}
|
||||
|
||||
Vector2 GetLocalUIPosition(RectTransform fromPort, RectTransform targetSpace)
|
||||
{
|
||||
Vector2 screenPos = RectTransformUtility.WorldToScreenPoint(null, fromPort.position);
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(targetSpace, screenPos, null, out Vector2 localPos);
|
||||
return localPos;
|
||||
}
|
||||
|
||||
public void SetUpBackground()
|
||||
{
|
||||
float maxRight = float.MinValue;
|
||||
|
||||
foreach (var block in generatedBlocks)
|
||||
{
|
||||
float rightEdge = block.blockRect.anchoredPosition.x + block.blockRect.sizeDelta.x * 0.5f;
|
||||
if (rightEdge > maxRight)
|
||||
{
|
||||
maxRight = rightEdge;
|
||||
}
|
||||
}
|
||||
|
||||
maxRight += marginRight;
|
||||
|
||||
Debug.Log(maxRight);
|
||||
|
||||
if (maxRight < 1920f)
|
||||
{
|
||||
maxRight = 1920f;
|
||||
}
|
||||
|
||||
content.sizeDelta = new Vector2(maxRight, content.sizeDelta.y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public method to generate nodes
|
||||
/// </summary>
|
||||
public void Generate(List<Vector2> positions, List<Vector2> sizes)
|
||||
{
|
||||
if (content == null || textBlockPrefab == null || positions.Count != sizes.Count)
|
||||
{
|
||||
Debug.LogError("NodeScrollGenerator: Invalid input data.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear old nodes
|
||||
foreach (Transform child in content)
|
||||
{
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
|
||||
float maxRight = float.MinValue;
|
||||
|
||||
for (int i = 0; i < positions.Count; i++)
|
||||
{
|
||||
// Calculate right edge
|
||||
float rightEdge = positions[i].x + marginLeft + sizes[i].x * 0.5f;
|
||||
if (rightEdge > maxRight)
|
||||
maxRight = rightEdge;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Create nodes
|
||||
for (int i = 0; i < positions.Count; i++)
|
||||
{
|
||||
GameObject node = Instantiate(textBlockPrefab, content);
|
||||
RectTransform rectTransform = node.GetComponent<RectTransform>();
|
||||
|
||||
// Set position and size
|
||||
rectTransform.anchoredPosition = positions[i] + new Vector2(marginLeft, 0);
|
||||
rectTransform.sizeDelta = sizes[i];
|
||||
|
||||
// Optionally, you can set the name or other properties of the node here
|
||||
node.name = $"Node_{i}";
|
||||
node.GetComponent<TextBlockUI>().Initialize("ZakoCurse 0", "Zako Curse 0");
|
||||
}
|
||||
|
||||
// Set content width
|
||||
content.sizeDelta = new Vector2(maxRight + marginRight, content.sizeDelta.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Ichni.Story.UI
|
||||
{
|
||||
public class TextBlockUI : StoryBlockUIBase
|
||||
{
|
||||
public string blockName;
|
||||
public string blockTitle;
|
||||
public Button button;
|
||||
public TMP_Text titleText;
|
||||
|
||||
public void Initialize(string blockName, string blockTitle)
|
||||
{
|
||||
this.blockName = blockName;
|
||||
this.blockTitle = blockTitle;
|
||||
|
||||
titleText.text = blockTitle;
|
||||
button.onClick.AddListener(() =>
|
||||
{
|
||||
DialogManager.instance.SetDialog(blockName);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Ichni.Story.UI
|
||||
{
|
||||
public class TutorialBlockUI : StoryBlockUIBase
|
||||
{
|
||||
public string blockName;
|
||||
public Button button;
|
||||
public TMP_Text tutorialText;
|
||||
|
||||
public void Initialize(string blockName)
|
||||
{
|
||||
this.blockName = blockName;
|
||||
|
||||
tutorialText.text = "Tutorial";
|
||||
button.onClick.AddListener(() =>
|
||||
{
|
||||
// DialogManager.instance.SetDialog(blockName);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user