티스토리 뷰
금일은 액션 시스템을 어떻게 만들었는 가를 정리해 보고자 한다.
본 프로젝트의 액션 시스템은 다음 구성으로 작성되어 있다.
- BaseActionActor: 액션의 발동을 담당하는 클래스.
- ObjectPool에서 ActionObject 프리팹을 가져와서 각도를 설정한다.
- 이후 아래의 ActionObjectContainer 클래스를 초기화한다.
- ActionObjectContainer: ActionObject의 정물(Pacade)에 해당하는 클래스.
- ActionPositionHandler: ActionObject의 위치를 담당하는 클래스.
- Hold: 사용 캐릭터의 하위 오브젝트로 적용. 캐릭터가 이동하면 ActionObject도 함께 움직인다.
- Projectile: 투사체로 발사. 발사 방향은 사용 캐릭터로 부터의 연장선으로 고정.
- Spread: 사용 캐릭터 일정 거리 밖에서 랜덤 위치
- Distance: 사용 캐릭터의 전방에 사용. Hold와의 다른 점은 캐릭터가 이동해도 Action은 이동하지 않는다.
- ActionAnimationHandler: 애니메이션 이벤트를 이용해서 충돌 판정 및 ObjectPool 반환을 하는 클래스.
- 추가로, 초기화할 때에 애니메이션과 스프라이트를 변형한다.
- ActionCollisionHandler: 충돌 판정 담당. 충돌 정보를 가지고 있다가 Effecter를 이용해서 액션의 효과 적용.
- ActionEffecter: 효과 적용 담당 클래스. CollisionHandler에서 배열로 가지고 있다.
- ActionPositionHandler: ActionObject의 위치를 담당하는 클래스.
이렇게 구성한 이유는 '오브젝트 풀의 난점'때문이다.
오브젝트 풀은 기본적으로 '오브젝트의 생성, 파괴에 의해서 발생하는 오버헤드'를 줄이기 위해서 사용하는 디자인 패턴으로, 자주 생성, 파괴되는 오브젝트들을 미리 생성해 두고 활성화 상태와 초기화를 이용, 더욱 적은 비용을 이용해서 오브젝트의 생성/제거를 구현하는 개념이다.
즉, 오브젝트 풀으로 구성된 것들은 기본적으로 '다수가 이미 만들어져 있는 상태'가 유지된다는 것이다.
그렇다면, '쓸 지도 모른다'는 이유로 오브젝트 풀에 넣어놓으면 어떻게 될까.
자연히 이 '미사용 오브젝트'들이 하이어라키 상에 쭈우욱 하고 생성된다는 뜻이며, 그들이 '쓰일지는 모른다'는 문제가 발생할 것이다. 그럼에도 불구하고 캐릭터의 공격에 해당하는 오브젝트는 매우 빈번하게 생성되기 때문에 반드시 오브젝트 풀 안에 넣어야 하는 요소다.
이러한 딜레마를 해결하기 위해서, '오브젝트 풀의 항목을 1개 만 쓰기 위해서' 구성한 것이 위의 구조다.
본 작의 Action의 작동과 외형은 ActionPositionHandler에서 공격체의 사용 방식을, ActionAnimationHandler에서 스프라이트와 애니메이션이, ActionCollisionHandler에서 효과를 적용하는 방식으로 초기화가 되어 있다.
이를 통해서 오브젝트 풀의 1개 항목 만으로 모든 액션을 관리할 수 있게 되는 것.
또한, 이런 방식으로 구성한 것으로 ActionData라는 데이터 클래스 하나 만을 이용해서 액션을 구성하여 작동할 수 있게 되기도 하였다.
[실제로는 이쪽이 메인 목적이고, 상술한 내용은 추가로 구성한 내용이다.]
트러블 슈팅
묶여져야 할 것이 흩어진 한 때
처음에는 그저 상술한 구조 만을 구성해서 작동시켰었다.
그렇다 보니, Spread에서 다수의 ActionObject로 특정한 형태를 이루도록 구성하는 것이 불가능하다는 사실을 깨닫게 되었다. 그래서 ActionContainer와 ActionObject로 프리팹을 나누어서 ActionObject를 ActionContainer 안에 넣어둔 뒤, ActionContainer에 있는 ActionPositionHandler로 위치 변경이 되도록 구성했었다.
그랬더니 유니티 엔진 상에서 프레임 드랍이 어마어마하게 발생하기 시작했었다.
원인은, ActionObject를 가져올 때 마다 GetComponent를 해야 했던 것.
GetComponent 자체가 매우 무거운 연산이다 보니 이 것이 쌓이면서 프레임 드랍이 발생한 것이었다.
그렇기에, 이번에는 콘테이너와 오브젝트를 따로 떼어놓지 않는 방식으로 되돌리고, 그냥 생성 단위를 묶음으로써 생성되는 위치, 각도 등을 같은 값으로 초기화되도록 구성하여 해결하였다.
그럼에도 Effecter는 가비지다.
상술한 구조에서 조금 유심히 살펴보도록 하자.
본 형태로는 CollisionHandler마다 다수의 Effecter를 가지고 있으며, 이 Effecter는 해당 ActionObject가 오브젝트 풀으로 반환되면 가비지로 전락해 버리고 마는 구성이다.
이러한 구성은 가비지 컬렉터에 의한 시스템 부하가 심해진다고 판단, 액션 시스템의 대대적인 리팩토링을 진행하였다.
목표로 한 것은 '한 Action 안에서의 ActionObject들은 모두 같은 Effecter를 공유한다' 였다. 이를 통해서 가비지를 최대한 줄이고자 하였다. 또한, 액션 시스템의 제작 폭을 늘리기 위해서 이전의 Container에 Object를 다수 넣어서 작동하는 알고리즘을 다시 선택하고, ActionContainer와 ActionObject(클래스)를 보관하는 오브젝트 풀을 이용해서 GetComponent 없이 공격 오브젝트를 형성할 수 있도록 구성하였다.
이 사항은 현재 진행 중인 사항이기 때문에 아직 NewAction 시스템으로서 작성만 되어 있는 상황이나, 조만간 프로젝트에 적용하여 이 문제점을 해결할 예정.
사실, NewActionSystem을 적용하기 전에는 끝났다고 보기는 힘든 내용이긴 하지만...
일단 어느 정도 일단락 되었기 때문에, 본 문제 작성한다.
해당 파트를 작업하는 것은 사실상 최적화와의 싸운이었던 거 같다.
사실상 리팩토링을 한 이유가 죄다 최적화이니까...;;
사실 본 파트를 처음 시작했을 때에는 몸 상태가 별로 좋지 않았다.
덕분에 처음에 만들었던 내용은 상당히 난잡했었고, 그걸 1차적으로 리팩토링 해서 GetComponent 이슈가 발생하고, 그걸 2차로 리팩토링 해서 Effecter 이슈를 발견한 거였다.
덕분에 이 시스템을 완성하면서 느낀 감정이 뿌듯하면서도 뭔가 아련한... 그 사이의 무언가였다...
그래도 결과물 만 보면 만족스러운 편인 거 같다.
'스파르타 내일배움캠프 > 최종 프로젝트 리포트' 카테고리의 다른 글
최종 프로젝트 리포트 - 5 [중간 발표] (0) | 2025.01.07 |
---|---|
최종 프로젝트 리포트 - 4 [컴포넌트 기반 오브젝트 풀] (0) | 2025.01.06 |
최종 프로젝트 리포트 - 3 [인벤토리 시스템] (1) | 2024.12.29 |
최종 프로젝트 리포트 - 1 [몬스터 기본 시스템] (1) | 2024.12.08 |
[팀 프로젝트 구상안] Rogue & Slash! (0) | 2024.11.24 |