接入Spine
This commit is contained in:
		| @ -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[] {  }); | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
							
								
								
									
										42
									
								
								Source/BusyRabbit/Private/Level/Actor/BusyPawnBase.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								Source/BusyRabbit/Private/Level/Actor/BusyPawnBase.cpp
									
									
									
									
									
										Normal 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; | ||||
| } | ||||
							
								
								
									
										18
									
								
								Source/BusyRabbit/Private/Level/Actor/BusyPlayerRole.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								Source/BusyRabbit/Private/Level/Actor/BusyPlayerRole.cpp
									
									
									
									
									
										Normal 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)); | ||||
| } | ||||
| @ -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()); | ||||
| } | ||||
|  | ||||
| @ -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; | ||||
| } | ||||
| @ -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; | ||||
| } | ||||
							
								
								
									
										178
									
								
								Source/BusyRabbit/Private/Level/Generator/VoronoiDiagram.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								Source/BusyRabbit/Private/Level/Generator/VoronoiDiagram.cpp
									
									
									
									
									
										Normal 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; | ||||
| } | ||||
| @ -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; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										52
									
								
								Source/BusyRabbit/Private/Level/LevelPlayerController.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								Source/BusyRabbit/Private/Level/LevelPlayerController.cpp
									
									
									
									
									
										Normal 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); | ||||
| } | ||||
							
								
								
									
										55
									
								
								Source/BusyRabbit/Private/Level/LevelPlayerState.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								Source/BusyRabbit/Private/Level/LevelPlayerState.cpp
									
									
									
									
									
										Normal 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; | ||||
| } | ||||
| @ -0,0 +1 @@ | ||||
| #include "Level/Map/ClimateLayerComponent.h" | ||||
| @ -0,0 +1 @@ | ||||
| #include "Level/Map/CreatureLayerComponent.h" | ||||
| @ -0,0 +1 @@ | ||||
| #include "Level/Map/DecorationLayerComponent.h" | ||||
							
								
								
									
										22
									
								
								Source/BusyRabbit/Private/Level/Map/GameMapActor.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								Source/BusyRabbit/Private/Level/Map/GameMapActor.cpp
									
									
									
									
									
										Normal 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); | ||||
| } | ||||
| @ -0,0 +1 @@ | ||||
| #include "Level/Map/LightingLayerComponent.h" | ||||
| @ -0,0 +1 @@ | ||||
| #include "Level/Map/PlacementLayerComponent.h" | ||||
							
								
								
									
										264
									
								
								Source/BusyRabbit/Private/Level/Map/TerrainLayerComponent.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										264
									
								
								Source/BusyRabbit/Private/Level/Map/TerrainLayerComponent.cpp
									
									
									
									
									
										Normal 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); | ||||
| 	} | ||||
| } | ||||
| @ -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); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -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(); | ||||
| } | ||||
|  | ||||
|  | ||||
| @ -8,7 +8,7 @@ UTerrainGenerator* UTerrainGeneratorBlueprintLibrary::CreateTerrainGenerator() | ||||
| 	return NewObject<UTerrainGenerator>(); | ||||
| } | ||||
|  | ||||
| void UTerrainGeneratorBlueprintLibrary::SetupExampleTerrainConfig(UTerrainGenerator* Generator) | ||||
| void UTerrainGeneratorBlueprintLibrary::SetupExampleTerrainConfig(UTerrainGeneratorBase* Generator) | ||||
| { | ||||
| 	if (!Generator) | ||||
| 	{ | ||||
|  | ||||
| @ -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); | ||||
| } | ||||
|  | ||||
|  | ||||
							
								
								
									
										61
									
								
								Source/BusyRabbit/Public/Level/Actor/BusyPawnBase.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								Source/BusyRabbit/Public/Level/Actor/BusyPawnBase.h
									
									
									
									
									
										Normal 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; | ||||
| 	 | ||||
| }; | ||||
							
								
								
									
										32
									
								
								Source/BusyRabbit/Public/Level/Actor/BusyPlayerRole.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								Source/BusyRabbit/Public/Level/Actor/BusyPlayerRole.h
									
									
									
									
									
										Normal 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; | ||||
| 	 | ||||
| }; | ||||
| @ -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; | ||||
| }; | ||||
| @ -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; | ||||
| }; | ||||
| @ -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; | ||||
|  | ||||
| }; | ||||
							
								
								
									
										139
									
								
								Source/BusyRabbit/Public/Level/Generator/VoronoiDiagram.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								Source/BusyRabbit/Public/Level/Generator/VoronoiDiagram.h
									
									
									
									
									
										Normal 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); | ||||
| }; | ||||
| @ -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; | ||||
| }; | ||||
							
								
								
									
										41
									
								
								Source/BusyRabbit/Public/Level/LevelPlayerController.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								Source/BusyRabbit/Public/Level/LevelPlayerController.h
									
									
									
									
									
										Normal 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; | ||||
|  | ||||
| }; | ||||
							
								
								
									
										40
									
								
								Source/BusyRabbit/Public/Level/LevelPlayerState.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								Source/BusyRabbit/Public/Level/LevelPlayerState.h
									
									
									
									
									
										Normal 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; | ||||
| }; | ||||
| @ -0,0 +1,7 @@ | ||||
| #pragma once | ||||
|  | ||||
| class ClimateLayerComponent | ||||
| { | ||||
| public: | ||||
| 	 | ||||
| }; | ||||
| @ -0,0 +1,7 @@ | ||||
| #pragma once | ||||
|  | ||||
| class CreatureLayerComponent | ||||
| { | ||||
| public: | ||||
| 	 | ||||
| }; | ||||
| @ -0,0 +1,7 @@ | ||||
| #pragma once | ||||
|  | ||||
| class DecorationLayerComponent | ||||
| { | ||||
| public: | ||||
| 	 | ||||
| }; | ||||
							
								
								
									
										25
									
								
								Source/BusyRabbit/Public/Level/Map/GameMapActor.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Source/BusyRabbit/Public/Level/Map/GameMapActor.h
									
									
									
									
									
										Normal 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; | ||||
|  | ||||
| }; | ||||
|  | ||||
| @ -0,0 +1,7 @@ | ||||
| #pragma once | ||||
|  | ||||
| class LightingLayerComponent | ||||
| { | ||||
| public: | ||||
| 	 | ||||
| }; | ||||
| @ -0,0 +1,7 @@ | ||||
| #pragma once | ||||
|  | ||||
| class PlacementLayerComponent | ||||
| { | ||||
| public: | ||||
| 	 | ||||
| }; | ||||
							
								
								
									
										66
									
								
								Source/BusyRabbit/Public/Level/Map/TerrainLayerComponent.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								Source/BusyRabbit/Public/Level/Map/TerrainLayerComponent.h
									
									
									
									
									
										Normal 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; | ||||
| }; | ||||
| @ -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(); | ||||
| }; | ||||
|  | ||||
| @ -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; | ||||
| }; | ||||
|  | ||||
| @ -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); | ||||
| }; | ||||
|  | ||||
| @ -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; | ||||
| }; | ||||
|  | ||||
		Reference in New Issue
	
	Block a user