TIL: Unreal C++ 26일차

박춘팔·7일 전

언리얼 TIL

목록 보기
25/29

누적 학습 시간 : 248시간 34분

📅 2026-05-07

C++ 캐릭터 생성

PlayerCharacter.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "InputActionValue.h"

#include "PlayerCharacter.generated.h"

class UInputMappingContext;
class UInputAction;
class USceneComponent;

UCLASS()
class METAL3D_API APlayerCharacter : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	APlayerCharacter();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

	// 인풋 컴포넌트 오버라이드
	virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override;
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Input")
	TObjectPtr<UInputMappingContext> DefaultMappingContext;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Input")
	TObjectPtr<UInputAction> MoveAction;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Input")
	TObjectPtr<UInputAction> LookAction;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Input")
	TObjectPtr<UInputAction> FireAction;

	virtual void NotifyControllerChanged() override;


	// Projectile
	UPROPERTY(EditDefaultsOnly, Category="Combat")
	TSubclassOf<class AProjectile> ProjectileClass;

	USceneComponent* FindMuzzlePoint() const;
	UPROPERTY(EditDefaultsOnly, Category="Combat")
	FName MuzzlePointComponentName = TEXT("MuzzlePoint");

	UPROPERTY(EditDefaultsOnly, Category="Combat")
	float AimTraceDistance = 10000.f;

	void Fire();

public:
	// Called every frame
	virtual void Tick(float DeltaTime) override;

private:
	void Move(const FInputActionValue& Value);
	void Look(const FInputActionValue& Value);
};

PlayerCharacter.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "Player/PlayerCharacter.h"

#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "Projectile/Projectile.h"
#include "GameFramework/PlayerController.h"
#include "Camera/CameraComponent.h"
#include "Kismet/GameplayStatics.h"
#include "GameFramework/PlayerController.h"
#include "DrawDebugHelpers.h"

// Sets default values
APlayerCharacter::APlayerCharacter()
{
	// Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
}

// Called when the game starts or when spawned
void APlayerCharacter::BeginPlay()
{
	Super::BeginPlay();
}

// Called every frame
void APlayerCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
}

// Called to bind functionality to input
void APlayerCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent);

	if (!EnhancedInputComponent)
	{
		return;
	}

	if (MoveAction)
	{
		EnhancedInputComponent->BindAction(
			MoveAction,
			ETriggerEvent::Triggered,
			this,
			&APlayerCharacter::Move
		);
	}

	if (LookAction)
	{
		EnhancedInputComponent->BindAction(
			LookAction,
			ETriggerEvent::Triggered,
			this,
			&APlayerCharacter::Look
		);
	}

	if (FireAction)
	{
		EnhancedInputComponent->BindAction(
			FireAction,
			ETriggerEvent::Triggered,
			this,
			&APlayerCharacter::Fire
		);
	}
}

void APlayerCharacter::NotifyControllerChanged()
{
	Super::NotifyControllerChanged();

	APlayerController* PlayerController = Cast<APlayerController>(GetController());
	if (!PlayerController)
	{
		return;
	}

	ULocalPlayer* LocalPlayer = PlayerController->GetLocalPlayer();
	if (!LocalPlayer)
	{
		return;
	}

	UEnhancedInputLocalPlayerSubsystem* InputSubsystem = LocalPlayer->GetSubsystem<
		UEnhancedInputLocalPlayerSubsystem>();
	if (InputSubsystem && DefaultMappingContext)
	{
		InputSubsystem->AddMappingContext(DefaultMappingContext, 0);
	}
}


void APlayerCharacter::Move(const FInputActionValue& Value)
{
	const FVector2D MovementVector = Value.Get<FVector2D>();

	if (Controller == nullptr)
	{
		return;
	}

	const FRotator ControlRotation = Controller->GetControlRotation();
	const FRotator YawRotation(0.0f, ControlRotation.Yaw, 0.0f);

	const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
	const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);

	AddMovementInput(ForwardDirection, MovementVector.Y);
	AddMovementInput(RightDirection, MovementVector.X);
}

void APlayerCharacter::Look(const FInputActionValue& Value)
{
	const FVector2D LookAxis = Value.Get<FVector2D>();

	AddControllerYawInput(LookAxis.X);
	AddControllerPitchInput(-LookAxis.Y);
}

USceneComponent* APlayerCharacter::FindMuzzlePoint() const
{
	TArray<USceneComponent*> SceneComponents;

	GetComponents<USceneComponent>(SceneComponents);

	for (USceneComponent* Component : SceneComponents)
	{
		if (IsValid(Component) && Component->GetName() == MuzzlePointComponentName.ToString())
		{
			return Component;
		}
	}

	return nullptr;
}


void APlayerCharacter::Fire()
{
	if (!ProjectileClass)
	{
		return;
	}

	APlayerController* PC = Cast<APlayerController>(GetController());
	if (!PC)
	{
		return;
	}

	int32 ViewportX = 0;
	int32 ViewportY = 0;

	PC->GetViewportSize(ViewportX, ViewportY);

	const float ScreenCenterX = ViewportX * 0.5f;
	const float ScreenCenterY = ViewportY * 0.5f;

	FVector WorldLocation;
	FVector WorldDirection;

	const bool bDeprojected = PC->DeprojectScreenPositionToWorld(
		ScreenCenterX,
		ScreenCenterY,
		WorldLocation,
		WorldDirection
	);

	if (!bDeprojected)
	{
		return;
	}

	const FVector TraceStart = WorldLocation;
	const FVector TraceEnd = TraceStart + WorldDirection * AimTraceDistance;

	FHitResult CameraHit;

	FCollisionQueryParams QueryParams;
	QueryParams.AddIgnoredActor(this);

	GetWorld()->LineTraceSingleByChannel(
		CameraHit,
		TraceStart,
		TraceEnd,
		ECC_Visibility,
		QueryParams
	);

	const FVector AimTarget = CameraHit.bBlockingHit ? CameraHit.ImpactPoint : TraceEnd;

	const USceneComponent* MuzzlePoint = FindMuzzlePoint();
	const FVector MuzzleLocation = MuzzlePoint->GetComponentLocation();

	const FVector FireDirection = (AimTarget - MuzzleLocation).GetSafeNormal();

	const FRotator SpawnRotation = FireDirection.Rotation();

	DrawDebugLine(
		GetWorld(),
		TraceStart,
		TraceEnd,
		FColor::Green,
		false,
		2.f,
		0,
		1.f
	);

	DrawDebugLine(
		GetWorld(),
		MuzzleLocation,
		MuzzleLocation + FireDirection * 3000.f,
		FColor::Red,
		false,
		2.f,
		0,
		2.f
	);

	FActorSpawnParameters SpawnParams;
	SpawnParams.Owner = this;
	SpawnParams.Instigator = this;
	SpawnParams.SpawnCollisionHandlingOverride =
		ESpawnActorCollisionHandlingMethod::AlwaysSpawn;

	GetWorld()->SpawnActor<AProjectile>(
		ProjectileClass,
		MuzzleLocation,
		SpawnRotation,
		SpawnParams
	);
}
profile
이것 저것 다해보는 삶

0개의 댓글