ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • (32) 연출 시스템 구축 & 챕터1 맵 최종완성
    Galaxy Ball/2. 싱글플레이 - 스토리모드 2024. 6. 24. 16:05

    사실 지난 5일동안 많은것을 개발했었지만 도저히 글로 적을 시간까지 나질 않아 건너뛰어 왔었다

    물론 기록을 남기는것도 중요하지만 이제 남은 시간이 한달밖에 없다보니 여기에 글을 적을 시간조차 아까워지는

    순간이 와서 소홀했었던것 같다

     

    그러니 최대한 간결하게 어떤것들을 구현했는지 핵심적인것들만 짚고 넘어가겠다

     

    우선 가장 큰 코어는 연출 시스템을 구축했다는 것이다

    예를 들어 1번 대사가 끝나면 1번 연출을 보여주고, 특정 조건을 만족시켰을시 2번 연출을 재생하는...

    이 모든것들을 손쉽게 다룰수 있는 코드를 작성해보았다

     

         if (audioSource != null)
         {
             TextManager textManager = FindObjectOfType<TextManager>();
             audioSource.Stop();
    
             ShowText showTextScript = textManager.Linebox.GetComponent<ShowText>();
             if (showTextScript != null)
             {
                 showTextScript.OnChatComplete.AddListener(OnChatComplete);
                 currentChatId = 1; // 현재 채팅 ID 저장
                 textManager.GiveMeTextId(currentChatId);
             }
             else
             {
                 Debug.LogError("ShowText script not found on Linebox.");
             }
         }
     }

    순서대로 설명하자면 우선 게임매니저를 만든다. 이 스크립트의 이름은 언제든지 바뀔수 있다

    프롤로그에 사용될 연출이라면 프롤로그 매니저, 챕터1,2,3,4 매니저 등등...

     

    그리고 이 스크립트에서 TextManager와 ShowText를 불러온뒤 textManager에 텍스트 id를 넣어준다

     

    using UnityEngine;
    
    public class TextManager : MonoBehaviour
    {
        public GameObject Linebox;
    
        public void GiveMeTextId(int chatId)
        {
            if (Linebox != null)
            {
                Linebox.SetActive(true); 
    
                ShowText showTextScript = Linebox.GetComponent<ShowText>();
                if (showTextScript != null)
                {
                    showTextScript.DisplayChatById(chatId);
                }
                else
                {
                    Debug.LogError("ShowText script not found on Linebox.");
                }
            }
            else
            {
                Debug.LogError("Linebox is not assigned.");
            }
        }
    }

    그럼 TextManager에서는 텍스트 박스를 활성화 시켜준뒤 받은 id값을 showtext에 넘겨준다

     

    using Newtonsoft.Json;
    using System.Collections;
    using System.Collections.Generic;
    using TMPro;
    using UnityEngine;
    using UnityEngine.Events;
    
    class TextWithDelay
    {
        public string text;
        public float delay;
    }
    
    class Chat
    {
        public int id;
        public List<TextWithDelay> textWithDelay;
    }
    
    public class ShowText : MonoBehaviour
    {
        public TMP_Text chatting;
        public GameObject ChatBox;
        public TextAsset JsonFile;
        private List<Chat> chats;
        private int currentChatIndex = 0;
        private int currentTextIndex = 0;
        private bool isTyping = false;
        private Coroutine typingCoroutine;
        private int chatIdToDisplay = -1;
    
        public UnityEvent<int> OnChatComplete; // 이벤트 선언
    
        void Awake()
        {
            ChatBox.gameObject.SetActive(true);
            if (JsonFile != null)
            {
                try
                {
                    var json = JsonFile.text;
                    chats = JsonConvert.DeserializeObject<List<Chat>>(json);
                }
                catch (JsonReaderException e)
                {
                    Debug.LogError("Failed to parse JSON: " + e.Message);
                }
            }
        }
    
        void OnEnable()
        {
            if (chatIdToDisplay != -1)
            {
                DisplayChatById(chatIdToDisplay);
            }
        }
    
        void Update()
        {
            if (Input.GetMouseButtonDown(0))
            {
                if (isTyping)
                {
                    StopCoroutine(typingCoroutine);
                    chatting.text = chats[currentChatIndex].textWithDelay[currentTextIndex].text;
                    isTyping = false;
                }
                else
                {
                    currentTextIndex++;
                    if (currentTextIndex >= chats[currentChatIndex].textWithDelay.Count)
                    {
                        currentTextIndex = 0;
                        currentChatIndex++;
                    }
                    DisplayCurrentChat();
                }
            }
        }
    
        private void DisplayCurrentChat()
        {
            if (currentChatIndex < chats.Count)
            {
                if (currentTextIndex < chats[currentChatIndex].textWithDelay.Count)
                {
                    var textWithDelay = chats[currentChatIndex].textWithDelay[currentTextIndex];
                    typingCoroutine = StartCoroutine(Typing(textWithDelay.text, textWithDelay.delay));
                }
                else
                {
                    ChatBox.gameObject.SetActive(false);
                }
            }
            else
            {
                ChatBox.gameObject.SetActive(false);
            }
    
            if (currentChatIndex >= chats.Count || currentTextIndex >= chats[currentChatIndex].textWithDelay.Count)
            {
                OnChatComplete.Invoke(chats[currentChatIndex - 1].id); // 모든 문장 출력 완료 시 이벤트 호출
            }
        }
    
        IEnumerator Typing(string talk, float delaytime)
        {
            isTyping = true;
            chatting.text = string.Empty;
    
            for (int i = 0; i < talk.Length; i++)
            {
                chatting.text += talk[i];
                yield return new WaitForSeconds(delaytime);
            }
    
            isTyping = false;
        }
    
        public void DisplayChatById(int chatId)
        {
            chatIdToDisplay = chatId;
    
            var chat = chats.Find(c => c.id == chatId);
            if (chat != null)
            {
                currentChatIndex = chats.IndexOf(chat);
                currentTextIndex = 0;
                DisplayCurrentChat();
            }
            else
            {
                Debug.LogError("Chat with ID " + chatId + " not found.");
            }
        }
    }

     

    그럼 showtext는 TextManager에게 받은 텍스트id값에 해당하는 텍스트를 출력해주면 되는것이다

     

    왜 굳이 이렇게 나누는건가 할수 있지만 이런 방식을 거치게 되면 결과적으로 봤을때

      void OnChatComplete(int chatId)
      {
          Debug.Log($"아이디 {chatId}에 해당하는 문장을 출력완료했습니다.");
          if (chatId == 1)
          {
              StartCoroutine(FadeInImage(FadeIn));
          }
      }

     

    다시 맨처음 스크립트로 돌아가 조건에 따라 id의 값을 손쉽게 조절하며 textManager.Givemetextid에 값을

    넣어주기만 하면 된다

     

    이러면 그토록 내가 어려워하던 스토리,연출에 대한 부담이 조금은 내려가게 된다

     

    ------------------------------------------------

     

    이제 정말 마지막으로 챕터1에 사용될 맵을 최종완성해보자

     

    가장 먼저 첫번째 데드존을 없앴다. 그 이유는

     

     

    프롤로그 영상에 보면 이런 연출이 나오면서 개발자의 피조물들은 지금도 생성중이라는 대사가 나오기도 하고

    생각해보니 저 넓은 공간에 플레이어 하나 덩그러니 냅두는것도 심심하고,

    무엇보다 앞으로 나올 스토리에 무조건 없어선 안될 존재기에 추가해야했다

     

    근데 첫번째 데드존이 존재해버리면 모든게 여러모로 부자연스러워지기 때문

     

     

    그래서 추가했다. 스토리상에서는 195개의 구체들이지만 굳이 이런것까지 맞춰줄 필욘 없으니

    거의 100개에 가까운 구체들을 추가했다

     

     

    using UnityEngine;
    
    public class ContinuousRandomMovement : MonoBehaviour
    {
    
        private Vector2 moveDirection; // 이동 방향
    
        void Start()
        {
            float randomAngle = Random.Range(0f, 360f);
            moveDirection = new Vector2(Mathf.Cos(randomAngle * Mathf.Deg2Rad), Mathf.Sin(randomAngle * Mathf.Deg2Rad));
    
            moveDirection.Normalize();
        }
    
        void Update()
        {
            transform.Translate(moveDirection * Random.Range(2, 17) * Time.deltaTime);
        }
    
        private void OnTriggerStay2D(Collider2D collision)
        {
            StageGameManager gameManager = FindObjectOfType<StageGameManager>();
    
            switch (collision.gameObject.name)
            {
                case "Bottom":
                    if (gameManager.StageClearID < 6)
                    {
                        transform.Translate(0, 470, 0);
                    }
                    transform.Translate(0, 170, 0);
                    break;
                case "Top":
                    if (gameManager.StageClearID < 6)
                    {
                        transform.Translate(0, -470, 0);
                    }
                    transform.Translate(0, -170, 0);
                    break;
                case "Left":
                    if (gameManager.StageClearID < 6)
                    {
                        transform.Translate(460, 0, 0);
                    }
                    transform.Translate(165, 0, 0);
                    break;
                case "Right":
                    if (gameManager.StageClearID < 6)
                    {
                        transform.Translate(-460, 0, 0);
                    }
                    transform.Translate(-165, 0, 0);
                    break;
            }
        }
    }

     

    코드도 수정해주었다. 원래는 일정한 속도로 날아가게만 하였으나

    이제 데드존에 닿으면 플레이어와 똑같이 위치조절을 받게 된다

     

     

     

    이제 게임을 실행하면 이렇게 자연스럽게 다양한 색상과 크기의 구체들이 돌아다니는것을 볼 수 있다

     

     

    근데 이렇게하니 초반에만 가운데서 와글거리고 조금만 시간이 지나면 전부 흩어지는것을 확인할 수 있다

     

    그래서 구체들을 컨트롤하는 스크립트 Start 메서드에 코드 한줄을 추가해주었다

    씬이 시작될때 데드라인 안에 있는 공간이라면 어디든 상관없이 랜덤한 위치에서 시작하는것이다

     

     

     

    오...확실히 더 보기좋게 골고루 퍼져 움직이는것 같다

     

     

    그리고 캡쳐본에는 잘 보이지 않지만 스테이지들도 각각 조금씩 떼어주었다. 

    플레이어가 직접 돌아다니며 찾게 할거다

Designed by Tistory.