📅 2025-08-20
게임 기획
-> 뱀서라이크 "드래곤 서바이벌"
게임 핵심 목표
이번 프로젝트에서 AI / 몬스터 파트를 맡아 구현했다.
오브젝트 풀링
빈번한 몬스터 객체 생성/파괴로 인한 성능 저하와 메모리 단편화 방지
활성화 풀, 비활성화 풀로 객체를 관리
스폰 타이머
웨이브별로 다른 스폰 타이머를 실행하도록 웨이브 데이터를 테이블로 관리
스폰 위치 탐색
플레이어를 중심으로 최소~최대 반경 사이에서 무작위로 지점을 선택하여 네비 메시 위의 유효한 위치인지 확인
캐릭터 데이터 랜덤 적용
캐릭터 활성화 시 해당 웨이브에 맞는 몬스터데이터 에셋을 랜덤 적용하여 같은 객체를 재사용 하더라도 다른 타입의 몬스터로 활성화할 수 있다.
오브젝트 풀링을 위해 몬스터 객체를 Spawn, Destroy하지 않고 비활성화 / 활성화로 관리했다.
플레이어 캐릭터와 공통적인 로직은 부모 클래스인 베이스 캐릭터에서 구현하고 AI 캐릭터에서 추가로 필요한 로직은 오버라이딩하여 구현했다.
비활성화
사용한 AI 컨트롤러 저장 후 연결 해제
활성화
사용하던 컨트롤러가 있다면 재사용
메시 스케일을 랜덤 적용하고 그 스케일에 맞게 캡슐 컴포넌트 크기 조정
OnDeathCleanup()
사망 어빌리티 종료시 호출되는 함수
스포너가 구독하여 해당 AI캐릭터를 비활성화 풀로 반환
AI 캐릭터가 활성화 될 때 컨트롤러가 빙의하고 이 때 블랙보드 데이터를 초기화하고 몬스터별로 다른 비해비어 트리를 실행한다.
빙의 해제 시 비해비어 트리를 종료한다.
단계별 몬스터를 구현하여 웨이브가 넘어갈 수록 난이도를 높이고 마지막에 보스전을 할 수 있도록했다.
일반 몬스터
몰려오는 몬스터

엘리트 몬스터
총 5종류로 다 다른 행동패턴과 공격 스킬을 갖는다. 높은 스탯을 갖고 많은 경험치 젬 드랍한다.
몬스터 채널을 만들어 엘리트 몬스터가 일반 몬스터를 넘어서 플레이어에게 바로 갈 수 있도록 구현했다.

보스 몬스터
최종 보스로 아주 높은 스탯을 갖고 6개의 범위별 공격을 하며 플레이어가 일정 거리 멀어지면 그 위치로 순간 이동을 할 수 있다.

원시 포인터의 위험성
수동으로 해제하지 않으면 메모리 누수 발생
이중 삭제나 삭제된 메모리에 접근하면 크래시 발생
스마트 포인터
특정 조건이 맞으면(스코프에서 벗어나거나 참조 카운트가 0이거나 등) 메모리 자동 해제
2-1. 공유 소유권
2-2. 참조 카운팅 시스템의 내부 구조
이 객체를 참조하는 포인터가 몇개인지 세다가 0이되면 메모리를 해제한다.
template<typename T>
class TSharedPtr
{
private:
T* ObjectPtr; // 실제 객체
FReferenceController* RefController; // 참조 카운트와 제어 정보
};
FReferenceController 안에 들어 있는 값SharedRefCount : 강한 참조 (TSharedPtr)가 몇 개인지WeakRefCount : 약한 참조 (TWeakPtr)가 몇 개인지처음 생성했을때는 각각 1씩 올라감
복사하면 강한 참조만 1씩 올라감
참조를 해제하여 강한참조 카운트가 0이되면 약한참조 카운트도 즉시 0이되어 메모리가 해제된다.
2-3. 게임에서 사용 예시
퀘스트와 같이 여러 매니저나 시스템에서 같은 데이터를 공유하야 하는 객체를 복사해서 받는게 아니라 쉐어드 포인터로 공유하여 메모리를 절약하고 동기화를 용이하게 한다.
2-4. TSharedRef vs TSharedPtr
TSharedPtr: nullptr일 수 있다. 사용할 때 항상 유효성 체크가 필요하다.
TSharedRef: 항상 생성 시 초기화해야하고 비어있을 수 없다. null 체크가 필요없다. 반드시 있어야하는 객체를 다룰 때 사용한다.
3-1. 순환 참조
서로 강한 참조로만 가리키고 있으면 참조 카운트가 항상 0보다 크기 때문에 메모리가 해제되지 않는다. 스코프를 벗어나 객체가 사라지면 객체에 접근할 방법은 사라지지만 메모리가 해제되지 않아 메모리 누수가 발생한다.
3-2. 순환 참조를 해결하는 방법
한 쪽을 약한 참조로 바꿔주면 약한 참조는 참조 카운트에 포함되지 않아 순환을 끊을 수 있다.
3-3. Pin()
약한 참조는 객체에 바로 접근할 수 없다(컴파일 에러 ). Pin()을 사용하여 약한 참조를 강한 참조로 바꾸어 접근해야한다.
4-1. 독점 소유권
한 포인터만 소유할 수 있다. 복사하면 컴파일 에러가 발생한다. 소유권 이동(Move)은 가능하다.
메모리 효율성이 뛰어나고 소유권이 명확하다. 자동 메모리 해제가 확실하다.
RAII: 객체의 생명주기 = 자원의 생명주기
4-2. 게임에서 사용 예시
텍스처, 사운드 매니저에서만 독점 관리할 때 사용.
클라이언트별로 네트워크 연결 객체 독점 관리.
TUniquePtrTSharedPtrTSharedPtrTSharedRefTWeakPtrTSharedPtr출처: 스파르타코딩 내일배움캠프