接入Spine

This commit is contained in:
2025-09-23 02:52:33 +08:00
parent 89d751ac34
commit c90b46f430
291 changed files with 32686 additions and 473 deletions

View File

@ -11,7 +11,7 @@ public class BusyRabbit : ModuleRules
PublicDependencyModuleNames.AddRange(new string[] {
"Core", "CoreUObject", "Engine", "InputCore",
"EnhancedInput", "Paper2D", "UMG", "Slate",
"GameplayAbilities", "GameplayTags","GameplayTasks"
"GameplayAbilities", "GameplayTags","GameplayTasks", "SpinePlugin"
});
PrivateDependencyModuleNames.AddRange(new string[] { });

View File

@ -4,7 +4,7 @@
void ABusyPlayerController::BeginPlay(){
Super::BeginPlay();
bShowMouseCursor = true; // <20><>ʾ<EFBFBD><CABE><EFBFBD><EFBFBD>
bShowMouseCursor = true;
FInputModeGameAndUI InputMode;
InputMode.SetHideCursorDuringCapture(false);
InputMode.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock);

View File

@ -0,0 +1,42 @@
#include "Level/Actor/BusyPawnBase.h"
#include "Components/SphereComponent.h"
#include "SpineSkeletonRendererComponent.h"
#include "SpineSkeletonAnimationComponent.h"
#include "Level/Actor/Components/BusyPawnMovement.h"
#include "SpineBoneFollowerComponent.h"
ABusyPawnBase::ABusyPawnBase()
{
RootScene = CreateDefaultSubobject<USceneComponent>(TEXT("RootScene"));
SpineRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SpineRoot"));
SphereComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
SpineRenderComponent = CreateDefaultSubobject<USpineSkeletonRendererComponent>(TEXT("SpineRenderComponent"));
SpineAnimationComponent = CreateDefaultSubobject<USpineSkeletonAnimationComponent>(TEXT("SpineAnimationComponent"));
MovementComponent = CreateDefaultSubobject<UBusyPawnMovement>(TEXT("MovementComponent"));
RootComponent = RootScene;
SpineRoot->SetupAttachment(RootScene);
SphereComponent->SetupAttachment(SpineRoot);
SpineRenderComponent->SetupAttachment(SpineRoot);
SpineRoot->SetRelativeRotation(FRotator(0, 0, -90));
}
void ABusyPawnBase::BeginPlay()
{
Super::BeginPlay();
SpineAnimationComponent->SetSkin(DefaultSkinName);
SpineAnimationComponent->SetAnimation(0, DefaultAnimationName, true);
}
void ABusyPawnBase::UpdateMoveDirection_Implementation(const FVector2D& InDirection)
{
}
float ABusyPawnBase::GetSpeed_Implementation()const
{
return 280;
}

View File

@ -0,0 +1,18 @@
#include "Level/Actor/BusyPlayerRole.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"
ABusyPlayerRole::ABusyPlayerRole()
{
CameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));
SpringArmComponent = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArmComponent"));
SpringArmComponent->SetupAttachment(SpineRoot);
CameraComponent->SetupAttachment(SpringArmComponent);
CameraComponent->SetOrthoWidth(3840);
CameraComponent->SetProjectionMode(ECameraProjectionMode::Orthographic);
SpringArmComponent->SetRelativeRotation(FRotator(0, -90.0, 0.0));
}

View File

@ -0,0 +1,68 @@
#include "Level/Actor/Components/BusyPawnMovement.h"
UBusyPawnMovement::UBusyPawnMovement()
{
this->PrimaryComponentTick.bCanEverTick = true;
}
void UBusyPawnMovement::MoveTo(const FVector2D& Target)
{
MoveTargetLocation = Target;
}
FVector2D UBusyPawnMovement::GetMoveDirection()const
{
if (AActor *Owner = GetOwner())
{
const FVector CurrentLocation = Owner->GetActorLocation();
FVector2D Direction = MoveTargetLocation - FVector2D(CurrentLocation);
if (Direction.Normalize())
{
return Direction;
}
else
{
return FVector2D();
}
}
return FVector2D();
}
void UBusyPawnMovement::TickComponent(float DeltaTime, ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
AActor* Owner = GetOwner();
const IBusyMovable* Movable = Cast<IBusyMovable>(Owner);
if (!Owner || !Movable) return;
FVector NewLocation;
const float MoveDistance = DeltaTime * Movable->Execute_GetSpeed(Owner);
const FVector CurrentLocation = Owner->GetActorLocation();
FVector2D MoveDirection = MoveTargetLocation - FVector2D(CurrentLocation);
if (MoveDistance * MoveDistance > MoveDirection.SizeSquared()) // 已经到达目的地
{
NewLocation = FVector(MoveTargetLocation, CurrentLocation.Z);
}
else
{
if (MoveDirection.Normalize())
{
NewLocation = CurrentLocation + FVector(MoveDirection * MoveDistance, 0);
}
else
{
NewLocation = CurrentLocation;
}
}
if (!NewLocation.Equals(CurrentLocation))
{
Owner->SetActorLocation(NewLocation, true);
}
Movable->Execute_UpdateMoveDirection(Owner, GetMoveDirection());
}

View File

@ -0,0 +1,88 @@
#include "Level/Generator/MitchellBestCandidate.h"
#include "Math/UnrealMathUtility.h"
UMitchellBestCandidate::UMitchellBestCandidate()
{
// 初始化随机数生成器
RandomStream = FRandomStream(FMath::Rand());
}
TArray<FVector2D> UMitchellBestCandidate::GeneratePoints(int32 NumPoints, float Width, float Height, int32 NumCandidates)const
{
TArray<FVector2D> Points;
// 参数验证
if (NumPoints <= 0 || Width <= 0 || Height <= 0 || NumCandidates <= 0)
{
UE_LOG(LogTemp, Warning, TEXT("Invalid parameters for GeneratePoints"));
return Points;
}
// 步骤1: 添加第一个随机点作为起始点
FVector2D FirstPoint(
RandomStream.FRandRange(0.0f, Width),
RandomStream.FRandRange(0.0f, Height)
);
Points.Add(FirstPoint);
// 步骤2: 为剩余的每个点生成候选点并选择最佳的一个
for (int32 i = 1; i < NumPoints; i++)
{
FVector2D BestCandidate;
float BestDistance = -1.0f;
// 为当前点生成NumCandidates个候选点
for (int32 j = 0; j < NumCandidates; j++)
{
// 生成随机候选点
FVector2D Candidate(
RandomStream.FRandRange(0.0f, Width),
RandomStream.FRandRange(0.0f, Height)
);
// 计算候选点到现有点集的最小距离
float MinDist = MinDistanceToSet(Candidate, Points);
// 选择距离现有点集最远的候选点
if (MinDist > BestDistance)
{
BestDistance = MinDist;
BestCandidate = Candidate;
}
}
// 将最佳候选点添加到点集中
Points.Add(BestCandidate);
}
return Points;
}
float UMitchellBestCandidate::Distance(const FVector2D& A, const FVector2D& B)
{
// 使用UE内置的向量距离计算函数
return FVector2D::Distance(A, B);
}
float UMitchellBestCandidate::MinDistanceToSet(const FVector2D& Point, const TArray<FVector2D>& PointSet)
{
// 处理空点集的情况
if (PointSet.Num() == 0)
{
return FLT_MAX;
}
float MinDist = FLT_MAX;
// 遍历点集中的所有点,找到最小距离
for (const FVector2D& ExistingPoint : PointSet)
{
float Dist = Distance(Point, ExistingPoint);
if (Dist < MinDist)
{
MinDist = Dist;
}
}
return MinDist;
}

View File

@ -0,0 +1,158 @@
#include "Level/Generator/TerrainGeneratorBase.h"
UTerrainGeneratorBase::UTerrainGeneratorBase()
{
}
int32 UTerrainGeneratorBase::AddTerrain(const FGameplayTag& TerrainTag)
{
FTerrainNodeInfo NewNode;
NewNode.TerrainTag = TerrainTag;
NewNode.Probability = 1.0f;
NewNode.Weight = 1.0f;
NewNode.ParentHandle = -1;
NewNode.bIsLeafNode = true;
int32 Handle = NextHandle++;
TerrainNodes.Add(Handle, NewNode);
return Handle;
}
void UTerrainGeneratorBase::SetWeight(int32 Handle, float Weight)
{
if (FTerrainNodeInfo* Node = TerrainNodes.Find(Handle))
{
Node->Weight = FMath::Max(0.0f, Weight);
}
}
void UTerrainGeneratorBase::SetProbability(int32 Handle, float Probability)
{
if (FTerrainNodeInfo* Node = TerrainNodes.Find(Handle))
{
Node->Probability = FMath::Clamp(Probability, 0.0f, 1.0f);
}
}
void UTerrainGeneratorBase::SetExclusive(const TArray<int32>& Handles){
if (Handles.Num() <= 1) return;
ExclusiveGroups.Add(TSet<int32>(Handles));
}
void UTerrainGeneratorBase::BindChildTerrain(int32 ParentHandle, const TArray<int32>& ChildHandles)
{
if (FTerrainNodeInfo* ParentNode = TerrainNodes.Find(ParentHandle))
{
// 设置父节点为非叶子节点
ParentNode->bIsLeafNode = false;
// 添加子节点
ParentNode->ChildHandles = ChildHandles;
// 设置子节点的父节点
for (int32 ChildHandle : ChildHandles)
{
if (FTerrainNodeInfo* ChildNode = TerrainNodes.Find(ChildHandle))
{
ChildNode->ParentHandle = ParentHandle;
}
}
}
}
FGameplayTag UTerrainGeneratorBase::GetFinalTerrainTag(const int32 Handle) const{
if (const FTerrainNodeInfo* Node = TerrainNodes.Find(Handle)){
return Node->TerrainTag;
}
return FGameplayTag();
}
TArray<int32> UTerrainGeneratorBase::GetValidLeafNodes() const
{
TSet<int32> ValidNodes;
GetPreCheckVisibleHandles(ValidNodes);
// 检查互斥关系, 待实现
TSet<int32> FinalNodes;
for (int32 Handle : ValidNodes){
if (FinalNodes.Find(Handle)) continue;
FinalNodes.Add(Handle);
}
// 只保留叶子节点
TArray<int32> LeafNodes;
for (int32 Handle : FinalNodes)
{
const FTerrainNodeInfo* Node = TerrainNodes.Find(Handle);
if (Node && Node->bIsLeafNode)
{
LeafNodes.Add(Handle);
}
}
return LeafNodes;
}
void UTerrainGeneratorBase::GetPreCheckVisibleHandles(TSet<int32>& VisibleHandles)const{
bool bShouldAppear;
TArray<bool> AppearanceCheckResult;
const int TerrainNodeCount = TerrainNodes.Num();
AppearanceCheckResult.Init(true, TerrainNodeCount);
for (const auto& Pair : TerrainNodes) {
const int Handle = Pair.Key;
const FTerrainNodeInfo* Node = &Pair.Value;
if (AppearanceCheckResult[Handle]) {
bShouldAppear = FMath::FRand() < Node->Probability;
AppearanceCheckResult[Handle] = bShouldAppear;
}
else {
bShouldAppear = false;
}
if (!bShouldAppear) {
for (const int32 ChildHandle : Pair.Value.ChildHandles) {
AppearanceCheckResult[ChildHandle] = false;
}
}
}
for (int i = 0; i < TerrainNodeCount; ++i) {
if (AppearanceCheckResult[i]) {
VisibleHandles.Add(i);
}
}
}
TArray<FGameplayTag> UTerrainGeneratorBase::GenerateMap(int32 Width, int32 Height)
{
TArray<FGameplayTag> Map;
Map.SetNum(Width * Height);
// 获取有效的叶子节点
TArray<int32> ValidLeafNodes = GetValidLeafNodes();
if (ValidLeafNodes.Num() == 0)
{
UE_LOG(LogTemp, Error, TEXT("No valid terrain nodes to generate map!"));
return Map;
}
if (GenerateWithNodes(Map, Width, Height, ValidLeafNodes))
{
return Map;
}
else
{
return TArray<FGameplayTag>();
}
}
bool UTerrainGeneratorBase::GenerateWithNodes(TArray<FGameplayTag>& Map, int32 Width, int32 Height, const TArray<int32>& ValidLeafNodes)
{
return false;
}

View File

@ -0,0 +1,178 @@
#include "Level/Generator/VoronoiDiagram.h"
#include "Math/UnrealMathUtility.h"
#include "Math/Vector2D.h"
UVoronoiDiagram::UVoronoiDiagram()
{
// 默认构造函数
}
TArray<FVoronoiCell> UVoronoiDiagram::GenerateDiagram(const TArray<FVector2D>& Sites, float Width, float Height)
{
TArray<FVoronoiCell> Cells;
// 参数验证
if (Sites.Num() == 0 || Width <= 0 || Height <= 0)
{
UE_LOG(LogTemp, Warning, TEXT("Invalid parameters for GenerateDiagram"));
return Cells;
}
// 为每个生成点创建泰森多边形单元
for (int32 i = 0; i < Sites.Num(); i++)
{
FVoronoiCell Cell;
Cell.Site = Sites[i];
// 计算边界框的四个角点(作为初始多边形)
TArray<FVector2D> Bounds = {
FVector2D(0, 0),
FVector2D(Width, 0),
FVector2D(Width, Height),
FVector2D(0, Height)
};
// 简化的泰森多边形生成算法
// 注意:这是一个简化实现,完整的泰森多边形算法更复杂
TArray<FVector2D> Polygon = Bounds;
// 对每个其他生成点进行裁剪(使用垂直平分线)
for (int32 j = 0; j < Sites.Num(); j++)
{
if (i == j) continue;
FVector2D Midpoint, Direction;
if (CalculatePerpendicularBisector(Sites[i], Sites[j], Midpoint, Direction))
{
// 这里简化处理,实际泰森多边形算法需要更复杂的几何计算
// 使用中点作为参考点来调整多边形边界
}
}
// 裁剪多边形到边界范围内
Cell.Vertices = ClipPolygonToBounds(Polygon, Width, Height);
Cells.Add(Cell);
}
return Cells;
}
int32 UVoronoiDiagram::FindCellIndex(const FVector2D& Point, const TArray<FVector2D>& Sites)
{
// 处理空点集的情况
if (Sites.Num() == 0)
{
return -1;
}
int32 ClosestIndex = 0;
float MinDistance = FVector2D::Distance(Point, Sites[0]);
// 遍历所有生成点,找到距离最近的点
for (int32 i = 1; i < Sites.Num(); i++)
{
float Distance = FVector2D::Distance(Point, Sites[i]);
if (Distance < MinDistance)
{
MinDistance = Distance;
ClosestIndex = i;
}
}
return ClosestIndex;
}
TArray<int32> UVoronoiDiagram::ConvertToGrid(const TArray<FVector2D>& Sites, int32 Width, int32 Height, float RegionWidth, float RegionHeight)
{
TArray<int32> Grid;
Grid.SetNum(Width * Height);
// 参数验证
if (Sites.Num() == 0 || Width <= 0 || Height <= 0 || RegionWidth <= 0 || RegionHeight <= 0)
{
UE_LOG(LogTemp, Warning, TEXT("Invalid parameters for ConvertToGrid"));
return Grid;
}
// 计算每个网格单元格的大小
float CellWidth = RegionWidth / Width;
float CellHeight = RegionHeight / Height;
// 遍历网格中的每个单元格
for (int32 Y = 0; Y < Height; Y++)
{
for (int32 X = 0; X < Width; X++)
{
// 计算单元格中心点坐标
FVector2D GridPoint(
(X + 0.5f) * CellWidth,
(Y + 0.5f) * CellHeight
);
// 查找该点所在的泰森多边形单元
int32 CellIndex = FindCellIndex(GridPoint, Sites);
Grid[Y * Width + X] = CellIndex;
}
}
return Grid;
}
bool UVoronoiDiagram::CalculatePerpendicularBisector(const FVector2D& A, const FVector2D& B, FVector2D& OutMidpoint, FVector2D& OutDirection)
{
// 计算两点中点
OutMidpoint = (A + B) * 0.5f;
FVector2D AB = B - A;
// 检查两点是否重合
if (AB.SizeSquared() < KINDA_SMALL_NUMBER)
{
return false;
}
// 计算垂直方向向量AB向量的垂直向量
OutDirection = FVector2D(-AB.Y, AB.X);
OutDirection.Normalize();
return true;
}
bool UVoronoiDiagram::CalculateLineIntersection(const FVector2D& P1, const FVector2D& D1, const FVector2D& P2, const FVector2D& D2, FVector2D& OutIntersection)
{
// 检查两条直线是否平行叉积接近0
float Cross = D1.X * D2.Y - D1.Y * D2.X;
if (FMath::Abs(Cross) < KINDA_SMALL_NUMBER)
{
return false;
}
// 计算交点参数
FVector2D P2MinusP1 = P2 - P1;
float T = (P2MinusP1.X * D2.Y - P2MinusP1.Y * D2.X) / Cross;
// 计算交点坐标
OutIntersection = P1 + D1 * T;
return true;
}
bool UVoronoiDiagram::IsPointInBounds(const FVector2D& Point, float Width, float Height)
{
// 检查点是否在矩形边界内
return Point.X >= 0 && Point.X <= Width && Point.Y >= 0 && Point.Y <= Height;
}
TArray<FVector2D> UVoronoiDiagram::ClipPolygonToBounds(const TArray<FVector2D>& Polygon, float Width, float Height)
{
TArray<FVector2D> ClippedPolygon;
// 遍历多边形所有顶点,只保留在边界内的顶点
for (const FVector2D& Vertex : Polygon)
{
if (IsPointInBounds(Vertex, Width, Height))
{
ClippedPolygon.Add(Vertex);
}
}
return ClippedPolygon;
}

View File

@ -0,0 +1,175 @@
#include "Level/Generator/VoronoiTerrainGenerator.h"
#include "Math/UnrealMathUtility.h"
#include "Math/RandomStream.h"
UVoronoiTerrainGenerator::UVoronoiTerrainGenerator()
{
// 初始化默认参数
NextHandle = 0;
VoronoiRegionCount = 50; // 默认50个泰森多边形区域
MitchellCandidateCount = 10; // 默认每个点10个候选点
// 创建算法实例
MitchellGenerator = NewObject<UMitchellBestCandidate>();
VoronoiGenerator = NewObject<UVoronoiDiagram>();
}
bool UVoronoiTerrainGenerator::GenerateWithNodes(
TArray<FGameplayTag>& Map, int32 Width, int32 Height,
const TArray<int32>& ValidLeafNodes
){
// 使用泰森多边形算法生成地图
GenerateWithVoronoi(Map, Width, Height, ValidLeafNodes);
return true;
}
void UVoronoiTerrainGenerator::SetVoronoiRegionCount(int32 Count)
{
// 设置泰森多边形区域数量确保至少为1
VoronoiRegionCount = FMath::Max(1, Count);
}
void UVoronoiTerrainGenerator::SetMitchellCandidateCount(int32 Count)
{
// 设置米切尔候选点数确保至少为1
MitchellCandidateCount = FMath::Max(1, Count);
}
void UVoronoiTerrainGenerator::GenerateWithVoronoi(TArray<FGameplayTag>& Map, int32 Width, int32 Height,
const TArray<int32>& ValidLeafNodes) const
{
// 检查是否有有效的叶子节点
if (ValidLeafNodes.Num() == 0)
{
return;
}
FRandomStream RandomStream(FMath::Rand());
// 步骤1: 计算所有有效叶子节点的总权重
float TotalWeight = 0.0f;
for (int32 Handle : ValidLeafNodes)
{
if (const FTerrainNodeInfo* Node = TerrainNodes.Find(Handle))
{
TotalWeight += Node->Weight;
}
}
// 检查总权重是否有效
if (TotalWeight <= 0.0f)
{
return;
}
// 步骤2: 使用米切尔最佳候选算法生成均匀分布的点集
TArray<FVector2D> Sites = MitchellGenerator->GeneratePoints(
VoronoiRegionCount, Width, Height, MitchellCandidateCount
);
// 步骤3: 将泰森多边形转换为网格表示
TArray<int32> VoronoiGrid = UVoronoiDiagram::ConvertToGrid(Sites, Width, Height, Width, Height);
// 步骤4: 根据权重分配地形类型到各个泰森多边形区域
TArray<int32> RegionToTerrain;
RegionToTerrain.SetNum(Sites.Num());
// 为每个泰森多边形区域分配地形类型
for (int32 RegionIndex = 0; RegionIndex < Sites.Num(); RegionIndex++)
{
float RandValue = RandomStream.FRand();
float CurrentWeight = 0.0f;
// 根据权重比例分配地形
for (int32 i = 0; i < ValidLeafNodes.Num(); i++)
{
if (const FTerrainNodeInfo* Node = TerrainNodes.Find(ValidLeafNodes[i]))
{
CurrentWeight += Node->Weight / TotalWeight;
if (RandValue <= CurrentWeight)
{
RegionToTerrain[RegionIndex] = i;
break;
}
}
}
}
// 步骤5: 填充地图数据
for (int32 Y = 0; Y < Height; Y++)
{
for (int32 X = 0; X < Width; X++)
{
int32 Index = Y * Width + X;
int32 RegionIndex = VoronoiGrid[Index];
// 确保区域索引有效
if (RegionIndex >= 0 && RegionIndex < RegionToTerrain.Num())
{
int32 TerrainIndex = RegionToTerrain[RegionIndex];
// 确保地形索引有效
if (TerrainIndex >= 0 && TerrainIndex < ValidLeafNodes.Num())
{
FGameplayTag TerrainTag = GetFinalTerrainTag(ValidLeafNodes[TerrainIndex]);
Map[Index] = TerrainTag;
}
}
}
}
// 步骤6: 应用平滑处理,改善地形过渡
for (int32 Y = 1; Y < Height - 1; Y++)
{
for (int32 X = 1; X < Width - 1; X++)
{
int32 Index = Y * Width + X;
// 统计周围8个邻居的地形类型
TMap<FGameplayTag, int32> NeighborCount;
for (int32 dy = -1; dy <= 1; dy++)
{
for (int32 dx = -1; dx <= 1; dx++)
{
if (dx == 0 && dy == 0) continue; // 跳过自身
int32 NX = X + dx;
int32 NY = Y + dy;
// 检查邻居是否在边界内
if (NX >= 0 && NX < Width && NY >= 0 && NY < Height)
{
FGameplayTag NeighborTag = Map[NY * Width + NX];
NeighborCount.FindOrAdd(NeighborTag)++;
}
}
}
// 找到出现次数最多的邻居地形
FGameplayTag MostCommonTag;
int32 MaxCount = 0;
for (const auto& Pair : NeighborCount)
{
if (Pair.Value > MaxCount)
{
MaxCount = Pair.Value;
MostCommonTag = Pair.Key;
}
}
// 如果周围大多数是另一种地形,则改变当前地形(平滑处理)
if (MaxCount >= 5 && MostCommonTag != Map[Index]) // 至少5个相同邻居
{
Map[Index] = MostCommonTag;
}
}
}
}

View File

@ -0,0 +1,52 @@
#include "Level/LevelPlayerController.h"
#include "Level/Actor/BusyPlayerRole.h"
#include "EnhancedInput/Public/EnhancedInputSubsystems.h"
ALevelPlayerController::ALevelPlayerController()
{
}
void ALevelPlayerController::BeginPlay()
{
Super::BeginPlay();
bShowMouseCursor = true;
FInputModeGameAndUI InputMode;
InputMode.SetHideCursorDuringCapture(false);
InputMode.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock);
SetInputMode(InputMode);
// 注册输入
const auto Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer());
if (InputMapping && Subsystem)
{
Subsystem->AddMappingContext(InputMapping, 0);
}
}
bool ALevelPlayerController::GetCursorPosition(FVector2D& Position) const
{
float CursorX = 0.f, CursorY = 0.f;
if (GetMousePosition(CursorX, CursorY))
{
FVector WorldLocation, WorldDirection;
if (DeprojectMousePositionToWorld(WorldLocation, WorldDirection))
{
Position.Set(WorldLocation.X, WorldLocation.Y);
return true;
}
}
return false;
}
void ALevelPlayerController::GetCursorHitResult(TArray<AActor*>& Results) const
{
}
void ALevelPlayerController::GetControlledRole() const
{
}
void ALevelPlayerController::SwitchControlledRole(ABusyPlayerRole* Target)
{
this->SetViewTarget(Target);
}

View File

@ -0,0 +1,55 @@
#include "Level/LevelPlayerState.h"
#include "Level/LevelPlayerController.h"
#include "Level/Actor/BusyPlayerRole.h"
DEFINE_LOG_CATEGORY(LogLevelPlayerState);
void ALevelPlayerState::BeginPlay()
{
Super::BeginPlay();
UWorld* World = GetWorld();
const auto PC = Cast<ALevelPlayerController>(GetPlayerController());
if (!World || !PC)
{
UE_LOG(LogLevelPlayerState, Error, TEXT("ALevelPlayerController::BeginPlay() failed!"));
return;
}
FVector2D SpawnLocation = GetSpawnLocation();
for (auto PawnClass : RoleClasses)
{
FTransform SpawnTransform;
FActorSpawnParameters Params;
SpawnTransform.SetLocation(FVector(SpawnLocation, 50));
if (auto *NewRole = Cast<ABusyPlayerRole>(World->SpawnActor(PawnClass, nullptr, Params)))
{
Roles.Add(NewRole);
}
}
if (Roles.IsValidIndex(0))
{
PC->SwitchControlledRole(Roles[0]);
}
}
ABusyPlayerRole* ALevelPlayerState::GetControlledRole() const
{
if (Roles.IsValidIndex(ControlledRoleIndex))
{
return Roles[ControlledRoleIndex];
}
else
{
return nullptr;
}
}
FVector2D ALevelPlayerState::GetSpawnLocation()const
{
return FVector2D::ZeroVector;
}

View File

@ -0,0 +1 @@
#include "Level/Map/ClimateLayerComponent.h"

View File

@ -0,0 +1 @@
#include "Level/Map/CreatureLayerComponent.h"

View File

@ -0,0 +1 @@
#include "Level/Map/DecorationLayerComponent.h"

View File

@ -0,0 +1,22 @@
#include "Level/Map/GameMapActor.h"
AGameMapActor::AGameMapActor()
{
SceneComp = CreateDefaultSubobject<USceneComponent>(TEXT("SceneComp"));
TerrainLayer = CreateDefaultSubobject<UTerrainLayerComponent>(TEXT("TerrainLayer"));
this->RootComponent = SceneComp;
}
void AGameMapActor::BeginPlay()
{
Super::BeginPlay();
}
void AGameMapActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
}

View File

@ -0,0 +1 @@
#include "Level/Map/LightingLayerComponent.h"

View File

@ -0,0 +1 @@
#include "Level/Map/PlacementLayerComponent.h"

View File

@ -0,0 +1,264 @@
#include "Level/Map/TerrainLayerComponent.h"
#include "Level/Generator/VoronoiTerrainGenerator.h"
#include "Paper2D/Classes/PaperTileLayer.h"
#include "Paper2D/Classes/PaperTileMapComponent.h"
#include "Paper2D/Classes/PaperTileSet.h"
#include "PaperTileMap.h"
/* 相邻四格地形信息映射到TileSet的索引
* 以左上、右上、左下、右下地形块存在为1不存在为0, 从左到右构建一个4位二进制整数
* 假设原始TileSet中0号索引左上、右上、右下为空, 对应二进制整数为0x0010
* 那么TileSet的这个索引与相邻数据对应关系就是[0] -> 2
* 也就是DefaultNeighborDataToIdxMappings[2] = 0
*/
const static int DefaultNeighborDataToIdxMappings[16] = {
12, 13, 0, 3, 8, 1, 14, 5, 15, 4, 11, 2, 9, 10, 7, 6
};
const static FGameplayTag SortedTerrainsWithPriority [] = {
FGameplayTag::RequestGameplayTag("Terrain.Desert"),
FGameplayTag::RequestGameplayTag("Terrain.Forest"),
FGameplayTag::RequestGameplayTag("Terrain.Grassland"),
FGameplayTag::RequestGameplayTag("Terrain.Swamp.Land"),
FGameplayTag::RequestGameplayTag("Terrain.Swamp.Water"),
FGameplayTag::RequestGameplayTag("Terrain.Land"),
FGameplayTag::RequestGameplayTag("Terrain.Water.Deep"),
FGameplayTag::RequestGameplayTag("Terrain.Water.Shallow"),
};
static bool GenerateTerrain(const TArray<FGameplayTag>& InTerrains, const TArray<int32> InPriority, int32 InMapSize, TArray<FGameplayTag>& OutTerrains)
{
// 验证输入参数
if (InTerrains.Num() == 0 || InTerrains.Num() != InPriority.Num() || InMapSize <= 0)
{
return false;
}
// 计算总权重
int32 TotalPriority = 0;
for (int32 Priority : InPriority)
{
if (Priority < 0) return false; // 权重不能为负数
TotalPriority += Priority;
}
if (TotalPriority == 0) return false; // 总权重不能为零
// 准备输出数组
OutTerrains.Empty();
OutTerrains.SetNum(InMapSize * InMapSize);
// 计算每种地形的阈值范围
TArray<float> Thresholds;
Thresholds.SetNum(InTerrains.Num());
float Accumulated = 0.0f;
for (int32 i = 0; i < InPriority.Num(); i++)
{
Accumulated += static_cast<float>(InPriority[i]) / TotalPriority;
Thresholds[i] = Accumulated;
}
// 设置柏林噪声参数
float Frequency = 0.05f; // 可以调整这个值来改变地形的细节程度
float OffsetX = FMath::RandRange(0.0f, 1000.0f); // 随机偏移以确保每次生成不同的地形
float OffsetY = FMath::RandRange(0.0f, 1000.0f);
// 生成地形
for (int32 Y = 0; Y < InMapSize; Y++)
{
for (int32 X = 0; X < InMapSize; X++)
{
// 计算柏林噪声值 (范围在-1到1之间)
float NoiseValue = FMath::PerlinNoise2D(FVector2D((X + OffsetX) * Frequency, (Y + OffsetY) * Frequency));
// 将噪声值映射到0到1范围
float NormalizedNoise = (NoiseValue + 1.0f) / 2.0f;
// 根据噪声值选择地形
for (int32 i = 0; i < Thresholds.Num(); i++)
{
if (NormalizedNoise <= Thresholds[i] || i == Thresholds.Num() - 1)
{
OutTerrains[Y * InMapSize + X] = InTerrains[i];
break;
}
}
}
}
return true;
}
static UPaperTileLayer* GetTileMapLayer(
const FGameplayTag& InTerrainType,
const TMap<FGameplayTag, TObjectPtr<UPaperTileMapComponent>>& TileMapMeshes
){
// 将给定的数据绘制到TileMapLayer上
const TObjectPtr<UPaperTileMapComponent> *TileMapMesh = TileMapMeshes.Find(InTerrainType);
if (!TileMapMesh)
{
return nullptr;
}
const TObjectPtr<UPaperTileMap> TileMap = TileMapMesh->Get()->TileMap;
if (!TileMap)
{
return nullptr;
}
if (TileMap->TileLayers.Num() == 0)
{
return nullptr;
}
return TileMap->TileLayers[0];
}
static UPaperTileSet* GetMapTileSet(
const FGameplayTag& InTerrainType,
TMap<FGameplayTag, TObjectPtr<UPaperTileSet>>& TileSetConfigs
){
if (const TObjectPtr<UPaperTileSet> *TileSet = TileSetConfigs.Find(InTerrainType))
{
return TileSet->Get();
}
return nullptr;
}
static inline int32 GetTileSetIndex(const bool LeftUp, const bool RightUp, const bool LeftDown, const bool RightDown)
{
int32 GetTileSetIndex = 0;
GetTileSetIndex += (LeftUp ? 1 : 0) << 3;
GetTileSetIndex += (RightUp ? 1 : 0) << 2;
GetTileSetIndex += (LeftDown ? 1 : 0) << 1;
GetTileSetIndex += (RightDown ? 1 : 0);
return GetTileSetIndex;
}
UTerrainLayerComponent::UTerrainLayerComponent()
{
}
void UTerrainLayerComponent::BeginPlay()
{
Super::BeginPlay();
SetupTerrainMeshes();
TArray<int32> Priority;
TArray<FGameplayTag> TerrainData;
TArray<FGameplayTag> TerrainTypes;
TArray<bool> FilteredTerrainData;
for (auto TileSetConfig : TileSetConfigs)
{
TerrainTypes.Add(TileSetConfig.Key);
Priority.Add(1);
}
GenerateTerrain(TerrainTypes, Priority, MapWidth, TerrainData);
for (auto TerrainType : TerrainTypes)
{
FilteredTerrainData.Init(false, TerrainData.Num());
for (int32 i = 0; i < TerrainData.Num(); i++)
{
FilteredTerrainData[i] = (TerrainData[i] == TerrainType);
}
SetTerrainData(TerrainType, FilteredTerrainData);
}
}
void UTerrainLayerComponent::SetTerrainData(const FGameplayTag& InTerrainType, const TArray<bool>& TerrainData)
{
// 将给定的数据绘制到TileMapLayer上
UPaperTileSet* TileSet = GetMapTileSet(InTerrainType, TileSetConfigs);
UPaperTileLayer *TileMapLayer = GetTileMapLayer(InTerrainType, TerrainMeshes);
if (!TileSet || !TileMapLayer)
{
UE_LOG(LogTemp, Warning, TEXT("UTerrainLayerComponent::SetTerrainData"));
return;
}
if (TerrainData.Num() != MapWidth * MapHeight)
{
UE_LOG(LogTemp, Warning, TEXT("UTerrainLayerComponent::SetTerrainData"));
return;
}
for (int32 i = 0; i < MapHeight - 1; i++)
{
for (int32 j = 0; j < MapWidth - 1; j++)
{
FPaperTileInfo TileInfo;
const int32 CurRow = i * MapWidth;
const int32 NextRow = (i + 1) * MapWidth;
const int32 NeighborIndex = GetTileSetIndex(
TerrainData[CurRow + j],
TerrainData[CurRow + j + 1],
TerrainData[NextRow + j],
TerrainData[NextRow + j + 1]
);
TileInfo.TileSet = TileSet;
TileInfo.PackedTileIndex = DefaultNeighborDataToIdxMappings[NeighborIndex];
TileMapLayer->SetCell(j, i, TileInfo);
}
}
}
void UTerrainLayerComponent::SetupTerrainMeshes()
{
if (TileSetConfigs.Num() == 0)
{
return;
}
TerrainMeshes.Empty();
USceneComponent *RootScene = GetOwner()->GetRootComponent();
int32 Z = 0;
for (const FGameplayTag &TerrainType : SortedTerrainsWithPriority)
{
if (TileSetConfigs.Find(TerrainType) == nullptr)
{
continue;
}
// 创建一个新的TileMap组件新的TileMap新的TileLayer
auto* NewTileMapMesh = NewObject<UPaperTileMapComponent>(this);
NewTileMapMesh->RegisterComponent();
UPaperTileMap* NewTileMap = NewObject<UPaperTileMap>(NewTileMapMesh);
NewTileMap->MapWidth = MapWidth;
NewTileMap->MapHeight = MapHeight;
NewTileMap->TileWidth = 128;
NewTileMap->TileHeight = 128;
UPaperTileLayer* NewLayer = NewObject<UPaperTileLayer>(NewTileMap);
NewLayer->LayerName = FText::FromString("TerrainLayer");
NewLayer->ResizeMap(NewTileMap->MapWidth, NewTileMap->MapHeight);
NewTileMap->TileLayers.Add(NewLayer);
NewTileMapMesh->SetTileMap(NewTileMap);
NewTileMapMesh->SetRelativeLocation(FVector(-(NewTileMap->TileWidth / 2), -(NewTileMap->TileHeight / 2), Z++));
NewTileMapMesh->SetRelativeRotation(FRotator(0, 0, -90));
NewTileMapMesh->AttachToComponent(RootScene, FAttachmentTransformRules::KeepWorldTransform);
TerrainMeshes.Add(TerrainType, NewTileMapMesh);
}
}

View File

@ -1,34 +1,33 @@
#include "Level/PaperTerrainMapActor.h"
#include "Level/TerrainGenerator.h"
#include "Level/TerrainGeneratorBlueprintLibrary.h"
#include "Paper2D/Classes/PaperTileMapComponent.h"
#include "Paper2D/Classes/PaperTileSet.h"
#include "Paper2D/Classes/PaperTileMap.h"
#include "Paper2D/Classes/PaperTileLayer.h"
#include "Engine/Engine.h"
#include "UObject/ConstructorHelpers.h"
/* 相邻四格地形信息映射到TileSet的索引
* 以左上、右上、左下、右下地形块存在为1不存在为0, 从左到右构建一个4位二进制整数
* 假设原始TileSet中0号索引左上、右上、右下为空, 对应二进制整数为0x0010
* 那么TileSet的这个索引与相邻数据对应关系就是[0] -> 2
* 也就是DefaultNeighborDataToIdxMappings[2] = 0
*/
const static int DefaultNeighborDataToIdxMappings[16] = {
12, 13, 0, 3, 8, 1, 14, 5, 15, 4, 11, 2, 9, 10, 7, 6
};
APaperTerrainMapActor::APaperTerrainMapActor()
{
PrimaryActorTick.bCanEverTick = false;
// 创建根组??
// 创建根组
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));
// 创建TileMap组件
TileMapComponent = CreateDefaultSubobject<UPaperTileMapComponent>(TEXT("TileMapComponent"));
TileMapComponent->SetupAttachment(RootComponent);
// 创建地形生成??
TerrainGenerator = CreateDefaultSubobject<UTerrainGenerator>(TEXT("TerrainGenerator"));
// 默认参数设置
bAutoGenerateOnConstruction = true;
bAutoGenerateOnBeginPlay = false;
DefaultTileIndex = 0;
// 设置默认地形映射
SetupDefaultTerrainConfig();
}
void APaperTerrainMapActor::BeginPlay()
@ -44,7 +43,8 @@ void APaperTerrainMapActor::BeginPlay()
void APaperTerrainMapActor::OnConstruction(const FTransform& Transform)
{
Super::OnConstruction(Transform);
// 初始化地形生成器
InitializeGenerator();
// 初始化TileMap
InitializeTileMap();
@ -61,6 +61,11 @@ void APaperTerrainMapActor::GenerateTerrainMap()
UE_LOG(LogTemp, Error, TEXT("TerrainGenerator is null!"));
return;
}
if (!TerrainTileSetConfigs)
{
UE_LOG(LogTemp, Error, TEXT("TerrainTileSetConfigs is null!"));
return;
}
// 生成地形数据
GeneratedTerrainData = TerrainGenerator->GenerateMap(MapWidth, MapHeight);
@ -99,16 +104,6 @@ void APaperTerrainMapActor::ClearMap()
UE_LOG(LogTemp, Log, TEXT("Map cleared!"));
}
UTerrainGenerator* APaperTerrainMapActor::GetTerrainGenerator() const
{
return TerrainGenerator;
}
TArray<FGameplayTag> APaperTerrainMapActor::GetGeneratedTerrainData() const
{
return GeneratedTerrainData;
}
#if WITH_EDITOR
void APaperTerrainMapActor::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
@ -121,33 +116,31 @@ void APaperTerrainMapActor::PostEditChangeProperty(FPropertyChangedEvent& Proper
// 当地图尺寸或Tile大小改变时重新初始化TileMap
if (PropertyName == GET_MEMBER_NAME_CHECKED(APaperTerrainMapActor, MapWidth) ||
PropertyName == GET_MEMBER_NAME_CHECKED(APaperTerrainMapActor, MapHeight) ||
PropertyName == GET_MEMBER_NAME_CHECKED(APaperTerrainMapActor, TileSize))
PropertyName == GET_MEMBER_NAME_CHECKED(APaperTerrainMapActor, TileSize) ||
PropertyName == GET_MEMBER_NAME_CHECKED(APaperTerrainMapActor, TileSetDataAsset))
{
InitializeTileMap();
// 如果已经有生成的数据,重新应??
if (GeneratedTerrainData.Num() > 0)
{
ApplyTerrainToTileMap(GeneratedTerrainData);
}
GenerateTerrainMap();
}
}
#endif
void APaperTerrainMapActor::InitializeTileMap()
{
if (!TileMapComponent)
{
return;
}
if (!TileMapComponent) return;
// 创建或获取TileMap资产
UPaperTileMap* TileMap = TileMapComponent->TileMap;
if (!TileMap)
if (TileSetDataAsset)
{
TileMap = NewObject<UPaperTileMap>(this);
TileMapComponent->SetTileMap(TileMap);
TerrainTileSetConfigs = &TileSetDataAsset.Get()->TerrainTileSetConfigs;
}
else
{
TerrainTileSetConfigs = nullptr;
}
// 创建TileMap
UPaperTileMap *TileMap = NewObject<UPaperTileMap>(this);
// 设置TileMap参数
TileMap->MapWidth = MapWidth;
@ -155,134 +148,128 @@ void APaperTerrainMapActor::InitializeTileMap()
TileMap->TileWidth = TileSize;
TileMap->TileHeight = TileSize;
// 确保有足够的图层
if (TileMap->TileLayers.Num() == 0)
// 创建四层layer
for (int i = 0; i < 4; ++i)
{
UPaperTileLayer* NewLayer = NewObject<UPaperTileLayer>(TileMap);
NewLayer->LayerName = FText::FromString("TerrainLayer");
NewLayer->LayerName = FText::FromString(
FString::Format(TEXT("TerrainLayer{0}"), {FString::FromInt(i)})
);
NewLayer->ResizeMap(MapWidth, MapHeight);
TileMap->TileLayers.Add(NewLayer);
}
// 设置组件大小
FVector NewScale(TileSize * MapWidth / 100.0f, 1.0f, TileSize * MapHeight / 100.0f);
TileMapComponent->SetRelativeScale3D(NewScale);
TileMapComponent->SetTileMap(TileMap);
UE_LOG(LogTemp, Log, TEXT("TileMap initialized: %dx%d, TileSize: %d"), MapWidth, MapHeight, TileSize);
}
void APaperTerrainMapActor::ApplyTerrainToTileMap(const TArray<FGameplayTag>& TerrainData)
void APaperTerrainMapActor::InitializeGenerator()
{
// 创建地形生成器
if (GeneratorClass.Get())
{
TerrainGenerator = NewObject<UTerrainGeneratorBase>(this, GeneratorClass.Get());
UTerrainGeneratorBlueprintLibrary::SetupExampleTerrainConfig(TerrainGenerator);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("APaperTerrainMapActor::APaperTerrainMapActor, GeneratorClass is nullptr"));
}
}
void APaperTerrainMapActor::ApplyTerrainToTileMap(const TArray<FGameplayTag>& TerrainData) const
{
if (!TileMapComponent || !TileMapComponent->TileMap || TerrainData.Num() != MapWidth * MapHeight)
{
UE_LOG(LogTemp, Error, TEXT("Cannot apply terrain data to TileMap!"));
return;
}
if (TileMapComponent->TileMap->TileLayers.Num() == 0)
{
UE_LOG(LogTemp, Error, TEXT("No tile layers in TileMap!"));
return;
}
// 应用地形数据到TileMap - 创建新的TileMap并设置所有单元格
UPaperTileMap* NewTileMap = NewObject<UPaperTileMap>(this);
NewTileMap->MapWidth = MapWidth;
NewTileMap->MapHeight = MapHeight;
NewTileMap->TileWidth = TileSize;
NewTileMap->TileHeight = TileSize;
// 创建地形图层
UPaperTileLayer* TerrainLayer = NewObject<UPaperTileLayer>(NewTileMap);
TerrainLayer->LayerName = FText::FromString("TerrainLayer");
NewTileMap->TileLayers.Add(TerrainLayer);
// 设置图层单元??- 使用SetCell方法
// 首先调整图层尺寸
TerrainLayer->ResizeMap(MapWidth, MapHeight);
FGameplayTag TerrainTags[4];
const int32 LimitedWidth = MapWidth - 1;
const int32 LimitedHeight = MapHeight - 1;
for (int32 Y = 0; Y < MapHeight; Y++)
for (int32 Y = 0; Y < LimitedHeight; Y++)
{
for (int32 X = 0; X < MapWidth; X++)
for (int32 X = 0; X < LimitedWidth; X++)
{
int32 Index = Y * MapWidth + X;
if (TerrainData.IsValidIndex(Index))
{
FGameplayTag TerrainTag = TerrainData[Index];
int32 TileIndex = GetTileIndexForTerrain(TerrainTag);
// 创建Tile信息并使用SetCell方法
FPaperTileInfo TileInfo;
TileInfo.TileSet = TileSet;
TileInfo.PackedTileIndex = TileIndex;
TerrainLayer->SetCell(X, Y, TileInfo);
}
TerrainTags[0] = TerrainData[Y * MapWidth + X];
TerrainTags[1] = TerrainData[Y * MapWidth + X + 1];
TerrainTags[2] = TerrainData[(Y + 1) * MapWidth + X];
TerrainTags[3] = TerrainData[(Y + 1) * MapWidth + X + 1];
DrawTile(X, Y, TerrainTags);
}
}
// 设置新的TileMap
TileMapComponent->SetTileMap(NewTileMap);
UE_LOG(LogTemp, Log, TEXT("Terrain data applied to TileMap!"));
}
int32 APaperTerrainMapActor::GetTileIndexForTerrain(const FGameplayTag& TerrainTag) const
static inline int32 GetNeighborInfo(const FGameplayTag& TargetTag, FGameplayTag TerrainTags[4])
{
if (!TerrainTag.IsValid())
{
return DefaultTileIndex;
}
int32 NeighborInfo = 0;
NeighborInfo += (TargetTag == TerrainTags[0] ? 1 : 0) << 3;
NeighborInfo += (TargetTag == TerrainTags[1] ? 1 : 0) << 2;
NeighborInfo += (TargetTag == TerrainTags[2] ? 1 : 0) << 1;
NeighborInfo += (TargetTag == TerrainTags[3] ? 1 : 0);
return NeighborInfo;
}
// 查找地形映射
for (const FTerrainTileMapping& Mapping : TerrainTileMappings)
static inline void GetSortedLayerDrawingInfo(FGameplayTag **LayerTerrainTags, int32 *LayerNeighborInfo, FGameplayTag TerrainTags[4])
{
// 返回0-3层每层应该画哪种地形格子以及这个格子的哪种形状可能需要排序
LayerTerrainTags[0] = &TerrainTags[0];
LayerTerrainTags[1] = &TerrainTags[1];
LayerTerrainTags[2] = &TerrainTags[2];
LayerTerrainTags[3] = &TerrainTags[3];
LayerNeighborInfo[0] = GetNeighborInfo(TerrainTags[0], TerrainTags);
LayerNeighborInfo[1] = GetNeighborInfo(TerrainTags[1], TerrainTags);
LayerNeighborInfo[2] = GetNeighborInfo(TerrainTags[2], TerrainTags);
LayerNeighborInfo[3] = GetNeighborInfo(TerrainTags[3], TerrainTags);
}
void APaperTerrainMapActor::DrawTile(const int32 X, const int32 Y, FGameplayTag TerrainTags[4])const
{
int32 LayerNeighborInfo[4];
FGameplayTag *LayerTerrainTags[4];
UPaperTileMap* TileMap = TileMapComponent->TileMap;
GetSortedLayerDrawingInfo(LayerTerrainTags, LayerNeighborInfo, TerrainTags);
for (int32 Index = 0; Index < 4; ++Index)
{
if (Mapping.TerrainTag == TerrainTag)
int32 TileIndex = 0;
FPaperTileInfo TileInfo;
const FTerrainTileSetConfig *TileSetConfig = TerrainTileSetConfigs->Find(*LayerTerrainTags[Index]);
if (!TileSetConfig)
{
return Mapping.TileIndex;
UE_LOG(LogTemp, Warning, TEXT("APaperTerrainMapActor::DrawTile TileSetConfig not found: %s"), *LayerTerrainTags[Index]->GetTagName().ToString());
continue;
}
}
// 如果没有找到映射返回默认Tile
return DefaultTileIndex;
}
void APaperTerrainMapActor::SetupDefaultTerrainConfig()
{
// 设置默认地形映射
TerrainTileMappings.Empty();
// 森林 -> Tile 1
FTerrainTileMapping ForestMapping;
ForestMapping.TerrainTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Forest"));
ForestMapping.TileIndex = 1;
TerrainTileMappings.Add(ForestMapping);
// 草地 -> Tile 2
FTerrainTileMapping GrasslandMapping;
GrasslandMapping.TerrainTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Grassland"));
GrasslandMapping.TileIndex = 2;
TerrainTileMappings.Add(GrasslandMapping);
// 水体 -> Tile 3
FTerrainTileMapping WaterMapping;
WaterMapping.TerrainTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Water"));
WaterMapping.TileIndex = 3;
TerrainTileMappings.Add(WaterMapping);
// 沼泽 -> Tile 4
FTerrainTileMapping SwampMapping;
SwampMapping.TerrainTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Swamp"));
SwampMapping.TileIndex = 4;
TerrainTileMappings.Add(SwampMapping);
// 土地 -> Tile 5
FTerrainTileMapping LandMapping;
LandMapping.TerrainTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Land"));
LandMapping.TileIndex = 5;
TerrainTileMappings.Add(LandMapping);
// 设置地形生成器的默认配置
if (TerrainGenerator)
{
UTerrainGeneratorBlueprintLibrary::SetupExampleTerrainConfig(TerrainGenerator);
const int32 NeighborInfo = LayerNeighborInfo[Index];
if (TileSetConfig->bNeedOverrideMappings)
{
if (NeighborInfo < TileSetConfig->NeighborDataToIdxMappings.Num())
{
TileIndex = TileSetConfig->NeighborDataToIdxMappings[NeighborInfo];
}
else
{
UE_LOG(LogTemp, Warning, TEXT("APaperTerrainMapActor::DrawTile Neighbor Data not find: %s, %d"),
*LayerTerrainTags[Index]->GetTagName().ToString(), NeighborInfo
)
}
}
else
{
TileIndex = DefaultNeighborDataToIdxMappings[NeighborInfo];
}
UPaperTileLayer* TerrainLayer = TileMap->TileLayers[Index];
TileInfo.TileSet = TileSetConfig->TileSet;
TileInfo.PackedTileIndex = TileIndex;
TerrainLayer->SetCell(X, Y, TileInfo);
}
}

View File

@ -7,196 +7,20 @@ UTerrainGenerator::UTerrainGenerator()
NextHandle = 0;
}
int32 UTerrainGenerator::AddTerrain(const FGameplayTag& TerrainTag)
bool UTerrainGenerator::GenerateWithNodes(TArray<FGameplayTag>& Map, int32 Width, int32 Height, const TArray<int32>& ValidLeafNodes)
{
FTerrainNodeInfo NewNode;
NewNode.TerrainTag = TerrainTag;
NewNode.Probability = 1.0f;
NewNode.Weight = 1.0f;
NewNode.ParentHandle = -1;
NewNode.bIsLeafNode = true;
int32 Handle = NextHandle++;
TerrainNodes.Add(Handle, NewNode);
return Handle;
}
void UTerrainGenerator::SetWeight(int32 Handle, float Weight)
{
if (FTerrainNodeInfo* Node = TerrainNodes.Find(Handle))
{
Node->Weight = FMath::Max(0.0f, Weight);
}
}
void UTerrainGenerator::SetProbability(int32 Handle, float Probability)
{
if (FTerrainNodeInfo* Node = TerrainNodes.Find(Handle))
{
Node->Probability = FMath::Clamp(Probability, 0.0f, 1.0f);
}
}
void UTerrainGenerator::SetExclusive(const TArray<int32>& Handles){
if (Handles.Num() <= 1) return;
ExclusiveGroups.Add(TSet<int32>(Handles));
}
void UTerrainGenerator::BindChildTerrain(int32 ParentHandle, const TArray<int32>& ChildHandles)
{
if (FTerrainNodeInfo* ParentNode = TerrainNodes.Find(ParentHandle))
{
// 设置父节点为非叶子节点
ParentNode->bIsLeafNode = false;
// 添加子节点
ParentNode->ChildHandles = ChildHandles;
// 设置子节点的父节点
for (int32 ChildHandle : ChildHandles)
{
if (FTerrainNodeInfo* ChildNode = TerrainNodes.Find(ChildHandle))
{
ChildNode->ParentHandle = ParentHandle;
}
}
}
}
TArray<FGameplayTag> UTerrainGenerator::GenerateMap(int32 Width, int32 Height)
{
TArray<FGameplayTag> Map;
Map.SetNum(Width * Height);
// 获取有效的叶子节点
TArray<int32> ValidLeafNodes = CalcValidLeafNodes();
if (ValidLeafNodes.Num() == 0)
{
UE_LOG(LogTemp, Error, TEXT("No valid terrain nodes to generate map!"));
return Map;
}
// 使用区域生长算法生成基础地图
RegionGrowing(Map, Width, Height, ValidLeafNodes);
// 应用柏林噪声进行平滑处理
ApplyPerlinNoiseSmoothing(Map, Width, Height);
return Map;
}
TMap<int32, FTerrainNodeInfo> UTerrainGenerator::GetAllTerrainNodes() const
{
return TerrainNodes;
}
void UTerrainGenerator::GetPreCheckVisibleHandles(TSet<int32>& VisibleHandles)const{
bool bShouldAppear;
const FTerrainNodeInfo* Node;
int32 Handle, TerrainNodeCount;
TArray<bool> AppearanceCheckResult;
TerrainNodeCount = TerrainNodes.Num();
AppearanceCheckResult.Init(true, TerrainNodeCount);
for (const auto& Pair : TerrainNodes) {
Handle = Pair.Key;
Node = &Pair.Value;
if (AppearanceCheckResult[Handle]) {
bShouldAppear = FMath::FRand() < Node->Probability;
AppearanceCheckResult[Handle] = bShouldAppear;
}
else {
bShouldAppear = false;
}
if (!bShouldAppear) {
for (auto ChildHandle : Pair.Value.ChildHandles) {
AppearanceCheckResult[ChildHandle] = false;
}
}
}
for (int i = 0; i < TerrainNodeCount; ++i) {
if (AppearanceCheckResult[i]) {
VisibleHandles.Add(i);
}
}
}
bool UTerrainGenerator::ShouldNodeAppear(int32 Handle) const
{
const FTerrainNodeInfo* Node = TerrainNodes.Find(Handle);
if (!Node)
{
return false;
}
// 检查概率
if (FMath::FRand() > Node->Probability)
{
return false;
}
// 递归检查父节点
if (Node->ParentHandle != -1)
{
return ShouldNodeAppear(Node->ParentHandle);
}
return true;
}
bool UTerrainGenerator::CheckExclusive(int32 Handle, const TSet<int32>& AppearingNodes) const {
//for (const TArray<int32>& Group : ExclusiveGroups)
//{
// if (Group.Contains(Handle))
// {
// // 检查互斥组中是否有其他节点已经出现
// for (int32 OtherHandle : Group)
// {
// if (OtherHandle != Handle && AppearingNodes.Contains(OtherHandle))
// {
// return false;
// }
// }
// }
//}
return true;
}
TArray<int32> UTerrainGenerator::CalcValidLeafNodes()const{
TSet<int32> ValidNodes;
GetPreCheckVisibleHandles(ValidNodes);
// 检查互斥关系
TSet<int32> FinalNodes;
for (int32 Handle : ValidNodes){
if (FinalNodes.Find(Handle)) continue;
if (CheckExclusive(Handle, ValidNodes))
{
FinalNodes.Add(Handle);
}
}
// 只保留叶子节点
TArray<int32> LeafNodes;
for (int32 Handle : FinalNodes)
{
const FTerrainNodeInfo* Node = TerrainNodes.Find(Handle);
if (Node && Node->bIsLeafNode)
{
LeafNodes.Add(Handle);
}
}
return LeafNodes;
}
void UTerrainGenerator::RegionGrowing(TArray<FGameplayTag>& Map, int32 Width, int32 Height,
const TArray<int32>& ValidLeafNodes) const
const TArray<int32>& ValidLeafNodes) const
{
if (ValidLeafNodes.Num() == 0)
{
@ -393,10 +217,4 @@ void UTerrainGenerator::ApplyPerlinNoiseSmoothing(TArray<FGameplayTag>& Map, int
}
}
FGameplayTag UTerrainGenerator::GetFinalTerrainTag(int32 Handle) const{
const FTerrainNodeInfo* Node = TerrainNodes.Find(Handle);
if (Node){
return Node->TerrainTag;
}
return FGameplayTag();
}

View File

@ -8,7 +8,7 @@ UTerrainGenerator* UTerrainGeneratorBlueprintLibrary::CreateTerrainGenerator()
return NewObject<UTerrainGenerator>();
}
void UTerrainGeneratorBlueprintLibrary::SetupExampleTerrainConfig(UTerrainGenerator* Generator)
void UTerrainGeneratorBlueprintLibrary::SetupExampleTerrainConfig(UTerrainGeneratorBase* Generator)
{
if (!Generator)
{

View File

@ -15,8 +15,11 @@ void UBusyRoleMovement::BeginPlay(){
}
void UBusyRoleMovement::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction){
void UBusyRoleMovement::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
const AActor* Owner = GetOwner();
if (!Owner) return;
this->ReceiveComponentTick(DeltaTime);
}

View File

@ -0,0 +1,61 @@
#pragma once
#include "LuaPawn.h"
#include "Level/Actor/Components/BusyPawnMovement.h"
#include "BusyPawnBase.generated.h"
class USphereComponent;
class USpineBoneFollowerComponent;
class USpineSkeletonRendererComponent;
class USpineSkeletonAnimationComponent;
UCLASS()
class ABusyPawnBase : public ALuaPawn, public IBusyMovable
{
GENERATED_BODY()
public:
ABusyPawnBase();
virtual void BeginPlay()override;
virtual void UpdateMoveDirection_Implementation(const FVector2D& InDirection) override;
virtual float GetSpeed_Implementation()const override;
protected:
UPROPERTY(EditDefaultsOnly)
TObjectPtr<USceneComponent> RootScene; //场景根组件
/*-----------------------------碰撞相关组件-----------------------------*/
UPROPERTY(EditDefaultsOnly)
TObjectPtr<USphereComponent> SphereComponent;
/*-------------------------------------------------------------------*/
/*----------------------------spine相关组件----------------------------*/
UPROPERTY(EditDefaultsOnly)
TObjectPtr<USceneComponent> SpineRoot;
UPROPERTY(EditDefaultsOnly)
TObjectPtr<USpineSkeletonRendererComponent> SpineRenderComponent;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TObjectPtr<USpineSkeletonAnimationComponent> SpineAnimationComponent;
/*-------------------------------------------------------------------*/
/*-------------------------------移动组件------------------------------*/
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TObjectPtr<UBusyPawnMovement> MovementComponent;
/*-------------------------------------------------------------------*/
protected:
UPROPERTY(EditAnywhere)
FString DefaultSkinName;
UPROPERTY(EditAnywhere)
FString DefaultAnimationName;
};

View File

@ -0,0 +1,32 @@
#pragma once
#include "BusyPawnBase.h"
#include "BusyPlayerRole.generated.h"
UCLASS()
class ABusyPlayerRole : public ABusyPawnBase
{
GENERATED_BODY()
public:
ABusyPlayerRole();
protected:
/*--------------------相机相关--------------------------*/
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
TObjectPtr<class USpringArmComponent> SpringArmComponent;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
TObjectPtr<class UCameraComponent> CameraComponent;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
TObjectPtr<class UBusyRoleMovement> Movement; // 移动组件
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
TObjectPtr<class UPW_AbilitySystemComponent> RoleAbility; // 技能组件
UPROPERTY(BlueprintReadWrite)
TObjectPtr<class URoleAnimation> RoleAnimation;
};

View File

@ -0,0 +1,47 @@
#pragma once
#include "LuaActorComponent.h"
#include "BusyPawnMovement.generated.h"
UINTERFACE(MinimalAPI, Blueprintable)
class UBusyMovable : public UInterface
{
GENERATED_BODY()
};
class IBusyMovable
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintNativeEvent)
float GetSpeed() const;
UFUNCTION(BlueprintNativeEvent, Category = "Movement")
void UpdateMoveDirection(const FVector2D &InDirection);
};
UCLASS()
class UBusyPawnMovement : public ULuaActorComponent
{
GENERATED_BODY()
public:
UBusyPawnMovement();
public:
UFUNCTION(BlueprintCallable)
void MoveTo(const FVector2D& Target);
UFUNCTION(BlueprintCallable)
FVector2D GetMoveDirection()const;
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)override;
public:
UPROPERTY(EditAnywhere, BlueprintReadOnly)
float MoveSpeed = 400;
protected:
UPROPERTY(EditAnywhere, BlueprintReadOnly)
FVector2D MoveTargetLocation;
};

View File

@ -0,0 +1,61 @@
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "MitchellBestCandidate.generated.h"
/**
* @brief 米切尔最佳候选算法实现类
*
* 该算法用于生成高质量、均匀分布的随机点集,常用于程序化内容生成。
* 通过为每个点生成多个候选点并选择距离现有点集最远的点,确保点集分布均匀。
*/
UCLASS(BlueprintType, Blueprintable)
class BUSYRABBIT_API UMitchellBestCandidate : public UObject
{
GENERATED_BODY()
public:
UMitchellBestCandidate();
/**
* @brief 生成米切尔最佳候选点集
*
* 使用米切尔最佳候选算法生成均匀分布的点集。算法为每个点生成多个候选点,
* 然后选择距离现有点集最远的候选点,确保点集分布均匀。
*
* @param NumPoints 要生成的点数
* @param Width 生成区域的宽度
* @param Height 生成区域的高度
* @param NumCandidates 每个点生成的候选点数默认为10值越大点集越均匀但性能开销越大
* @return 生成的均匀分布点集
*/
UFUNCTION(BlueprintCallable, Category = "Mitchell Best Candidate")
TArray<FVector2D> GeneratePoints(int32 NumPoints, float Width, float Height, int32 NumCandidates = 10)const;
/**
* @brief 计算两点之间的欧几里得距离
*
* @param A 第一个点
* @param B 第二个点
* @return 两点之间的欧几里得距离
*/
UFUNCTION(BlueprintCallable, Category = "Mitchell Best Candidate")
static float Distance(const FVector2D& A, const FVector2D& B);
/**
* @brief 计算点到点集的最小距离
*
* 计算给定点到点集中所有点的最小距离,用于评估候选点的质量。
*
* @param Point 要评估的点
* @param PointSet 点集
* @return 点到点集的最小距离
*/
UFUNCTION(BlueprintCallable, Category = "Mitchell Best Candidate")
static float MinDistanceToSet(const FVector2D& Point, const TArray<FVector2D>& PointSet);
private:
/** 随机数生成器,用于生成随机点 */
FRandomStream RandomStream;
};

View File

@ -0,0 +1,89 @@
#pragma once
#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "TerrainGeneratorBase.generated.h"
USTRUCT(BlueprintType)
struct FTerrainNodeInfo
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FGameplayTag TerrainTag;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float Probability;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float Weight;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 ParentHandle;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TArray<int32> ChildHandles;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool bIsLeafNode;
FTerrainNodeInfo()
: Probability(1.0f)
, Weight(1.0f)
, ParentHandle(-1)
, bIsLeafNode(false)
{
}
};
UCLASS(BlueprintType, Blueprintable)
class BUSYRABBIT_API UTerrainGeneratorBase : public UObject
{
GENERATED_BODY()
public:
UTerrainGeneratorBase();
UFUNCTION(BlueprintCallable, Category = "Terrain Generator")
int32 AddTerrain(const FGameplayTag& TerrainTag);
UFUNCTION(BlueprintCallable, Category = "Terrain Generator")
void SetWeight(int32 Handle, float Weight);
UFUNCTION(BlueprintCallable, Category = "Terrain Generator")
void SetProbability(int32 Handle, float Probability);
UFUNCTION(BlueprintCallable, Category = "Terrain Generator")
void SetExclusive(const TArray<int32>& Handles);
UFUNCTION(BlueprintCallable, Category = "Terrain Generator")
void BindChildTerrain(int32 ParentHandle, const TArray<int32>& ChildHandles);
UFUNCTION(BlueprintCallable, Category = "Terrain Generator")
TArray<FGameplayTag> GenerateMap(int32 Width, int32 Height);
public: // get
FGameplayTag GetFinalTerrainTag(int32 Handle) const;
TArray<int32> GetValidLeafNodes()const;
void GetPreCheckVisibleHandles(TSet<int32>& VisibleHandles)const;
public:
UFUNCTION(BlueprintCallable, Category = "Terrain Generator")
TMap<int32, FTerrainNodeInfo> GetAllTerrainNodes() const {return TerrainNodes;};
public: // 需要子类重写的函数
virtual bool GenerateWithNodes(TArray<FGameplayTag>& Map, int32 Width, int32 Height, const TArray<int32>& ValidLeafNodes);
protected:
int32 NextHandle;
TArray<TSet<int32>> ExclusiveGroups;
TMap<int32, FTerrainNodeInfo> TerrainNodes;
};

View File

@ -0,0 +1,139 @@
#pragma once
#include "CoreMinimal.h"
#include "VoronoiDiagram.generated.h"
/**
* @brief 泰森多边形单元结构
*
* 表示泰森多边形图中的一个单元,包含生成点、顶点和相邻单元信息。
*/
USTRUCT(BlueprintType)
struct FVoronoiCell
{
GENERATED_BODY()
/** 泰森多边形单元的生成点(站点) */
UPROPERTY(BlueprintReadOnly)
FVector2D Site;
/** 泰森多边形单元的顶点列表,按顺时针或逆时针顺序排列 */
UPROPERTY(BlueprintReadOnly)
TArray<FVector2D> Vertices;
/** 相邻泰森多边形单元的索引列表 */
UPROPERTY(BlueprintReadOnly)
TArray<int32> NeighborCells;
/** 默认构造函数 */
FVoronoiCell()
: Site(FVector2D::ZeroVector)
{
}
};
/**
* @brief 泰森多边形图生成器
*
* 用于生成和管理泰森多边形图Voronoi Diagram将平面划分为多个区域
* 每个区域包含距离其生成点最近的所有点。
*/
UCLASS(BlueprintType, Blueprintable)
class BUSYRABBIT_API UVoronoiDiagram : public UObject
{
GENERATED_BODY()
public:
UVoronoiDiagram();
/**
* @brief 生成完整的泰森多边形图
*
* 根据给定的生成点集创建泰森多边形图,将平面划分为多个区域。
* 每个区域包含距离对应生成点最近的所有点。
*
* @param Sites 生成点集(站点)
* @param Width 生成区域的宽度
* @param Height 生成区域的高度
* @return 生成的泰森多边形单元数组
*/
UFUNCTION(BlueprintCallable, Category = "Voronoi Diagram")
TArray<FVoronoiCell> GenerateDiagram(const TArray<FVector2D>& Sites, float Width, float Height);
/**
* @brief 查找点所在的泰森多边形单元索引
*
* 通过计算点到所有生成点的距离,找到最近的生成点对应的单元索引。
*
* @param Point 要查询的点坐标
* @param Sites 生成点集
* @return 单元索引从0开始如果未找到返回-1
*/
UFUNCTION(BlueprintCallable, Category = "Voronoi Diagram")
static int32 FindCellIndex(const FVector2D& Point, const TArray<FVector2D>& Sites);
/**
* @brief 将泰森多边形图转换为二维网格
*
* 将连续的泰森多边形图离散化为网格,每个网格单元格包含对应的生成点索引。
*
* @param Sites 生成点集
* @param Width 网格的宽度(单元格数量)
* @param Height 网格的高度(单元格数量)
* @param RegionWidth 实际区域的宽度
* @param RegionHeight 实际区域的高度
* @return 网格数据数组,每个元素是对应的生成点索引
*/
UFUNCTION(BlueprintCallable, Category = "Voronoi Diagram")
static TArray<int32> ConvertToGrid(const TArray<FVector2D>& Sites, int32 Width, int32 Height, float RegionWidth, float RegionHeight);
private:
/**
* @brief 计算两个点的垂直平分线
*
* 计算两点连线的垂直平分线,用于泰森多边形边界的生成。
*
* @param A 第一个点
* @param B 第二个点
* @param OutMidpoint 输出的中点坐标
* @param OutDirection 输出的垂直方向向量(单位向量)
* @return 是否成功计算(两点不能重合)
*/
static bool CalculatePerpendicularBisector(const FVector2D& A, const FVector2D& B, FVector2D& OutMidpoint, FVector2D& OutDirection);
/**
* @brief 计算两条直线的交点
*
* 计算两条直线的交点,用于确定泰森多边形顶点。
*
* @param P1 第一条直线的起点
* @param D1 第一条直线的方向向量
* @param P2 第二条直线的起点
* @param D2 第二条直线的方向向量
* @param OutIntersection 输出的交点坐标
* @return 是否成功计算(直线不能平行)
*/
static bool CalculateLineIntersection(const FVector2D& P1, const FVector2D& D1, const FVector2D& P2, const FVector2D& D2, FVector2D& OutIntersection);
/**
* @brief 检查点是否在边界框内
*
* @param Point 要检查的点
* @param Width 边界框宽度
* @param Height 边界框高度
* @return 点是否在边界框内
*/
static bool IsPointInBounds(const FVector2D& Point, float Width, float Height);
/**
* @brief 裁剪多边形到边界
*
* 移除多边形中超出边界的顶点,确保多边形在指定边界内。
*
* @param Polygon 要裁剪的多边形顶点列表
* @param Width 边界宽度
* @param Height 边界高度
* @return 裁剪后的多边形顶点列表
*/
static TArray<FVector2D> ClipPolygonToBounds(const TArray<FVector2D>& Polygon, float Width, float Height);
};

View File

@ -0,0 +1,78 @@
#pragma once
#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "Level/Generator/MitchellBestCandidate.h"
#include "Level/Generator/VoronoiDiagram.h"
#include "Level/Generator/TerrainGeneratorBase.h"
#include "VoronoiTerrainGenerator.generated.h"
/**
* @brief 泰森多边形地形生成器
*
* 结合米切尔最佳候选算法和泰森多边形算法的高级地形生成器。
* 使用米切尔算法生成均匀分布的点集,然后使用泰森多边形划分区域,
* 最后根据权重分配地形类型到各个区域。
*/
UCLASS(BlueprintType, Blueprintable)
class BUSYRABBIT_API UVoronoiTerrainGenerator : public UTerrainGeneratorBase
{
GENERATED_BODY()
public:
UVoronoiTerrainGenerator();
/**
* @brief 设置泰森多边形区域数量
*
* 控制地图被划分的泰森多边形区域数量,影响地形的粒度。
*
* @param Count 区域数量至少为1
*/
UFUNCTION(BlueprintCallable, Category = "Voronoi Terrain Generator")
void SetVoronoiRegionCount(int32 Count);
/**
* @brief 设置米切尔候选点数
*
* 控制米切尔算法中每个点生成的候选点数,影响点集的质量。
*
* @param Count 候选点数至少为1
*/
UFUNCTION(BlueprintCallable, Category = "Voronoi Terrain Generator")
void SetMitchellCandidateCount(int32 Count);
virtual bool GenerateWithNodes(TArray<FGameplayTag>& Map, int32 Width, int32 Height, const TArray<int32>& ValidLeafNodes)override;
private:
/** 泰森多边形区域数量参数 */
int32 VoronoiRegionCount;
/** 米切尔候选点数参数 */
int32 MitchellCandidateCount;
/** 米切尔最佳候选算法实例 */
UPROPERTY()
UMitchellBestCandidate* MitchellGenerator;
/** 泰森多边形算法实例 */
UPROPERTY()
UVoronoiDiagram* VoronoiGenerator;
/**
* @brief 使用泰森多边形算法生成地图
*
* 核心生成算法,结合米切尔最佳候选和泰森多边形技术。
*
* @param Map 输出的地图数组
* @param Width 地图宽度
* @param Height 地图高度
* @param ValidLeafNodes 有效的叶子节点列表
*/
void GenerateWithVoronoi(TArray<FGameplayTag>& Map, int32 Width, int32 Height,
const TArray<int32>& ValidLeafNodes) const;
};

View File

@ -0,0 +1,41 @@
#pragma once
#include "LuaPlayerController.h"
#include "LevelPlayerController.generated.h"
class ABusyPlayerRole;
UCLASS()
class ALevelPlayerController : public ALuaPlayerController
{
GENERATED_BODY()
public:
ALevelPlayerController();
public:
virtual void BeginPlay() override;
public:
UFUNCTION(BlueprintCallable, Category = "Controller")
bool GetCursorPosition(FVector2D& Position) const;
UFUNCTION(BlueprintCallable, Category = "Controller")
void GetCursorHitResult(TArray<AActor*>& Results) const;
UFUNCTION(BlueprintCallable, Category = "Controller")
void GetControlledRole() const;
UFUNCTION(BlueprintCallable, Category = "Controller")
void SwitchControlledRole(ABusyPlayerRole* Target);
public: // 输入相关
UPROPERTY(EditDefaultsOnly, Category = "Input")
TObjectPtr<class UInputMappingContext> InputMapping;
UPROPERTY(EditDefaultsOnly, Category = "Input")
TObjectPtr<class UInputAction> TouchAction;
};

View File

@ -0,0 +1,40 @@
#pragma once
#include "LuaPlayerState.h"
#include "LevelPlayerState.generated.h"
class ABusyPlayerRole;
DECLARE_LOG_CATEGORY_EXTERN(LogLevelPlayerState, Log, All);
UCLASS()
class ALevelPlayerState : public ALuaPlayerState
{
GENERATED_BODY()
public:
virtual void BeginPlay() override;
public:
UFUNCTION(BlueprintCallable)
ABusyPlayerRole* GetControlledRole() const;
protected:
virtual FVector2D GetSpawnLocation()const;
public:
UPROPERTY(EditAnywhere)
TArray<TSubclassOf<ABusyPlayerRole>> RoleClasses;
protected:
UPROPERTY()
int ControlledRoleIndex = -1;
UPROPERTY()
TArray<ABusyPlayerRole*> Roles;
};

View File

@ -0,0 +1,7 @@
#pragma once
class ClimateLayerComponent
{
public:
};

View File

@ -0,0 +1,7 @@
#pragma once
class CreatureLayerComponent
{
public:
};

View File

@ -0,0 +1,7 @@
#pragma once
class DecorationLayerComponent
{
public:
};

View File

@ -0,0 +1,25 @@
#pragma once
#include "Level/Map/TerrainLayerComponent.h"
#include "GameMapActor.generated.h"
UCLASS(Blueprintable, Blueprintable)
class AGameMapActor : public AActor
{
GENERATED_BODY()
public:
AGameMapActor();
public:
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
protected:
UPROPERTY(EditDefaultsOnly)
TObjectPtr<UTerrainLayerComponent> TerrainLayer;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
TObjectPtr<class USceneComponent> SceneComp;
};

View File

@ -0,0 +1,7 @@
#pragma once
class LightingLayerComponent
{
public:
};

View File

@ -0,0 +1,7 @@
#pragma once
class PlacementLayerComponent
{
public:
};

View File

@ -0,0 +1,66 @@
#pragma once
#include "GameplayTagContainer.h"
#include "Components/ActorComponent.h"
#include "Paper2D/Classes/PaperTileMapComponent.h"
#include "TerrainLayerComponent.generated.h"
USTRUCT(BlueprintType)
struct FTerrainTileSetConfig{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly, DisplayName="该地形对应的TileSet")
TObjectPtr<class UPaperTileSet> TileSet;
UPROPERTY(EditAnywhere, BlueprintReadOnly, DisplayName="是否需要重载周围数据信息到TileSet的索引映射")
bool bNeedOverrideMappings = false;
UPROPERTY(EditAnywhere, BlueprintReadOnly, DisplayName="周围数据信息到TileSet索引的映射", meta=(EditCondition="bNeedOverrideMappings", EditConditionHides))
TArray<int32> NeighborDataToIdxMappings;
};
UCLASS()
class UTerrainDoubleGridDataAsset: public UDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, DisplayName="瓦片设置")
TMap<FGameplayTag, FTerrainTileSetConfig> TerrainTileSetConfigs;
};
UCLASS(BlueprintType, Blueprintable)
class UTerrainLayerComponent:public UActorComponent
{
GENERATED_BODY(TerrainLayerComponent)
public:
UTerrainLayerComponent();
public:
virtual void BeginPlay() override;
public:
void SetTerrainData(const FGameplayTag& InTerrainType, const TArray<bool> &TerrainData);
protected:
void SetupTerrainMeshes();
protected:
UPROPERTY(EditAnywhere, BlueprintReadOnly)
int32 MapWidth = 32;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
int32 MapHeight = 32;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TMap<FGameplayTag, TObjectPtr<UPaperTileSet>> TileSetConfigs;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TMap<FGameplayTag, TObjectPtr<UPaperTileMapComponent>> TerrainMeshes;
};

View File

@ -3,27 +3,13 @@
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "GameplayTagContainer.h"
#include "Generator/TerrainGeneratorBase.h"
#include "Map/TerrainLayerComponent.h"
#include "PaperTerrainMapActor.generated.h"
// 地形到Tile的映射结<E5B084><E7BB93>?
USTRUCT(BlueprintType)
struct FTerrainTileMapping
{
GENERATED_BODY()
// 地形标签
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FGameplayTag TerrainTag;
// 对应的TileSet中的Tile索引
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 TileIndex;
FTerrainTileMapping()
: TileIndex(0)
{
}
};
UCLASS(Blueprintable, BlueprintType)
class BUSYRABBIT_API APaperTerrainMapActor : public AActor
@ -38,7 +24,7 @@ protected:
virtual void OnConstruction(const FTransform& Transform) override;
public:
// 生成地图可在蓝图中调用<EFBFBD><EFBFBD>?
// 生成地图(可在蓝图中调用)
UFUNCTION(BlueprintCallable, Category = "Terrain Map")
void GenerateTerrainMap();
@ -46,36 +32,31 @@ public:
UFUNCTION(BlueprintCallable, Category = "Terrain Map")
void ClearMap();
// 获取地形生成器实<E599A8><E5AE9E>?
UFUNCTION(BlueprintCallable, Category = "Terrain Map")
class UTerrainGenerator* GetTerrainGenerator() const;
// 获取生成的地形数<E5BDA2><E695B0>?
UFUNCTION(BlueprintCallable, Category = "Terrain Map")
TArray<FGameplayTag> GetGeneratedTerrainData() const;
#if WITH_EDITOR
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
protected:
// Paper2D瓦片地图组件
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
class UPaperTileMapComponent* TileMapComponent;
// 地形生成器<EFBFBD><EFBFBD>?
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Terrain")
class UTerrainGenerator* TerrainGenerator;
protected: // 生成器相关
// 地形生成器类
// 生成的地形数<E5BDA2><E695B0>?
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Terrain")
UPROPERTY(EditAnywhere, Category = "Terrain Map")
TSubclassOf<class UTerrainGeneratorBase> GeneratorClass;
// 地形生成器实例
UPROPERTY()
TObjectPtr<class UTerrainGeneratorBase> TerrainGenerator;
// 生成的地形数据
TArray<FGameplayTag> GeneratedTerrainData;
// 地图宽度格子数<E5AD90><E695B0>?
protected: // 地图配置相关
// 地图宽度
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Settings", meta = (ClampMin = "1", ClampMax = "1024"))
int32 MapWidth = 16;
// 地图高度格子数<EFBFBD><EFBFBD>?
// 地图高度
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Settings", meta = (ClampMin = "1", ClampMax = "1024"))
int32 MapHeight = 16;
@ -83,35 +64,34 @@ protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Settings", meta = (ClampMin = "1"))
int32 TileSize = 16;
protected: // 瓦片相关
TMap<FGameplayTag, FTerrainTileSetConfig> *TerrainTileSetConfigs;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Settings", DisplayName="瓦片设置")
TObjectPtr<UTerrainDoubleGridDataAsset> TileSetDataAsset;
protected:
// Paper2D瓦片地图组件
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TObjectPtr<class UPaperTileMapComponent> TileMapComponent;
// 是否在构造时自动生成地图
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Settings")
bool bAutoGenerateOnConstruction;
bool bAutoGenerateOnConstruction = true;
// 是否在开始时自动生成地图
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Settings")
bool bAutoGenerateOnBeginPlay;
// 地形到Tile的映射表
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Terrain Mapping")
TArray<FTerrainTileMapping> TerrainTileMappings;
// 默认Tile索引用于未映射的地形
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Terrain Mapping", meta = (ClampMin = "0"))
int32 DefaultTileIndex;
UPROPERTY(EditAnywhere, Category = Sprite)
TObjectPtr<class UPaperTileSet> TileSet;
bool bAutoGenerateOnBeginPlay = true;
private:
// 初始化TileMap组件
void InitializeTileMap();
void InitializeGenerator();
// 应用地形数据到TileMap
void ApplyTerrainToTileMap(const TArray<FGameplayTag>& TerrainData);
void ApplyTerrainToTileMap(const TArray<FGameplayTag>& TerrainData)const;
// 根据地形标签获取Tile索引
int32 GetTileIndexForTerrain(const FGameplayTag& TerrainTag) const;
void DrawTile(const int32 X, const int32 Y, FGameplayTag TerrainTags[4])const;
// 创建默认地形配置
void SetupDefaultTerrainConfig();
};

View File

@ -1,91 +1,29 @@
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "GameplayTagContainer.h"
#include "Generator/TerrainGeneratorBase.h"
#include "TerrainGenerator.generated.h"
USTRUCT(BlueprintType)
struct FTerrainNodeInfo
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FGameplayTag TerrainTag;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float Probability;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float Weight;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 ParentHandle;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TArray<int32> ChildHandles;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool bIsLeafNode;
FTerrainNodeInfo()
: Probability(1.0f)
, Weight(1.0f)
, ParentHandle(-1)
, bIsLeafNode(false)
{
}
};
UCLASS(BlueprintType, Blueprintable)
class BUSYRABBIT_API UTerrainGenerator : public UObject
class BUSYRABBIT_API UTerrainGenerator : public UTerrainGeneratorBase
{
GENERATED_BODY()
public:
UTerrainGenerator();
UFUNCTION(BlueprintCallable, Category = "Terrain Generator")
int32 AddTerrain(const FGameplayTag& TerrainTag);
UFUNCTION(BlueprintCallable, Category = "Terrain Generator")
void SetWeight(int32 Handle, float Weight);
UFUNCTION(BlueprintCallable, Category = "Terrain Generator")
void SetProbability(int32 Handle, float Probability);
UFUNCTION(BlueprintCallable, Category = "Terrain Generator")
void SetExclusive(const TArray<int32>& Handles);
UFUNCTION(BlueprintCallable, Category = "Terrain Generator")
void BindChildTerrain(int32 ParentHandle, const TArray<int32>& ChildHandles);
UFUNCTION(BlueprintCallable, Category = "Terrain Generator")
TArray<FGameplayTag> GenerateMap(int32 Width = 256, int32 Height = 256);
UFUNCTION(BlueprintCallable, Category = "Terrain Generator")
TMap<int32, FTerrainNodeInfo> GetAllTerrainNodes() const;
virtual bool GenerateWithNodes(
TArray<FGameplayTag>& Map, int32 Width, int32 Height,
const TArray<int32>& ValidLeafNodes
)override;
private:
TMap<int32, FTerrainNodeInfo> TerrainNodes;
TArray<TSet<int32>> ExclusiveGroups;
int32 NextHandle;
void GetPreCheckVisibleHandles(TSet<int32>& VisibleHandles)const;
bool ShouldNodeAppear(int32 Handle) const;
bool CheckExclusive(int32 Handle, const TSet<int32>& AppearingNodes) const;
TArray<int32> CalcValidLeafNodes()const;
void RegionGrowing(TArray<FGameplayTag>& Map, int32 Width, int32 Height,
const TArray<int32>& ValidLeafNodes) const;
void ApplyPerlinNoiseSmoothing(TArray<FGameplayTag>& Map, int32 Width, int32 Height) const;
FGameplayTag GetFinalTerrainTag(int32 Handle) const;
};

View File

@ -5,22 +5,22 @@
#include "GameplayTagContainer.h"
#include "TerrainGeneratorBlueprintLibrary.generated.h"
// 蓝图函数库方便在蓝图中使用地形生成<E7949F><EFBFBD>?
// 蓝图函数库方便在蓝图中使用地形生成<E7949F>?
UCLASS()
class BUSYRABBIT_API UTerrainGeneratorBlueprintLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
// 创建地形生成器实<E599A8><EFBFBD>?
// 创建地形生成器实<E599A8>?
UFUNCTION(BlueprintCallable, Category = "Terrain Generator")
static class UTerrainGenerator* CreateTerrainGenerator();
// 快速设置示例地形配<E5BDA2><EFBFBD>?
// 快速设置示例地形配<E5BDA2>?
UFUNCTION(BlueprintCallable, Category = "Terrain Generator")
static void SetupExampleTerrainConfig(class UTerrainGenerator* Generator);
static void SetupExampleTerrainConfig(class UTerrainGeneratorBase* UTerrainGenerator);
// 生成地图并返回一维数组蓝图不支持嵌套TArray<61><EFBFBD>?
// 生成地图并返回一维数组蓝图不支持嵌套TArray<61>?
UFUNCTION(BlueprintCallable, Category = "Terrain Generator")
static TArray<FGameplayTag> GenerateMap(
class UTerrainGenerator* Generator,
@ -36,11 +36,11 @@ public:
int32& Height
);
// 获取地形标签的显示名<E7A4BA><EFBFBD>?
// 获取地形标签的显示名<E7A4BA>?
UFUNCTION(BlueprintCallable, Category = "Terrain Generator")
static FString GetTerrainDisplayName(const FGameplayTag& TerrainTag);
// 检查地形标签是否有<E590A6><EFBFBD>?
// 检查地形标签是否有<E590A6>?
UFUNCTION(BlueprintCallable, Category = "Terrain Generator")
static bool IsValidTerrainTag(const FGameplayTag& TerrainTag);
};

View File

@ -1,5 +1,3 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
@ -11,9 +9,9 @@
UENUM(BlueprintType)
enum class ERoleMoveDirection: uint8 {
Move_Right, // <20><><EFBFBD><EFBFBD><EFBFBD>ƶ<EFBFBD>
Move_Left, // <20><><EFBFBD><EFBFBD><EFBFBD>ƶ<EFBFBD>
Move_All_Cnt // ö<><C3B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֵ
Move_Right,
Move_Left,
Move_All_Cnt
};
/**
@ -21,12 +19,14 @@ enum class ERoleMoveDirection: uint8 {
*/
UCLASS()
class BUSYRABBIT_API UBusyRoleMovement : public ULuaActorComponent
//class BUSYRABBIT_API UBusyRoleMovement : public UObject, public ILuaOverriderInterface
{
GENERATED_BODY()
public:
UBusyRoleMovement();
public:
public:
virtual void BeginPlay()override;
@ -37,4 +37,7 @@ public:
void ReceiveComponentBeginPlay();
virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)override;
protected:
FVector MovementDirection;
};