개발/개발일지

Tales Saga Chronicle Blast 개발일지 6 - AUTO/MENU 버튼, 애니메이션 추가 등

메피카타츠 2023. 1. 16. 01:05

오늘 목표는 일부 감정 표현을 따와 애니메이션 적용, 메뉴, 오토 기능 추가, 스토리 20번까지 진행 + 캐릭터의 일러스트를 변경하는 함수나 이동도 좀 더 편리하게 사용할 수 있는 함수 만들기였다. 메뉴 버튼의 로그와 스킵 기능은 어느정도 스토리가 진행된 상태에서 만드는 것이 좋을 것 같아 메뉴를 숨기는 버튼만 구현했고, 애니메이션을 만들고 스토리를 진행하려고 하니, 스토리 진행 다음 부분이 선택지라 내일로 미뤄두기로 했다.

 

1. AUTO 버튼 기능 추가

 

Button에서 OnClick을 받아 StoryManager의 함수를 실행시켜준다. 버튼을 누르는 효과음은 블루 아카이브의 효과음을 녹음해서 사용했다.

public void OnClickAutoBtn()
    {
        storyManager.AutoProgress();
        audioManager.PlaySFX("ButtonClick_BlueArchive");
    }

 

AutoProgress에서는 이미 오토 버튼이 눌려있으면 오토 기능을 끄고, 오토 버튼의 이미지를 바꿔주며 꺼져있으면 오토 기능을 키고 오토 버튼의 이미지를 바꿔준다.

 

자동으로 스토리가 진행되는 부분은 코루틴으로 구현했는데, 1초마다 대화 내용이 전부 출력되었는지 확인하고, 출력되었다면 유저가 읽을 시간을 주기 위해 최소 1초간 대기하기로 했다. 그런데 이때 AUTO 버튼을 눌러놓고 터치를 하면 문제가 발생할 수 있었다. 예를 들어서 4번째 스토리 재생이 완료되면 1초 뒤에 스토리를 다음으로 진행시키는데, 이때 터치를 2번 해서 1초 안에 다음 스토리 출력이 완료되면 곧바로 6번째 스토리로 넘어가는 식이다. 확인해보니 블루 아카이브에서도 동일한 이슈가 있었다. 아마 비슷한 방식으로 구현된 것 같았다. 물론 오토 버튼을 누르고 터치를 하는 경우는 거의 없을테니 딱히 큰 상관은 없는 문제였다. 아무튼, 그래서 스토리 진행이 완료된 걸 확인했을 때 현재 스토리 번호를 저장하고, 대기를 한 뒤에 스토리 번호가 달라지면 다시 처음부터 진행하게끔 설계를 했다.

// 스토리에서 AUTO 버튼을 눌렀을 때 자동으로 스토리가 진행되도록 해주는 함수
    public void AutoProgress()
    {
        if (isAutoStoryProgress)
        {
            StopCoroutine(autoStoryProgress);
            isAutoStoryProgress = false;
            story.transform.Find("Episode/UI/BtnAuto").GetComponent<Image>().sprite = buttonImage.buttonArray[0];
            return;
        }
        autoStoryProgress = StartCoroutine(CoroutineAutoProgress());
        isAutoStoryProgress = true;
        story.transform.Find("Episode/UI/BtnAuto").GetComponent<Image>().sprite = buttonImage.buttonArray[1];
    }

    // 자동으로 스토리가 진행되도록 해주는 코루틴
    public IEnumerator CoroutineAutoProgress()
    {
        while(true)
        {
            // 대화가 출력되지 있지 않을 때만 진행
            if (!isStoryProgressing)
            {
                // 대화 출력이 완료된 후 최소 1초 대기
                autoStoryNum = storyNum;
                yield return new WaitForSeconds(1);
                // 스토리 자동 진행 중 유저가 화면을 터치해서 다음 대사로 넘어가면 카운트를 다시 실행
                // 이게 없으면 유저가 2번 터치를 해서 다음 대사로 타이밍이 안좋게 넘어갈 경우, 대사가 곧바로 스킵되는 문제가 발생함
                // 확인해보니 블루 아카이브에서도 동일한 이슈가 있으나.. AUTO 버튼을 누른 채로 터치하는 유저가 많지는 않을테니...
                if (autoStoryNum == storyNum)
                {
                    StoryProgress();
                }
            }
            else
            {
                yield return new WaitForSeconds(1); // 1초마다 확인
            }
        }
    }

 

2. 각종 애니메이션 추가

겁에 질려 몸을 떠는 애니메이션과 모모이의 경우 기본 일러스트면 눈을 깜빡이는 모션이 있어 해당 애니메이션을 추가했다. 그리고 반짝이는 효과가 나오는 애니메이션도 추가했다. 이 과정에서 애니메이션 리소스를 캡쳐하다가 발견한 건데, 몇몇 애니메이션의 시작 부분에서 빠르게 Fade In, Fade Out이 된 이후에 애니메이션이 재생되는 것 같은 느낌을 받았다. 아주 주의 깊게 보지 않으면 눈치채기 어려운 장면이기는 하다. 버그인지, 혹은 기기의 문제인지 정확히는 모르겠다.

 

아, 그리고 겁에 질려 몸을 떠는 애니메이션이 x축이 변경되는 애니메이션인데 이것을 추가한 것 때문에 x축이 고정되서 캐릭터가 움직이지 않는 버그가 생겼었다. 왜 문제가 발생했는지 이유를 찾아봤는데 이유를 찾지는 못했고, 그냥 부모 객체를 만들어서 움직이는 것으로 해결했다. 이 과정에서 시간이 조금 소요되었다.

 

3. 캐릭터 일러스트를 편하게 관리하기 위한 함수 추가

 

캐릭터마다 표정이 다양한데, 이 표정들을 편하게 관리하기 위한 함수를 추가했다. 모모이의 경우 기본 일러스트면 눈을 깜빡이는 애니메이션을 키고, 다른 일러스트면 애니메이션을 끄도록 하는 기능도 추가했다. 그리고 일러스트를 편하게 넣기 위해서 Serializable과 SerializeField를 활용했다.

[Serializable]
    public class MomoiImage
    {
        public Sprite[] imageArray;
    }
    [Serializable]
    public class MidoriImage
    {
        public Sprite[] imageArray;
    }
    [Serializable]
    public class ArisImage
    {
        public Sprite[] imageArray;
    }
    [Serializable]
    public class YuzuImage
    {
        public Sprite[] imageArray;
    }
    [Serializable]
    public class ButtonImage
    {
        public Sprite[] buttonArray;
    }
    [SerializeField] MomoiImage momoiImage;
    [SerializeField] MidoriImage midoriImage;
    [SerializeField] ArisImage arisImage;
    [SerializeField] YuzuImage yuzuImage;
    [SerializeField] ButtonImage buttonImage;
// 학생들의 이미지를 변경해주는 함수
    private void ChangeCharacterImage(GameObject characterName, int num)
    {
        if(characterName == momoi)
        {
            momoi.transform.Find("CharacterImage").GetComponent<Image>().sprite = momoiImage.imageArray[num];
            // 모모이의 표정이 0번일 때는 눈을 깜빡이는 모션이 있음
            if(num == 0)
            {
                momoi.transform.Find("CharacterImage").GetComponent<Animator>().enabled = true;
            }
            else
            {
                momoi.transform.Find("CharacterImage").GetComponent<Animator>().enabled = false;
            }
        }
        else if(characterName == midori)
        {
            midori.transform.Find("CharacterImage").GetComponent<Image>().sprite = midoriImage.imageArray[num];
        }
        else if(characterName == aris)
        {
            aris.transform.Find("CharacterImage").GetComponent<Image>().sprite = arisImage.imageArray[num];
        }
        else if(characterName == yuzu)
        {
            yuzu.transform.Find("CharacterImage").GetComponent<Image>().sprite = yuzuImage.imageArray[num];
        }
    }

 

 

4. 메뉴  버튼 기능 추가

챕터를 고르는 방식으로 변경할 것이기 때문에 메뉴 버튼을 블루 아카이브와 동일하게 UI 숨기기, 대화 로그, 스킵으로 구성했다. 스킵은 한 챕터가 끝난 이후에, 로그는 대화가 일정 이상 진행된 다음에 구현하는 것이 좋을 것 같아 현재는 UI 숨기기만 구현해놓았다.

 

또, 메뉴창이 열린 상태로 화면을 터치하면 메뉴창을 숨기고, UI가 숨겨진 상태에서 화면을 터치하면 다시 UI를 나타내게끔 설정했다.

public void OnClickMenuBtn()
    {
        if(gameObject.transform.parent.Find("Menu").gameObject.activeSelf)
        {
            gameObject.transform.parent.Find("Menu").gameObject.SetActive(false); // 메뉴창이 열려있으면 메뉴창 닫기
        }
        else
        {
            gameObject.transform.parent.Find("Menu").gameObject.SetActive(true); // 메뉴창이 닫혀있으면 메뉴창 열기
        }
        audioManager.PlaySFX("ButtonClick_BlueArchive");
    }

    public void OnClickHideUI()
    {
        gameObject.transform.parent.gameObject.SetActive(false); // 열린 메뉴창 숨기기
        gameObject.transform.parent.parent.gameObject.SetActive(false); // UI 숨기기
        gameObject.transform.parent.parent.parent.Find("Dialog").gameObject.SetActive(false); // 대화창 숨기기
    }
public void OnClickEpisodeBackground()
    {
        if(gameObject.transform.parent.Find("Dialog").gameObject.activeSelf == false)
        {
            gameObject.transform.parent.Find("Dialog").gameObject.SetActive(true);
            gameObject.transform.parent.Find("UI").gameObject.SetActive(true);
        }
        else
        {
            storyManager.StoryProgress();
        }
    }

 

 

결과적으로 오늘 완성된 부분은 이렇다.

이제보니 UI를 숨기는 버튼을 누를 때 소리가 안나오는데, 이 부분을 수정해야겠다. 그리고 내일은 친구들과 보드게임 카페에 가서 오랜만에 오프라인 마작을 치기로 했기 때문에 많은 부분을 개발하기는 힘들 것 같다. 어제는 몸이 안좋기도 했고 와우에서 레이드를 두 차례 다녀와서 별로 개발을 못했고, 오늘도 친구들이 게임을 하자고 해서 진행이 많이 되지는 않은 것 같다. 게임을 하자고 해서 하긴 했는데 개발을 하고 싶은 마음이 더 커서 게임에도 별로 집중을 못하고 개발도 별로 못했다. 결과적으로 별로 개운하지 못한 하루를 보낸 기분이 든다. 매일 개발만 하고 살 수는 없는 노릇이니 다른 일을 할 때면 그것에 집중하는 것이 좋겠다. 이전에 친구들과 돈을 모아 마작매트+마작패를 샀었는데, 장소를 제공해주던 친구가 해외로 나가버리는 바람에 거의 반 년간 오프라인 마작을 하지 못했다. 그런데 내일 드디어 할 수 있게 되었으니 오늘과는 다르게 좀 재미있게 놀아야겠다. 그러고나면 개발에 더 집중할 수 있게 될 것이다.

 

앞으로는 우선 스토리의 진행을 위해 선택지를 나타내고, 고르는 기능을 추가해야겠다. 또, 스토리를 진행시키면서 필요한 애니메이션들을 추가해야겠다. 아마 목요일이나 금요일 즈음에 설이라 부산으로 내려가게 될 것 같은데, 그때 시나리오를 마저 쓰고 3개의 게임에 대한 구상을 자세히 해야겠다. 도트를 찍는 방법에 대해서도 배워야겠다. 또, 얼마나 시간이 소요될지 일정표를 짜보는 것도 좋을 것 같다. 아마 각종 애니메이션과 기본적인 기능들을 추가하면 스토리를 진행시키는 것은 크게 어렵지는 않을 것 같다는 생각이 든다. 기간은 넉넉히 1달은 잡아야 할 것 같지만, 빠르게 개발해서 2~3주 안에 끝내면 좋을 것 같다. 다만 3번째 게임을 구현하는 것이 큰 문제일 것 같다. 아마 그 부분을 제외하면 2주 안에 끝낼 수 있을 것 같지만, 3번째 게임을 구현하는 데 2주는 걸릴 것 같은 게 문제이다.

기능을 추가하고 구현하는 것에는 사실 시간이 많이 소요되지는 않지만, 각종 리소스들을 직접 구하고 수정해서 추가해야 하는 부분이 시간을 많이 잡아먹는 것 같다. 이런 일에 익숙하지 않기도 하다보니 시간이 더 오래 걸리는 것 같기도 하다. 협업의 중요성을 통감한다. 그래도 이런 것들을 하면서 배우는 부분도 많은 것 같다. 아마 이 게임을 완성하면서 한 층 더 성장할 것 같은 기분이 든다. 시간이 조금 걸리더라도 이왕 시작한 것 제대로 마무리를 지을 수 있도록 노력해야겠다.