mirror of
https://github.com/HoonTB/Project-AS.git
synced 2025-12-26 11:51:21 +09:00
feat: implement core visual novel script parsing, command execution, and character direction with animation queues.
This commit is contained in:
@@ -1,58 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
public class Command
|
|
||||||
{
|
|
||||||
private List<CommandSet> _actions;
|
|
||||||
private int _currentIndex = -1;
|
|
||||||
private Dictionary<string, int> _labelMap = new();
|
|
||||||
|
|
||||||
public Command(List<CommandSet> actions, Dictionary<string, int> labelMap)
|
|
||||||
{
|
|
||||||
_actions = actions;
|
|
||||||
_labelMap = labelMap;
|
|
||||||
_currentIndex = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool HasNextAction()
|
|
||||||
{
|
|
||||||
return _currentIndex < _actions.Count - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommandSet Continue()
|
|
||||||
{
|
|
||||||
if (!HasNextAction())
|
|
||||||
return null;
|
|
||||||
|
|
||||||
_currentIndex++;
|
|
||||||
CommandSet currentAction = _actions[_currentIndex];
|
|
||||||
|
|
||||||
return currentAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommandSet GetCurrent()
|
|
||||||
{
|
|
||||||
if (_currentIndex >= 0 && _currentIndex < _actions.Count)
|
|
||||||
return _actions[_currentIndex];
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommandSet PeekNext()
|
|
||||||
{
|
|
||||||
if (_currentIndex < _actions.Count - 1)
|
|
||||||
return _actions[_currentIndex + 1];
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void JumpTo(string labelName)
|
|
||||||
{
|
|
||||||
_currentIndex = _labelMap[labelName] - 1; // Continue() 호출 시 해당 인덱스가 되도록 -1
|
|
||||||
Debug.Log($"Script :: Jump to label: {labelName} (Index: {_currentIndex + 1})");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Save()
|
|
||||||
{
|
|
||||||
// TODO: _currentIndex 값을 받아와서 파일에 기록 or DB에 기록
|
|
||||||
// 20251126_191933_SAVE -> { _currentIndex, expData, ... }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
public class CommandSet
|
|
||||||
{
|
|
||||||
public string Type { get; set; }
|
|
||||||
public Dictionary<string, object> Params { get; set; } = new();
|
|
||||||
public List<Dictionary<string, string>> Choices { get; set; }
|
|
||||||
|
|
||||||
public string GetParam(string key, string defaultValue = "")
|
|
||||||
{
|
|
||||||
return Params.ContainsKey(key) ? Params[key].ToString() : defaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: ea31a181cac32d34c9511c7c988fea57
|
|
||||||
@@ -7,12 +7,12 @@ public class Parser
|
|||||||
private static readonly Regex AttrRegex = new(@"(\w+)=(""[^""]*""|'[^']*'|[^ \t\]]+)");
|
private static readonly Regex AttrRegex = new(@"(\w+)=(""[^""]*""|'[^']*'|[^ \t\]]+)");
|
||||||
private static readonly Regex ChoiceRegex = new(@"^\*\s*(.+?)\s*>\s*(.+)$");
|
private static readonly Regex ChoiceRegex = new(@"^\*\s*(.+?)\s*>\s*(.+)$");
|
||||||
|
|
||||||
public static Command Parse(string text)
|
public static Script Parse(string text)
|
||||||
{
|
{
|
||||||
List<CommandSet> commands = new();
|
List<Command> commands = new();
|
||||||
Dictionary<string, int> labelMap = new();
|
Dictionary<string, int> labelMap = new();
|
||||||
|
|
||||||
CommandSet lastChoice = null;
|
Command lastChoice = null;
|
||||||
|
|
||||||
text = Regex.Replace(text, "<shake>", "<link=shake>");
|
text = Regex.Replace(text, "<shake>", "<link=shake>");
|
||||||
text = Regex.Replace(text, "</shake>", "</link>");
|
text = Regex.Replace(text, "</shake>", "</link>");
|
||||||
@@ -32,7 +32,7 @@ public class Parser
|
|||||||
string tagName = tagMatch.Groups[1].Value;
|
string tagName = tagMatch.Groups[1].Value;
|
||||||
string attrString = tagMatch.Groups[2].Value;
|
string attrString = tagMatch.Groups[2].Value;
|
||||||
|
|
||||||
var scriptAction = new CommandSet { Type = tagName };
|
var scriptAction = new Command { Type = tagName };
|
||||||
|
|
||||||
if (!attrString.Contains("=")) scriptAction.Params["content"] = attrString;
|
if (!attrString.Contains("=")) scriptAction.Params["content"] = attrString;
|
||||||
else ParseAttributes(attrString, scriptAction.Params);
|
else ParseAttributes(attrString, scriptAction.Params);
|
||||||
@@ -70,10 +70,10 @@ public class Parser
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
commands.Add(new CommandSet { Type = "msg", Params = { { "content", line } } });
|
commands.Add(new Command { Type = "msg", Params = { { "content", line } } });
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Command(commands, labelMap);
|
return new Script(commands, labelMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ParseAttributes(string attrString, Dictionary<string, object> paramDict)
|
private static void ParseAttributes(string attrString, Dictionary<string, object> paramDict)
|
||||||
|
|||||||
63
Assets/_MAIN/Scripts/Core/Script.cs
Normal file
63
Assets/_MAIN/Scripts/Core/Script.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class Command
|
||||||
|
{
|
||||||
|
public string Type { get; set; }
|
||||||
|
public Dictionary<string, object> Params { get; set; } = new();
|
||||||
|
public List<Dictionary<string, string>> Choices { get; set; }
|
||||||
|
|
||||||
|
public string GetParam(string key, string defaultValue = "")
|
||||||
|
{
|
||||||
|
return Params.ContainsKey(key) ? Params[key].ToString() : defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public class Script
|
||||||
|
{
|
||||||
|
private List<Command> _commands;
|
||||||
|
private int _currentIndex = -1;
|
||||||
|
private Dictionary<string, int> _labelMap = new();
|
||||||
|
|
||||||
|
public Script(List<Command> commands, Dictionary<string, int> labelMap)
|
||||||
|
{
|
||||||
|
_commands = commands;
|
||||||
|
_labelMap = labelMap;
|
||||||
|
_currentIndex = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasNextCommand()
|
||||||
|
{
|
||||||
|
return _currentIndex < _commands.Count - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Command Continue()
|
||||||
|
{
|
||||||
|
if (!HasNextCommand())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
_currentIndex++;
|
||||||
|
Command currentCommand = _commands[_currentIndex];
|
||||||
|
|
||||||
|
return currentCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Command GetCurrent()
|
||||||
|
{
|
||||||
|
if (_currentIndex >= 0 && _currentIndex < _commands.Count)
|
||||||
|
return _commands[_currentIndex];
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Command PeekNext()
|
||||||
|
{
|
||||||
|
if (_currentIndex < _commands.Count - 1)
|
||||||
|
return _commands[_currentIndex + 1];
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void JumpTo(string labelName)
|
||||||
|
{
|
||||||
|
_currentIndex = _labelMap[labelName] - 1;
|
||||||
|
Debug.Log($"Script :: Jump to label: {labelName} (Index: {_currentIndex + 1})");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
using System.Collections;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using Cysharp.Threading.Tasks;
|
||||||
using PrimeTween;
|
using PrimeTween;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
@@ -7,52 +9,58 @@ using UnityEngine.UI;
|
|||||||
public class VNDirector : MonoBehaviour
|
public class VNDirector : MonoBehaviour
|
||||||
{
|
{
|
||||||
// ========================= [Enums] =========================
|
// ========================= [Enums] =========================
|
||||||
public enum EntranceType { Left, Right, BottomLeft, BottomRight, Center, Top, LeftRun, RightRun }
|
public enum DirectionType { Left, Right, BottomLeft, BottomRight, Center, Top, RunLeft, RunRight }
|
||||||
public enum ActionType { Jump, Shake, Nod, Punch, Run }
|
public enum AnimationType { Jump, Shake, Nod, Punch, Run }
|
||||||
|
|
||||||
[Header("UI 연결")]
|
[Header("UI 연결")]
|
||||||
public Transform characterPanel;
|
public Transform characterPanel;
|
||||||
public GameObject slotPrefab;
|
|
||||||
|
|
||||||
[Header("설정")]
|
[Header("설정")]
|
||||||
public float charWidth = 350f;
|
public float charWidth = 350f;
|
||||||
public float defaultDuration = 0.5f;
|
public float defaultDuration = 0.5f;
|
||||||
public float moveDistance = 800f;
|
public float moveDistance = 800f;
|
||||||
|
|
||||||
// ========================= [Queue System] =========================
|
private const string CharacterPathPrefix = "Images/Characters/";
|
||||||
private Dictionary<string, Queue<IEnumerator>> actionQueues = new();
|
|
||||||
private Dictionary<string, Coroutine> activeCoroutines = new();
|
|
||||||
|
|
||||||
private void EnqueueAction(string charName, IEnumerator action)
|
// ========================= [Queue System] =========================
|
||||||
|
// bool argument: isImmediate (skip animation)
|
||||||
|
private Dictionary<string, Queue<Func<bool, UniTask>>> actionQueues = new();
|
||||||
|
private Dictionary<string, CancellationTokenSource> activeCTS = new();
|
||||||
|
|
||||||
|
private void EnqueueAction(string charName, Func<bool, UniTask> actionFactory)
|
||||||
{
|
{
|
||||||
if (!actionQueues.ContainsKey(charName))
|
if (!actionQueues.ContainsKey(charName))
|
||||||
{
|
{
|
||||||
actionQueues[charName] = new Queue<IEnumerator>();
|
actionQueues[charName] = new Queue<Func<bool, UniTask>>();
|
||||||
}
|
}
|
||||||
actionQueues[charName].Enqueue(action);
|
actionQueues[charName].Enqueue(actionFactory);
|
||||||
|
|
||||||
if (!activeCoroutines.ContainsKey(charName) || activeCoroutines[charName] == null)
|
if (!activeCTS.ContainsKey(charName) || activeCTS[charName] == null)
|
||||||
{
|
{
|
||||||
activeCoroutines[charName] = StartCoroutine(ProcessActionQueue(charName));
|
var cts = new CancellationTokenSource();
|
||||||
|
activeCTS[charName] = cts;
|
||||||
|
ProcessActionQueue(charName, cts.Token).Forget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CompleteAllActions()
|
public void CompleteAllActions()
|
||||||
{
|
{
|
||||||
// 1. Stop all active processing coroutines
|
// 1. Stop all active processing
|
||||||
foreach (var kvp in activeCoroutines)
|
foreach (var kvp in activeCTS)
|
||||||
{
|
{
|
||||||
if (kvp.Value != null) StopCoroutine(kvp.Value);
|
kvp.Value?.Cancel();
|
||||||
|
kvp.Value?.Dispose();
|
||||||
}
|
}
|
||||||
activeCoroutines.Clear();
|
activeCTS.Clear();
|
||||||
|
|
||||||
// 2. Process remaining items in queues immediately
|
// 2. Process remaining items in queues immediately
|
||||||
foreach (var queue in actionQueues.Values)
|
foreach (var queue in actionQueues.Values)
|
||||||
{
|
{
|
||||||
while (queue.Count > 0)
|
while (queue.Count > 0)
|
||||||
{
|
{
|
||||||
var action = queue.Dequeue();
|
var actionFactory = queue.Dequeue();
|
||||||
RunImmediate(action);
|
// Execute immediately (skipping animations)
|
||||||
|
actionFactory(true).Forget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,40 +68,44 @@ public class VNDirector : MonoBehaviour
|
|||||||
Tween.CompleteAll();
|
Tween.CompleteAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RunImmediate(IEnumerator enumerator)
|
private async UniTaskVoid ProcessActionQueue(string charName, CancellationToken token)
|
||||||
{
|
{
|
||||||
while (enumerator.MoveNext())
|
try
|
||||||
{
|
{
|
||||||
var current = enumerator.Current;
|
while (actionQueues.ContainsKey(charName) && actionQueues[charName].Count > 0)
|
||||||
if (current is IEnumerator nested)
|
|
||||||
{
|
{
|
||||||
RunImmediate(nested);
|
if (token.IsCancellationRequested) break;
|
||||||
}
|
|
||||||
// Force complete any tweens that might have been started
|
|
||||||
Tween.CompleteAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerator ProcessActionQueue(string charName)
|
var actionFactory = actionQueues[charName].Dequeue();
|
||||||
{
|
await actionFactory(false); // Normal execution
|
||||||
while (actionQueues.ContainsKey(charName) && actionQueues[charName].Count > 0)
|
}
|
||||||
{
|
}
|
||||||
IEnumerator action = actionQueues[charName].Dequeue();
|
catch (OperationCanceledException)
|
||||||
yield return StartCoroutine(action);
|
{
|
||||||
|
// Cancelled
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Only remove if this is the CTS we started with (handling race conditions slightly)
|
||||||
|
if (activeCTS.ContainsKey(charName) && activeCTS[charName].Token == token)
|
||||||
|
{
|
||||||
|
var cts = activeCTS[charName];
|
||||||
|
activeCTS.Remove(charName);
|
||||||
|
cts.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
activeCoroutines.Remove(charName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================= [1. 등장 (Entry)] =========================
|
// ========================= [1. 등장 (Entry)] =========================
|
||||||
public void AddCharacter(string fileName, EntranceType type)
|
public void AddCharacter(string fileName, string type)
|
||||||
{
|
{
|
||||||
string path = "Images/Characters/" + fileName;
|
string path = CharacterPathPrefix + fileName;
|
||||||
Sprite loadedSprite = Resources.Load<Sprite>(path);
|
Sprite loadedSprite = Resources.Load<Sprite>(path);
|
||||||
Debug.Log($"VisualNovelLayoutDirector :: AddCharacter: {fileName} ({path})");
|
Debug.Log($"VisualNovelLayoutDirector :: AddCharacter: {fileName} ({path})");
|
||||||
|
|
||||||
if (loadedSprite != null)
|
if (loadedSprite != null)
|
||||||
{
|
{
|
||||||
EnqueueAction(fileName, SpawnRoutine(fileName, loadedSprite, type));
|
EnqueueAction(fileName, (isImmediate) => SpawnAsync(fileName, loadedSprite, GetDirectionType(type), isImmediate));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -101,170 +113,110 @@ public class VNDirector : MonoBehaviour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerator SpawnRoutine(string name, Sprite sprite, EntranceType type)
|
private async UniTask SpawnAsync(string name, Sprite sprite, DirectionType type, bool isImmediate)
|
||||||
{
|
{
|
||||||
if (FindSlot(name) != null)
|
if (FindSlot(name) != null)
|
||||||
{
|
{
|
||||||
Debug.LogWarning($"이미 존재하는 캐릭터입니다: {name}");
|
Debug.LogWarning($"이미 존재하는 캐릭터입니다: {name}");
|
||||||
yield break;
|
return;
|
||||||
}
|
}
|
||||||
// 1. 슬롯 생성
|
|
||||||
GameObject newSlot = Instantiate(slotPrefab, characterPanel);
|
|
||||||
|
|
||||||
// 오브젝트 이름을 파일명(ID)으로 설정
|
// 1. 슬롯 생성 (Programmatic)
|
||||||
newSlot.name = name;
|
var (newSlot, layoutElement, containerRect, charImage) = CreateCharacterSlot(name);
|
||||||
|
|
||||||
LayoutElement layoutElement = newSlot.GetComponent<LayoutElement>();
|
|
||||||
|
|
||||||
// Slot -> MotionContainer -> Image
|
|
||||||
GameObject motionContainer = new("MotionContainer");
|
|
||||||
RectTransform containerRect = motionContainer.AddComponent<RectTransform>();
|
|
||||||
motionContainer.transform.SetParent(newSlot.transform, false);
|
|
||||||
|
|
||||||
// Container 설정 (부모 꽉 채우기)
|
|
||||||
containerRect.anchorMin = Vector2.zero;
|
|
||||||
containerRect.anchorMax = Vector2.one;
|
|
||||||
containerRect.sizeDelta = Vector2.zero;
|
|
||||||
|
|
||||||
// 기존 Image를 Container 자식으로 이동
|
|
||||||
Transform imageTransform = newSlot.transform.GetChild(0); // Prefab의 첫 번째 자식이 Image라고 가정
|
|
||||||
imageTransform.SetParent(motionContainer.transform, false);
|
|
||||||
|
|
||||||
Image charImage = imageTransform.GetComponent<Image>();
|
|
||||||
|
|
||||||
// 2. 초기화
|
// 2. 초기화
|
||||||
charImage.sprite = sprite;
|
charImage.sprite = sprite;
|
||||||
FitImageToScreen(charImage);
|
FitImageToScreen(charImage);
|
||||||
layoutElement.preferredWidth = 0; layoutElement.minWidth = 0;
|
layoutElement.preferredWidth = 0;
|
||||||
|
layoutElement.minWidth = 0;
|
||||||
|
|
||||||
// 3. 순서 재배치
|
// 3. 순서 재배치
|
||||||
int totalCount = characterPanel.childCount;
|
ArrangeSlotOrder(newSlot.transform, type);
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case EntranceType.Left:
|
|
||||||
case EntranceType.LeftRun:
|
|
||||||
case EntranceType.BottomLeft:
|
|
||||||
newSlot.transform.SetSiblingIndex(0); break;
|
|
||||||
case EntranceType.Right:
|
|
||||||
case EntranceType.RightRun:
|
|
||||||
case EntranceType.BottomRight:
|
|
||||||
newSlot.transform.SetSiblingIndex(totalCount - 1); break;
|
|
||||||
case EntranceType.Center:
|
|
||||||
case EntranceType.Top:
|
|
||||||
// 삭제 중인 캐릭터를 제외하고 순서 계산
|
|
||||||
List<Transform> activeChildren = new();
|
|
||||||
for (int i = 0; i < totalCount; i++)
|
|
||||||
{
|
|
||||||
Transform child = characterPanel.GetChild(i);
|
|
||||||
if (child != newSlot.transform && !child.name.Contains("_Removing"))
|
|
||||||
{
|
|
||||||
activeChildren.Add(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int targetIndex = activeChildren.Count / 2;
|
|
||||||
if (targetIndex < activeChildren.Count)
|
|
||||||
{
|
|
||||||
// activeChildren[targetIndex]의 현재 인덱스 앞에 배치
|
|
||||||
newSlot.transform.SetSiblingIndex(activeChildren[targetIndex].GetSiblingIndex());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 맨 뒤로
|
|
||||||
newSlot.transform.SetSiblingIndex(totalCount - 1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. 위치 잡기 및 애니메이션
|
// 4. 위치 잡기 및 애니메이션
|
||||||
Vector2 startPos = GetDirectionVector(type);
|
Vector2 startPos = GetDirectionVector(type);
|
||||||
containerRect.anchoredPosition = startPos;
|
containerRect.anchoredPosition = startPos;
|
||||||
charImage.color = new Color(1, 1, 1, 0);
|
charImage.color = new Color(1, 1, 1, 0);
|
||||||
|
|
||||||
yield return new WaitForEndOfFrame();
|
if (!isImmediate) await UniTask.WaitForEndOfFrame(this);
|
||||||
|
|
||||||
// Tween 실행 및 대기
|
// Tween 실행 및 대기
|
||||||
switch (type)
|
float duration = isImmediate ? 0f : defaultDuration;
|
||||||
|
|
||||||
|
if (type == DirectionType.RunLeft || type == DirectionType.RunRight)
|
||||||
{
|
{
|
||||||
case EntranceType.LeftRun:
|
PlayActionAsync(newSlot.transform, AnimationType.Run, isImmediate).Forget();
|
||||||
case EntranceType.RightRun:
|
|
||||||
StartCoroutine(PlayActionRoutine(newSlot.transform, ActionType.Run));
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return Sequence.Create()
|
await Sequence.Create()
|
||||||
.Group(Tween.Custom(layoutElement, 0f, charWidth, defaultDuration, (t, x) => t.preferredWidth = x, Ease.OutQuart))
|
.Group(Tween.Custom(layoutElement, 0f, charWidth, duration, (t, x) => t.preferredWidth = x, Ease.OutQuart))
|
||||||
.Group(Tween.UIAnchoredPosition(containerRect, Vector2.zero, defaultDuration, Ease.OutQuart))
|
.Group(Tween.UIAnchoredPosition(containerRect, Vector2.zero, duration, Ease.OutQuart))
|
||||||
.Group(Tween.Alpha(charImage, 1f, defaultDuration))
|
.Group(Tween.Alpha(charImage, 1f, duration))
|
||||||
.ToYieldInstruction();
|
.ToUniTask(cancellationToken: this.GetCancellationTokenOnDestroy());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================= [2. 퇴장 (Exit)] =========================
|
// ========================= [2. 퇴장 (Exit)] =========================
|
||||||
public void RemoveCharacter(string characterName, EntranceType exitTo)
|
public void RemoveCharacter(string characterName, string exitTo)
|
||||||
{
|
{
|
||||||
EnqueueAction(characterName, ExitRoutine(characterName, exitTo));
|
EnqueueAction(characterName, (isImmediate) => ExitAsync(characterName, GetDirectionType(exitTo), isImmediate));
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerator ExitRoutine(string characterName, EntranceType exitTo)
|
private async UniTask ExitAsync(string characterName, DirectionType exitTo, bool isImmediate)
|
||||||
{
|
{
|
||||||
Transform targetSlot = FindSlot(characterName);
|
Transform targetSlot = FindSlot(characterName);
|
||||||
|
|
||||||
if (targetSlot == null)
|
if (targetSlot == null)
|
||||||
{
|
{
|
||||||
Debug.LogWarning($"삭제 실패: '{characterName}' 캐릭터를 찾을 수 없습니다.");
|
Debug.LogWarning($"삭제 실패: '{characterName}' 캐릭터를 찾을 수 없습니다.");
|
||||||
yield break;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 중복 호출 방지를 위해 이름을 바꿔둠
|
// 중복 호출 방지를 위해 이름을 바꿔둠
|
||||||
targetSlot.name += "_Removing";
|
targetSlot.name += "_Removing";
|
||||||
|
|
||||||
LayoutElement layoutElement = targetSlot.GetComponent<LayoutElement>();
|
LayoutElement layoutElement = targetSlot.GetComponent<LayoutElement>();
|
||||||
|
|
||||||
// [변경] 계층 구조 반영
|
|
||||||
Transform container = targetSlot.GetChild(0); // MotionContainer
|
Transform container = targetSlot.GetChild(0); // MotionContainer
|
||||||
RectTransform containerRect = container.GetComponent<RectTransform>();
|
RectTransform containerRect = container.GetComponent<RectTransform>();
|
||||||
Image charImage = container.GetChild(0).GetComponent<Image>(); // Image
|
Image charImage = container.GetChild(0).GetComponent<Image>(); // Image
|
||||||
|
|
||||||
Vector2 targetPos = GetDirectionVector(exitTo);
|
Vector2 targetPos = GetDirectionVector(exitTo);
|
||||||
|
float duration = isImmediate ? 0f : defaultDuration;
|
||||||
|
|
||||||
switch (exitTo)
|
if (exitTo == DirectionType.RunLeft || exitTo == DirectionType.RunRight)
|
||||||
{
|
{
|
||||||
case EntranceType.LeftRun:
|
PlayActionAsync(targetSlot, AnimationType.Run, isImmediate).Forget();
|
||||||
case EntranceType.RightRun:
|
|
||||||
StartCoroutine(PlayActionRoutine(targetSlot, ActionType.Run));
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 이미지 날리기 & 투명화 & 공간 닫기 (동시 실행 및 대기)
|
// 이미지 날리기 & 투명화 & 공간 닫기 (동시 실행 및 대기)
|
||||||
yield return Sequence.Create()
|
await Sequence.Create()
|
||||||
.Group(Tween.UIAnchoredPosition(containerRect, targetPos, defaultDuration, Ease.OutQuart))
|
.Group(Tween.UIAnchoredPosition(containerRect, targetPos, duration, Ease.OutQuart))
|
||||||
.Group(Tween.Alpha(charImage, 0f, defaultDuration * 0.8f))
|
.Group(Tween.Alpha(charImage, 0f, duration * 0.8f))
|
||||||
.Group(Tween.Custom(layoutElement, layoutElement.preferredWidth, 0f, defaultDuration, (t, x) => t.preferredWidth = x, Ease.OutQuart))
|
.Group(Tween.Custom(layoutElement, layoutElement.preferredWidth, 0f, duration, (t, x) => t.preferredWidth = x, Ease.OutQuart))
|
||||||
.ToYieldInstruction();
|
.ToUniTask(cancellationToken: this.GetCancellationTokenOnDestroy());
|
||||||
|
|
||||||
Destroy(targetSlot.gameObject);
|
Destroy(targetSlot.gameObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================= [3. 액션 (Action)] =========================
|
// ========================= [3. 액션 (Action)] =========================
|
||||||
public void PlayAction(string characterName, ActionType action)
|
public void PlayAction(string characterName, string action)
|
||||||
{
|
{
|
||||||
EnqueueAction(characterName, PlayActionRoutine(characterName, action));
|
EnqueueAction(characterName, (isImmediate) => PlayActionAsync(characterName, GetAnimationType(action), isImmediate));
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerator PlayActionRoutine(string characterName, ActionType action)
|
private async UniTask PlayActionAsync(string characterName, AnimationType action, bool isImmediate)
|
||||||
{
|
{
|
||||||
Transform targetSlot = FindSlot(characterName);
|
Transform targetSlot = FindSlot(characterName);
|
||||||
|
|
||||||
if (targetSlot == null)
|
if (targetSlot == null)
|
||||||
{
|
{
|
||||||
Debug.LogWarning($"액션 실패: '{characterName}' 캐릭터를 찾을 수 없습니다.");
|
Debug.LogWarning($"액션 실패: '{characterName}' 캐릭터를 찾을 수 없습니다.");
|
||||||
yield break;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return PlayActionRoutine(targetSlot, action);
|
await PlayActionAsync(targetSlot, action, isImmediate);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerator PlayActionRoutine(Transform targetSlot, ActionType action)
|
private async UniTask PlayActionAsync(Transform targetSlot, AnimationType action, bool isImmediate)
|
||||||
{
|
{
|
||||||
// [변경] 계층 구조 반영: Slot -> Container -> Image
|
// [변경] 계층 구조 반영: Slot -> Container -> Image
|
||||||
// 액션은 Image에만 적용 (Container는 이동 담당)
|
// 액션은 Image에만 적용 (Container는 이동 담당)
|
||||||
@@ -274,111 +226,81 @@ public class VNDirector : MonoBehaviour
|
|||||||
Tween.StopAll(targetImageRect);
|
Tween.StopAll(targetImageRect);
|
||||||
targetImageRect.anchoredPosition = Vector2.zero;
|
targetImageRect.anchoredPosition = Vector2.zero;
|
||||||
|
|
||||||
|
if (isImmediate) return; // 즉시 실행 시 애니메이션 스킵
|
||||||
|
|
||||||
Tween actionTween = default;
|
Tween actionTween = default;
|
||||||
Sequence actionSequence = default;
|
Sequence actionSequence = default;
|
||||||
bool isSequence = false;
|
bool isSequence = false;
|
||||||
|
|
||||||
switch (action)
|
switch (action)
|
||||||
{
|
{
|
||||||
case ActionType.Jump:
|
case AnimationType.Jump:
|
||||||
actionTween = Tween.PunchLocalPosition(targetImageRect, new Vector3(0, 100f, 0), 0.5f, frequency: 2);
|
actionTween = Tween.PunchLocalPosition(targetImageRect, new Vector3(0, 100f, 0), 0.5f, frequency: 2);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ActionType.Shake:
|
case AnimationType.Shake:
|
||||||
actionTween = Tween.ShakeLocalPosition(targetImageRect, new Vector3(50f, 0, 0), 0.5f, frequency: 10);
|
actionTween = Tween.ShakeLocalPosition(targetImageRect, new Vector3(50f, 0, 0), 0.5f, frequency: 10);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ActionType.Run:
|
case AnimationType.Run:
|
||||||
actionTween = Tween.PunchLocalPosition(targetImageRect, new Vector3(0, 50f, 0), 0.5f, frequency: 10);
|
actionTween = Tween.PunchLocalPosition(targetImageRect, new Vector3(0, 50f, 0), 0.5f, frequency: 10);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ActionType.Nod:
|
case AnimationType.Nod:
|
||||||
isSequence = true;
|
isSequence = true;
|
||||||
actionSequence = Sequence.Create()
|
actionSequence = Sequence.Create()
|
||||||
.Chain(Tween.UIAnchoredPositionY(targetImageRect, -30f, 0.15f, Ease.OutQuad))
|
.Chain(Tween.UIAnchoredPositionY(targetImageRect, -30f, 0.15f, Ease.OutQuad))
|
||||||
.Chain(Tween.UIAnchoredPositionY(targetImageRect, 0f, 0.15f, Ease.InQuad));
|
.Chain(Tween.UIAnchoredPositionY(targetImageRect, 0f, 0.15f, Ease.InQuad));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ActionType.Punch:
|
case AnimationType.Punch:
|
||||||
actionTween = Tween.PunchScale(targetImageRect, new Vector3(0.2f, 0.2f, 0), 0.4f, frequency: 1);
|
actionTween = Tween.PunchScale(targetImageRect, new Vector3(0.2f, 0.2f, 0), 0.4f, frequency: 1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSequence)
|
if (isSequence)
|
||||||
{
|
{
|
||||||
if (actionSequence.isAlive) yield return actionSequence.ToYieldInstruction();
|
if (actionSequence.isAlive) await actionSequence.ToUniTask(cancellationToken: this.GetCancellationTokenOnDestroy());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (actionTween.isAlive) yield return actionTween.ToYieldInstruction();
|
if (actionTween.isAlive) await actionTween.ToUniTask(cancellationToken: this.GetCancellationTokenOnDestroy());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================= [4. 표정 변경 (Change Expression)] =========================
|
// ========================= [4. 표정 변경 (Change Expression)] =========================
|
||||||
public void ChangeExpression(string characterName, string spriteName)
|
public void ChangeExpression(string characterName, string spriteName)
|
||||||
{
|
{
|
||||||
EnqueueAction(characterName, ChangeExpressionRoutine(characterName, spriteName));
|
EnqueueAction(characterName, (isImmediate) => ChangeExpressionAsync(characterName, spriteName, isImmediate));
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerator ChangeExpressionRoutine(string characterName, string spriteName)
|
private async UniTask ChangeExpressionAsync(string characterName, string spriteName, bool isImmediate)
|
||||||
{
|
{
|
||||||
Transform targetSlot = FindSlot(characterName);
|
Transform targetSlot = FindSlot(characterName);
|
||||||
if (targetSlot == null) yield break;
|
if (targetSlot == null) return;
|
||||||
|
|
||||||
// [변경] 계층 구조 반영
|
|
||||||
Image charImage = targetSlot.GetChild(0).GetChild(0).GetComponent<Image>();
|
Image charImage = targetSlot.GetChild(0).GetChild(0).GetComponent<Image>();
|
||||||
Sprite newSprite = Resources.Load<Sprite>("Images/Characters/" + spriteName);
|
Sprite newSprite = Resources.Load<Sprite>(CharacterPathPrefix + spriteName);
|
||||||
|
|
||||||
if (newSprite != null)
|
if (newSprite != null)
|
||||||
{
|
{
|
||||||
// 1. 마스크 컨테이너 생성
|
if (isImmediate)
|
||||||
GameObject maskObj = new("MaskContainer");
|
{
|
||||||
maskObj.transform.SetParent(charImage.transform, false); // [변경] 부모를 이미지로 설정하여 액션(Scale/Move) 동기화
|
charImage.sprite = newSprite;
|
||||||
|
FitImageToScreen(charImage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
RectTransform maskRect = maskObj.AddComponent<RectTransform>();
|
// 마스크 및 오버레이 설정
|
||||||
maskRect.anchorMin = new Vector2(0.5f, 1f); // Top Center
|
var (maskObj, maskRect, overlayRect) = SetupMaskAndOverlay(charImage, newSprite);
|
||||||
maskRect.anchorMax = new Vector2(0.5f, 1f);
|
|
||||||
maskRect.pivot = new Vector2(0.5f, 1f);
|
|
||||||
|
|
||||||
// 마스크 영역을 위로 올려서 상단 Softness가 이미지에 영향을 주지 않도록 함
|
|
||||||
float softnessOffset = 100f;
|
|
||||||
float currentWidth = charImage.rectTransform.rect.width; // [변경] 실제 이미지 너비 사용
|
|
||||||
maskRect.anchoredPosition = new Vector2(0, softnessOffset);
|
|
||||||
maskRect.sizeDelta = new Vector2(currentWidth, 0); // 너비는 캐릭터 폭, 높이는 0부터 시작
|
|
||||||
|
|
||||||
RectMask2D rectMask = maskObj.AddComponent<RectMask2D>();
|
|
||||||
rectMask.softness = new Vector2Int(0, (int)softnessOffset); // 세로 방향 Softness 설정
|
|
||||||
|
|
||||||
// 2. 오버레이 이미지 생성
|
|
||||||
GameObject overlayObj = new("ExpressionOverlay");
|
|
||||||
overlayObj.transform.SetParent(maskObj.transform, false);
|
|
||||||
|
|
||||||
Image overlayImage = overlayObj.AddComponent<Image>();
|
|
||||||
overlayImage.sprite = newSprite;
|
|
||||||
overlayImage.color = charImage.color;
|
|
||||||
overlayImage.material = charImage.material;
|
|
||||||
overlayImage.raycastTarget = charImage.raycastTarget;
|
|
||||||
overlayImage.type = Image.Type.Simple;
|
|
||||||
overlayImage.preserveAspect = true;
|
|
||||||
|
|
||||||
// 오버레이 위치 보정 (마스크가 올라간 만큼 내려줌)
|
|
||||||
RectTransform overlayRect = overlayImage.rectTransform;
|
|
||||||
overlayRect.anchorMin = new Vector2(0.5f, 1f);
|
|
||||||
overlayRect.anchorMax = new Vector2(0.5f, 1f);
|
|
||||||
overlayRect.pivot = new Vector2(0.5f, 1f);
|
|
||||||
overlayRect.anchoredPosition = new Vector2(0, -softnessOffset); // 원위치 유지
|
|
||||||
|
|
||||||
FitImageToScreen(overlayImage);
|
|
||||||
|
|
||||||
// 렌더링 순서 보장 (마스크 컨테이너를 가장 앞으로)
|
|
||||||
maskObj.transform.SetAsLastSibling();
|
|
||||||
|
|
||||||
// 3. 애니메이션 실행 (마스크 높이를 키워서 이미지를 드러냄)
|
// 3. 애니메이션 실행 (마스크 높이를 키워서 이미지를 드러냄)
|
||||||
// 목표 높이: 캐릭터 이미지 높이 + 오프셋
|
float softnessOffset = 100f;
|
||||||
float targetHeight = overlayRect.sizeDelta.y + softnessOffset;
|
float targetHeight = overlayRect.sizeDelta.y + softnessOffset;
|
||||||
|
float currentWidth = charImage.rectTransform.rect.width;
|
||||||
|
|
||||||
yield return Tween.UISizeDelta(maskRect, new Vector2(currentWidth, targetHeight), 0.5f, Ease.OutQuart)
|
await Tween.UISizeDelta(maskRect, new Vector2(currentWidth, targetHeight), 0.5f, Ease.OutQuart)
|
||||||
.ToYieldInstruction();
|
.ToUniTask(cancellationToken: this.GetCancellationTokenOnDestroy());
|
||||||
|
|
||||||
// 원본 교체 및 정리
|
// 원본 교체 및 정리
|
||||||
charImage.sprite = newSprite;
|
charImage.sprite = newSprite;
|
||||||
@@ -392,21 +314,124 @@ public class VNDirector : MonoBehaviour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// [Helper] 이름으로 슬롯 찾기
|
// ========================= [Helpers] =========================
|
||||||
|
|
||||||
|
private (GameObject slot, LayoutElement layout, RectTransform container, Image image) CreateCharacterSlot(string name)
|
||||||
|
{
|
||||||
|
GameObject newSlot = new GameObject(name);
|
||||||
|
newSlot.transform.SetParent(characterPanel, false);
|
||||||
|
|
||||||
|
LayoutElement layoutElement = newSlot.AddComponent<LayoutElement>();
|
||||||
|
|
||||||
|
GameObject motionContainer = new("MotionContainer");
|
||||||
|
RectTransform containerRect = motionContainer.AddComponent<RectTransform>();
|
||||||
|
motionContainer.transform.SetParent(newSlot.transform, false);
|
||||||
|
|
||||||
|
containerRect.anchorMin = Vector2.zero;
|
||||||
|
containerRect.anchorMax = Vector2.one;
|
||||||
|
containerRect.sizeDelta = Vector2.zero;
|
||||||
|
|
||||||
|
GameObject imageObj = new GameObject("Image");
|
||||||
|
imageObj.transform.SetParent(motionContainer.transform, false);
|
||||||
|
|
||||||
|
Image charImage = imageObj.AddComponent<Image>();
|
||||||
|
|
||||||
|
return (newSlot, layoutElement, containerRect, charImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private (GameObject maskObj, RectTransform maskRect, RectTransform overlayRect) SetupMaskAndOverlay(Image charImage, Sprite newSprite)
|
||||||
|
{
|
||||||
|
GameObject maskObj = new("MaskContainer");
|
||||||
|
maskObj.transform.SetParent(charImage.transform, false);
|
||||||
|
|
||||||
|
RectTransform maskRect = maskObj.AddComponent<RectTransform>();
|
||||||
|
maskRect.anchorMin = new Vector2(0.5f, 1f); // Top Center
|
||||||
|
maskRect.anchorMax = new Vector2(0.5f, 1f);
|
||||||
|
maskRect.pivot = new Vector2(0.5f, 1f);
|
||||||
|
|
||||||
|
float softnessOffset = 100f;
|
||||||
|
float currentWidth = charImage.rectTransform.rect.width;
|
||||||
|
maskRect.anchoredPosition = new Vector2(0, softnessOffset);
|
||||||
|
maskRect.sizeDelta = new Vector2(currentWidth, 0);
|
||||||
|
|
||||||
|
RectMask2D rectMask = maskObj.AddComponent<RectMask2D>();
|
||||||
|
rectMask.softness = new Vector2Int(0, (int)softnessOffset);
|
||||||
|
|
||||||
|
GameObject overlayObj = new("ExpressionOverlay");
|
||||||
|
overlayObj.transform.SetParent(maskObj.transform, false);
|
||||||
|
|
||||||
|
Image overlayImage = overlayObj.AddComponent<Image>();
|
||||||
|
overlayImage.sprite = newSprite;
|
||||||
|
overlayImage.color = charImage.color;
|
||||||
|
overlayImage.material = charImage.material;
|
||||||
|
overlayImage.raycastTarget = charImage.raycastTarget;
|
||||||
|
overlayImage.type = Image.Type.Simple;
|
||||||
|
overlayImage.preserveAspect = true;
|
||||||
|
|
||||||
|
RectTransform overlayRect = overlayImage.rectTransform;
|
||||||
|
overlayRect.anchorMin = new Vector2(0.5f, 1f);
|
||||||
|
overlayRect.anchorMax = new Vector2(0.5f, 1f);
|
||||||
|
overlayRect.pivot = new Vector2(0.5f, 1f);
|
||||||
|
overlayRect.anchoredPosition = new Vector2(0, -softnessOffset);
|
||||||
|
|
||||||
|
FitImageToScreen(overlayImage);
|
||||||
|
|
||||||
|
maskObj.transform.SetAsLastSibling();
|
||||||
|
|
||||||
|
return (maskObj, maskRect, overlayRect);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ArrangeSlotOrder(Transform slotTransform, DirectionType type)
|
||||||
|
{
|
||||||
|
int totalCount = characterPanel.childCount;
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case DirectionType.Left:
|
||||||
|
case DirectionType.RunLeft:
|
||||||
|
case DirectionType.BottomLeft:
|
||||||
|
slotTransform.SetSiblingIndex(0); break;
|
||||||
|
case DirectionType.Right:
|
||||||
|
case DirectionType.RunRight:
|
||||||
|
case DirectionType.BottomRight:
|
||||||
|
slotTransform.SetSiblingIndex(totalCount - 1); break;
|
||||||
|
case DirectionType.Center:
|
||||||
|
case DirectionType.Top:
|
||||||
|
List<Transform> activeChildren = new();
|
||||||
|
for (int i = 0; i < totalCount; i++)
|
||||||
|
{
|
||||||
|
Transform child = characterPanel.GetChild(i);
|
||||||
|
if (child != slotTransform && !child.name.Contains("_Removing"))
|
||||||
|
{
|
||||||
|
activeChildren.Add(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int targetIndex = activeChildren.Count / 2;
|
||||||
|
if (targetIndex < activeChildren.Count)
|
||||||
|
{
|
||||||
|
slotTransform.SetSiblingIndex(activeChildren[targetIndex].GetSiblingIndex());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
slotTransform.SetSiblingIndex(totalCount - 1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Transform FindSlot(string name)
|
private Transform FindSlot(string name)
|
||||||
{
|
{
|
||||||
// CharacterPanel 바로 아래 자식들 중에서 이름을 검색
|
|
||||||
return characterPanel.Find(name);
|
return characterPanel.Find(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector2 GetDirectionVector(EntranceType type)
|
private Vector2 GetDirectionVector(DirectionType type)
|
||||||
{
|
{
|
||||||
return type switch
|
return type switch
|
||||||
{
|
{
|
||||||
EntranceType.Left or EntranceType.LeftRun => new Vector2(-moveDistance, 0),
|
DirectionType.Left or DirectionType.RunLeft => new Vector2(-moveDistance, 0),
|
||||||
EntranceType.Right or EntranceType.RightRun => new Vector2(moveDistance, 0),
|
DirectionType.Right or DirectionType.RunRight => new Vector2(moveDistance, 0),
|
||||||
EntranceType.Center or EntranceType.BottomLeft or EntranceType.BottomRight => new Vector2(0, -moveDistance),
|
DirectionType.Center or DirectionType.BottomLeft or DirectionType.BottomRight => new Vector2(0, -moveDistance),
|
||||||
EntranceType.Top => new Vector2(0, moveDistance),
|
DirectionType.Top => new Vector2(0, moveDistance),
|
||||||
_ => Vector2.zero,
|
_ => Vector2.zero,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -415,12 +440,7 @@ public class VNDirector : MonoBehaviour
|
|||||||
{
|
{
|
||||||
image.SetNativeSize();
|
image.SetNativeSize();
|
||||||
|
|
||||||
Canvas rootCanvas = GetComponentInParent<Canvas>();
|
float maxHeight = Screen.height * 0.95f;
|
||||||
if (rootCanvas == null) return;
|
|
||||||
|
|
||||||
RectTransform canvasRect = rootCanvas.GetComponent<RectTransform>();
|
|
||||||
// 화면 높이의 95%를 넘지 않도록 설정
|
|
||||||
float maxHeight = canvasRect.rect.height * 0.95f;
|
|
||||||
|
|
||||||
if (image.rectTransform.rect.height > maxHeight)
|
if (image.rectTransform.rect.height > maxHeight)
|
||||||
{
|
{
|
||||||
@@ -431,4 +451,31 @@ public class VNDirector : MonoBehaviour
|
|||||||
image.rectTransform.sizeDelta = new Vector2(newWidth, newHeight);
|
image.rectTransform.sizeDelta = new Vector2(newWidth, newHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private AnimationType GetAnimationType(string str)
|
||||||
|
{
|
||||||
|
return str switch
|
||||||
|
{
|
||||||
|
"jump" => AnimationType.Jump,
|
||||||
|
"shake" => AnimationType.Shake,
|
||||||
|
"run" => AnimationType.Run,
|
||||||
|
"nod" => AnimationType.Nod,
|
||||||
|
"punch" => AnimationType.Punch
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private DirectionType GetDirectionType(string str)
|
||||||
|
{
|
||||||
|
return str switch
|
||||||
|
{
|
||||||
|
"left" => DirectionType.Left,
|
||||||
|
"right" => DirectionType.Right,
|
||||||
|
"center" => DirectionType.Center,
|
||||||
|
"bottomleft" => DirectionType.BottomLeft,
|
||||||
|
"bottomright" => DirectionType.BottomRight,
|
||||||
|
"top" => DirectionType.Top,
|
||||||
|
"runleft" => DirectionType.RunLeft,
|
||||||
|
"runright" => DirectionType.RunRight
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -26,10 +26,9 @@ public class VNManager : MonoBehaviour
|
|||||||
float charsPerSecond = 45f;
|
float charsPerSecond = 45f;
|
||||||
|
|
||||||
public VNDirector director;
|
public VNDirector director;
|
||||||
private readonly float shakeAmount = 1.1f;
|
|
||||||
private bool isChoiceAvailable = false;
|
private bool isChoiceAvailable = false;
|
||||||
private Tween dialogueTween;
|
private Tween dialogueTween;
|
||||||
private Command _currentScript;
|
private Script _currentScript;
|
||||||
|
|
||||||
public static string NextScriptPath = "";
|
public static string NextScriptPath = "";
|
||||||
|
|
||||||
@@ -81,9 +80,9 @@ public class VNManager : MonoBehaviour
|
|||||||
|
|
||||||
private void NextStep()
|
private void NextStep()
|
||||||
{
|
{
|
||||||
if (_currentScript.HasNextAction())
|
if (_currentScript.HasNextCommand())
|
||||||
{
|
{
|
||||||
CommandSet command = _currentScript.Continue();
|
Command command = _currentScript.Continue();
|
||||||
Execute(command);
|
Execute(command);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -91,7 +90,7 @@ public class VNManager : MonoBehaviour
|
|||||||
Debug.Log("ScriptManager :: End of Script");
|
Debug.Log("ScriptManager :: End of Script");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Execute(CommandSet command)
|
private void Execute(Command command)
|
||||||
{
|
{
|
||||||
if (command.Type == "label")
|
if (command.Type == "label")
|
||||||
{
|
{
|
||||||
@@ -111,19 +110,10 @@ public class VNManager : MonoBehaviour
|
|||||||
{
|
{
|
||||||
string charFile = command.GetParam("img");
|
string charFile = command.GetParam("img");
|
||||||
if (string.IsNullOrEmpty(charFile))
|
if (string.IsNullOrEmpty(charFile))
|
||||||
{
|
|
||||||
charFile = command.GetParam("target");
|
charFile = command.GetParam("target");
|
||||||
}
|
|
||||||
string charEntrance = command.GetParam("enter");
|
string direction = command.GetParam("enter").ToLower();
|
||||||
if (charEntrance == "") charEntrance = "center";
|
director.AddCharacter(charFile, direction);
|
||||||
if (charEntrance.ToLower() == "center") director.AddCharacter(charFile, VNDirector.EntranceType.Center);
|
|
||||||
if (charEntrance.ToLower() == "top") director.AddCharacter(charFile, VNDirector.EntranceType.Top);
|
|
||||||
if (charEntrance.ToLower() == "left") director.AddCharacter(charFile, VNDirector.EntranceType.Left);
|
|
||||||
if (charEntrance.ToLower() == "right") director.AddCharacter(charFile, VNDirector.EntranceType.Right);
|
|
||||||
if (charEntrance.ToLower() == "bottomleft") director.AddCharacter(charFile, VNDirector.EntranceType.BottomLeft);
|
|
||||||
if (charEntrance.ToLower() == "bottomright") director.AddCharacter(charFile, VNDirector.EntranceType.BottomRight);
|
|
||||||
if (charEntrance.ToLower() == "leftrun") director.AddCharacter(charFile, VNDirector.EntranceType.LeftRun);
|
|
||||||
if (charEntrance.ToLower() == "rightrun") director.AddCharacter(charFile, VNDirector.EntranceType.RightRun);
|
|
||||||
Debug.Log($"ScriptManager :: Character: {charFile}");
|
Debug.Log($"ScriptManager :: Character: {charFile}");
|
||||||
NextStep();
|
NextStep();
|
||||||
return;
|
return;
|
||||||
@@ -131,34 +121,18 @@ public class VNManager : MonoBehaviour
|
|||||||
if (command.Type == "remove")
|
if (command.Type == "remove")
|
||||||
{
|
{
|
||||||
string charName = command.GetParam("target");
|
string charName = command.GetParam("target");
|
||||||
string exitType = command.GetParam("exit");
|
string direction = command.GetParam("exit").ToLower();
|
||||||
if (exitType == "") exitType = "center";
|
|
||||||
|
|
||||||
VNDirector.EntranceType type = new();
|
director.RemoveCharacter(charName, direction);
|
||||||
if (exitType.ToLower() == "center") type = VNDirector.EntranceType.Center;
|
Debug.Log($"ScriptManager :: Remove Character: {charName} to {direction}");
|
||||||
if (exitType.ToLower() == "left") type = VNDirector.EntranceType.Left;
|
|
||||||
if (exitType.ToLower() == "right") type = VNDirector.EntranceType.Right;
|
|
||||||
if (exitType.ToLower() == "bottomleft") type = VNDirector.EntranceType.BottomLeft;
|
|
||||||
if (exitType.ToLower() == "bottomright") type = VNDirector.EntranceType.BottomRight;
|
|
||||||
if (exitType.ToLower() == "top") type = VNDirector.EntranceType.Top;
|
|
||||||
if (exitType.ToLower() == "leftrun") type = VNDirector.EntranceType.LeftRun;
|
|
||||||
if (exitType.ToLower() == "rightrun") type = VNDirector.EntranceType.RightRun;
|
|
||||||
|
|
||||||
director.RemoveCharacter(charName, type);
|
|
||||||
Debug.Log($"ScriptManager :: Remove Character: {charName} to {exitType}");
|
|
||||||
NextStep();
|
NextStep();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (command.Type == "action")
|
if (command.Type == "action")
|
||||||
{
|
{
|
||||||
string charName = command.GetParam("target");
|
string charName = command.GetParam("target");
|
||||||
string charAnim = command.GetParam("anim");
|
string charAnim = command.GetParam("anim").ToLower();
|
||||||
if (charAnim == "") charAnim = "center";
|
director.PlayAction(charName, charAnim);
|
||||||
if (charAnim.ToLower() == "jump") director.PlayAction(charName, VNDirector.ActionType.Jump);
|
|
||||||
if (charAnim.ToLower() == "shake") director.PlayAction(charName, VNDirector.ActionType.Shake);
|
|
||||||
if (charAnim.ToLower() == "run") director.PlayAction(charName, VNDirector.ActionType.Run);
|
|
||||||
if (charAnim.ToLower() == "nod") director.PlayAction(charName, VNDirector.ActionType.Nod);
|
|
||||||
if (charAnim.ToLower() == "punch") director.PlayAction(charName, VNDirector.ActionType.Punch);
|
|
||||||
Debug.Log($"ScriptManager :: Action: {charName} {charAnim}");
|
Debug.Log($"ScriptManager :: Action: {charName} {charAnim}");
|
||||||
NextStep();
|
NextStep();
|
||||||
return;
|
return;
|
||||||
@@ -272,6 +246,33 @@ public class VNManager : MonoBehaviour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private VNDirector.AnimationType GetAnimationType(string str)
|
||||||
|
{
|
||||||
|
return str switch
|
||||||
|
{
|
||||||
|
"jump" => VNDirector.AnimationType.Jump,
|
||||||
|
"shake" => VNDirector.AnimationType.Shake,
|
||||||
|
"run" => VNDirector.AnimationType.Run,
|
||||||
|
"nod" => VNDirector.AnimationType.Nod,
|
||||||
|
"punch" => VNDirector.AnimationType.Punch
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private VNDirector.DirectionType GetDirectionType(string str)
|
||||||
|
{
|
||||||
|
return str switch
|
||||||
|
{
|
||||||
|
"left" => VNDirector.DirectionType.Left,
|
||||||
|
"right" => VNDirector.DirectionType.Right,
|
||||||
|
"center" => VNDirector.DirectionType.Center,
|
||||||
|
"bottomleft" => VNDirector.DirectionType.BottomLeft,
|
||||||
|
"bottomright" => VNDirector.DirectionType.BottomRight,
|
||||||
|
"top" => VNDirector.DirectionType.Top,
|
||||||
|
"runleft" => VNDirector.DirectionType.RunLeft,
|
||||||
|
"runright" => VNDirector.DirectionType.RunRight
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public void DebugReload()
|
public void DebugReload()
|
||||||
{
|
{
|
||||||
speakerText.SetText(" ");
|
speakerText.SetText(" ");
|
||||||
@@ -342,10 +343,7 @@ public class VNManager : MonoBehaviour
|
|||||||
|
|
||||||
if (linkName == "shake")
|
if (linkName == "shake")
|
||||||
{
|
{
|
||||||
Vector3 offset = new(
|
Vector3 offset = new(Random.Range(-1.1f, 1.1f), Random.Range(-1.1f, 1.1f));
|
||||||
Random.Range(-shakeAmount, shakeAmount),
|
|
||||||
Random.Range(-shakeAmount, shakeAmount)
|
|
||||||
);
|
|
||||||
for (byte j = 0; j < 4; j++)
|
for (byte j = 0; j < 4; j++)
|
||||||
vertices[idx + j] += offset;
|
vertices[idx + j] += offset;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"com.cysharp.unitask": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask",
|
||||||
"com.heavycaffiner.ide.vscodeuniversal": "https://github.com/heavycaffeiner/Unity-VSC-OSS-Editor.git",
|
"com.heavycaffiner.ide.vscodeuniversal": "https://github.com/heavycaffeiner/Unity-VSC-OSS-Editor.git",
|
||||||
"com.kyrylokuzyk.primetween": "file:../Assets/Plugins/PrimeTween/internal/com.kyrylokuzyk.primetween.tgz",
|
"com.kyrylokuzyk.primetween": "file:../Assets/Plugins/PrimeTween/internal/com.kyrylokuzyk.primetween.tgz",
|
||||||
"com.unity.2d.sprite": "1.0.0",
|
"com.unity.2d.sprite": "1.0.0",
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"com.cysharp.unitask": {
|
||||||
|
"version": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask",
|
||||||
|
"depth": 0,
|
||||||
|
"source": "git",
|
||||||
|
"dependencies": {},
|
||||||
|
"hash": "73a63b7f672b88f7e9992f6917eb458a8cbb6fa9"
|
||||||
|
},
|
||||||
"com.heavycaffiner.ide.vscodeuniversal": {
|
"com.heavycaffiner.ide.vscodeuniversal": {
|
||||||
"version": "https://github.com/heavycaffeiner/Unity-VSC-OSS-Editor.git",
|
"version": "https://github.com/heavycaffeiner/Unity-VSC-OSS-Editor.git",
|
||||||
"depth": 0,
|
"depth": 0,
|
||||||
@@ -45,8 +52,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"com.unity.burst": "1.8.23",
|
"com.unity.burst": "1.8.23",
|
||||||
"com.unity.mathematics": "1.3.2",
|
"com.unity.mathematics": "1.3.2",
|
||||||
"com.unity.nuget.mono-cecil": "1.11.5",
|
|
||||||
"com.unity.test-framework": "1.4.6",
|
"com.unity.test-framework": "1.4.6",
|
||||||
|
"com.unity.nuget.mono-cecil": "1.11.5",
|
||||||
"com.unity.test-framework.performance": "3.0.3"
|
"com.unity.test-framework.performance": "3.0.3"
|
||||||
},
|
},
|
||||||
"url": "https://packages.unity.com"
|
"url": "https://packages.unity.com"
|
||||||
@@ -172,9 +179,9 @@
|
|||||||
"depth": 0,
|
"depth": 0,
|
||||||
"source": "registry",
|
"source": "registry",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"com.unity.modules.audio": "1.0.0",
|
||||||
"com.unity.modules.director": "1.0.0",
|
"com.unity.modules.director": "1.0.0",
|
||||||
"com.unity.modules.animation": "1.0.0",
|
"com.unity.modules.animation": "1.0.0",
|
||||||
"com.unity.modules.audio": "1.0.0",
|
|
||||||
"com.unity.modules.particlesystem": "1.0.0"
|
"com.unity.modules.particlesystem": "1.0.0"
|
||||||
},
|
},
|
||||||
"url": "https://packages.unity.com"
|
"url": "https://packages.unity.com"
|
||||||
|
|||||||
Reference in New Issue
Block a user