Rust在虚幻引擎5中的使用

电子说

1.2w人已加入

描述

 

前言

前段时间,研究了一套 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
Rust

创建 UE 插件

我们将 Html2md 的功能封装成一个插件,这样就可以在各个项目中去使用它。

我们创建一个空白插件,插件名随意,我这边就叫 html2md

Rust

 

在插件中添加 Text Render

我们要在插件中添加一个 Actor,作为处理 HTTP 请求,并渲染 MarkdownText Render

一定要选择添加到插件中,而不是项目中。

Rust

 

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

Rust 

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"

src/md_loader.rs

在这里我们实现一个从 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 目录更改为 Html2mdDyLibhtml2md-dylib.build.cs 也需要更为 Html2mdDyLib.build.cs

将动态链接库添加到依赖

我们编辑 html2md.build.cs,也就是插件的构建脚本。在 PublicDependencyModuleNames 添加 Html2mdDyLibProjects

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 拖入场景。

Rust 

运行游戏!我们会发现 Text Render 展示了 Rust 官网的内容。

Rust

 

总结

通过这次案例,我发现 Rust 可以在 UE 中做很多事情,我只是使用了 html2md 库作为案例来演示,大家感兴趣的话也可以去使用 wsmysql 等,关于网络通讯、数据库、甚至可以在 Rust 中实现游戏功能的算法、状态机等接入到虚幻引擎中使用。
能用少量并安全的代码去编写这些复杂的功能,何乐而不为呢?

用洛佳大佬的话来说:“如果996了一整天,每个开发者都无法避免疲惫的自己忘记释放指针或者释放了两次,很有可能一个漏洞就埋下来了。

能用编程语言理论检查出来漏洞还是好事情。这也不意味着我可以做一个强行检查 C++ 的编译器来达到一样的效果,因为这种理论要求整个语言要重新设计,Rust 就是重新设计的结果”


审核编辑 :李倩

 


打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分