mirror of
https://github.com/HoonTB/Project-AS.git
synced 2025-12-26 11:51:21 +09:00
feat: refactor character animation system with motion container
- Separate character hierarchy into Slot → MotionContainer → Image - Improve expression change with mask-based smooth transition - Replace scene-based navigation with label-based jump system - Add Top direction support and refactor direction vector logic - Minor code improvements (C# index operator, cleaner initialization)
This commit is contained in:
@@ -5,12 +5,12 @@ public class Script
|
||||
{
|
||||
private List<ScriptAction> _actions;
|
||||
private int _currentIndex = -1;
|
||||
private Dictionary<string, int> _sceneMap = new Dictionary<string, int>();
|
||||
private Dictionary<string, int> _labelMap = new();
|
||||
|
||||
public Script(List<ScriptAction> actions, Dictionary<string, int> sceneMap)
|
||||
public Script(List<ScriptAction> actions, Dictionary<string, int> labelMap)
|
||||
{
|
||||
_actions = actions;
|
||||
_sceneMap = sceneMap;
|
||||
_labelMap = labelMap;
|
||||
_currentIndex = -1;
|
||||
}
|
||||
|
||||
@@ -37,10 +37,10 @@ public class Script
|
||||
return null;
|
||||
}
|
||||
|
||||
public void JumpTo(string sceneName)
|
||||
public void JumpTo(string labelName)
|
||||
{
|
||||
_currentIndex = _sceneMap[sceneName] - 1; // Continue() 호출 시 해당 인덱스가 되도록 -1
|
||||
Debug.Log($"Script :: Jump to scene: {sceneName} (Index: {_currentIndex + 1})");
|
||||
_currentIndex = _labelMap[labelName] - 1; // Continue() 호출 시 해당 인덱스가 되도록 -1
|
||||
Debug.Log($"Script :: Jump to label: {labelName} (Index: {_currentIndex + 1})");
|
||||
}
|
||||
|
||||
public void Save()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
using PrimeTween;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
@@ -38,36 +37,10 @@ public class ScriptManager : MonoBehaviour
|
||||
dialogueText.SetText(" ");
|
||||
dialogueText.ForceMeshUpdate(true);
|
||||
|
||||
StartCoroutine(TestAnim());
|
||||
|
||||
_currentScript = ScriptParser.Parse(scriptFile.text);
|
||||
NextStep();
|
||||
}
|
||||
|
||||
IEnumerator TestAnim()
|
||||
{
|
||||
director.AddCharacter("chino01", VisualNovelLayoutDirector.EntranceType.Center);
|
||||
yield return new WaitForSeconds(1f);
|
||||
director.AddCharacter("chino02", VisualNovelLayoutDirector.EntranceType.Left);
|
||||
yield return new WaitForSeconds(1f);
|
||||
director.AddCharacter("chino03", VisualNovelLayoutDirector.EntranceType.Right);
|
||||
yield return new WaitForSeconds(1f);
|
||||
director.RemoveCharacter("chino02", VisualNovelLayoutDirector.EntranceType.Left);
|
||||
yield return new WaitForSeconds(1f);
|
||||
director.RemoveCharacter("chino03", VisualNovelLayoutDirector.EntranceType.Right);
|
||||
yield return new WaitForSeconds(1f);
|
||||
director.PlayAction("chino01", VisualNovelLayoutDirector.ActionType.Jump);
|
||||
yield return new WaitForSeconds(1f);
|
||||
director.PlayAction("chino01", VisualNovelLayoutDirector.ActionType.Shake);
|
||||
yield return new WaitForSeconds(1f);
|
||||
director.PlayAction("chino01", VisualNovelLayoutDirector.ActionType.Nod);
|
||||
yield return new WaitForSeconds(1f);
|
||||
director.PlayAction("chino01", VisualNovelLayoutDirector.ActionType.Punch);
|
||||
yield return new WaitForSeconds(1f);
|
||||
director.AddCharacter("chino02", VisualNovelLayoutDirector.EntranceType.Left);
|
||||
yield return new WaitForSeconds(1f);
|
||||
director.AddCharacter("chino03", VisualNovelLayoutDirector.EntranceType.Center);
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
@@ -96,10 +69,10 @@ public class ScriptManager : MonoBehaviour
|
||||
|
||||
private void ExecuteAction(ScriptAction action)
|
||||
{
|
||||
if (action.Type == "scene")
|
||||
if (action.Type == "label")
|
||||
{
|
||||
string sceneName = action.GetParam("name");
|
||||
Debug.Log($"ScriptManager :: Change Scene: {sceneName}");
|
||||
string labelName = action.GetParam("content");
|
||||
Debug.Log($"ScriptManager :: Change Label: {labelName}");
|
||||
NextStep();
|
||||
return;
|
||||
}
|
||||
@@ -110,6 +83,61 @@ public class ScriptManager : MonoBehaviour
|
||||
NextStep();
|
||||
return;
|
||||
}
|
||||
if (action.Type == "char")
|
||||
{
|
||||
string charFile = action.GetParam("img");
|
||||
string charEntrance = action.GetParam("enter");
|
||||
if (charEntrance == "") charEntrance = "center";
|
||||
if (charEntrance.ToLower() == "center") director.AddCharacter(charFile, VisualNovelLayoutDirector.EntranceType.Center);
|
||||
if (charEntrance.ToLower() == "left") director.AddCharacter(charFile, VisualNovelLayoutDirector.EntranceType.Left);
|
||||
if (charEntrance.ToLower() == "right") director.AddCharacter(charFile, VisualNovelLayoutDirector.EntranceType.Right);
|
||||
if (charEntrance.ToLower() == "bottomleft") director.AddCharacter(charFile, VisualNovelLayoutDirector.EntranceType.BottomLeft);
|
||||
if (charEntrance.ToLower() == "bottomright") director.AddCharacter(charFile, VisualNovelLayoutDirector.EntranceType.BottomRight);
|
||||
Debug.Log($"ScriptManager :: Character: {charFile}");
|
||||
NextStep();
|
||||
return;
|
||||
}
|
||||
if (action.Type == "remove")
|
||||
{
|
||||
string charName = action.GetParam("target");
|
||||
string exitType = action.GetParam("exit");
|
||||
if (exitType == "") exitType = "center";
|
||||
|
||||
VisualNovelLayoutDirector.EntranceType type = VisualNovelLayoutDirector.EntranceType.Center;
|
||||
if (exitType.ToLower() == "left") type = VisualNovelLayoutDirector.EntranceType.Left;
|
||||
if (exitType.ToLower() == "right") type = VisualNovelLayoutDirector.EntranceType.Right;
|
||||
if (exitType.ToLower() == "bottomleft") type = VisualNovelLayoutDirector.EntranceType.BottomLeft;
|
||||
if (exitType.ToLower() == "bottomright") type = VisualNovelLayoutDirector.EntranceType.BottomRight;
|
||||
if (exitType.ToLower() == "top") type = VisualNovelLayoutDirector.EntranceType.Top;
|
||||
|
||||
director.RemoveCharacter(charName, type);
|
||||
Debug.Log($"ScriptManager :: Remove Character: {charName} to {exitType}");
|
||||
NextStep();
|
||||
return;
|
||||
}
|
||||
if (action.Type == "action")
|
||||
{
|
||||
string charName = action.GetParam("target");
|
||||
string charAnim = action.GetParam("anim");
|
||||
if (charAnim == "") charAnim = "center";
|
||||
if (charAnim.ToLower() == "jump") director.PlayAction(charName, VisualNovelLayoutDirector.ActionType.Jump);
|
||||
if (charAnim.ToLower() == "shake") director.PlayAction(charName, VisualNovelLayoutDirector.ActionType.Shake);
|
||||
if (charAnim.ToLower() == "shakehorizontal") director.PlayAction(charName, VisualNovelLayoutDirector.ActionType.ShakeHorizontal);
|
||||
if (charAnim.ToLower() == "nod") director.PlayAction(charName, VisualNovelLayoutDirector.ActionType.Nod);
|
||||
if (charAnim.ToLower() == "punch") director.PlayAction(charName, VisualNovelLayoutDirector.ActionType.Punch);
|
||||
Debug.Log($"ScriptManager :: Action: {charName} {charAnim}");
|
||||
NextStep();
|
||||
return;
|
||||
}
|
||||
if (action.Type == "expr")
|
||||
{
|
||||
string charName = action.GetParam("target");
|
||||
string charExpr = action.GetParam("expr");
|
||||
director.ChangeExpression(charName, charExpr);
|
||||
Debug.Log($"ScriptManager :: Expression: {charName} {charExpr}");
|
||||
NextStep();
|
||||
return;
|
||||
}
|
||||
if (action.Type == "spk")
|
||||
{
|
||||
string speaker = action.GetParam("name");
|
||||
@@ -129,8 +157,8 @@ public class ScriptManager : MonoBehaviour
|
||||
}
|
||||
if (action.Type == "goto")
|
||||
{
|
||||
string targetScene = action.GetParam("scene");
|
||||
_currentScript.JumpTo(targetScene);
|
||||
string targetLabel = action.GetParam("content");
|
||||
_currentScript.JumpTo(targetLabel);
|
||||
NextStep();
|
||||
return;
|
||||
}
|
||||
@@ -182,9 +210,11 @@ public class ScriptManager : MonoBehaviour
|
||||
|
||||
private bool IsPointerOverInteractiveUI()
|
||||
{
|
||||
PointerEventData eventData = new PointerEventData(EventSystem.current);
|
||||
eventData.position = Input.mousePosition;
|
||||
List<RaycastResult> results = new List<RaycastResult>();
|
||||
PointerEventData eventData = new(EventSystem.current)
|
||||
{
|
||||
position = Input.mousePosition
|
||||
};
|
||||
List<RaycastResult> results = new();
|
||||
EventSystem.current.RaycastAll(eventData, results);
|
||||
|
||||
foreach (RaycastResult result in results)
|
||||
@@ -238,7 +268,7 @@ public class ScriptManager : MonoBehaviour
|
||||
|
||||
if (linkName == "shake")
|
||||
{
|
||||
Vector3 offset = new Vector3(
|
||||
Vector3 offset = new(
|
||||
Random.Range(-shakeAmount, shakeAmount),
|
||||
Random.Range(-shakeAmount, shakeAmount)
|
||||
);
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using UnityEngine;
|
||||
|
||||
public class ScriptParser
|
||||
{
|
||||
private static readonly Regex TagRegex = new Regex(@"^\[(\w+)(?:\s+(.*))?\]$");
|
||||
private static readonly Regex AttrRegex = new Regex(@"(\w+)=(""[^""]*""|'[^']*'|[^ \t\]]+)");
|
||||
private static readonly Regex ChoiceOptionRegex = new Regex(@"^\*\s*(.+?)\s*>\s*(.+)$");
|
||||
private static readonly Regex TagRegex = new(@"^\[(\w+)(?:\s+(.*))?\]$");
|
||||
private static readonly Regex AttrRegex = new(@"(\w+)=(""[^""]*""|'[^']*'|[^ \t\]]+)");
|
||||
private static readonly Regex ChoiceOptionRegex = new(@"^\*\s*(.+?)\s*>\s*(.+)$");
|
||||
|
||||
public static Script Parse(string text)
|
||||
{
|
||||
List<ScriptAction> actions = new();
|
||||
Dictionary<string, int> sceneMap = new();
|
||||
Dictionary<string, int> labelMap = new();
|
||||
|
||||
ScriptAction lastChoice = null;
|
||||
|
||||
@@ -31,16 +32,19 @@ public class ScriptParser
|
||||
{
|
||||
string tagName = tagMatch.Groups[1].Value;
|
||||
string attrString = tagMatch.Groups[2].Value;
|
||||
Debug.Log($"ScriptParser :: Tag: {tagName} {attrString}");
|
||||
|
||||
var scriptAction = new ScriptAction { Type = tagName };
|
||||
ParseAttributes(attrString, scriptAction.Params);
|
||||
|
||||
if (tagName == "scene")
|
||||
if (!attrString.Contains("=")) scriptAction.Params["content"] = attrString;
|
||||
else ParseAttributes(attrString, scriptAction.Params);
|
||||
|
||||
if (tagName == "label")
|
||||
{
|
||||
string sceneName = scriptAction.GetParam("name");
|
||||
if (!string.IsNullOrEmpty(sceneName) && !sceneMap.ContainsKey(sceneName))
|
||||
string label = scriptAction.GetParam("content");
|
||||
if (!string.IsNullOrEmpty(label) && !labelMap.ContainsKey(label))
|
||||
{
|
||||
sceneMap[sceneName] = actions.Count;
|
||||
labelMap[label] = actions.Count;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +75,7 @@ public class ScriptParser
|
||||
actions.Add(new ScriptAction { Type = "msg", Params = { { "content", line } } });
|
||||
}
|
||||
|
||||
return new Script(actions, sceneMap);
|
||||
return new Script(actions, labelMap);
|
||||
}
|
||||
|
||||
private static void ParseAttributes(string attrString, Dictionary<string, object> paramDict)
|
||||
@@ -85,7 +89,7 @@ public class ScriptParser
|
||||
string rawValue = m.Groups[2].Value;
|
||||
|
||||
if (rawValue.Length >= 2 && (rawValue.StartsWith("\"") || rawValue.StartsWith("'")))
|
||||
rawValue = rawValue.Substring(1, rawValue.Length - 2);
|
||||
rawValue = rawValue[1..^1];
|
||||
|
||||
paramDict[key] = rawValue;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using PrimeTween;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using PrimeTween;
|
||||
using System.Collections;
|
||||
|
||||
public class VisualNovelLayoutDirector : MonoBehaviour
|
||||
{
|
||||
// ========================= [Enums] =========================
|
||||
public enum EntranceType { Left, Right, BottomLeft, BottomRight, Center, Top }
|
||||
public enum ActionType { Jump, Shake, Nod, Punch }
|
||||
public enum ActionType { Jump, Shake, Nod, Punch, ShakeHorizontal }
|
||||
|
||||
[Header("UI 연결")]
|
||||
public Transform characterPanel;
|
||||
@@ -18,6 +19,8 @@ public class VisualNovelLayoutDirector : MonoBehaviour
|
||||
public float defaultDuration = 0.5f;
|
||||
public float moveDistance = 800f;
|
||||
|
||||
|
||||
|
||||
// ========================= [1. 등장 (Entry)] =========================
|
||||
public void AddCharacter(string fileName, EntranceType type)
|
||||
{
|
||||
@@ -50,7 +53,24 @@ public class VisualNovelLayoutDirector : MonoBehaviour
|
||||
newSlot.name = name;
|
||||
|
||||
LayoutElement layoutElement = newSlot.GetComponent<LayoutElement>();
|
||||
Image charImage = newSlot.transform.GetChild(0).GetComponent<Image>();
|
||||
|
||||
// [변경] MotionContainer 생성 및 계층 구조 변경
|
||||
// 기존: Slot -> Image
|
||||
// 변경: 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. 초기화
|
||||
charImage.sprite = sprite;
|
||||
@@ -73,14 +93,15 @@ public class VisualNovelLayoutDirector : MonoBehaviour
|
||||
}
|
||||
|
||||
// 4. 위치 잡기 및 애니메이션
|
||||
// [변경] 움직임은 MotionContainer가 담당
|
||||
Vector2 startPos = GetDirectionVector(type);
|
||||
charImage.rectTransform.anchoredPosition = startPos;
|
||||
containerRect.anchoredPosition = startPos; // Image -> Container
|
||||
charImage.color = new Color(1, 1, 1, 0);
|
||||
|
||||
yield return new WaitForEndOfFrame();
|
||||
|
||||
Tween.Custom(layoutElement, 0f, charWidth, defaultDuration, (t, x) => t.preferredWidth = x, Ease.OutQuart);
|
||||
Tween.UIAnchoredPosition(charImage.rectTransform, Vector2.zero, defaultDuration, Ease.OutQuart);
|
||||
Tween.UIAnchoredPosition(containerRect, Vector2.zero, defaultDuration, Ease.OutQuart); // Image -> Container
|
||||
Tween.Alpha(charImage, 1f, defaultDuration);
|
||||
}
|
||||
|
||||
@@ -102,14 +123,20 @@ public class VisualNovelLayoutDirector : MonoBehaviour
|
||||
private IEnumerator ExitRoutine(Transform slotTransform, EntranceType exitTo)
|
||||
{
|
||||
// 중복 호출 방지를 위해 이름을 바꿔둠 (빠르게 연타했을 때 에러 방지)
|
||||
slotTransform.name = slotTransform.name + "_Removing";
|
||||
slotTransform.name += "_Removing";
|
||||
|
||||
LayoutElement layoutElement = slotTransform.GetComponent<LayoutElement>();
|
||||
Image charImage = slotTransform.GetChild(0).GetComponent<Image>();
|
||||
|
||||
// [변경] 계층 구조 반영
|
||||
Transform container = slotTransform.GetChild(0); // MotionContainer
|
||||
RectTransform containerRect = container.GetComponent<RectTransform>();
|
||||
Image charImage = container.GetChild(0).GetComponent<Image>(); // Image
|
||||
|
||||
Vector2 targetPos = GetDirectionVector(exitTo);
|
||||
|
||||
// 이미지 날리기 & 투명화
|
||||
Tween.UIAnchoredPosition(charImage.rectTransform, targetPos, defaultDuration, Ease.OutQuart);
|
||||
// [변경] 움직임은 Container, 투명도는 Image
|
||||
Tween.UIAnchoredPosition(containerRect, targetPos, defaultDuration, Ease.OutQuart);
|
||||
Tween.Alpha(charImage, 0f, defaultDuration * 0.8f);
|
||||
|
||||
// 공간 닫기
|
||||
@@ -121,16 +148,23 @@ public class VisualNovelLayoutDirector : MonoBehaviour
|
||||
|
||||
// ========================= [3. 액션 (Action)] =========================
|
||||
public void PlayAction(string characterName, ActionType action)
|
||||
{
|
||||
StartCoroutine(PlayActionRoutine(characterName, action));
|
||||
}
|
||||
|
||||
private IEnumerator PlayActionRoutine(string characterName, ActionType action)
|
||||
{
|
||||
Transform targetSlot = FindSlot(characterName);
|
||||
|
||||
if (targetSlot == null)
|
||||
{
|
||||
Debug.LogWarning($"액션 실패: '{characterName}' 캐릭터를 찾을 수 없습니다.");
|
||||
return;
|
||||
yield break;
|
||||
}
|
||||
|
||||
RectTransform targetImageRect = targetSlot.GetChild(0).GetComponent<RectTransform>();
|
||||
// [변경] 계층 구조 반영: Slot -> Container -> Image
|
||||
// 액션은 Image에만 적용 (Container는 이동 담당)
|
||||
RectTransform targetImageRect = targetSlot.GetChild(0).GetChild(0).GetComponent<RectTransform>();
|
||||
|
||||
// 기존 애니메이션 정지 및 초기화
|
||||
Tween.StopAll(targetImageRect);
|
||||
@@ -148,6 +182,11 @@ public class VisualNovelLayoutDirector : MonoBehaviour
|
||||
Tween.ShakeLocalPosition(targetImageRect, new Vector3(50f, 0, 0), 0.5f, frequency: 10);
|
||||
break;
|
||||
|
||||
case ActionType.ShakeHorizontal:
|
||||
// 상하 흔들기 (진동 횟수 10번)
|
||||
Tween.PunchLocalPosition(targetImageRect, new Vector3(0, 50f, 0), 0.5f, frequency: 10);
|
||||
break;
|
||||
|
||||
case ActionType.Nod:
|
||||
// (Sequence는 변경 없음)
|
||||
Sequence.Create()
|
||||
@@ -162,6 +201,84 @@ public class VisualNovelLayoutDirector : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
// ========================= [4. 표정 변경 (Change Expression)] =========================
|
||||
public void ChangeExpression(string characterName, string spriteName)
|
||||
{
|
||||
Transform targetSlot = FindSlot(characterName);
|
||||
if (targetSlot == null) return;
|
||||
|
||||
// [변경] 계층 구조 반영
|
||||
Image charImage = targetSlot.GetChild(0).GetChild(0).GetComponent<Image>();
|
||||
Sprite newSprite = Resources.Load<Sprite>("Images/Characters/" + spriteName);
|
||||
|
||||
if (newSprite != null)
|
||||
{
|
||||
// [수정] 기존 이미지를 복제하여 오버레이 생성
|
||||
// Instantiate는 원본의 위치, 회전, 크기(Scale)를 그대로 복사하므로
|
||||
// 별도로 위치나 스케일을 0/1로 초기화하면 안 됨 (좌우 반전된 캐릭터 등이 원상복구 되어버릴 수 있음)
|
||||
// 1. 마스크 컨테이너 생성 (Softness 효과를 위해)
|
||||
GameObject maskObj = new("MaskContainer");
|
||||
maskObj.transform.SetParent(charImage.transform, false); // [변경] 부모를 이미지로 설정하여 액션(Scale/Move) 동기화
|
||||
|
||||
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);
|
||||
|
||||
// 마스크 영역을 위로 올려서 상단 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. 오버레이 이미지 생성 및 설정
|
||||
// [변경] Instantiate 대신 직접 생성 (이미지에 자식이 있을 경우 복제 방지)
|
||||
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); // 원위치 유지
|
||||
|
||||
overlayImage.SetNativeSize();
|
||||
|
||||
// 렌더링 순서 보장 (마스크 컨테이너를 가장 앞으로)
|
||||
maskObj.transform.SetAsLastSibling();
|
||||
|
||||
// 3. 애니메이션 실행 (마스크 높이를 키워서 이미지를 드러냄)
|
||||
// 목표 높이: 캐릭터 이미지 높이 + 오프셋
|
||||
float targetHeight = overlayRect.sizeDelta.y + softnessOffset;
|
||||
|
||||
Tween.UISizeDelta(maskRect, new Vector2(currentWidth, targetHeight), 0.5f, Ease.OutQuart)
|
||||
.OnComplete(() =>
|
||||
{
|
||||
// 원본 교체 및 정리
|
||||
charImage.sprite = newSprite;
|
||||
charImage.SetNativeSize();
|
||||
|
||||
Destroy(maskObj); // 마스크 컨테이너 삭제 (자식인 오버레이도 같이 삭제됨)
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"표정 스프라이트를 찾을 수 없습니다: {spriteName}");
|
||||
}
|
||||
}
|
||||
|
||||
// [Helper] 이름으로 슬롯 찾기
|
||||
private Transform FindSlot(string name)
|
||||
{
|
||||
@@ -171,20 +288,13 @@ public class VisualNovelLayoutDirector : MonoBehaviour
|
||||
|
||||
private Vector2 GetDirectionVector(EntranceType type)
|
||||
{
|
||||
switch (type)
|
||||
return type switch
|
||||
{
|
||||
case EntranceType.Left:
|
||||
return new Vector2(-moveDistance, 0);
|
||||
case EntranceType.Right:
|
||||
return new Vector2(moveDistance, 0);
|
||||
case EntranceType.Center:
|
||||
case EntranceType.BottomLeft:
|
||||
case EntranceType.BottomRight:
|
||||
return new Vector2(0, -moveDistance);
|
||||
case EntranceType.Top:
|
||||
return new Vector2(0, moveDistance);
|
||||
default:
|
||||
return Vector2.zero;
|
||||
}
|
||||
EntranceType.Left => new Vector2(-moveDistance, 0),
|
||||
EntranceType.Right => new Vector2(moveDistance, 0),
|
||||
EntranceType.Center or EntranceType.BottomLeft or EntranceType.BottomRight => new Vector2(0, -moveDistance),
|
||||
EntranceType.Top => new Vector2(0, moveDistance),
|
||||
_ => Vector2.zero,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user