오늘의 할일
1. 코드카타 정리
2. 세션 정리
한국중학교에 다니는 학생들은 각자 정수 번호를 갖고 있습니다. 이 학교 학생 3명의 정수 번호를 더했을 때 0이 되면 3명의 학생은 삼총사라고 합니다. 예를 들어, 5명의 학생이 있고, 각각의 정수 번호가 순서대로 -2, 3, 0, 2, -5일 때, 첫 번째, 세 번째, 네 번째 학생의 정수 번호를 더하면 0이므로 세 학생은 삼총사입니다. 또한, 두 번째, 네 번째, 다섯 번째 학생의 정수 번호를 더해도 0이므로 세 학생도 삼총사입니다. 따라서 이 경우 한국중학교에서는 두 가지 방법으로 삼총사를 만들 수 있습니다.
한국중학교 학생들의 번호를 나타내는 정수 배열 number가 매개변수로 주어질 때, 학생들 중 삼총사를 만들 수 있는 방법의 수를 return 하도록 solution 함수를 완성하세요.
조합문제인 것 같음
for문으로 하면 될 듯..?
#include <string>
#include <vector>
using namespace std;
int solution(vector<int> number) {
int answer = 0;
int n = number.size(); // 학생이 총 몇 명인지
// 첫 번째 학생 고르기
for (int i = 0; i < n - 2; i++) {
// 두 번째 학생 고르기 (첫 번째 학생 다음 번호부터 끝까지)
for (int j = i + 1; j < n - 1; j++) {
// 세 번째 학생 고르기 (두 번째 학생 다음 번호부터 끝까지)
for (int k = j + 1; k < n; k++) {
// 뽑힌 3명의 번호를 모두 더해서 0이 되면 삼총사
if (number[i] + number[j] + number[k] == 0) {
answer++;
}
}
}
}
return answer;
}
AI가 작성한 코드
#include <vector>
#include <unordered_map>
using namespace std;
int solution(vector<int> number) {
int answer = 0;
int n = number.size();
// 이전에 지나쳐 온 학생들의 번호와 그 등장 횟수를 적어두는 출석부
unordered_map<int, int> seen;
for (int i = 0; i < n; i++) {
// 1. 기준이 되는 학생(i)과 그 뒤에 있는 학생(j)을 짝지어 봅니다.
for (int j = i + 1; j < n; j++) {
// 2. 삼총사가 되기 위해 필요한 '나머지 한 명의 번호'를 계산합니다.
int target = -(number[i] + number[j]);
// 3. 출석부에 그 번호가 있다면, 적혀있는 인원수만큼 정답에 더합니다.
// (C++ unordered_map은 찾는 값이 없으면 0을 반환하므로 에러가 나지 않습니다.)
answer += seen[target];
}
// 4. i번째 학생의 탐색이 끝났으므로, 이제 이 학생도 출석부에 등록해 줍니다.
seen[number[i]]++;
}
return answer;
}
세 명을 동시에 찾지 말고, 두 명만 고른 뒤 한 명은 출석부에서 찾기
미리 '출석부(Hash Map)'를 하나 만들어 두고
학생 두 명(i, j)을 먼저 짝짓기. 두 사람 번호의 합이 3이라면, 삼총사가 되기 위해 필요한 번호는 -3
굳이 세 번째 학생을 찾으러 끝까지 돌아다닐 필요 없이, "혹시 출석부에 -3번 학생이 몇 명 적혀있나?" 하고 조회만 하면 끝
일단 저번 강의에서 만든 Game Instance를 활용하기 위해 GameState 코드 수정
#include "SpartaGameState.h"
#include "SpartaGameInstance.h"
#include "Kismet/GameplayStatics.h"
#include "SpawnVolume.h"
#include "CoinItem.h"
ASpartaGameState::ASpartaGameState()
{
Score = 0;
SpawnedCoinCount = 0;
CollectedCoinCount = 0;
LevelDuration = 30.0f; // 한 레벨당 30초
CurrentLevelIndex = 0;
MaxLevels = 3;
}
void ASpartaGameState::BeginPlay()
{
Super::BeginPlay();
// 게임 시작 시 첫 레벨부터 진행
StartLevel();
}
int32 ASpartaGameState::GetScore() const
{
return Score;
}
void ASpartaGameState::AddScore(int32 Amount)
{
if (UGameInstance* GameInstance = GetGameInstance()) {
USpartaGameInstance* SpartaGameInstance = Cast<USpartaGameInstance>(GameInstance);
if (SpartaGameInstance) {
SpartaGameInstance->AddToScore(Amount);
}
}
}
void ASpartaGameState::StartLevel()
{
if (UGameInstance* GameInstance = GetGameInstance()) {
USpartaGameInstance* SpartaGameInstance = Cast<USpartaGameInstance>(GameInstance);
if (SpartaGameInstance) {
CurrentLevelIndex = SpartaGameInstance->CurrentLevelIndex;
}
}
// 레벨 시작 시, 코인 개수 초기화
SpawnedCoinCount = 0;
CollectedCoinCount = 0;
// 현재 맵에 배치된 모든 SpawnVolume을 찾아 아이템 40개를 스폰
TArray<AActor*> FoundVolumes;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASpawnVolume::StaticClass(), FoundVolumes);
const int32 ItemToSpawn = 40;
for (int32 i = 0; i < ItemToSpawn; i++)
{
if (FoundVolumes.Num() > 0)
{
ASpawnVolume* SpawnVolume = Cast<ASpawnVolume
>(FoundVolumes[0]);
if (SpawnVolume)
{
AActor* SpawnedActor = SpawnVolume->SpawnRandomItem();
// 만약 스폰된 액터가 코인 타입이라면 SpawnedCoinCount 증가
if (SpawnedActor && SpawnedActor->IsA(ACoinItem::StaticClass()))
{
SpawnedCoinCount++;
}
}
}
}
// 30초 후에 OnLevelTimeUp()가 호출되도록 타이머 설정
GetWorldTimerManager().SetTimer(
LevelTimerHandle,
this,
&ASpartaGameState::OnLevelTimeUp,
LevelDuration,
false
);
UE_LOG(LogTemp, Warning, TEXT("Level %d Start!, Spawned %d coin"),
CurrentLevelIndex + 1,
SpawnedCoinCount);
}
void ASpartaGameState::OnLevelTimeUp()
{
// 시간이 다 되면 레벨을 종료
EndLevel();
}
void ASpartaGameState::OnCoinCollected()
{
CollectedCoinCount++;
UE_LOG(LogTemp, Warning, TEXT("Coin Collected: %d / %d"),
CollectedCoinCount,
SpawnedCoinCount)
// 현재 레벨에서 스폰된 코인을 전부 주웠다면 즉시 레벨 종료
if (SpawnedCoinCount > 0 && CollectedCoinCount >= SpawnedCoinCount)
{
EndLevel();
}
}
void ASpartaGameState::EndLevel()
{
// 타이머 해제
GetWorldTimerManager().ClearTimer(LevelTimerHandle);
// 다음 레벨 인덱스로
CurrentLevelIndex++;
if (UGameInstance* GameInstance = GetGameInstance()) {
USpartaGameInstance* SpartaGameInstance = Cast<USpartaGameInstance>(GameInstance);
if (SpartaGameInstance) {
AddScore(Score);
SpartaGameInstance->CurrentLevelIndex = CurrentLevelIndex;
}
}
// 모든 레벨을 다 돌았다면 게임 오버 처리
if (CurrentLevelIndex >= MaxLevels)
{
OnGameOver();
return;
}
// 레벨 맵 이름이 있다면 해당 맵 불러오기
if (LevelMapNames.IsValidIndex(CurrentLevelIndex))
{
UGameplayStatics::OpenLevel(GetWorld(), LevelMapNames[CurrentLevelIndex]);
}
else
{
// 맵 이름이 없으면 게임오버
OnGameOver();
}
}
void ASpartaGameState::OnGameOver()
{
UE_LOG(LogTemp, Warning, TEXT("Game Over!!"));
// 여기서 UI를 띄운다거나, 재시작 기능을 넣을 수도 있음
}
HUD (Heads-Up Display)
Widget Blueprint
UI를 시각적으로 설계할 수 있도록 제공되는 에디터용 블루프린트

PlayerController에서 구현
Widget이 플레이어의 입력과 상호작용하며 화면에 표시되는 요소
TSubclassOf<UUserWidget> 변수를 선언#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "SpartaPlayerController.generated.h"
class UInputMappingContext;
class UInputAction;
UCLASS()
class TESTPROJECT2_API ASpartaPlayerController : public APlayerController
{
GENERATED_BODY()
public:
ASpartaPlayerController();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
UInputMappingContext* InputMappingContext;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
UInputAction* MoveAction;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
UInputAction* JumpAction;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
UInputAction* LookAction;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
UInputAction* SprintAction;
// UMG 위젯 클래스를 에디터에서 할당받을 변수
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI")
TSubclassOf<UUserWidget> HUDWidgetClass;
protected:
virtual void BeginPlay() override;
};
#include "SpartaPlayerController.h"
#include "EnhancedInputSubsystems.h"
#include "Blueprint/UserWidget.h" // 헤더 추가
ASpartaPlayerController::ASpartaPlayerController()
: InputMappingContext(nullptr),
MoveAction(nullptr),
JumpAction(nullptr),
LookAction(nullptr),
SprintAction(nullptr)
{
}
void ASpartaPlayerController::BeginPlay()
{
Super::BeginPlay();
if (ULocalPlayer* LocalPlayer = GetLocalPlayer())
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem =
LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
{
if (InputMappingContext) {
Subsystem->AddMappingContext(InputMappingContext, 0); //0 = 가장 높은 우선순위로 할당
}
}
}
// HUD 위젯 생성 및 표시
if (HUDWidgetClass)
{
UUserWidget* HUDWidget = CreateWidget<UUserWidget>(this, HUDWidgetClass);
if (HUDWidget)
{
HUDWidget->AddToViewport();
}
}
}
코드를 빌드하려면, CreateWidget이 작동하려면 UMG 모듈이 빌드 설정에 추가
되어 있어야 함
TestProject2.Build.cs -> “UMG” 모듈을 추가
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class TestProject2 : ModuleRules
{
public TestProject2(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput", "UMG" }); //UMG 추가
PrivateDependencyModuleNames.AddRange(new string[] { });
}
}
GameState에서 Score 읽어오기

이렇게 블루프린트로 하면 바인딩 방식은 틱 함수를 사용하는 것처럼 부하가 있게 됨
그래서 SetText 호출하는 방법을 권장함!!
PlayerController의 Tick 함수(또는 Timer 이벤트)에서 GameState의 Score를 읽어와서, HUD 위젯에 있는 Text Block에 SetText를 호출하는 방식
-> 필요할 때만 UI를 업데이트할 수 있어 퍼포먼스에 유리
SetText 방식으로 위젯 갱신
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameState.h"
#include "SpartaGameState.generated.h"
UCLASS()
class TESTPROJECT2_API ASpartaGameState : public AGameState
{
GENERATED_BODY()
public:
ASpartaGameState();
virtual void BeginPlay() override;
UPROPERTY(VisibleAnyWhere, BlueprintReadWrite, Category = "Score")
int32 Score;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Coin")
int32 SpawnedCoinCount;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Coin")
int32 CollectedCoinCount;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Level")
float LevelDuration;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Level")
int32 CurrentLevelIndex;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Level")
int32 MaxLevels;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Level")
TArray<FName> LevelMapNames;
FTimerHandle LevelTimerHandle;
FTimerHandle HUDUpdateTimerHandle; //추가
UFUNCTION(BlueprintPure, Category = "Score")
int32 GetScore() const;
UFUNCTION(BlueprintCallable, Category = "Score")
void AddScore(int32 Amount);
UFUNCTION(BlueprintCallable, Category = "Level")
void OnGameOver();
void StartLevel();
void OnLevelTimeUp();
void OnCoinCollected();
void EndLevel();
void UpdateHUD(); //추가
};
#include "SpartaGameState.h"
#include "SpartaGameInstance.h"
#include "SpartaPlayerController.h"
#include "SpawnVolume.h"
#include "CoinItem.h"
#include "Kismet/GameplayStatics.h"
#include "Components/TextBlock.h"
#include "Blueprint/UserWidget.h"
// 헤더에 필요한 것들 모두 추가해주기
ASpartaGameState::ASpartaGameState()
{
Score = 0;
SpawnedCoinCount = 0;
CollectedCoinCount = 0;
LevelDuration = 50.0f;
CurrentLevelIndex = 0;
MaxLevels = 3;
}
void ASpartaGameState::BeginPlay()
{
Super::BeginPlay();
StartLevel();
GetWorldTimerManager().SetTimer(
HUDUpdateTimerHandle,
this,
&ASpartaGameState::UpdateHUD,
0.1f,
true
);
}
void ASpartaGameState::StartLevel()
{
if (UGameInstance* GameInstance = GetGameInstance()) {
USpartaGameInstance* SpartaGameInstance = Cast<USpartaGameInstance>(GameInstance);
if (SpartaGameInstance) {
CurrentLevelIndex = SpartaGameInstance->CurrentLevelIndex;
}
}
SpawnedCoinCount = 0;
CollectedCoinCount = 0;
TArray<AActor*> FoundVolumes;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASpawnVolume::StaticClass(), FoundVolumes);
const int32 ItemToSpawn = 40;
if (FoundVolumes.Num() > 0)
{
ASpawnVolume* SpawnVolume = Cast<ASpawnVolume>(FoundVolumes[0]);
if (SpawnVolume)
{
for (int32 i = 0; i < ItemToSpawn; i++) {
AActor* SpawnedActor = SpawnVolume->SpawnRandomItem();
if (SpawnedActor && SpawnedActor->IsA(ACoinItem::StaticClass()))
{
SpawnedCoinCount++;
}
}
}
}
// 여기에서 HUD 업데이트 함수 호출
UpdateHUD();
GetWorldTimerManager().SetTimer(
LevelTimerHandle,
this,
&ASpartaGameState::OnLevelTimeUp,
LevelDuration,
false
);
UE_LOG(LogTemp, Warning, TEXT("Level %d Start!, Spawned %d coin"),
CurrentLevelIndex + 1,
SpawnedCoinCount);
}
// 여기에서 HUD 업데이트 함수 호출
void ASpartaGameState::OnGameOver()
{
UpdateHUD();
UE_LOG(LogTemp, Warning, TEXT("Game Over!!"));
}
void ASpartaGameState::UpdateHUD()
{
if (APlayerController* PlayerController = GetWorld()->GetFirstPlayerController())
{
if (ASpartaPlayerController* SpartaPlayerController = Cast <ASpartaPlayerController>(PlayerController))
{
if (UUserWidget* HUDWidget = SpartaPlayerController->GetHUDWidget())
{
if (UTextBlock* TimeText = Cast<UTextBlock>(HUDWidget->GetWidgetFromName(TEXT("Time"))))
{
float RemainingTime = GetWorldTimerManager().GetTimerRemaining(LevelTimerHandle);
TimeText->SetText(FText::FromString(FString::Printf(TEXT("Time: %.1f"), RemainingTime)));
}
if (UTextBlock* ScoreText = Cast<UTextBlock>(HUDWidget->GetWidgetFromName(TEXT("Score"))))
{
if (UGameInstance* GameInstance = GetGameInstance())
{
USpartaGameInstance* SpartaGameInstance = Cast<USpartaGameInstance>(GameInstance);
if (SpartaGameInstance)
{
ScoreText->SetText(FText::FromString(FString::Printf(TEXT("Score: %d"), SpartaGameInstance->TotalScore)));
}
}
}
if (UTextBlock* LevelIndexText = Cast<UTextBlock>(HUDWidget->GetWidgetFromName(TEXT("Level"))))
{
LevelIndexText->SetText(FText::FromString(FString::Printf(TEXT("Level: %d"), CurrentLevelIndex + 1)));
}
}
}
}
}
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "SpartaPlayerController.generated.h"
class UInputMappingContext;
class UInputAction;
UCLASS()
class TESTPROJECT2_API ASpartaPlayerController : public APlayerController
{
GENERATED_BODY()
public:
ASpartaPlayerController();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
UInputMappingContext* InputMappingContext;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
UInputAction* MoveAction;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
UInputAction* JumpAction;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
UInputAction* LookAction;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
UInputAction* SprintAction;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI")
TSubclassOf<UUserWidget> HUDWidgetClass;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HUD")
UUserWidget * HUDWidgetInstance;
UFUNCTION(BlueprintPure, Category = "HUD")
UUserWidget* GetHUDWidget() const;
protected:
virtual void BeginPlay() override;
};
#include "SpartaPlayerController.h"
#include "EnhancedInputSubsystems.h"
#include "SpartaGameState.h"
#include "Blueprint/UserWidget.h" // 헤더 추가
ASpartaPlayerController::ASpartaPlayerController()
: InputMappingContext(nullptr),
MoveAction(nullptr),
JumpAction(nullptr),
LookAction(nullptr),
SprintAction(nullptr),
HUDWidgetClass(nullptr),
HUDWidgetInstance(nullptr)
{
}
void ASpartaPlayerController::BeginPlay()
{
Super::BeginPlay();
if (ULocalPlayer* LocalPlayer = GetLocalPlayer())
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem =
LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
{
if (InputMappingContext) {
Subsystem->AddMappingContext(InputMappingContext, 0);
}
}
}
if (HUDWidgetClass)
{
HUDWidgetInstance = CreateWidget<UUserWidget>(this, HUDWidgetClass);
if (HUDWidgetInstance)
{
HUDWidgetInstance->AddToViewport();
}
}
ASpartaGameState* SpartaGameState = GetWorld() ? GetWorld() -> GetGameState<ASpartaGameState>() : nullptr;
if (SpartaGameState)
{
SpartaGameState->UpdateHUD();
}
}
UUserWidget* ASpartaPlayerController::GetHUDWidget() const
{
return HUDWidgetInstance;
}
실제 게임 플레이 장면
