RPG游戲開發日志14:利用噪聲實現隨機地形

寫在前面

本項目同步上傳于coding上,國內讀者可以通過在coding下載項目。
也歡迎你加入我的UE4學習交流QQ群:872537977。如果你喜歡我寫的文章,也希望你點贊、收藏、轉發。謝謝!
如果你喜歡我寫的文章,也希望你點贊、收藏、轉發。謝謝!
如果你想參與到這個項目的開發中來,唯一的要求是像我一樣編寫開發日志讓更多的人看到并學習。
coding地址:https://git.dev.tencent.com/JeremyBrett/uRPG.git
上一節中,我們實現了無限延伸的地形。這一節我們想實現隨機高度的地形。這一節涉及到的知識點包括:

  • UE4插件的應用;
  • 噪聲函數的應用;
  • 用C++創建Actor;

需求分析

關于地形生成的算法有很多,這其中包括噪聲、細胞自動機和維諾圖等。了解不同的算法的利弊,結合實際需求去選擇對應的算法就可以了。
由于本系列文章是開發日志,一切以最大性價比實現需求為目的,這里就不展開講解噪聲算法的實現了。我這里直接找了github上一個開源的針對UE4的噪聲插件。這里可以直接使用。大家可以star這個插件,也可以查看源碼了解其實現方法:SimplexNoise

導入插件

UE4有一個很成熟的插件機制,允許開發者制作各類插件(編輯器擴展),并且使用其他開發者創造的插件。這里我們首先在項目目錄下創建一個插件文件夾,命名必須時Plugins

Plugins文件夾

然后將下載好的SimplexNoise插件放入這個文件夾下即可。unreal引擎會掃描這個文件夾來確認我們加載了哪些插件。

應用噪聲

安裝好插件之后,我們打開任意藍圖,可以看到現在多出了很多關于noise的函數,這里我們主要應用SimplexNoise2D這個節點,來生成隨機高度:

noise函數

還記得上一節中我們創建的test藍圖么,這里我們依然需要用它做一個試驗,我們將之前添加的staticMesh組件刪除,添加一個InstancedStaticMesh組件,這個組件,簡單說就是可以“有效渲染同個靜態網格體的多個實例”
InstancedStaticMesh組件

然后我們在StaticMesh這里選擇Cube:
選擇Cube

現在我們要在這個藍圖的構造腳本(ConstructionScript)中做點手腳,和之前一樣,我們用兩個ForLoop節點來遍歷X軸和Y軸:
遍歷坐標點

此時如果我們將坐標點(乘cube邊長)作為生成staticMesh的位置傳入其中,Z軸保持默認的0:
addInstance節點

結果會如何呢?沒錯,400個cube組成了一個長方形板子,就像這樣:
長方形板子

這就是我們使用噪聲的原因——隨機高度,如果我們不使用噪音,而使用完全隨機的數會怎樣?答案是毫無規律可言,我們可以將其稱為“白噪音”:
白噪音

所以這里我們還是使用SimplexNoise2D來產生隨機數吧。這里我們在將索引值傳給SimplexNoise2D節點前,需要先乘一個系數,這個系數用來調整隨機數的幅度。這里我們先手填1個參數,下一步可以將其提取成為一個變量。
使用SimplexNoise2D

然后我們將SimplexNoise2D節點的返回值作為AddInstance時候的Z軸數值(也需要乘一個調整用的系數)。
SimplexNoise2D結果

一個美麗的弧線就出現了,但這只是在藍圖中預覽的樣子。我們還需要讓其在游戲場景中根據不同的位置有所改變。怎么做呢?只需要將當前test實例的坐標位置參與到SimplexNoise2D輸入的X、Y值的運算中即可:
實例位置參與運算

我們先通過GetActorLocation獲取當前實例的坐標點,然后除100。將X和Y值與原本的索引值相加就可以了,我們將Actor添加到場景中試驗一下,多復制幾個實例。并改變其位置:
多個Test實例

看起來還不錯,然后我們改變上文中提到的調整參數,就可以使形狀更合理。

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類型的。并且在AddChunkRemoveChunks函數中都要進行相應修改

AddChunk修改

下期預告

好的,這一期的開發任務就暫時完成了,下一步我們就在VoxelActor中實現隨機地形吧!

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容