寫在前面
本項目同步上傳于coding上,國內讀者可以通過在coding下載項目。
也歡迎你加入我的UE4學習交流QQ群:872537977。如果你喜歡我寫的文章,也希望你點贊、收藏、轉發。謝謝!
如果你喜歡我寫的文章,也希望你點贊、收藏、轉發。謝謝!
如果你想參與到這個項目的開發中來,唯一的要求是像我一樣編寫開發日志讓更多的人看到并學習。
coding地址:https://git.dev.tencent.com/JeremyBrett/uRPG.git
上一節中,我們實現了無限延伸的地形。這一節我們想實現隨機高度的地形。這一節涉及到的知識點包括:
- UE4插件的應用;
- 噪聲函數的應用;
- 用C++創建Actor;
需求分析
關于地形生成的算法有很多,這其中包括噪聲、細胞自動機和維諾圖等。了解不同的算法的利弊,結合實際需求去選擇對應的算法就可以了。
由于本系列文章是開發日志,一切以最大性價比實現需求為目的,這里就不展開講解噪聲算法的實現了。我這里直接找了github上一個開源的針對UE4的噪聲插件。這里可以直接使用。大家可以star這個插件,也可以查看源碼了解其實現方法:SimplexNoise
導入插件
UE4有一個很成熟的插件機制,允許開發者制作各類插件(編輯器擴展),并且使用其他開發者創造的插件。這里我們首先在項目目錄下創建一個插件文件夾,命名必須時Plugins
:
然后將下載好的SimplexNoise插件放入這個文件夾下即可。unreal引擎會掃描這個文件夾來確認我們加載了哪些插件。
應用噪聲
安裝好插件之后,我們打開任意藍圖,可以看到現在多出了很多關于noise的函數,這里我們主要應用SimplexNoise2D
這個節點,來生成隨機高度:
還記得上一節中我們創建的
test
藍圖么,這里我們依然需要用它做一個試驗,我們將之前添加的staticMesh
組件刪除,添加一個InstancedStaticMesh
組件,這個組件,簡單說就是可以“有效渲染同個靜態網格體的多個實例”然后我們在StaticMesh這里選擇Cube:
現在我們要在這個藍圖的構造腳本(ConstructionScript)中做點手腳,和之前一樣,我們用兩個
ForLoop
節點來遍歷X軸和Y軸:此時如果我們將坐標點(乘cube邊長)作為生成staticMesh的位置傳入其中,Z軸保持默認的0:
結果會如何呢?沒錯,400個cube組成了一個長方形板子,就像這樣:
這就是我們使用噪聲的原因——隨機高度,如果我們不使用噪音,而使用完全隨機的數會怎樣?答案是毫無規律可言,我們可以將其稱為“白噪音”:
所以這里我們還是使用SimplexNoise2D來產生隨機數吧。這里我們在將索引值傳給SimplexNoise2D節點前,需要先乘一個系數,這個系數用來調整隨機數的幅度。這里我們先手填1個參數,下一步可以將其提取成為一個變量。
然后我們將
SimplexNoise2D
節點的返回值作為AddInstance
時候的Z軸數值(也需要乘一個調整用的系數)。一個美麗的弧線就出現了,但這只是在藍圖中預覽的樣子。我們還需要讓其在游戲場景中根據不同的位置有所改變。怎么做呢?只需要將當前test實例的坐標位置參與到SimplexNoise2D輸入的X、Y值的運算中即可:
我們先通過GetActorLocation獲取當前實例的坐標點,然后除100。將X和Y值與原本的索引值相加就可以了,我們將Actor添加到場景中試驗一下,多復制幾個實例。并改變其位置:
看起來還不錯,然后我們改變上文中提到的調整參數,就可以使形狀更合理。
VoxelActor
我們創建一個新的C++文件,繼承自Actor,命名為VoxelActor
。
上文中我們使用InstancedStaticMesh
組件和SimplexNoise
插件快速的實現了隨機地形高度,驗證了我們設計思路的合理性。這也是藍圖的最主要用途,驗證想法合理性,快速開發玩法原型,敏捷迭代。
但是這種實現方式并不能作為最終方式存在,為什么?我們發現這種方案有很大的優化空間。主要就在一cube是一個6面體,而很多時候大部分面是可以隱藏的(與其他cube重疊)這可以節省大量的面數。這就要用到ProceduralMeshComponent
這個組件。
要使用這個組件,我們首先需要在uRPG.Build.cs
文件中添加對應配置
public class uRPG : ModuleRules
{
public uRPG(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] {
"Core",
"CoreUObject",
"Engine",
"InputCore"
});
PrivateDependencyModuleNames.AddRange(new string[] {
"ProceduralMeshComponent"
});
PrivateIncludePaths.AddRange(new string[]
{
"SimplexNoise/Private"
});
}
}
我這里直接貼出VoxelActor.h
,主要是聲明了一些變量,并保證部分變量是可以在藍圖中直接設置的。
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ProceduralMeshComponent.h"
#include "VoxelActor.generated.h"
UCLASS()
class URPG_API AVoxelActor : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere,BlueprintReadWrite)
TArray<UMaterialInterface *> Materials;
UPROPERTY(EditAnywhere,BlueprintReadWrite,Meta = (ExposeOnSpawn = true))
int32 rabdinSeed = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
int32 voxelSize = 200;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
int32 chunkLineElements = 10;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
int32 chunkXindex = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
int32 chunkYindex = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float xMult = 1;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float yMult = 1;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float zMult = 1;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float weight = 1;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float freq = 1;
UPROPERTY()
int32 chunkTotalElements;
UPROPERTY()
int32 chunkZElements;
UPROPERTY()
int32 chunkLineElementsP2;
UPROPERTY()
int32 voxelSizeHalf;
UPROPERTY()
TArray<int32> chunnkFields;
UPROPERTY()
UProceduralMeshComponent* proceduralComponent;
public:
// Sets default values for this actor's properties
AVoxelActor();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
virtual void OnConstruction(const FTransform & Transform) override;
};
需要注意的是OnConstruction
函數,這個函數在Actor被實例化的時候調用(我猜的,這里不準確)。這個函數主要是對于一些沒有暴露給藍圖的變量的初始化,以及對于proceduralComponent
組建的初始化,和命名。
void AVoxelActor::OnConstruction(const FTransform & Transform)
{
chunkZElements = 80;
chunkTotalElements = chunkLineElements * chunkLineElements * chunkZElements;
chunkLineElements = chunkLineElements * chunkLineElements;
voxelSizeHalf = voxelSize / 2;
FString string = "Voxel_" + FString::FromInt(chunkXindex) + "_" + FString::FromInt(chunkYindex);
FName name = FName(*string);
proceduralComponent = NewObject<UProceduralMeshComponent>(this, name);
proceduralComponent->RegisterComponent();
RootComponent = proceduralComponent;
RootComponent->SetWorldTransform(Transform);
Super::OnConstruction(Transform);
}
然后記得將Bp_PlayerController節點中的Chunks變量改為VoxelActor類型的。并且在AddChunk
和RemoveChunks
函數中都要進行相應修改
下期預告
好的,這一期的開發任務就暫時完成了,下一步我們就在VoxelActor
中實現隨機地形吧!