前言
前段时间,研究了一套 Rust 接入 Maya Plugin 的玩法,主要原理还是使用 C ABI 去交互。那我想着 UE 是使用 C++ 写的,肯定也可以使用 C ABI 去交互,如果可以的话在 UE 中就可以使用 Rust 代码去跑,甚至还可以使用 Rust Crates,免得使用 C++ 去写关于数据库操作、加密操作等容易引发安全漏洞的代码。所以我在昨天开始了这个计划,使用了 Rust 的 html2md 的库在 UE 中使用,效果图如下。
开工
这个案例就是在 UE 中实现 html2md,虽然实际效果可能没卵用,主要目的还是带大家跑下这套流程。
我们要实现的功能就是在 Level 放置一个 Text Render。
游戏开始阶段,这个 Text Render 就会拉取 Rust 官网页面,并将它转为 Markdown 格式展示在游戏中。
创建 UE 项目
我这里使用的版本是 5.0.1,大家使用 4.x 也是可以的。
我们创建一个第三人称游戏 C++项目,命名为Html2mdExample。
创建 UE 插件
我们将 Html2md 的功能封装成一个插件,这样就可以在各个项目中去使用它。
我们创建一个空白插件,插件名随意,我这边就叫 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,并修改它的颜色、旋转、缩放等属性。
#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);
}
创建 Rust 项目
我们 Rust 项目要创建在 UE 插件项目目录下。找到插件源码目录,与 C++ 源码同级运行以下命令创建项目。
cargo new --lib html2md-dylib
Cargo.toml
[package]
name = "html2md-dylib"
version = "0.1.0"
edition = "2021"
# 将库打包成动态链接库
[lib]
crate-type = ["dylib"]
name = "html2md_dylib"
[dependencies]
# 用于 HTML 转为 Markdown
html2md = "0.2.14"
# 用于进行 HTTP 请求
reqwest = { version = "0.11.13", features = ["blocking"] }
[build-dependencies]
# 用于生成 C 头文件
cbindgen = "0.24.3"
在这里我们实现一个从 HTTP 请求拉取 HTML 并转为 Markdown 的实现。
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
将函数导出,这样在动态链接库中就可以调用这个函数了。
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
我们需要使用到构建脚本来帮我们生成 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 认到我们的动态链接库。相关文档
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 文件复制到这边
RuntimeDependencies.Add("$(PluginDir)/Binaries/Win64/html2md_dylib.dll");
}
}
}
构建 Rust 项目
我们先运行构建命令
cargo build --release
然后将 html2md_dylib.dll 复制一份到 插件目录/Binaries/Win64/html2md_dylib.dll。
这一步可以使用脚本去完成,我这边就不写了。
连接 Rust & UE
因为我们 Rust 项目目录名不符合 UE 的规范,所以我们要将 html2md-dylib 目录更改为 Html2mdDyLib,html2md-dylib.build.cs 也需要更为 Html2mdDyLib.build.cs。
将动态链接库添加到依赖
我们编辑 html2md.build.cs,也就是插件的构建脚本。在 PublicDependencyModuleNames 添加 Html2mdDyLib 和 Projects。
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"Html2mdDyLib",
"Projects",
// ... add other public dependencies that you statically link with here ...
}
);
插件加载动态链接库
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
插件加载动态链接库
如果与本案例命名不同,记得替换代码中的路径
// 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 调用 Rust
终于来到了最后要实现的目标,我们将调用 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();
// 在这里调用 Rust 接口
const char* text = "https://www.rust-lang.org/";
FString result = FString(load_md_from_url_ffi(text));
Text->SetText(FText::FromString(result)); // 设置 Text 内容
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 拖入场景。
运行游戏!我们会发现 Text Render 展示了 Rust 官网的内容。
总结
通过这次案例,我发现 Rust 可以在 UE 中做很多事情,我只是使用了 html2md 库作为案例来演示,大家感兴趣的话也可以去使用 ws,mysql 等,关于网络通讯、数据库、甚至可以在 Rust 中实现游戏功能的算法、状态机等接入到虚幻引擎中使用。
能用少量并安全的代码去编写这些复杂的功能,何乐而不为呢?
用洛佳大佬的话来说:“如果996了一整天,每个开发者都无法避免疲惫的自己忘记释放指针或者释放了两次,很有可能一个漏洞就埋下来了。
能用编程语言理论检查出来漏洞还是好事情。这也不意味着我可以做一个强行检查 C++ 的编译器来达到一样的效果,因为这种理论要求整个语言要重新设计,Rust 就是重新设计的结果”
审核编辑 :李倩
-
数据库
+关注
关注
7文章
3799浏览量
64379 -
编程语言
+关注
关注
10文章
1945浏览量
34732 -
Rust
+关注
关注
1文章
228浏览量
6607
原文标题:Rust 在虚幻引擎 5 中的使用
文章出处:【微信号:Rust语言中文社区,微信公众号:Rust语言中文社区】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论