Showing

[언리얼] Unreal C++로 Player를 쫓아오는 Enemy 구현 본문

Unreal

[언리얼] Unreal C++로 Player를 쫓아오는 Enemy 구현

RabbitCode 2023. 10. 19. 16:31

구현 사항

 -1- 플레이어가 상하좌우로 움직인다

 -2- emeny는 플레이어를 쫓아다니면서 총알을 발사한다.

위의 구현사항에서 emeny는 실시간으로 변경하는 플레이어의 방향으로 움직이고, enemy의 정면은 플레이어를 향하여 총알을 발사하면 플레이어에게 가도록 작성하여야 한다.

 

따라서,  enemy의 위치값 dir과 방향값 rot이 tick 함수 안에서 적절하게 변경되어야 한다.

 

 

구현 코드

EnemyActor.cpp

for문 TActorIterator<APlayerPawn>을 통해 target을 찾는 것은 비용이 드므로 Tick이 아닌 Begin에서 단 한번 해주도록 한다.

Tick에서는 begin에서 구한 target의 Position과 Rotation을 이용해 enemy가 취해야 할 dir과 rot 벡터를 구해준다.

move에서 최종적으로 dir과 for을 enemy 액터에게 적용시켜준다.

#include "EnemyActor.h"
#include "Components/BoxComponent.h"
#include "Components/StaticMeshComponent.h"
#include "EngineUtils.h"
#include "PlayerPawn.h"
#include "Components/ArrowComponent.h"
#include "Bullet.h"
#include "Kismet/GameplayStatics.h"
#include "Kismet/KismetMathLibrary.h"

AEnemyActor::AEnemyActor()
{
 	PrimaryActorTick.bCanEverTick = true;

	boxComp = CreateDefaultSubobject<UBoxComponent>(TEXT("My Box Component"));
	SetRootComponent(boxComp);

	meshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("My Static Mesh"));
	meshComp->SetupAttachment(boxComp);

	FVector boxSize = FVector(50.0f, 50.0f, 50.0f);
	boxComp->SetBoxExtent(boxSize);

	firePosition = CreateDefaultSubobject<UArrowComponent>(TEXT("FirePos"));
	firePosition->SetupAttachment(meshComp);

	fireTimerTime = 0;
	fireCoolTime = 2.0f;
	fireReady = false;
}

void AEnemyActor::BeginPlay()
{
	Super::BeginPlay();

	// 플레이 중에 실시간으로 플레이어 폰을 인식하려면 월드 공간에 배치된 전체 액터 중에서 플레이어를 검색
	for (TActorIterator<APlayerPawn> player(GetWorld()); player; ++player) {
		if (player->GetName().Contains(TEXT("BP_PlayerPawn"))) {
			target = *player;
		}
	}
} 

// Called every frame
void AEnemyActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	// setting dir and rot
	dir = target->GetActorLocation() - GetActorLocation();
	dir.Normalize();
	rot = UKismetMathLibrary::FindLookAtRotation(this->GetActorLocation(), target->GetActorLocation());

	Move(DeltaTime);

	if (fireReady == false) {
		FireCoolTimer(fireCoolTime, DeltaTime);
	}
	else {
		Fire();
	}
}

void AEnemyActor::Fire()
{
		ABullet* bullet = GetWorld()->SpawnActor<ABullet>(bulletFactory, firePosition->GetComponentLocation()
			, firePosition->GetComponentRotation());
		fireReady = false;
		// fireSound : 월드(어느 월드), 소리낼 사운드, 소리 날 위치
		UGameplayStatics::PlaySoundAtLocation(GetWorld(), fireSound, firePosition->GetComponentLocation());
}

void AEnemyActor::FireCoolTimer(float cooltime, float deltaTime)
{
	if (fireTimerTime < cooltime) {
		fireTimerTime += deltaTime;
	}
	else
	{
		fireTimerTime = 0;
		fireReady = true;
	}
}

void AEnemyActor::Move(float deltaTime)
{
	FVector newLocation = GetActorLocation() + dir * moveSpeed * deltaTime;
	SetActorLocation(newLocation);
	SetActorRotation(rot);
}

EnemyActor.h

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

#pragma once

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

UCLASS()
class GIT_TEMP_API AEnemyActor : public AActor
{
	GENERATED_BODY()
	
public:	
	AEnemyActor();
	void FireCoolTimer(float cooltime, float deltaTime);
	void Move(float DeltaTime);
protected:
	virtual void BeginPlay() override;

public:	
	virtual void Tick(float DeltaTime) override;

	UPROPERTY(EditAnywhere)
	class UBoxComponent* boxComp;

	UPROPERTY(EditAnywhere)
	class UStaticMeshComponent* meshComp;

	UPROPERTY(EditAnywhere)
	int32 traceRate = 50;

	UPROPERTY(EditAnywhere)
	float moveSpeed = 100;

	UPROPERTY(EditAnywhere)
	class UArrowComponent* firePosition;

	UPROPERTY(EditAnywhere)
	TSubclassOf<class ABullet> bulletFactory;

	UPROPERTY(EditAnywhere)
	class USoundBase* fireSound;

	UPROPERTY(EditAnyWhere)
	float fireCoolTime = 2.0f;

	UPROPERTY(EditAnywhere)
	class APlayerPawn* target;

private:
	FVector dir;
	FRotator rot;
	void Fire();
	float fireTimerTime; // 타이머의 시간
	bool fireReady; // 타이머 도달 여부 
};

 

필요했던 문법

Iterator

월드 공간에 배치된 모든 액터를 검색하려면, 월드의 모든 액터를 대상으로 처음부터 끝까지 순환해서 조사해보면서 조사중인 액터가 플레이어 폰 클래스인지 알아내야 한다.
월드 공간에 생성되어 있는 모든 액터를 검색하는 반복문은 Iterator

 

		for (TActorIterator<APlayerPawn> player(GetWorld()); player; ++player) {
			if (player->GetName().Contains(TEXT("BP_PlayerPawn"))) {
				dir = player->GetActorLocation() - GetActorLocation();
				dir.Normalize();
			}
		}
		dir = GetActorForwardVector();

UKismetMathLibrary::FindLookAtRotation

Find Look at Rotation in C++ - Development / Programming & Scripting - Epic Developer Community Forums (unrealengine.com) 해당 질문과 그에 달린 답변들을 참고하였다.

 

 

기타 참고 사이트

유니티 개발자를 위한 언리얼 엔진 4 | 언리얼 엔진 문서 (unrealengine.com)

 

유니티 개발자를 위한 언리얼 엔진 4

유니티 사용자분들의 빠른 적응을 도울 수 있도록 유니티 지식을 UE4 로 옮겨봅니다.

docs.unrealengine.com

UKismetMathLibrary::FindLookAtRotation | Unreal Engine Documentation 

 

UKismetMathLibrary::FindLookAtRotation

Find a rotation for an object at Start location to point at Target location.

docs.unrealengine.com