BGM Game Wiki - UAVisonline/Portfolio GitHub Wiki

BGM Game

4지선다 BGM 맞추기 Game

4지선다 BGM 맞추기 Game은 다양한 게임의 유명 BGM이 재생되면 4지선다 선택지를 이용해 게임 제목을 맞추는 Game이다. 해당 Game은 Practice mode 및 Arcade mode가 존재하며 Practice는 플레이어가 그만두고 싶을 때까지 게임 제목을 맞출 수 있는 mode이며, Arcade는 30개의 문제를 시스템이 가져오고 플레이어는 이를 맞추어서 몇 문제를 맞추는지 확인하는 mode이다.

해당 프로젝트는 2020년 2월 작업을 시작하여 틀(게임 시스템 및 콘텐츠 템플릿)을 제작, 그 후 콘텐츠 추가 및 게임 Scene에서 시스템을 동작하기 위해 UI를 추가, 연결하는 방식으로 개발을 완료했다.

해당 프로젝트를 진행하면서 관련 음악 재킷 및 음원 파일은 아래와 같이 추출, 편집했으며, 프로젝트 완료 후 문제가 될 것이 확실한 배포 같은 경우는 진행하지 않았다.

  • 음악 재킷 : 스팀 및 구글 Image를 통해 이미지 자킷 추출
  • 음원 파일 : 유튜브 음악 영상을 추출, 해당 영상의 음원에서 필요한 부분만 편집해 2차 추출

해당 위키에서는 프로젝트에 대해 아래 두 항목들에 관해 서술하고자 한다.

  • 게임 시스템이 코드 상 어떻게 동작, 상호작용 하는가?

  • 지금 이를 보는 시점에서 프로젝트의 잘못된 부분은 어디라고 생각하는가? 이를 어떻게 수정해야 하는가?

게임 시스템이 코드 상 어떻게 동작, 상호작용 하는가?

1. Game Resources Load (Problem_Base Script)

Game을 실행하면 우선 Resource folder내에 있는 음악파일(해당 프로젝트에서는 Problem_file Script Component를 가진 게임오브젝트)을 읽어들이는 과정이 필요하다. 해당 프로젝트에서는 이러한 작업을 Problem_Base Script에서 아래와 같이 처리하고 있다.

34 public int Load(string folder) // Resource Folder로 들어가서 음악 문제 불러옴
   {
      object[] obj = Resources.LoadAll(folder);
      for(int i = 0;i<obj.Length;i++)
      {
        GameObject _gameobject = (GameObject)obj[i];
        _cache[_gameobject.name] = _gameobject;
        Problem_file temp = _gameobject.GetComponent<Problem_file>(); 
      }
      return obj.Length; // 폴더 내 오브젝트의 숫자를 반환
44 } 
//_cache는 <string,GameObject>형태 Dictionary, string folder는 Resources folder 내 읽고 싶은 folder의 이름 

이를 통해 Problem_Base Script는 음악 오브젝트 파일을 읽을 수 있으며, 스크립트는 Start function에서 난이도 별로 나누어진 오브젝트 파일을 아래와 같이 읽는다.

90          if (loop == 0) list_size = Load("0_Very_Easy");
            else if (loop == 1) list_size = Load("1_Easy");
            else if (loop == 2) list_size = Load("2_Normal");
            else if (loop == 3) list_size = Load("3_Hard");
            else if (loop == 4) list_size = Load("4_Very_Hard");
            else if (loop == 5) list_size = Load("5_Expert");
96          else if (loop == 6) list_size = Load("6_Nightmare");

그 후 Game 실행에 있어서 필요한 변수를 초기값으로 설정, 플레이어가 실제 Game에 돌입할 때 까지 대기한다.


2.본 게임 Scene에서의 작동 (Problem_Base Script 및 Button Script들)

Main_Menu_Button Script에서는 function 2개(training mode, arcade mode 진입)를 통해 본 게임으로 진입하며 이 때 Problem_Base Singleton의 Start_Game function을 호출한 뒤 Scene을 Load하는 방식을 사용한다. 그 후 Problem_Base의 Start_Game function은 진입한 game mode에 따라 2가지 Coroutine(Start_Practice, Start_Arcade) 중 하나를 실행하게 된다.

449 IEnumerator Start_Arcade() // Arcade mode Coroutine
    {
        while (true)
        {
            if (SceneManager.GetActiveScene().name == "Game") break;
            yield return new WaitForSeconds(Time.deltaTime);
        }

457     GameObject test_obj = GameObject.Find("Only_Test"); // 테스트용 설정 오브젝트 탐색 (UI)
458     test_obj.SetActive(false); // 그 후 해당 오브젝트 종료 (arcade mode에서는 practice mode 전용 UI가 사라져야 하기 때문)

460     ScoreManager.score_manager.arcade_text(); // ScoreManager Singleton에 대해 해당 문제가 몇번째 문제인지 표기하도록 함
        problem_file = Instantiate(Get(problem_list[list_pos]), Vector2.zero, Quaternion.identity).GetComponent<Problem_file>(); // 음악 오브젝트 파일 생성
        problem_file.Problem_set(); // 오브젝트에 대한 정보, 힌트, 선택지 세팅 및 음악 clip을 Problem_Base Script에서 표시 가능하도록 설정

        jacket_black();
        correct = false;
        solved = false;
        Music_Play();
        yield return new WaitForSeconds(1.0f);
        Directer_machine.directer.set_panel_slide(false);
470 }

위 코드는 Arcade mode에 진입할 때 실행되는 Coroutine이며, Practice mode는 457~460 Line의 내용만 바뀐 Coroutine(Arcade mode UI를 삭제)이 실행되어 Game Scene에 진입하게 된다.

그 후 플레이어는 Game Scene에서 Button UI를 이용해 Game을 진행할 수 있으며 이는 아래와 같은 Button Script와 Problem_Base Script의 상호작용으로 이루어진다.

  • Answer_Button Script : answer_button(int i) - Problem_Base Script : answer_function(int i) [정답 선택 버튼]
  • Answer_Button Script : Hint_Button(int num) - Problem_Base Script : give_a_hint(int num) [힌트 제공 버튼]
  • Answer_Button Script : Pass_Button() - Problem_Base Script : pass_function() [Pass 기능 버튼]
  • Play_Button Script : Play_Button_Click() - Problem_Base Script : Music_Play() [음악 재생 버튼 - 다시 처음부터 재생하기]
  • Play_Button Script : Back_Button_Click() - Problem_Base Script : Music_back_ten_second() [음악 되감기 버튼]
  • Answer_Button Script : back_button_click() - Problem_Base Script : Back_Game() & ScoreManager Script : set_initial() [Main화면으로 돌아가기]
  • Answer_Button Script : next_button_click() - Answer_Button Script : next_problem coroutine -> 수많은 ProblemBase Code와 상호작용

이러한 상호작용을 이용해 플레이어는 Game을 진행하는 것이 가능하며, 이를 그래프로 표현하면 아래와 같은 이미지로 나타낼 수 있다.

BGM Game

프로젝트의 잘못된 부분은 어디라고 생각하는가? 이를 어떻게 수정해야 좋을까?

1. Game 시작 시 Load 문제

해당 프로젝트에서는 Asset/Resources를 통해 동적으로 파일을 메모리에 불러오는 방식을 사용하고 있다. 그러나 해당 프로젝트는 Problem_Base Script가 Resources를 Load해 내부 Dictionary에 저장해두므로 그 뒤에는 따로 메모리에 Resources file을 올려둘 이유가 없다. 그러나 해당 프로젝트에서는 이를 Unload하는 부분이 존재하지 않으며, 따라서 Problem_Base script를 통해 이미 불러올 준비가 마친 파일을 계속 메모리에 올려두는 성능 낭비가 발생하고 있다.

또한 문제의 정보를 저장하고 있는 Problem_File Script는 게임 오브젝트 내 컴포넌트의 형태로 들어가 관리가 진행된다. 그러나 프로젝트에 있어서 필요한건 게임 오브젝트가 아니라 Problem_File 컴포넌트이며, 따라서 Problem_File Script를 ScriptableObject 형태로 바꾼다면 게임 오브젝트 형태로 데이터를 관리하는 불편함이 사라지게 되며, 관리 효율성도 더 증가하게 될 것이다.

2. Coroutine 실행 시 new WaitforSeconds 메모리 할당 문제

해당 프로젝트에서는 다양한 Coroutine을 통해 게임 메커니즘(Main화면 이동, 게임 mode 실행, 문제 교체)를 구현하였는데 이러한 부분에 있어서 시간 관리를 new WaitforSecond로 처리하고 있다. 따라서 코루틴이 실행될때마다 새로운 WaitforSecond 데이터를 메모리에 선언해 사용하고 있으며 이는 성능에 있어서 좋지 않은 영향을 미친다.

다행히 Main화면 이동, 게임 mode 실행은 자주 실행되는 Coroutine이 아니라 이러한 부분이 큰 영향을 미치지는 않지만, 문제 교체는 이보다는 빈번하게 실행되므로 매번 메모리를 선언하는 것이 부담이 될 수 있다. 따라서 Script 내 WaitforSecond 변수를 추가하고 여기에 메모리를 선언한 뒤 Coroutine이 실행될 때 마다 해당 변수를 불러와 사용하는 방식으로 구조를 바꾼다면 메모리 부담을 줄일 수 있을 것이다.

⚠️ **GitHub.com Fallback** ⚠️