본문 바로가기
Unity

유니티 시스템 프로그래밍 Pt.1 - 2주차

by argentdarae 2024. 9. 16.

개요

2주차 내용에 대하여 공부하고, 강의 내용 보완 혹은 추가적인 정보 제공을 목적으로 글을 서술할 예정이다

 

목차는 다음과 같다

  • 유저 데이터 및 데이터 관리 시스템
  • 데이터 테이블 관리 시스템
  • 오디오 시스템
  • UI 시스템

 

 

내용

필수 게임 시스템 제작

유저 데이터 및 데이터 관리 시스템

세이브/로드 기능이 있는 대다수의 게임이라면, 어떠한 형태로든 데이터를 저장하여야 한다

이 강의의 유저 데이터 클래스 또한 무난한 클래스 구현이 진행되는데, 다만 짚고 넘어가고 싶은 점이 두개 있었다

  1. 꼭 IUserData를 인터페이스 형태로 구현되어야 하는가? 구현 내용을 보면 추상 클래스로 정의하는 것이 좋을 것 같았다
  2. 강의라 PlayerPrefs를 사용하여 저장하는 형태를 취했지만, 실무에서는 이런 민감한 데이터를 보안이 취약한 형태로 저장해서는 안된다

1번의 생각은 강의가 더 진행되면 어떻게 사용하는지 보고 생각이 바뀔 수 도 있을 것 같다

내가 인터페이스를 사용하는 기준은, 아예 성격이 다른 클래스들이 같은 동작을 수행하게 만드는 것. 즉 다형성을 이용할 때만 사용한다

따라서 이 경우 굳이 메서드 구현 강제를 위해서라면 추상클래스로 구현하는 것이 UserData라는 정체성을 더 확실하게 보여주지 않을까 싶다

 

2번의 경우 다음의 공식 문서를 참고해 달라

PlayerPrefs 형태로 저장되는 데이터는 로컬 레지스트리에 저장되기 때문에 보안에 취약하다. 그래서 보통 UUID를 구현, 서버와의 통신으로 인증 절차를 거치고 게임을 실행시키는 과정을 거치게 된다

만약 게임에서 PlayerPrefs를 사용하는 경우는, 탈취당하여도 상관 없는 데이터만 기록해야 한다

예를 들어 튜토리얼 시퀀스가 여러개로 나뉘어 있다면, 어디까지 진행되었는지 기록하는, 그런 사소한 데이터를 저장할 것 같다

 

데이터 테이블 관리 시스템

CSV 형태로 데이터가 관리되고, 이를 파싱하여 사용하는 전형적인 형태로 구현되어있다

인사이트는 CSVReader 자체는 범용적인 모듈이라 코멘트할 부분이 없다. Regex 정규표현식을 한번 복습하는 정도라면 충분할 것 같다

 

하지만 강의 내용과는 별개로, 강사의 코드를 보고 드는 의문점이 있었다

  • ChapterDataTable을 굳이 List로 관리할 이유가 있을까?

Dic 형태로 명확하게 의도를 나타내고, TryGetValue로 간단하게 표현할 수 있지 않을까?

 

오디오 시스템

강사의 방식은

  1. 오디오 리소스들을 Resources에 import 파일과 똑같은 enum 멤버를 정의
  2. 런타임에 리소스 로드
  3. Instantiate
  4. AddComponent
  5. Dic에 열거형을 Key로 참조 세팅

인데.. 어중간하다는 생각을 지울 수 없었다

 

  1. 굳이 열거형 멤버와 리소스 파일 네이밍을 같게 가져가야 하는가. 리소스가 늘어날 때마다 이렇게 불편하게 추가할 것인지
  2. AddComponent로 연산을 또 할거면 애초에 프리팹/스크립터블 오브젝트 형태로 리소스를 저장하고 사용하는게 더 합리적이지 않을지
  3. 구현된 오디오 매니저의 SFX는 Play()를 사용하고 있는데, PlayOneShot으로 바꾸는 것이 더 좋지 않을까. 물론 이부분은 기획에 따라 조금씩 달라지는 부분이겠지만

 

또 최적화 관련해서도

 

/// <summary>
/// 지정된 BGM을 재생합니다.
/// </summary>
/// <param name="bgm">재생할 BGM의 열거형 값.</param>
public void PlayBGM(EBGM bgm)
{
    if (_currentBGMSource)
    {
        _currentBGMSource.Stop();
        _currentBGMSource = null;
    }

    if (!_bgmDic.ContainsKey(bgm))
    {
        Debug.LogError($"Invalid clip name. {bgm}");
        return;
    }

    _currentBGMSource = _bgmDic[bgm];
    _currentBGMSource.Play();
}

 

이런 식으로 Contains를 사용하고 있는데, 위와 같이 코드를 짤 바에는

 

/// <summary>
/// 지정된 BGM을 재생합니다.
/// </summary>
/// <param name="bgm">재생할 BGM의 열거형 값.</param>
public void PlayBGM(EBGM bgm)
{
    if (_currentBGMSource)
    {
        _currentBGMSource.Stop();
        _currentBGMSource = null;
    }

    if (!_bgmDic.TryGetValue(bgm, out AudioSource bgmSource))
    {
        Debug.LogError($"Invalid clip name. {bgm}");
        return;
    }

    _currentBGMSource = bgmSource;
    _currentBGMSource.Play();
}

 

TryGetValue를 사용하는 것이 최적화 할 수 있는 방법이었다

24.09.17 추가
C# ContainsKey vs TryGetValue 글을 작성, 명확하게 성능을 비교해보았다

 

 

여러모로 디테일이 아쉬웠던 오디오 매니저 파트였다

 

 

마무리

1주차와 비교하여 많은 문답이 있어서 더 재밌었던 2주차 였다

 

문답을 진행하면서 Linq에 관한 내용이라던지, Contains와 TryGetValue의 차이점이라던지 예전에 공부했던 내용을 다시 상기 할 수 있었는데 이 부분도 별도의 포스팅으로 남겨두면 좋겠다는 생각이 들었다

사소한 디테일 하나하나가 쌓여 전체적인 퀄리티 차이를 만든다고 생각하는데, 높은 퀄리티의 프로젝트를 만들 수 있는 이런 지식 하나하나를 나누고 싶다는 생각이 들었다

 

3주차도 더 열심히 의견을 나누고, 인사이트를 가져갈 수 있는 시간이 되었으면 좋겠다