TIL: Unreal C++ 27일차

박춘팔·6일 전

언리얼 TIL

목록 보기
26/29

누적 학습 시간 : 260시간 34분

📅 2026-05-08

무한성이라고 불리던 스테이지를 일찍 탈출한 6명이서 사이드 프로젝트를 진행하기로 했다.
순전히 기능 공부를 위함이고 출시계획같은건 당연하게도 없다.

프로젝트 진행기간 : 26.05.11 ~ 26.05.21

주제는 3D 뱀파이어 서바이벌이다.
개발 진행상황은 레포지토리 참조

MVP 스팩을 크게 6가지로 나눴다.
스킬 / 캐릭터 / 적 / 시스템(웨이브, 스포너) / 아이템 / UI
나는 그 중 시스템(웨이브, 스포너)를 담당하게 됐다.

EnemySpawner

마침 해당 사이드 프로젝트를 진행하기 전 EnemySpawner를 만들어봤었다.
해당 코드 첨부

EnemySpawner.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "EnemySpawner.generated.h"

class ACharacter;

UCLASS()
class METAL3D_API AEnemySpawner : public AActor
{
	GENERATED_BODY()

public:
	// Sets default values for this actor's properties
	AEnemySpawner();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:
	UFUNCTION(BlueprintCallable, Category="Spawner")
	void StartSpawning();

	UFUNCTION(BlueprintCallable, Category="Spawner")
	void StopSpawning();

private:
	UPROPERTY(EditAnywhere, Category="Spawner")
	TSubclassOf<ACharacter> EnemyClass;

	UPROPERTY(EditAnywhere, Category="Spawner")
	float SpawnRadius = 1200.f;

	UPROPERTY(EditAnywhere, Category="Spawner")
	float SpawnInterval = 3.f;

	UPROPERTY(EditAnywhere, Category="Spawner")
	int32 MaxEnemyCount = 100;

	UPROPERTY(EditAnywhere, Category="Spawner")
	bool bAutoStart = true;

	UPROPERTY()
	TArray<TObjectPtr<AActor>> SpawnedEnemies;

	FTimerHandle SpawnTimerHandler;

	void SpawnEnemy();

	bool FindSpawnLocation(FVector& OutLocation) const;

	void CleanupInvalidEnemies();
};

EnemySpawner.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "Spawner/EnemySpawner.h"

#include "NavigationSystem.h"
#include "GameFramework/Character.h"
#include "TimerManager.h"
#include "Components/CapsuleComponent.h"
#include "Engine/World.h"

// Sets default values
AEnemySpawner::AEnemySpawner()
{
	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
}

// Called when the game starts or when spawned
void AEnemySpawner::BeginPlay()
{
	Super::BeginPlay();

	if (bAutoStart)
	{
		StartSpawning();
	}
}

void AEnemySpawner::StartSpawning()
{
	if (!GetWorld())
	{
		return;
	}

	GetWorld()->GetTimerManager().SetTimer(
		SpawnTimerHandler,
		this,
		&AEnemySpawner::SpawnEnemy,
		SpawnInterval,
		true,
		0.f
	);
}

void AEnemySpawner::StopSpawning()
{
	if (!GetWorld())
	{
		return;
	}

	GetWorld()->GetTimerManager().ClearTimer(SpawnTimerHandler);
}

void AEnemySpawner::SpawnEnemy()
{
	if (!EnemyClass)
	{
		UE_LOG(LogTemp, Warning, TEXT("Enemy Spawner: EnemyClass not set"));
		return;
	}

	CleanupInvalidEnemies();
	if (SpawnedEnemies.Num() >= MaxEnemyCount)
	{
		return;
	}

	FVector SpawnLocation;
	if (!FindSpawnLocation(SpawnLocation))
	{
		UE_LOG(LogTemp, Warning, TEXT("Enemy Spawner: Failed to find spawn location"));
		return;
	}

	ACharacter* DefaultEnemy = EnemyClass->GetDefaultObject<ACharacter>();
	if (!DefaultEnemy || !DefaultEnemy->GetCapsuleComponent())
	{
		return;
	}

	const float CapsuleHalfHeight = DefaultEnemy->GetCapsuleComponent()->GetScaledCapsuleHalfHeight();
	SpawnLocation.Z += CapsuleHalfHeight;

	const FRotator SpawnRotation = FRotator::ZeroRotator;

	FActorSpawnParameters SpawnParams;

	SpawnParams.Owner = this;
	SpawnParams.SpawnCollisionHandlingOverride =
		ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding;

	ACharacter* SpawnedEnemy = GetWorld()->SpawnActor<ACharacter>(
		EnemyClass,
		SpawnLocation,
		SpawnRotation,
		SpawnParams
	);

	if (SpawnedEnemy)
	{
		SpawnedEnemies.Add(SpawnedEnemy);
	}
}

bool AEnemySpawner::FindSpawnLocation(FVector& OutLocation) const
{
	UNavigationSystemV1* NavSystem = UNavigationSystemV1::GetCurrent(GetWorld());

	if (!NavSystem)
	{
		return false;
	}

	FNavLocation NavLocation;

	const bool bFound = NavSystem->GetRandomReachablePointInRadius(
		GetActorLocation(),
		SpawnRadius,
		NavLocation
	);

	if (!bFound)
	{
		return false;
	}

	OutLocation = NavLocation.Location;
	return true;
}

void AEnemySpawner::CleanupInvalidEnemies()
{
	SpawnedEnemies.RemoveAll([](const TObjectPtr<AActor>& Enemy)
	{
		return !IsValid(Enemy);
	});
}

해당 코드를 복붙 후 수정.. 하고싶지만 우선은 Unreal c++에 익숙해지는게 먼저기 때문에 다시 손으로 따라쳐볼 예정이다. 거기에 달라지는 점들이 몇가지 있어서 추가 수정을 붙일 예정이다.

profile
이것 저것 다해보는 삶

0개의 댓글