How to Create a Rust Plugin for Unreal Engine
Introduction
In this post I’ll explain how to call Rust code in Unreal Engine. The idea is to build a static library, use cbindgen to autogenerate a C/C++11 header file, and load it up using Unreal’s plugin system.
Caveat
You may have a more difficult time getting Rust working on consoles, though I hear it’s possible. Also, it sounds like extra work is needed to use std, so you may want to go with a no_std strategy.
Create the Unreal project
There are two places plugins can exist where UE5 will automatically find them:
<Unreal Engine Root Directory>/Engine/Plugins/<Project Root Directory>/Plugins/
I used the project root.
- Create a new Unreal C++ project. I used the Top Down starter project with default settings and called it
MyProject. - In the
MyProjectdirectory (C:\Users\username\Documents\Unreal Projects\MyProject), add aPluginsfolder.
Create the Rust library
In
Pluginsruncargo new --lib TestPlugin. Unreal likes to use Pascal case.Open the
TestPluginfolder.In
cargo.tomlchange the package name totest-plugin.In
cargo.tomladd:[lib] crate-type = ["staticlib"]This tells cargo to build a
.libfile.Replace
src/lib.rswith the following content:#[no_mangle] pub extern fn add_5(num: u32) -> u32 { num + 5 } #[cfg(test)] mod tests { use super::*; #[test] fn it_works() { let result = add_5(2); assert_eq!(result, 7); } }Install
cbindgen:cargo install --force cbindgen--forcewill update it if it’s already installed.In
TestPluginadd acbindgen.tomlwith the following content:autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" namespace = "test_plugin"Now generate the C header file:
cbindgen --config cbindgen.toml --crate test-plugin --output Source/TestPlugin/Public/TestPlugin.hThis creates a header file in
Source/TestPlugin/Public, the default location for header files.Run
cargo build --releaseto build the project inreleasemode. You may run into “unresolved external symbol” errors if it’s adebugbuild. More on that later.
Add the Unreal wiring
In
TestPluginaddTestPlugin.upluginwith the following content:{ "FileVersion": 3, "Version": 1, "VersionName": "1.0", "FriendlyName": "Test Plugin", "Description": "", "Category": "Other", "CreatedBy": "", "CreatedByURL": "", "DocsURL": "", "MarketplaceURL": "", "SupportURL": "", "CanContainContent": true, "IsBetaVersion": false, "IsExperimentalVersion": false, "Installed": false }- We need this file so that when we “Generate Visual Studio Project Files” in a later step, our plugin will show in the solution.
- If you generate a new C++ plugin from within the Unreal Editor, it will also add a section called “Modules”, however we can omit it. It’s only needed if your plugin is a C++ module.
In
TestPluginaddSource/TestPlugin/TestPlugin.Build.cswith the following content:using UnrealBuildTool; using System.IO; public class TestPlugin : ModuleRules { public TestPlugin(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; Type = ModuleType.External; string lib = Path.Combine(PluginDirectory, "target\\release\\test_plugin.lib"); string includes = Path.Combine(ModuleDirectory, "Public"); PublicAdditionalLibraries.Add(lib); PublicIncludePaths.Add(includes); } }- With this folder structure, Unreal will find our build file automatically.
UseExplicitOrSharedPCHsensures our module complies with Unreal’s IWYU conventions.PluginDirectorypoints toC:\Users\username\Documents\Unreal Projects\MyProject\Plugins\TestPlugin.ModuleDirectorypoints toC:\Users\username\Documents\Unreal Projects\MyProject\Plugins\TestPlugin\Source\TestPlugin\.- Normally, Unreal tries to build C++ modules when you build your project. Since we don’t want it to build anything, we set the
TypetoExternal. - We also tell Unreal where our static library and C header files are. In the future, you could get fancy with this and use
Target.Platformto point to a different library file depending on the platform.
In the
MyProjectfolder, right click the.uprojectfile and click “Generate Visual Studio Project Files”.Open
MyProject.sln.In
MyProject.Build.cs(C:\Users\username\Documents\Unreal Projects\MyProject\Source\MyProject\MyProject.Build.cs), append “TestPlugin” toPublicDependencyModuleNames.PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "NavigationSystem", "AIModule", "Niagara", "EnhancedInput", "TestPlugin" });Rebuild the project.
Calling our Rust code from a C++ class
At this point, you should be able to type #include "TestP in one of your source files and IntelliSense should list your header file you generated earlier. Note that I was having issues with IntelliSense on one of my attempts. You may have to reopen the solution and/or file. The real test is if you can include the header file and build the project successfully.
- In
MyProjectPlayerController.cppadd the following includes:#include "Engine/Engine.h" #include "TestPlugin.h" - In the
BeginPlaymethod, add the following:if (GEngine) { int num = test_plugin::add_5(3); FString msg = FString::FromInt(num); GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, *msg); }
Now when you build and run the game, you should see a glorious 8 appear on screen.
Fixing unresolved external symbols
If you use std, you may see errors like the ones below when you try to build your Unreal project:
1>test_plugin.lib(std-e493bcbfdc66a475.std.9ab95dd99822253f-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol __imp_GetUserProfileDirectoryW referenced in function _ZN3std3env8home_dir17h01329e00848f730aE
1>test_plugin.lib(std-e493bcbfdc66a475.std.9ab95dd99822253f-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol __imp_NtCreateFile referenced in function _ZN3std3sys7windows2fs20open_link_no_reparse17hee3358b6bcfc697eE
1>test_plugin.lib(std-e493bcbfdc66a475.std.9ab95dd99822253f-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol __imp_RtlNtStatusToDosError referenced in function _ZN3std3sys7windows2fs20open_link_no_reparse17hee3358b6bcfc697eE
1>test_plugin.lib(std-e493bcbfdc66a475.std.9ab95dd99822253f-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol __imp_NtReadFile referenced in function _ZN3std3sys7windows6handle6Handle16synchronous_read17h4a41a152df556564E
1>test_plugin.lib(std-e493bcbfdc66a475.std.9ab95dd99822253f-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol __imp_NtWriteFile referenced in function _ZN3std3sys7windows6handle6Handle17synchronous_write17h4888e2c67110ede7E
1>test_plugin.lib(std-e493bcbfdc66a475.std.9ab95dd99822253f-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol __imp_BCryptGenRandom referenced in function _ZN3std3sys7windows4pipe9anon_pipe17h0951b5613efec006E
These methods are from system libraries. To find out what system libraries your crate uses, run this command:
cargo rustc -q -- --print=native-static-libs
-q = “Do not print cargo log messages”
The output should look something like this:
note: Link against the following native artifacts when linking against this static library. The order and any duplication can be significant on some platforms.
note: native-static-libs: kernel32.lib advapi32.lib bcrypt.lib kernel32.lib ntdll.lib userenv.lib ws2_32.lib kernel32.lib ws2_32.lib kernel32.lib ntdll.lib kernel32.lib msvcrt.lib
Now that we know the names, we can add them to TestPlugin.Build.cs, and Unreal will check the default paths to find them.
PublicSystemLibraries.Add("kernel32.lib");
PublicSystemLibraries.Add("advapi32.lib");
PublicSystemLibraries.Add("bcrypt.lib");
PublicSystemLibraries.Add("kernel32.lib");
PublicSystemLibraries.Add("ntdll.lib");
PublicSystemLibraries.Add("userenv.lib");
PublicSystemLibraries.Add("ws2_32.lib");
PublicSystemLibraries.Add("kernel32.lib");
PublicSystemLibraries.Add("ws2_32.lib");
PublicSystemLibraries.Add("kernel32.lib");
PublicSystemLibraries.Add("ntdll.lib");
PublicSystemLibraries.Add("kernel32.lib");
PublicSystemLibraries.Add("msvcrt.lib");
PublicAdditionalLibraries.Add(lib);
Your project should now build successfully.
Support the blog! Buy a t-shirt or a mug! Or just buy me a beverage🧋. Thanks!
Check out my other projects:
- Emerald Geography - Learn geography on a 3D globe.
- Word Rummage - Random word generator, word finder, and dictionary.
- Color Changer web extension for Firefox and Chrome - Change colors of websites to make them easier to read.