TIL(38)

이채원·2026년 8월 11일

TIL

목록 보기
38/83

오늘의 할일
1. 코드카타 정리
2. 세션 정리
3. 궁금증 해결


코드카타 정리

길이가 n이고, "수박수박수박수...."와 같은 패턴을 유지하는 문자열을 리턴하는 함수

#include <string>
#include <vector>

using namespace std;

string solution(int n) {
    string answer = "";
    
    for (int i = 0; i < n; i++) {
        if ( i % 2 == 0) {
            answer += "수";
        } else {
            answer += "박";
        }
    }
    return answer;
}

AI가 작성한 코드

#include <string>

using namespace std;

string solution(int n) {
    string answer = "";
    
    // 1. 메모리 재할당 방지 (선택 사항이지만 성능 최적화에 좋음)
    // 한글은 보통 1글자에 3바이트를 차지하므로, n * 3 크기만큼 미리 공간을 확보해 둡니다.
    answer.reserve(n * 3);

    // 2. 반복 횟수를 절반(n / 2)으로 줄여 "수박"을 통째로 이어 붙입니다.
    for (int i = 0; i < n / 2; ++i) {
        answer += "수박";
    }

    // 3. n이 홀수일 경우, 마지막에 짝을 맞추지 못한 "수"만 한 번 더해줍니다.
    if (n % 2 != 0) {
        answer += "수";
    }

    return answer;
}

이 방식을 선호하는 이유
1. 연산 횟수 감소: n번 반복하며 짝홀수(% 2)를 매번 계산하던 것을, n / 2번 반복으로 확 줄였습니다.

  1. reserve 메모리 최적화: C++에서 문자열은 길이가 길어지면 내부적으로 더 큰 이사 갈 메모리 찾고 데이터를 복사하는 과정을 반복. reserve로 처음부터 "나 이만큼 쓸 거야!" 하고 미리 방을 잡아두면, 불필요한 메모리 재할당을 막아 속도가 빨라집니다.

C++와 Unreal Engine으로 3D 게임 개발 : 1회차 정리

1-1 : 연동하기

비주얼 스튜디오와 언리얼을 연동하는 과정을 진행하고 폴더의 구조를 알아보았다

1-2 : 빌드 프로세스 이해하기

이렇게 비주얼 스튜디오 상에 보이는 구조가 솔루션 구조

  • 프로젝트 폴더 구조와는 조금 다름

  • Engine 폴더 : 언리얼 엔진 자체 소스 코드와 리소스가 담긴 곳

  • Games 폴더 : 프로젝트 코드(Source , Config , .uproject 파일 등)

  • Programs 폴더 : 엔진 동작에 필요한 유틸리티 프로그램이나 서버 모듈

  • Rules 폴더 : 엔진과 게임 등 각 모듈의 빌드 규칙을 정의해 놓은 파일

  • Visualizers 폴더 : 언리얼 엔진의 복잡한 자료구조를 보다 읽기 쉽게 시각화할 수 있도록 도와주는 설정 파일

Games 폴더 안

Games 폴더 안에는 프로젝트 이름이 보이는데 그걸 드롭다운하면 여러가지가 있음
이때 프로젝트 이름이 담긴 폴더가 프로젝트 루트라고 함. 게임 개발에 필요한 모든 리소스와 설정 파일이 담겨 있음

  • Config 폴더
    .ini 파일을 통해 에디터와 게임의 초기 상태를 지정
    DefaultEditor.ini : 에디터 환경 설정 (뷰포트, UI 등)
    DefaultEngine.ini : 엔진 전반 설정 (렌더링, 네트워킹 등)
    DefaultGame.ini : 게임플레이 관련 설정 (게임 모드, 플레이어 컨트롤러 클래
    스 등)
    DefaultInput.ini : 키보드, 마우스, 패드 등의 기본 입력

  • Source 폴더
    실제 C++ 소스 코드( .cpp , .h ).
    프로젝트 최초 생성 시에는 프로젝트명.cpp , 프로젝트명.h 등만 있지만,
    새 클래스를 만들수록 점차 늘어남
    빌드 설정 관련 주요 파일도 포함
    프로젝트명.Build.cs : 해당 프로젝트에 필요한 모듈, 라이브러리, 종속성 등 빌드 설정 파일
    프로젝트명.Target.cs , 프로젝트명Editor.Target.cs : 각각 게임 실행용, 에디터용 빌드 방식

빌드

파일 수정 -> 빌드 (컴파일 + 링크) -> DLL(동적 라이브러리) 파일 생성 -> 언리얼 에디터에서 가져감
이 과정을 계속 반복

빌드 설정 방법
빌드 구성(Configuration)과 플랫폼(Platform)에 Development Editor& Win64를 설정

빌드 방법
전체 빌드보다는 부분 빌드 추천 -> 빌드 시간 줄이기

  • 언리얼 에디터를 종료하고 빌드하는 것 권장 (안전)

TestProject2를 시작 프로제트로 설정하기

  • 비주얼 스튜디오에서 F5를 누르면 언리얼이 실행됨 (권장)

라이브 코딩으로 에디터 연결 종료 안해도 결과를 확인할 수 있도록 할 수 있지만 아직 많이 업데이트가 안돼서 원래 빌드하던 방식을 추천(종료 후 빌드 -> 재실행)

<빌드 꼬인 거 해결하는 방법>
C++ 코드를 수정하고 빌드를 완료 하였음에도 불구하고, 언리얼 에디터에서 반영
이 안 되는 경우

  1. 언리얼 에디터, 비주얼 스튜디오 모두 종료 후 프로젝트 폴더 열기
  2. DerivedDataCache, Intermediate, Saved 폴더 삭제하기(Binaries까지 삭제해도 괜찮음)
  3. .uproject 파일 우클릭 후 추가 옵션 표시 - Generate Visual Studio project files 클릭
  4. 솔루션 파일이 재생성됨
  5. 언리얼 에디터 실행 후 확인 --- 6~70퍼센트 가량의 문제는 해결됨
  6. 아직 안됐으면 솔루션 파일 들어가서 Build - 클린 솔루션 눌러주기
  7. 전체 빌드 솔루션 하기 --- 90퍼센트 가량의 문제는 해결됨

1-3 Actor 클래스 생성 및 삭제

리소스 프로젝트를 다운받고 그 안에 있는 매터리얼들을 프로젝트에 옮겨옴(Migrate)

Maps 폴더 생성 후 FIle - New Level - Basic 으로 새 레벨을 생성
-> 그리고 Edit - Project Settings - Maps - Editor Startup Map, Game Default Map에 기본 맵을 지정 (이름은 MainLevel)

Object Vs Actor

  1. Object(= UObject)
    UObject : 언리얼 엔진에서 모든 클래스의 최상위 부모 클래스
    스스로 월드(레벨)에 배치될 수 없고 주로 데이터나 로직만 담당
    ex) 플레이어 능력치(HP, 경험치), AI 정보, 게임 설정 값, 임시 계산 로직 등
    주로 화면에 보이지 않는 데이터나 로직

  2. Actor (= AActor)
    AActor : UObject를 상속한 클래스, 월드에 배치(Spawn) 가능
    위치, 회전, 크기 등 공간적 정보 포함됨/ 여러 컴포넌트를 추가로 붙일 수 있음
    ex) 실제 게임 세계에서 보이고 상호작용하는 캐릭터, 적 몬스터, 무기, 조명, 파티클 효과 등

Actor 클래스 생성&삭제

Actor 클래스 생성
1. Tools - New C++ Class - Actor - Public - 생성 (클래스 이름은 Item)
2. 자동 빌드가 진행되는데 빌드가 완료되면 비주얼 스튜디오에서 프로젝트가
변경되었다는 메시지가 나타나면, Reload All 선택
3. Public 폴더 아래에 Item.h , Private 폴더 아래에 Item.cpp 파일이 생성

배치는 드래그 앤 드롭으로 할 수 있음 -> Outliner로 확인

Actor 클래스 삭제
1. 언리얼 에디터 종료하고 비주얼 스튜디오에서 클래스 제거(h 와 .cpp 파일)
2. 프로젝트 폴더에서 .h , .cpp 파일을 직접 삭제

💡 항상 에디터 종료 → 솔루션 Remove → 물리 파일 삭제 → (Visual Studio 빌드) → 에디터 재실행 순서 지키기!!!

1-4 Actor 클래스에 컴포넌트 추가

Actor을 처음 만들었을 때의 .h, .cpp 파일 이해해보기

  • Item.h 파일 상단 코드
#pragma once						//동일 헤더가 프로젝트 곳곳에서 여러번 포함되더라도 컴파일러가 한 번만 처리
#include "CoreMinimal.h"			//필수 기본 헤더
#include "GameFramework/Actor.h"	//AActor 클래스 선언을 사용 가능하게 함
#include "Item.generated.h"			//언리얼 헤더툴 (UHT)이 자동 생성하는 코드를 포함하는 것으로, 항상 헤더의 마지막 줄에 위치
  • .h 파일 클래스 선언부
    -> 접두사 규칙 (A (Actor 계열), U (Object 계열), F (구조체), T (템플릿), E (열거형) 등)
    -> SPARTAPROJECT_API : 이 클래스를 모듈(현재 모듈 : TestProject2) 외부에서도 클래스를 사용할 수 있게 하기 위한 매크로
UCLASS()	//클래스를 리플렉션 시스템에 등록하는 매크로
class SPARTAPROJECT_API AItem : public AActor 	//AActor 를 상속받아 AItem 클래스를 정의한다는 의미
{
	GENERATED_BODY()	//클래스를 리플렉션 시스템에 등록하는 매크로
public:
// 생성자
	AItem();
protected:
 // 액터가 월드에 스폰 (배치된) 직후 한 번만 호출
	virtual void BeginPlay() override;
// 매 프레임마다 호출
	virtual void Tick(float DeltaTime) override;
};
  • .cpp 파일
#include "Item.h" //항상 첫 줄에! 짝 헤더를 가장 먼저 include
// 생성자 구현부
AItem::AItem()
{
	PrimaryActorTick.bCanEverTick = true;
}
// BeginPlay() 구현부
void AItem::BeginPlay()
{
	// 부모 클래스(AActor)의 BeginPlay()를 먼저 호출
	Super::BeginPlay();
}
// Tick() 구현부
void AItem::Tick(float DeltaTime)
{
	// 부모 클래스(AActor)의 Tick() 먼저 호출
	Super::Tick(DeltaTime);
}

Actor 클래스에 컴포넌트 멤버 변수 추가하고 초기화하기

  1. 뷰포트에 보이기 위해서는 컴포넌트 추가해줘야함
  • 모든 Actor는 루트 컴포넌트를 가져야함
    💡 루트 컴포넌트 : 액터의 트랜스폼(위치, 회전, 크기)을 정의하는 최상위 컴포넌트
    @@ 보통 Scene Component를 루트 컴포넌트로 설정
  • Item.h에서 포인터 멤버 변수를 추가
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Item.generated.h"
UCLASS()
class SPARTAPROJECT_API AItem : public AActor
{
	GENERATED_BODY()
public:
	AItem();
protected:
	// 루트 컴포넌트를 나타내는 Scene Component 포인터
	USceneComponent* SceneRoot;
	// Static Mesh Component 포인터
	UStaticMeshComponent* StaticMeshComp;
};
  • Item.cpp에서 포인터 멤버 변수를 추가
#include "Item.h"
AItem::AItem()
{
	// Scene Component를 생성하고 루트로 설정
	SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
	SetRootComponent(SceneRoot);	//루트 컴포넌트를 SceneRoot로 설정
	// Static Mesh Component를 생성하고 Scene Component에 Attach
	StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));
	StaticMeshComp->SetupAttachment(SceneRoot);		//StaticMeshComp 를 SceneRoot에 부착
}

⭐ 필요한 함수(BeginPlay, Tick)가 없거나 미사용 상태라면, 헤더와 .cpp 파일에서 제거해도 됨

여기서 사용된 함수

//컴포넌트를 생성하고 초기화할 때 사용하는 함수
//템플릿 타입 <T> 로 생성할 컴포넌트의 유형을 지정
//"StaticMesh" , "SceneRoot" 는 각 컴포넌트의 식별 이름
//TEXT()는 문자열을 유니코드로 처리하기 위한 것
CreateDefaultSubobject<T>(TEXT(""))

메시 및 머티리얼 할당하고 Actor 배치

Static Mesh와 Material이 있어야 배치했을 때 시각적으로 확인 가능!!

컨텐트 브라우저에서 원하는 Static Mesh와 Material에 우클릭을 하고 copy Reference를 해서 붙여넣기 -> 주소만 남겨놓고 다 삭제

#include "Item.h"

AItem::AItem()
{
	SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
	SetRootComponent(SceneRoot);

	StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));
	StaticMeshComp->SetupAttachment(SceneRoot);

	//'/Game/Resources/Props/SM_Chair.SM_Chair'
	//'/Game/Resources/Materials/M_Metal_Gold.M_Metal_Gold'

	//⭐ Static Mesh를 코드에서 설정
	static ConstructorHelpers::FObjectFinder<UStaticMesh> MeshAsset(TEXT("/Game/Resources/Props/SM_Chair.SM_Chair"));
	if (MeshAsset.Succeeded()) {
		StaticMeshComp->SetStaticMesh(MeshAsset.Object);
	}
    
	//⭐ Material을 코드에서 설정
	static ConstructorHelpers::FObjectFinder<UMaterial> MaterialAsset(TEXT("/Game/Resources/Materials/M_Metal_Gold.M_Metal_Gold"));
	if (MaterialAsset.Succeeded()) {
		StaticMeshComp->SetMaterial(0, MaterialAsset.Object);
	}
}

여기에 쓰인 함수들

//특정 리소스를 경로 기반으로 로드하는 클래스
ConstructorHelpers::FObjectFinder<T>

//리소스의 경로
TEXT("/Game/Resources/Props/SM_Chair.SM_Chair") 

//지정된 경로에서 리소스를 성공적으로 찾았는지 확인
.Succeeded()

//성공적으로 로드된 Static Mesh를 StaticMeshComp에 설정
//여기서 Material은 인덱스를 꼭 붙여줘야함/ 첫번째 머티리얼 슬롯 (Index 0)
SetStaticMesh() , SetMaterial()

1-5 Actor의 라이프 사이클

Actor 클래스에 로그 추가

  • BeginPlay() 함수에 로그 추가
protected:
	USceneComponent* SceneRoot;
	UStaticMeshComponent* StaticMeshComp;
	// BeginPlay 함수를 다시 선언
	virtual void BeginPlay() override;
};
  • Item.cpp에 BeginPlay 추가
void AItem::BeginPlay()
{
	Super::BeginPlay();
	// BeginPlay 호출 시점을 로그로 확인
 	UE_LOG(LogTemp, Warning, TEXT("My Log!"));
}

로그 카테고리를 Logtemp라고 지정하고 문구를 출력하라고 했을 때 실행하면 Output Log에 지정한 출력 문구가 뜨는데 filter에서 Logtemp만 뜨게 할 수 있음 -> 만약 Logtemp가 없다면 출력 문구가 안 떴다고 의심해야함
-> 고유한 카테고리 만들어서 디버깅 됐는지 확인 용도로 사용하면 좋음

로그 수준(Log Level):

  • Display : 일반적인 실행 흐름이나 상태 확인 메시지 (흰색)

  • Warning : 예상치 못한 동작이나 잠재적인 문제 (노란색)

  • Error : 즉시 수정이 필요한 심각한 문제 (빨간색)

  • Item.h 상단(inclue 바로 밑)에 "LogSparta"로 로그 카테고리 선언

DECLARE_LOG_CATEGORY_EXTERN(LogSparta, Warning, All);
  • .cpp 파일에서 로그 카테고리를 구현(정의) -- 위치는 헤더랑 같음
DEFINE_LOG_CATEGORY(LogSparta);

💡로그 카테고리를 다른 파일에서 쓰려면 선언한 헤더파일을 include해야함.
💡로그 카테고리를 여럿이 공유하기 때문에, 별도의 공용 헤더에 선언해 두는 경우가 많음

Actor의 라이프 사이클

Actor 라이프 사이클 : Actor가 게임 도중에 언제든지 생성(Spawn)되고, 필요 없어지면 파괴(Destroy)되는 과정

Actor 라이프 사이클을 이해하면 초기화 시점을 보다 쉽게 결정할 수 있고, 성능을 최적화하거나(Tick 함수), 리소스 정리를 잘 할 수 있음

순서 : 생성 → 초기화 → 월드 배치 → Tick(실행) → 제거

  • 라이프 사이클 함수 6가지
// 생성자 - 메모리에 생김. 딱 한번 호출. -> 컴포넌트를 생성해서 붙여주는 작업
// PostInitializeComponents() - 컴포넌트가 완성된 직후 호출. 컴포넌트끼리 데이터 주고 받기, 상호작용 -> 월드에 등장하기 전에 작업해줘야 할 것들
// BeginPlay() - 배치 직후
// Tick(float DeltaTime) - 매 프레임마다 호출됨. -> 캐릭터 이동 등 자연스러운 움직임들, 열심히 활동하는 함수
// Destroyed() - 삭제가 되기 직전에 호출 -> 리소스 정리
// EndPlay() - 게임 종료, 파괴 (Destroyed()), 레벨 전환 등등
  • 헤더에 라이프 사이클 함수 선언
protected:
	USceneComponent* SceneRoot;
	UStaticMeshComponent* StaticMeshComp;

	virtual void PostInitializeComponents() override;
	virtual void BeginPlay() override;
	virtual void Tick(float DeltaTime) override;
	virtual void Destroyed() override;
	virtual void EndPlay(const EEndPlayReason:: Type EndPlayReason) override;
  • cpp 파일에서 각 함수 구현 및 로그 출력 & UE_LOG로 호출 시점에 메시지 남기기
AItem::AItem()
{
	UE_LOG(LogSparta, Warning, TEXT("%s Constructor"), *GetName());
}

void AItem::PostInitializeComponents() {
	Super::PostInitializeComponents();
	UE_LOG(LogSparta, Warning, TEXT("%s PostInitializeComponents"), *GetName());
}

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

	UE_LOG(LogSparta, Warning, TEXT("%s BeginPlay"), *GetName());
}

void AItem::Tick(float DeltaTime) {
	Super::Tick(DeltaTime);
}

void AItem::Destroyed() {
	UE_LOG(LogSparta, Warning, TEXT("%s Destroyed"), *GetName());
	Super::Destroyed();
}

void AItem::EndPlay(const EEndPlayReason::Type EndPlayReason) {
	UE_LOG(LogSparta, Warning, TEXT("%s EndPlay"), *GetName());
	Super::EndPlay(EndPlayReason);
}

궁금증 해결하기

Class Type 옵션에 대한 추가 설명

  1. Public으로 생성할 경우:
  • .h 가 Public 폴더에, .cpp 가 Private 폴더에 생김
  • 프로젝트의 다른 모듈에서 쉽게 #include할 수 있음
  1. Private으로 생성할 경우:
  • .h 와 .cpp 모두 Private 폴더에 저장
  • 해당 모듈에서만 접근 가능하게 되며 특정 로직이나 구현을 캡슐화해서 외부에 노출하고 싶지 않을 때 사용
    ** 학습 단계에서는 보통 Public 방식을 추천

EndPlay vs. Destroyed
EndPlay: 액터가 월드에서 사라지는 모든 상황(게임 종료, 레벨 전환, Destroy 호출 등)에 대해 호출
Destroyed: 보통 Destroy() 함수가 명시적으로 불렸을 때만 호출, 게임 종료나 레벨 언로드 시에는 호출되지 않을 수 있음

profile
초보 개발자

0개의 댓글