参考了LandscapeEdModeComponentTool代码,魔改以后可在运行时动态增加LandscapeComponent,更换贴图,按需加载地图
主要是为了landscape的优越性能,LOD等
为实现无限地图提供了思路,只要把google的卫星地图动态加载进来,就可以实现无限大的真实地景
.c文件
#pragma once #include "CoreMinimal.h" #include "Components/ActorComponent.h" #include "RuntimeGenerateTerrain.generated.h" class ALandscapeProxy; class UMaterialInstanceDynamic; class UMaterialInstance; class ULandscapeComponent; UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) class FLIGHTSIM_API URuntimeGenerateTerrain : public UActorComponent { GENERATED_BODY() public: // Sets default values for this component's properties URuntimeGenerateTerrain(); protected: // Called when the game starts virtual void BeginPlay() override; public: // Called every frame virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; bool LoadTexture(ULandscapeComponent* C); void DynamicAddLandscapeComponent(); void SetXYtoComponentMap(ULandscapeComponent* C); public: ALandscapeProxy* mLandscape = nullptr; UMaterialInstanceDynamic* GI; UMaterialInstance* SounceMaterial; TMap<FIntPoint, ULandscapeComponent*> mXYtoComponentMap; private: bool bAddComponent:1; };
.cpp文件
URuntimeGenerateTerrain::URuntimeGenerateTerrain() { // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features // off to improve performance if you don't need them. PrimaryComponentTick.bCanEverTick = true; bAddComponent = true; // ... static ConstructorHelpers::FObjectFinder<UMaterialInstance> _material(TEXT("MaterialInstanceConstant'/Game/GoogleMap/M_GoogleBASE_Inst.M_GoogleBASE_Inst'")); if (_material.Succeeded()) { SounceMaterial = _material.Object; } }
初始landscape更换贴图
void URuntimeGenerateTerrain::BeginPlay() { Super::BeginPlay(); // ... UWorld* world = GetWorld(); check(world); TArray<AActor*> _actor; UGameplayStatics::GetAllActorsOfClass(GetWorld(), ALandscapeProxy::StaticClass(), _actor); if (_actor.Num() > 0) { mLandscape = (ALandscapeProxy*)_actor[0]; } if (mLandscape) { mLandscape->LandscapeMaterial = SounceMaterial; for (ULandscapeComponent* C : mLandscape->LandscapeComponents) { LoadTexture(C); SetXYtoComponentMap(C); } } }
bool URuntimeGenerateTerrain::LoadTexture(ULandscapeComponent* C) { if (C->IsRenderStateCreated()) { C->MarkRenderStateDirty(); FlushRenderingCommands(); } for (int j = 0; j < C->MaterialInstances.Num(); j++) { if (!C->MaterialInstances[j]->IsA(UMaterialInstanceDynamic::StaticClass())) { C->MaterialInstances[j] = (UMaterialInstanceConstant*)UMaterialInstanceDynamic::Create(C->MaterialInstances[j], GetTransientPackage());// HACKY CAST! } UMaterialInstanceDynamic* MID = (UMaterialInstanceDynamic*)C->MaterialInstances[j]; int XIndex = C->GetRelativeTransform().GetLocation().X / 7 + 1; int YIndex = C->GetRelativeTransform().GetLocation().Y / 7 + 1; FString _fileName = FString("Texture2D'/Game/GoogleMap/satellite_en/Terrain_1/18/"); int rowOffset = FCString::Atoi(*UFS_Utils::GetTerrainConfigSection(FString("rowOffset"))); //UFS::Utils::GetTerrainConfigSection静态方法,获取config文件中定义的贴图初始的offset int columnOffset = FCString::Atoi(*UFS_Utils::GetTerrainConfigSection(FString("columnOffset"))); _fileName.Append(FString::FromInt(XIndex + rowOffset)); _fileName.Append(FString("/")); _fileName.Append(FString::FromInt(YIndex + columnOffset)); _fileName.Append(FString(".")); _fileName.Append(FString::FromInt(YIndex + columnOffset)); _fileName.Append(FString("'")); UTexture2D* texture = LoadObject<UTexture2D>(NULL, *_fileName); if (texture) { MID->SetTextureParameterValue(FName("Texture"), texture); } else { return false; } } C->RecreateRenderState_Concurrent(); return true; }
只需要把Google卫星地图编号,按照component的行号,列号对应相应的贴图,加载进来。
LandscapeComponent的行号,列号可以通过计算其相对位置获得,7是Quard数目
int XIndex = C->GetRelativeTransform().GetLocation().X / 7 + 1; int YIndex = C->GetRelativeTransform().GetLocation().Y / 7 + 1;
在runtime状态,无法获取LandscapeInfo,所以我们要自己去存储x,y索引号对应的landscapeComponent
void URuntimeGenerateTerrain::SetXYtoComponentMap(ULandscapeComponent* C) { int XIndex = C->GetRelativeTransform().GetLocation().X / 7 + 1; int YIndex = C->GetRelativeTransform().GetLocation().Y / 7 + 1; mXYtoComponentMap.Add(FIntPoint(XIndex, YIndex), C); }
声明:
TMap<FIntPoint, ULandscapeComponent*> mXYtoComponentMap;
在动态增删LandscapeComponent要根据x,y索引来获取相应的Component
动态增加:
void URuntimeGenerateTerrain::DynamicAddLandscapeComponent() { if (!mLandscape)return; //目前为硬编码做测试,后续这里是变量,动态改变 int ComponentIndexX1 = 0; int ComponentIndexY1 = -1; int ComponentIndexX2 = 8; int ComponentIndexY2 = -1; TArray<ULandscapeComponent*> NewComponents; mLandscape->Modify(); for (int32 ComponentIndexY = ComponentIndexY1; ComponentIndexY <= ComponentIndexY2; ComponentIndexY++) { for (int32 ComponentIndexX = ComponentIndexX1; ComponentIndexX <= ComponentIndexX2; ComponentIndexX++) { ULandscapeComponent* LandscapeComponent = mXYtoComponentMap.FindRef(FIntPoint(ComponentIndexX, ComponentIndexY)); if (!LandscapeComponent) { // Add New component... FIntPoint ComponentBase = FIntPoint(ComponentIndexX, ComponentIndexY)*mLandscape->ComponentSizeQuads; LandscapeComponent = NewObject<ULandscapeComponent>(mLandscape, NAME_None, RF_Transactional); mLandscape->LandscapeComponents.Add(LandscapeComponent); NewComponents.Add(LandscapeComponent); LandscapeComponent->Init( ComponentBase.X, ComponentBase.Y, mLandscape->ComponentSizeQuads, mLandscape->NumSubsections, mLandscape->SubsectionSizeQuads ); LandscapeComponent->AttachToComponent(mLandscape->GetRootComponent(), FAttachmentTransformRules::SnapToTargetIncludingScale); //按理来说,landscapeComponent的相对位置z应该为0,但不知道什么原因,设置为0时,新增的component和landscape有高度差,只能通过一个一个试,最后确定256为正确值(目前不知道原因) LandscapeComponent->SetRelativeLocation(FVector(ComponentBase.X, ComponentBase.Y, 256.f)); // Assign shared properties LandscapeComponent->UpdatedSharedPropertiesFromActor(); int32 ComponentVerts = (mLandscape->SubsectionSizeQuads + 1) * mLandscape->NumSubsections; // Update Weightmap Scale Bias LandscapeComponent->WeightmapScaleBias = FVector4(1.0f / (float)ComponentVerts, 1.0f / (float)ComponentVerts, 0.5f / (float)ComponentVerts, 0.5f / (float)ComponentVerts); LandscapeComponent->WeightmapSubsectionOffset = (float)(LandscapeComponent->SubsectionSizeQuads + 1) / (float)ComponentVerts; TArray<FColor> HeightData; HeightData.Empty(FMath::Square(ComponentVerts)); HeightData.AddZeroed(FMath::Square(ComponentVerts)); LandscapeComponent->InitHeightmapData(HeightData, true); LandscapeComponent->UpdateMaterialInstances(); LandscapeComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision); SetXYtoComponentMap(LandscapeComponent); LoadTexture(LandscapeComponent); } } } // Need to register to use general height/xyoffset data update for (int32 Idx = 0; Idx < NewComponents.Num(); Idx++) { NewComponents[Idx]->RegisterComponent(); } //必须的 否则新增的component闪烁 for (ULandscapeComponent* NewComponent : NewComponents) { // Update Collision NewComponent->UpdateCachedBounds(); NewComponent->UpdateBounds(); NewComponent->MarkRenderStateDirty(); } }
因为ue4是不支持runtime landscape的,所以很多方法都是有check(GIsEditor)判定的,要修改源码,把这些Assert注释掉,否则会崩溃
具体有:
LandscapeEdit.cpp 208行 282行
Landscape.cpp 985
MaterialInstanceConstant.cpp 54行 78 行90行
MaterialInstance 3508行