Note
All projects were made with UE 5.3
All source codes are from a lecture in udemy, <Unreal Engine 5 C++ Developer: Learn C++ & Make Video Games>.
- Firstperson Adventure/Puzzle
- Explore medieval dungeon and solve hidden puzzles!
- UPhysicsHandleComponent
- ๋ฌผ์ฒด๋ฅผ ์ก๊ธฐ ์ํ ์ปดํฌ๋ํธ ํด๋์ค
- SetTargetLocation()์ผ๋ก ๋์ ๋ฌผ์ฒด์ ์์น์ ํ์ ๊ฐ์ ์ค์๊ฐ์ผ๋ก ์กฐ์ ํฉ๋๋ค.
UPhysicsHandleComponent *PhysicsHandle = GetPhysicsHandle();
PhysicsHandle->GrabComponentAtLocationWithRotation(
HitComponent,
NAME_None,
HitResult.ImpactPoint,
HitResult.GetComponent()->GetComponentRotation()
);
- UPrimitiveComponent
- USceneComponent์ ํ์ ํด๋์ค๋ก ๊ธฐํํ์ ํํ๋ฅผ ๊ฐ์ง ์ ์๊ณ ์ถฉ๋ ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํ ์ปดํฌ๋ํธ์ ๋๋ค.
- USceneComponent๋ ๋ค๋ฅธ ์ปดํฌ๋ํธ์ ๋ถ์ฐฉ๋ ์๋ ์์ง๋ง ๋ ๋๋ง์ด๋ ์ถฉ๋ ์ฒ๋ฆฌ๊ฐ ๋ถ๊ฐ๋ฅํฉ๋๋ค.
- AActor::GetRootComponent()์ ๋ฐํ๊ฐ์ USceneComponent* ์ด๋ฏ๋ก UPrimitiveComponent๋ก ์ฌ์ฉ ์ Cast(dynamic cast)๊ฐ ํ์ํฉ๋๋ค.
UPrimitiveComponent* Component = Cast<UPrimitiveComponent>(Target->GetRootComponent());
if (Component != nullptr)
{
Component->SetSimulatePhysics(false);
}
Target->AttachToComponent(this, FAttachmentTransformRules::KeepWorldTransform);
- AActor::GetOverlappingActors()
- overlap์ค์ธ ๋ชจ๋ ์กํฐ๋ค์ TArray ํํ๋ก ๋ฐํํฉ๋๋ค.
- ์ถฉ๋์๋ ignore, overlap, block ์ด ์ธ ๊ฐ์ง ์ต์ ์ด ์์ผ๋ฉฐ ์ด ์ค overlap์ ๋ง ๊ทธ๋๋ก ์ค์ฒฉ์ํ๋ฅผ ๋งํฉ๋๋ค.
AActor *UTriggerComponent::GetAcceptableActor() const
{
TArray<AActor *> Actors;
GetOverlappingActors(Actors);
for (auto &actor : Actors)
{
bool HasAcceptableTag = actor->ActorHasTag(UnlockTag);
bool IsGrabbed = actor->ActorHasTag("Grabbed");
if (HasAcceptableTag && !IsGrabbed)
{
return actor;
}
}
return nullptr;
}
- FMath::VInterpConstantTo()
- static ํจ์์ด๋ฉฐ ๋ ๋ฒกํฐ๋ฅผ ๊ณ ์ ๋ ๊ฐ์ผ๋ก ๋ณด๊ฐํฉ๋๋ค. ์ฃผ๋ก ์กํฐ๋ฅผ ํน์ ํฑ๋งํผ ์ด๋์ํค๊ธฐ ์ํด ์ฌ์ฉํฉ๋๋ค.
FVector NewLocation = FMath::VInterpConstantTo(CurrentLocation, TargetLocation, DeltaTime, Speed);
GetOwner()->SetActorLocation(NewLocation);
- Lumen์ ํตํด ๋์ ์ผ๋ก ๊ด์ ์ ์ถ์ ํ๊ณ ๋ ๋๋งํฉ๋๋ค.
- ํ๋ถ์ ๋ฐ๊ธฐ๋ ์ฝํด์ก๋ค ๊ฐํด์ง๊ธฐ๋ฅผ ๋ฐ๋ณตํฉ๋๋ค. ์ด๋ฅผ ํตํด ๋ ํ์ค๊ฐ ์๋ ๋์ ์ ๋ฌ์ฌํฉ๋๋ค.
- ์์ ํ ์ธ๋ถ ๋น์ ์ฐจ๋จ์ ์ํด ๋์ ์ธ๋ถ์ ์ถ๊ฐ์ ์ธ ๋ฒฝ ๋ฉ์๋ค์ ๋ฐฐ์นํ์ต๋๋ค.
- Quarter view shooting
- Cartoon style graphic
- Avoid tower's attack and kill them all!
- CreateDefaultSubobject()
- ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค๊ณ ๊ณ์ธต๊ตฌ์กฐ๋ฅผ ์ค๊ณํฉ๋๋ค.
- SetupAttachment๋ก ์์ ์ปดํฌ๋ํธ์ ๋ถ์ฐฉํฉ๋๋ค.
// Sets default values
ABasePawn::ABasePawn()
{
// Set this pawn to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
CapsuleComp = CreateDefaultSubobject<UCapsuleComponent>(TEXT("Capsule Collider"));
RootComponent = CapsuleComp;
BaseMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Base Mesh"));
BaseMesh->SetupAttachment(CapsuleComp);
TurretMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Turret Mesh"));
TurretMesh->SetupAttachment(BaseMesh);
ProjectileSpawnPoint = CreateDefaultSubobject<USceneComponent>(TEXT("Projectile Spawn Point"));
ProjectileSpawnPoint->SetupAttachment(TurretMesh);
}
- SetupPlayerInputComponent()
- Input๊ณผ ๊ด๋ จํ ์ค์ ์ ์ด๊ธฐํํฉ๋๋ค.
- BeginPlay๋ณด๋ค ๋จผ์ ์คํ๋ฉ๋๋ค.
- BindAxis๋ก Axis Input์ผ๋ก ์คํ๋ ํจ์๋ฅผ ์ง์ ํฉ๋๋ค.
void ATank::SetupPlayerInputComponent(UInputComponent *PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
PlayerInputComponent->BindAxis(TEXT("MoveForward"), this, &ATank::Move);
PlayerInputComponent->BindAxis(TEXT("Turn"), this, &ATank::Turn);
PlayerInputComponent->BindAction(TEXT("Fire"), IE_Pressed, this, &ATank::Fire);
}
- USceneComponent::SetWorldRotation()
- ์ปดํฌ๋ํธ(์ฃผ๋ก ๋ฉ์)์ ์๋ ํ์ ๊ฐ์ ์กฐ์ ํฉ๋๋ค.
- FRotator()๋ก ๋ฒกํฐ์ ์๋ ํ์ ๊ฐ์ ๊ฐ์ ธ์ต๋๋ค.
- FMath::RInterpTo()๋ก ํ์ ๊ฐ์ ๋ณด๊ฐ์ ์ ์ฉํ์ฌ ๋ถ๋๋ฝ๊ฒ ์์ง์ด๋๋ก ๊ตฌํํฉ๋๋ค.
- UGameplayStatistics::GetWorldDeltaSeconds()๋ก Tick๋ฒ์ ๋ฐ์์ DeltaSeconds ๊ฐ์ ๋ฐ์์ฌ ์ ์์ต๋๋ค.
void ABasePawn::RotateTurret(FVector LookAtTarget)
{
FVector ToTarget = LookAtTarget - TurretMesh->GetComponentLocation();
FRotator LookAtRotation = FRotator(0.f, ToTarget.Rotation().Yaw, 0.f);
TurretMesh->SetWorldRotation(
FMath::RInterpTo(
TurretMesh->GetComponentRotation(),
LookAtRotation,
UGameplayStatics::GetWorldDeltaSeconds(this),
5.f)
);
}
- APlayerController::GetHitResultUnderCursor()
- ๋ง์ฐ์ค ์ปค์ ์์น๋ก Line Traceํ ๋ค์ ๊ฒฐ๊ณผ๋ฅผ FHitResult ๊ฐ์ฒด๋ก ๊ฐ์ ธ์ต๋๋ค.
// Called every frame
void ATank::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (PlayerController != nullptr)
{
FHitResult HitResult;
PlayerController->GetHitResultUnderCursor(
ECollisionChannel::ECC_Visibility,
false,
HitResult);
RotateTurret(HitResult.ImpactPoint);
}
}
- AddActorLocalOffset() & AddActorLocalRotation()
- ์กํฐ์ ์์น์ ํ์ ๊ฐ์ Delta๊ฐ ๋งํผ ๋ณ๊ฒฝํฉ๋๋ค.
void ATank::Move(float Value)
{
FVector DeltaLocation = FVector::ZeroVector;
DeltaLocation.X = Value * Speed * UGameplayStatics::GetWorldDeltaSeconds(this);
AddActorLocalOffset(DeltaLocation, true);
}
void ATank::Turn(float Value)
{
FRotator DeltaRotaion = FRotator::ZeroRotator;
DeltaRotaion.Yaw = Value * TurnRate * UGameplayStatics::GetWorldDeltaSeconds(this);
AddActorLocalRotation(DeltaRotaion, true);
}
- UProjectileMovementComponent
- UE์ physics์ ๋ณ๊ฐ๋ก ์๋ํ๋ ๋ฐ์ฌ์ฒด ์ฒ๋ฆฌ ์ปดํฌ๋ํธ
- ์ผ๋ฐ์ ์ผ๋ก ํ์ ๊ฐ์ด ๊ณ ์ ๋์ด ์๊ณ ๋ชฉํ์ง์ ์ ํฅํด ๋ ์๊ฐ๊ฒ๋ ํ๋ฉฐ ์ค๋ ฅ์ด ์ ์ฉ๋ฉ๋๋ค.
- ๋ฐ์ฌ์ฒด์ ์์ง์๊ณผ ๊ด๋ จํ ๋ค์ํ ์ต์ ์ ์ ๊ณตํฉ๋๋ค.
ProjectileMovement = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("Projectile Movement"));
ProjectileMovement->MaxSpeed = 2000.f;
ProjectileMovement->InitialSpeed = 1500.f;
- OnComponentHit
- ์ปดํฌ๋ํธ์ ์ถฉ๋ ์์ ์ ์ฝ๋ฐฑ ํจ์๋ฅผ ํธ์ถํ๋ ์ด๋ฒคํธ
- AddDynamic()์ผ๋ก ์ฝ๋ฐฑ ํจ์์ ์ ๋ณด๋ฅผ ๋๊ฒจ์ค๋๋ค.
- Instigator๋ ์ด๋ฒคํธ์ ๋ฑ๋ก๋ ์ฝ๋ฐฑ ํจ์๋ค์ ์ผ๊ด ํธ์ถ(Invoke)ํ๋ ๊ฐ์ฒด์ ๋๋ค.
// Called when the game starts or when spawned
void AProjectile::BeginPlay()
{
Super::BeginPlay();
ProjectileMesh->OnComponentHit.AddDynamic(this, &AProjectile::OnHit);
}
void AProjectile::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
AActor* MyOwner = GetOwner();
if (MyOwner == nullptr)
{
Destroy();
return;
}
AController* MyOwnerInstigator = MyOwner->GetInstigatorController();
UClass* DamageTypeClass = UDamageType::StaticClass();
if (OtherActor && OtherActor != this && OtherActor != MyOwner && MyOwnerInstigator)
{
UGameplayStatics::ApplyDamage(OtherActor, Damage, MyOwnerInstigator, this, DamageTypeClass);
}
Destroy();
}
- UGameplayStatics::SpawnEmitterAtLocation()
- UParticleSystem ๊ฐ์ฒด๋ฅผ ์ง์ ๋ ์์น์ ์ํํฉ๋๋ค. ํด๋น ์์น์ ํํฐํด ํจ๊ณผ๊ฐ ๋ํ๋ฉ๋๋ค.
- UGameplayStatics::PlaySoundAtLocation()
- USoundBase ๊ฐ์ฒด๋ฅผ ์ง์ ๋ ์์น์ ์ํํฉ๋๋ค. ํด๋น ์์น๋ก๋ถํฐ ์๋ฆฌ๊ฐ ์ฌ์๋์ด ๋๋ฆฌ ํผ์ง๋๋ค.
- APlayerController::ClientStartCameraShake()
- UCameraShakeBase ๊ฐ์ฒด์ ์ ์ฅ๋ ๊ฐ์ ํ ๋๋ก ์นด๋ฉ๋ผ์ ์์ง์ ํจ๊ณผ๋ฅผ ์คํํฉ๋๋ค.
- TSubclassOf๋ก ์๋ํฐ ์์์ ์ง์ ๊ฐ๋ฅํ ํด๋์ค์ ๋ฒ์๋ฅผ ํ์ ํฉ๋๋ค.
- UE 5.0 ์ดํ์ Matinee Camera Shake ํด๋์ค๊ฐ UE 5.1 ์ดํ์์๋ Legacy Camera Shake๋ก ๋ณ๊ฒฝ๋์์ต๋๋ค.
void ABasePawn::HandleDestruction()
{
if (ExplosionParticles)
{
UGameplayStatics::SpawnEmitterAtLocation(this, ExplosionParticles, GetActorLocation(), GetActorRotation());
}
if (DeathSound)
{
UGameplayStatics::PlaySoundAtLocation(this, DeathSound, GetActorLocation());
}
if (DeathCameraShakeClass)
{
GetWorld()->GetFirstPlayerController()->ClientStartCameraShake(DeathCameraShakeClass);
}
}
- Thirdperson shooter
- Find and eliminate enemies in the base in Antarctica!
- 4๊ฐ์ InputAction์ 1๊ฐ์ InputMappingContext์ ๋งคํํ ํ ์บ๋ฆญํฐ์ ์ฐ๊ฒฐํ์ฌ ์์ง์์ ์ ์ดํฉ๋๋ค.
- UInputComponent::BindAction์ผ๋ก ์ ๋ ฅ์ ํธ์ถ๋ ์ฝ๋ฐฑ ํจ์๋ฅผ ์ฐ๊ฒฐํฉ๋๋ค.
- ๊ฑท๊ธฐ ๊ธฐ๋ฅ์ ์ ํ์ ๋์ผํ๊ฒ ๋ ํจ์๋ฅผ ์ ์ธํ๋ bool ๋ณ์๋ฅผ ๋์ด Move ํจ์ ๋ด์์ ์ ์ด๋๋๋ก ํ์ฌ ๊ตฌํํฉ๋๋ค.
// .h
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Enhanced Input")
class UInputMappingContext* InputMapping;
UPROPERTY(EditDefaultsOnly, Category = "Enhanced Input")
class UInputAction* MoveAction;
UPROPERTY(EditDefaultsOnly, Category = "Enhanced Input")
UInputAction* LookAction;
UPROPERTY(EditDefaultsOnly, Category = "Enhanced Input")
UInputAction* JumpAction;
UPROPERTY(EditDefaultsOnly, Category = "Enhanced Input")
UInputAction* WalkAction;
bool bWalking = false;
void Move(const struct FInputActionValue& Value);
void Look(const FInputActionValue& Value);
void Walk() { bWalking = true; }
void StopWalking() { bWalking = false; }
// .cpp
void AShooterCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
// Get the player controller
APlayerController* PlayerController = Cast<APlayerController>(GetController());
if (PlayerController)
{
// Get the local player subsystem
UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer());
// Clear out existing mapping, and add our mapping
if (Subsystem)
{
Subsystem->AddMappingContext(InputMapping, 0);
}
}
UEnhancedInputComponent* PlayerEnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent);
if (PlayerEnhancedInputComponent)
{
PlayerEnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AShooterCharacter::Move);
PlayerEnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AShooterCharacter::Look);
PlayerEnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &ACharacter::Jump);
PlayerEnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);
PlayerEnhancedInputComponent->BindAction(WalkAction, ETriggerEvent::Started, this, &AShooterCharacter::Walk);
PlayerEnhancedInputComponent->BindAction(WalkAction, ETriggerEvent::Completed, this, &AShooterCharacter::StopWalking);
}
}
void AShooterCharacter::Move(const FInputActionValue& Value)
{
if (Controller)
{
FVector2D MovementVector = Value.Get<FVector2D>();
if (bWalking)
{
MovementVector *= 0.5f;
}
AddMovementInput(GetActorForwardVector(), MovementVector.Y);
AddMovementInput(GetActorRightVector(), MovementVector.X);
}
}
void AShooterCharacter::Look(const FInputActionValue& Value)
{
if (Controller)
{
FVector2D LookAxisVector = Value.Get<FVector2D>();
AddControllerYawInput(LookAxisVector.X);
AddControllerPitchInput(LookAxisVector.Y);
}
}
- ์ด ๊ฐ์ฒด์์ ๋ฐ์ฌ๋ฅผ ์ ์ดํฉ๋๋ค.
- LineTraceSingleByChannel()๋ก ํน์ ์ฝ๋ฆฌ์ ์ฑ๋์ ๋ถํฉํ๋ ๋์์๊ฒ Line Traceํฉ๋๋ค. ๋์์ ์บ๋ฆญํฐ ํน์ ๋ฒฝ๊ณผ ๊ฐ์ ์กํฐ์ผ ์ ์์ต๋๋ค.
- ECollisionQueryParams๋ก ์ถฉ๋์ํค์ง ์์ ์กํฐ๋ฅผ ์ค์ ํ์ฌ ์ด์์ด ๋ฐฉํด๋ฐ์ง ์๊ณ ๋ชฉํ์ ์ถฉ๋๋ ์ ์๋๋ก ํฉ๋๋ค.
bool AGun::GunTrace(FHitResult& Hit, FVector& ShotDirection)
{
AController* OwnerController = GetOwnerController();
if (OwnerController == nullptr) return false;
FVector ViewLocation;
FRotator ViewRotator;
OwnerController->GetPlayerViewPoint(ViewLocation, ViewRotator);
FVector End = ViewLocation + ViewRotator.Vector() * MaxRange;
ShotDirection = -ViewRotator.Vector();
FCollisionQueryParams Params;
Params.AddIgnoredActor(this);
Params.AddIgnoredActor(GetOwner());
return GetWorld()->LineTraceSingleByChannel(Hit, ViewLocation, End, ECollisionChannel::ECC_GameTraceChannel1, Params);
}
- Behavior Tree๋ก AI ์บ๋ฆญํฐ์ ํ๋์ ์ ์ดํฉ๋๋ค.
- Blackboard ๋ณ์๋ฅผ ๋์ด ์ง์์ ์ผ๋ก C++ ํด๋์ค์ ์ํธ์์ฉํ๋ฉด์ AI์ ์ํ๋ฅผ ๋ณ๊ฒฝํฉ๋๋ค.
- Behavior Tree์๋ ํจ์์ ์ ์ธ๋ถ๋ฅผ ๋๊ณ C++ ํด๋์ค์๋ ๊ตฌํ๋ถ๋ฅผ ๋๋ ๊ตฌ์กฐ์ ๋๋ค. ์ด๋ BTTask์ BTService๋ฅผ ํตํด ๊ฐ๋ฅํฉ๋๋ค.
- ์ ์ด ํ๋ ์ด์ด๋ฅผ ๋ฐ๊ฒฌํ ๋ ์ํ์ด ์๋ ๋ถ์ฑ๊ผด ํํ์ ์์ผ๋ฅผ ๊ฐ์ง๋๋ค.
- ์ด๋ฅผ ๊ตฌํํ๊ธฐ ์ํด์๋ FOV ๊ฐ๋ ์ ์ ์ํด์ผ ํ๋๋ฐ, ๋จผ์ ์ ์บ๋ฆญํฐ์ ์ ๋ฐฉ๋ฒกํฐ์ ํ๋ ์ด์ด ์์น๋ก์ ๋ฒกํฐ๊ฐ์ ๊ฐ๋๋ฅผ ๊ตฌํฉ๋๋ค.
- ๊ทธ ๊ฐ๋๋ฅผ ๊ตฌํ๊ธฐ ์ํด ๋ฒกํฐ์ ๋ด์ (๋ ธ๋ฉ ๋ฒกํฐ์ ๋ด์ ๊ฐ์ ์ฝ์ฌ์ธ) - ์ํฌ์ฝ์ฌ์ธ์ ๊ณผ์ ์ ๊ฑฐ์นฉ๋๋ค.
- ๊ตฌํ ๊ฐ๋๊ฐ FOV ๊ฐ๋๋ณด๋ค ์์ ๊ฒฝ์ฐ ํ๋ ์ด์ด๊ฐ ์์ผ์ ์๋ค๊ณ ํ๋จํฉ๋๋ค.
bool UBTService_PlayerLocationIfSeen::IsPlayerInFieldOfView(AAIController* Controller) const
{
APawn *PlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
if (PlayerPawn == nullptr) return false;
FVector EnemyLocation = Controller->GetPawn()->GetActorLocation();
FVector PlayerLocation = PlayerPawn->GetActorLocation();
FVector DirectionToPlayer = (PlayerLocation - EnemyLocation).GetSafeNormal();
// Calculate the angle between the enemy's forward vector and the direction to the player
FVector ForwardVector = Controller->GetPawn()->GetActorForwardVector();
float DotProduct = FVector::DotProduct(ForwardVector, DirectionToPlayer); // cos value
float Angle = FMath::Acos(DotProduct); // Returns angle in radians with arccos
Angle = FMath::RadiansToDegrees(Angle); // Convert angle to degrees
float FieldOfView = 210.f;
return Angle < FieldOfView / 2 && Controller->LineOfSightTo(PlayerPawn);
}
- ์ฒด๋ ฅ๋ฐ์ ํฌ๋ก์คํค์ด๋ฅผ Widget Blueprint(UserWidget ํด๋์ค)๋ก ๊ตฌํํฉ๋๋ค.
- ์ฒด๋ ฅ๋ฐ์ ๊ฒฝ์ฐ Percentage์ ํ๋ ์ด์ด์ ์ฒด๋ ฅ๊ฐ์ bindํ์ฌ ๋๊ธฐํํฉ๋๋ค. Widget์ Graph์์ ์ฒด๋ ฅ๊ฐ์ ๊ฐ์ ธ์ต๋๋ค.
- Blend Space์์ Speed์ Angle ์ด ๋ ๊ฐ์ง ์ถ์ ์ฌ์ฉํ๋ 2์ฐจ์ ์ ๋๋ฉ์ดํ ๊ณต๊ฐ์ ๋ง๋ค๊ณ ๊ฐ ๊ฐ์ ๋ฐ๋ผ ์ด๋ ์๋์ ํ์ ๊ฐ ๋ณ๊ฒฝ ๋ฑ Locomotion์ด ์ด๋ฃจ์ด์ง ์ ์๋๋ก ํฉ๋๋ค.
- ์ ํ, ์ฐฉ์ง์ ๊ฐ์ State Machine์ ๋ง๋ค์ด ์บ๋ฆญํฐ์ ๋ชจ์ ์ด Input์ ๋ฐ๋ผ ์ฌ์ดํด์ด ํ์ฑ๋๋๋ก ํฉ๋๋ค.
- ์ ์ ๋ชจ๋ ์ฌ์ดํ ๊ฒฝ์ฐ ํ๋ ์ด์ด์ ์น๋ฆฌ, ๋ง์ฝ ๊ทธ ์ ์ ํ๋ ์ด์ด๊ฐ ์ฌ๋งํ๋ฉด ํจ๋ฐฐํฉ๋๋ค.
- ์ด๋ฅผ ์ํด ํผ์ ๊ตฌ๋ถ์ด ์๋ Controller ํด๋์ค์ GameHasEnded() ํจ์๋ฅผ ๊ตฌํํฉ๋๋ค. Controller๋ PlayerController์ AIController ํด๋์ค ๋ชจ๋ ์์๋ฐ์ต๋๋ค.
- ์นํจ ์ฌ๋ถ์ ๋ฐ๋ผ ๋ชจ๋ ์ปจํธ๋กค๋ฌ์ GameHasEnded() ํจ์๋ฅผ ํธ์ถํฉ๋๋ค. ์ด๋ฅผ ์ํด TActorRange ํ ํ๋ฆฟ ๋ ์ธ์ง๋ฅผ ํ์ฉํ์ฌ ์๋์ ์กด์ฌํ๋ ๋ชจ๋ ์ปจํธ๋กค๋ฌ๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
- ํ๋ ์ด์ด์ GameHasEnded์์๋ ์น๋ฆฌ ๋๋ ํจ๋ฐฐ์ ๋ฐ๋ฅธ HUD๋ฅผ ์ถ๋ ฅํฉ๋๋ค.
void AKillEmAllGameMode::PawnKilled(APawn* PawnKilled)
{
Super::PawnKilled(PawnKilled);
APlayerController* PlayerController = Cast<APlayerController>(PawnKilled->GetController());
if (PlayerController)
{
EndGame(false);
}
for (AShooterAIController* Controller : TActorRange<AShooterAIController>(GetWorld()))
{
if (!Controller->IsDead())
{
return;
}
}
EndGame(true);
}
void AKillEmAllGameMode::EndGame(bool bIsPlayerWinner)
{
for (AController* Controller : TActorRange<AController>(GetWorld()))
{
bool bIsWinner = Controller->IsPlayerController() == bIsPlayerWinner;
Controller->GameHasEnded(Controller->GetPawn(), bIsWinner);
}
}
Note
ํ์ฌ ๊ณ์ ์ ๋ฐ์ดํธ ์ค์ ๋๋ค.