C++과 Unreal Engine으로 3D 게임 개발 7

김여울·2025년 7월 7일
0

내일배움캠프

목록 보기
39/114

코드카타

부족한 금액 계산하기

#include <iostream>

using namespace std;

long long solution(int price, int money, int count)	// 매개변수는 초기화 안 해도 됨
{
    long long total = 0;	// 지역변수는 초기화 안 하면 쓰레기값 들어감

    for (int i = 1; i <= count; ++i)
    {
        total += price * i; // 각 회차마다 가격을 누적
    }

    if (total > money)
    { 
        return total = money; // 부족한 금액 계산
    }

    return 0; // 부족하지 않으면 0 반환
}

문자열 다루기 기본

#include <string>
#include <vector>

using namespace std;

bool solution(string s)
{
	// 길이가 4 또는 6이 아니면 false
    if (s.length() != 4 && s.length() != 6)
    {
        return false; 
    }

	// 문자 하나씩 돌면서 숫자인지 확인
    for (int i = 0; i < s.length(); ++i)
    {
        if (s[i] <'0' || s[i] > '9')  // 각 문자가 숫자가 아닌 경우, 숫자는 문자로 되어있음
        {
            return false; // 숫자가 아닌 문자가 있으면 false
        }
    }

    return true; // 길이가 4나 6이고, 모두 숫자이면 true
}
  • s.length()
    문자열의 길이
  • s[i]
    i번째 글자를 하나씩 꺼내서 for문 안에서 검사함
  • s[i] <'0' || s[i] > '9'
    문자는 컴퓨터 안에서 숫자로 저장됨 → 9 아니라 '9'

    📎ASCII Table

📍 3주차 2강

6. 아이템 상호작용

충돌 처리를 통한 아이템 상호작용 활성화

6.1 Item에 Collision 추가

a. Component 추가

  • Scene(Root Component)
  • Sphere
  • StaticMesh

ItemBase.h

protected:	
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item") // 리플렉션으로 에디터에 노출
	FName ItemType;  // 아이템의 타입을 저장하는 변수

	// 컴포넌트 선언
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item|Component") // 객체 자체 변경 x, 내부 속성은 에디터에서 조정 가능
	USceneComponent* Scene;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item|Component")
	USphereComponent* Collision;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item|Component")
	UStaticMeshComponent* StaticMesh;

ItemBase.cpp

#include "BaseItem.h"
#include "Components/SphereComponent.h"  // 스피어 컴포넌트 헤더 포함

ABaseItem::ABaseItem()
{
	PrimaryActorTick.bCanEverTick = false;

	Scene = CreateDefaultSubobject<USceneComponent>(TEXT("Scene"));	// 객체 생성
	SetRootComponent(Scene);	// 항상 Scene을 루트 컴포넌트로 설정

	Collision = CreateDefaultSubobject<USphereComponent>(TEXT("Collision"));	// 스피어 컴포넌트 생성
	Collision->SetupAttachment(Scene);	// Scene(Root Component)에 붙임

	StaticMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));	// 스태틱 메시 컴포넌트 생성
	StaticMesh->SetupAttachment(Collision);	// Collision에 붙임
}

b. 블루프린트 상속

실질적으로 구현할 아이템들만 블루프린트 상속하도록 만들기
ItemBase에서 Component 생성했기 때문에 나머지 자손클래스들도 모두 생성됨

c. Static Mesh

  • 아이템들 뷰포트 가서 스태틱 메쉬 설정하기
  • BigCoinItem은 루트 컴포넌트에서 크기 조절
    • 루트 컴포넌트에서 스케일 조절하면 전체 액터(컴포넌트 포함)에 영향을 주고,
      Static Mesh만 스케일 조절하면 그 컴포넌트에만 영향을 줌

d. Collision Presets

Spehere Component가 어떤 종류의 충돌을 감지하고 무시할지 설정

Preset 이름설명
NoCollision충돌 무시, 겹쳐지거나 막지도 않음, 액터랑 상호작용 ❌
🧩 하늘에 있는 것 (구름), 장식용
BlockAll모든 것과 충돌(막음), Overlap 없음
🧩 벽, 바닥
BlockAllDynamic움직이는 객체만 막음
🧩 플레이어랑만 상호작용 하고 싶을 때 - 문 or 굴러오는 물체
OverlapAll모든 것과 겹침(Overlap), 충돌 없음
🧩 투명한 객체, 트리거 존 - 감지 센서
OverlapAllDynamic정적 객체는 무시, 움직이는 객체와 오버랩 이벤트
🧩 감지 센서 ✅ Item들 이거 적용

Custom 설정충돌 감지 (Query: Overlap, Hit)물리적 반응 (Physics)
Collision Enabled (Query and Physics)
Query Only
Physics Only

Collision Prestets 설정하고 Collision Component에 추가하기
BaseItem.cpp

Collision = CreateDefaultSubobject<USphereComponent>(TEXT("Collision"));	// 스피어 컴포넌트 생성
Collision->SetCollisionProfileName(TEXT("OverlapAllDynamic"));	// 충돌 프리셋 설정
Collision->SetupAttachment(Scene);	// Scene(Root Component)에 붙임

6.2 Event Binding

  • 어떤 이벤트가 발생했을 때 특정 함수를 자동으로 실행되도록 연결(바인딩)하는 것
  • 런타임 중에 이벤트와 함수를 동적으로 연결 가능
  • 이벤트가 발생한 순간 관련된 정보(예: 충돌 정보 등)가 매개변수로 전달됨
    → 이 정보를 받기 위해서는 이벤트가 요구하는 함수 시그니처와 정확히 일치하는 함수만 바인딩할 수 있음
    → Overlap 관련 이벤트에 바인딩하려면, Overlap 이벤트의 매개변수 시그니처를 그대로 따라야 함

ItemInterface.h

public:	
	virtual void OnItemOverlap(  // 아이템과 겹쳤을 때 호출되는 함수
	UPrimitiveComponent* OverlappedComp, // 오버랩이 발생한 자기 자신 = SphereComponent
	AActor* OtherActor,	// 겹친 다른 액터 = 플레이어 캐릭터
	UPrimitiveComponent* OtherComp, // 충돌 원인 컴포넌트 = 플레이어 캐릭터의 CapsuleComponent
	int32 OtherBodyIndex,
	bool bFromSweep,
	const FHitResult& SweepResult) = 0;	
virtual void OnItemEndOverlap(  // 아이템과 겹침이 끝났을 때 호출되는 함수
	UPrimitiveComponent* OverlappedComp,
	AActor* OtherActor,	
	UPrimitiveComponent* OtherComp, 
	int32 OtherBodyIndex, ) = 0;

BaseItem.h

virtual void OnItemOverlap(
	UPrimitiveComponent* OverlappedComp, 
	AActor* OtherActor,	
	UPrimitiveComponent* OtherComp, 
	int32 OtherBodyIndex,
	bool bFromSweep,
	const FHitResult& SweepResult) override;
virtual void OnItemEndOverlap(
	UPrimitiveComponent* OverlappedComp,
	AActor* OtherActor,
	UPrimitiveComponent* OtherComp,
	int32 OtherBodyIndex) override;

BaseItem.cpp

#include "BaseItem.h"
#include "Components/SphereComponent.h"  // 스피어 컴포넌트 헤더 포함

ABaseItem::ABaseItem()
{
	PrimaryActorTick.bCanEverTick = false;

	Scene = CreateDefaultSubobject<USceneComponent>(TEXT("Scene"));	// 객체 생성
	SetRootComponent(Scene);	// 항상 Scene을 루트 컴포넌트로 설정

	Collision = CreateDefaultSubobject<USphereComponent>(TEXT("Collision"));	// 스피어 컴포넌트 생성
	Collision->SetCollisionProfileName(TEXT("OverlapAllDynamic"));	// 충돌 프리셋 설정
	Collision->SetupAttachment(Scene);	// Scene(Root Component)에 붙임

	StaticMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));	// 스태틱 메시 컴포넌트 생성
	StaticMesh->SetupAttachment(Collision);	// Collision에 붙임

	// 이벤트 바인딩
	Collision->OnComponentBeginOverlap.AddDynamic(this, &ABaseItem::OnItemOverlap);	// 겹쳤을 때 호출되는 함수
	Collision->OnComponentEndOverlap.AddDynamic(this, &ABaseItem::OnItemEndOverlap);	// 겹침이 끝났을 때 호출되는 함수
}

void ABaseItem::OnItemOverlap(
	UPrimitiveComponent* OverlappedComp,
	AActor* OtherActor,
	UPrimitiveComponent* OtherComp,
	int32 OtherBodyIndex,
	bool bFromSweep,
	const FHitResult& SweepResult)
{
	if (OtherActor && OtherActor->ActorHasTag("Player"))	// ✅ 플레이어 태그
	{
		ActivateItem(OtherActor);  // 플레이어와 겹쳤을 때 아이템 활성화 함수 호출
	}
}

void ABaseItem::OnItemEndOverlap(UPrimitiveComponent* OverlappedComp,
	AActor* OtherActor,
	UPrimitiveComponent* OtherComp,
	int32 OtherBodyIndex)
{
}

6.3 리플렉션 시스템에 연동

  • 인터페이스 함수 앞에 UFUNCTION()을 붙이면, 언리얼 리플렉션 시스템에 등록됨
  • 이후 해당 인터페이스를 구현하는 클래스에서도 바인딩 및 호출 가능

Interface.h

public:	
	UFUNCTION()	// 엔진의 리플렉션 시스템에 함수 등록 -> 엔진이 런타임 때 이 함수를 호출할 수 있도록
	virtual void OnItemOverlap(  // 아이템과 겹쳤을 때 호출되는 함수
		UPrimitiveComponent* OverlappedComp, // 오버랩이 발생한 자기 자신 = SphereComponent
		AActor* OtherActor,	// 겹친 다른 액터 = 플레이어 캐릭터
		UPrimitiveComponent* OtherComp, // 충돌 원인 컴포넌트 = 플레이어 캐릭터의 CapsuleComponent
		int32 OtherBodyIndex,
		bool bFromSweep,
		const FHitResult& SweepResult) = 0;	
	UFUNCTION()
	virtual void OnItemEndOverlap(  // 아이템과 겹침이 끝났을 때 호출되는 함수
		UPrimitiveComponent* OverlappedComp,
		AActor* OtherActor,	
		UPrimitiveComponent* OtherComp, 
		int32 OtherBodyIndex, ) = 0;	
	virtual void ActivateItem(AActor* Activator) = 0;	// 아이템을 활성화할 때 호출되는 함수
	virtual FName GetItemType() const = 0;	// 아이템의 타입을 반환하는 함수 -> return만 하기 때문에 const
	// String 말고 빠른 FName을 사용

6.4 ActorHasTag 설정

  • 충돌이나 이벤트가 발생했을 때 상대가 어떤 종류의 액터인지 판별
  • 바인딩된 이벤트 안에서 "이거 플레이어가 한 행동 맞나?"를 구분하기 위해 태그를 확인
구분목적
이벤트 바인딩이벤트 발생 시 자동으로 함수를 실행하기 위해
플레이어 태그이벤트의 대상이 플레이어인지 확인하기 위해
둘을 함께 쓰는 이유상호작용 이벤트가 "플레이어가 했는지" 확인해서 조건에 맞는 행동을 하도록 하기 위해

BaseItem.cpp

if (OtherActor && OtherActor->ActorHasTag("Player"))	// ✅ 플레이어 태그
	{
		ActivateItem(OtherActor);  // 플레이어와 겹쳤을 때 아이템 활성화 함수 호출
	}

BP_SpartaCharacter

6.5 Overlap 로그 찍기

BaseItem.cpp

if (OtherActor && OtherActor->ActorHasTag("Player"))
{
	GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Green, FString::Printf(TEXT("Overlap!!!")));
	ActivateItem(OtherActor);  // 플레이어와 겹쳤을 때 아이템 활성화 함수 호출
}

Overlap 이벤트 발생 시 Destroy() + 로그 출력됨

6.6 Item들 세부 설정

a. ActivateItem override

override 해야 아이템마다 각자의 동작 설정할 수 있음

CoinItem.h

헤더파일에서 선언

virtual void ActivateItem(AActor* Activator) override;

CoinItem.cpp

소스파일에서 구현 (초기화도 해주기)

#include "CoinItem.h"

ACoinItem::ACoinItem()  // 생성자
{
	PointValue = 0;  // 초기화
	ItemType = "DefaultCoin";  // 아이템 타입 초기화
}

void ACoinItem::ActivateItem(AActor* Activator)
{
	if (Activator && Activator->ActorHasTag("Player"))
	{
		GEngine->AddOnScreenDebugMessage(
			-1,
			2.0f, 
			FColor::Green, 
			FString::Printf(TEXT("Player gained %d points!"), PointValue));
		DestroyItem();
	}
}

자식클래스(Big & Small)에선 불러오기만 하면 됨

BigCoinItem.cpp / SmallCoin.cpp

void ABigCoinItem::ActivateItem(AActor* Activator)
{
	// DestroyItem(); -> CoinItem에서 DestroyItem()을 호출 
	Super::ActivateItem(Activator);  // 부모 클래스의 ActivateItem 호출
}

HealingItem.cpp

#pragma once

#include "CoreMinimal.h"
#include "BaseItem.h"
#include "HealingItem.generated.h"

UCLASS()
class SPARTAPROJECT_API AHealingItem : public ABaseItem
{
	GENERATED_BODY()

public:
	AHealingItem();	// 생성자
	
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
	int32 HealAmount;	// 회복량	✅int로 변경
	

	virtual void ActivateItem(AActor* Activator) override; // ✅

};

b. Collision 추가

MineItem

반경을 Collision Component로 추가
그 안에 오버랩된 게 플레이어다 -> 플레이어한테 데미지 입힘

블루프린트로 반경 크기 가늠하기

Collision Component로 추가

추가했던 Collision 컴포넌트 지우고

코드로 Collision Component 추가하기

MineItem.h

#pragma once

#include "CoreMinimal.h"
#include "BaseItem.h"
#include "MineItem.generated.h"

UCLASS()
class SPARTAPROJECT_API AMineItem : public ABaseItem
{
	GENERATED_BODY()

public:
	AMineItem();	// 생성자

	USphereComponent* ExplosionCollision;	//  ✅ 폭발 범위 콜리전 컴포넌트

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
	float ExplosionDelay;	// 폭발 지연 시간
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
	float ExplosionRadius;	// 폭발 범위
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
	int32 ExplosionDamage;	// 폭발 피해 ✅ int로 변경
	
	virtual void ActivateItem(AActor* Activator) override;

	void Explode();	//  ✅ 폭발 함수
};

MineItem.cpp

#include "MineItem.h"
#include "Components/SphereComponent.h"

AMineItem::AMineItem()
{
	ExplosionDelay = 5.0f; // 기본 폭발 지연 시간 설정
	ExplosionRadius = 300.0f; // 기본 폭발 범위 설정 
	ExplosionDamage = 30.0f; // 기본 폭발 피해 설정
	ItemType = "Mine"; // 아이템 타입 설정

	//  ✅ 콜리전 컴포넌트 추가 (반경)
	ExplosionCollision = CreateDefaultSubobject<USphereComponent>(TEXT("ExplosionCollision"));
	ExplosionCollision->InitSphereRadius(ExplosionRadius);  // 300.0f 넣기
	ExplosionCollision->SetCollisionProfileName(TEXT("OverlapAllDynamic")); // 모든 동적 오버랩 허용
	ExplosionCollision->SetupAttachment(Scene);	// 루트 컴포넌트인 씬 컴포넌트에 부착

}

//  ✅ 타이머 추가 : 5초 뒤 폭발
void AMineItem::ActivateItem(AActor* Activator)	// 활성화되고 5초 후에 폭발
{
	// 게임 월드에는 각자의 타이머를 관리하는 타이머 매니저가 있음
	// 타이머 핸들러 : 각자의 타이머를 갖고 있음

	// 월드에서 타이머 매니저 부름
	GetWorld()->GetTimerManager().SetTimer(
		ExplosionTimerHandle,	// 타이머 달아줌
		this,  // 타이머가 끝나면 호출할 대상 : 이 객체에
		&AMineItem::Explode,  // 호출할 함수
		ExplosionDelay,  // 지연 시간
		false  // 반복 여부 (false면 한 번만 실행)
	);
}

//  ✅ 태그 찾기
void AMineItem::Explode()
{
	TArray<AActor*> OverlappingActors;  // 범위 내 겹치는 액터 검색
	ExplosionCollision->GetOverlappingActors(OverlappingActors);

	// 범위 내 돌면서 태그 확인
	for (AActor* Actor : OverlappingActors)
	{
		if (Actor && Actor->ActorHasTag("Player")) // Actor가 nullptr이 아니고 플레이어 태그가 있는지 확인
		{
			GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Green, FString::Printf(TEXT("Player damaged %d by MineItem"), ExplosionDamage));
		}
	}

	DestroyItem();
}



각 아이템들마다 오버랩 이벤트 & 출력 완료


💭

예전엔 블루프린트로만 오버랩을 구현했는데, 이번엔 C++로 해보니까 일일이 설정하던 걸 인터페이스랑 부모 클래스 덕분에 좀 더 깔끔하게 만들 수 있어서 좋았다!
… 근데 아직 어렵긴 하다

0개의 댓글