10. 적 캐릭터 - Elysia-ff/UE4-Custom_Stencil_Tutorial GitHub Wiki
적 캐릭터를 만들 차례다.
AStealthCharacter
를 확장해서 AEnemy
를 생성하고 AAIController
를 확장해서 AEnemyAIController
를 생성한다.
그리고 AEnemyAIController
를 AEnemy
의 기본 컨트롤러로 등록한다.
public:
AEnemy(const FObjectInitializer& ObjectInitializer);
AEnemy::AEnemy(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
PrimaryActorTick.bCanEverTick = false;
AIControllerClass = AEnemyAIController::StaticClass();
}
AEnemy
를 상속받는 BP_Enemy
를 생성하고 BP_Hitman
과 마찬가지로 기본 메시로 세팅한다.
Level에 끌어다 놓으면 잘 나온다.
그런데 시야 밖에 있는 캐릭터도 보이는건 이상하니까 보이지 않게 수정하겠다.
새 마테리얼을 생성해서 BP_Enemy
에 연결한다.
그다음 Material -> Blend Mode를 Translucent
로 변경하고
다음 코드를 추가한다.
(Opacity를 스텐실값의 첫번째 비트가 켜져있으면 100%, 그렇지 않으면 0%로 설정한다.)
실행하면 시야 밖의 캐릭터는 보이지 않는다.
다음은 게임 시작 시 자동으로 적이 생성되게 하겠다.
AActor
를 확장해서 AEnemySpawnManager
를 생성하고 아래 코드를 추가한다.
public:
// Sets default values for this actor's properties
AEnemySpawnManager(const FObjectInitializer& ObjectInitializer);
const TArray<AEnemy*>* GetEnemies() const;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
private:
void SpawnEnemy(const FTransform& SpawnTransform);
public:
UPROPERTY(EditAnywhere, Category = "Spawn Position")
TArray<TWeakObjectPtr<AActor>> SpawnPoses;
private:
UPROPERTY()
UClass* EnemyBlueprint;
UPROPERTY()
TArray<AEnemy*> Enemies;
#include "Character/Enemy.h"
// Sets default values
AEnemySpawnManager::AEnemySpawnManager(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = false;
static ConstructorHelpers::FObjectFinder<UClass> EnemyObjectFinder(TEXT("Blueprint'/Game/Blueprint/Character/BP_Enemy.BP_Enemy_C'"));
check(EnemyObjectFinder.Succeeded());
EnemyBlueprint = EnemyObjectFinder.Object;
}
const TArray<AEnemy*>* AEnemySpawnManager::GetEnemies() const
{
return &Enemies;
}
// Called when the game starts or when spawned
void AEnemySpawnManager::BeginPlay()
{
Super::BeginPlay();
Enemies.Reserve(SpawnPoses.Num());
for (int32 i = 0; i < SpawnPoses.Num(); i++)
{
if (!SpawnPoses[i].IsValid())
{
UE_LOG(LogTemp, Warning, TEXT("Invalid spawn position : %d"), i);
continue;
}
SpawnEnemy(SpawnPoses[i]->GetTransform());
}
}
void AEnemySpawnManager::SpawnEnemy(const FTransform& SpawnTransform)
{
check(EnemyBlueprint);
FActorSpawnParameters Param;
Param.Owner = this;
Param.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
AEnemy* NewEnemy = GetWorld()->SpawnActor<AEnemy>(EnemyBlueprint, SpawnTransform, Param);
check(NewEnemy);
NewEnemy->SpawnDefaultController();
Enemies.Add(NewEnemy);
}
이제 AEnemySpawnManager
를 Level에 끌어다 놓고 빈액터를 임의의 위치에 생성한 후 AEnemySpawnManager
에 연결한다.
플레이하면 액터들의 위치에 적이 스폰된다.
/Character/Enemy.h
// Copyright 2021. Elysia-ff
#pragma once
#include "CoreMinimal.h"
#include "Character/StealthCharacter.h"
#include "Enemy.generated.h"
/**
*
*/
UCLASS()
class STEALTHTUTORIAL_API AEnemy : public AStealthCharacter
{
GENERATED_BODY()
public:
AEnemy(const FObjectInitializer& ObjectInitializer);
};
/Character/Enemy.cpp
// Copyright 2021. Elysia-ff
#include "Enemy.h"
#include "Character/Controller/EnemyAIController.h"
AEnemy::AEnemy(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
PrimaryActorTick.bCanEverTick = false;
AIControllerClass = AEnemyAIController::StaticClass();
}
/Character/Controller/EnemyAIController.h
// Copyright 2021. Elysia-ff
#pragma once
#include "CoreMinimal.h"
#include "AIController.h"
#include "EnemyAIController.generated.h"
/**
*
*/
UCLASS()
class STEALTHTUTORIAL_API AEnemyAIController : public AAIController
{
GENERATED_BODY()
};
/SpawnManager/EnemySpawnManager.h
// Copyright 2021. Elysia-ff
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "EnemySpawnManager.generated.h"
class AEnemy;
UCLASS()
class STEALTHTUTORIAL_API AEnemySpawnManager : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AEnemySpawnManager(const FObjectInitializer& ObjectInitializer);
const TArray<AEnemy*>* GetEnemies() const;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
private:
void SpawnEnemy(const FTransform& SpawnTransform);
public:
UPROPERTY(EditAnywhere, Category = "Spawn Position")
TArray<TWeakObjectPtr<AActor>> SpawnPoses;
private:
UPROPERTY()
UClass* EnemyBlueprint;
UPROPERTY()
TArray<AEnemy*> Enemies;
};
/SpawnManager/EnemySpawnManager.cpp
// Copyright 2021. Elysia-ff
#include "EnemySpawnManager.h"
#include "Character/Enemy.h"
// Sets default values
AEnemySpawnManager::AEnemySpawnManager(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = false;
static ConstructorHelpers::FObjectFinder<UClass> EnemyObjectFinder(TEXT("Blueprint'/Game/Blueprint/Character/BP_Enemy.BP_Enemy_C'"));
check(EnemyObjectFinder.Succeeded());
EnemyBlueprint = EnemyObjectFinder.Object;
}
const TArray<AEnemy*>* AEnemySpawnManager::GetEnemies() const
{
return &Enemies;
}
// Called when the game starts or when spawned
void AEnemySpawnManager::BeginPlay()
{
Super::BeginPlay();
Enemies.Reserve(SpawnPoses.Num());
for (int32 i = 0; i < SpawnPoses.Num(); i++)
{
if (!SpawnPoses[i].IsValid())
{
UE_LOG(LogTemp, Warning, TEXT("Invalid spawn position : %d"), i);
continue;
}
SpawnEnemy(SpawnPoses[i]->GetTransform());
}
}
void AEnemySpawnManager::SpawnEnemy(const FTransform& SpawnTransform)
{
check(EnemyBlueprint);
FActorSpawnParameters Param;
Param.Owner = this;
Param.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
AEnemy* NewEnemy = GetWorld()->SpawnActor<AEnemy>(EnemyBlueprint, SpawnTransform, Param);
check(NewEnemy);
NewEnemy->SpawnDefaultController();
Enemies.Add(NewEnemy);
}