前言
前段時間,研究了一套 Rust 接入 Maya Plugin 的玩法,主要原理還是使用 C ABI 去交互。那我想著 UE 是使用 C++ 寫的,肯定也可以使用 C ABI 去交互,如果可以的話在 UE 中就可以使用 Rust 代碼去跑,甚至還可以使用 Rust Crates,免得使用 C++ 去寫關(guān)于數(shù)據(jù)庫操作、加密操作等容易引發(fā)安全漏洞的代碼。所以我在昨天開始了這個計劃,使用了 Rust 的 html2md 的庫在 UE 中使用,效果圖如下。
開工
這個案例就是在 UE 中實現(xiàn) html2md,雖然實際效果可能沒卵用,主要目的還是帶大家跑下這套流程。
我們要實現(xiàn)的功能就是在 Level 放置一個 Text Render。
游戲開始階段,這個 Text Render 就會拉取 Rust 官網(wǎng)頁面,并將它轉(zhuǎn)為 Markdown 格式展示在游戲中。
創(chuàng)建 UE 項目
我這里使用的版本是 5.0.1,大家使用 4.x 也是可以的。
我們創(chuàng)建一個第三人稱游戲 C++項目,命名為Html2mdExample。
創(chuàng)建 UE 插件
我們將 Html2md 的功能封裝成一個插件,這樣就可以在各個項目中去使用它。
我們創(chuàng)建一個空白插件,插件名隨意,我這邊就叫 html2md。
在插件中添加 Text Render
我們要在插件中添加一個 Actor,作為處理 HTTP 請求,并渲染 Markdown 的 Text Render。
一定要選擇添加到插件中,而不是項目中。
TextRender.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Runtime/Engine/Classes/Components/TextRenderComponent.h"
#include "TextRender.generated.h"
UCLASS()
class HTML2MD_API ATextRender : public AActor
{
GENERATED_BODY()
UPROPERTY(VisibleAnywhere)
UTextRenderComponent* Text;
public:
ATextRender();
protected:
virtual void BeginPlay() override;
public:
virtual void Tick(float DeltaTime) override;
};
TextRender.cpp
簡單寫一寫代碼,添加一個 UTextRenderComponent,并修改它的顏色、旋轉(zhuǎn)、縮放等屬性。
#include "TextRender.h"
ATextRender::ATextRender()
{
PrimaryActorTick.bCanEverTick = true;
Text = CreateDefaultSubobject<UTextRenderComponent>(TEXT("Text"));
Text->SetupAttachment(RootComponent);
}
void ATextRender::BeginPlay()
{
Super::BeginPlay();
Text->SetRelativeRotation(FRotator(90.f, 180.f, 0.f));
Text->SetTextRenderColor(FColor(0, 255, 225));
Text->SetRelativeScale3D(FVector(2.f, 2.f, 2.f));
}
void ATextRender::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
創(chuàng)建 Rust 項目
我們 Rust 項目要創(chuàng)建在 UE 插件項目目錄下。找到插件源碼目錄,與 C++ 源碼同級運行以下命令創(chuàng)建項目。
cargo new --lib html2md-dylib
Cargo.toml
[package]
name = "html2md-dylib"
version = "0.1.0"
edition = "2021"
# 將庫打包成動態(tài)鏈接庫
[lib]
crate-type = ["dylib"]
name = "html2md_dylib"
[dependencies]
# 用于 HTML 轉(zhuǎn)為 Markdown
html2md = "0.2.14"
# 用于進(jìn)行 HTTP 請求
reqwest = { version = "0.11.13", features = ["blocking"] }
[build-dependencies]
# 用于生成 C 頭文件
cbindgen = "0.24.3"
在這里我們實現(xiàn)一個從 HTTP 請求拉取 HTML 并轉(zhuǎn)為 Markdown 的實現(xiàn)。
pub struct MDLoader;
impl MDLoader {
pub fn load_md_from_url(url: &str) -> String {
let body = if let Ok(res) = reqwest::get(url) {
if let Ok(text) = res.text() {
text
} else {
return format!("Failed get {} text", url);
}
} else {
return format!("Failed get {} body", url);
};
html2md::parse_html(&body)
}
}
src/lib.rs
將函數(shù)導(dǎo)出,這樣在動態(tài)鏈接庫中就可以調(diào)用這個函數(shù)了。
use std::{c_char, CStr, CString};
mod md_loader;
#[no_mangle]
pub extern "C" fn load_md_from_url_ffi(url: *const c_char) -> *const c_char {
let url = unsafe { CStr::from_ptr(url) };
let res = md_loader::load_md_from_url(&url.to_string_lossy());
CString::new(res).unwrap().into_raw()
}
build.rs
我們需要使用到構(gòu)建腳本來幫我們生成 C 頭文件,我們將在 C++ 代碼中使用它。
頭文件生成到 include/UEHtml2md.h
extern crate cbindgen;
use std::env;
fn main() {
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let mut config: cbindgen::Config = Default::default();
config.language = cbindgen::Cxx;
cbindgen::generate_with_config(&crate_dir, config)
.expect("Unable to generate bindings")
.write_to_file("include/UEHtml2md.h");
}
html2md-dylib.build.cs
我們要添加一個 Rust 項目名.build.cs,讓 UE 認(rèn)到我們的動態(tài)鏈接庫。相關(guān)文檔
using System;
using System.IO;
using UnrealBuildTool;
public class Html2mdDyLib : ModuleRules
{
public Html2mdDyLib(ReadOnlyTargetRules Target) : base(Target)
{
Type = ModuleType.External;
if (Target.Platform == UnrealTargetPlatform.Win64)
{
// 添加頭文件目錄
PublicIncludePaths.Add(Path.Combine(ModuleDirectory, "include"));
// 添加 .lib
PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "target", "release", "html2md_dylib.dll.lib"));
// 添加 .dll
PublicDelayLoadDLLs.Add("html2md_dylib.dll");
// 我們需要將 .dll 文件復(fù)制到這邊
RuntimeDependencies.Add("$(PluginDir)/Binaries/Win64/html2md_dylib.dll");
}
}
}
構(gòu)建 Rust 項目
我們先運行構(gòu)建命令
cargo build --release
然后將 html2md_dylib.dll 復(fù)制一份到 插件目錄/Binaries/Win64/html2md_dylib.dll。
這一步可以使用腳本去完成,我這邊就不寫了。
連接 Rust & UE
因為我們 Rust 項目目錄名不符合 UE 的規(guī)范,所以我們要將 html2md-dylib 目錄更改為 Html2mdDyLib,html2md-dylib.build.cs 也需要更為 Html2mdDyLib.build.cs。
將動態(tài)鏈接庫添加到依賴
我們編輯 html2md.build.cs,也就是插件的構(gòu)建腳本。在 PublicDependencyModuleNames 添加 Html2mdDyLib 和 Projects。
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"Html2mdDyLib",
"Projects",
// ... add other public dependencies that you statically link with here ...
}
);
插件加載動態(tài)鏈接庫
html2md.h
插件頭文件中聲明 DLL 句柄
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
class Fhtml2mdModule : public IModuleInterface
{
void* Html2mdLibraryHandle;
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};
html2md.cpp
插件加載動態(tài)鏈接庫
如果與本案例命名不同,記得替換代碼中的路徑
// Copyright Epic Games, Inc. All Rights Reserved.
#include "html2md.h"
#include "Core.h"
#include "Modules/ModuleManager.h"
#include "Interfaces/IPluginManager.h"
#define LOCTEXT_NAMESPACE "Fhtml2mdModule"
void Fhtml2mdModule::StartupModule()
{
FString BaseDir = IPluginManager::Get().FindPlugin("html2md")->GetBaseDir();
FString Html2mdLibraryPath = FPaths::Combine(*BaseDir, TEXT("Binaries/Win64/html2md_dylib.dll"));
Html2mdLibraryHandle = !Html2mdLibraryPath.IsEmpty() ? FPlatformProcess::GetDllHandle(*Html2mdLibraryPath) : nullptr;
if (Html2mdLibraryHandle == nullptr)
{
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("ThirdPartyLibraryError", "Failed to load Html2mdLibrary"));
}
}
void Fhtml2mdModule::ShutdownModule()
{
FPlatformProcess::FreeDllHandle(Html2mdLibraryHandle);
Html2mdLibraryHandle = nullptr;
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(Fhtml2mdModule, html2md)
Text Render 調(diào)用 Rust
終于來到了最后要實現(xiàn)的目標(biāo),我們將調(diào)用 Rust 接口,將返回值顯示在 Text Render 中。
TextRender.cpp
#include "TextRender.h"
#include "Html2mdDyLib/include/UEHtml2md.h"
ATextRender::ATextRender()
{
PrimaryActorTick.bCanEverTick = true;
Text = CreateDefaultSubobject<UTextRenderComponent>(TEXT("Text"));
Text->SetupAttachment(RootComponent);
}
void ATextRender::BeginPlay()
{
Super::BeginPlay();
// 在這里調(diào)用 Rust 接口
const char* text = "https://www.rust-lang.org/";
FString result = FString(load_md_from_url_ffi(text));
Text->SetText(FText::FromString(result)); // 設(shè)置 Text 內(nèi)容
Text->SetRelativeRotation(FRotator(90.f, 180.f, 0.f));
Text->SetTextRenderColor(FColor(0, 255, 225));
Text->SetRelativeScale3D(FVector(2.f, 2.f, 2.f));
}
void ATextRender::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
編譯項目
在 Visual Studio 或 虛幻引擎 中編譯都可以。
在 UE 中查看效果
我們將 TextRender 拖入場景。
運行游戲!我們會發(fā)現(xiàn) Text Render 展示了 Rust 官網(wǎng)的內(nèi)容。
總結(jié)
通過這次案例,我發(fā)現(xiàn) Rust 可以在 UE 中做很多事情,我只是使用了 html2md 庫作為案例來演示,大家感興趣的話也可以去使用 ws,mysql 等,關(guān)于網(wǎng)絡(luò)通訊、數(shù)據(jù)庫、甚至可以在 Rust 中實現(xiàn)游戲功能的算法、狀態(tài)機等接入到虛幻引擎中使用。
能用少量并安全的代碼去編寫這些復(fù)雜的功能,何樂而不為呢?
用洛佳大佬的話來說:“如果996了一整天,每個開發(fā)者都無法避免疲憊的自己忘記釋放指針或者釋放了兩次,很有可能一個漏洞就埋下來了。
能用編程語言理論檢查出來漏洞還是好事情。這也不意味著我可以做一個強行檢查 C++ 的編譯器來達(dá)到一樣的效果,因為這種理論要求整個語言要重新設(shè)計,Rust 就是重新設(shè)計的結(jié)果”
審核編輯 :李倩
-
數(shù)據(jù)庫
+關(guān)注
關(guān)注
7文章
3794瀏覽量
64362 -
編程語言
+關(guān)注
關(guān)注
10文章
1942瀏覽量
34707 -
Rust
+關(guān)注
關(guān)注
1文章
228瀏覽量
6601
原文標(biāo)題:Rust 在虛幻引擎 5 中的使用
文章出處:【微信號:Rust語言中文社區(qū),微信公眾號:Rust語言中文社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論