Tile.h 로직 수정
Tile.cpp 로직 수정
TileGrid.h 를 약간 수정
TileGrid.cpp 를 약간 수정
월드에 있는 Grid위치 조정
PlayerController를 상속받은 C++ 클래스 하나 추가한다
만든 클래스를 블루프린트로 만들고, Worldsetting에 설정한다
TWeakObjectPtr<ATile>
: TObjectPtr<ATile>
:UPROPERTY() 를 사용한 UObject 참조
: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")
FName TileType;
// 타일의 외형을 위한 Static Mesh Component
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Tile Properties")
UStaticMeshComponent* TileMesh;
// 타일의 StaticMesh 설정을 위한 TSubclassOf
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Tile Properties")
TMap<FName, UStaticMesh*> TileMeshes;
// 타일 매칭 확인 함수
bool IsMatching(ATile* OtherTile);
// 타일이 선택되었는지 여부
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Tile")
bool bIsSelected;
// 타일을 선택 또는 해제하는 함수
void SetSelected(bool bSelected);
// 타일의 외형을 TileType에 따라 설정하는 함수
void UpdateTileAppearance();
// 병렬 처리를 테스트하는 함수
void ProcessDataInParallerl();
protected:
void UpdateAppearance();
};
Tile.cpp
#include "Tile.h"
#include "Components\StaticMeshComponent.h"
#include "Engine\Engine.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;
// StaticMeshComponent 생성
TileMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("TileMesh"));
RootComponent = TileMesh; // 타일의 루트 컴포넌트 설정
// 메시 업데이트를 위한 타일 타입 초기화
TileType = FName("Default");
bIsSelected = false; // 선택여부 초기화
}
// Called when the game starts or when spawned
void ATile::BeginPlay()
{
Super::BeginPlay();
// 타일 외형 업데이트 ( TileType에 따라 메시 변경 )
UpdateTileAppearance();
}
// Called every frame
void ATile::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
bool ATile::IsMatching(ATile* OtherTile)
{
return TileType == OtherTile->TileType;
}
void ATile::SetSelected(bool bSelected)
{
bIsSelected = bSelected;
UpdateAppearance();
}
void ATile::UpdateTileAppearance()
{
// TileMeshs Map에 TileType에 해당하는 키값이 존재하는가?
if (TileMeshes.Contains(TileType))
{
TileMesh->SetStaticMesh(TileMeshes[TileType]);
}
else
{
UE_LOG(LogTemp, Error, TEXT("Attempting to update tile appearance"));
}
}
void ATile::ProcessDataInParallerl()
{
TArray<int32> DataArray;
DataArray.Init(0, 100);
ParallelFor(DataArray.Num(), [&](int32 Index)
{
DataArray[Index] = Index * 2;
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, FString::Printf(TEXT("Tile %d - %d "), Index, DataArray[Index]));
}
}
);
UE_LOG(LogTemp, Warning, TEXT("ParallerlForFinish"));
}
void ATile::UpdateAppearance()
{
// 선택되었을 때의 시각적 피드백 ( 예: 색상 변경 )
if (bIsSelected)
{
// 타일이 선택되었을 때의 효과
// 예: StaticMeshComponent의 색상 변경
if (UStaticMeshComponent* MeshComponent = TileMesh)
{
// 선택된 타일을 강조
TileMesh->SetRenderCustomDepth(true);
// 강조된 색상 변경
TileMesh->SetScalarParameterValueOnMaterials(TEXT("EmissiveStrength"), 10.0f);
}
}
else
{
if (UStaticMeshComponent* MeshComponent = TileMesh)
{
// 기본 상태로 복귀
TileMesh->SetRenderCustomDepth(false);
// 기본 색상 변경
TileMesh->SetScalarParameterValueOnMaterials(TEXT("EmissiveStrength"), 0.0f);
}
}
}
TileGrid.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "TileGrid.generated.h"
class ATile;
UCLASS()
class PUZZLESTUDY_API ATileGrid : public AActor
{
GENERATED_BODY()
protected:
virtual void BeginPlay() override;
public:
// Sets default values for this actor's properties
ATileGrid();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Tile Grid")
int32 GridWidth;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Tile Grid")
int32 GridHeigh;
// 타일 간의 배치 간격 ( 기본값 : 100 )
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Tile Grid")
float TileSpacing;
UPROPERTY()
TArray<ATile*> TileArray;
// 타일을 생성할 Blueprint 클래스를 선택할 수 있도록 TSubClassOf 사용
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Tile Grid")
TSubclassOf<ATile> TileClass; // TileClass는 월드에 배치된 TileGrid에서 설정할 수 있음
// 그리드 초기화하는 함수
void InitializeGrid();
// 특정 타일의 위치를 얻는 함수
ATile* GetTile(int32 x, int32 y) const;
// 특정 타일의 위치를 설정하는 함수
void SetTile(int32 x, int32 y, ATile* Tile);
// 바둑판 크기 정의
const int32 Rows = 4;
const int32 Colums = 4;
// TArray 선언
TArray<int32> GridArray;
// 바둑판 그리기 함수
void DrawGrid();
};
TileGrid.cpp
#include "TileGrid.h"
#include "Tile.h"
#include "Async\ParallelFor.h"
#include "Async/Async.h"
void ATileGrid::BeginPlay()
{
Super::BeginPlay();
InitializeGrid();
}
// Sets default values
ATileGrid::ATileGrid()
{
PrimaryActorTick.bCanEverTick = true;
GridWidth = 8;
GridHeigh = 8;
TileSpacing = 100;
TileArray.SetNum(GridWidth * GridHeigh);
// 1차원 배열로 메모리 할당 8*8 형태
}
void ATileGrid::InitializeGrid()
{
// 가능한 TileType 리스트
TArray<FName> TileTypes =
{
FName("Cone"),
FName("Cube"),
FName("Cylinder"),
FName("Sphere"),
FName("Capsule"),
FName("Pyramid")
};
for (int32 x = 0; x < GridWidth; ++x)
{
for (int32 y = 0; y < GridHeigh; ++y)
{
// 비동기적으로 타일을 생성
AsyncTask(ENamedThreads::GameThread, [this, x, y, TileTypes]()
{
// 타일 타입을 핸덤하게 결정 ( 비동기 작업 )
FName RandomTileType = TileTypes[FMath::RandRange(0, TileTypes.Num() - 1)];
// 게임 스레드에서 타일 생성과 외형 설정을 처리
AsyncTask(ENamedThreads::GameThread, [this, x, y, RandomTileType]()
{
if (!TileClass)
{
UE_LOG(LogTemp, Warning, TEXT("TileClass is not set in TileGrid "));
return;
}
FActorSpawnParameters SpawnParams;
ATile* NewTile = GetWorld()->SpawnActor<ATile>(TileClass, SpawnParams);
if (NewTile)
{
NewTile->TileType = RandomTileType;
NewTile->UpdateTileAppearance();
// 타일을 TIleGrid의 자식으로 부착
NewTile->AttachToActor(this, FAttachmentTransformRules::KeepRelativeTransform);
FVector RelativeLocation = FVector(x * TileSpacing, y * TileSpacing, 0.0f);
NewTile->SetActorRelativeLocation(RelativeLocation);
// 그리드에 타일 저장
SetTile(x, y, NewTile);
UE_LOG(LogTemp, Warning, TEXT("Tile Create at [ %d, %d ] with type %s "), x, y, *RandomTileType.ToString());
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Failed to Spawn Tile at [ %d, %d ]"), x, y);
}
});
});
}
}
// 가능한 TileType 리스트
}
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;
}
}
// 바둑판 출력 함수
void ATileGrid::DrawGrid()
{
// 1차원 배열을 바둑판 형태로 출력
for (int32 i = 0; i < Rows; i++)
{
FString RowText;
for (int32 j = 0; j < Colums; j++)
{
int32 Index = i * Colums + j;
RowText += FString::Printf(TEXT("%2d "), GridArray[Index]);
}
// 현재 행 출력
UE_LOG(LogTemp, Warning, TEXT("Row %d : %s "), i, *RowText);
}
}
TileGamePlayerController.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "InputActionValue.h"
#include "TIleGmaePlayerController.generated.h"
class UInputMappingContext;
class UInputAction;
class ATile;
class ATileGrid;
UCLASS()
class PUZZLESTUDY_API ATIleGmaePlayerController : public APlayerController
{
GENERATED_BODY()
public:
ATIleGmaePlayerController();
protected:
// Called when the game starts
virtual void BeginPlay() override;
// 마우스 커서 보이기
virtual void SetupInputComponent() override;
// 첫 번째와 두 번째로 선택된 타일을 약한 참조로 저장 ( GC 대응 )
TWeakObjectPtr<ATile> FirstSelectedTile; // +참고 _)UPROPETY()를 사용하면 Unreal Engine의 가비지 컬렉션에 의해 관리되므로, 안전하게 참조를 유지
TWeakObjectPtr<ATile> SecondSelectedTile; // +참고 _)UPROPETY()를 사용하면 Unreal Engine의 가비지 컬렉션에 의해 관리되므로, 안전하게 참조를 유지
// TWeakObjectPtr를 사용하면 가비지 켈렉션으로 삭제된 객체가 있는지 체크 할 수 있습니다
// 예시 : 스타그래프트 부대 지정 메모리를 간직하기 위해서
// TileGrid에 대한 참조
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Grid")
ATileGrid* TileGrid;
// Input 관련
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
UInputMappingContext* InputMapping;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
UInputAction* SelectTileAction;
// 타일을 선택하는 함수
void SelectTile(const FInputActionValue& Value);
// 두 개의 타일을 선택하고 처리하는 함수
void ProcessSelectedTile();
};
TileGamePlayerController.cpp
#include "TIleGmaePlayerController.h"
#include "Tile.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "SwapTileCommand.h"
#include "TileCommandInvoker.h"
ATIleGmaePlayerController::ATIleGmaePlayerController()
{
// 약한 참조로 타일을 관리 ( 처음에는 아무 것도 선택되 않는 상태 )
FirstSelectedTile = nullptr;
SecondSelectedTile = nullptr;
}
void ATIleGmaePlayerController::BeginPlay()
{
Super::BeginPlay();
// 마우스 커서 보이기 설정
bShowMouseCursor = true;
// Enhanced Input 설정
if (APlayerController* PlayerController = Cast<APlayerController>(this))
{
// Enhanced Input 하위 시스템을 추가하여 InputMappingContext를 활성화
UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer());
if (Subsystem)
{
Subsystem->AddMappingContext(InputMapping, 0); // 우선순위 0으로 설정
}
}
}
void ATIleGmaePlayerController::SetupInputComponent()
{
Super::SetupInputComponent();
if (UEnhancedInputComponent* EnhancedInputComp = Cast< UEnhancedInputComponent>(InputComponent))
{
EnhancedInputComp->BindAction(SelectTileAction, ETriggerEvent::Triggered, this, &ATIleGmaePlayerController::SelectTile);
}
}
void ATIleGmaePlayerController::SelectTile(const FInputActionValue& Value)
{
// 마우스 클릭 위치를 가져옴
FHitResult Hit;
GetHitResultUnderCursor(ECC_Visibility, false, Hit);
if (Hit.bBlockingHit)
{
ATile* ClickedTile = Cast<ATile>(Hit.GetActor());
if (ClickedTile)
{
if (!FirstSelectedTile.IsValid())
{
// 첫 번째 타일 선택
FirstSelectedTile = ClickedTile;
FirstSelectedTile->SetSelected(true);
UE_LOG(LogTemp, Warning, TEXT("First Tile selected : %s "), *FirstSelectedTile->GetName());
}
else if (!SecondSelectedTile.IsValid() && ClickedTile != FirstSelectedTile)
{
// 두 번쨰 타일 선택
SecondSelectedTile = ClickedTile;
SecondSelectedTile->SetSelected(true);
UE_LOG(LogTemp, Warning, TEXT("Second Tile selected : %s "), *SecondSelectedTile->GetName());
// 두 타일 선택 완료 후 처리
ProcessSelectedTile();
}
}
}
}
void ATIleGmaePlayerController::ProcessSelectedTile()
{
// 두 개의 타일이 선택되었을때,
if (FirstSelectedTile.IsValid() && SecondSelectedTile.IsValid())
{
// 타일 처리 로직 ( 자리교한, 매칭 확인 )
// 교환 명령 생성
USwapTileCommand* SwapCommand = NewObject<USwapTileCommand>(); // 클래스 이름 수정
SwapCommand->Initalize(FirstSelectedTile.Get(), SecondSelectedTile.Get());
// 커맨드 실행
ATileCommandInvoker* CommandInvoker = GetWorld()->SpawnActor<ATileCommandInvoker>();
CommandInvoker->ExcuteCommand(SwapCommand);
// 매칭 확인 로직 ( 교환 후 매칭이 있는지 확인 )
// MatchCheck(FirstSelectedTile, SecondSelectedTile);
// 두 타일의 선택 해제
FirstSelectedTile->SetSelected(false);
SecondSelectedTile->SetSelected(false);
// 선택 초기화
FirstSelectedTile = nullptr;
SecondSelectedTile = nullptr;
}
}
SwapTileCommand.h
#pragma once
#include "CoreMinimal.h"
#include "Command.h"
#include "SwapTileCommand.generated.h"
class ATile;
UCLASS()
class PUZZLESTUDY_API USwapTileCommand : public UObject, public ICommand
{
GENERATED_BODY()
private:
ATile* FirstTile;
ATile* SecondTile;
FVector FirstTileOrigineType;
FVector SecondTileOrigineType;
public:
void Initalize(ATile* InFirstTile, ATile* InSecondTile);
// ICommand의 인터페이스 함수들 Imprement ~
virtual void Excute() override;
virtual void Undo() override;
};
SwapTileCommand.cpp
#include "SwapTileCommand.h"
#include "Tile.h"
void USwapTileCommand::Initalize(ATile* InFirstTile, ATile* InSecondTile)
{
FirstTile = InFirstTile;
SecondTile = InSecondTile;
// 원래 위치를 저장
FirstTileOrigineType = FirstTile->GetActorLocation();
SecondTileOrigineType = SecondTile->GetActorLocation();
}
void USwapTileCommand::Excute()
{
// 서로 두 타일 위치를 교환
FirstTile->SetActorLocation(SecondTileOrigineType);
SecondTile->SetActorLocation(FirstTileOrigineType);
// TODO : 매칭 확인 등의 로직
}
void USwapTileCommand::Undo()
{
// 타일의 워치를 원래대로 되돌림
FirstTile->SetActorLocation(FirstTileOrigineType);
SecondTile->SetActorLocation(SecondTileOrigineType);
}