일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 운영체제
- sampling theory
- 유니티
- linear difference equation
- Unity #Indie Game
- Race condition
- AINCAA
- MLFQ
- 게임 개발
- 메카님
- 언리얼엔진
- 게임개발
- MAC
- Security
- dtft
- STCF
- stride
- frequency-domain spectrum analysis
- dirty cow
- DSP
- 유스케이스
- ret2libc
- DP
- pdlc
- 배경 그림
- CTF
- RBAC
- Double free
- Rr
- TSet
- Today
- Total
다양한 기록
[UE Game Framework] #9 Infinity Map 본문
스테이지 기믹 기획
- 스테이지는 플레이어와 NPC가 1:1로 겨루는 장소
- 스테이지는 총 4개의 상태를 가지고 있으며 순서대로 진행
- READY : 플레이어의 입장을 처리하는 단계
- FIGRT : 플레이어와 NPC가 대전하는 단계
- REWARD : 플레이어가 보상을 선택하는 단계
- NEXT : 다음 스테이지로 이동을 처리하는 단계
- 무한히 순환 ...
스테이지 준비 단계
- 스테이지 중앙에 위치한 트리거 볼륨을 준비
- 플레이어가 트리거 볼륨에 진입하면 대전 단계로 이동
스테이지 대전 단계
- 플레이어가 못나가게 스테이지의 모든 문을 닫고 대전할 NPC를 스폰
- NPC가 없어지면 보상 단계로 이동
스테이지 보상 선택 단계
- 정해진 위치의 4개의 상자에서 아이템을 랜덤하게 생성
- 상자 중 하나를 선택하면 다음 스테이지 단계로 이동
다음 스테이지 선택 단계
- 스테이지의 문을 개방
- 문에 설치된 트리거 볼륨을 활용해 통과하는 문에 새로운 스테이지를 스폰
애셋 매니저
- 언리얼 엔진이 제공하는 애셋을 관리하는 싱글톤 클래스
- 엔진이 초기화될 때 제공되며 애셋 정보를 요청해 받을 수 있음
- PrimaryAssetId를 사용해 프로젝트 내 애셋의 주소를 얻어올 수 있음
- PrimaryAssetId는 태그와 이름의 두가지 키 조합으로 구성됨 (PrimaryAssetTag / PrimaryAssetName)
- 특정 태그를 가진 모든 애셋 목록을 가져올 수 있음
랜덤 보상 설정
- 아이템 데이터에 ABItemData라는 태그를 지정
- 프로젝트 설정에서 해당 애셋들이 담긴 폴더를 지정
- 전체 애셋 목록 중에서 하나를 랜덤으로 선택하고 이를 로딩해 보상으로 할당
// Fill out your copyright notice in the Description page of Project Settings.
#include "Gimmick/ABStageGimmick.h"
#include "Components/StaticMeshComponent.h"
#include "Components/BoxComponent.h"
#include "Physics/ABCollision.h"
// Sets default values
AABStageGimmick::AABStageGimmick()
{
// Stage Section
Stage = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Stage"));
RootComponent = Stage;
static ConstructorHelpers::FObjectFinder<UStaticMesh> StageMeshRef(TEXT("/Script/Engine.StaticMesh'/Game/ArenaBattle/Environment/Stages/SM_SQUARE.SM_SQUARE'"));
if (StageMeshRef.Object)
{
Stage->SetStaticMesh(StageMeshRef.Object);
}
StageTrigger = CreateDefaultSubobject<UBoxComponent>(TEXT("StageTrigger"));
StageTrigger->SetBoxExtent(FVector(775.0f, 775.0f, 300.0f));
StageTrigger->SetupAttachment(Stage);
StageTrigger->SetRelativeLocation(FVector(0.0f, 0.0f, 250.0f));
StageTrigger->SetCollisionProfileName(CPROFILE_ABTRIGGER);
StageTrigger->OnComponentBeginOverlap.AddDynamic(this, &AABStageGimmick::OnStageTriggerBeginOverlap);
// Gate Section
static FName GateSockets[] = { TEXT("+XGate"), TEXT("-XGate"), TEXT("+YGate"), TEXT("-YGate") };
static ConstructorHelpers::FObjectFinder<UStaticMesh> GateMeshRef(TEXT("/Script/Engine.StaticMesh'/Game/ArenaBattle/Environment/Props/SM_GATE.SM_GATE'"));
for (FName GateSocket : GateSockets)
{
UStaticMeshComponent* Gate = CreateDefaultSubobject<UStaticMeshComponent>(GateSocket);
Gate->SetStaticMesh(GateMeshRef.Object);
Gate->SetupAttachment(Stage, GateSocket);
Gate->SetRelativeLocation(FVector(0.0f, -80.5f, 0.0f));
Gate->SetRelativeRotation(FRotator(0.0f, -90.0f, 0.0f));
Gates.Add(GateSocket, Gate);
FName TriggerName = *GateSocket.ToString().Append(TEXT("Trigger"));
UBoxComponent* GateTrigger = CreateDefaultSubobject<UBoxComponent>(TriggerName);
GateTrigger->SetBoxExtent(FVector(100.0f, 100.0f, 300.0f));
GateTrigger->SetupAttachment(Stage, GateSocket);
GateTrigger->SetRelativeLocation(FVector(70.0f, 0.0f, 250.0f));
GateTrigger->SetCollisionProfileName(CPROFILE_ABTRIGGER);
GateTrigger->OnComponentBeginOverlap.AddDynamic(this, &AABStageGimmick::OnGateTriggerBeginOverlap);
GateTrigger->ComponentTags.Add(GateSocket);
GateTriggers.Add(GateTrigger);
}
}
void AABStageGimmick::OnStageTriggerBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepHitResult)
{
}
void AABStageGimmick::OnGateTriggerBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepHitResult)
{
}
계속 해왔던 컴포넌트 만들고 부착하기
메쉬 들어가보면 소켓 매니저 있음
상속해서 블루프린트 하나 만들어주기
이렇게 만들어진 걸 볼 수 있음
레벨 새로 만들고 기믹 추가
스테이트 설정
// Stage Section
protected:
UPROPERTY(VisibleAnywhere, Category = Stage, Meta = ( AllowPrivateAccess = "true"))
TObjectPtr<class UStaticMeshComponent> Stage;
UPROPERTY(VisibleAnywhere, Category = Stage, Meta = (AllowPrivateAccess = "true"))
TObjectPtr<class UBoxComponent> StageTrigger;
UFUNCTION()
void OnStageTriggerBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepHitResult);
// Gate Section
UPROPERTY(VisibleAnywhere, Category = Gate, Meta = (AllowPrivateAccess = "true"))
TMap<FName, TObjectPtr<class UStaticMeshComponent>> Gates;
UPROPERTY(VisibleAnywhere, Category = Stage, Meta = (AllowPrivateAccess = "true"))
TArray< TObjectPtr<class UBoxComponent> > GateTriggers;
UFUNCTION()
void OnGateTriggerBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepHitResult);
void OpenAllGates();
void CloseAllGates();
스테이지랑 게이트
OpenAllGates랑 CloseAllGates는 이렇게 설정됨
// State Section
UPROPERTY(EditAnywhere, Category = Stage, Meta = (AllowPrivateAccess = "true"))
EStageState CurrentState;
void SetState(EStageState InNewState);
UPROPERTY()
TMap<EStageState, FStageChangedDelegateWrapper> StateChangeActions;
void SetReady();
void SetFight();
void SetChooseReward();
void SetChooseNext();
스테이트 섹션
// State Section
CurrentState = EStageState::READY;
StateChangeActions.Add(EStageState::READY, FStageChangedDelegateWrapper(FOnStageChangedDelegate::CreateUObject(this, &AABStageGimmick::SetReady)));
StateChangeActions.Add(EStageState::FIGHT, FStageChangedDelegateWrapper(FOnStageChangedDelegate::CreateUObject(this, &AABStageGimmick::SetFight)));
StateChangeActions.Add(EStageState::REWARD, FStageChangedDelegateWrapper(FOnStageChangedDelegate::CreateUObject(this, &AABStageGimmick::SetChooseReward)));
StateChangeActions.Add(EStageState::NEXT, FStageChangedDelegateWrapper(FOnStageChangedDelegate::CreateUObject(this, &AABStageGimmick::SetChooseNext)));
구현부의 생성자에선 델리게이트 추가해주고
void AABStageGimmick::SetState(EStageState InNewState)
{
CurrentState = InNewState;
if (StateChangeActions.Contains(CurrentState))
{
StateChangeActions[CurrentState].StageDelegate.ExecuteIfBound();
}
}
SetState에서 CurrentState로 델리게이트 실행시키기
이 부분을 CurrentState에 의한 스위치문 같은 걸로 해결해도 상관없긴 함
뭐가 되었든 CurrentState에 의해 바인딩된 네가지 함수 중 하나가 실행됨
void AABStageGimmick::SetReady()
{
StageTrigger->SetCollisionProfileName(CPROFILE_ABTRIGGER);
for (auto& GateTrigger : GateTriggers)
{
GateTrigger->SetCollisionProfileName(TEXT("NoCollision"));
}
OpenAllGates();
}
void AABStageGimmick::SetFight()
{
StageTrigger->SetCollisionProfileName(TEXT("NoCollision"));
for (auto& GateTrigger : GateTriggers)
{
GateTrigger->SetCollisionProfileName(TEXT("NoCollision"));
}
CloseAllGates();
}
void AABStageGimmick::SetChooseReward()
{
StageTrigger->SetCollisionProfileName(TEXT("NoCollision"));
for (auto& GateTrigger : GateTriggers)
{
GateTrigger->SetCollisionProfileName(TEXT("NoCollision"));
}
}
void AABStageGimmick::SetChooseNext()
{
StageTrigger->SetCollisionProfileName(TEXT("NoCollision"));
for (auto& GateTrigger : GateTriggers)
{
GateTrigger->SetCollisionProfileName(CPROFILE_ABTRIGGER);
}
OpenAllGates();
}
일단 문 닫기만 구현해두기
이제 CurrentState의 변화로 문 열리고 닫히는 걸 확인하고 싶음
=> OnConstruction 함수
트랜스폼 말고도 속성의 변환까지 감지해서 에디터에서 보여줌
CurrentState 변화를 감지해서 SetState가 발동됨
SetState가 발동되어 문이 닫히고 열리는 걸 확인 가능
무한 맵 설정
void AABStageGimmick::OnStageTriggerBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepHitResult)
{
SetState(EStageState::FIGHT);
}
void AABStageGimmick::OnGateTriggerBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepHitResult)
{
check(OverlappedComponent->ComponentTags.Num() == 1);
FName ComponentTag = OverlappedComponent->ComponentTags[0];
FName SocketName = FName(*ComponentTag.ToString().Left(2));
check(Stage->DoesSocketExist(SocketName));
FVector NewLocation = Stage->GetSocketLocation(SocketName);
TArray<FOverlapResult> OverlapResults;
FCollisionQueryParams CollisionQueryParam(SCENE_QUERY_STAT(GateTrigger), false, this);
bool bResult = GetWorld()->OverlapMultiByObjectType(
OverlapResults,
NewLocation,
FQuat::Identity,
FCollisionObjectQueryParams::InitType::AllObjects,
FCollisionShape::MakeSphere(775.0f),
CollisionQueryParam
);
if (!bResult)
{
GetWorld()->SpawnActor<AABStageGimmick>(NewLocation, FRotator::ZeroRotator);
}
}
트리거에 태그 달아줬으니 하나 있는지 체크해야 함
그걸로 소켓 이름 만들기
게이트 통과할 때 트리거 오버랩 발동하는데, 해당 위치에 이미 만들어진 기믹이 없으면 기믹 새로 스폰
=> 무한 맵
GetWorld()->SpawnActor<AABStageGimmick>(NewLocation, FRotator::ZeroRotator);
같은 형태로 월드의 도움을 받아 액터를 스폰
적 NPC 스폰
// Fight Section
protected:
UPROPERTY(EditAnywhere, Category = Fight, Meta = (AllowPrivateAccess = "true"))
TSubclassOf<class AABCharacterNonPlayer> OpponentClass;
UPROPERTY(EditAnywhere, Category = Fight, Meta = (AllowPrivateAccess = "true"))
float OpponentSpawnTime;
UFUNCTION()
void OnOpponentDestroyed(AActor* DestroyedActor);
FTimerHandle OpponentTimerHandle;
void OnOpponentSpawn();
TSubClassOf는 지정된 클래스 또는 서브 클래스만 참조할 수 있게 한다
확장성을 위해 저렇게 해줌
// Fight Section
OpponentSpawnTime = 2.0f;
OpponentClass = AABCharacterNonPlayer::StaticClass();
생성자에서 스폰 타임이랑, 클래스 지정
void AABStageGimmick::OnOpponentDestroyed(AActor* DestroyedActor)
{
SetState(EStageState::REWARD);
}
void AABStageGimmick::OnOpponentSpawn()
{
const FVector SpawnLocation = GetActorLocation() + FVector::UpVector * 88.0f;
AActor* OpponentActor = GetWorld()->SpawnActor(OpponentClass, &SpawnLocation, &FRotator::ZeroRotator);
AABCharacterNonPlayer* ABOpponentCharacter = Cast<AABCharacterNonPlayer>(OpponentActor);
if (ABOpponentCharacter)
{
ABOpponentCharacter->OnDestroyed.AddDynamic(this, &AABStageGimmick::OnOpponentDestroyed);
}
}
2초 뒤 한 번 스폰하도록 설정
SetFight에서 타이머 설정해서 OnOpponentSpawn을 불러냄
보상 설정과 애셋 매니저
일단 ABItemData에서 PrimaryAssetId() 만들어줌
프로젝트 세팅의 애셋 매니저에서 애셋 클래스 지정하고,
모아두는 디렉토리 지정
// Reward Section
protected:
UPROPERTY(VisibleAnywhere, Category = Reward, Meta = (AllowPrivateAccess = "true"))
TSubclassOf<class AABItemBox> RewardBoxClass;
UPROPERTY(VisibleAnywhere, Category = Reward, Meta = (AllowPrivateAccess = "true"))
TArray<TWeakObjectPtr<class AABItemBox>> RewardBoxes;
TMap<FName, FVector> RewardBoxLocations;
UFUNCTION()
void OnRewardTriggerBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
void SpawnRewardBoxes();
};
// Reward Section
RewardBoxClass = AABItemBox::StaticClass();
for (FName GateSocket : GateSockets)
{
FVector BoxLocation = Stage->GetSocketLocation(GateSocket) / 2;
RewardBoxLocations.Add(GateSocket, BoxLocation);
}
리워드는 일단 AABItemClass로 해두고,
스폰할 박스 위치 미리 만들어줌
TWeakObjectPtr은 약참조로, TObjectPtr은 강참조라 함
강참조는 자기가 사라지기 전까지 소유하는 오브젝트는 꼭 가지고 있는데,
약참조는 관리는 해야 하는데 독립적으로 활동해서 할 일 다하면 알아서 사라지면 좋겠을 때 사용함
void AABStageGimmick::OnRewardTriggerBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
for (const auto& RewardBox : RewardBoxes)
{
if (RewardBox.IsValid())
{
AABItemBox* ValidItemBox = RewardBox.Get();
AActor* OverlappedBox = OverlappedComponent->GetOwner();
if (OverlappedBox != ValidItemBox)
{
ValidItemBox->Destroy();
}
}
}
SetState(EStageState::NEXT);
}
void AABStageGimmick::SpawnRewardBoxes()
{
for (const auto& RewardBoxLocation : RewardBoxLocations)
{
FVector WorldSpawnLocation = GetActorLocation() + RewardBoxLocation.Value + FVector(0.0f, 0.0f, 30.0f);
AActor* ItemActor = GetWorld()->SpawnActor(RewardBoxClass, &WorldSpawnLocation, &FRotator::ZeroRotator);
AABItemBox* RewardBoxActor = Cast<AABItemBox>(ItemActor);
if (RewardBoxActor)
{
RewardBoxActor->Tags.Add(RewardBoxLocation.Key);
RewardBoxActor->GetTrigger()->OnComponentBeginOverlap.AddDynamic(this, &AABStageGimmick::OnRewardTriggerBeginOverlap);
RewardBoxes.Add(RewardBoxActor);
}
}
}
* 리워드 박스가 약참조라 IsValid 해주고 Get으로 가져와야 함
그리고 박스에서 랜덤으로 아이템 로드함
자꾸 무기 애셋만 못찾아서 찾아보니
무기 리로드하고 무기 애셋 들어가서 다시 재저장하니까 인식된다
'언리얼 엔진 > Unreal Game Framework' 카테고리의 다른 글
[UE Game Framework] #8 Item System (0) | 2025.01.06 |
---|---|
[UE Game Framework] #7 Character Stat & Widget (0) | 2025.01.05 |
[UE Game Framework] #6 Character Attack Hit Check (0) | 2025.01.05 |
[UE Game Framework] #5 Character Combo Action (0) | 2025.01.04 |
[UE Game Framework] #4 Character Animation Setting (0) | 2025.01.04 |