diff --git a/Assets/_MAIN/Scripts/Core/Script.cs b/Assets/_MAIN/Scripts/Core/Script.cs index 2cc6e6b..0f851aa 100644 --- a/Assets/_MAIN/Scripts/Core/Script.cs +++ b/Assets/_MAIN/Scripts/Core/Script.cs @@ -42,4 +42,10 @@ public class Script _currentIndex = _sceneMap[sceneName] - 1; // Continue() 호출 시 해당 인덱스가 되도록 -1 Debug.Log($"Script :: Jump to scene: {sceneName} (Index: {_currentIndex + 1})"); } + + public void Save() + { + // TODO: _currentIndex 값을 받아와서 파일에 기록 or DB에 기록 + // 20251126_191933_SAVE -> { _currentIndex, expData, ... } + } } diff --git a/Assets/_MAIN/Scripts/Core/ScriptManager.cs b/Assets/_MAIN/Scripts/Core/ScriptManager.cs index 4bb7a8a..09d720c 100644 --- a/Assets/_MAIN/Scripts/Core/ScriptManager.cs +++ b/Assets/_MAIN/Scripts/Core/ScriptManager.cs @@ -9,22 +9,22 @@ public class ScriptManager : MonoBehaviour { [SerializeField] TextAsset scriptFile; - [SerializeField] TextMeshProUGUI speakerText; - + [SerializeField] + GameObject speakerSprite; [SerializeField] TextMeshProUGUI dialogueText; - [SerializeField] private GameObject choiceButtonPrefab; - [SerializeField] private Transform choiceButtonContainer; - + [SerializeField] + private Image choiceBackground; [SerializeField] float charsPerSecond = 45f; + public VisualNovelLayoutDirector director; private readonly float shakeAmount = 1.1f; private bool isChoiceAvailable = false; private Tween dialogueTween; @@ -37,7 +37,23 @@ public class ScriptManager : MonoBehaviour dialogueText.SetText(" "); dialogueText.ForceMeshUpdate(true); + director.AddCharacter("chino01"); + Invoke("test1", 2f); + Invoke("test2", 4f); + + _currentScript = ScriptParser.Parse(scriptFile.text); + NextStep(); + } + + void test1() + { + director.AddCharacter("chino01"); + } + + void test2() + { + director.AddCharacter("chino01"); } 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() { if (_currentScript.HasNextAction()) @@ -94,6 +100,9 @@ public class ScriptManager : MonoBehaviour if (action.Type == "spk") { string speaker = action.GetParam("name"); + if (speaker == "") + speakerSprite.SetActive(false); + speakerText.SetText(speaker); speakerText.ForceMeshUpdate(true); NextStep(); @@ -117,6 +126,11 @@ public class ScriptManager : MonoBehaviour Debug.Log("ScriptManager :: Show Choices"); isChoiceAvailable = true; + // WTF.. is this shit + Color tempColor = choiceBackground.color; + tempColor.a = 0.8f; + choiceBackground.color = tempColor; + foreach (var choice in action.Choices) { string text = choice["content"]; @@ -129,8 +143,12 @@ public class ScriptManager : MonoBehaviour { foreach (Transform child in choiceButtonContainer) Destroy(child.gameObject); - isChoiceAvailable = false; + + // shitty code + tempColor.a = 0f; + choiceBackground.color = tempColor; + _currentScript.JumpTo(target); 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() { PointerEventData eventData = new PointerEventData(EventSystem.current); diff --git a/Assets/_MAIN/Scripts/Core/VisualNovelLayoutDirector.cs b/Assets/_MAIN/Scripts/Core/VisualNovelLayoutDirector.cs new file mode 100644 index 0000000..8a59af1 --- /dev/null +++ b/Assets/_MAIN/Scripts/Core/VisualNovelLayoutDirector.cs @@ -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("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(); + Image charImage = newSlot.transform.GetChild(0).GetComponent(); // 자식에 있는 이미지 + + // 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(); + Image img = targetSlot.GetChild(0).GetComponent(); + + // 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)); + } + } +} \ No newline at end of file