[UE4 지뢰찾기] 타일을 관리하는 Board(판) 만들기
타일을 관리하는 Board 클래스를 생성한다.
게임이 시작되면 GameMode에 의해 Board는 타일맵을 생성한다.
UCLASS()
class FINDMINE_API ABoard : public AActor
{
GENERATED_BODY()
public:
ABoard();
protected:
virtual void BeginPlay() override;
float GamePlayTime = 0; // 이번 게임 플레이 시간
int32 Row = 0; // 행, x에 해당
int32 Column = 0; // 열, y에 해당
int32 UserSetFlagCount = 0; // 유저가 세운 깃발 수
int32 MineSetFlagCount = 0; // 실제 지뢰 위에 깃발 수
TArray<TArray<ATile*>> TileMap;
ATile* GetTileAt(int x, int y);
void ChangeTileMesh(ATile* pTile); // 타일 메시 변경 함수
void CheckNearTileState(int32 x, int32 y); // x, y 주변 탐색 함수, 재귀적으로 반복
void CheckTileState(int32 x, int32 y); // X, Y 좌표에 해당하는 타일 검사
public:
UPROPERTY(VisibleAnywhere, Category = "Board")
int32 TotalMineCount = 0; // 전체 지뢰 갯수
/* 일반 노말 상태의 이미지*/
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Board")
UMaterial* NormalTileMaterial;
/* 지뢰 상태의 이미지*/
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Board")
UMaterial* MineTileMaterial;
/* 깃발 꽂은 상태의 이미지*/
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Board")
UMaterial* FlagTileMaterial;
/* 숫자 상태의 이미지 배열, 인덱스 0은 주변에 없는 칸, 1부터는 숫자 1*/
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Board")
TArray<UMaterial*> NumberTileMaterials;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
UFUNCTION()
void CreateBoard(int32 row, int32 column, int32 mineCount);
UFUNCTION()
void DestroyBoard();
UFUNCTION()
EMineGameState CheckMineInUserTouchedTile(int32 x, int32 y, bool isSetFlag);
inline void SetGamePlayTime(float Time) { GamePlayTime = Time; };
inline float GetGamePlayTime() { return GamePlayTime; };
};
생성자에서는 기본적인 데이터를 만들어준다.
게임을 시작하기 전까지는 특별하게 하는 동작이 없기 때문에 타일 관리에 사용될 리소스 정도만 생성한다.
ABoard::ABoard()
{
PrimaryActorTick.bCanEverTick = true;
SetRootComponent(CreateDefaultSubobject<USceneComponent>(TEXT("Root")));
MineTileMaterial = CreateDefaultSubobject<UMaterial>(TEXT("MineTileMaterial"));
NormalTileMaterial = CreateDefaultSubobject<UMaterial>(TEXT("NormalTileMaterial"));
FlagTileMaterial = CreateDefaultSubobject<UMaterial>(TEXT("FlagTileMaterial"));
}
중요한 부분인 CreateBoard 함수이다.
void ABoard::CreateBoard(int32 row, int32 column, int32 mineCount)
{
int32 nInitMineCount = 0;
FVector OriginLocation = GetActorLocation();
float x, y;
this->Row = row;
this->Column = column;
this->TotalMineCount = mineCount;
for (int i = 0; i < row; i++)
{
TArray<ATile*> RowArray;
y = OriginLocation.Z - i * 100;
for (int j = 0; j < column; j++)
{
ATile* tile = (ATile*)GetWorld()->SpawnActor(ATile::StaticClass());
if (tile)
{
if (nInitMineCount < mineCount)
{
//?? KGR 지뢰 세팅 함수 변경
tile->SetTileHasMine(true);
nInitMineCount++;
}
x = OriginLocation.Y + j * 100;
//tile->SetActorRelativeLocation(FVector(OriginLocation.X, x, y));
tile->SetActorLocation(FVector(-500, x-395, y+395));
ChangeTileMesh(tile);
RowArray.Emplace(tile);
UE_LOG(LogTemp, Warning, TEXT("Row: %d, Column : %d"), i, j);
UE_LOG(LogTemp, Warning, TEXT("x: %f, y : %f"), x, y);
}
}
TileMap.Add(RowArray);
}
// 타일의 주변 지뢰 숫자 세팅
}
아직 타일 주변 지뢰를 찾아 갯수를 세팅하는 부분은 생성하지 않았다.
현재 지뢰를 생성하는 로직을 그냥 순차적으로 부여하도록 해놓았다.
지뢰배치는 나중으로 미룬다.
게임이 클리어되거나 새로운 게임을 생성할 때 사용될 DestroyBoard 함수도 제대로 만들어준다.
void ABoard::DestroyBoard()
{
this->Row = 0;
this->Column = 0;
this->TotalMineCount = 0;
for (int i = 0; i < Row; i++)
{
TArray<ATile*> row = TileMap[i];
if (row.Num() != 0)
{
for (int j = 0; j < Column; j++)
{
row[j]->Destroy();
}
row.Empty();
}
}
TileMap.Empty();
}
매번 TArray에서 찾아내기 불편하니 좌표를 받아 해당 타일을 가져오는 함수도 생성한다.
ATile* ABoard::GetTileAt(int x, int y)
{
if (x < 0 || y < 0)
return nullptr;
if (x >= TileMap.Num())
return nullptr;
TArray<ATile*> column = TileMap[x];
if (y >= column.Num())
return nullptr;
return column[y];
}
유저가 특정 타일을 터치 또는 클릭했을 때 Board에서 체크한다.
이 때 타일의 상태를 가져와 현재 게임 상태를 계산하여 게임 상태를 반환한다.
여기서 return된 게임 상태를 GameMode에서 체크하여 게임 종료 선언 등을 진행한다.
EMineGameState ABoard::CheckMineInUserTouchedTile(int32 x, int32 y, bool isSetFlag)
{
if (x < 0 || x >= this->Row || y < 0 || y >= this->Row)
return EMineGameState::GS_NotStartGame;
ATile* userTouchTile = GetTileAt(x, y);
if (userTouchTile == nullptr)
return EMineGameState::GS_NotStartGame;
ETileState touchTileState = userTouchTile->CheckTileState(isSetFlag);
// 타일 메시 변경
ChangeTileMesh(userTouchTile);
// 타일 애니메이션 동작
userTouchTile->StartTileAnimation();
// 게임 상태 체크
switch (touchTileState)
{
case ETileState::TS_OpenClear:
// 해당 타일의 주변에 지뢰가 전혀 없으므로 주변 탐색 진행
CheckNearTileState(x, y);
break;
case ETileState::TS_Mine:
// 지뢰를 클릭했으므로 게임 종료
return EMineGameState::GS_StepOnMine;
break;
case ETileState::TS_FlagNotMine:
// 유저가 지뢰라고 생각하여 깃발을 심었지만 지뢰가 아님, 종료체크 안함
this->UserSetFlagCount++;
break;
case ETileState::TS_NotOpenFlagNotMine:
// 잘못된 깃발을 되돌리는 중
userTouchTile->SetTileState(ETileState::TS_NotOpen);
this->UserSetFlagCount--;
break;
case ETileState::TS_Flag:
// 유저가 해당 타일이 지뢰라고 생각하여 깃발을 심었기에 종료 체크 진행
this->UserSetFlagCount++;
this->MineSetFlagCount++;
break;
case ETileState::TS_NotOpen:
// 깃발을 눌렀을 때만 이게 반환됨. 현재 깃발 개수 감소
this->MineSetFlagCount--;
this->UserSetFlagCount--;
break;
default:
// 게임 종료 상황이 아님
break;
}
// 모든 지뢰를 찾고 잘못 선택한 지뢰가 없으면 게임 클리어
if (MineSetFlagCount >= TotalMineCount && MineSetFlagCount == UserSetFlagCount)
return EMineGameState::GS_FoundAllMine;
return EMineGameState::GS_Playing;
}
터치한 타일의 주변을 체크하는 것은 재귀적으로 동작한다.
void ABoard::CheckNearTileState(int32 x, int32 y)
{
if (x < 0 || x >= this->Row || y < 0 || y >= this->Column)
return;
CheckTileState(x, y - 1); // 상
CheckTileState(x, y + 1); // 하
CheckTileState(x - 1, y); // 좌
CheckTileState(x + 1, y); // 우
}
void ABoard::CheckTileState(int32 x, int32 y)
{
if (x < 0 || x >= this->Row || y < 0 || y >= this->Column)
return;
ATile* tile = GetTileAt(x, y);
if (tile == nullptr)
return;
switch (tile->GetTileState())
{
case ETileState::TS_NotOpen:
if (tile->GetTilehasMine() == true)
{
// 해당 타일이 지뢰면 멈춤
break;
}
else if (tile->GetNearMineCount() == 0)
{
// 주변 지뢰가 없으면 다시 반복
tile->SetTileState(ETileState::TS_OpenClear);
CheckNearTileState(x, y);
}
else
{
// 주변 지뢰가 있으면 타일 상태만 바꿈
tile->SetTileState(ETileState::TS_OpenCount);
}
ChangeTileMesh(tile);
break;
default:
break;
}
return;
}
위 코드는 지뢰를 만나거나 타일 맵 밖을 만나게 된다면 종료될 것이다.
타일에 변화가 생긴다면 타일의 외형도 변경해주어야 한다.
이때는 Material만 바꿔 지속적으로 StaticMeshComponent를 재생성하는 일을 줄인다.
void ABoard::ChangeTileMesh(ATile* pTile)
{
if (pTile == nullptr)
return;
switch (pTile->GetTileState())
{
case ETileState::TS_Mine:
pTile->SetTileMeshMaterial(this->MineTileMaterial);
break;
case ETileState::TS_Flag:
case ETileState::TS_FlagNotMine:
pTile->SetTileMeshMaterial(this->FlagTileMaterial);
break;
case ETileState::TS_NotOpen:
pTile->SetTileMeshMaterial(this->NormalTileMaterial);
break;
case ETileState::TS_OpenCount:
case ETileState::TS_OpenClear:
pTile->SetTileMeshMaterial(this->NumberTileMaterials[pTile->GetNearMineCount()]);
break;
default:
break;
}
}
이 Board는 블루프린트로 만들어 월드맵에 배치한다.
해당 보드의 위치에서 타일이 생성되기 때문에 보드 위치를 잘 설정하도록 한다.
이 모든 과정을 마치고 게임을 시작하면 아래처럼 타일이 배치된다.
'[Unreal4] > 간단지뢰찾기 게임 만들기' 카테고리의 다른 글
[UE4 지뢰찾기] 키 입력 처리하기 (0) | 2022.12.12 |
---|---|
[UE4 지뢰찾기] 타일의 머테리얼 세팅하기 (0) | 2022.11.30 |
[UE4 지뢰찾기] 지뢰 매설하기 (0) | 2022.11.30 |
[UE4 지뢰찾기] 맵으로 사용될 타일 만들기 (0) | 2022.11.09 |
[UE4 지뢰찾기] 간단한 지뢰찾기 게임 만들기 (1) | 2022.10.17 |