MyGameInstance.h
#pragma once
#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "MyGameInstance.generated.h"
UCLASS()
class PUZZLESTUDY_API UMyGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
UMyGameInstance();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Game Data")
int32 PlayerScore;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Game Data")
int32 RemainingMoves;
UFUNCTION(BlueprintCallable, Category = "Game Functions")
void AddScore(int32 Points);
UFUNCTION(BlueprintCallable, Category = "Game Funcitons")
void DecreaseMoves();
UFUNCTION(BlueprintCallable, Category = "Game Functions")
void ResetGameState();
};
MyGameInstance.cpp
#include "MyGameInstance.h"
UMyGameInstance::UMyGameInstance()
{
PlayerScore = 0;
RemainingMoves = 30;
}
void UMyGameInstance::AddScore(int32 Points)
{
PlayerScore += Points;
}
void UMyGameInstance::DecreaseMoves()
{
if (RemainingMoves > 0)
{
RemainingMoves--;
}
}
void UMyGameInstance::ResetGameState()
{
PlayerScore = 0;
RemainingMoves = 30;
}
Tile.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Tile.generated.h"
UCLASS()
class PUZZLESTUDY_API ATile : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ATile();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Tile Properties")
FColor TileColor;
bool IsMatching(ATile* OtherTile);
void ProcessDataInParallerl();
};
Tile.cpp
#include "Tile.h"
#include "Async\ParallelFor.h"
// Sets default values
ATile::ATile()
{
// 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;
}
// Called when the game starts or when spawned
void ATile::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void ATile::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
bool ATile::IsMatching(ATile* OtherTile)
{
return TileColor == OtherTile->TileColor;
}
void ATile::ProcessDataInParallerl()
{
TArray<int32> DataArray;
DataArray.Init(0, 100);
ParallelFor(DataArray.Num(), [&](int32 Index)
{
DataArray[Index] = Index * 2;
}
);
UE_LOG(LogTemp, Warning, TEXT("ParallerlForFinish"));
}
TileGrid.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "TileGrid.generated.h"
class ATile;
UCLASS()
class PUZZLESTUDY_API ATileGrid : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ATileGrid();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Grid")
int32 GridWidth;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Grid")
int32 GridHeigh;
UPROPERTY()
TArray<ATile*> TileArray;
void InitializeGrid();
// 특정 타일의 위치를 얻는 함수
ATile* GetTile(int32 x, int32 y) const;
// 특정 타일의 위치를 설정하는 함수
void SetTile(int32 x, int32 y, ATile* Tile);
};
TileGrid.cpp
#include "TileGrid.h"
#include "Tile.h"
#include "Async\ParallelFor.h"
// Sets default values
ATileGrid::ATileGrid()
{
// 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;
GridWidth = 8;
GridHeigh = 8;
TileArray.SetNum(GridWidth * GridHeigh);
// 1차원 배열로 메모리 할당 8*8 형태
}
void ATileGrid::InitializeGrid()
{
// for문을 돌면서 타일 그리드 그리기
for (int32 x = 0; x < GridWidth; ++x)
{
for (int32 y = 0; y < GridHeigh; ++y)
{
// 타일을 생성하고, 1차원 배열에 저장
ATile* NewTile = GetWorld()->SpawnActor<ATile>(ATile::StaticClass());
SetTile(x, y, NewTile);
}
}
}
ATile* ATileGrid::GetTile(int32 x, int32 y) const
{
if (x < 0 || x >= GridWidth || y < 0 || y >= GridHeigh)
{
// 유효하지 않은 좌표 거리
return nullptr;
}
return TileArray[y * GridWidth + x];
}
void ATileGrid::SetTile(int32 x, int32 y, ATile* Tile)
{
if (x >= 0 && x < GridWidth && y >= 0 && y < GridHeigh)
{
TileArray[y * GridWidth + x] = Tile;
}
}
Match3GameMmode.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "Match3GameMmode.generated.h"
UCLASS()
class PUZZLESTUDY_API AMatch3GameMmode : public AGameModeBase
{
GENERATED_BODY()
public:
virtual void BeginPlay() override;
};
Match3GameMmode.cpp
#include "Match3GameMmode.h"
#include "MyGameInstance.h"
#include "TileGrid.h"
#include "Kismet\GameplayStatics.h"
void AMatch3GameMmode::BeginPlay()
{
Super::BeginPlay();
// 어느 클래스에서 가져오던 GetWorld내에 하나밖에 없으므로 내가 만든 MyGameInstance 이다
UMyGameInstance* GameInstance = Cast<UMyGameInstance>(UGameplayStatics::GetGameInstance(GetWorld()));
if (GameInstance)
{
GameInstance->ResetGameState();
}
ATileGrid* TileGrid = GetWorld()->SpawnActor<ATileGrid>(ATileGrid::StaticClass());
if (TileGrid)
{
TileGrid->InitializeGrid();
}
}
ParallelFor의 기본 개념
멀티스레딩을 활용한 타일 생성 최적화
ParallelFor의 장점
주의 사항
ParallelFor 기본 문법
ParallelFor(int32 Num, [&](int32 Index)
{
// 반복될 작업 로직 작성
});
ParallelFor 예제
TArray<int32> DataArray;
DataArray.Init(0, 100);
// 배열의 각 요소를 병렬로 수정하는 예시
ParallelFor(DataArray.Num(), [&](int32 Index)
{
DataArray[Index] = Index * 2;
});
AsyncTask
기본 개념
AsyncTask 기본예제
AsyncTask(ENamedThreads::ThreadType, TaskFunction);
// 비동기 처리 ( 비동기 타입, 실행될 함수 )
AsyncTask 사용 예제
#include "Async/Async.h"
void MyAsyncTask()
{
// 비동기 작업을 수행
Async(ENameThreads::AnyBackgroundThreadNormalTask, []()
{
// 백그라운드 스레드에서 실행될 작업
FPlatformProcess::Sleep(2.0f); // 예: 2초 동안 대기 (긴 작업을 시뮬레이션)
int32 Result = 42;
// 메인 스레드에서 후속 작업 실행
Async(ENameThreads::GameThread, [Result]()
{
// 메인 스레드에서 결과를 처리
UE_LOG(LogTemp, Warning, TEXT("AsyncTask completed! Result: %d"), Result);
});
});
}
ENameThreads
- ENameThreads는 주로 작업이 실행될 스레드나 스레드 그룹을 명시적으로 지정할 때 사용됩니다. Unreal Engine은 여러 스레드와 스레드 풀(Task Graph)을 사용하여 작업을 효율적으로 분산 처리합니다.
- 주요 역할
- 스레드에 이름을 지정하거나 특정 스레드 그룹을 정의합니다.
- 예시
- 메인 게임 스레드, 렌더 스레드, 또는 Task Graph에서 정의된 특정 스레드 그룹.
- 사용 상황
- 주로 FGraphEvent나 FGraphEventRef와 같은 Task Graph 작업에서 사용되며, 특정 작업을 특정 스레드에서 실행되도록 명시할 때 활용됩니다.
ENameThreads 예시 코드
// 특정 스레드에서 실행될 작업 정의 FGraphEventRef Task = FFunctionGraphTask::CreateAndDispatchWhenReady([]() { UE_LOG(LogTemp, Warning, TEXT("This is running on a named thread!")); }, TStatId(), nullptr, ENamedThreads::GameThread);
- ENamedThreads::GameThread : 이 코드는 작업을 메인 게임 스레드에서 실행하도록 지정합니다.
EAsyncExecution
- EAsyncExecution은 Unreal Engine에서 비동기 작업의 실행 방식을 정의하는 열거형입니다. 비동기 작업을 스레드풀에서 실행하거나 특정 스레드에서 실행하도록 설정할 때 사용됩니다. Async 함수를 호출할 때 이 열거형을 사용해 작업을 어느 스레드 그룹에서 처리할지 결정할 수 있습니다.
- 주요 역할
- 비동기 작업의 실행 환경을 설정합니다.
- 예시
- 메인 스레드, 백그라운드 스레드, 스레드풀 등.
사용 상황- Async 함수를 호출할 때 어떤 스레드에서 비동기 작업이 실행될지 선택합니다.
EAsyncExecution 예시 코드
Async(EAsyncExecution::ThreadPool, []() { // 백그라운드 스레드풀에서 실행할 작업 UE_LOG(LogTemp, Warning, TEXT("This is running in the thread pool!")); });
스레드 그룹
에서 작업을 실행하도록 명시
할 때 사용합니다.특정 스레드
에서 작업을 처리
할 수 있게 해줍니다쓰레드 풀( Thread Pool )
미리 생성된 스레드의 모음으로, 여러 개의 작업(작업 단위)을 동시에 처리할 수 있도록 하는 멀티스레딩 기법
- 쓰레드 풀의 개념
스레드 재사용
: 새로운 작업이 있을 때마다 새로운 스레드를 생성하지 않고, 쓰레드 풀에서 이미 생성된 스레드를 할당해 작업을 수행합니다.작업 대기열
: 작업이 생기면 큐(queue)에 추가되고, 풀에 있는 스레드들이 이를 가져가서 처리합니다. 모든 스레드가 바쁘면 새로운 작업은 대기열에 쌓이게 됩니다.성능 최적화
: 스레드의 생성과 소멸은 비용이 많이 들기 때문에, 미리 생성된 스레드를 재사용함으로써 시스템 자원을 아끼고 처리 시간을 줄일 수 있습니다.- 쓰레드 풀이 필요한 이유
스레드 생성 오버헤드 감소
: 스레드를 계속 생성하고 제거하는 과정은 비용이 많이 드므로, 풀을 사용하면 이 과정을 줄여 성능을 최적화할 수 있습니다.자원 관리
: 많은 스레드가 동시에 생성되면 시스템 자원을 과도하게 사용할 수 있습니다. 풀의 크기를 제한하여 자원 사용을 효율적으로 관리할 수 있습니다.작업 분배
: 작업을 자동으로 분배하여, 여러 작업을 동시에 처리하거나 특정 작업이 지연되는 것을 방지할 수 있습니다.
Unreal Engine 쓰레드 풀 예시
#include "Async/Async.h"
void ProcessHeavyTask()
{
// 쓰레드 풀에서 작업을 실행
Async(EAsyncExecution::ThreadPool, []()
{
// 이 작업은 쓰레드 풀의 스레드 중 하나에서 실행됩니다
FPlatformProcess::Sleep(2.0f); // 긴 작업 시뮬레이션
int32 Result = 100;
// 메인 스레드에서 후속 작업 실행
Async(EAsyncExecution::TaskGraphMainThread, [Result]()
{
UE_LOG(LogTemp, Warning, TEXT("Heavy task completed! Result: %d"), Result);
});
});
}
EAsyncExecution::ThreadPool
: 비동기 작업을 쓰레드 풀에서 실행하도록 지정합니다. 여기서 풀에 있는 스레드 중 하나가 할당되어 작업을 처리합니다.긴 작업
: 작업이 길어지더라도 쓰레드 풀이 사용되기 때문에 메인 스레드의 성능에 영향을 주지 않습니다.작업 완료 후 콜백
: 작업이 완료되면 결과를 메인 스레드에서 처리할 수 있도록 EAsyncExecution::TaskGraphMainThread를 사용합니다.성능 최적화
: 스레드 생성 및 소멸 비용이 줄어들어 성능이 향상됩니다.안정성
: 시스템 자원을 제한적으로 사용하여, 과도한 스레드 생성으로 인한 시스템 불안정성을 방지합니다.확장성
: 여러 작업을 효율적으로 분산 처리할 수 있어, 게임에서 대규모 연산을 수행할 때 유용합니다.