2023. 8. 4. 17:08ㆍ[Unity] 게임 개발
참고 원문 : 2022年現在におけるUniRxの使いみち
UniRx와 async/await와 UniTask
async/await, 이쪽은 간단하게 "편리하게 된 코루틴" 이라고 하는 인식으로 일단은 문제 없습니다. async/await 자체는 비동기 처리의 대기 처리를 간소하게 쓸 수 있도록 하기 위한, C#의 언어 기능입니다. (구조는 전혀 다르지만) async/await는 외형으로서는 Unity의 코루틴과 매우 비슷합니다.
UniRx에서도 코루틴과의 조합은 편리했지만, async/await에서도 UniRx와 조합하면 그 편리성이 높아집니다. 특히 UniTask라는 라이브러리가 강력하며, 여기를 도입함으로써 Unity에서 async/await의 취급을 대폭 강화할 수 있습니다. (async/await와 UniTask는 반드시 세트로 다루고 싶을 정도로 강력합니다)
여기서 오해하고 싶지 않지만, "async/await이 있기 때문에 UniRx는 불필요해졌다" 는 것은 아닙니다. UniRx는 UniRx에서 사용할 수 있는 경우도 아직도 있고, async/await또한 편리하게 사용할 수 있는 경우도 다수 있습니다. 옛날에는 UniRx 밖에 선택사항이 없었지만, 현대에 있어서는 UniRx를 사용하지 않아도 보다 간단하게 구현할 수 있는 수법이 늘어났다고 하는 것입니다.
UniRx가 편리한 장소, async/await가 편리한 장소
UniRx는 결코 다목적이 아닙니다. 상황에 따라서는 async/await(혹은, 완전히 다른 도구)를 사용하는 편이 깨끗하게 쓸 수 있는 경우도 많이 있습니다.
그럼 UniRx async/await를 어떻게 구사할까요, 기본적으로는 다음 구분으로 문제 없습니다.
- UniRx가 편리하게 사용할 수 있는 경우
- 불특정 다수에 여러 번 이벤트 메시지를 전달하는 경우
- 특정 처리의 흐름(시퀀스)을 여러 번 반복 실행하는 경우
- 종속성을 역전하려는 경우
- Push형으로 구동할 필요가 있는 경우
- async/await(및 UniTask조합)을 편리하게 사용할 수 있는 경우
- "한 번만 실행되는 처리"를 기다리는 경우
- 절차적 (if문 for문)으로 처리를 작성하고 싶은 경우
- 처리의 동작이 Pull형으로 끝나는 경우
UniRx는 '반복 실행'에 특화되어 있습니다. 그 때문에 "몇번인가 발행되는 이벤트를 처리한다" "Update()의 루프 대신에 사용한다"라고 하는 용도에 매치하고 있습니다.
한편으로 async/await는 "뭔가의 처리를 1회만 기다린다"에 특화하고 있습니다. 그 때문에 "초기화 처리가 끝나는 것을 기다린다" "외부와의 통신을 기다린다"라고 하는 경우는 async/await에서 쓰는 편이 스마트하게 쓸 수 있습니다. (단발의 처리를 UniRx로 쓰는 것도 물론 할 수 있지만, 중복이 되기 쉽다)
UniRX 실제 적용 비교 예시
매 프레임 특정 Camera의 위치를 바라보게 하는 Billboard Code.
using UnityEngine;
public class LookAtCamera : MonoBehaviour
{
public Camera targetCamera; // 특정 카메라를 지정하기 위한 변수
private void Update()
{
if (targetCamera != null)
{
// 이미지가 특정 카메라를 바라보게 회전을 조절
Vector3 lookDirection = targetCamera.transform.position - transform.position;
transform.rotation = Quaternion.LookRotation(lookDirection, Vector3.up);
}
}
}
UniRX의 EveryValueChanged 기능을 활용하여 특정 Camera의 위치에 변경값이 있을때만 호출되도록 수정.
using UnityEngine;
using UniRx;
public class LookAtCamera : MonoBehaviour
{
public Camera targetCamera; // 특정 카메라를 지정하기 위한 변수
private void Start()
{
if (targetCamera != null)
{
// 카메라의 위치를 감시하고 위치가 변경될 때마다 이벤트를 발생시키는 Observable
Observable.EveryValueChanged(() => targetCamera.transform.position)
.Subscribe(_ => UpdateRotation()) // 위치가 변경될 때마다 UpdateRotation 함수 호출
.AddTo(this); // 스크립트가 파괴될 때 구독을 해제하기 위해 AddTo(this) 사용
}
}
private void UpdateRotation()
{
// 이미지가 특정 카메라를 바라보게 회전을 조절
Vector3 lookDirection = targetCamera.transform.position - transform.position;
transform.rotation = Quaternion.LookRotation(lookDirection, Vector3.up);
}
}
- 효과
- 성능 개선: Observable.EveryValueChanged()는 해당 속성 값이 변경될 때만 이벤트를 발생시킵니다. 따라서 속성의 값이 변하지 않는 경우에는 불필요한 호출을 막아 성능을 개선할 수 있습니다. Update문으로 처리할 경우 프레임마다 매번 카메라 위치를 확인하여 회전을 업데이트해야 합니다. 하지만 Observable.EveryValueChanged()를 사용하면 값이 변할 때만 이벤트를 발생시키기 때문에 성능상 이점이 있습니다.
- 코드 가독성: Observable.EveryValueChanged()를 사용하면 변화하는 값에 대한 감시를 명시적으로 표현할 수 있습니다. 코드가 더 명확해지고 가독성이 향상됩니다. "카메라의 위치를 감시하고 위치가 변경될 때만 이벤트를 발생시킨다"라는 의도가 코드에 명시적으로 드러나기 때문에 이해하기 쉽습니다.
- 유지보수성: Observable.EveryValueChanged()를 사용하면 감시하고자 하는 속성이나 값이 추가되거나 변경될 때 코드를 수정하기가 더욱 용이합니다. 코드가 명시적이고 모듈화되어 있기 때문에 유지보수가 간편해집니다.
요약
- "UniRx 전용"해서 사용해야 되는 시대는 사라졌습니다.
- 현대는 UniRx 이상에 편리한 라이브러리와 언어 기능이 갖추어져 있습니다.
- 특히 async/await+UniTask는 매우 강력하며 UniRx 대신 사용할 수 있는 패턴이 많습니다.
- 그러나 한편으로 UniRx의 용도가 완전히 없어진 것은 아니다.
- 단순한 Observer 패턴의 구현 라이브러리로 사용해도 편리
- ReactiveProperty는 특히 편리성이 높고 현역에서 사용할 수 있습니다.
- UniRx를 너무 과신하지 말자
- 사용 장소의 판별은 중요
- async/await+UniTask쪽이 깨끗하게 쓸 수 있는 패턴도 있다
'[Unity] 게임 개발' 카테고리의 다른 글
[Unity] 특정 Component 삭제법. (0) | 2022.07.18 |
---|---|
[Unity] Unity 3d Shader 외곽선 처리 코드 (0) | 2021.07.03 |
[Unity] 성능 비교 정리 (0) | 2021.05.31 |
[Unity] 모바일 2D 액션 게임 'PencilHero' 출시. (0) | 2021.04.06 |
[Unity] ios 빌드 시 UI 터치 제한 기능 (0) | 2021.03.14 |