mirror of
https://github.com/HoonTB/Project-AS.git
synced 2025-12-26 11:51:21 +09:00
feat(VN): Implement basic character appearance feature
This commit is contained in:
@@ -42,4 +42,10 @@ public class Script
|
|||||||
_currentIndex = _sceneMap[sceneName] - 1; // Continue() 호출 시 해당 인덱스가 되도록 -1
|
_currentIndex = _sceneMap[sceneName] - 1; // Continue() 호출 시 해당 인덱스가 되도록 -1
|
||||||
Debug.Log($"Script :: Jump to scene: {sceneName} (Index: {_currentIndex + 1})");
|
Debug.Log($"Script :: Jump to scene: {sceneName} (Index: {_currentIndex + 1})");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
// TODO: _currentIndex 값을 받아와서 파일에 기록 or DB에 기록
|
||||||
|
// 20251126_191933_SAVE -> { _currentIndex, expData, ... }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,22 +9,22 @@ public class ScriptManager : MonoBehaviour
|
|||||||
{
|
{
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
TextAsset scriptFile;
|
TextAsset scriptFile;
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
TextMeshProUGUI speakerText;
|
TextMeshProUGUI speakerText;
|
||||||
|
[SerializeField]
|
||||||
|
GameObject speakerSprite;
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
TextMeshProUGUI dialogueText;
|
TextMeshProUGUI dialogueText;
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
private GameObject choiceButtonPrefab;
|
private GameObject choiceButtonPrefab;
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
private Transform choiceButtonContainer;
|
private Transform choiceButtonContainer;
|
||||||
|
[SerializeField]
|
||||||
|
private Image choiceBackground;
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
float charsPerSecond = 45f;
|
float charsPerSecond = 45f;
|
||||||
|
|
||||||
|
public VisualNovelLayoutDirector director;
|
||||||
private readonly float shakeAmount = 1.1f;
|
private readonly float shakeAmount = 1.1f;
|
||||||
private bool isChoiceAvailable = false;
|
private bool isChoiceAvailable = false;
|
||||||
private Tween dialogueTween;
|
private Tween dialogueTween;
|
||||||
@@ -37,7 +37,23 @@ public class ScriptManager : MonoBehaviour
|
|||||||
dialogueText.SetText(" ");
|
dialogueText.SetText(" ");
|
||||||
dialogueText.ForceMeshUpdate(true);
|
dialogueText.ForceMeshUpdate(true);
|
||||||
|
|
||||||
|
director.AddCharacter("chino01");
|
||||||
|
Invoke("test1", 2f);
|
||||||
|
Invoke("test2", 4f);
|
||||||
|
|
||||||
|
|
||||||
_currentScript = ScriptParser.Parse(scriptFile.text);
|
_currentScript = ScriptParser.Parse(scriptFile.text);
|
||||||
|
NextStep();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test1()
|
||||||
|
{
|
||||||
|
director.AddCharacter("chino01");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test2()
|
||||||
|
{
|
||||||
|
director.AddCharacter("chino01");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Update()
|
void Update()
|
||||||
@@ -53,16 +69,6 @@ public class ScriptManager : MonoBehaviour
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DebugReload()
|
|
||||||
{
|
|
||||||
speakerText.SetText(" ");
|
|
||||||
speakerText.ForceMeshUpdate(true);
|
|
||||||
dialogueText.SetText(" ");
|
|
||||||
dialogueText.ForceMeshUpdate(true);
|
|
||||||
|
|
||||||
_currentScript = ScriptParser.Parse(scriptFile.text);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void NextStep()
|
private void NextStep()
|
||||||
{
|
{
|
||||||
if (_currentScript.HasNextAction())
|
if (_currentScript.HasNextAction())
|
||||||
@@ -94,6 +100,9 @@ public class ScriptManager : MonoBehaviour
|
|||||||
if (action.Type == "spk")
|
if (action.Type == "spk")
|
||||||
{
|
{
|
||||||
string speaker = action.GetParam("name");
|
string speaker = action.GetParam("name");
|
||||||
|
if (speaker == "")
|
||||||
|
speakerSprite.SetActive(false);
|
||||||
|
|
||||||
speakerText.SetText(speaker);
|
speakerText.SetText(speaker);
|
||||||
speakerText.ForceMeshUpdate(true);
|
speakerText.ForceMeshUpdate(true);
|
||||||
NextStep();
|
NextStep();
|
||||||
@@ -117,6 +126,11 @@ public class ScriptManager : MonoBehaviour
|
|||||||
Debug.Log("ScriptManager :: Show Choices");
|
Debug.Log("ScriptManager :: Show Choices");
|
||||||
isChoiceAvailable = true;
|
isChoiceAvailable = true;
|
||||||
|
|
||||||
|
// WTF.. is this shit
|
||||||
|
Color tempColor = choiceBackground.color;
|
||||||
|
tempColor.a = 0.8f;
|
||||||
|
choiceBackground.color = tempColor;
|
||||||
|
|
||||||
foreach (var choice in action.Choices)
|
foreach (var choice in action.Choices)
|
||||||
{
|
{
|
||||||
string text = choice["content"];
|
string text = choice["content"];
|
||||||
@@ -129,8 +143,12 @@ public class ScriptManager : MonoBehaviour
|
|||||||
{
|
{
|
||||||
foreach (Transform child in choiceButtonContainer)
|
foreach (Transform child in choiceButtonContainer)
|
||||||
Destroy(child.gameObject);
|
Destroy(child.gameObject);
|
||||||
|
|
||||||
isChoiceAvailable = false;
|
isChoiceAvailable = false;
|
||||||
|
|
||||||
|
// shitty code
|
||||||
|
tempColor.a = 0f;
|
||||||
|
choiceBackground.color = tempColor;
|
||||||
|
|
||||||
_currentScript.JumpTo(target);
|
_currentScript.JumpTo(target);
|
||||||
NextStep();
|
NextStep();
|
||||||
});
|
});
|
||||||
@@ -139,6 +157,16 @@ public class ScriptManager : MonoBehaviour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void DebugReload()
|
||||||
|
{
|
||||||
|
speakerText.SetText(" ");
|
||||||
|
speakerText.ForceMeshUpdate(true);
|
||||||
|
dialogueText.SetText(" ");
|
||||||
|
dialogueText.ForceMeshUpdate(true);
|
||||||
|
|
||||||
|
_currentScript = ScriptParser.Parse(scriptFile.text);
|
||||||
|
}
|
||||||
|
|
||||||
private bool IsPointerOverInteractiveUI()
|
private bool IsPointerOverInteractiveUI()
|
||||||
{
|
{
|
||||||
PointerEventData eventData = new PointerEventData(EventSystem.current);
|
PointerEventData eventData = new PointerEventData(EventSystem.current);
|
||||||
|
|||||||
90
Assets/_MAIN/Scripts/Core/VisualNovelLayoutDirector.cs
Normal file
90
Assets/_MAIN/Scripts/Core/VisualNovelLayoutDirector.cs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using PrimeTween;
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
public class VisualNovelLayoutDirector : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Header("UI 연결")]
|
||||||
|
public Transform characterPanel; // Horizontal Layout Group이 달린 부모
|
||||||
|
public GameObject slotPrefab; // 슬롯 프리팹 (빈 부모 + 이미지 자식)
|
||||||
|
|
||||||
|
[Header("연출 설정")]
|
||||||
|
public float charWidth = 350f; // 캐릭터 하나가 차지할 최종 너비
|
||||||
|
public float enterDuration = 0.6f; // 등장(위로 올라옴 + 투명도) 시간
|
||||||
|
public float slideDuration = 0.5f; // 옆으로 밀려나는 시간
|
||||||
|
public float startOffsetY = -100f; // 시작 Y 위치 (화면 아래쪽 오프셋)
|
||||||
|
|
||||||
|
// 파일명으로 캐릭터 등장시키기
|
||||||
|
public void AddCharacter(string fileName)
|
||||||
|
{
|
||||||
|
// 1. 리소스 로드
|
||||||
|
Sprite loadedSprite = Resources.Load<Sprite>("Images/Characters/" + fileName);
|
||||||
|
|
||||||
|
if (loadedSprite != null)
|
||||||
|
{
|
||||||
|
StartCoroutine(SpawnRoutine(loadedSprite));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogError($"이미지 로드 실패: Resources/Images/Characters/{fileName} 파일을 확인하세요.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator SpawnRoutine(Sprite sprite)
|
||||||
|
{
|
||||||
|
// 2. 슬롯 생성 (Panel의 자식으로)
|
||||||
|
GameObject newSlot = Instantiate(slotPrefab, characterPanel);
|
||||||
|
|
||||||
|
// 컴포넌트 찾아오기
|
||||||
|
LayoutElement layoutElement = newSlot.GetComponent<LayoutElement>();
|
||||||
|
Image charImage = newSlot.transform.GetChild(0).GetComponent<Image>(); // 자식에 있는 이미지
|
||||||
|
|
||||||
|
// 3. 초기 세팅
|
||||||
|
charImage.sprite = sprite;
|
||||||
|
charImage.SetNativeSize(); // 이미지 원본 비율 맞춤
|
||||||
|
|
||||||
|
// [중요] 공간을 0으로 만들어둠 -> 기존 캐릭터들이 아직 움직이지 않음
|
||||||
|
layoutElement.preferredWidth = 0;
|
||||||
|
layoutElement.minWidth = 0;
|
||||||
|
|
||||||
|
// [중요] 이미지는 화면 아래(startOffsetY)에 배치하고 투명하게 설정
|
||||||
|
// 슬롯(부모)은 Layout에 묶여도, 이미지(자식)는 자유롭게 움직일 수 있음
|
||||||
|
charImage.rectTransform.anchoredPosition = new Vector2(0, startOffsetY);
|
||||||
|
charImage.color = new Color(1, 1, 1, 0); // Alpha 0 (투명)
|
||||||
|
|
||||||
|
// UI 갱신 대기 (필수)
|
||||||
|
yield return new WaitForEndOfFrame();
|
||||||
|
|
||||||
|
// 4. 애니메이션 실행
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// (참고) 캐릭터 퇴장 기능
|
||||||
|
public void RemoveCharacter(int index)
|
||||||
|
{
|
||||||
|
if (index < characterPanel.childCount)
|
||||||
|
{
|
||||||
|
Transform targetSlot = characterPanel.GetChild(index);
|
||||||
|
LayoutElement le = targetSlot.GetComponent<LayoutElement>();
|
||||||
|
Image img = targetSlot.GetChild(0).GetComponent<Image>();
|
||||||
|
|
||||||
|
// 1. 이미지 사라지기 (Fade Out)
|
||||||
|
Tween.Alpha(img, 0, slideDuration);
|
||||||
|
|
||||||
|
// 2. 공간 닫기 & 종료 후 삭제
|
||||||
|
Tween.Custom(le, le.preferredWidth, 0, slideDuration,
|
||||||
|
(target, x) => target.preferredWidth = x,
|
||||||
|
ease: Ease.OutQuart)
|
||||||
|
.OnComplete(targetSlot.gameObject, go => Object.Destroy(go));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user