var나 T(제네릭)을 사용하는 이유를 명확하게 몰랐는데 boxing과 unboxing 등 메모리의 구조에 대해 공부하며 조금이나마 깨달았다.

초기의 var는 값형만 받을 수 있기 때문에 class를 받을 수 없었고 참조타입을 받을 수 있도록 object가 등장하며 모든 타입을 받을 수 있게 되었다. var는 값을 초기화 한 후 값이 지정되며 제네릭은 값을 지정하기 때문에 boxing과 unboxing이 일어나지 않는다. object는 참조 타입으로  boxing과 unboxing이 일어나기 때문에 가비지 컬렉션의 대상이 될 수 있다.

 

먼저 boxing과 unboxing은 값 형식과 참조 형식을 서로 변환해 주는 것이다.

boxing과 unboxing이란

박싱된 객체는 힙 영역에 할당되어 가비지 컬렉션의 대상이 될 수 있어 메모리 효율로 인해 성능 저하가 올 수 있다. boxing과 unboxing는 System.Collections.ArrayList, param object[], Debug.Log 등에 사용되는데 이때 아무 자료형이나 올 수 있는 var 키워드나 Generic 사용으로 boxing이 일어나지 않게 할 수 있다.

 

그러나  코딩을 할 때 가독성 등의 이유로 자료형을 명확히 표기해야 하기 때문에 var와 제네릭을 무조건 사용하는 것은 지양해야 한다. var와 제네릭, boxing과 unboxing 모두 필요에 따라 적절하게 사용해야 한다.

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

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

게임 컨셉에 맞는 에셋을 찾기 힘들어서 직접 만들까 고민이다.

그러나 Animation까지 직접 만들기엔 마감을 맞추기 어려울 것 같고... 2D Animation을 이용하면 캐릭터를 파츠별로 만들어두기마나 해도 간단하게 Animation을 구현할 수 있어 고민해봤는데 2D Animaion 사용 방법을 학습하는 것도 시간이 걸릴 것 같다.

일단 에셋은 아무거나 쓰고 버전업을 하면서 건드는게 나을 것 같다.

임시 에셋
파츠별로 그려둔 캐릭터였던 것. 안녕 제이크...

 

 

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

  • 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

+ Recent posts