This commit is contained in:
SoulliesOfficial
2025-06-06 10:14:55 -04:00
parent d4e860fa16
commit db4d131192
1088 changed files with 45704 additions and 2260 deletions

View File

@@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Events;
using Random = UnityEngine.Random;
namespace Ichni
{
public static class GeneralExtensions
{
public static void Invoke(this Dictionary<string, UnityAction> dictionary)
{
foreach (var action in dictionary.Values)
{
action.Invoke();
}
}
public static void Invoke<T>(this Dictionary<string, UnityAction<T>> dictionary, T arg)
{
foreach (var action in dictionary.Values)
{
action.Invoke(arg);
}
}
public static void Invoke<T1, T2>(this Dictionary<string, UnityAction<T1, T2>> dictionary, T1 arg1, T2 arg2)
{
foreach (var action in dictionary.Values)
{
action.Invoke(arg1, arg2);
}
}
/// <summary>
/// 遍历列表中的每个元素并执行指定的操作用于foreach不能遍历的情况例如在foreach中删除元素
/// </summary>
public static void For<T>(this IList<T> list, UnityAction<T> action)
{
for (int i = 0; i < list.Count; i++)
{
action.Invoke(list[i]);
}
}
/// <summary>
/// 将列表中的元素从此列表转移到另一个列表
/// </summary>
public static void Transfer<T>(this List<T> fromList, List<T> toList, T item)
{
if (fromList.Contains(item))
{
fromList.Remove(item);
toList.Add(item);
}
else
{
Debug.LogError($"Item {item} not found in this List.");
}
}
/// <summary>
/// 在列表中交换两个元素
/// </summary>
public static void Swap<T>(this IList<T> list, int i, int j)
{
(list[i], list[j]) = (list[j], list[i]);
}
/// <summary>
/// 随机打乱列表中的元素
/// </summary>
public static void Shuffle<T>(this IList<T> list)
{
for (int i = 0; i < list.Count; i++)
{
list.Swap(i, Random.Range(i, list.Count));
}
}
/// <summary>
/// 随机获取列表中的元素
/// </summary>
public static List<T> GetRandomElements<T>(this List<T> list, int count)
{
List<T> randomElements = new List<T>();
if (count >= list.Count)
{
randomElements = new List<T>(list);
return randomElements;
}
List<int> randomIndices = new List<int>();
while (randomIndices.Count < count)
{
int randomIndex = Random.Range(0, list.Count);
if (!randomIndices.Contains(randomIndex))
{
randomIndices.Add(randomIndex);
randomElements.Add(list[randomIndex]);
}
}
return randomElements;
}
/// <summary>
/// 删除所有满足 predicate 的键值对。
/// </summary>
public static void RemoveWhere<TKey, TValue>(this IDictionary<TKey, TValue> dict, Func<TKey, TValue, bool> predicate)
{
dict.Where(pair => predicate(pair.Key, pair.Value)).Select(pair => pair.Key).ToList().ForEach(key => dict.Remove(key));
}
/// <summary>
/// 获取所有子物体
/// </summary>
public static List<Transform> GetAllChildren(this Transform parent)
{
return parent.Cast<Transform>().ToList();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ee9b362a3cc19d14eb7a39d2caac7e6d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -38,7 +38,7 @@ namespace Ichni
public Canvas judgeHintCanvas;
public GameUIController gameUIController;
public float songTime => audioManager.musicPlayer.songTimeSegment;
public float songTime => audioManager.songPlayer.songTimeSegment;
public bool isDebugging;

View File

@@ -33,7 +33,7 @@ namespace Ichni
public void Update()
{
if (!GameManager.instance.audioManager.musicPlayer.isPlaying)
if (!GameManager.instance.audioManager.songPlayer.isPlaying)
{
return;
}
@@ -146,7 +146,7 @@ namespace Ichni
private void LateUpdate()
{
if (!GameManager.instance.audioManager.musicPlayer.isPlaying)
if (!GameManager.instance.audioManager.songPlayer.isPlaying)
{
return;
}

View File

@@ -40,7 +40,7 @@ namespace Ichni
(GameManager.instance.beatmapContainer.matchedBM as BeatmapContainer_BM).remainingElementAmount.Value == 0)
.First().Subscribe(_ =>
{
GameManager.instance.audioManager.musicPlayer.PlayMusic();
GameManager.instance.audioManager.songPlayer.PlaySong();
});
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Ichni.Menu;
using Ichni.UI;
using Sirenix.OdinInspector;
using UnityEngine;
namespace Ichni
{
public partial class ChapterSelectionManager : SerializedMonoBehaviour
{
public static ChapterSelectionManager instance;
public ChapterSelectionUIPage chapterSelectionUIPage;
public List<ChapterSelectionUnit> chapters;
}
public partial class ChapterSelectionManager
{
private void Awake()
{
instance = this;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8aa09572a86d1ab47af92aa372e1f90d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,6 +1,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using AK.Wwise;
using Sirenix.OdinInspector;
using UnityEngine;
@@ -14,16 +16,37 @@ namespace Ichni.Menu
public string chapterSubtitle;
public Color themeColor;
public Sprite avatar;
public Switch chapterSwitch;
[ListDrawerSettings(ShowFoldout = true)]
public List<SongItemData> songs = new List<SongItemData>();
[Button]
public void SetUpDefaultDifficulties()
{
foreach (SongItemData song in songs)
{
if(song.difficultyDataList.All(d => d.difficultyName != "Easy"))
{
song.difficultyDataList.Add(new DifficultyData("Easy","", "",
new Color(0f, 0.7f, 0.2f, 1f)));
}
if (song.difficultyDataList.All(d => d.difficultyName != "Hard"))
{
song.difficultyDataList.Add(new DifficultyData("Hard", "", "",
new Color(1f, 0.2f, 0.2f, 1f)));
}
}
}
}
[InlineProperty]
[Serializable]
public class SongItemData
{
[HideLabel, FoldoutGroup("$songName", true)]
[FoldoutGroup("$songName", true)]
public string songName;
[FoldoutGroup("$songName")]
@@ -36,7 +59,7 @@ namespace Ichni.Menu
public bool isNewSong;
[FoldoutGroup("$songName")]
public AK.Wwise.Switch songSwitch;
public Switch songSwitch;
[FoldoutGroup("$songName")]
public Sprite albumIconCover;
@@ -52,16 +75,6 @@ namespace Ichni.Menu
[FoldoutGroup("$songName")]
public List<DifficultyData> difficultyDataList;
[Button]
[FoldoutGroup("$songName")]
public void AddDefaultDifficulties()
{
difficultyDataList.Add(
new DifficultyData("Easy","", "", new Color(0f, 0.7f, 0.2f, 1f)));
difficultyDataList.Add(
new DifficultyData("Hard","", "", new Color(1f, 0.2f, 0.2f, 1f)));
}
}
[Serializable]

View File

@@ -2,25 +2,39 @@ using System;
using System.Collections;
using System.Collections.Generic;
using AK.Wwise;
using Ichni.UI;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.Serialization;
namespace Ichni.Menu
namespace Ichni
{
public partial class MenuManager : MonoBehaviour
{
public static MenuManager instance;
[FormerlySerializedAs("preparePage")] public PrepareUIPage prepareUIPage;
}
public partial class MenuManager
{
AsyncOperation asyncOperation;
private void Awake()
{
instance = this;
Application.targetFrameRate = 120;
/*asyncOperation = SceneManager.LoadSceneAsync("GameScene");
asyncOperation.allowSceneActivation = false;*/
}
private void Start()
{
asyncOperation = SceneManager.LoadSceneAsync("GameScene");
asyncOperation.allowSceneActivation = false;
}
}
public partial class MenuManager : MonoBehaviour
public partial class MenuManager
{
public Sprite testSprite;
public Switch testChapterSwitch;
@@ -37,7 +51,7 @@ namespace Ichni.Menu
"Trader",
Color.red, testSprite,testChapterSwitch, testMusicSwitch);
SceneManager.LoadScene("GameScene");
asyncOperation.allowSceneActivation = true;
}
[Button]

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b7a92293064a12b44b29a26166706d0a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,18 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SongSelectionManager : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 726b9313110aee54fbba1c1891bdaa1a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0f7a3d96ccce4d840bd4911722a981b5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Ichni
{
public class GameSettings
{
public int mainVolume = 100;
public int backgroundMusicVolume = 100;
public int soundEffectVolume = 100;
public int uiVolume = 100;
public int judgeOffset = 0;
public int targetFrame = 60;
public bool lowResolution = false;
public bool debugMode = false;
public GameSettings()
{
}
public GameSettings(int mainVolume, int backgroundMusicVolume, int soundEffectVolume, int uiVolume,
int judgeOffset, int targetFrame, bool lowResolution, bool debugMode)
{
this.mainVolume = mainVolume;
this.backgroundMusicVolume = backgroundMusicVolume;
this.soundEffectVolume = soundEffectVolume;
this.uiVolume = uiVolume;
this.judgeOffset = judgeOffset;
this.targetFrame = targetFrame;
this.lowResolution = lowResolution;
this.debugMode = debugMode;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c2ca67619927cac4a91d5b0612d6f7eb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,51 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Ichni.UI;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.Serialization;
namespace Ichni
{
public class SettingsManager : SerializedMonoBehaviour
{
public static SettingsManager instance;
[FormerlySerializedAs("settingsPage")] public SettingsUIPage settingsUIPage;
public GameSettings gameSettings;
private void Awake()
{
instance = this;
}
private void Start()
{
LoadGameSettings();
}
[Button]
public void SaveGameSettings()
{
string savePath = Application.persistentDataPath + "/GameData/GameSettings.json";
ES3.Save("GameSettings", gameSettings, savePath);
}
[Button]
public void LoadGameSettings()
{
string savePath = Application.persistentDataPath + "/GameData/GameSettings.json";
if (ES3.FileExists(savePath))
{
gameSettings = ES3.Load<GameSettings>("GameSettings", savePath);
}
else
{
gameSettings = new GameSettings(
100, 100, 100, 100,
0, 60, false, false);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 49501a5fe129ecf4ca83fd1c5d03411e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,93 @@
using UnityEngine;
namespace Ichni
{
public static class SpaceConverter
{
/// <summary>
/// 世界坐标转换为屏幕坐标
/// </summary>
/// <param name="worldPoint">屏幕坐标</param>
/// <returns></returns>
public static Vector2 WorldPointToScreenPoint(Vector3 worldPoint)
{
// Camera.main 世界摄像机
Vector2 screenPoint = GameManager.instance.cameraManager.gameCamera.gameCamera.WorldToScreenPoint(worldPoint);
return screenPoint;
}
/// <summary>
/// 屏幕坐标转换为世界坐标
/// </summary>
/// <param name="screenPoint">屏幕坐标</param>
/// <param name="planeZ">距离摄像机 Z 平面的距离</param>
/// <returns></returns>
public static Vector3 ScreenPointToWorldPoint(Vector2 screenPoint, float planeZ)
{
// Camera.main 世界摄像机
Vector3 position = new Vector3(screenPoint.x, screenPoint.y, planeZ);
Vector3 worldPoint = GameManager.instance.cameraManager.gameCamera.gameCamera.ScreenToWorldPoint(position);
return worldPoint;
}
public static Vector2 WorldPointToUILocalPoint(RectTransform rt, Vector3 worldPosition)
{
// 将世界坐标转换为屏幕坐标
var screenPosition = WorldPointToScreenPoint(worldPosition);
// 将屏幕坐标转换为 UGUI 坐标
Vector2 localPoint = ScreenPointToUILocalPoint(rt, screenPosition);
return localPoint;
}
public static Vector3 WorldPointToUIPoint(RectTransform rt, Vector3 worldPosition)
{
// 将世界坐标转换为屏幕坐标
var screenPosition = WorldPointToScreenPoint(worldPosition);
// 将屏幕坐标转换为 UGUI 坐标
Vector3 uiPoint = ScreenPointToUIPoint(rt, screenPosition);
return uiPoint;
}
// RectTransformUtility.WorldToScreenPoint
// RectTransformUtility.ScreenPointToWorldPointInRectangle
// RectTransformUtility.ScreenPointToLocalPointInRectangle
// 上面三个坐标转换的方法使用 Camera 的地方
// 当 Canvas renderMode 为 RenderMode.ScreenSpaceCamera、RenderMode.WorldSpace 时 传递参数 canvas.worldCamera
// 当 Canvas renderMode 为 RenderMode.ScreenSpaceOverlay 时 传递参数 null
// UI 坐标转换为屏幕坐标
public static Vector2 UIPointToScreenPoint(Vector3 worldPoint)
{
Vector2 screenPoint = RectTransformUtility.WorldToScreenPoint(GameManager.instance.cameraManager.uiCamera, worldPoint);
return screenPoint;
}
// 屏幕坐标转换为 UGUI 坐标
public static Vector3 ScreenPointToUIPoint(RectTransform rt, Vector2 screenPoint)
{
//UI屏幕坐标转换为世界坐标
// 当 Canvas renderMode 为 RenderMode.ScreenSpaceCamera、RenderMode.WorldSpace 时 uiCamera 不能为空
// 当 Canvas renderMode 为 RenderMode.ScreenSpaceOverlay 时 uiCamera 可以为空
RectTransformUtility.ScreenPointToWorldPointInRectangle(rt, screenPoint, GameManager.instance.cameraManager.uiCamera, out Vector3 globalMousePos);
// 转换后的 globalMousePos 使用下面方法赋值
// target 为需要使用的 UI RectTransform
// rt 可以是 target.GetComponent<RectTransform>(), 也可以是 target.parent.GetComponent<RectTransform>()
// target.transform.position = globalMousePos;
return globalMousePos;
}
// 屏幕坐标转换为 UGUI RectTransform 的 anchoredPosition
public static Vector2 ScreenPointToUILocalPoint(RectTransform parentRT, Vector2 screenPoint)
{
RectTransformUtility.ScreenPointToLocalPointInRectangle(parentRT, screenPoint, GameManager.instance.cameraManager.uiCamera, out Vector2 localPos);
// 转换后的 localPos 使用下面方法赋值
// target 为需要使用的 UI RectTransform
// parentRT 是 target.parent.GetComponent<RectTransform>()
// 最后赋值 target.anchoredPosition = localPos;
return localPos;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1d0e13f7c5078234d9d2a058d3a929af
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2de1e277bcf059c42826353d1c086ccc
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,400 @@
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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9e89b9d7eea97734baa166072b050239
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,91 @@
using System.Collections.Generic;
using System.Text.RegularExpressions;
using UnityEngine;
namespace Ichni.Story
{
public class DialogSentence
{
public string content;
public string audioEventName;
public string characterName;
public string characterEmotion;
public DialogSentence()
{
}
public DialogSentence(string content, string audioEventName, string characterName, string characterEmotion)
{
this.content = content;
this.audioEventName = audioEventName;
this.characterName = characterName;
this.characterEmotion = characterEmotion;
}
/// <summary>
/// 匹配{@FUNCTION},解析函数并返回解析后的语句内容。
/// </summary>
public string GetInterpretedContent()
{
List<string> parts = new List<string>();
Regex regex = new Regex(@"\{\@.*?\}");
int lastIndex = 0;
foreach (Match match in regex.Matches(content))
{
if (match.Index > lastIndex)
{
parts.Add(content.Substring(lastIndex, match.Index - lastIndex));
}
parts.Add(match.Value);
lastIndex = match.Index + match.Length;
}
if (lastIndex < content.Length)
{
parts.Add(content.Substring(lastIndex));
}
for (int i = 0; i < parts.Count; i++)
{
if (parts[i].StartsWith("{@") && parts[i].EndsWith("}"))
{
string expression = parts[i].Substring(2, parts[i].Length - 3);
object result = StoryInterpreters.FunctionInterpreter.Eval(expression);
parts[i] = result.ToString();
}
}
return string.Join("", parts);
}
}
public class Choice
{
public string choiceText;
public string hint;
public string nextDialogName;
}
public class Condition
{
public string conditionSentence;
public string nextDialogName;
public bool GetConditionResult()
{
bool result = StoryInterpreters.ConditionInterpreter.Eval<bool>(conditionSentence);
return result;
}
}
public class DialogCharacter
{
public string name;
public string title;
public Dictionary<string, Sprite> emotions;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d32abf19c3d0f4546b2b86f9fcc4e117
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,48 @@
using System;
using DynamicExpresso;
using Ichni.Story;
using UnityEngine;
namespace Ichni.Story
{
public static partial class StoryInterpreters
{
public static readonly Interpreter FunctionInterpreter;
public static readonly Interpreter ConditionInterpreter;
static StoryInterpreters()
{
FunctionInterpreter = new Interpreter();
ConditionInterpreter = new Interpreter();
SetFunctionInterpreter();
SetConditionInterpreter();
}
static void SetFunctionInterpreter()
{
FunctionInterpreter.SetFunction("GetGlobalVariable", new Func<string, int>(GetGlobalVariable));
}
static void SetConditionInterpreter()
{
ConditionInterpreter.SetFunction("GetGlobalVariable", new Func<string, int>(GetGlobalVariable));
}
}
public static partial class StoryInterpreters
{
/// <summary>
/// 获取全局变量的值
/// </summary>
static int GetGlobalVariable(string variableName)
{
if (StoryManager.instance.globalVariables.TryGetValue(variableName, out int value))
{
return value;
}
throw new ArgumentException($"Global variable '{variableName}' not found.");
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dd119fb631aebb548a637407d15c5eea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using Ichni.Story.UI;
using Ichni.UI;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.Serialization;
namespace Ichni.Story
{
public class StoryManager : SerializedMonoBehaviour
{
public static StoryManager instance;
public StorylineDisplay storylineDisplay;
[FormerlySerializedAs("StoryPage")] public StoryUIPage storyUIPage;
public Dictionary<string, int> globalVariables;
void Awake()
{
instance = this;
}
}
}

View File

@@ -0,0 +1,28 @@
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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7ab917c50249812429ebd44d6574497c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,61 @@
%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: []

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6e95609f9d235c042a48dbabae121053
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -6,6 +6,24 @@ namespace Ichni.Story
{
public class StorySave : MonoBehaviour
{
public StoryBlockSave blockSave;
public StoryVariableSave variableSave;
}
public class StoryBlockSave
{
public enum StoryBlockState
{
Locked,
Current,
Completed
}
public Dictionary<string, StoryBlockState> storyBlockStates = new Dictionary<string, StoryBlockState>();
}
public class StoryVariableSave
{
public Dictionary<string, int> variables = new Dictionary<string, int>();
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a28c0aedac2c2664c876534db3e815f5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,44 @@
using System;
using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.Serialization;
namespace Ichni.UI
{
public class UIPageBase : SerializedMonoBehaviour
{
public CanvasGroup mainCanvasGroup;
private void Awake()
{
CanvasGroup group = GetComponent<CanvasGroup>();
if (group != null)
{
mainCanvasGroup ??= group;
}
}
public void FadeIn(float duration = 0.5f)
{
mainCanvasGroup.gameObject.SetActive(true);
mainCanvasGroup.DOFade(1f, duration).OnComplete(() =>
{
mainCanvasGroup.interactable = true;
mainCanvasGroup.blocksRaycasts = true;
}).Play();
}
public void FadeOut(float duration = 0.5f)
{
mainCanvasGroup.interactable = false;
mainCanvasGroup.blocksRaycasts = false;
mainCanvasGroup.DOFade(0f, duration).OnComplete(() =>
{
mainCanvasGroup.gameObject.SetActive(false);
}).Play();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b5131dcefd0f7134b8d7c8956e949e52
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 215cc7c4af3bc0545b877d49d482468f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,37 @@
using System.Collections;
using System.Collections.Generic;
using Ichni.Menu;
using Ichni.Story;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace Ichni.UI
{
public class ChapterSelectionUI : MonoBehaviour
{
public RectTransform content;
public TMP_Text title;
public TMP_Text subtitle;
public Image avatar;
public Image border;
public Button area;
public void Initialize(ChapterSelectionUnit chapter)
{
title.text = chapter.chapterName;
title.color = chapter.themeColor;
subtitle.text = chapter.chapterSubtitle;
subtitle.color = chapter.themeColor;
border.color = chapter.themeColor;
avatar.sprite = chapter.avatar;
area.onClick.AddListener(() =>
{
ChapterSelectionManager.instance.chapterSelectionUIPage.FadeOut();
StoryManager.instance.storyUIPage.FadeIn();
});
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a483faf242f8c4f449b809db70741cf2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,22 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Ichni.UI
{
public class ChapterSelectionUIPage : UIPageBase
{
public RectTransform chapterContainer;
public GameObject chapterSelectionUIPrefab;
private void Start()
{
foreach (var chapter in ChapterSelectionManager.instance.chapters)
{
ChapterSelectionUI item = Instantiate(chapterSelectionUIPrefab, chapterContainer).GetComponent<ChapterSelectionUI>();
item.Initialize(chapter);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3adff9bbefe437245af8b1d1c9bf7e56
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8c234abdb2207c8459280e59152ba1c9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,48 @@
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>());
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 62ce59e989ba99d4ca0407641e0ae2f2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Ichni.UI;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Serialization;
namespace Ichni.Story.UI
{
public class DialogUIPage : UIPageBase
{
public TextFrame textFrame;
public ChoiceFrame choiceFrame;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 17254192719abee4f9222246fd403de5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,38 @@
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();
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 14874d8a3e4a31941879415479892501
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0b91bdc95925cee4eaec293d270c570c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,12 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace Ichni.UI
{
public class PrepareUIPage : UIPageBase
{
public Button enterGameButton;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f3060506b42e3df44962829ee40e95d9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: bfe7bb3757d56ed4ba5f39d9432eeb88
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -2,9 +2,9 @@ using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Ichni.Story
namespace Ichni.UI
{
public class StoryManager : MonoBehaviour
public class SettingsUIPage : UIPageBase
{
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1d9b1595bce378541852c9c6f86f58ee
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: be2c6dbc0cd6b3e419fc467ea5f07091
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,11 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Ichni.UI
{
public class SongSelectionUIPage : UIPageBase
{
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2bc849ebcc9595745a8a2584eb86eeee
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -3,9 +3,9 @@ using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Ichni.Menu
namespace Ichni.UI
{
public class StartPage : MonoBehaviour
public class StartUIPage : UIPageBase
{
public AudioContainer audioContainer;
@@ -17,6 +17,8 @@ namespace Ichni.Menu
public void TouchToStart()
{
audioContainer.PlaySoundFX("TouchToStart");
FadeOut();
ChapterSelectionManager.instance.chapterSelectionUIPage.FadeIn();
}
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: cf843fc3f4fd10e499a6cc1b60bc1861
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,29 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI.Extensions;
namespace Ichni.Story
{
public class BlockConnectorUI : MonoBehaviour
{
public UILineRenderer curve;
public void SetCurve(Vector2 startPosition, Vector2 endPosition)
{
Vector2 mid1 = (startPosition + endPosition) / 2;
Vector2 mid2 = (startPosition + endPosition) / 2;
mid1.y = startPosition.y;
mid2.y = endPosition.y;
curve.Points = new Vector2[]
{
startPosition,
mid1,
mid2,
endPosition
};
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 98bbf463dca50cc43961c0bb2b8d4f22
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,29 @@
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();
});
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5efe0a39fe908354e9ab6d1edfdb8843
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,13 @@
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;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d489f986a1eea1e4c938658f0fd468ca
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,11 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Ichni.UI
{
public class StoryUIPage : UIPageBase
{
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3ec0b23c77de2764d9425add8f28ea00
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,192 @@
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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 42d37c15aeeaf1d4abbdc13962bb8b70
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,29 @@
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);
});
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b976128eb3ec59e4e866dbb610441706
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,27 @@
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);
});
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 821e50e1519236a46b03eb1f005acebe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -3,12 +3,13 @@ using System.Collections;
using System.Collections.Generic;
using AK.Wwise;
using UnityEngine;
using UnityEngine.Serialization;
namespace Ichni
{
public class AudioManager : MonoBehaviour
{
public MusicPlayer musicPlayer;
[FormerlySerializedAs("musicPlayer")] public SongPlayer songPlayer;
public AudioContainer generalSoundEffects;
private void Start()

View File

@@ -6,7 +6,7 @@ using Event = AK.Wwise.Event;
namespace Ichni
{
public class MusicPlayer : SerializedMonoBehaviour
public class SongPlayer : SerializedMonoBehaviour
{
private uint _playingId;
@@ -70,7 +70,7 @@ namespace Ichni
[Button]
public void PlayMusic()
public void PlaySong()
{
_playingId = PlayMusicEvent.Post(gameObject,
(uint)AkCallbackType.AK_EnableGetMusicPlayPosition | (uint)AkCallbackType.AK_MusicSyncAll,
@@ -82,7 +82,7 @@ namespace Ichni
}
[Button]
public void PauseMusic()
public void PauseSong()
{
pauseTimeSegment = PlaySegment();
isPlaying = false;
@@ -92,7 +92,7 @@ namespace Ichni
}
[Button]
public void ResumeMusic()
public void ResumeSong()
{
Time.timeScale = 1;
isPlaying = true;
@@ -101,7 +101,7 @@ namespace Ichni
}
[Button]
public void StopMusic()
public void StopSong()
{
isPlaying = false;
isPausing = false;