mirror of
https://github.com/HoonTB/Project-AS.git
synced 2025-12-26 11:51:21 +09:00
feat: Implement additional animations
- Also performed minor structural refactoring for better readability.
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections;
|
||||||
using PrimeTween;
|
using PrimeTween;
|
||||||
using TMPro;
|
using TMPro;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@@ -37,23 +38,35 @@ public class ScriptManager : MonoBehaviour
|
|||||||
dialogueText.SetText(" ");
|
dialogueText.SetText(" ");
|
||||||
dialogueText.ForceMeshUpdate(true);
|
dialogueText.ForceMeshUpdate(true);
|
||||||
|
|
||||||
director.AddCharacter("chino01");
|
StartCoroutine(TestAnim());
|
||||||
Invoke("test1", 2f);
|
|
||||||
Invoke("test2", 4f);
|
|
||||||
|
|
||||||
|
|
||||||
_currentScript = ScriptParser.Parse(scriptFile.text);
|
_currentScript = ScriptParser.Parse(scriptFile.text);
|
||||||
NextStep();
|
NextStep();
|
||||||
}
|
}
|
||||||
|
|
||||||
void test1()
|
IEnumerator TestAnim()
|
||||||
{
|
{
|
||||||
director.AddCharacter("chino01");
|
director.AddCharacter("chino01", VisualNovelLayoutDirector.EntranceType.Center);
|
||||||
}
|
yield return new WaitForSeconds(1f);
|
||||||
|
director.AddCharacter("chino02", VisualNovelLayoutDirector.EntranceType.Left);
|
||||||
void test2()
|
yield return new WaitForSeconds(1f);
|
||||||
{
|
director.AddCharacter("chino03", VisualNovelLayoutDirector.EntranceType.Right);
|
||||||
director.AddCharacter("chino01");
|
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()
|
void Update()
|
||||||
|
|||||||
@@ -5,86 +5,186 @@ using System.Collections;
|
|||||||
|
|
||||||
public class VisualNovelLayoutDirector : MonoBehaviour
|
public class VisualNovelLayoutDirector : MonoBehaviour
|
||||||
{
|
{
|
||||||
|
// ========================= [Enums] =========================
|
||||||
|
public enum EntranceType { Left, Right, BottomLeft, BottomRight, Center, Top }
|
||||||
|
public enum ActionType { Jump, Shake, Nod, Punch }
|
||||||
|
|
||||||
[Header("UI 연결")]
|
[Header("UI 연결")]
|
||||||
public Transform characterPanel; // Horizontal Layout Group이 달린 부모
|
public Transform characterPanel;
|
||||||
public GameObject slotPrefab; // 슬롯 프리팹 (빈 부모 + 이미지 자식)
|
public GameObject slotPrefab;
|
||||||
|
|
||||||
[Header("연출 설정")]
|
[Header("설정")]
|
||||||
public float charWidth = 350f; // 캐릭터 하나가 차지할 최종 너비
|
public float charWidth = 350f;
|
||||||
public float enterDuration = 0.6f; // 등장(위로 올라옴 + 투명도) 시간
|
public float defaultDuration = 0.5f;
|
||||||
public float slideDuration = 0.5f; // 옆으로 밀려나는 시간
|
public float moveDistance = 800f;
|
||||||
public float startOffsetY = -100f; // 시작 Y 위치 (화면 아래쪽 오프셋)
|
|
||||||
|
|
||||||
// 파일명으로 캐릭터 등장시키기
|
// ========================= [1. 등장 (Entry)] =========================
|
||||||
public void AddCharacter(string fileName)
|
public void AddCharacter(string fileName, EntranceType type)
|
||||||
{
|
{
|
||||||
// 1. 리소스 로드
|
// 중복 방지
|
||||||
Sprite loadedSprite = Resources.Load<Sprite>("Images/Characters/" + fileName);
|
if (FindSlot(fileName) != null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"이미 존재하는 캐릭터입니다: {fileName}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string path = "Images/Characters/" + fileName;
|
||||||
|
Sprite loadedSprite = Resources.Load<Sprite>(path);
|
||||||
|
|
||||||
if (loadedSprite != null)
|
if (loadedSprite != null)
|
||||||
{
|
{
|
||||||
StartCoroutine(SpawnRoutine(loadedSprite));
|
StartCoroutine(SpawnRoutine(fileName, loadedSprite, type));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.LogError($"이미지 로드 실패: Resources/Images/Characters/{fileName} 파일을 확인하세요.");
|
Debug.LogError($"이미지 로드 실패: {path}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerator SpawnRoutine(Sprite sprite)
|
private IEnumerator SpawnRoutine(string name, Sprite sprite, EntranceType type)
|
||||||
{
|
{
|
||||||
// 2. 슬롯 생성 (Panel의 자식으로)
|
// 1. 슬롯 생성
|
||||||
GameObject newSlot = Instantiate(slotPrefab, characterPanel);
|
GameObject newSlot = Instantiate(slotPrefab, characterPanel);
|
||||||
|
|
||||||
// 컴포넌트 찾아오기
|
// 오브젝트 이름을 파일명(ID)으로 설정
|
||||||
|
newSlot.name = name;
|
||||||
|
|
||||||
LayoutElement layoutElement = newSlot.GetComponent<LayoutElement>();
|
LayoutElement layoutElement = newSlot.GetComponent<LayoutElement>();
|
||||||
Image charImage = newSlot.transform.GetChild(0).GetComponent<Image>(); // 자식에 있는 이미지
|
Image charImage = newSlot.transform.GetChild(0).GetComponent<Image>();
|
||||||
|
|
||||||
// 3. 초기 세팅
|
// 2. 초기화
|
||||||
charImage.sprite = sprite;
|
charImage.sprite = sprite;
|
||||||
charImage.SetNativeSize(); // 이미지 원본 비율 맞춤
|
charImage.SetNativeSize();
|
||||||
|
layoutElement.preferredWidth = 0; layoutElement.minWidth = 0;
|
||||||
|
|
||||||
// [중요] 공간을 0으로 만들어둠 -> 기존 캐릭터들이 아직 움직이지 않음
|
// 3. 순서 재배치 (기존 로직 유지)
|
||||||
layoutElement.preferredWidth = 0;
|
int totalCount = characterPanel.childCount;
|
||||||
layoutElement.minWidth = 0;
|
switch (type)
|
||||||
|
{
|
||||||
// [중요] 이미지는 화면 아래(startOffsetY)에 배치하고 투명하게 설정
|
case EntranceType.Left:
|
||||||
// 슬롯(부모)은 Layout에 묶여도, 이미지(자식)는 자유롭게 움직일 수 있음
|
case EntranceType.BottomLeft:
|
||||||
charImage.rectTransform.anchoredPosition = new Vector2(0, startOffsetY);
|
newSlot.transform.SetSiblingIndex(0); break;
|
||||||
charImage.color = new Color(1, 1, 1, 0); // Alpha 0 (투명)
|
case EntranceType.Right:
|
||||||
|
case EntranceType.BottomRight:
|
||||||
// UI 갱신 대기 (필수)
|
newSlot.transform.SetSiblingIndex(totalCount - 1); break;
|
||||||
yield return new WaitForEndOfFrame();
|
case EntranceType.Center:
|
||||||
|
case EntranceType.Top:
|
||||||
// 4. 애니메이션 실행
|
newSlot.transform.SetSiblingIndex((totalCount - 1) / 2); break;
|
||||||
|
|
||||||
// A. 공간 벌리기 (기존 캐릭터들이 스르륵 밀려남)
|
|
||||||
Tween.Custom(layoutElement, layoutElement.preferredWidth, charWidth, slideDuration,
|
|
||||||
(target, x) => target.preferredWidth = x,
|
|
||||||
ease: Ease.OutQuart);
|
|
||||||
|
|
||||||
// B. 이미지 등장 (아래에서 위로 올라오며 선명해짐)
|
|
||||||
Tween.UIAnchoredPositionY(charImage.rectTransform, 0, enterDuration, ease: Ease.OutBack);
|
|
||||||
Tween.Alpha(charImage, 1, enterDuration);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// (참고) 캐릭터 퇴장 기능
|
// 4. 위치 잡기 및 애니메이션
|
||||||
public void RemoveCharacter(int index)
|
Vector2 startPos = GetDirectionVector(type);
|
||||||
{
|
charImage.rectTransform.anchoredPosition = startPos;
|
||||||
if (index < characterPanel.childCount)
|
charImage.color = new Color(1, 1, 1, 0);
|
||||||
{
|
|
||||||
Transform targetSlot = characterPanel.GetChild(index);
|
|
||||||
LayoutElement le = targetSlot.GetComponent<LayoutElement>();
|
|
||||||
Image img = targetSlot.GetChild(0).GetComponent<Image>();
|
|
||||||
|
|
||||||
// 1. 이미지 사라지기 (Fade Out)
|
yield return new WaitForEndOfFrame();
|
||||||
Tween.Alpha(img, 0, slideDuration);
|
|
||||||
|
|
||||||
// 2. 공간 닫기 & 종료 후 삭제
|
Tween.Custom(layoutElement, 0f, charWidth, defaultDuration, (t, x) => t.preferredWidth = x, Ease.OutQuart);
|
||||||
Tween.Custom(le, le.preferredWidth, 0, slideDuration,
|
Tween.UIAnchoredPosition(charImage.rectTransform, Vector2.zero, defaultDuration, Ease.OutQuart);
|
||||||
(target, x) => target.preferredWidth = x,
|
Tween.Alpha(charImage, 1f, defaultDuration);
|
||||||
ease: Ease.OutQuart)
|
}
|
||||||
.OnComplete(targetSlot.gameObject, go => Object.Destroy(go));
|
|
||||||
|
// ========================= [2. 퇴장 (Exit)] =========================
|
||||||
|
public void RemoveCharacter(string characterName, EntranceType exitTo)
|
||||||
|
{
|
||||||
|
Transform targetSlot = FindSlot(characterName);
|
||||||
|
|
||||||
|
if (targetSlot != null)
|
||||||
|
{
|
||||||
|
StartCoroutine(ExitRoutine(targetSlot, exitTo));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"삭제 실패: '{characterName}' 캐릭터를 찾을 수 없습니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator ExitRoutine(Transform slotTransform, EntranceType exitTo)
|
||||||
|
{
|
||||||
|
// 중복 호출 방지를 위해 이름을 바꿔둠 (빠르게 연타했을 때 에러 방지)
|
||||||
|
slotTransform.name = slotTransform.name + "_Removing";
|
||||||
|
|
||||||
|
LayoutElement layoutElement = slotTransform.GetComponent<LayoutElement>();
|
||||||
|
Image charImage = slotTransform.GetChild(0).GetComponent<Image>();
|
||||||
|
Vector2 targetPos = GetDirectionVector(exitTo);
|
||||||
|
|
||||||
|
// 이미지 날리기 & 투명화
|
||||||
|
Tween.UIAnchoredPosition(charImage.rectTransform, targetPos, defaultDuration, Ease.OutQuart);
|
||||||
|
Tween.Alpha(charImage, 0f, defaultDuration * 0.8f);
|
||||||
|
|
||||||
|
// 공간 닫기
|
||||||
|
yield return Tween.Custom(layoutElement, layoutElement.preferredWidth, 0f, defaultDuration,
|
||||||
|
(t, x) => t.preferredWidth = x, Ease.OutQuart).ToYieldInstruction();
|
||||||
|
|
||||||
|
Destroy(slotTransform.gameObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================= [3. 액션 (Action)] =========================
|
||||||
|
public void PlayAction(string characterName, ActionType action)
|
||||||
|
{
|
||||||
|
Transform targetSlot = FindSlot(characterName);
|
||||||
|
|
||||||
|
if (targetSlot == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"액션 실패: '{characterName}' 캐릭터를 찾을 수 없습니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RectTransform targetImageRect = targetSlot.GetChild(0).GetComponent<RectTransform>();
|
||||||
|
|
||||||
|
// 기존 애니메이션 정지 및 초기화
|
||||||
|
Tween.StopAll(targetImageRect);
|
||||||
|
targetImageRect.anchoredPosition = Vector2.zero;
|
||||||
|
|
||||||
|
switch (action)
|
||||||
|
{
|
||||||
|
case ActionType.Jump:
|
||||||
|
// frequency: 2 (위로 갔다가 한두 번 띠용~ 하고 멈춤)
|
||||||
|
Tween.PunchLocalPosition(targetImageRect, new Vector3(0, 100f, 0), 0.5f, frequency: 2);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ActionType.Shake:
|
||||||
|
// 좌우 흔들기 (진동 횟수 10번)
|
||||||
|
Tween.ShakeLocalPosition(targetImageRect, new Vector3(50f, 0, 0), 0.5f, frequency: 10);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ActionType.Nod:
|
||||||
|
// (Sequence는 변경 없음)
|
||||||
|
Sequence.Create()
|
||||||
|
.Chain(Tween.UIAnchoredPositionY(targetImageRect, -30f, 0.15f, Ease.OutQuad))
|
||||||
|
.Chain(Tween.UIAnchoredPositionY(targetImageRect, 0f, 0.15f, Ease.InQuad));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ActionType.Punch:
|
||||||
|
// frequency: 1 (커졌다가 딱 한 번 출렁이고 복구됨)
|
||||||
|
Tween.PunchScale(targetImageRect, new Vector3(0.2f, 0.2f, 0), 0.4f, frequency: 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// [Helper] 이름으로 슬롯 찾기
|
||||||
|
private Transform FindSlot(string name)
|
||||||
|
{
|
||||||
|
// CharacterPanel 바로 아래 자식들 중에서 이름을 검색
|
||||||
|
return characterPanel.Find(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector2 GetDirectionVector(EntranceType type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user