• 아이템 오브젝트와 아이템 데이터 따로 관리하기

  • Ditionary로 관리

 

언어 호환을 한다면 아래의 유니티 기본 표로 관리하기 때문에 Key값만 있어도 된다.

 

  • 아이템 능력치

능력치별로 나누면 없어도 되는 데이터까지 가지게 되므로 Modifiers로 한 번에 묶고 Split 함수로 잘라쓴다.

csv 파일로 저장할 때, 쉼표(,)로 항목이 나뉘어 저장되므로 다른 문자로 나눈다.

'부트캠프 > Study' 카테고리의 다른 글

var, 제네릭, object  (0) 2024.02.04
Asset  (0) 2024.01.22
<Unity> 유니티 내장 오브젝트 풀(Object Pool)  (1) 2024.01.11
enable과 SetActive의 차이점  (0) 2023.12.28
<프로젝트> Unity 게임 개발 심화 개인과제  (1) 2023.12.27

Pool과 Prefab을 연동하여 구현하는 것을 오브젝트 풀이라고 한다.

오브젝트 풀 구현 방법에는 여러 가지가 있는데 오늘은 그중 유니티에 내장되어있는 오브젝트 풀에서 공부했다.

 

일단 일반적인 방법으로 Prefab 하나에 오브젝트 풀 스크립트를 붙여주는 것인데 이 방법의 단점은 Prefab을 하나 만들 때마다 오브젝트 풀 클래스를 만들어줘야하는 것이다. 그래서 유니티에서 지원하는 풀링을 이용해볼 것이다.

 

먼저 유니티에서 제공하는 Pool을 만들려면 OnCreate, OnGet, OnRelease, OnDestroy 콜백 함수 네 가지가 필요하다. 이 함수들을 IObjectPool 인터페이스를 상속하는 ObjectPool 클래스에 넣어준다. 제네릭으로 GameObject라는 자료형으로 제한한다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;

public class Pool {
    private GameObject _prefab; // 어떤 프리팹과 엮일 것인가.
    private IObjectPool<GameObject> _pool; // 프리팹들을 담을 풀.
    private Transform _root; // 오브젝트가 생성될 위치.
	
    // 읽기 속성 프로퍼티 - 오브젝트가 비어있다면 생성해서 반환.
    private Transform Root {
        get {
            if (_root == null) {
                GameObject obj = new() { name = $"[Pool_Root] {_prefab.name}" };
                _root = obj.transform;
            }
            return _root;
        }
    }

	// 생성자 - MonoBehaviour를 상속받고 있지 않기 때문에 생성자로부터 초기화.
    public Pool(GameObject prefab) {
        this._prefab = prefab;
        this._pool = new ObjectPool<GameObject>(OnCreate, OnGet, OnRelease, OnDestroy);
    }

    public GameObject Pop() { // 스택, 선입후출/후입선출 형식의 자료 꺼내기.
        return _pool.Get(); // OnGet 호출.
    }

    public void Push(GameObject obj) { // 자료 들여보내기.
        _pool.Release(obj); // OnRelease 호출.
    }

	// 유니티에서 제공하는 Pool을 사용하기 위한 콜백 함수.
    #region Callbacks

    private GameObject OnCreate() {
        GameObject obj = GameObject.Instantiate(_prefab); // 오브젝트 생성.
        obj.transform.SetParent(Root); // 생성될 위치 등록.
        obj.name = _prefab.name; // 프리팹 이름 등록.
		// List.Add(obj);
        return obj;
    }
    private void OnGet(GameObject obj) { // Pool이 이미 있는 경우.
        obj.SetActive(true);
    }

    private void OnRelease(GameObject obj) { // Pool을 사용하지 않을 때.
        obj.SetActive(false);
    }

    private void OnDestroy(GameObject obj) { // Pool이 너무 많은 경우 정리.
        GameObject.Destroy(obj);
    }

    #endregion

}

 

이제 Manager를 통해 Pool을 관리해보자.

public class PoolManager {
	
    // 딕셔너리 생성.
    private Dictionary<string, Pool> _pools = new();

    public GameObject Pop(GameObject prefab) {
        // #1. 풀이 없으면 새로 만든다.
        if (_pools.ContainsKey(prefab.name) == false) {
            CreatePool(prefab);
        }

        // #2. 해당 풀에서 하나 가져온다.
        return _pools[prefab.name].Pop();
    }

    public bool Push(GameObject obj) {
        // #1. 풀이 있는지 확인한다.
        if (_pools.ContainsKey(obj.name) == false) return false;

        // #2. 풀에 게임 오브젝트를 넣는다.
        _pools[obj.name].Push(obj);

        return true;
    }

    private void CreatePool(GameObject prefab) { // 유지보수를 위해 Pool 생성을 함수로 만든다.
        Pool pool = new(prefab);
        _pools.Add(prefab.name, pool);
    }

	// 씬에 변화가 있을 때 비워주기 위한 함수.
    public void Clear() {
        _pools.Clear();
    }
}

 

테스트로 프리팹을 넣어 오브젝트 풀을 사용해보자.

public class BaseScene : MonoBehaviour {

    private bool _initialized;

    void Start() {
        Initialize();
    }

    protected virtual bool Initialize() {
        if (_initialized) return false;
        // 각종 초기화 함수.
        // DataManager 초기화
        // GameManager 초기화

        _initialized = true;
        return true;
    }

}

 

public class GameScene : BaseScene {

    public GameObject prefab1;
    public GameObject prefab2;

    protected override bool Initialize() {
        if (!base.Initialize()) return false;

        GameObject obj1 = Main.Pool.Pop(prefab1);
        GameObject obj2 = Main.Pool.Pop(prefab1);
        GameObject obj3 = Main.Pool.Pop(prefab1);
        GameObject obj4 = Main.Pool.Pop(prefab1);
        GameObject obj5 = Main.Pool.Pop(prefab1);

        Main.Pool.Push(obj2);
        Main.Pool.Push(obj4);

        return true;
    }

}

 

 

유니티 ObjectPool은 캡슐화가 잘 되어있다. 내부 로직을 몰라도 CallBack 함수 네 가지만 사용해도 ObjectPool을 이용할 수 있기 때문에 협업에 유용하다.

Manager로 관리하는 경우에도 마찬가지이다. Pool 클래스를 몰라도 생성자와 Pop, Push 함수만 알아도 손쉽게 사용할 수 있다.

추가로 그렇기 때문에 협업에서 중요한 점은 내부 로직을 몰라도 함수명만 봐도 어떤 함수인지 알 수 있도록 쉽게 이용할 수 있게 명확히 표기하는 것이 좋다.

'부트캠프 > Study' 카테고리의 다른 글

Asset  (0) 2024.01.22
<Unity> Item 관리  (0) 2024.01.12
enable과 SetActive의 차이점  (0) 2023.12.28
<프로젝트> Unity 게임 개발 심화 개인과제  (1) 2023.12.27
<Unity> 프로젝트 빌드 하는 방법(Building)  (1) 2023.12.26

팀프로젝트가 마무리 되었다. 발표를 마치고 아쉬웠던 점을 팀원분들과 이야기했다.

일단 어디서 잘못 건든건지 리듬게임에 가장 치명적인 싱크가 안맞는 문제가 있었다는게 아쉬웠다. 노트 생성이 게임이 시작하자마자 되도록 바뀌어 있어서 노트와 노트 타격하는 곳의 거리 계산이 적용되고 있지 않는데 아마 노트 클론을 리스트로 옮기면서 생긴 버그 같다.

그 외에도 씬 전환 시, 아이템 장착이나 설정 옵션이 뜨지 않는 등의 문제가 있었다.

실력이 부족하여 구현하지 못한 아쉬움도 많았지만  평생 꼭 도전해보고 싶었던 리듬게임이라는 장르를 도전하여 제작한 과정이 진심으로 즐거웠다. 나중에 최종 프로젝트가 끝나고 개인 과제를 좀더 다듬어서 다시 도전해보고 싶다.

노트 최적화

오버랩은 레이어 마스크로 지정된 Collider를 찾는 메서드이다. IsTrigger를 사용할 경우, 노트가 너무 빠르면 감지가 안되는 경우가 있기 때문에 최적화를 위해  처음엔 RigidBody 대신 오버랩(Overlap)을 사용하려했다.

그런데 아예 노트와 노트 판정하는 위치의Transform 값을 받아와서 비교하면  Collider를 사용하지 않고 할 수 있었다.

 

노트를 리스트에 추가하기 위해 TimingManager 스크립트와 GetComponent로 연결하는데 연결이 되지 않고 null 값이 떴다. GetComponent는 자기 자신을 가져오는데 Note 스크립트와 TimingManager 스크립트가 같은 오브젝트에 있지 않았기 때문이었다.

Find 메서드를 사용하여 해결하였다.

public class Note : MonoBehaviour
{
    [SerializeField] private NoteMove noteMove;
    public KeyCode key;
    private Color color;
    TimingManager timingManager;
   

    private void Awake()
    {
        color = GetComponent<SpriteRenderer>().color;
        timingManager = FindObjectOfType<TimingManager>();

    }

    public void NoteCreate()
    {
        NoteMove note = Instantiate(noteMove, transform.position, Quaternion.identity);
        note.GetComponent<SpriteRenderer>().color = color;
        note.noteSpeed = PlaySetting.speed;
        
        timingManager.boxNoteList.Add(note.gameObject);
    }
}

 

트러블 슈팅

게임 플레이 도중 일시정지하거나 재시작 하는 기능을 추가하기로 했는데 노래 선택해서 플레이 하면서 일시정지 후, 게임을 나갔다가 다시 들어오면 게임 플레이 화면이 로딩되지 않는 현상이 있었다.

일시정지 기능을 Time.timeScale = 0으로 구현했는데 그 후에 다시 시간이 흐르도록 처리하지 않아서 생긴 문제였다.

 

+ Recent posts