이제 함정이 설치되는 통로를 만들어보자
우선 액터를 하나 만들어준다.
//액터 헤더
class USkyLightComponent;
UCLASS()
class TALES_API ADungeonPath : public AActor
{
GENERATED_BODY()
public:
ADungeonPath();
void InitWallMesh();
virtual void Tick(float DeltaTime) override;
protected:
virtual void BeginPlay() override;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
TArray<UStaticMeshComponent*> WallMesh;
UPROPERTY(VisibleAnywhere)
USkyLightComponent* SkyLight;
private:
};
// 액터 cpp
ADungeonPath::ADungeonPath()
{
PrimaryActorTick.bCanEverTick = true;
InitWallMesh();
}
void ADungeonPath::InitWallMesh()
{
WallMesh.Add(CreateDefaultSubobject<UStaticMeshComponent>(TEXT("FloorMeshComponent")));
WallMesh.Add(CreateDefaultSubobject<UStaticMeshComponent>(TEXT("LeftMeshComponent")));
WallMesh.Add(CreateDefaultSubobject<UStaticMeshComponent>(TEXT("RightrMeshComponent")));
WallMesh.Add(CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ForwardMeshComponent")));
WallMesh.Add(CreateDefaultSubobject<UStaticMeshComponent>(TEXT("BackWardMeshComponent")));
WallMesh.Add(CreateDefaultSubobject<UStaticMeshComponent>(TEXT("CeilMeshComponent")));
RootComponent = WallMesh[0];
for (int i = 1; i < WallMesh.Num(); i++)
{
WallMesh[i]->SetupAttachment(RootComponent);
}
SkyLight = CreateDefaultSubobject<USkyLightComponent>(TEXT("SkyLightComponent"));
SkyLight->SetupAttachment(RootComponent);
}
// Called when the game starts or when spawned
void ADungeonPath::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void ADungeonPath::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
SkyLight를 넣어서 밝기를 조절해줬다.
(완성후 뺄 예정)
일단 하드코딩하기전에 staticmesh를 배치해서 원하는대로 되는지 확인해보자
됐다.
이제 스태틱메시와 마테리얼을 제외하고 좌표와 크기같은건 전부 cpp에서 작업해주자.
왜 이렇게 작업을 하냐면
바닥을 이렇게 격자공간으로 나눠주고
해당 공간마다 함정이 배치될지 말지를 랜덤하게 정해주고 싶기때문이다.
(벽에는 안만들거임)
이렇게 하려고 좀 봐봤는데
나는 루트컴포넌트의 스케일을 10,10,0.5로 했었는데
이게 다른 자식 요소에도 전부 적용이 된다.
따라서 차라리 BeginPlay같은곳에서 액터의 x,y크기를 구한다음 해당 크기에 맞춰서 활성화 시키는 방식이 더 올바른듯하다.
그래서 찾은 getactorbounds
참고로
BoxExtent에서 절반크기를 주기때문에 *2를 해야 진짜 크기를 알수있다.
되긴했는데 뭔가 이상하다.
높이도 안맞고 액터 자체의 크기를 가져왔기때문에 바닥보다 살짝 넓은 범위까지 쓰려한다.
그래서 좀 더 찾아보니
컴포넌트의 크기를 가져오는 함수를 찾았다.
근데 원하는대로 동작을 안해서 더 찾아보니
WallMesh[0]->Bounds.BoxExtent;
이렇게 하면 된다는걸 알았다.
void ADungeonPath::SpawnTraps()
{
// 액터의 바운드를 구합니다.
FVector Origin, BoxExtent;
BoxExtent=WallMesh[0]->Bounds.BoxExtent;
Origin = WallMesh[0]->Bounds.Origin;
// 바운드 최소/최대 좌표 계산 (가정: 액터가 월드 축에 맞게 정렬되어 있다고 가정)
FVector Min = Origin - BoxExtent;
// (여기서는 Min과 Origin의 Z값을 바닥 높이로 사용할 수 있습니다.)
// Trap 액터의 크기 (사전에 정의된 값 또는 다른 방법으로 얻은 값)
const float TrapWidth = 250.f; // 예시 값
const float TrapDepth = 250.f; // 예시 값
// 통로(바닥)의 전체 크기 (X, Y)
float TotalWidth = BoxExtent.X * 2.0f;
float TotalDepth = BoxExtent.Y * 2.0f;
// 완전히 맞아떨어지는 셀의 개수 (남는 영역은 무시)
int32 NumCellsX = FMath::FloorToInt(TotalWidth / TrapWidth);
int32 NumCellsY = FMath::FloorToInt(TotalDepth / TrapDepth);
// 배치 확률 (원하는 확률로 설정, 예: 50% 확률)
const float SpawnProbability = 0.5f;
// Trap 액터 클래스들을 변수로 보관 (블루프린트에서 설정하거나 코드에서 초기화)
// 예: TrapActorTypeA, TrapActorTypeB
for (int32 i = 0; i < NumCellsX; ++i)
{
for (int32 j = 0; j < NumCellsY; ++j)
{
// 각 셀의 중심 위치 계산
float CellCenterX = Min.X + TrapWidth * (i + 0.5f);
float CellCenterY = Min.Y + TrapDepth * (j + 0.5f);
// 바닥에 배치하므로 Z는 바닥 높이(예: Origin.Z)로 설정
FVector SpawnLocation(CellCenterX, CellCenterY, Origin.Z);
// 랜덤으로 이 셀에 Trap을 배치할지 결정
if (FMath::FRand() < SpawnProbability)
{
if (TrapActorA && TrapActorB)
{
// 랜덤으로 두 트랩 중 하나 선택
TSubclassOf<ATrap> ChosenTrapClass = FMath::RandBool() ? TrapActorA->GetClass() : TrapActorB->GetClass();
// 원하는 위치와 회전값으로 스폰 (SpawnLocation은 계산된 위치)
FRotator SpawnRotation = FRotator::ZeroRotator;
GetWorld()->SpawnActor<ATrap>(ChosenTrapClass, SpawnLocation, SpawnRotation);
}
}
}
}
}
그래서 이렇게 작성하고 실행했는데
발판이 안나와서 한참을 헤맸다.
근데 높이가 딱맞게 설정되어버려서
이렇게 바닥 블럭안에 있는 상태였다.
그래서 스폰할 Z위치를 살짝 수정했다.
FVector SpawnLocation(CellCenterX, CellCenterY, Origin.Z+BoxExtent.Z);
이렇게 수정했다.
바닥과 딱붙어서 반짝거린다. 조금만 위로 올려주면
완성이다.
이제 확률만 조금 조정해주자.
void ADungeonPath::SpawnTraps()
{
// 액터의 바운드를 구합니다.
FVector Origin, BoxExtent;
BoxExtent=WallMesh[0]->Bounds.BoxExtent;
Origin = WallMesh[0]->Bounds.Origin;
// 바운드 최소/최대 좌표 계산 (가정: 액터가 월드 축에 맞게 정렬되어 있다고 가정)
FVector Min = Origin - BoxExtent;
// (여기서는 Min과 Origin의 Z값을 바닥 높이로 사용할 수 있습니다.)
// Trap 액터의 크기 (사전에 정의된 값 또는 다른 방법으로 얻은 값)
const float TrapWidth = 250.f; // 예시 값
const float TrapDepth = 250.f; // 예시 값
// 통로(바닥)의 전체 크기 (X, Y)
float TotalWidth = BoxExtent.X * 2.0f;
float TotalDepth = BoxExtent.Y * 2.0f;
// 완전히 맞아떨어지는 셀의 개수 (남는 영역은 무시)
int32 NumCellsX = FMath::FloorToInt(TotalWidth / TrapWidth);
int32 NumCellsY = FMath::FloorToInt(TotalDepth / TrapDepth);
// 배치 확률 (원하는 확률로 설정, 예: 50% 확률)
const float SpawnProbability = 0.1f;
// Trap 액터 클래스들을 변수로 보관 (블루프린트에서 설정하거나 코드에서 초기화)
// 예: TrapActorTypeA, TrapActorTypeB
for (int32 i = 0; i < NumCellsX; ++i)
{
for (int32 j = 0; j < NumCellsY; ++j)
{
// 각 셀의 중심 위치 계산
float CellCenterX = Min.X + TrapWidth * (i + 0.5f);
float CellCenterY = Min.Y + TrapDepth * (j + 0.5f);
// 바닥에 배치하므로 Z는 바닥 높이(예: Origin.Z)로 설정
FVector SpawnLocation(CellCenterX, CellCenterY, Origin.Z+BoxExtent.Z+1.f);
// 랜덤으로 이 셀에 Trap을 배치할지 결정
if (FMath::FRand() < SpawnProbability)
{
if (TrapActorA && TrapActorB)
{
// 랜덤으로 두 트랩 중 하나 선택
TSubclassOf<ATrap> ChosenTrapClass = FMath::RandBool() ? TrapActorA->GetClass() : TrapActorB->GetClass();
// 원하는 위치와 회전값으로 스폰 (SpawnLocation은 계산된 위치)
FRotator SpawnRotation = FRotator::ZeroRotator;
GetWorld()->SpawnActor<ATrap>(ChosenTrapClass, SpawnLocation, SpawnRotation);
}
}
}
}
}
일단 10%로 작성했는데 이수치는 나중에 다시 조정해주자.
결과
참고로 함수는 BeginPlay에서 시작해주어야한다.
'IT > UE5' 카테고리의 다른 글
[UE5] 체력UI 구현 (0) | 2025.03.23 |
---|---|
[UE5] 달리기->대시 구현 및 체력 구현 (0) | 2025.03.22 |
[UE5] 튕겨나는 발판 수정 [튕겨나는 발판 - 4] (0) | 2025.03.22 |
[UE5] 착지 대쉬 구현하기 (0) | 2025.03.22 |
[UE5] 스턴상태일때 좌우연타시 스턴시간 감소 구현 [튕겨나는 발판 - 3] (0) | 2025.03.21 |