Unity - ObjectPool(내장)

김도현·2023년 11월 15일
0

TIL

목록 보기
62/76

ObjectPool이란?

오브젝트 풀이란 반복되어 사용되는 객체를 생성 및 파괴하여 활용하지 않고 재사용 할 수 있도록 만드는 기능입니다.

예전에는 ObjectPool을 따로 만들어야 되었지만 2021년도 이후 버전부터는 Unity 내장되어 있어서 손쉽게 사용할 수 있게 되었습니다.

사용 방법

사용되는 객체 스크립트

  1. using UnityEngine.pool을 맨 위 상단에 적는다.
  2. private IObjectPool<사용되는 객체의 스크립트> 변수이름을 선언한다.
  3. 아래의 코드 처럼 메서드를 만듭니다. - ObjectPool의 생성 후 pool의 관리를 받기위한 매서드
public void SetManagedPool(IObjectPool<사용되는 객체의 스크립트> pool)
{
	_managedPool = pool;
}
  1. 아래 코드 처럼 메서드를 작성합니다. - 객체를 사용후 ObjectPool에 반환하기 위한 메서드
public void Destroy()
{
	_managedPool.Release(this);
}

사용 예시 - SFX(효과음)

using UnityEngine;
using UnityEngine.Pool;

public class SFX : MonoBehaviour
{
    private AudioSource _audioSource;
    private Transform _thisTransform;
    private IObjectPool<SFX> _managedPool;
    private SoundManager _soundManager;

    public string SFXName { get; private set; }

    private void Awake()
    {
        _soundManager = SoundManager.Instance;
        _thisTransform = this.transform;
        _audioSource = GetComponent<AudioSource>();
    }

    public void SetManagedPool(IObjectPool<SFX> pool)
    {
        _managedPool = pool;
    }

    public void PlaySFX(AudioClip audioClip, Vector3 position, float playTime)
    {
        _thisTransform.position = position;
        _audioSource.clip = audioClip;
        SFXName = audioClip.name;
        _audioSource.loop = false;
        _audioSource.Play();
        Invoke("DestroyAudioSource", playTime);
    }

    public SFX PlayLoopSFX(AudioClip audioClip, Vector3 position)
    {
        _thisTransform.position = position;
        _audioSource.clip = audioClip;
        SFXName = audioClip.name;
        _audioSource.loop = true;
        _audioSource.Play();
        return this;
    }

    public void DestroyAudioSource()
    {
        _soundManager.OnSFXAllStopEvent -= DestroyAudioSource;
        StopSFX();
        _managedPool.Release(this);
    }

    private void StopSFX()
    {
        _audioSource.Stop();
    }
}

객체를 관리하는 스크립트

  1. using UnityEngine.pool을 맨 위 상단에 적는다.
  2. private IObjectPool<사용되는 객체의 스크립트> 변수이름을 선언한다.
  3. ObjectPool을 사용하기 위한 메서드를 만들어 줍니다 - 생성방법, 불러올때 메서드, 돌아올때 메서드, 용량초과로 삭제될때 메서드
private 사용되는 객체의 스크립트 CreateObject()
{
	사용되는 객체의 스크립트 obj = Instantiate(생성되는 객체).GetComponent<사용되는 객체의 스크립트>();
    obj.SetManagedPool(2번으로 선언된 변수이름);
    return obj;
}

private void OnGetSFX(사용되는 객체의 스크립트 obj)
{
	obj.gameObject.SetActive(true);
}
private void OnReleasSFX(사용되는 객체의 스크립트 sfx)
{
    obj.gameObject.SetActive(false);
}

private void OnDestroySFX(사용되는 객체의 스크립트 obj)
{
    Destroy(obj.gameObject);
}
  1. 위에 선언한 변수이름을 생성자를 사용해 생성방법, 불러올때 메서드, 돌아올때 메서드, 용량초과로 삭제될때 메서드, 최대용량 순으로 적어서 생성하면 됩니다.
변수이름 = new ObjectPool<사용되는 객체의 스크립트>(생성방법, 불러올때 메서드, 돌아올때 메서드, 용량초과로, 최대용량);

예시

[SerializeField] private int _poolMaxCount = 20;
private void Awake()
{
	_objectPool_AudioSources = new ObjectPool<SFX>(CreateSFX, OnGetSFX, OnReleasSFX, OnDestroySFX, maxSize: _poolMaxCount);
}
//Objectpool
private SFX CreateSFX()
{
	SFX sfx = Instantiate(_sfxPrefab,this.transform).GetComponent<SFX>();
    sfx.SetManagedPool(_objectPool_AudioSources);
    return sfx;
}

private void OnGetSFX(SFX sfx)
{
	sfx.gameObject.SetActive(true);
}
private void OnReleasSFX(SFX sfx)
{
    sfx.gameObject.SetActive(false);
}

private void OnDestroySFX(SFX sfx)
{
    Destroy(sfx.gameObject);
}
//---------
  1. 불러와서 사용하기 - 2번으로 선언된 변수이름.Get();
  • 2번으로 선언된 변수이름.Get을 객체에 저장되어있는 스크립트를 불러와서 사용하면 됩니다.
    예시 코드
private void PlaySFX(AudioClip clip, Vector3 pos, float playTime)
{
	SFX sfx = _objectPool_AudioSources.Get();
	sfx.PlaySFX(clip, pos, playTime);
	OnSFXAllStopEvent += sfx.DestroyAudioSource;
}

예시 코드

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

public enum ClipType
{
    BGM,
    PlayerSFX,
    EnemySFX,
    NPCSFX,
    EnvironmentSFX,
    UISFX
}


public class SoundManager : CustomSingleton<SoundManager>
{
    protected SoundManager() { }

    [SerializeField] private AudioMixer _mixer;
    [SerializeField] private int _poolMaxCount = 20;
    [SerializeField] private string _defaultBGMName = "미지의 섬";

    private Dictionary<string, AudioClip>[] _ClipDics;

    private GameObject _sfxPrefab;
    private AudioSource _bgm;
    private IObjectPool<SFX> _objectPool_AudioSources;
    private List<SFX> _playLoopSFXList;

    public event Action OnSoundAllStopEvent;
    public event Action OnSFXAllStopEvent;
    private void Awake()
    {
        _bgm = this.GetComponent<AudioSource>();
        CreateSoundList();
        _sfxPrefab = Resources.Load<GameObject>("Prefabs/Sound/SFX");
        _objectPool_AudioSources = new ObjectPool<SFX>(CreateSFX, OnGetSFX, OnReleasSFX, OnDestroySFX, maxSize: _poolMaxCount);
        _playLoopSFXList = new List<SFX>();
    }

    private void CreateSoundList()
    {
        int i = 0;
        _ClipDics = new Dictionary<string, AudioClip>[6];
        foreach (ClipType type in Enum.GetValues(typeof(ClipType)))
        {
            _ClipDics[i] = new Dictionary<string, AudioClip>();
            AudioClip[] clips = Resources.LoadAll<AudioClip>("Sound/" + type);

            foreach (AudioClip clip in clips)
                _ClipDics[i].Add(clip.name, clip);

            i++;
        }
    }

    private void Start()
    {
        BGMPlay(_ClipDics[0][_defaultBGMName]);
        OnSoundAllStopEvent += BGMStop;
        OnSoundAllStopEvent += OnSFXAllStopEvent;
    }

    //Objectpool
    private SFX CreateSFX()
    {
        SFX sfx = Instantiate(_sfxPrefab,this.transform).GetComponent<SFX>();
        sfx.SetManagedPool(_objectPool_AudioSources);
        return sfx;
    }

    private void OnGetSFX(SFX sfx)
    {
        sfx.gameObject.SetActive(true);
    }
    private void OnReleasSFX(SFX sfx)
    {
        sfx.gameObject.SetActive(false);
    }

    private void OnDestroySFX(SFX sfx)
    {
        Destroy(sfx.gameObject);
    }
    //---------

    //효과음
    /// <summary>
    /// 효과음 출력(Button or Trigger) - 재생시간은 효과음의 재생시간으로 설정됩니다.
    /// </summary>
    /// <param name="clipType">효과음 종류를 골라주세요</param>
    /// <param name="sfxName">효과음의 이름</param>
    /// <param name="pos">효과음이 생기는 위치를 정해주세요 ex) 플레이어의 효과음이면 플레이어의 위치를 보내주세요</param>
    public bool CallPlaySFX(ClipType clipType, string sfxName, Vector3 pos)
    {
        if (_ClipDics[(int)clipType].TryGetValue(sfxName, out AudioClip value))
        {
            PlaySFX(value, pos, value.length);
            return true;
        }
        else
        {
            Debug.Log("Error 이 효과음과 같은 이름이 없습니다. 다시 확인해 주세요");
            return false;
        }
    }

    /// <summary>
    /// 효과음 출력 (Button or Trigger)
    /// </summary>
    /// <param name="clipType">효과음 종류를 골라주세요</param>
    /// <param name="sfxName">효과음의 이름</param>
    /// <param name="pos">효과음이 생기는 위치를 정해주세요 ex) 플레이어의 효과음이면 플레이어의 위치를 보내주세요</param>
    /// <param name="playTime">재생시간을 설정해 주세요</param>
    /// <returns></returns>
    public bool CallPlaySFX(ClipType clipType, string sfxName, Vector3 pos, float playTime)
    {
        if (_ClipDics[(int)clipType].TryGetValue(sfxName, out AudioClip value))
        {
            PlaySFX(value, pos, playTime);
            return true;
        }
        else
        {
            Debug.Log("Error 이 효과음과 같은 이름이 없습니다. 다시 확인해 주세요");
            return false;
        }
    }

    private void PlaySFX(AudioClip clip, Vector3 pos, float playTime)
    {
        SFX sfx = _objectPool_AudioSources.Get();
        sfx.PlaySFX(clip, pos, playTime);
        OnSFXAllStopEvent += sfx.DestroyAudioSource;
    }


    public bool CallPlayLoopSFX(ClipType clipType, string sfxName, Vector3 pos)
    {
        if (_ClipDics[(int)clipType].TryGetValue(sfxName, out AudioClip value))
        {
            PlayLoopSFX(value, pos);
            return true;
        }
        else
        {
            Debug.Log("Error 이 효과음과 같은 이름이 없습니다. 다시 확인해 주세요");
            return false;
        }
    }

    public bool CallStopLoopSFX(ClipType clipType, string sfxName)
    {
        int _playLoopSFXListCount = _playLoopSFXList.Count;
        for (int i = _playLoopSFXListCount - 1;  i >= 0; i--)
        {
            if(_playLoopSFXList[i].SFXName == sfxName)
            {
                _playLoopSFXList[i].DestroyAudioSource();
                _playLoopSFXList.RemoveAt(i);
                return true;
            }
        }
        return false;
    }


    private void PlayLoopSFX(AudioClip clip, Vector3 pos)
    {
        SFX sfx = _objectPool_AudioSources.Get();
        _playLoopSFXList.Add(sfx.PlayLoopSFX(clip, pos));
        OnSFXAllStopEvent += sfx.DestroyAudioSource;
    }
    //-------

    //사운드 조절
    public void MasterVolume(float val)
    {
        _mixer.SetFloat("MasterVolume", Mathf.Log10(val) * 20);
    }

    public void BGMVolume(float val)
    {
        _mixer.SetFloat("BGMVolume", Mathf.Log10(val) * 20);
    }

    public void SFXVolume(float val)
    {
        _mixer.SetFloat("SFXVolume", Mathf.Log10(val) * 20);
    }
    //------

    //배경음 변경
    public void ChangeBGM(string bgmName)
    {
        if (_ClipDics[0].TryGetValue(bgmName, out AudioClip value))
            BGMPlay(value);
        else
            BGMPlay(_ClipDics[0][_defaultBGMName]);
    }

    private void BGMPlay(AudioClip clip)
    {
        _bgm.clip = clip;
        _bgm.loop = true;
        _bgm.volume = 0.1f;
        _bgm.Play();
    }

    public void BGMStop()
    {
        _bgm.Stop();
    }
    //-------

    public void SoundAllStop()
    {
        OnSoundAllStopEvent?.Invoke();
    }

    public void SFXAllStop() 
    {
        OnSFXAllStopEvent?.Invoke();
    }
}

0개의 댓글