프로젝트 명 : Snow Man (유튜브 영상 바로가기)
- 개발 기간 : 2026.02.05 ~ 2026.02.10 (총 5일)
- 참여 인원 : 1명 (개인)
- 게임 장르 : 아케이드 액션
- 플랫폼 : PC Windows
- 계속해서 등장하는 적들을 눈덩이로 만들어 처치하고, 최대한 많은 점수 획득하기. 한 눈덩이로 한 번에 여러 적을 처치할 경우 보너스 점수 획득해야 한다.
- 시간이 지나면 아이템이 등장하며, 아이템을 획득하며 최대한 많은 점수를 획득하기.
[ 프로젝트 구조 설명 ]
1. Engine프로젝트 -> 게임을 구동하는 프레임 워크. DLL파일을 뽑아낼 수 있도록 처리(RTTI 처리)
2. Game프로젝트 -> 실제 게임 콘텐츠를 담당.
- Engine 관련 핵심 루프.
- Engine코드에서는 Input과 Level의 Tick호출이 이루어집니다.
- Engine으로부터 Tick호출을 물려 받은 Level에서는 Actor와 CollisionSystem의 Tick을 호출.
- Actor의 Tick에서는 Component들을 관리할 수 있도록 Component의 Tick을 호출.
- Game 프로젝트 구조
- 기본적으로 Engine에서 최상위 개념으로 루프가 돌고 있는 Level, Actor, Component등을 상속 받아서 로직이 돌고 있는 구조 입니다.
- 게임 월드에 존재하는 ‘객체’의 기본 단위인 Actor를 상속 받는 Player와 Enemy가 있으며, Tick에서 행동이 업데이트가 됩니다. 이러한 Player와 Enemy에는 Component를 붙일 수 있습니다.
- Component의 경우 Actor에 붙일 수 있는 기능에 대한 단위를 나타내며, 이동(MoveComponent), 충돌(BoxCollider), 공격(AttackComponent)을 소유하게 될 경우 각 기능에 대한 처리가 가능해지도록 구현하였습니다.
- 특히, BoxCollider의 경우 소유하게 되는 시점에 CollisionSystem에 등록(Register)되어, 충돌에 대한 모든 처리는 이 CollisionSystem에서 이루어지게 됩니다. 이 부분에 대한 자세한 사항은 아래 핵심 기술 구현에서 말씀드리겠습니다.
[핵심 기술 구현]
- 주요 기능 정리 :
- 1. Collision System,
- 2. Component,
- 3. 눈덩이
- 4. Enemy AI
- 5. 아이템
1. Collision System
- 기존에는 Actor들의 위치 계산으로만 충돌 계산을 하고 있었으나, 충돌 개념만을 담당하는 역할을 수행할 관리자가 필요하다고 생각하였습니다. 또한, 충돌 개념만을 담당하기 위해서는 기존에 엔진에 존재하지 않았던 Collider라는 개념이 필요하게 되었고, BoxCollider를 추가적으로 제작하도록 하였습니다.
- 즉, 이 Collision System은 다른 정보는 모르며 오로지 BoxCollider정보들만 넘겨받아 충돌 처리만을 담당합니다. 참고로 충돌은 AABB충돌을 적용하였습니다.
- 이전 프레임에서 충돌된 BoxCollider들과 현재 프레임에 충돌된 BoxCollider들간의 차이를 통해 Enter, Stay, Exit을 수행하도록 구현.

2. Component
- 1번에서 CollisionSytem을 위해 BoxCollider라는 개념이 추가됨으로써, 또 다른 기능들이 추가 될 것을 고려하여 Component라는 개념의 도입을 생각하게 되었습니다.
- 유니티 엔진을 다뤄보았던 경험 들을 토대로 Component가 어떻게 관리되면 좋을지 등을 생각해보며 작업을 진행하게 되었습니다. 유니티 엔진에서는 GameObject가 Component를 들고있던 개념이었기에, 이를 활용하여 현재 프로젝트에서 사용하는 Actor라는 객체가 Component를 들고 있도록 처리하였습니다.
- Tick의 흐름은 다음과 같습니다.
- Engine > Level > Actor > Component로 흐름을 전이시키며 동작하게 됩니다.
- Actor는 Component들을 관리하는 역할을 하며 이는 Is – A 관계로 설정하였습니다. ( ex ) Actor의 멤버 변수 : vector<Component*> components; )
- Actor생성 시에 new AnotherComponent() 를 AddComponent함수에 넣어주게 되면 바로 해당 컴포넌트가 사용가능한 구조로 되어 있습니다.
3. 눈덩이
- 눈덩이 연출 : 눈덩이가 점점 커지는 듯한 연출 구현. 각 눈덩이마다 체력이 존재하고 해당 체력을 깎을 때마다 다음 눈덩이로 전환되도록 구현하였습니다.

- 발사 메커니즘 : 최대 크기의 눈덩이가 되었을 때, 마지막 체력을 깎을 경우 해당 눈덩이를 발사.
4. Enemy AI
- 플레이어의 x좌표까지 이동한 후, y좌표를 기준으로 플레이어보다 아래에 있다면 점프 실행. (Component를 제작하였기에 몬스터 또한 MoveComponent를 추가하도록 진행. 플레이어와 Enemy의 점프 로직에 대한 중복 코드 제거)
5. 아이템 구현

- 맵 곳곳에 생기는 아이템을 구현하였습니다. (S : 스피드업, P : 파워업, R : 사거리업)
[문제 해결]
1. 충돌 처리 시스템
문제 발생) 처음에는 단순 충돌이라 생각하여 충돌 했는지에 대한 여부만 처리하면 되지 않을까 싶어, 충돌 처리 로직을 제작할 때 Exit 되는 것을 고려하지 않고 현재 들고 있는 Colliders에 대해서만 충돌 처리를 진행하도록 제작하였습니다. 하지만 계속해서 게임을 제작해나가다 보니 이전 프레임에는 충돌을 했지만 이후 프레임에는 충돌하지 않았을 경우에 대해서도 이벤트를 발동 시켜야 하는 상황이 발생하였습니다.
해결 방법 고민) 반드시 처리되어야 하는 절대적인 기준은 이전 프레임에 충돌했는데 현재 프레임에 대해서는 충돌했는지에 대한 여부가 필요했습니다. 이를 토대로 다시 시스템을 제작하게 되었습니다.
처리 과정) 이전 프레임과 현재 프레임 간에 충돌 여부의 차이점을 알아야 했기에 각각을 담당하는 unordered_set 자료구조를 사용하여 두 가지의 변수를 추가하였습니다. 루프가 돌고있는 Tick함수에서 현재 충돌을 하고 있는 박스들에 대해서는 currentPairs라는 변수에 추가시켜주었습니다. 그리고 해당 틱에서 previousPairs와 currentPairs랑 차이가 있다면 그 차이에 대한 알맞은 이벤트를 호출해주게 되고 모든 차이에 대해 처리가 완료되면 마지막에 previousPairs의 정보를 currentPairs의 정보로 스왑하여 갱신하도록 처리해주었습니다.
해결 결과)
- previousPairs에 있지만 currentPairs에 없는 경우 -> Exit.
- previousPairs에 있지만 currentParis에 있는 경우 -> Stay.
- previousPairs에 없지만 currentParis에 있는 경우 -> Enter.
-> 이렇게 명확하게 구분해주어 이벤트를 호출해주니 전체적인 충돌 처리가 알맞게 돌아가는 것을 확인할 수 있었습니다.
2. Enemy <-> Snow 전환(댕글링 포인터 문제)
문제 발생) 기존에는 Enemy와 Snow간의 전환에 대해 Enemy는 상태관리로, Snow는 Enemy* 형식으로 진행하면 될 것이라 생각하여 이렇게 관리를 진행하였습니다. 처음에는 문제 없이 잘 진행되다가 어느 순간부터 invalid 에러가 터지게 된 것을 확인하게 되었습니다. 확인해보니 눈덩이가 소유하고 있는 Enemy*에 대해서 Kill을 진행해주었지만, Snow가 아직 소유하고 있는 Enemy*에 대해서 무언가를 호출하려고 하는 것이 문제였습니다.
해결 방법 고민) 우선, 간단하게 예외처리를 진행해줄 수 있었습니다. destroyRequested가 true인 경우에는 무언가를 하지 않는다. enemy에 대해서는 nullptr는 아니었기 때문에 nullptr 예외처리를 할 수는 없었으며, destroyRequested가 true인 경우에는 무언가를 하지 않는다는 식의 예외처리로 진행하려 했었습니다. 하지만, 이렇게 될 경우 여러 곳에서 예외처리를 진행해줘야 하는 상황이 발생할 것이라 생각하였습니다. 또한, CollisionSystem에서는 해당 프레임에 아직 충돌체가 남아있기도 하였으며, 이는 근본적인 문제를 해결하기 위한 과정이 시작되었습니다.
해결 방법 및 결과)
해결한 방식은 다음과 같습니다. Enemy는 상태로, Snow는 Enemy를 소유하고 있도록 구현한 것이 잘못되었다는 것을 깨달았습니다. Snow도 Enemy*를 소유하도록 구현했었으니, Enemy도 Snow를 소유할 수 있도록 Snow* 를 가지게 하였습니다. 이제부터 Enemy입장에서 공격을 한 대라도 맞는 순간 Enemy와 Snow간의 관계가 성립되며, 반대로 Kill이 되었을 때는 서로의 관계를 끊어냄으로써, 더 이상 호출할 수 없는 상태가 된 것을 확인할 수 있었습니다.
No Comment! Be the first one.