Add cell selection, auto camera placement, and FGridMath

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
yuichipandaandClaude Sonnet 5.5 committed 2026-11-01 18:37:31 +09:00
1 parent 46e5509e3c
commit 3a8082775f
10 files changed
+168 -22

No files matched your search

+17 -4
View File
@@ -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);
} }
} }
+17 -1
View File
@@ -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 =
+27
View File
@@ -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);
}
+26 -5
View File
@@ -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)
{ {
UE_LOG(LogTacticaInput, Log, TEXT("Cell clicked: (%d, %d)"), return; // マス以外のクリックでは選択を変えない
Cell->GridCoord.X, Cell->GridCoord.Y);
} }
if (SelectedCell)
{
SelectedCell->SetSelected(false);
}
SelectedCell = Cell;
SelectedCell->SetSelected(true);
UE_LOG(LogTacticaInput, Log, TEXT("Cell clicked: (%d, %d)"),
Cell->GridCoord.X, Cell->GridCoord.Y);
} }
+14 -4
View File
@@ -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;
+6
View File
@@ -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();
}; };
+23
View File
@@ -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);
};
+12 -1
View File
@@ -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;
}; };