Skip to content

sa04108/unreal-engine-tutorials

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

18 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

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>.

CryptRaider

  • Firstperson Adventure/Puzzle
  • Explore medieval dungeon and solve hidden puzzles!

Features

Grab

  • 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;
}

Moving Object

  • FMath::VInterpConstantTo()
    • static ํ•จ์ˆ˜์ด๋ฉฐ ๋‘ ๋ฒกํ„ฐ๋ฅผ ๊ณ ์ •๋œ ๊ฐ’์œผ๋กœ ๋ณด๊ฐ„ํ•ฉ๋‹ˆ๋‹ค. ์ฃผ๋กœ ์•กํ„ฐ๋ฅผ ํŠน์ • ํ‹ฑ๋งŒํผ ์ด๋™์‹œํ‚ค๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
FVector NewLocation = FMath::VInterpConstantTo(CurrentLocation, TargetLocation, DeltaTime, Speed);
GetOwner()->SetActorLocation(NewLocation);

Lighting

  • Lumen์„ ํ†ตํ•ด ๋™์ ์œผ๋กœ ๊ด‘์„ ์„ ์ถ”์ ํ•˜๊ณ  ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.
  • ํšƒ๋ถˆ์˜ ๋ฐ๊ธฐ๋Š” ์•ฝํ•ด์กŒ๋‹ค ๊ฐ•ํ•ด์ง€๊ธฐ๋ฅผ ๋ฐ˜๋ณตํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋” ํ˜„์‹ค๊ฐ ์žˆ๋Š” ๋˜์ „์„ ๋ฌ˜์‚ฌํ•ฉ๋‹ˆ๋‹ค.
  • ์™„์ „ํ•œ ์™ธ๋ถ€ ๋น›์˜ ์ฐจ๋‹จ์„ ์œ„ํ•ด ๋˜์ „ ์™ธ๋ถ€์— ์ถ”๊ฐ€์ ์ธ ๋ฒฝ ๋ฉ”์‹œ๋“ค์„ ๋ฐฐ์น˜ํ–ˆ์Šต๋‹ˆ๋‹ค.

ToonTanks

  • Quarter view shooting
  • Cartoon style graphic
  • Avoid tower's attack and kill them all!

Features

Code-based hierarchy

  • 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);
}

Input

  • 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);
}

Pawn Control

  • 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);
}

Projectile

  • 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();
}

Effects

  • 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);
	}
}

SimpleShooter

  • Thirdperson shooter
  • Find and eliminate enemies in the base in Antarctica!

Features

Enhanced Input with C++

  • 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);
	}
}

Shooting

  • ์ด ๊ฐ์ฒด์—์„œ ๋ฐœ์‚ฌ๋ฅผ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.
  • 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);
}

AI - Behavior Tree

  • Behavior Tree๋กœ AI ์บ๋ฆญํ„ฐ์˜ ํ–‰๋™์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.
  • Blackboard ๋ณ€์ˆ˜๋ฅผ ๋‘์–ด ์ง€์†์ ์œผ๋กœ C++ ํด๋ž˜์Šค์™€ ์ƒํ˜ธ์ž‘์šฉํ•˜๋ฉด์„œ AI์˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.
  • Behavior Tree์—๋Š” ํ•จ์ˆ˜์˜ ์„ ์–ธ๋ถ€๋ฅผ ๋‘๊ณ  C++ ํด๋ž˜์Šค์—๋Š” ๊ตฌํ˜„๋ถ€๋ฅผ ๋‘๋Š” ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค. ์ด๋Š” BTTask์™€ BTService๋ฅผ ํ†ตํ•ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

AI - Radial Sight

  • ์ ์ด ํ”Œ๋ ˆ์ด์–ด๋ฅผ ๋ฐœ๊ฒฌํ•  ๋•Œ ์›ํ˜•์ด ์•„๋‹Œ ๋ถ€์ฑ„๊ผด ํ˜•ํƒœ์˜ ์‹œ์•ผ๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค.
  • ์ด๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” 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);
}

HUD

  • ์ฒด๋ ฅ๋ฐ”์™€ ํฌ๋กœ์Šคํ—ค์–ด๋ฅผ Widget Blueprint(UserWidget ํด๋ž˜์Šค)๋กœ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
  • ์ฒด๋ ฅ๋ฐ”์˜ ๊ฒฝ์šฐ Percentage์— ํ”Œ๋ ˆ์ด์–ด์˜ ์ฒด๋ ฅ๊ฐ’์„ bindํ•˜์—ฌ ๋™๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค. Widget์˜ Graph์—์„œ ์ฒด๋ ฅ๊ฐ’์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

Character Animation - Blend Space

  • Blend Space์—์„œ Speed์™€ Angle ์ด ๋‘ ๊ฐ€์ง€ ์ถ•์„ ์‚ฌ์šฉํ•˜๋Š” 2์ฐจ์› ์• ๋‹ˆ๋ฉ”์ดํŒ… ๊ณต๊ฐ„์„ ๋งŒ๋“ค๊ณ  ๊ฐ ๊ฐ’์— ๋”ฐ๋ผ ์ด๋™ ์†๋„์™€ ํšŒ์ „๊ฐ’ ๋ณ€๊ฒฝ ๋“ฑ Locomotion์ด ์ด๋ฃจ์–ด์งˆ ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

Character Animation - State Machine

  • ์ ํ”„, ์ฐฉ์ง€์™€ ๊ฐ™์€ State Machine์„ ๋งŒ๋“ค์–ด ์บ๋ฆญํ„ฐ์˜ ๋ชจ์…˜์ด Input์— ๋”ฐ๋ผ ์‚ฌ์ดํด์ด ํ˜•์„ฑ๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

End Game

  • ์ ์„ ๋ชจ๋‘ ์‚ฌ์‚ดํ•œ ๊ฒฝ์šฐ ํ”Œ๋ ˆ์ด์–ด์˜ ์Šน๋ฆฌ, ๋งŒ์•ฝ ๊ทธ ์ „์— ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ์‚ฌ๋งํ•˜๋ฉด ํŒจ๋ฐฐํ•ฉ๋‹ˆ๋‹ค.
  • ์ด๋ฅผ ์œ„ํ•ด ํ”ผ์•„ ๊ตฌ๋ถ„์ด ์—†๋Š” 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

ํ˜„์žฌ ๊ณ„์† ์—…๋ฐ์ดํŠธ ์ค‘์ž…๋‹ˆ๋‹ค.

About

Tutorial projects with UE5

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published