<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>코드망령</title>
    <link>https://codeghost.tistory.com/</link>
    <description>언리얼 자주 다룹니다.</description>
    <language>ko</language>
    <pubDate>Tue, 28 Jul 2026 20:31:21 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>코드망령</managingEditor>
    <item>
      <title>언리얼 C++ 과제 4</title>
      <link>https://codeghost.tistory.com/39</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;스파르타 클럽 챕터3 과제4 진행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Pawn으로 이동하는 객체 만들기와 도전과제로 비행체 만들기입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=qnovx8h6Qr8&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=qnovx8h6Qr8&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=qnovx8h6Qr8&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/Jc2C3/dJMb84X6Lcn/QeqWstlZFWGukcpfF1ACl0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/cGCLBr/dJMb81G5sIw/S8MMmSGAKHs7W8Tv7DCpzk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/g2epj/dJMb83kA0zZ/YS847csTvljKAtCbS2yEO0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;Pawn으로 Character구현 및 날아다니는 비행체 구현&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/qnovx8h6Qr8&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pawn에 걷기, 점프, 달리기를 추가했고, 추가적으로 Pawn클래스를 하나 더 만들어 비행체를 구현했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이동과 화면회전은 AddActorLocalLocation과 AddActorLocalRotation으로 구현했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Character클래스의 경우는 이동할 때 바닥이나 벽면에서 Block되었어도 미끄러지도록 구현이 되어있지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pawn의 경우 그런 물리로직을 직접 구현해야 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpartaPawn.h&lt;/p&gt;
&lt;pre id=&quot;code_1780978498616&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#pragma once

#include &quot;CoreMinimal.h&quot;
#include &quot;GameFramework/Pawn.h&quot;
#include &quot;SpartaPawn.generated.h&quot;

class UCameraComponent;
class UCapsuleComponent;
class USkeletalMeshComponent;
class USpringArmComponent;
class USpartaMoveComponent;
struct FInputActionValue;

UCLASS()
class SPARTA3_4_API ASpartaPawn : public APawn
{
	GENERATED_BODY()

public:
	ASpartaPawn();

	UPROPERTY(EditAnywhere)
	UCapsuleComponent* Capsule;
	UPROPERTY(EditAnywhere)
	USkeletalMeshComponent* SkeletalMesh;
	UPROPERTY(EditAnywhere)
	USpringArmComponent* SpringArm;
	UPROPERTY(EditAnywhere)
	UCameraComponent* Camera;
	UPROPERTY(EditAnywhere)
	USpartaMoveComponent* MoveComponent;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Look&quot;)
	float LookSensitivity = 90.f;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Look&quot;)
	float MinPitch = -50.f;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Look&quot;)
	float MaxPitch = 40.f;

	UFUNCTION(BlueprintCallable)
	USpartaMoveComponent* GetMoveComponent() { return MoveComponent; }
	
protected:
	virtual void BeginPlay() override;
	virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override;

public:
	virtual void Tick(float DeltaTime) override;

private:
	FVector2D LookInput = FVector2D::ZeroVector;
	float CurrentPitch = 0.f;

	void Move(const FInputActionValue&amp;amp; Value);
	void Look(const FInputActionValue&amp;amp; Value);
	void StartJump(const FInputActionValue&amp;amp; Value);
	void StopJump(const FInputActionValue&amp;amp; Value);
	void StartSprint(const FInputActionValue&amp;amp; Value);
	void StopSprint(const FInputActionValue&amp;amp; Value);

	void UpdateLook(float DeltaTime);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpartaPawn.cpp&lt;/p&gt;
&lt;pre id=&quot;code_1780978546296&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &quot;SpartaPawn.h&quot;

#include &quot;EnhancedInputComponent.h&quot;
#include &quot;SpartaPlayerContrller.h&quot;
#include &quot;Camera/CameraComponent.h&quot;
#include &quot;Components/CapsuleComponent.h&quot;
#include &quot;Components/SkeletalMeshComponent.h&quot;
#include &quot;GameFramework/SpringArmComponent.h&quot;
#include &quot;SpartaMoveComponent.h&quot;

/*
 * Pawn의 역할
 *
 * 이 클래스는 플레이어의 컴포넌트 구성과 입력 연결을 담당합니다.
 * 실제 이동, 점프, 중력, 벽 슬라이딩은 USpartaMoveComponent에서 처리합니다.
 *
 * 카메라 회전은 여기서 직접 처리합니다.
 * - 마우스 X: Pawn 전체를 좌우(Yaw) 회전
 * - 마우스 Y: SpringArm만 위아래(Pitch) 회전
 *
 * 이렇게 나누면 Capsule은 항상 똑바로 서 있고,
 * 카메라만 위아래로 움직이는 Character 같은 시점을 만들 수 있습니다.
 */
ASpartaPawn::ASpartaPawn()
{
	PrimaryActorTick.bCanEverTick = true;

	// Capsule은 RootComponent이며, 플레이어 이동 충돌을 담당합니다.
	Capsule = CreateDefaultSubobject&amp;lt;UCapsuleComponent&amp;gt;(TEXT(&quot;Capsule&quot;));
	SetRootComponent(Capsule);
	Capsule-&amp;gt;InitCapsuleSize(42.f, 96.f);
	Capsule-&amp;gt;SetCollisionEnabled(ECollisionEnabled::QueryOnly);
	Capsule-&amp;gt;SetCollisionResponseToAllChannels(ECR_Block);
	Capsule-&amp;gt;SetSimulatePhysics(false);
	
	// SkeletalMesh는 화면에 보이는 용도만 담당하고, 충돌은 Capsule만 사용합니다.
	SkeletalMesh = CreateDefaultSubobject&amp;lt;USkeletalMeshComponent&amp;gt;(TEXT(&quot;Skeleton&quot;));
	SkeletalMesh-&amp;gt;SetupAttachment(Capsule);
	SkeletalMesh-&amp;gt;SetCollisionEnabled(ECollisionEnabled::NoCollision);
	SkeletalMesh-&amp;gt;SetSimulatePhysics(false);

	// SpringArm은 카메라의 위아래 회전을 담당합니다. Pawn 본체는 위아래로 기울이지 않습니다.
	SpringArm = CreateDefaultSubobject&amp;lt;USpringArmComponent&amp;gt;(TEXT(&quot;Spring Arm&quot;));
	SpringArm-&amp;gt;AttachToComponent(Capsule, FAttachmentTransformRules::KeepRelativeTransform);
	SpringArm-&amp;gt;SetRelativeLocation(FVector(0.f, 0.f, 70.f));
	SpringArm-&amp;gt;SetRelativeRotation(FRotator(CurrentPitch, 0.f, 0.f));
	SpringArm-&amp;gt;TargetArmLength = 500.f;
	SpringArm-&amp;gt;bUsePawnControlRotation = false;
	SpringArm-&amp;gt;bDoCollisionTest = false;

	// Camera는 SpringArm 소켓에 붙어 SpringArm 끝 위치를 따라갑니다.
	Camera = CreateDefaultSubobject&amp;lt;UCameraComponent&amp;gt;(TEXT(&quot;Camera&quot;));
	Camera-&amp;gt;SetupAttachment(SpringArm, USpringArmComponent::SocketName);
	Camera-&amp;gt;SetRelativeLocation(FVector::ZeroVector);
	Camera-&amp;gt;SetRelativeRotation(FRotator::ZeroRotator);
	Camera-&amp;gt;bUsePawnControlRotation = false;

	// 실제 이동, 점프, 중력, 벽 슬라이딩은 MoveComponent에서 처리합니다.
	MoveComponent = CreateDefaultSubobject&amp;lt;USpartaMoveComponent&amp;gt;(TEXT(&quot;MoveComponent&quot;));
	MoveComponent-&amp;gt;UpdatedComponent = Capsule;

	bUseControllerRotationPitch = false;
	bUseControllerRotationYaw = false;
	bUseControllerRotationRoll = false;
}

void ASpartaPawn::BeginPlay()
{
	Super::BeginPlay();

}

void ASpartaPawn::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	UpdateLook(DeltaTime);
}


void ASpartaPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	// Pawn은 Enhanced Input을 받고, 이동 관련 입력은 MoveComponent로 전달합니다.
	if (UEnhancedInputComponent* EnhancedInput = Cast&amp;lt;UEnhancedInputComponent&amp;gt;(PlayerInputComponent))
	{
		if (ASpartaPlayerContrller* PlayerController = Cast&amp;lt;ASpartaPlayerContrller&amp;gt;(GetController()))
		{
			EnhancedInput-&amp;gt;BindAction(PlayerController-&amp;gt;MoveAction, ETriggerEvent::Triggered, this, &amp;amp;ASpartaPawn::Move);
			EnhancedInput-&amp;gt;BindAction(PlayerController-&amp;gt;MoveAction, ETriggerEvent::Completed, this, &amp;amp;ASpartaPawn::Move);
			EnhancedInput-&amp;gt;BindAction(PlayerController-&amp;gt;LookAction, ETriggerEvent::Triggered, this, &amp;amp;ASpartaPawn::Look);
			EnhancedInput-&amp;gt;BindAction(PlayerController-&amp;gt;LookAction, ETriggerEvent::Completed, this, &amp;amp;ASpartaPawn::Look);
			EnhancedInput-&amp;gt;BindAction(PlayerController-&amp;gt;JumpAction, ETriggerEvent::Triggered, this, &amp;amp;ASpartaPawn::StartJump);
			EnhancedInput-&amp;gt;BindAction(PlayerController-&amp;gt;JumpAction, ETriggerEvent::Completed, this, &amp;amp;ASpartaPawn::StopJump);
			EnhancedInput-&amp;gt;BindAction(PlayerController-&amp;gt;SprintAction, ETriggerEvent::Triggered, this, &amp;amp;ASpartaPawn::StartSprint);
			EnhancedInput-&amp;gt;BindAction(PlayerController-&amp;gt;SprintAction, ETriggerEvent::Completed, this, &amp;amp;ASpartaPawn::StopSprint);
		}
	}
}


void ASpartaPawn::Move(const FInputActionValue&amp;amp; Value)
{
	// 이동 입력값만 MoveComponent에 넘기고, 실제 이동은 MoveComponent Tick에서 처리합니다.
	if (MoveComponent)
	{
		MoveComponent-&amp;gt;SetMoveInput(Value.Get&amp;lt;FVector2D&amp;gt;());
	}
}

void ASpartaPawn::Look(const FInputActionValue&amp;amp; Value)
{
	// 마우스 입력값은 저장해두고 Tick에서 DeltaTime을 곱해 회전 처리합니다.
	LookInput = Value.Get&amp;lt;FVector2D&amp;gt;();
}

void ASpartaPawn::StartJump(const FInputActionValue&amp;amp; Value)
{
	if (MoveComponent)
	{
		MoveComponent-&amp;gt;StartJump();
	}
}

void ASpartaPawn::StopJump(const FInputActionValue&amp;amp; Value)
{
	if (MoveComponent)
	{
		MoveComponent-&amp;gt;StopJump();
	}
}

void ASpartaPawn::StartSprint(const FInputActionValue&amp;amp; Value)
{
	if (MoveComponent &amp;amp;&amp;amp; Value.Get&amp;lt;bool&amp;gt;())
	{
		MoveComponent-&amp;gt;StartSprint();
	}
}

void ASpartaPawn::StopSprint(const FInputActionValue&amp;amp; Value)
{
	if (MoveComponent)
	{
		MoveComponent-&amp;gt;StopSprint();
	}
}

void ASpartaPawn::UpdateLook(float DeltaTime)
{
	if (LookInput.IsNearlyZero()) return;

	// 마우스 X축은 Pawn 전체를 좌우(Yaw)로 회전시킵니다.
	// 이 회전은 이동 방향에도 영향을 줍니다. Forward/Right 벡터가 같이 바뀌기 때문입니다.
	const float Yaw = LookInput.X * LookSensitivity * DeltaTime;
	AddActorLocalRotation(FRotator(0.f, Yaw, 0.f), false);

	// 마우스 Y축은 SpringArm만 위아래(Pitch)로 회전시켜 Capsule은 항상 똑바로 서 있게 합니다.
	// CurrentPitch를 제한해서 카메라가 너무 위나 아래로 꺾이지 않게 합니다.
	CurrentPitch = FMath::Clamp(
		CurrentPitch - LookInput.Y * LookSensitivity * DeltaTime,
		MinPitch,
		MaxPitch
	);

	if (SpringArm) SpringArm-&amp;gt;SetRelativeRotation(FRotator(CurrentPitch, 0.f, 0.f));
	
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpartaMoveComponent.h&lt;/p&gt;
&lt;pre id=&quot;code_1780978609760&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#pragma once

#include &quot;CoreMinimal.h&quot;
#include &quot;GameFramework/PawnMovementComponent.h&quot;
#include &quot;SpartaMoveComponent.generated.h&quot;

UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class SPARTA3_4_API USpartaMoveComponent : public UPawnMovementComponent
{
	GENERATED_BODY()

public:
	USpartaMoveComponent();

	// 기본 이동 속도입니다. Unreal 단위 기준으로 초당 이동 거리입니다.
	UPROPERTY(EditAnywhere, Category = &quot;Move&quot;)
	float MoveSpeed = 450;
	// 달리기 입력이 켜져 있을 때 MoveSpeed에 곱해지는 값입니다.
	UPROPERTY(EditAnywhere, Category = &quot;Move&quot;)
	float SprintMultiplier = 2.5f;
	// 점프를 시작할 때 위쪽으로 주는 초기 속도입니다.
	UPROPERTY(EditAnywhere, Category = &quot;Jump&quot;)
	float JumpPower = 800.f;
	// 공중에 있을 때 매 프레임 아래쪽으로 누적되는 중력 가속도입니다.
	UPROPERTY(EditAnywhere, Category = &quot;Physics&quot;)
	float Gravity = 980.f;
	// Pawn 중심에서 아래로 레이를 쏴서 바닥을 찾는 거리입니다.
	UPROPERTY(EditAnywhere, Category = &quot;Physics&quot;)
	float GroundCheckDistance = 120.f;
	// 이 값보다 Normal.Z가 크면 걸을 수 있는 바닥이나 경사로 판단합니다.
	UPROPERTY(EditAnywhere, Category = &quot;Physics&quot;)
	float WalkableFloorZ = 0.6f;

	// 이동 및 달리기 애니메이션을 위한 값을 반환합니다.
	UFUNCTION(BlueprintCallable)
	bool IsMove() { return MoveInput.IsNearlyZero(); }
	// 점프하는 애니메이션을 위한 값을 반환합니다.
	UFUNCTION(BlueprintCallable)
	float GetVerticalVelocity() { return VerticalVelocity; }
	// 떨어지는 애니메이션을 위한 값을 반환합니다.
	UFUNCTION(BlueprintCallable)
	bool GetIsGrounded() { return bIsGrounded; }
	// 달리는 애니메이션을 위한 값을 반환합니다.
	UFUNCTION(BlueprintCallable)
	bool GetIsSprinting() { return bIsSprinting; }
	
protected:
	virtual void BeginPlay() override;
	virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

public:
	void SetMoveInput(const FVector2D&amp;amp; Value);

	void StartJump();
	void StopJump();

	void StartSprint();
	void StopSprint();
private:
	// 매 Tick마다 GetOwner, FindComponent를 반복하지 않기 위해 캐싱합니다.
	UPROPERTY()
	APawn* CachedOwnerPawn = nullptr;
	UPROPERTY()
	UCapsuleComponent* CachedCapsule = nullptr;
	// 현재 밟고 있는 Actor입니다. 움직이는 발판을 따라가기 위해 사용합니다.
	UPROPERTY()
	AActor* GroundActor = nullptr;

	// Pawn의 Enhanced Input 함수에서 받은 이동 입력값을 저장합니다.
	FVector2D MoveInput = FVector2D::ZeroVector;

	// CharacterMovement를 쓰지 않기 때문에 Z축 속도를 직접 관리합니다.
	float VerticalVelocity = 0.f;
	// Capsule이 현재 바닥 위에 정확히 서기 위한 ActorLocation.Z 값입니다.
	float GroundZ =0.f;
	// 기본이동값과 달리기의 값을 저장하기 위한 변수입니다.
	float CurrentSpeed = 0.f;
	// 현재 밟고 있는 바닥의 Normal입니다. 울퉁불퉁한 바닥을 따라 이동할 때 사용합니다.
	FVector GroundNormal = FVector::UpVector;
	// 이전 프레임의 GroundActor 위치입니다. 움직인 발판의 이동량을 계산합니다.
	FVector LastGroundActorLocation = FVector::ZeroVector;
	
	bool bIsGrounded = true;
	bool bIsSprinting = false;

	// 아래 방향 LineTrace로 바닥 여부, 바닥 Normal, 밟고 있는 Actor를 갱신합니다.
	void CheckGround();

	// 입력 이동, 중력, 점프 속도를 합친 뒤 Sweep 이동과 벽면 슬라이드를 처리합니다.
	void ApplyMovement(float DeltaTime);
	// 현재 밟고 있는 움직이는 발판의 이동량만큼 Pawn을 먼저 이동시킵니다.
	void ApplyGroundActorMovement();
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpartaMoveComponent.cpp&lt;/p&gt;
&lt;pre id=&quot;code_1780978641549&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &quot;SpartaMoveComponent.h&quot;

#include &quot;Components/CapsuleComponent.h&quot;
#include &quot;GameFramework/Pawn.h&quot;

/*
 * 이 컴포넌트의 전체 이동 원리
 *
 * 1. Pawn에서 받은 입력값을 MoveInput에 저장한다.
 * 2. Tick마다 아래 순서로 이동을 처리한다.
 *    - 이전 프레임에 밟고 있던 발판이 움직였으면 Pawn도 먼저 따라간다.
 *    - 아래로 LineTrace를 쏴서 현재 바닥 상태를 확인한다.
 *    - 입력 이동, 점프 속도, 중력을 합쳐 이번 프레임 이동량을 만든다.
 *    - Capsule을 Sweep 이동시켜 벽이나 바닥에 부딪혔는지 확인한다.
 *    - 충돌했다면 SlideAlongSurface로 벽면이나 경사면을 따라 미끄러지게 한다.
 *
 * Simulate Physics는 사용하지 않는다.
 * 대신 직접 계산한 이동량 + Sweep 충돌 검사 + 바닥 레이 + 슬라이딩으로
 * CharacterMovement와 비슷한 동작을 흉내낸다.
 */

USpartaMoveComponent::USpartaMoveComponent()
{
	PrimaryComponentTick.bCanEverTick = true;
}

void USpartaMoveComponent::BeginPlay()
{
	Super::BeginPlay();

	CachedOwnerPawn = Cast&amp;lt;APawn&amp;gt;(GetOwner());
	if (CachedOwnerPawn)
	{
		CachedCapsule = CachedOwnerPawn-&amp;gt;FindComponentByClass&amp;lt;UCapsuleComponent&amp;gt;();
		SetUpdatedComponent(CachedOwnerPawn-&amp;gt;GetRootComponent());
		GroundZ = CachedOwnerPawn-&amp;gt;GetActorLocation().Z;
	}
}

void USpartaMoveComponent::TickComponent(float DeltaTime, ELevelTick TickType,
	FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	if (!CachedOwnerPawn || !UpdatedComponent || ShouldSkipUpdate(DeltaTime))
	{
		return;
	}

	// 1. 지난 프레임에 밟고 있던 발판이 움직였다면 Pawn도 그만큼 먼저 따라갑니다.
	// CharacterMovement의 Movement Base 개념을 간단하게 흉내낸 부분입니다.
	ApplyGroundActorMovement();
	// 2. 현재 프레임 기준으로 바닥에 있는지, 바닥 Normal이 무엇인지 다시 확인합니다.
	CheckGround();
	// 3. 입력 이동, 중력, 점프, 충돌, 슬라이딩을 한 번에 처리합니다.
	ApplyMovement(DeltaTime);
}

void USpartaMoveComponent::SetMoveInput(const FVector2D&amp;amp; Value)
{
	MoveInput = Value;
}

void USpartaMoveComponent::StartJump()
{
	// 바닥에 있을 때만 점프를 시작합니다.
	// 점프는 실제 물리 힘이 아니라 VerticalVelocity 값을 직접 올리는 방식입니다.
	if (bIsGrounded)
	{
		VerticalVelocity = JumpPower;
		bIsGrounded = false;
	}
}

void USpartaMoveComponent::StopJump()
{
	// 점프 키를 떼면 상승 속도를 줄여서 짧은 점프가 가능하게 합니다.
	if (VerticalVelocity &amp;gt; JumpPower * 0.5f)
	{
		VerticalVelocity = JumpPower * 0.5f;
	}
}

void USpartaMoveComponent::StartSprint()
{
	bIsSprinting = true;
}

void USpartaMoveComponent::StopSprint()
{
	bIsSprinting = false;
}

void USpartaMoveComponent::CheckGround()
{
	if (!CachedOwnerPawn)
	{
		return;
	}

	UWorld* World = CachedOwnerPawn-&amp;gt;GetWorld();
	if (!World)
	{
		return;
	}

	const FVector Start = CachedOwnerPawn-&amp;gt;GetActorLocation();
	const FVector End = Start - FVector(0.f, 0.f, GroundCheckDistance);

	FHitResult Hit;
	FCollisionQueryParams Params(SCENE_QUERY_STAT(SpartaGroundCheck), false, CachedOwnerPawn);

	// CharacterMovement의 FloorCheck를 직접 흉내내는 부분입니다.
	// Pawn 중심에서 아래로 레이를 쏴서 걸을 수 있는 바닥인지 확인합니다.
	//
	// Hit.Normal.Z가 1에 가까우면 평평한 바닥이고,
	// 0에 가까우면 수직 벽입니다.
	// WalkableFloorZ 이상이면 걸을 수 있는 바닥이나 경사로 판단합니다.
	const bool bHit = World-&amp;gt;LineTraceSingleByChannel(
		Hit,
		Start,
		End,
		ECC_Visibility,
		Params
	);

	if (bHit &amp;amp;&amp;amp; Hit.Normal.Z &amp;gt;= WalkableFloorZ &amp;amp;&amp;amp; VerticalVelocity &amp;lt;= 0.f)
	{
		bIsGrounded = true;
		// 바닥의 기울기를 저장해두면 이동 벡터를 바닥면에 맞춰 보정할 수 있습니다.
		GroundNormal = Hit.Normal;
		// Hit.ImpactPoint.Z는 바닥 표면 높이입니다.
		// Pawn의 ActorLocation은 Capsule 중심이므로 HalfHeight를 더해야 바닥 위에 서게 됩니다.
		GroundZ = Hit.ImpactPoint.Z + CachedCapsule-&amp;gt;GetScaledCapsuleHalfHeight();
		// 바닥 Actor를 저장해두면, 다음 Tick에서 움직이는 발판의 이동량을 따라갈 수 있습니다.
		GroundActor = Hit.GetActor();
		if (GroundActor)
		{
			LastGroundActorLocation = GroundActor-&amp;gt;GetActorLocation();
		}
	}
	else
	{
		bIsGrounded = false;
		GroundNormal = FVector::UpVector;
		GroundActor = nullptr;
	}
}

void USpartaMoveComponent::ApplyMovement(float DeltaTime)
{
	if (!CachedOwnerPawn || !UpdatedComponent)
	{
		return;
	}

	CurrentSpeed = bIsSprinting ? MoveSpeed * SprintMultiplier : MoveSpeed;

	// 바닥에 있을 때는 아래로 떨어지는 속도가 계속 누적되지 않도록 0으로 정리합니다.
	// 공중이면 매 프레임 중력을 빼서 위로 올라가던 속도가 줄고, 결국 아래로 떨어지게 합니다.
	if (bIsGrounded &amp;amp;&amp;amp; VerticalVelocity &amp;lt; 0.f)
	{
		VerticalVelocity = 0.f;
	}
	else if (!bIsGrounded)
	{
		// 공중에 있을 때만 중력을 직접 적용합니다.
		VerticalVelocity -= Gravity * DeltaTime;
	}

	// MoveInput.X/Y를 Pawn의 Right/Forward 방향으로 변환해서 월드 이동량을 만듭니다.
	// X는 좌우, Y는 앞뒤 입력입니다.
	FVector WorldHorizontalDelta = CachedOwnerPawn-&amp;gt;GetActorForwardVector() * MoveInput.Y;
	WorldHorizontalDelta += CachedOwnerPawn-&amp;gt;GetActorRightVector() * MoveInput.X;
	WorldHorizontalDelta = WorldHorizontalDelta.GetClampedToMaxSize(1.f) * CurrentSpeed * DeltaTime;

	if (bIsGrounded)
	{
		// 바닥면에 이동 벡터를 투영합니다.
		// 이렇게 해야 울퉁불퉁한 바닥이나 경사에서 이동 방향이 바닥을 따라갑니다.
		// 즉, 바닥 안으로 파고드는 성분은 제거하고 바닥 표면을 따라가는 성분만 남깁니다.
		WorldHorizontalDelta = FVector::VectorPlaneProject(WorldHorizontalDelta, GroundNormal);
	}

	// 수평 입력 이동량과 수직 점프/중력 이동량을 합쳐 이번 프레임 최종 이동량을 만듭니다.
	const FVector WorldVerticalDelta(0.f, 0.f, VerticalVelocity * DeltaTime);
	const FVector DesiredDelta = WorldHorizontalDelta + WorldVerticalDelta;

	if (DesiredDelta.IsNearlyZero())
	{
		return;
	}

	FHitResult Hit;
	// Capsule을 Sweep 이동합니다.
	// 단순 순간이동이 아니라 이동 경로에 벽/바닥이 있으면 Hit에 충돌 정보가 들어옵니다.
	// Sweep은 물리 시뮬레이션이 아니라 충돌 쿼리입니다.
	SafeMoveUpdatedComponent(DesiredDelta, UpdatedComponent-&amp;gt;GetComponentQuat(), true, Hit);

	if (!Hit.IsValidBlockingHit())
	{
		return;
	}

	if (Hit.Normal.Z &amp;gt;= WalkableFloorZ &amp;amp;&amp;amp; VerticalVelocity &amp;lt;= 0.f)
	{
		// 충돌한 면이 걸을 수 있는 바닥이면 새로운 바닥으로 저장합니다.
		bIsGrounded = true;
		GroundNormal = Hit.Normal;
		GroundZ = Hit.ImpactPoint.Z + CachedCapsule-&amp;gt;GetScaledCapsuleHalfHeight();
		VerticalVelocity = 0.f;
		GroundActor = Hit.GetActor();
		if (GroundActor)
		{
			LastGroundActorLocation = GroundActor-&amp;gt;GetActorLocation();
		}
	}
	else
	{
		// 벽이나 너무 가파른 경사면에 부딪힌 경우입니다.
		// 이때는 바닥으로 보지 않아서 중력/낙하가 유지됩니다.
		bIsGrounded = false;
		GroundNormal = FVector::UpVector;
		GroundActor = nullptr;
	}

	// 벽에 딱 붙어서 멈추지 않고, 충돌한 표면을 따라 미끄러지도록 합니다.
	// 원리:
	// - 원래 이동 방향에서 벽 Normal 방향으로 들어가는 성분을 제거한다.
	// - 남은 이동량으로 벽면을 따라 이동한다.
	// Unreal의 MovementComponent가 제공하는 기본 슬라이딩 처리입니다.
	SlideAlongSurface(DesiredDelta, 1.f - Hit.Time, Hit.Normal, Hit, true);
}

void USpartaMoveComponent::ApplyGroundActorMovement()
{
	if (!bIsGrounded || !GroundActor || !UpdatedComponent)
	{
		return;
	}

	const FVector CurrentGroundActorLocation = GroundActor-&amp;gt;GetActorLocation();
	const FVector GroundDelta = CurrentGroundActorLocation - LastGroundActorLocation;
	LastGroundActorLocation = CurrentGroundActorLocation;

	if (GroundDelta.IsNearlyZero())
	{
		return;
	}

	FHitResult Hit;
	// 플레이어 입력 이동 전에, 발판이 움직인 거리만큼 Pawn을 먼저 이동시킵니다.
	// 이 처리가 없으면 위아래로 움직이는 플랫폼이 Pawn 밑에서 빠져나가게 됩니다.
	// 즉, &quot;발판 위에 서 있으면 발판의 월드 이동량을 Pawn에게도 적용한다&quot;는 뜻입니다.
	SafeMoveUpdatedComponent(GroundDelta, UpdatedComponent-&amp;gt;GetComponentQuat(), true, Hit);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpartaAircraft.h&lt;/p&gt;
&lt;pre id=&quot;code_1780978702937&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#pragma once

#include &quot;CoreMinimal.h&quot;
#include &quot;GameFramework/Pawn.h&quot;
#include &quot;SpartaAircraft.generated.h&quot;

class UCameraComponent;
class USpringArmComponent;
class UStaticMeshComponent;
struct FInputActionValue;

UCLASS()
class SPARTA3_4_API ASpartaAircraft : public APawn
{
	GENERATED_BODY()

public:
	// 6자유도 비행체 Pawn입니다.
	// 현재 회전 상태를 기준으로 전/후, 좌/우, 상/하 이동하고 Yaw/Pitch/Roll을 직접 계산합니다.
	ASpartaAircraft();

	// 비행체의 시각/충돌 루트 컴포넌트입니다.
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Components&quot;)
	UStaticMeshComponent* Mesh;

	// 카메라를 비행체 뒤에 배치하기 위한 SpringArm입니다.
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Components&quot;)
	USpringArmComponent* SpringArm;

	// 실제 플레이어 시야 카메라입니다.
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Components&quot;)
	UCameraComponent* Camera;

	// 지상 또는 일반 비행 상태에서의 기본 이동 속도입니다.
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Move&quot;)
	float MoveSpeed = 800.f;

	// 공중 상태에서 입력 이동 속도에 곱해지는 값입니다.
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Move&quot;)
	float AirControlMultiplier = 0.4f;

	// 마우스 X/Y로 적용되는 Yaw/Pitch 회전 속도입니다.
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Rotation&quot;)
	float LookSpeed = 90.f;

	// Roll 입력에 적용되는 회전 속도입니다.
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Rotation&quot;)
	float RollSpeed = 120.f;

	// 공중에서 매 프레임 적용할 인공 중력입니다.
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Gravity&quot;)
	float Gravity = 980.f;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Gravity&quot;)
	bool bEnableArtificialGravity = false;

	// 아래 방향 지면 체크 거리입니다.
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Gravity&quot;)
	float GroundCheckDistance = 120.f;

	// 이 값 이상이면 착지 가능한 지면으로 판단합니다.
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Gravity&quot;)
	float WalkableFloorZ = 0.6f;

protected:
	virtual void BeginPlay() override;
	virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override;

public:
	virtual void Tick(float DeltaTime) override;

private:
	// MoveInput.X: 좌우, MoveInput.Y: 전후 입력입니다.
	FVector2D MoveInput = FVector2D::ZeroVector;
	// LookInput.X: Yaw, LookInput.Y: Pitch 입력입니다.
	FVector2D LookInput = FVector2D::ZeroVector;
	// 상하 이동 입력입니다. Space는 +1, Shift는 -1로 사용합니다.
	float VerticalInput = 0.f;
	// Roll 입력입니다. 마우스 휠 또는 별도 키 Axis 값으로 사용합니다.
	float RollInput = 0.f;
	// 인공 중력으로 누적되는 월드 Z축 속도입니다.
	float FallingVelocity = 0.f;
	bool bIsGrounded = false;

	void Move(const FInputActionValue&amp;amp; Value);
	void Look(const FInputActionValue&amp;amp; Value);
	void StartMoveUp(const FInputActionValue&amp;amp; Value);
	void StopMoveUp(const FInputActionValue&amp;amp; Value);
	void StartMoveDown(const FInputActionValue&amp;amp; Value);
	void StopMoveDown(const FInputActionValue&amp;amp; Value);

	void CheckGround();
	void ApplyMovement(float DeltaTime);
	void ApplyRotation(float DeltaTime);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpartaAircraft.cpp&lt;/p&gt;
&lt;pre id=&quot;code_1780978812623&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &quot;SpartaAircraft.h&quot;

#include &quot;EnhancedInputComponent.h&quot;
#include &quot;SpartaPlayerContrller.h&quot;
#include &quot;Camera/CameraComponent.h&quot;
#include &quot;Components/StaticMeshComponent.h&quot;
#include &quot;GameFramework/SpringArmComponent.h&quot;

ASpartaAircraft::ASpartaAircraft()
{
	PrimaryActorTick.bCanEverTick = true;

	// StaticMesh를 루트로 사용해서 Sweep 충돌 검사를 받을 수 있게 합니다.
	Mesh = CreateDefaultSubobject&amp;lt;UStaticMeshComponent&amp;gt;(TEXT(&quot;AircraftMesh&quot;));
	SetRootComponent(Mesh);
	Mesh-&amp;gt;SetCollisionEnabled(ECollisionEnabled::QueryOnly);
	Mesh-&amp;gt;SetCollisionResponseToAllChannels(ECR_Block);
	Mesh-&amp;gt;SetSimulatePhysics(false);

	// 비행체 뒤쪽 카메라입니다. 비행체가 회전하면 카메라도 함께 따라갑니다.
	SpringArm = CreateDefaultSubobject&amp;lt;USpringArmComponent&amp;gt;(TEXT(&quot;SpringArm&quot;));
	SpringArm-&amp;gt;SetupAttachment(Mesh);
	SpringArm-&amp;gt;TargetArmLength = 600.f;
	SpringArm-&amp;gt;bUsePawnControlRotation = false;
	SpringArm-&amp;gt;bDoCollisionTest = false;

	Camera = CreateDefaultSubobject&amp;lt;UCameraComponent&amp;gt;(TEXT(&quot;Camera&quot;));
	Camera-&amp;gt;SetupAttachment(SpringArm, USpringArmComponent::SocketName);
	Camera-&amp;gt;bUsePawnControlRotation = false;
}

void ASpartaAircraft::BeginPlay()
{
	Super::BeginPlay();
}

void ASpartaAircraft::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (bEnableArtificialGravity)
	{
		CheckGround();
	}
	ApplyRotation(DeltaTime);
	ApplyMovement(DeltaTime);
}

void ASpartaAircraft::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	if (UEnhancedInputComponent* EnhancedInput = Cast&amp;lt;UEnhancedInputComponent&amp;gt;(PlayerInputComponent))
	{
		if (ASpartaPlayerContrller* PlayerController = Cast&amp;lt;ASpartaPlayerContrller&amp;gt;(GetController()))
		{
			// MoveAction(Vector2D): W/S는 전후, A/D는 좌우 이동입니다.
			EnhancedInput-&amp;gt;BindAction(PlayerController-&amp;gt;MoveAction, ETriggerEvent::Triggered, this, &amp;amp;ASpartaAircraft::Move);
			EnhancedInput-&amp;gt;BindAction(PlayerController-&amp;gt;MoveAction, ETriggerEvent::Completed, this, &amp;amp;ASpartaAircraft::Move);

			// LookAction(Vector2D): 마우스 X는 Yaw, 마우스 Y는 Pitch 회전입니다.
			EnhancedInput-&amp;gt;BindAction(PlayerController-&amp;gt;LookAction, ETriggerEvent::Triggered, this, &amp;amp;ASpartaAircraft::Look);
			EnhancedInput-&amp;gt;BindAction(PlayerController-&amp;gt;LookAction, ETriggerEvent::Completed, this, &amp;amp;ASpartaAircraft::Look);

			// JumpAction: Space 입력을 위쪽 이동으로 사용합니다.
			EnhancedInput-&amp;gt;BindAction(PlayerController-&amp;gt;JumpAction, ETriggerEvent::Triggered, this, &amp;amp;ASpartaAircraft::StartMoveUp);
			EnhancedInput-&amp;gt;BindAction(PlayerController-&amp;gt;JumpAction, ETriggerEvent::Completed, this, &amp;amp;ASpartaAircraft::StopMoveUp);

			// SprintAction: Shift 입력을 아래쪽 이동으로 사용합니다.
			EnhancedInput-&amp;gt;BindAction(PlayerController-&amp;gt;SprintAction, ETriggerEvent::Triggered, this, &amp;amp;ASpartaAircraft::StartMoveDown);
			EnhancedInput-&amp;gt;BindAction(PlayerController-&amp;gt;SprintAction, ETriggerEvent::Completed, this, &amp;amp;ASpartaAircraft::StopMoveDown);
		}
	}
}

void ASpartaAircraft::Move(const FInputActionValue&amp;amp; Value)
{
	MoveInput = Value.Get&amp;lt;FVector2D&amp;gt;();
}

void ASpartaAircraft::Look(const FInputActionValue&amp;amp; Value)
{
	LookInput = Value.Get&amp;lt;FVector2D&amp;gt;();
}

void ASpartaAircraft::StartMoveUp(const FInputActionValue&amp;amp; Value)
{
	if (Value.Get&amp;lt;bool&amp;gt;())
	{
		VerticalInput = 1.f;
		bEnableArtificialGravity = false;
	}
}

void ASpartaAircraft::StopMoveUp(const FInputActionValue&amp;amp; Value)
{
	if (VerticalInput &amp;gt; 0.f)
	{
		VerticalInput = 0.f;
	}
}

void ASpartaAircraft::StartMoveDown(const FInputActionValue&amp;amp; Value)
{
	if (Value.Get&amp;lt;bool&amp;gt;())
	{
		VerticalInput = -1.f;
		bEnableArtificialGravity = true;
	}
}

void ASpartaAircraft::StopMoveDown(const FInputActionValue&amp;amp; Value)
{
	if (VerticalInput &amp;lt; 0.f)
	{
		VerticalInput = 0.f;
	}
}

void ASpartaAircraft::CheckGround()
{
	UWorld* World = GetWorld();
	if (!World)
	{
		return;
	}

	const FVector Start = GetActorLocation();
	const FVector End = Start - FVector(0.f, 0.f, GroundCheckDistance);

	FHitResult Hit;
	FCollisionQueryParams Params(SCENE_QUERY_STAT(AircraftGroundCheck), false, this);

	const bool bHit = World-&amp;gt;LineTraceSingleByChannel(
		Hit,
		Start,
		End,
		ECC_Visibility,
		Params
	);

	bIsGrounded = bHit &amp;amp;&amp;amp; Hit.Normal.Z &amp;gt;= WalkableFloorZ &amp;amp;&amp;amp; FallingVelocity &amp;lt;= 0.f;

	if (bIsGrounded)
	{
		// 착지한 순간 낙하 속도를 초기화합니다.
		FallingVelocity = 0.f;
	}
}

void ASpartaAircraft::ApplyMovement(float DeltaTime)
{
	const float ControlMultiplier = !bIsGrounded
		? AirControlMultiplier
		: 1.f;
	const float CurrentMoveSpeed = MoveSpeed * ControlMultiplier;

	// 현재 Actor 회전 기준의 로컬 축으로 이동합니다.
	FVector WorldMoveDelta = GetActorForwardVector() * MoveInput.Y;
	WorldMoveDelta += GetActorRightVector() * MoveInput.X;
	WorldMoveDelta += GetActorUpVector() * VerticalInput;
	WorldMoveDelta = WorldMoveDelta.GetClampedToMaxSize(1.f) * CurrentMoveSpeed * DeltaTime;

	if (bEnableArtificialGravity &amp;amp;&amp;amp; !bIsGrounded)
	{
		// 인공 중력은 월드 아래 방향으로 적용합니다.
		FallingVelocity -= Gravity * DeltaTime;
	}

	if (bEnableArtificialGravity)
	{
		WorldMoveDelta.Z += FallingVelocity * DeltaTime;
	}

	if (WorldMoveDelta.IsNearlyZero())
	{
		return;
	}

	FHitResult Hit;
	AddActorWorldOffset(WorldMoveDelta, true, &amp;amp;Hit);

	if (Hit.IsValidBlockingHit())
	{
		if (bEnableArtificialGravity &amp;amp;&amp;amp; Hit.Normal.Z &amp;gt;= WalkableFloorZ &amp;amp;&amp;amp; FallingVelocity &amp;lt;= 0.f)
		{
			bIsGrounded = true;
			FallingVelocity = 0.f;
			return;
		}

		// 벽이나 가파른 면에 닿으면 표면을 따라 미끄러지게 이동합니다.
		const FVector RemainingDelta = WorldMoveDelta * (1.f - Hit.Time);
		const FVector SlideDelta = FVector::VectorPlaneProject(RemainingDelta, Hit.Normal);
		AddActorWorldOffset(SlideDelta, true);
	}
}

void ASpartaAircraft::ApplyRotation(float DeltaTime)
{
	if (LookInput.IsNearlyZero() &amp;amp;&amp;amp; FMath::IsNearlyZero(RollInput))
	{
		return;
	}

	// 6DOF 회전은 현재 비행체 로컬 축을 기준으로 누적됩니다.
	const float PitchDelta = -LookInput.Y * LookSpeed * DeltaTime;
	const float YawDelta = LookInput.X * LookSpeed * DeltaTime;
	const float RollDelta = RollInput * RollSpeed * DeltaTime;

	AddActorLocalRotation(FRotator(PitchDelta, YawDelta, RollDelta), true);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>언리얼/스파르타 클럽</category>
      <author>코드망령</author>
      <guid isPermaLink="true">https://codeghost.tistory.com/39</guid>
      <comments>https://codeghost.tistory.com/39#entry39comment</comments>
      <pubDate>Tue, 9 Jun 2026 13:23:42 +0900</pubDate>
    </item>
    <item>
      <title>동적할당</title>
      <link>https://codeghost.tistory.com/38</link>
      <description>&lt;h1&gt;동적 할당 (Dynamic Allocation)&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;동적 할당이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램 실행 중에 필요한 메모리를 직접 할당하는 것을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적으로 할당된 메모리는 &lt;b&gt;힙(Heap) 영역&lt;/b&gt;에 저장되며,&lt;br /&gt;개발자가 직접 생성하고 해제해야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;메모리 할당&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int* p = new int;
*p = 10;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동작 순서&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;힙 영역에 int 크기의 메모리 할당&lt;/li&gt;
&lt;li&gt;할당된 주소를 p에 저장&lt;/li&gt;
&lt;li&gt;해당 메모리에 10 저장&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;메모리 해제&lt;/h2&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;delete p;
p = nullptr;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동작 순서&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;힙 메모리 반환&lt;/li&gt;
&lt;li&gt;포인터는 더 이상 유효하지 않음&lt;/li&gt;
&lt;li&gt;nullptr로 초기화하는 것이 안전함&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배열 동적 할당&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int* arr = new int[5];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 int 5개를 저장할 수 있는 배열을 힙에 생성한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배열 메모리 해제&lt;/h2&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;delete[] arr;
arr = nullptr;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열은 반드시 &lt;code&gt;delete[]&lt;/code&gt;를 사용해야 한다.&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;delete arr; // 잘못된 사용&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;C 방식 동적 할당&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메모리 할당&lt;/h3&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;int* p = (int*)malloc(sizeof(int));
*p = 10;&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메모리 해제&lt;/h3&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;free(p);&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;new 와 malloc 차이&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;new&lt;/h3&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;int* p = new int;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특징&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 할당&lt;/li&gt;
&lt;li&gt;생성자 호출&lt;/li&gt;
&lt;li&gt;타입 캐스팅 불필요&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;malloc&lt;/h3&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;int* p = (int*)malloc(sizeof(int));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특징&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단순 메모리만 할당&lt;/li&gt;
&lt;li&gt;생성자 호출 안 함&lt;/li&gt;
&lt;li&gt;형변환 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;delete 와 free 차이&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;delete&lt;/h3&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;delete p;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특징&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소멸자 호출&lt;/li&gt;
&lt;li&gt;메모리 해제&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;free&lt;/h3&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;free(p);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특징&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리만 해제&lt;/li&gt;
&lt;li&gt;소멸자 호출 안 함&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;절대 혼용하면 안 됨&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;올바른 사용&lt;/h3&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;int* p = new int;
delete p;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;int* p = (int*)malloc(sizeof(int));
free(p);&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;잘못된 사용&lt;/h3&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;int* p = new int;
free(p); // 위험&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;int* p = (int*)malloc(sizeof(int));
delete p; // 위험&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;동적 할당의 특징&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실행 중 크기 결정 가능&lt;/li&gt;
&lt;li&gt;큰 메모리 사용 가능&lt;/li&gt;
&lt;li&gt;프로그램 유연성 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;int size;
cin &amp;gt;&amp;gt; size;

int* arr = new int[size];&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;직접 해제해야 함&lt;/li&gt;
&lt;li&gt;메모리 누수 발생 가능&lt;/li&gt;
&lt;li&gt;관리가 어려움&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;메모리 누수 (Memory Leak)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적으로 할당한 메모리를 해제하지 않으면 발생한다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void Func()
{
    int* p = new int;

} // delete 없음&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 함수가 끝나도 힙 메모리가 반환되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 메모리 누수라고 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Dangling Pointer (댕글링 포인터)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해제된 메모리를 계속 가리키는 포인터&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;int* p = new int;

delete p;

*p = 10; // 위험&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결 방법&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;delete p;
p = nullptr;&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;객체 동적 생성&lt;/h2&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;class Player
{
public:
    Player()
    {
        cout &amp;lt;&amp;lt; &quot;생성자 호출\n&quot;;
    }

    ~Player()
    {
        cout &amp;lt;&amp;lt; &quot;소멸자 호출\n&quot;;
    }
};

Player* p = new Player();

delete p;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;생성자 호출
소멸자 호출&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스마트 포인터 등장 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 방식&lt;/p&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;Player* p = new Player();

// delete를 깜빡할 수 있음&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현대 C++&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::shared_ptr&amp;lt;Player&amp;gt; p = std::make_shared&amp;lt;Player&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::unique_ptr&amp;lt;Player&amp;gt; p = std::make_unique&amp;lt;Player&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리를 자동으로 관리해준다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;메모리 영역과의 관계&lt;/h2&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;메모리 구조

┌─────────────┐
│ 코드 영역    │
├─────────────┤
│ 데이터 영역  │
├─────────────┤
│ 스택 영역    │ &amp;larr; 지역 변수
├─────────────┤
│ 힙 영역      │ &amp;larr; 동적 할당
└─────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int a = 10;            // 스택
int* p = new int(20);  // 힙&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 정리&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메모리 할당&lt;/h3&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;int* p = new int;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메모리 해제&lt;/h3&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;delete p;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배열 할당&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int* arr = new int[5];&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배열 해제&lt;/h3&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;delete[] arr;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;C 방식&lt;/h3&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;malloc()
free()&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;C++ 방식&lt;/h3&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;new
delete&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;한 줄 요약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적 할당은 &lt;b&gt;프로그램 실행 중 힙(Heap) 메모리를 직접 생성하고 관리하는 방식&lt;/b&gt;이며, 생성한 메모리는 반드시 직접 해제해야 한다.&lt;/p&gt;</description>
      <category>c++/기초</category>
      <author>코드망령</author>
      <guid isPermaLink="true">https://codeghost.tistory.com/38</guid>
      <comments>https://codeghost.tistory.com/38#entry38comment</comments>
      <pubDate>Mon, 8 Jun 2026 09:50:45 +0900</pubDate>
    </item>
    <item>
      <title>const</title>
      <link>https://codeghost.tistory.com/37</link>
      <description>&lt;h1&gt;const 정리&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. const 기본 개념&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;const&lt;/code&gt;는 값을 변경할 수 없게 만드는 키워드이다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const int a = 10;

a = 20; // 오류&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;읽기 전용 변수(상수)&lt;/li&gt;
&lt;li&gt;선언 후 값 변경 불가능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. const를 사용하는 이유&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;값 변경 방지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실수로 데이터를 수정하는 것을 막을 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드 의도 명확화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 값이 변경되면 안 된다는 것을 명확하게 표현할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;최적화 가능&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일러가 추가 최적화를 수행할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 포인터에서 const&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(1) 값이 const&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const int* p = &amp;amp;a;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int const* p = &amp;amp;a;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의미&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;포인터가 가리키는 값 변경 불가&lt;/li&gt;
&lt;li&gt;포인터 주소 변경 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;*p = 20; // 오류
p = &amp;amp;b;  // 가능&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(2) 포인터 자체가 const&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int* const p = &amp;amp;a;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의미&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;포인터 주소 변경 불가&lt;/li&gt;
&lt;li&gt;가리키는 값 변경 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;*p = 20; // 가능
p = &amp;amp;b;  // 오류&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(3) 둘 다 const&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const int* const p = &amp;amp;a;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의미&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가리키는 값 변경 불가&lt;/li&gt;
&lt;li&gt;포인터 주소 변경 불가&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;*p = 20; // 오류
p = &amp;amp;b;  // 오류&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 함수에서 const&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(1) 값 전달&lt;/h3&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;void func(const int x);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의미&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수 내부에서 x 변경 불가&lt;/li&gt;
&lt;li&gt;복사본을 받기 때문에 큰 의미는 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;void func(const int x)
{
    x = 10; // 오류
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(2) 참조 전달 (중요)&lt;/h3&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;void func(const int&amp;amp; x);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의미&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복사 발생하지 않음&lt;/li&gt;
&lt;li&gt;원본 수정 불가&lt;/li&gt;
&lt;li&gt;성능 향상&lt;/li&gt;
&lt;li&gt;안전성 향상&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;void func(const int&amp;amp; x)
{
    // x = 10; // 오류
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서 가장 많이 사용하는 형태 중 하나이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;객체 참조 전달&lt;/h3&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void PrintPlayer(const Player&amp;amp; player);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의미&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체 복사 없음&lt;/li&gt;
&lt;li&gt;원본 수정 불가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰 객체를 함수에 전달할 때 자주 사용한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 클래스에서 const 함수&lt;/h2&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;class A
{
public:
    int Get() const;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의미&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;멤버 변수 수정 불가&lt;/li&gt;
&lt;li&gt;const 객체에서도 호출 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class A
{
public:
    int Value = 10;

    int Get() const
    {
        return Value;
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;const 객체&lt;/h3&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;const A Obj;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const 객체는 const 함수만 호출 가능하다.&lt;/p&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;Obj.Get(); // 가능&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;Obj.Set(); // 오류&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;this 포인터 변화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 함수&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;A* const this&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const 함수&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;const A* const this&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, const 함수 내부에서는 객체를 수정할 수 없다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. const vs #define&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;#define&lt;/h3&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;#define SIZE 10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특징&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전처리기 치환&lt;/li&gt;
&lt;li&gt;타입 없음&lt;/li&gt;
&lt;li&gt;디버깅 어려움&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;const&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const int Size = 10;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특징&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;타입 존재&lt;/li&gt;
&lt;li&gt;컴파일러가 타입 검사 가능&lt;/li&gt;
&lt;li&gt;디버깅 쉬움&lt;/li&gt;
&lt;li&gt;더 안전함&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비교&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;#define&lt;/th&gt;
&lt;th&gt;const&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;타입&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;td&gt;있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;디버깅&lt;/td&gt;
&lt;td&gt;어려움&lt;/td&gt;
&lt;td&gt;쉬움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;타입 검사&lt;/td&gt;
&lt;td&gt;불가능&lt;/td&gt;
&lt;td&gt;가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;안전성&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;td&gt;높음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;사용 권장&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. const와 constexpr 차이&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;const&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 중에도 값이 결정될 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const int x = GetValue();&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;constexpr&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일 시간에 값이 확정되어야 한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;constexpr int x = 10;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;constexpr int Size = 100;
int Arr[Size];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일 타임 상수로 사용할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 실무에서 자주 보는 형태&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문자열 전달&lt;/h3&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void Print(const std::string&amp;amp; Str);&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;객체 전달&lt;/h3&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void Update(const Player&amp;amp; PlayerData);&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Getter&lt;/h3&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;int GetHP() const;&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;읽기 전용 포인터&lt;/h3&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;const Player* Target;&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. const 읽는 방법&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const int* p;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; &quot;p가 가리키는 int는 const&quot;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int* const p;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; &quot;p 자체가 const&quot;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const int* const p;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; &quot;가리키는 값도 const, 포인터도 const&quot;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 요약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;const&lt;/code&gt;는 값을 변경할 수 없게 만드는 키워드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 중요한 형태&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const int&amp;amp;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;const std::string&amp;amp;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;const Player&amp;amp;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const int*&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int* const&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const int* const&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;int GetHP() const;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 &lt;b&gt;&quot;수정할 필요가 없는 값은 최대한 const를 붙이는 것&quot;&lt;/b&gt; 이 일반적인 코딩 스타일이다.&lt;/p&gt;</description>
      <category>c++/기초</category>
      <author>코드망령</author>
      <guid isPermaLink="true">https://codeghost.tistory.com/37</guid>
      <comments>https://codeghost.tistory.com/37#entry37comment</comments>
      <pubDate>Mon, 8 Jun 2026 09:49:20 +0900</pubDate>
    </item>
    <item>
      <title>값 타입과 참조타입</title>
      <link>https://codeghost.tistory.com/36</link>
      <description>&lt;h1&gt;값 타입(Value Type)과 참조 타입(Reference Type)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 다루는 방식은 크게 &lt;b&gt;값을 직접 저장하는 방식&lt;/b&gt;과 &lt;b&gt;어딘가에 있는 데이터를 참조하는 방식&lt;/b&gt;으로 나눌 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;값 타입 (Value Type)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 자체를 직접 저장하는 타입이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수를 복사하면 데이터도 함께 복사된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예시&lt;/h2&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;int a = 10;
int b = a;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;a = 10
b = 10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;b&lt;/code&gt;는 &lt;code&gt;a&lt;/code&gt;의 값을 복사하여 별도의 데이터를 가진다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;값 변경&lt;/h2&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;int a = 10;
int b = a;

b = 100;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;a = 10
b = 100&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본은 변경되지 않는다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;메모리 구조&lt;/h2&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;┌──────┐
│ a=10 │
└──────┘

┌──────┐
│ b=10 │
└──────┘&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 다른 메모리를 가진다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;대표적인 값 타입&lt;/h2&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;char
short
int
long
long long
float
double
bool&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 포인터가 없는 일반 구조체도 값 타입처럼 동작한다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;struct Player
{
    int HP;
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;값 전달 (Call by Value)&lt;/h1&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void Func(int x)
{
    x = 100;
}

int main()
{
    int num = 10;

    Func(num);

    std::cout &amp;lt;&amp;lt; num;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값이 복사되었기 때문에 원본은 변경되지 않는다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;참조 타입 (Reference Type)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 자체를 저장하는 것이 아니라 데이터의 위치를 참조하는 타입이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참조 변수&lt;/h2&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;int value = 10;

int&amp;amp; ref = value;&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;메모리 구조&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;ref ───► value
          │
          ▼
         10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;ref&lt;/code&gt;는 &lt;code&gt;value&lt;/code&gt;의 별명(Alias)이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;값 변경&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int value = 10;

int&amp;amp; ref = value;

ref = 100;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;value = 100&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본이 변경된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;참조 전달 (Call by Reference)&lt;/h1&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;void Func(int&amp;amp; x)
{
    x = 100;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용&lt;/p&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;int num = 10;

Func(num);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;100&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본 데이터가 변경된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;포인터도 참조 개념을 사용한다&lt;/h1&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;int value = 10;

int* ptr = &amp;amp;value;&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;메모리 구조&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;ptr ───► value
          │
          ▼
         10&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;값 변경&lt;/h2&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;*ptr = 100;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;value = 100&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본 데이터가 변경된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;값 타입과 참조 타입 비교&lt;/h1&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;값 타입&lt;/th&gt;
&lt;th&gt;참조 타입&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;저장 내용&lt;/td&gt;
&lt;td&gt;데이터 자체&lt;/td&gt;
&lt;td&gt;데이터의 위치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;복사 시&lt;/td&gt;
&lt;td&gt;값 복사&lt;/td&gt;
&lt;td&gt;같은 데이터 참조&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;원본 변경&lt;/td&gt;
&lt;td&gt;불가능&lt;/td&gt;
&lt;td&gt;가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메모리 사용&lt;/td&gt;
&lt;td&gt;별도 메모리&lt;/td&gt;
&lt;td&gt;같은 메모리 공유&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;대표 예시&lt;/td&gt;
&lt;td&gt;int, float, double&lt;/td&gt;
&lt;td&gt;참조자(&amp;amp;), 포인터(*)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;값 캡처와 참조 캡처 (람다)&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;값 캡처&lt;/h2&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;int x = 10;

auto lambda = [x]()
{
    std::cout &amp;lt;&amp;lt; x;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;람다가 생성될 때 값이 복사된다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;x = 100;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해도&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;lambda();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;10&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참조 캡처&lt;/h2&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;int x = 10;

auto lambda = [&amp;amp;x]()
{
    x = 100;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 후&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;x = 100&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본이 변경된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;얕은 복사와의 차이&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값 타입과 참조 타입은 얕은 복사(Shallow Copy)와는 다른 개념이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;값 복사&lt;/h2&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;int a = 10;
int b = a;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;a = 10
b = 10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 독립적이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;얕은 복사&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class Player
{
public:
    int* HP;
};&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;Player p2 = p1;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;p1.HP ----+
          |
          v
        [100]
          ^
          |
p2.HP ----+&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 메모리를 가리킨다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;값 타입은 데이터를 직접 저장한다.&lt;/li&gt;
&lt;li&gt;값을 복사하면 별도의 데이터가 생성된다.&lt;/li&gt;
&lt;li&gt;참조 타입은 데이터의 위치를 참조한다.&lt;/li&gt;
&lt;li&gt;참조 타입은 원본 데이터를 수정할 수 있다.&lt;/li&gt;
&lt;li&gt;참조자(&lt;code&gt;&amp;amp;&lt;/code&gt;)와 포인터(&lt;code&gt;*&lt;/code&gt;)는 참조 개념을 사용한다.&lt;/li&gt;
&lt;li&gt;값 전달(Call by Value)은 원본이 변경되지 않는다.&lt;/li&gt;
&lt;li&gt;참조 전달(Call by Reference)은 원본이 변경된다.&lt;/li&gt;
&lt;li&gt;람다의 &lt;code&gt;[x]&lt;/code&gt;는 값 캡처, &lt;code&gt;[&amp;amp;x]&lt;/code&gt;는 참조 캡처이다.&lt;/li&gt;
&lt;li&gt;값 타입/참조 타입과 얕은 복사/깊은 복사는 서로 다른 개념이다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>c++/기초</category>
      <author>코드망령</author>
      <guid isPermaLink="true">https://codeghost.tistory.com/36</guid>
      <comments>https://codeghost.tistory.com/36#entry36comment</comments>
      <pubDate>Mon, 8 Jun 2026 09:47:35 +0900</pubDate>
    </item>
    <item>
      <title>extern (외부 변수)</title>
      <link>https://codeghost.tistory.com/35</link>
      <description>&lt;h1&gt;extern (외부 변수)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 파일이 아닌 다른 파일에 존재하는 변수나 함수를 참조하기 위한 키워드&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;외부 변수란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 파일의 코드 외에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 파일에 존재하는 변수를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예)&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// A.cpp

int a = 10;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;// B.cpp

extern int a;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;B.cpp&lt;/code&gt;에서는 실제 변수는 없지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;extern&lt;/code&gt;을 통해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;어딘가에 존재하는 변수 a를 사용하겠다&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 컴파일러에게 알려주는 것이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;사용 예제&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;변수 정의&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// A.cpp

int a = 10;&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;변수 참조&lt;/h2&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;// B.cpp

extern int a;

void Func()
{
    a = 20;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문법적으로 아무 문제가 없다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;extern의 동작 방식&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;컴파일 단계&lt;/h2&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;extern int a;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일러는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;어딘가에 int형 변수 a가 존재한다고 가정&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하고 코드를 컴파일한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 실제 변수의 존재 여부를 검사하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;extern int a;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만 있어도 컴파일은 성공한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;링크 단계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일이 끝나면 링커(Linker)가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 변수 &lt;code&gt;a&lt;/code&gt;가 존재하는지 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예)&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// A.cpp

int a = 10;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;존재한다면&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;extern int a;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와 연결하여 정상적으로 실행된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;링크 오류 발생 예제&lt;/h1&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;// B.cpp

extern int a;

void Func()
{
    a = 20;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;// A.cpp

// int a = 10;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 실제 변수가 존재하지 않는다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일은 성공하지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;링크 단계에서&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;unresolved external symbol&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오류가 발생한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;왜 컴파일은 성공할까?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일러는 현재 파일만 본다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;extern int a;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;다른 파일에 정의되어 있겠지&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 생각하고 넘어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 존재 여부는 컴파일러가 아니라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;링커가 검사한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;extern 변수의 메모리&lt;/h1&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;extern int a;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;는 새로운 변수를 생성하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;extern int a;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;는&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;int a;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와 다르다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;변수 생성&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int a = 10;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 생성&lt;/li&gt;
&lt;li&gt;저장 공간 할당&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;extern 선언&lt;/h3&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;extern int a;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 생성 안 함&lt;/li&gt;
&lt;li&gt;저장 공간 할당 안 함&lt;/li&gt;
&lt;li&gt;이미 존재하는 변수를 참조&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;extern 함수&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수도 동일하게 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;// A.cpp

void Print()
{
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;// B.cpp

extern void Print();

int main()
{
    Print();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 함수는 기본적으로 외부 연결(External Linkage)을 가지므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 경우 &lt;code&gt;extern&lt;/code&gt;을 생략해도 된다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void Print();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와 동일하게 동작한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;extern&lt;/code&gt;은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 파일에 존재하는 변수나 함수를 참조하기 위한 키워드이다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;extern int a;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로운 변수를 생성하지 않음&lt;/li&gt;
&lt;li&gt;메모리를 할당하지 않음&lt;/li&gt;
&lt;li&gt;다른 파일의 변수를 참조만 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일 단계에서는&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;extern int a;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만 있어도 통과한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 링크 단계에서 실제 변수 정의를 찾지 못하면&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;unresolved external symbol&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오류가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴파일러 &amp;rarr; 문법 검사&lt;/li&gt;
&lt;li&gt;링커 &amp;rarr; 실제 정의 연결&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 수행한다고 이해하면 된다.&lt;/p&gt;</description>
      <category>c++/기초</category>
      <author>코드망령</author>
      <guid isPermaLink="true">https://codeghost.tistory.com/35</guid>
      <comments>https://codeghost.tistory.com/35#entry35comment</comments>
      <pubDate>Fri, 5 Jun 2026 09:33:54 +0900</pubDate>
    </item>
    <item>
      <title>분할 구조 (Header / Source File)</title>
      <link>https://codeghost.tistory.com/34</link>
      <description>&lt;h1&gt;분할 구조 (Header / Source File)&lt;/h1&gt;
&lt;p&gt;하나의 코드를 &lt;code&gt;.h&lt;/code&gt; 파일과 &lt;code&gt;.cpp&lt;/code&gt; 파일로 나누어 관리하는 방식&lt;/p&gt;
&lt;p&gt;일반적으로&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.h&lt;/code&gt; : 선언(Declaration)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.cpp&lt;/code&gt; : 구현(Definition)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;을 담당한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;장점&lt;/h1&gt;
&lt;h2&gt;코드 관리가 편해짐&lt;/h2&gt;
&lt;p&gt;기능별로 파일을 분리할 수 있다.&lt;/p&gt;
&lt;p&gt;예)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;Player.h
Player.cpp

Monster.h
Monster.cpp

Inventory.h
Inventory.cpp&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;파일 역할이 명확해져 유지보수가 쉬워진다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;협업 시 충돌 감소&lt;/h2&gt;
&lt;p&gt;여러 사람이 동시에 작업할 때&lt;br&gt;각자 다른 파일을 수정할 수 있다.&lt;/p&gt;
&lt;p&gt;예)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A : Player.cpp 수정&lt;/li&gt;
&lt;li&gt;B : Inventory.cpp 수정&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Git 충돌이 줄어든다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;재사용 가능&lt;/h2&gt;
&lt;p&gt;헤더만 include 하면&lt;br&gt;다른 코드에서도 쉽게 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;예)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;quot;Player.h&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;컴파일 속도 개선 가능&lt;/h2&gt;
&lt;p&gt;변경된 cpp 파일만 다시 컴파일하면 된다.&lt;/p&gt;
&lt;p&gt;예)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;Player.cpp 수정&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;↓&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;Player.cpp만 재컴파일&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;큰 프로젝트일수록 효과가 커진다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;단점&lt;/h1&gt;
&lt;h2&gt;파일 수 증가&lt;/h2&gt;
&lt;p&gt;기능 하나를 만들더라도&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;Player.h
Player.cpp&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;처럼 최소 두 개의 파일이 필요하다.&lt;/p&gt;
&lt;p&gt;프로젝트 규모가 커질수록 파일 관리가 복잡해질 수 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;include 남용 시 컴파일 속도 저하&lt;/h2&gt;
&lt;p&gt;헤더를 include하면&lt;br&gt;전처리 단계에서 해당 파일 내용이 복사된다.&lt;/p&gt;
&lt;p&gt;예)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;quot;A.h&amp;quot;
#include &amp;quot;B.h&amp;quot;
#include &amp;quot;C.h&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;↓&lt;/p&gt;
&lt;p&gt;컴파일 시&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;A.h 내용
B.h 내용
C.h 내용&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 전부 복사되어 하나의 파일처럼 처리된다.&lt;/p&gt;
&lt;p&gt;따라서 불필요하게 많은 헤더를 include하면&lt;br&gt;컴파일 속도가 느려질 수 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;순환 참조(Circular Dependency) 문제&lt;/h2&gt;
&lt;p&gt;예)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;Player.h
#include &amp;quot;Monster.h&amp;quot;

Monster.h
#include &amp;quot;Player.h&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;서로를 include하게 되면&lt;br&gt;컴파일러가 타입을 완성할 수 없어 오류가 발생한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;해결 방법&lt;/h3&gt;
&lt;p&gt;전방 선언(Forward Declaration)을 사용한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;class Monster;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;class Player
{
private:
    Monster* Target;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;필요한 경우에만 cpp에서 include한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;중복 정의 문제&lt;/h2&gt;
&lt;p&gt;헤더에서 함수를 구현하면&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// MyClass.h

void Func()
{
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;해당 헤더를 여러 cpp가 include했을 때&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;A.cpp
#include &amp;quot;MyClass.h&amp;quot;

B.cpp
#include &amp;quot;MyClass.h&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;함수가 여러 번 정의되어&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;Multiple Definition&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;오류가 발생할 수 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;해결 방법&lt;/h3&gt;
&lt;p&gt;헤더에는 선언만 작성한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// MyClass.h

void Func();&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// MyClass.cpp

void Func()
{
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;디버깅이 불편할 수 있음&lt;/h2&gt;
&lt;p&gt;선언과 구현이 분리되어 있기 때문에&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;Player.h&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;↓&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;Player.cpp&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;를 계속 오가며 확인해야 한다.&lt;/p&gt;
&lt;p&gt;작은 프로젝트에서는 오히려 불편할 수 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;캡슐화가 깨질 위험&lt;/h2&gt;
&lt;p&gt;헤더에 너무 많은 내용을 공개하면&lt;/p&gt;
&lt;p&gt;외부 코드가 내부 구현에 의존하게 될 수 있다.&lt;/p&gt;
&lt;p&gt;예)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;class Player
{
public:
    int HP;
    int MP;
    int Level;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;외부에서 직접 접근 가능&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;player.HP = -9999;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;해결 방법&lt;/h3&gt;
&lt;p&gt;필요한 정보만 공개한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;class Player
{
private:
    int HP;

public:
    int GetHP() const;
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;p&gt;분할 구조는&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;코드 관리&lt;/li&gt;
&lt;li&gt;협업&lt;/li&gt;
&lt;li&gt;재사용성&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;측면에서 매우 큰 장점이 있다.&lt;/p&gt;
&lt;p&gt;하지만&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;include 남용&lt;/li&gt;
&lt;li&gt;순환 참조&lt;/li&gt;
&lt;li&gt;중복 정의&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;문제를 유발할 수 있으므로&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;전방 선언 사용&lt;/li&gt;
&lt;li&gt;필요한 헤더만 include&lt;/li&gt;
&lt;li&gt;선언과 구현 분리&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;를 지켜서 사용하는 것이 중요하다.&lt;/p&gt;</description>
      <category>c++/기초</category>
      <author>코드망령</author>
      <guid isPermaLink="true">https://codeghost.tistory.com/34</guid>
      <comments>https://codeghost.tistory.com/34#entry34comment</comments>
      <pubDate>Fri, 5 Jun 2026 09:31:13 +0900</pubDate>
    </item>
    <item>
      <title>nullptr</title>
      <link>https://codeghost.tistory.com/33</link>
      <description>&lt;h1&gt;nullptr&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포인터 전용 null 값&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;NULL의 문제점&lt;/h2&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;void func(int x);
void func(int* x);

func(NULL);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;NULL&lt;/code&gt;은 일반적으로 다음과 같이 정의되어 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;#define NULL 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;code&gt;NULL&lt;/code&gt;은 결국 정수 &lt;code&gt;0&lt;/code&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;void func(int);
void func(int*);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 함수 모두 호출 후보가 된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;0&lt;/code&gt; &amp;rarr; &lt;code&gt;int&lt;/code&gt; 변환 가능&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0&lt;/code&gt; &amp;rarr; &lt;code&gt;int*&lt;/code&gt; 변환 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일러는 어떤 함수를 호출해야 하는지 결정할 수 없기 때문에 &lt;b&gt;모호성(Ambiguity) 오류&lt;/b&gt;가 발생한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;nullptr 사용&lt;/h2&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;void func(int x);
void func(int* x);

func(nullptr);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;nullptr&lt;/code&gt;은 &lt;code&gt;std::nullptr_t&lt;/code&gt; 타입을 가지는 특수한 값이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;포인터 타입으로만 변환 가능&lt;/li&gt;
&lt;li&gt;정수 타입으로는 변환되지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;func(int*);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만 호출 후보가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 컴파일러는 명확하게 &lt;code&gt;func(int*)&lt;/code&gt;를 선택할 수 있으며 모호성 오류가 발생하지 않는다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;nullptr의 특징&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int* ptr = nullptr;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;nullptr&lt;/code&gt;은&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유효한 메모리를 가리키지 않는 상태를 표현하는 값&lt;/li&gt;
&lt;li&gt;포인터 전용 null 값&lt;/li&gt;
&lt;li&gt;&lt;code&gt;std::nullptr_t&lt;/code&gt; 타입을 가지는 특수한 리터럴&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;NULL vs nullptr&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;NULL&lt;/th&gt;
&lt;th&gt;nullptr&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;타입&lt;/td&gt;
&lt;td&gt;정수(0)&lt;/td&gt;
&lt;td&gt;std::nullptr_t&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;정수 변환&lt;/td&gt;
&lt;td&gt;가능&lt;/td&gt;
&lt;td&gt;불가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;포인터 변환&lt;/td&gt;
&lt;td&gt;가능&lt;/td&gt;
&lt;td&gt;가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;함수 오버로딩 모호성&lt;/td&gt;
&lt;td&gt;발생 가능&lt;/td&gt;
&lt;td&gt;발생하지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C++ 권장 여부&lt;/td&gt;
&lt;td&gt;비권장&lt;/td&gt;
&lt;td&gt;권장&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;권장 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현대 C++에서는 &lt;code&gt;NULL&lt;/code&gt; 대신 &lt;code&gt;nullptr&lt;/code&gt; 사용을 권장한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int* ptr = nullptr;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;if (ptr == nullptr)
{
    // 포인터가 아무것도 가리키지 않음
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포인터가 유효한 객체를 가리키지 않는다는 의미를 명확하게 표현할 수 있으며, 함수 오버로딩에서 발생할 수 있는 모호성 문제도 방지할 수 있다.&lt;/p&gt;</description>
      <category>c++/기초</category>
      <author>코드망령</author>
      <guid isPermaLink="true">https://codeghost.tistory.com/33</guid>
      <comments>https://codeghost.tistory.com/33#entry33comment</comments>
      <pubDate>Fri, 5 Jun 2026 09:30:06 +0900</pubDate>
    </item>
    <item>
      <title>언리얼 C++과제3</title>
      <link>https://codeghost.tistory.com/32</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;스파르타 클럽 챕터3 과제3 진행 (1/2)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회전발판과 움직이는 장애물을 만드는 과제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 발판의 회전과 움직이는 장애물을 구현했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/lf_np--qstI?si=o1tioc16b0MkbILz&quot;&gt;https://youtu.be/lf_np--qstI?si=o1tioc16b0MkbILz&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=lf_np--qstI&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/c4i2tZ/dJMb8ZvIXz1/nVvqBYKRkQ6WRK8Yv6MAE1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/dgxTGe/dJMb887giHm/ajQRO4i8OBjxEnXCRMmPM1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/dA8JUp/dJMb83Squoq/HefWFBDAKX3TCF4oRNN9m0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;움직이는 액터&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/lf_np--qstI&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;액터는 MovingActor와 RotateActor클래스를 따로 만들어서 BP로 상속받아서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 맵에 직접 넣는 방식으로 구현했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MovingActor.h&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include &quot;CoreMinimal.h&quot;
#include &quot;GameFramework/Actor.h&quot;
#include &quot;MovingActor.generated.h&quot;

UCLASS()
class SPARTA3_4_API AMovingActor : public AActor
{
    GENERATED_BODY()

public:    
    // Sets default values for this actor's properties
    AMovingActor();

    UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = &quot;Mesh&quot;)
    UStaticMeshComponent* MC;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Movement&quot;)
    float Distance = 500.f;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Movement&quot;)
    float Speed = 400.f;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Movement&quot;)
    float TimerInterval = 0.01f;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Movement&quot;)
    bool bMoveVertical = false;

    UFUNCTION(BlueprintCallable, Category = &quot;Movement&quot;)
    void StartMove();
protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

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

private:
    FTimerHandle MoveTimer;
    FTimerHandle ReturnTimer;

    FVector StartLocation;
    FVector EndLocation;

    bool bIsReturning = false;

    UFUNCTION()
    void MoveForward();
    UFUNCTION()
    void ReturnStartLocation();
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MovingActor.cpp&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;#include &quot;MovingActor.h&quot;
#include &quot;Components/StaticMeshComponent.h&quot;
#include &quot;TimerManager.h&quot;

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

    MC = CreateDefaultSubobject&amp;lt;UStaticMeshComponent&amp;gt;(TEXT(&quot;StaticMesh&quot;));
    RootComponent = MC;
}

void AMovingActor::StartMove()
{
    StartLocation = GetActorLocation();
    const FVector MoveDirection = bMoveVertical ? FVector::UpVector : GetActorForwardVector();
    EndLocation = StartLocation + MoveDirection * Distance;

    GetWorldTimerManager().ClearTimer(MoveTimer);
    GetWorldTimerManager().ClearTimer(ReturnTimer);

    GetWorldTimerManager().SetTimer(
        MoveTimer,
        this,
        &amp;amp;AMovingActor::MoveForward,
        TimerInterval,
        true
    );
}

void AMovingActor::BeginPlay()
{
    Super::BeginPlay();
}

void AMovingActor::MoveForward()
{
    const FVector CurrentLocation = GetActorLocation();
    const FVector ToTarget = EndLocation - CurrentLocation;
    const float DistanceToTarget = ToTarget.Size();
    const float Step = Speed * TimerInterval;

    if (DistanceToTarget &amp;lt;= Step)
    {
        SetActorLocation(EndLocation);

        GetWorldTimerManager().ClearTimer(MoveTimer);
        GetWorldTimerManager().SetTimer(
            ReturnTimer,
            this,
            &amp;amp;AMovingActor::ReturnStartLocation,
            TimerInterval,
            true
            );

        return;
    }

    SetActorLocation(CurrentLocation + ToTarget.GetSafeNormal() * Step);
}

void AMovingActor::ReturnStartLocation()
{
    const FVector CurrentLocation = GetActorLocation();
    const FVector ToTarget = StartLocation - CurrentLocation;
    const float DistanceToTarget = ToTarget.Size();
    const float Step = Speed * TimerInterval;

    if (DistanceToTarget &amp;lt;= Step)
    {
        SetActorLocation(StartLocation);

        GetWorldTimerManager().ClearTimer(ReturnTimer);
        GetWorldTimerManager().SetTimer(
            MoveTimer,
            this,
            &amp;amp;AMovingActor::MoveForward,
            TimerInterval,
            true
            );
        return;
    }

    SetActorLocation(CurrentLocation + ToTarget.GetSafeNormal() * Step);
}

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

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RotateActor.h&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include &quot;CoreMinimal.h&quot;
#include &quot;GameFramework/Actor.h&quot;
#include &quot;RotateActor.generated.h&quot;

UENUM(BlueprintType)
enum class ERotateMode : uint8
{
    KeepRotating UMETA(DisplayName = &quot;Keep Rotating&quot;),
    StopRotating UMETA(DisplayName = &quot;Stop After Time&quot;),
    ReverseRotating UMETA(DisplayName = &quot;Reverse Rotating&quot;),
    StopAndReverseRotating UMETA(DisplayName = &quot;Stop After Time And Reverse&quot;),
};

UENUM(BlueprintType)
enum class ERotateAxis : uint8
{
    Pitch UMETA(DisplayName = &quot;Pitch / Y Axis&quot;),
    Yaw UMETA(DisplayName = &quot;Yaw / Z Axis&quot;),
    Roll UMETA(DisplayName = &quot;Roll / X Axis&quot;),
};

UCLASS()
class SPARTA3_4_API ARotateActor : public AActor
{
    GENERATED_BODY()

public:
    // Sets default values for this actor's properties
    ARotateActor();

    UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = &quot;Mesh&quot;)
    UStaticMeshComponent* MC;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Rotation&quot;)
    float RotationSpeed = 90.f;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Rotation&quot;)
    float RotateDuration = 2.f;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Rotation&quot;)
    float TimerInterval = 0.01f;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Rotation&quot;)
    float StopDuration = 1.f;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Rotation&quot;)
    ERotateMode RotateMode = ERotateMode::KeepRotating;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Rotation&quot;)
    ERotateAxis RotateAxis = ERotateAxis::Yaw;

    UFUNCTION(BlueprintCallable, Category = &quot;Rotation&quot;)
    void StartRotating();

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;
public:
    // Called every frame
    virtual void Tick(float DeltaTime) override;

private:
    FTimerHandle RotateTimer;
    FTimerHandle RestartTimer;

    float ElapsedTime = 0.f;
    float RotateDirection = 1.f;

    UFUNCTION()
    void RotateStep();

    UFUNCTION()
    void StopRotate();
    UFUNCTION()
    void ReverseRotate();
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RotateActor.cpp&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;#include &quot;RotateActor.h&quot;


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

    MC = CreateDefaultSubobject&amp;lt;UStaticMeshComponent&amp;gt;(TEXT(&quot;StaticMesh&quot;));
    RootComponent = MC;
}

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

}

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



void ARotateActor::StartRotating()
{
    ElapsedTime = 0.f;

    GetWorldTimerManager().ClearTimer(RotateTimer);
    GetWorldTimerManager().ClearTimer(RestartTimer);
    GetWorldTimerManager().SetTimer(
        RotateTimer,
        this,
        &amp;amp;ARotateActor::RotateStep,
        TimerInterval,
        true
    );
}

void ARotateActor::RotateStep()
{
    ElapsedTime += TimerInterval;

    const float RotationAmount = RotationSpeed * RotateDirection * TimerInterval;
    FRotator DeltaRotation = FRotator::ZeroRotator;

    switch (RotateAxis)
    {
    case ERotateAxis::Pitch:
        DeltaRotation.Pitch = RotationAmount;
        break;

    case ERotateAxis::Yaw:
        DeltaRotation.Yaw = RotationAmount;
        break;

    case ERotateAxis::Roll:
        DeltaRotation.Roll = RotationAmount;
        break;
    }

    AddActorLocalRotation(DeltaRotation);

    if (RotateMode == ERotateMode::StopRotating &amp;amp;&amp;amp; ElapsedTime &amp;gt;= RotateDuration)
    {
        StopRotate();
    }
    else if (RotateMode == ERotateMode::ReverseRotating &amp;amp;&amp;amp; ElapsedTime &amp;gt;= RotateDuration)
    {
        StopRotate();
        ElapsedTime = 0.f;

        GetWorldTimerManager().SetTimer(
            RestartTimer,
            this,
            &amp;amp;ARotateActor::StartRotating,
            StopDuration,
            false
        );
    }
    else if (RotateMode == ERotateMode::StopAndReverseRotating &amp;amp;&amp;amp; ElapsedTime &amp;gt;= RotateDuration)
    {
        StopRotate();
        ReverseRotate();
        ElapsedTime = 0.f;

        GetWorldTimerManager().SetTimer(
            RestartTimer,
            this,
            &amp;amp;ARotateActor::StartRotating,
            StopDuration,
            false
        );
    }
}

void ARotateActor::StopRotate()
{
    GetWorldTimerManager().ClearTimer(RotateTimer);
}

void ARotateActor::ReverseRotate()
{
    RotateDirection *= -1.f;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Timer를 사용하여 0.01초가 지나면 조금씩 이동하도록 설정해서 부드럽게 움직이는 것 처럼 보이게 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 파라미터들은 에디터에서 변경 가능하도록 리플렉션 매크로를 EditAnywhere로 설정했습니다.&lt;/p&gt;</description>
      <category>언리얼/스파르타 클럽</category>
      <author>코드망령</author>
      <guid isPermaLink="true">https://codeghost.tistory.com/32</guid>
      <comments>https://codeghost.tistory.com/32#entry32comment</comments>
      <pubDate>Thu, 4 Jun 2026 21:25:52 +0900</pubDate>
    </item>
    <item>
      <title>FOnlineSessionSettings</title>
      <link>https://codeghost.tistory.com/31</link>
      <description>&lt;p&gt;제가 학습한 것을 기준으로 만들어진 것이기 때문에 이 글 내용이 틀렸을 수도 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;FOnlineSessionSettings&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;FOnlineSessionSettings&lt;/code&gt;는 세션을 만들 때 OnlineSubsystem에게 넘겨주는 설정값 묶음이다.&lt;/p&gt;
&lt;p&gt;직접 방을 생성하는 클래스라기보다는, 어떤 방식의 방을 만들지 설명하는 데이터 객체라고 볼 수 있다.&lt;/p&gt;
&lt;p&gt;실제 세션 생성은 &lt;code&gt;CreateSession()&lt;/code&gt;에서 이루어지며,&lt;br&gt;&lt;code&gt;FOnlineSessionSettings&lt;/code&gt;에 설정한 값에 따라 세션의 동작이 결정된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;기본 개념&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;TSharedPtr&amp;lt;FOnlineSessionSettings&amp;gt; SessionSettings = MakeShareable(new FOnlineSessionSettings());&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;기본 생성자에서는 대부분의 값이 다음과 같이 초기화된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;false
0&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;즉, 대부분의 기능은 기본적으로 꺼져 있으며,&lt;br&gt;필요한 기능만 개발자가 직접 활성화하는 방식이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;NumPublicConnections&lt;/h2&gt;
&lt;p&gt;공개 접속 슬롯 수&lt;/p&gt;
&lt;p&gt;다른 플레이어가 일반적으로 참가할 수 있는 자리 수를 의미한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SessionSettings-&amp;gt;NumPublicConnections = 4;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;위 설정은 최대 4명이 참가 가능한 세션을 의미한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;NumPrivateConnections&lt;/h2&gt;
&lt;p&gt;비공개 접속 슬롯 수&lt;/p&gt;
&lt;p&gt;초대 전용 슬롯이나 예약 슬롯 등에 사용할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SessionSettings-&amp;gt;NumPrivateConnections = 2;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;모든 OnlineSubsystem에서 적극적으로 사용되는 것은 아니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;bShouldAdvertise&lt;/h2&gt;
&lt;p&gt;세션을 검색에 노출할지 여부&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SessionSettings-&amp;gt;bShouldAdvertise = true;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;true&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;FindSessions() 검색 가능&lt;/li&gt;
&lt;li&gt;다른 플레이어가 방을 찾을 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;false&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;방은 생성되지만 검색 결과에 나타나지 않을 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;bAllowJoinInProgress&lt;/h2&gt;
&lt;p&gt;게임 진행 중 참가 허용 여부&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SessionSettings-&amp;gt;bAllowJoinInProgress = true;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;true&lt;/h3&gt;
&lt;p&gt;게임이 시작된 후에도 참가 가능&lt;/p&gt;
&lt;h3&gt;false&lt;/h3&gt;
&lt;p&gt;게임 시작 이후 참가 불가&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;bIsLANMatch&lt;/h2&gt;
&lt;p&gt;LAN 세션 여부&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SessionSettings-&amp;gt;bIsLANMatch = true;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;true&lt;/h3&gt;
&lt;p&gt;같은 네트워크 내부에서만 검색 가능&lt;/p&gt;
&lt;h3&gt;false&lt;/h3&gt;
&lt;p&gt;Steam, EOS 등의 온라인 서비스 사용&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;예시&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;SessionSettings-&amp;gt;bIsLANMatch =
    IOnlineSubsystem::Get()-&amp;gt;GetSubsystemName() == &amp;quot;NULL&amp;quot;;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;NULL Subsystem이면 LAN 세션으로 생성&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;bIsDedicated&lt;/h2&gt;
&lt;p&gt;전용 서버 여부&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SessionSettings-&amp;gt;bIsDedicated = true;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;true&lt;/h3&gt;
&lt;p&gt;Dedicated Server&lt;/p&gt;
&lt;h3&gt;false&lt;/h3&gt;
&lt;p&gt;Listen Server&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;일반적인 Host 방식&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;SessionSettings-&amp;gt;bIsDedicated = false;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;플레이어가 직접 호스트하는 방식&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;bUsesStats&lt;/h2&gt;
&lt;p&gt;온라인 통계 시스템 사용 여부&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SessionSettings-&amp;gt;bUsesStats = true;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;예시&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;킬 수&lt;/li&gt;
&lt;li&gt;승패 기록&lt;/li&gt;
&lt;li&gt;점수 기록&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;실제 지원 여부는 OnlineSubsystem에 따라 다르다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;bAllowInvites&lt;/h2&gt;
&lt;p&gt;친구 초대 허용 여부&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SessionSettings-&amp;gt;bAllowInvites = true;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;플랫폼이 제공하는 친구 초대 기능을 사용할 수 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;bUsesPresence&lt;/h2&gt;
&lt;p&gt;Presence 사용 여부&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SessionSettings-&amp;gt;bUsesPresence = true;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Presence란 현재 유저 상태 정보이다.&lt;/p&gt;
&lt;p&gt;예시&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;게임 접속 중&lt;/li&gt;
&lt;li&gt;로비 대기 중&lt;/li&gt;
&lt;li&gt;특정 세션 참가 중&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;활용 예시&lt;/h2&gt;
&lt;p&gt;Steam 친구 목록&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;친구가 현재 게임 플레이 중&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;같은 정보 제공&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;bAllowJoinViaPresence&lt;/h2&gt;
&lt;p&gt;Presence를 통한 참가 허용 여부&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SessionSettings-&amp;gt;bAllowJoinViaPresence = true;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;예시&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;친구 목록 → 게임 참가&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;기능 지원&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;bAllowJoinViaPresenceFriendsOnly&lt;/h2&gt;
&lt;p&gt;친구만 Presence 참가 허용&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SessionSettings-&amp;gt;bAllowJoinViaPresenceFriendsOnly = true;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;true&lt;/h3&gt;
&lt;p&gt;친구만 참가 가능&lt;/p&gt;
&lt;h3&gt;false&lt;/h3&gt;
&lt;p&gt;누구나 참가 가능&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;bAntiCheatProtected&lt;/h2&gt;
&lt;p&gt;안티치트 보호 세션 여부&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SessionSettings-&amp;gt;bAntiCheatProtected = true;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;예시&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;VAC&lt;/li&gt;
&lt;li&gt;PunkBuster&lt;/li&gt;
&lt;li&gt;Easy Anti Cheat&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;실제 동작은 플랫폼마다 다르다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;bUseLobbiesIfAvailable&lt;/h2&gt;
&lt;p&gt;Lobby API 사용 여부&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SessionSettings-&amp;gt;bUseLobbiesIfAvailable = true;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;플랫폼이 Lobby 기능을 지원하면&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Session API
    ↓
Lobby API 우선 사용&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;UE5 Steam/EOS 멀티플레이에서 자주 사용된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;bUseLobbiesVoiceChatIfAvailable&lt;/h2&gt;
&lt;p&gt;Lobby 음성 채팅 사용 여부&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SessionSettings-&amp;gt;bUseLobbiesVoiceChatIfAvailable = true;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Lobby 생성 시&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;음성 채팅방 생성&lt;/li&gt;
&lt;li&gt;자동 참가&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;기능을 활성화한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;SessionIdOverride&lt;/h2&gt;
&lt;p&gt;세션 ID 직접 지정&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SessionSettings-&amp;gt;SessionIdOverride = TEXT(&amp;quot;MySession&amp;quot;);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;보통은 사용하지 않는다.&lt;/p&gt;
&lt;p&gt;대부분 플랫폼이 자동 생성한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;BuildUniqueId&lt;/h2&gt;
&lt;p&gt;빌드 구분용 ID&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SessionSettings-&amp;gt;BuildUniqueId = 1;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;서로 다른 빌드끼리 세션 검색 결과가 섞이지 않도록 한다.&lt;/p&gt;
&lt;p&gt;예시&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;빌드&lt;/th&gt;
&lt;th&gt;ID&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;개발 빌드&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;테스트 빌드&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;출시 빌드&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;Settings&lt;/h2&gt;
&lt;p&gt;커스텀 세션 설정 저장소&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TMap&amp;lt;FName, FOnlineSessionSetting&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;매치 타입, 맵 이름, 게임 모드 등을 저장할 수 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;값 저장&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;SessionSettings-&amp;gt;Set(
    FName(&amp;quot;MatchType&amp;quot;),
    FString(&amp;quot;FreeForAll&amp;quot;),
    EOnlineDataAdvertisementType::ViaOnlineServiceAndPing
);

&amp;lt;br&amp;gt;
#### EOnlineDataAdverisementType

Set으로 저장한 값을 어디에 노출하는지 정하는 옵션이다.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;DontAdvertise&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;세션 안에는 저장하지만, 외부 검색결과나 Qos/ping 데이터로 노출하지 않음.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;ViaPingOnly&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;온라인 서비스의 세션 검색 데이터에는 올리지 않고, 서버에 ping/Qos 질의를 했을 때만 전달되게 함.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;ViaOnlineService&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Steam, EOS같은 Online Subsystem의 세션 검색 결과에 포함되게 함.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;ViaOnlineServiceAndPing&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;세션 검색 결과에도 나오고, ping/Qos응답에도 포함 될 수 있게 함.
---

## 값 읽기
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;FString MatchType;&lt;/p&gt;
&lt;p&gt;SearchResult.Session.SessionSettings.Get(&lt;br&gt;    FName(&amp;quot;MatchType&amp;quot;),&lt;br&gt;    MatchType&lt;br&gt;);&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
---

## 활용 예시
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;MatchType&lt;br&gt;MapName&lt;br&gt;GameMode&lt;br&gt;Difficulty&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
---

## MemberSettings

세션 전체가 아닌

플레이어 개별 설정 저장소

---

예시
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;플레이어 A&lt;br&gt; └ 팀 : Red&lt;/p&gt;
&lt;p&gt;플레이어 B&lt;br&gt; └ 팀 : Blue&lt;/p&gt;
&lt;p&gt;플레이어 C&lt;br&gt; └ 캐릭터 : Warrior&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
---

활용 가능 정보

-   팀 정보
-   역할 정보
-   선택 캐릭터
-   플레이어 상태

---

모든 OnlineSubsystem이 지원하는 것은 아니다.

---

## 정리

`FOnlineSessionSettings`

-   세션 생성 옵션을 저장하는 데이터 객체
-   직접 세션을 만드는 클래스는 아님
-   `CreateSession()` 호출 시 OnlineSubsystem에 전달됨

주요 설정
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;NumPublicConnections&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
공개 접속 슬롯 수
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;bShouldAdvertise&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
검색 노출 여부
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;bAllowJoinInProgress&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
중간 참가 허용 여부
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;bIsLANMatch&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
LAN 세션 여부
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;bUsesPresence&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Presence 사용 여부
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;bAllowJoinViaPresence&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
친구 목록을 통한 참가 허용
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;bUseLobbiesIfAvailable&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Lobby API 사용 여부
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Settings&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
매치 타입, 맵 이름 등 커스텀 정보 저장

---

즉, `FOnlineSessionSettings`는

&amp;gt; &amp;quot;어떤 방을 만들 것인가?&amp;quot;

를 OnlineSubsystem에게 설명하는 설정 데이터 묶음이다.

설정을 모두 완료한 뒤
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;SessionInterface-&amp;gt;CreateSession(...)&lt;/p&gt;
&lt;p&gt;```&lt;/p&gt;
&lt;p&gt;에 전달하면 OnlineSubsystem이 해당 설정을 기반으로 세션을 생성한다.&lt;/p&gt;</description>
      <category>언리얼/언리얼 학습</category>
      <author>코드망령</author>
      <guid isPermaLink="true">https://codeghost.tistory.com/31</guid>
      <comments>https://codeghost.tistory.com/31#entry31comment</comments>
      <pubDate>Wed, 3 Jun 2026 12:42:11 +0900</pubDate>
    </item>
    <item>
      <title>FInputModeUIOnly</title>
      <link>https://codeghost.tistory.com/30</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Udemy에서 구매한 강의를 공부하면서 배운 내용입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 따로 알고싶어서 공부한 부분들도 있으니 내용에 틀린 것이 있을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.udemy.com/course/unreal-engine-5-cpp-multiplayer-shooter/?couponCode=MT260602G3&quot;&gt;https://www.udemy.com/course/unreal-engine-5-cpp-multiplayer-shooter/?couponCode=MT260602G3&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1780387819792&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Unreal Engine 5 C++ Multiplayer Shooter&quot; data-og-description=&quot;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Over 64 hours of content!&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;p&amp;gt;Learn all about Unreal Engine multiplayer in this fully comprehensive C++&amp;nbsp;course. From scratch, we create a complete multiplayer shooter game, capable of creating and joining game sessions and connecting&quot; data-og-host=&quot;www.udemy.com&quot; data-og-source-url=&quot;https://www.udemy.com/course/unreal-engine-5-cpp-multiplayer-shooter/?couponCode=MT260602G3&quot; data-og-url=&quot;https://www.udemy.com/course/unreal-engine-5-cpp-multiplayer-shooter/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/4rMhs/dJMb8UHWtGC/QLUPotbgwNTYgU4XlsCXs1/img.jpg?width=480&amp;amp;height=270&amp;amp;face=0_0_480_270,https://scrap.kakaocdn.net/dn/bkBQFu/dJMb8WeG05Y/fsvPjuaix1t0NCUpjAxP80/img.jpg?width=480&amp;amp;height=270&amp;amp;face=0_0_480_270,https://scrap.kakaocdn.net/dn/GKsSZ/dJMb8UHWtGB/kWkxercqyYaiopRdy5O8jK/img.jpg?width=480&amp;amp;height=270&amp;amp;face=0_0_480_270&quot;&gt;&lt;a href=&quot;https://www.udemy.com/course/unreal-engine-5-cpp-multiplayer-shooter/?couponCode=MT260602G3&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.udemy.com/course/unreal-engine-5-cpp-multiplayer-shooter/?couponCode=MT260602G3&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/4rMhs/dJMb8UHWtGC/QLUPotbgwNTYgU4XlsCXs1/img.jpg?width=480&amp;amp;height=270&amp;amp;face=0_0_480_270,https://scrap.kakaocdn.net/dn/bkBQFu/dJMb8WeG05Y/fsvPjuaix1t0NCUpjAxP80/img.jpg?width=480&amp;amp;height=270&amp;amp;face=0_0_480_270,https://scrap.kakaocdn.net/dn/GKsSZ/dJMb8UHWtGB/kWkxercqyYaiopRdy5O8jK/img.jpg?width=480&amp;amp;height=270&amp;amp;face=0_0_480_270');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Unreal Engine 5 C++ Multiplayer Shooter&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Over 64 hours of content!&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;p&amp;gt;Learn all about Unreal Engine multiplayer in this fully comprehensive C++&amp;nbsp;course. From scratch, we create a complete multiplayer shooter game, capable of creating and joining game sessions and connecting&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.udemy.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 강의를 들으며 공부했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;FInputModeUIOnly&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플레이어 컨트롤러에 적용하여 &lt;b&gt;입력이 UI에만 전달되도록 설정하는 클래스&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 메뉴 화면, 옵션 창, 로그인 화면, 로비 UI처럼&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캐릭터 조작은 막고&lt;/li&gt;
&lt;li&gt;버튼 클릭&lt;/li&gt;
&lt;li&gt;텍스트 입력&lt;/li&gt;
&lt;li&gt;UI 네비게이션&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만 가능하게 만들 때 사용한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;강의 중 적은 코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Menu.h&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;#pragma once

#include &quot;CoreMinimal.h&quot;
#include &quot;Blueprint/UserWidget.h&quot;
#include &quot;Menu.generated.h&quot;

UCLASS()
class MULTIPLAYERSESSIONS_API UMenu : public UUserWidget
{
    GENERATED_BODY()

public:
    UFUNCTION(BlueprintCallable)
    void MenuSetUp();

private:
    UPROPERTY(meta = (BindWidget))
    class UButton* HostButton;

    UButton* JoinButton;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Menu.cpp&lt;/p&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;void UMenu::MenuSetUp()
{
    AddToViewport();
    SetVisibility(ESlateVisibility::Visible);
    SetIsFocusable(true);

    UWorld* World = GetWorld();

    if (World)
    {
        APlayerController* PlayerController =
            World-&amp;gt;GetFirstPlayerController();

        if (PlayerController)
        {
            FInputModeUIOnly InputModeData;

            InputModeData.SetWidgetToFocus(TakeWidget());
            InputModeData.SetLockMouseToViewportBehavior(
                EMouseLockMode::DoNotLock);

            PlayerController-&amp;gt;SetInputMode(InputModeData);
            PlayerController-&amp;gt;SetShowMouseCursor(true);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MenuSetUp 흐름&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. UI를 화면에 표시&lt;/h3&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;AddToViewport();
SetVisibility(ESlateVisibility::Visible);&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. UI가 키보드 입력을 받을 수 있도록 설정&lt;/h3&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;SetIsFocusable(true);&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;bIsFocusable = true&lt;/code&gt; 대신 사용&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 플레이어 컨트롤러 가져오기&lt;/h3&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;APlayerController* PlayerController =
    World-&amp;gt;GetFirstPlayerController();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력 모드를 변경하기 위해 필요하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. UI 전용 입력 모드 생성&lt;/h3&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;FInputModeUIOnly InputModeData;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI만 입력을 받을 수 있는 입력 모드&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 포커스를 줄 위젯 설정&lt;/h3&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;InputModeData.SetWidgetToFocus(TakeWidget());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 위젯에 키보드 포커스를 준다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 마우스 잠금 설정&lt;/h3&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;InputModeData.SetLockMouseToViewportBehavior(
    EMouseLockMode::DoNotLock);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마우스를 뷰포트 안에 가둘지 결정한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 입력 모드 적용&lt;/h3&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;PlayerController-&amp;gt;SetInputMode(InputModeData);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플레이어 입력을 UI 전용으로 변경&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8. 마우스 커서 표시&lt;/h3&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;PlayerController-&amp;gt;SetShowMouseCursor(true);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI를 조작할 수 있도록 마우스 커서를 표시&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SetWidgetToFocus&lt;/h2&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;ENGINE_API FInputModeUIOnly&amp;amp;
SetWidgetToFocus(TSharedPtr&amp;lt;SWidget&amp;gt; InWidgetToFocus);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI 입력 포커스를 받을 위젯을 설정한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용 예시&lt;/h2&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;InputModeData.SetWidgetToFocus(TakeWidget());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 UUserWidget을 포커스 대상으로 설정&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;반환형이 레퍼런스인 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;체이닝(Chaining)을 지원하기 위해 자기 자신을 반환한다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;InputModeData
    .SetWidgetToFocus(TakeWidget())
    .SetLockMouseToViewportBehavior(
        EMouseLockMode::DoNotLock);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 함수를 이어서 호출할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SetLockMouseToViewportBehavior&lt;/h2&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;ENGINE_API FInputModeUIOnly&amp;amp;
SetLockMouseToViewportBehavior(
    EMouseLockMode InMouseLockMode);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마우스를 뷰포트 내부에 가둘지 설정한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;EMouseLockMode 종류&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DoNotLock&lt;/h3&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;EMouseLockMode::DoNotLock&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마우스를 가두지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임 창 밖으로 이동 가능&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;LockAlways&lt;/h3&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;EMouseLockMode::LockAlways&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임이 활성화 상태라면 항상 마우스를 뷰포트 안에 가둔다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;LockInFullscreen&lt;/h3&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;EMouseLockMode::LockInFullscreen&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 화면 모드일 때만 마우스를 가둔다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;LockOnCapture&lt;/h3&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;EMouseLockMode::LockOnCapture&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클릭 또는 드래그 등 입력이 발생했을 때 마우스를 가둔다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용 예시&lt;/h2&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;InputModeData.SetLockMouseToViewportBehavior(
    EMouseLockMode::DoNotLock);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마우스가 게임 창 밖으로 이동 가능&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TakeWidget()&lt;/h2&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;TakeWidget()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;UUserWidget&lt;/code&gt;을 Slate 시스템의 &lt;code&gt;SWidget&lt;/code&gt; 형태로 변환하여 반환한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;SetWidgetToFocus()&lt;/code&gt;는&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;TSharedPtr&amp;lt;SWidget&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;형태를 요구한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 UMG 위젯은&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;UUserWidget&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기반이기 때문에 직접 전달할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;TakeWidget()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 사용하여 Slate 위젯으로 변환 후 전달한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용 예시&lt;/h2&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;InputModeData.SetWidgetToFocus(TakeWidget());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 위젯이 입력 포커스를 받도록 설정한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;FInputModeUIOnly&lt;/code&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UI만 입력 받도록 설정&lt;/li&gt;
&lt;li&gt;캐릭터 조작 불가능&lt;/li&gt;
&lt;li&gt;메뉴, 옵션, 로그인, 로비 UI에서 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주요 함수&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;SetWidgetToFocus()&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;입력 포커스를 줄 위젯 지정&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;SetLockMouseToViewportBehavior()&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마우스 잠금 방식 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;TakeWidget()&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UUserWidget &amp;rarr; SWidget 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;PlayerController-&amp;gt;SetInputMode(InputModeData);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UI 전용 입력 모드 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;PlayerController-&amp;gt;SetShowMouseCursor(true);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마우스 커서 표시&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>언리얼/언리얼 학습</category>
      <author>코드망령</author>
      <guid isPermaLink="true">https://codeghost.tistory.com/30</guid>
      <comments>https://codeghost.tistory.com/30#entry30comment</comments>
      <pubDate>Tue, 2 Jun 2026 17:02:33 +0900</pubDate>
    </item>
  </channel>
</rss>