Add cell selection, auto camera placement, and FGridMath
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
46e5509e3c
commit
3a8082775f
10 files changed
+167
-21
No files matched your search
@@ -27,6 +27,7 @@ AGridCell::AGridCell()
|
|||||||
|
|
||||||
// カーソルのレイキャスト(Visibilityチャンネル)に当たるようにする
|
// カーソルのレイキャスト(Visibilityチャンネル)に当たるようにする
|
||||||
MeshComponent->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
|
MeshComponent->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
|
||||||
|
MeshComponent->SetCollisionObjectType(ECC_WorldDynamic);
|
||||||
MeshComponent->SetCollisionResponseToAllChannels(ECR_Ignore);
|
MeshComponent->SetCollisionResponseToAllChannels(ECR_Ignore);
|
||||||
MeshComponent->SetCollisionResponseToChannel(ECC_Visibility, ECR_Block);
|
MeshComponent->SetCollisionResponseToChannel(ECC_Visibility, ECR_Block);
|
||||||
|
|
||||||
@@ -44,14 +45,26 @@ void AGridCell::BeginPlay()
|
|||||||
Super::BeginPlay();
|
Super::BeginPlay();
|
||||||
|
|
||||||
DynamicMaterial = MeshComponent->CreateDynamicMaterialInstance(0);
|
DynamicMaterial = MeshComponent->CreateDynamicMaterialInstance(0);
|
||||||
SetHighlighted(false);
|
RefreshColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AGridCell::SetHighlighted(bool bHighlighted)
|
void AGridCell::SetHovered(bool bInHovered)
|
||||||
|
{
|
||||||
|
bHovered = bInHovered;
|
||||||
|
RefreshColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGridCell::SetSelected(bool bInSelected)
|
||||||
|
{
|
||||||
|
bSelected = bInSelected;
|
||||||
|
RefreshColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGridCell::RefreshColor()
|
||||||
{
|
{
|
||||||
if (DynamicMaterial)
|
if (DynamicMaterial)
|
||||||
{
|
{
|
||||||
DynamicMaterial->SetVectorParameterValue(TEXT("Color"),
|
const FLinearColor& Color = bSelected ? SelectedColor : (bHovered ? HoverColor : NormalColor);
|
||||||
bHighlighted ? HighlightColor : NormalColor);
|
DynamicMaterial->SetVectorParameterValue(TEXT("Color"), Color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "GridManager.h"
|
#include "GridManager.h"
|
||||||
#include "GridCell.h"
|
#include "GridCell.h"
|
||||||
|
#include "GridMath.h"
|
||||||
#include "Engine/World.h"
|
#include "Engine/World.h"
|
||||||
|
|
||||||
// このファイル専用のログカテゴリ
|
// このファイル専用のログカテゴリ
|
||||||
@@ -20,6 +21,21 @@ void AGridManager::BeginPlay()
|
|||||||
GenerateGrid();
|
GenerateGrid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AGridCell* AGridManager::GetCellAt(FIntPoint Coord) const
|
||||||
|
{
|
||||||
|
const int32 Index = FGridMath::CoordToIndex(Coord, GridSizeX, GridSizeY);
|
||||||
|
if (Cells.IsValidIndex(Index) && Cells[Index] && Cells[Index]->GridCoord == Coord)
|
||||||
|
{
|
||||||
|
return Cells[Index];
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
FVector AGridManager::GetGridCenter() const
|
||||||
|
{
|
||||||
|
return FGridMath::GetGridCenter(GetActorLocation(), GridSizeX, GridSizeY, CellSize);
|
||||||
|
}
|
||||||
|
|
||||||
void AGridManager::GenerateGrid()
|
void AGridManager::GenerateGrid()
|
||||||
{
|
{
|
||||||
UWorld* World = GetWorld();
|
UWorld* World = GetWorld();
|
||||||
@@ -41,7 +57,7 @@ void AGridManager::GenerateGrid()
|
|||||||
{
|
{
|
||||||
for (int32 X = 0; X < GridSizeX; ++X)
|
for (int32 X = 0; X < GridSizeX; ++X)
|
||||||
{
|
{
|
||||||
const FVector Location = Origin + FVector(X * CellSize, Y * CellSize, 0.0f);
|
const FVector Location = FGridMath::CellToWorld(FIntPoint(X, Y), Origin, CellSize);
|
||||||
|
|
||||||
FActorSpawnParameters Params;
|
FActorSpawnParameters Params;
|
||||||
Params.SpawnCollisionHandlingOverride =
|
Params.SpawnCollisionHandlingOverride =
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#include "GridMath.h"
|
||||||
|
|
||||||
|
FVector FGridMath::CellToWorld(FIntPoint Coord, const FVector& Origin, float CellSize)
|
||||||
|
{
|
||||||
|
return Origin + FVector(Coord.X * CellSize, Coord.Y * CellSize, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
FIntPoint FGridMath::WorldToCell(const FVector& World, const FVector& Origin, float CellSize)
|
||||||
|
{
|
||||||
|
const FVector Local = World - Origin;
|
||||||
|
return FIntPoint(FMath::RoundToInt(Local.X / CellSize), FMath::RoundToInt(Local.Y / CellSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGridMath::IsInBounds(FIntPoint Coord, int32 SizeX, int32 SizeY)
|
||||||
|
{
|
||||||
|
return Coord.X >= 0 && Coord.X < SizeX && Coord.Y >= 0 && Coord.Y < SizeY;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 FGridMath::CoordToIndex(FIntPoint Coord, int32 SizeX, int32 SizeY)
|
||||||
|
{
|
||||||
|
return IsInBounds(Coord, SizeX, SizeY) ? Coord.Y * SizeX + Coord.X : INDEX_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
FVector FGridMath::GetGridCenter(const FVector& Origin, int32 SizeX, int32 SizeY, float CellSize)
|
||||||
|
{
|
||||||
|
return Origin + FVector((SizeX - 1) * CellSize * 0.5f, (SizeY - 1) * CellSize * 0.5f, 0.0f);
|
||||||
|
}
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
#include "TacticaCameraPawn.h"
|
#include "TacticaCameraPawn.h"
|
||||||
#include "Camera/CameraComponent.h"
|
#include "Camera/CameraComponent.h"
|
||||||
|
#include "GridManager.h"
|
||||||
|
#include "Kismet/GameplayStatics.h"
|
||||||
|
|
||||||
|
DEFINE_LOG_CATEGORY_STATIC(LogTacticaCamera, Log, All);
|
||||||
|
|
||||||
ATacticaCameraPawn::ATacticaCameraPawn()
|
ATacticaCameraPawn::ATacticaCameraPawn()
|
||||||
{
|
{
|
||||||
@@ -7,9 +11,26 @@ ATacticaCameraPawn::ATacticaCameraPawn()
|
|||||||
|
|
||||||
Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
|
Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
|
||||||
SetRootComponent(Camera);
|
SetRootComponent(Camera);
|
||||||
|
}
|
||||||
// 3x3グリッド(中心 (100,100))を斜め上から見下ろす位置。
|
|
||||||
// 配置したPawnのActor位置を変えれば調整できる
|
void ATacticaCameraPawn::BeginPlay()
|
||||||
Camera->SetRelativeLocation(FVector(-250.0f, 100.0f, 450.0f));
|
{
|
||||||
Camera->SetRelativeRotation(FRotator(-60.0f, 0.0f, 0.0f));
|
Super::BeginPlay();
|
||||||
|
|
||||||
|
const AGridManager* Grid = Cast<AGridManager>(
|
||||||
|
UGameplayStatics::GetActorOfClass(this, AGridManager::StaticClass()));
|
||||||
|
if (!Grid)
|
||||||
|
{
|
||||||
|
UE_LOG(LogTacticaCamera, Warning, TEXT("AGridManager が見つかりません。カメラは配置されません"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// グリッドの長辺の半分(余白つき)が画角に収まる距離を求める
|
||||||
|
const float HalfExtent = FMath::Max(Grid->GridSizeX, Grid->GridSizeY) * Grid->CellSize * 0.5f * MarginScale;
|
||||||
|
const float HalfFovRad = FMath::DegreesToRadians(Camera->FieldOfView * 0.5f);
|
||||||
|
const float Distance = HalfExtent / FMath::Tan(HalfFovRad);
|
||||||
|
|
||||||
|
const FRotator Rotation(Pitch, 0.0f, 0.0f);
|
||||||
|
const FVector Location = Grid->GetGridCenter() - Rotation.Vector() * Distance;
|
||||||
|
SetActorLocationAndRotation(Location, Rotation);
|
||||||
}
|
}
|
||||||
@@ -33,19 +33,24 @@ void ATacticaPlayerController::PlayerTick(float DeltaTime)
|
|||||||
|
|
||||||
if (HoveredCell)
|
if (HoveredCell)
|
||||||
{
|
{
|
||||||
HoveredCell->SetHighlighted(false);
|
HoveredCell->SetHovered(false);
|
||||||
}
|
}
|
||||||
HoveredCell = Cell;
|
HoveredCell = Cell;
|
||||||
if (HoveredCell)
|
if (HoveredCell)
|
||||||
{
|
{
|
||||||
HoveredCell->SetHighlighted(true);
|
HoveredCell->SetHovered(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AGridCell* ATacticaPlayerController::GetCellUnderCursor() const
|
AGridCell* ATacticaPlayerController::GetCellUnderCursor() const
|
||||||
{
|
{
|
||||||
|
// マスはWorldDynamicのみを対象にする。
|
||||||
|
// 地形などWorldStaticと上面が同じ高さでも、そちらに邪魔されない
|
||||||
|
const TArray<TEnumAsByte<EObjectTypeQuery>> ObjectTypes = {
|
||||||
|
UEngineTypes::ConvertToObjectType(ECC_WorldDynamic) };
|
||||||
|
|
||||||
FHitResult Hit;
|
FHitResult Hit;
|
||||||
if (GetHitResultUnderCursor(ECC_Visibility, false, Hit))
|
if (GetHitResultUnderCursorForObjects(ObjectTypes, false, Hit))
|
||||||
{
|
{
|
||||||
return Cast<AGridCell>(Hit.GetActor());
|
return Cast<AGridCell>(Hit.GetActor());
|
||||||
}
|
}
|
||||||
@@ -54,9 +59,19 @@ AGridCell* ATacticaPlayerController::GetCellUnderCursor() const
|
|||||||
|
|
||||||
void ATacticaPlayerController::OnClick()
|
void ATacticaPlayerController::OnClick()
|
||||||
{
|
{
|
||||||
if (const AGridCell* Cell = GetCellUnderCursor())
|
AGridCell* Cell = GetCellUnderCursor();
|
||||||
|
if (!Cell)
|
||||||
{
|
{
|
||||||
|
return; // マス以外のクリックでは選択を変えない
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SelectedCell)
|
||||||
|
{
|
||||||
|
SelectedCell->SetSelected(false);
|
||||||
|
}
|
||||||
|
SelectedCell = Cell;
|
||||||
|
SelectedCell->SetSelected(true);
|
||||||
|
|
||||||
UE_LOG(LogTacticaInput, Log, TEXT("Cell clicked: (%d, %d)"),
|
UE_LOG(LogTacticaInput, Log, TEXT("Cell clicked: (%d, %d)"),
|
||||||
Cell->GridCoord.X, Cell->GridCoord.Y);
|
Cell->GridCoord.X, Cell->GridCoord.Y);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -35,17 +35,27 @@ public:
|
|||||||
UPROPERTY(EditAnywhere, Category = "Grid|Highlight")
|
UPROPERTY(EditAnywhere, Category = "Grid|Highlight")
|
||||||
FLinearColor NormalColor = FLinearColor(0.15f, 0.15f, 0.15f);
|
FLinearColor NormalColor = FLinearColor(0.15f, 0.15f, 0.15f);
|
||||||
|
|
||||||
// ハイライト時の色
|
// ホバー時の色
|
||||||
UPROPERTY(EditAnywhere, Category = "Grid|Highlight")
|
UPROPERTY(EditAnywhere, Category = "Grid|Highlight")
|
||||||
FLinearColor HighlightColor = FLinearColor(1.0f, 0.8f, 0.1f);
|
FLinearColor HoverColor = FLinearColor(1.0f, 0.8f, 0.1f);
|
||||||
|
|
||||||
// ハイライトのON/OFF
|
// 選択時の色
|
||||||
void SetHighlighted(bool bHighlighted);
|
UPROPERTY(EditAnywhere, Category = "Grid|Highlight")
|
||||||
|
FLinearColor SelectedColor = FLinearColor(0.1f, 0.6f, 1.0f);
|
||||||
|
|
||||||
|
void SetHovered(bool bInHovered);
|
||||||
|
void SetSelected(bool bInSelected);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void BeginPlay() override;
|
virtual void BeginPlay() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// 状態から色を決めて反映する(優先順位: 選択 > ホバー > 通常)
|
||||||
|
void RefreshColor();
|
||||||
|
|
||||||
|
bool bHovered = false;
|
||||||
|
bool bSelected = false;
|
||||||
|
|
||||||
// 色変更用のダイナミックマテリアル
|
// 色変更用のダイナミックマテリアル
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
TObjectPtr<UMaterialInstanceDynamic> DynamicMaterial;
|
TObjectPtr<UMaterialInstanceDynamic> DynamicMaterial;
|
||||||
|
|||||||
@@ -38,6 +38,12 @@ public:
|
|||||||
UPROPERTY(VisibleAnywhere, Category = "Grid")
|
UPROPERTY(VisibleAnywhere, Category = "Grid")
|
||||||
TArray<TObjectPtr<AGridCell>> Cells;
|
TArray<TObjectPtr<AGridCell>> Cells;
|
||||||
|
|
||||||
|
// 座標のマスを返す(範囲外はnullptr)
|
||||||
|
AGridCell* GetCellAt(FIntPoint Coord) const;
|
||||||
|
|
||||||
|
// グリッド全体の中心(ワールド)
|
||||||
|
FVector GetGridCenter() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void GenerateGrid();
|
void GenerateGrid();
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
|
||||||
|
// グリッドの座標計算。Actorに依存しない純粋なロジック
|
||||||
|
// (将来のユニット移動や射線計算でも使う)
|
||||||
|
struct TACTICA_API FGridMath
|
||||||
|
{
|
||||||
|
// セル座標 → ワールド座標(セルの中心。Zは Origin.Z)
|
||||||
|
static FVector CellToWorld(FIntPoint Coord, const FVector& Origin, float CellSize);
|
||||||
|
|
||||||
|
// ワールド座標 → セル座標(四捨五入)
|
||||||
|
static FIntPoint WorldToCell(const FVector& World, const FVector& Origin, float CellSize);
|
||||||
|
|
||||||
|
// 範囲内か
|
||||||
|
static bool IsInBounds(FIntPoint Coord, int32 SizeX, int32 SizeY);
|
||||||
|
|
||||||
|
// 配列インデックス(Y * SizeX + X)。範囲外は INDEX_NONE
|
||||||
|
static int32 CoordToIndex(FIntPoint Coord, int32 SizeX, int32 SizeY);
|
||||||
|
|
||||||
|
// グリッド全体の中心(ワールド)
|
||||||
|
static FVector GetGridCenter(const FVector& Origin, int32 SizeX, int32 SizeY, float CellSize);
|
||||||
|
};
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
class UCameraComponent;
|
class UCameraComponent;
|
||||||
|
|
||||||
// 固定の俯瞰カメラ
|
// 固定の俯瞰カメラ。BeginPlayでグリッド全体が収まる位置に自動配置する
|
||||||
UCLASS()
|
UCLASS()
|
||||||
class TACTICA_API ATacticaCameraPawn : public APawn
|
class TACTICA_API ATacticaCameraPawn : public APawn
|
||||||
{
|
{
|
||||||
@@ -17,4 +17,15 @@ public:
|
|||||||
|
|
||||||
UPROPERTY(VisibleAnywhere, Category = "Camera")
|
UPROPERTY(VisibleAnywhere, Category = "Camera")
|
||||||
TObjectPtr<UCameraComponent> Camera;
|
TObjectPtr<UCameraComponent> Camera;
|
||||||
|
|
||||||
|
// 俯角(度。マイナスで見下ろす)
|
||||||
|
UPROPERTY(EditAnywhere, Category = "Camera")
|
||||||
|
float Pitch = -60.0f;
|
||||||
|
|
||||||
|
// グリッドに対する余白の倍率
|
||||||
|
UPROPERTY(EditAnywhere, Category = "Camera")
|
||||||
|
float MarginScale = 1.2f;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void BeginPlay() override;
|
||||||
};
|
};
|
||||||
@@ -24,7 +24,11 @@ private:
|
|||||||
|
|
||||||
void OnClick();
|
void OnClick();
|
||||||
|
|
||||||
// 現在ハイライト中のマス
|
// 現在ホバー中のマス
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
TObjectPtr<AGridCell> HoveredCell;
|
TObjectPtr<AGridCell> HoveredCell;
|
||||||
|
|
||||||
|
// 現在選択中のマス
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<AGridCell> SelectedCell;
|
||||||
};
|
};
|
||||||
Reference in new issue
Block a user