[{"content":"Game localization has traditionally been a time-consuming and expensive process. Manual translation work often requires weeks of coordination with external services. AI language models now provide developers with powerful tools to streamline this workflow while they maintain quality and reduce costs.\nThis guide demonstrates how to set up automated AI-powered translation for your Unreal Engine project using the AI Localization Automator plugin . The plugin turns hours of manual work into minutes of automated processing.\nYour browser does not support the video tag. Why AI-Powered Localization? Modern AI translation offers several advantages over traditional methods:\nSpeed: Translate hundreds of text entries in minutes rather than days Cost-Effectiveness: Significantly reduce translation costs, especially for indie developers Consistency: Maintain consistent terminology and style across your entire project Iteration-Friendly: Quick turnaround allows for rapid prototyping and testing Multiple Providers: Choose from various AI services based on quality, cost, and privacy needs Prerequisites Before you start automated translation, ensure you have:\nLocalizable text content in your project (using FText in C++ or Text properties in Blueprints) A configured localization target with gathered source text At least one target language added to your localization target Access to an AI translation service The AI Localization Automator plugin installed New to Unreal Engine localization? Click here for setup basics If you&rsquo;re unfamiliar with making text localizable in UE, the fundamental steps are:\nUse FText for localizable content - Only FText (Text in Blueprints) can be localized Mark text as localizable - Enable the &ldquo;Localize&rdquo; checkbox in text properties Create localization targets - Set up targets in the Localization Dashboard Gather source text - Use the &ldquo;Gather Text&rdquo; process to discover localizable content Add target languages - Specify which languages you want to translate to For detailed setup instructions, see the Localization Setup Guide .\nSetting Up AI Translation Step 1: Access the AI Translation Manager The AI Localization Automator plugin integrates directly with Unreal Engine&rsquo;s Localization Dashboard. You&rsquo;ll find AI translation options in your localization target&rsquo;s toolbar.\nOpen Tools \u2192 Localization Dashboard Select your localization target Click the &ldquo;AI Translate&rdquo; button in the toolbar Step 2: Choose Your AI Provider The plugin supports multiple AI providers, each with different advantages for your project needs:\nLocal AI (Ollama)\nComplete privacy - data never leaves your machine No API costs or usage limits Works offline once models are downloaded Requires local setup and capable hardware Cloud Providers\nOpenAI: Consistently high quality, extensive model selection Anthropic Claude: Excellent for creative and narrative content DeepSeek: Cost-effective option with good quality Google Gemini: Strong multilingual support with competitive pricing Step 3: Configure Your Translation Service Configuration varies by provider but typically includes:\nFor Cloud Services:\nAPI Key for authentication Model selection (newer models generally offer better quality) Temperature settings (0.3 recommended for consistent translations) Request timeout and retry settings For Local AI:\nBase URL (typically http:\/\/localhost:11434 for Ollama) Model name (e.g., &ldquo;llama3.2&rdquo;) Local processing parameters For detailed configuration options for each provider, see the Translation Providers Guide .\nStep 4: Process Your Translations With configuration complete, the automated translation process becomes straightforward:\nReview discovered content - The system automatically finds all localizable text Start processing - Click &ldquo;Start Translation&rdquo; to initiate batch translation Monitor progress - Watch real-time updates as translations complete Save results - Click &ldquo;Save Translations&rdquo; to commit successful translations The entire process typically completes in minutes for projects with hundreds of text entries.\nYour browser does not support the video tag. Completing the Localization Pipeline After AI translation, you need to complete the standard UE localization workflow:\n1. Update Localization System Return to the Localization Dashboard and run &ldquo;Gather Text&rdquo; to update the system with your new translations.\nYour browser does not support the video tag. 2. Compile Translations Use &ldquo;Compile Text&rdquo; to create the binary files your game will actually use.\nYour browser does not support the video tag. 3. Package Language Support In Project Settings \u2192 Packaging, ensure your target languages are selected for packaging:\nTesting Your Translations Verify your automated translations using UE&rsquo;s built-in preview features:\nEditor Preview: Set &ldquo;Preview Game Language&rdquo; in Editor Preferences \u2192 Region &amp; Language\nUMG Preview: Use the language selector in UMG widget previews\nBest Practices Start Small Begin with a subset of your content to test provider configuration and quality expectations before you process large batches.\nMonitor Quality Review translations, especially for:\nTechnical terminology specific to your game Cultural references that may need adaptation UI text that must fit within space constraints Maintain Consistency Use consistent prompt templates and provider settings across your project to ensure terminology remains uniform.\nVersion Control Integration The AI Localization Automator integrates with UE&rsquo;s standard localization files, which makes it compatible with your existing version control workflow.\nThe Impact on Game Development Automated AI translation changes localization from a bottleneck into an enabler:\nFaster Iteration: Test your game in multiple languages during development Broader Market Access: Cost-effectively target international markets Enhanced Accessibility: Make your game available to global audiences Resource Allocation: Focus human expertise on polish and cultural adaptation rather than initial translation Key Features of AI Localization Automator The AI Localization Automator plugin provides:\nMultiple AI Providers: Choose from OpenAI, Claude, DeepSeek, Gemini, or run locally with Ollama Seamless Integration: Built-in integration with Unreal Engine&rsquo;s Localization Dashboard Batch Processing: Translate hundreds of text entries efficiently with configurable concurrency Smart Retry System: Automatic retry logic with configurable failure thresholds Real-time Progress Tracking: Live progress updates with detailed status information Production Ready: Tested extensively with large-scale localization projects For complete documentation, visit docs.georgy.dev\/ai-localization-automator .\nConclusion AI-powered localization represents a significant shift in how developers approach international game distribution. The AI Localization Automator automates the translation process while it maintains integration with Unreal Engine&rsquo;s robust localization system. Developers can focus on creating great games rather than managing complex translation workflows.\nWhether you&rsquo;re an indie developer who wants to expand market reach or a studio that manages large-scale localization projects, automated AI translation provides the tools to make multilingual game development both accessible and efficient.\n","permalink":"https:\/\/georgy.dev\/posts\/automate-unreal-engine-localization-workflow-ai\/","summary":"<p>Game localization has traditionally been a time-consuming and expensive process. Manual translation work often requires weeks of coordination with external services. AI language models now provide developers with powerful tools to streamline this workflow while they maintain quality and reduce costs.<\/p>\n<p>This guide demonstrates how to set up automated AI-powered translation for your Unreal Engine project using the <a href=\"https:\/\/www.fab.com\/listings\/627cde30-5ab0-4393-a6de-01f297a9c8e3\" target=\"_blank\">\n    AI Localization Automator plugin\n  <\/a>. The plugin turns hours of manual work into minutes of automated processing.<\/p>","title":"How to automate your Unreal Engine localization workflow with AI"},{"content":"Introduction Still, as of UE 5.5, multi-line editable text boxes lack proper keyboard and gamepad navigation support. Let&rsquo;s fix this without modifying the engine&rsquo;s source code. I&rsquo;ll experiment with a simple 2x2 grid of text boxes to demonstrate how we can implement smooth, unified navigation across both keyboard and gamepad input devices, which you can also recreate for testing purposes.\nCurrent Limitations Keyboard Arrow keys only allow navigation within the content of a single text box, and there&rsquo;s no way to navigate between text boxes themselves.\nGamepad The left stick moves between the text boxes, but neither the right stick nor the D-pad can be used to navigate between the content of a text box or between boxes themselves.\nSolution To fix this, we will create custom widgets extending both UMultiLineEditableTextBox (for UMG) and SMultiLineEditableTextBox (for Slate). The UMG widget will override the RebuildWidget() method to instantiate our custom Slate widget, which will handle both keyboard and gamepad navigation in a unified way.\nHere&rsquo;s the implementation:\nClick to see the code Header:\nclass RUNTIMETTSDEMO_API SCustomMultiLineEditableTextBox : public SMultiLineEditableTextBox { protected: \/** Whether we should ignore the next OnKeyDown event *\/ bool bIgnoreOnKeyDown = false; public: \/\/~ Begin SWidget Interface virtual bool SupportsKeyboardFocus() const override { return true; } virtual FReply OnKeyDown(const FGeometry&amp; MyGeometry, const FKeyEvent&amp; InKeyEvent) override; \/\/~ End SWidget Interface virtual FReply HandleNavigation(const FGeometry&amp; MyGeometry, EUINavigation Navigation); }; UCLASS(ClassGroup = &#34;UI&#34;, meta = (Category = &#34;Mod.io Common UI&#34;)) class RUNTIMETTSDEMO_API UCustomMultiLineEditableTextBox : public UMultiLineEditableTextBox { GENERATED_BODY() protected: virtual TSharedRef&lt;SWidget&gt; RebuildWidget() override; }; Source:\nFReply SCustomMultiLineEditableTextBox::OnKeyDown(const FGeometry&amp; MyGeometry, const FKeyEvent&amp; InKeyEvent) { if (bIgnoreOnKeyDown) { return FReply::Unhandled(); } const FKey Key = InKeyEvent.GetKey(); if (Key == EKeys::Up || Key == EKeys::Gamepad_RightStick_Up || Key == EKeys::Gamepad_LeftStick_Up || Key == EKeys::Gamepad_DPad_Up) { return HandleNavigation(MyGeometry, EUINavigation::Up); } if (Key == EKeys::Down || Key == EKeys::Gamepad_RightStick_Down || Key == EKeys::Gamepad_LeftStick_Down || Key == EKeys::Gamepad_DPad_Down) { return HandleNavigation(MyGeometry, EUINavigation::Down); } if (Key == EKeys::Left || Key == EKeys::Gamepad_RightStick_Left || Key == EKeys::Gamepad_LeftStick_Left || Key == EKeys::Gamepad_DPad_Left) { return HandleNavigation(MyGeometry, EUINavigation::Left); } if (Key == EKeys::Right || Key == EKeys::Gamepad_RightStick_Right || Key == EKeys::Gamepad_LeftStick_Right || Key == EKeys::Gamepad_DPad_Right) { return HandleNavigation(MyGeometry, EUINavigation::Right); } return FReply::Unhandled(); } FReply SCustomMultiLineEditableTextBox::HandleNavigation(const FGeometry&amp; MyGeometry, EUINavigation Navigation) { TSharedPtr&lt;SWidget&gt; EditableTextWidget = StaticCastSharedPtr&lt;SWidget&gt;(EditableText); if (!EditableTextWidget) { UE_LOG(LogTemp, Error, TEXT(&#34;Unable to handle &#39;%s&#39; navigation for the multi line text box: EditableText widget is null&#34;), *UEnum::GetValueAsString(Navigation)); return FReply::Unhandled(); } UE_LOG(LogTemp, Log, TEXT(&#34;Sending &#39;%s&#39; navigation to the multiline editable text&#34;), *UEnum::GetValueAsString(Navigation)); FKey NavigationKey = [Navigation]() { switch (Navigation) { case EUINavigation::Previous: case EUINavigation::Left: return EKeys::Left; case EUINavigation::Next: case EUINavigation::Right: return EKeys::Right; case EUINavigation::Up: return EKeys::Up; case EUINavigation::Down: return EKeys::Down; default: { UE_LOG(LogTemp, Error, TEXT(&#34;Invalid navigation direction &#39;%s&#39;&#34;), *UEnum::GetValueAsString(Navigation)); return EKeys::Invalid; } } }(); const FTextLocation PriorCursorPosition = EditableText-&gt;GetCursorLocation(); FKeyEvent NavigationEvent(NavigationKey, FSlateApplication::Get().GetModifierKeys(), 0, false, 0, 0); bIgnoreOnKeyDown = true; FReply HandledEvent = EditableTextWidget-&gt;OnKeyDown(MyGeometry, NavigationEvent); bIgnoreOnKeyDown = false; const FTextLocation NewCursorPosition = EditableText-&gt;GetCursorLocation(); \/\/ If the cursor changed position, the editable text handled the event by moving the cursor if (PriorCursorPosition != NewCursorPosition) { return HandledEvent; } \/\/ Otherwise, simulate navigation using the gamepad to go beyond the widget&#39;s bounds const float AnalogValue = [Navigation]() { switch (Navigation) { case EUINavigation::Previous: case EUINavigation::Left: return -1.0f; case EUINavigation::Next: case EUINavigation::Right: return 1.0f; case EUINavigation::Up: return 1.0f; case EUINavigation::Down: return -1.0f; default: { UE_LOG(LogTemp, Error, TEXT(&#34;Invalid navigation direction &#39;%s&#39;&#34;), *UEnum::GetValueAsString(Navigation)); return 0.0f; } } }(); const FKey GamepadKey = [Navigation]() { switch (Navigation) { case EUINavigation::Previous: case EUINavigation::Left: return EKeys::Gamepad_LeftX; case EUINavigation::Next: case EUINavigation::Right: return EKeys::Gamepad_LeftX; case EUINavigation::Up: return EKeys::Gamepad_LeftY; case EUINavigation::Down: return EKeys::Gamepad_LeftY; default: { UE_LOG(LogTemp, Error, TEXT(&#34;Invalid navigation direction &#39;%s&#39;&#34;), *UEnum::GetValueAsString(Navigation)); return EKeys::Invalid; } } }(); FAnalogInputEvent GamepadEvent(GamepadKey, FSlateApplication::Get().GetModifierKeys(), 0, false, 0, 0, AnalogValue); FSlateApplication::Get().ProcessAnalogInputEvent(GamepadEvent); return FReply::Handled(); } TSharedRef&lt;SWidget&gt; UCustomMultiLineEditableTextBox::RebuildWidget() { MyEditableTextBlock = SNew(SCustomMultiLineEditableTextBox) .Style(&amp;WidgetStyle) .AllowContextMenu(AllowContextMenu) .IsReadOnly(bIsReadOnly) .VirtualKeyboardOptions(VirtualKeyboardOptions) .VirtualKeyboardDismissAction(VirtualKeyboardDismissAction) .OnTextChanged(BIND_UOBJECT_DELEGATE(FOnTextChanged, HandleOnTextChanged)) .OnTextCommitted(BIND_UOBJECT_DELEGATE(FOnTextCommitted, HandleOnTextCommitted)); MyEditableTextBlock-&gt;SetOnKeyDownHandler(FOnKeyDown::CreateWeakLambda(this, [this](const FGeometry&amp; MyGeometry, const FKeyEvent&amp; InKeyEvent) -&gt; FReply { if (TSharedPtr&lt;SWidget&gt; WidgetToHandle = StaticCastSharedPtr&lt;SWidget&gt;(MyEditableTextBlock)) { return WidgetToHandle-&gt;OnKeyDown(MyGeometry, InKeyEvent); } return FReply::Unhandled(); })); return MyEditableTextBlock.ToSharedRef(); } Result The result is smooth, consistent navigation:\nKeyboard: Gamepad: ","permalink":"https:\/\/georgy.dev\/posts\/multi-line-editable-text-box-navigations\/","summary":"<h3 id=\"introduction\">Introduction<\/h3>\n<p>Still, as of UE 5.5, multi-line editable text boxes lack proper keyboard and gamepad navigation support. Let&rsquo;s fix this without modifying the engine&rsquo;s source code. I&rsquo;ll experiment with a simple 2x2 grid of text boxes to demonstrate how we can implement smooth, unified navigation across both keyboard and gamepad input devices, which you can also recreate for testing purposes.<\/p>\n<h3 id=\"current-limitations\">Current Limitations<\/h3>\n<h4 id=\"keyboard\">Keyboard<\/h4>\n<p>Arrow keys only allow navigation within the content of a single text box, and there&rsquo;s no way to navigate between text boxes themselves.<\/p>","title":"Improve multi-line editable text box keyboard and gamepad navigations"},{"content":"Need to use third-party libraries that depend on spdlog in your Unreal project? Here&rsquo;s how to simulate the spdlog API using Unreal&rsquo;s native UE_LOG system, to let you avoid adding extra dependencies while keeping your code clean.\nWe&rsquo;ll create a logging utility class that matches the spdlog interface, handling type conversions to FString and formatting along the way.\nImplementation Here&rsquo;s the code that bridges spdlog and Unreal&rsquo;s logging system:\n#pragma once #include &#34;Containers\/UnrealString.h&#34; #include &#34;Internationalization\/Text.h&#34; #include &#34;Templates\/EnableIf.h&#34; #include &#34;Templates\/IsIntegral.h&#34; #include &#34;Templates\/IsFloatingPoint.h&#34; #include &#34;Logging\/LogMacros.h&#34; #include &lt;string&gt; DEFINE_LOG_CATEGORY_STATIC(LogPiperLibrary, Log, All); \/** * Class to simulate the spdlog API in Unreal Engine * spdlog is used in the original piper *\/ class spdlog { private: \/\/ Template function for integral types template &lt;typename T, typename TEnableIf&lt;TIsIntegral&lt;T&gt;::Value, bool&gt;::Type = 0&gt; static FString ToFString(T value) { \/\/ Most integral types won&#39;t be larger than signed 64-bit integer \/\/ TODO: Add support for larger integral types supported by UE (uint64) return FString::Printf(TEXT(&#34;%lld&#34;), static_cast&lt;int64&gt;(value)); } \/\/ Template function for floating-point types template &lt;typename T, typename TEnableIf&lt;TIsFloatingPoint&lt;T&gt;::Value, bool&gt;::Type = 0&gt; static FString ToFString(T value) { return FString::Printf(TEXT(&#34;%f&#34;), static_cast&lt;double&gt;(value)); } \/\/ Specialize for FString static FString ToFString(const FString&amp; Value) { return Value; } \/\/ Specialize for std::string static FString ToFString(const std::string&amp; Value) { return StringCast&lt;TCHAR&gt;(Value.c_str(), Value.size()).Get(); } \/\/ Convert the format string to UTF-16 for Unreal Engine static FText ConvertFormatString(const char* FormatString) { \/\/ Convert the format string to FText return FText::FromString(StringCast&lt;TCHAR&gt;(FormatString).Get()); } \/\/ Convert a single argument to FFormatArgumentValue template &lt;typename T&gt; static FFormatArgumentValue ToFormatArgumentValue(const T&amp; value) { return FFormatArgumentValue(FText::FromString(ToFString(value))); } \/\/ Specialize for FString static FFormatArgumentValue ToFormatArgumentValue(const FString&amp; value) { return FFormatArgumentValue(FText::FromString(value)); } \/\/ Specialize for std::string static FFormatArgumentValue ToFormatArgumentValue(const std::string&amp; value) { return FFormatArgumentValue(FText::FromString(StringCast&lt;TCHAR&gt;(value.c_str(), value.size()).Get())); } \/** * Replace {} placeholders with {0}, {1}, etc * This is necessary because FText::Format does not support {} placeholders * @param FormatString The format string to convert * @return The converted format string *\/ static FString ConvertBracedFormatString(const FString&amp; FormatString) { FString ConvertedFormatString = FormatString; int32 PlaceholderIndex = 0; while (ConvertedFormatString.Contains(TEXT(&#34;{}&#34;))) { FStringBuilderBase Builder; Builder = ConvertedFormatString; Builder.ReplaceAt(ConvertedFormatString.Find(TEXT(&#34;{}&#34;)), 2, FString::Printf(TEXT(&#34;{%d}&#34;), PlaceholderIndex++)); ConvertedFormatString = Builder.ToString(); } return ConvertedFormatString; } \/\/ Format log message with provided arguments template &lt;typename... Args&gt; static FString FormatLogMessage(const char* FormatString, Args... args) { \/\/ Convert the format string to FText and replace placeholders FText FormatText = ConvertFormatString(FormatString); FText ConvertedFormatText = FText::FromString(ConvertBracedFormatString(FormatText.ToString())); \/\/ Create an array to hold the arguments for formatting TArray&lt;FFormatArgumentValue&gt; FormatArgs; (FormatArgs.Add(ToFormatArgumentValue(args)), ...); \/\/ Use FText to format the string with arguments FText FormattedText = FText::Format(ConvertedFormatText, FormatArgs); return FormattedText.ToString(); } public: \/\/~ Functions for simulating the spdlog API template &lt;typename... Args&gt; static void error(const char* FormatString, Args... args) { FString LogMessage = FormatLogMessage(FormatString, args...); UE_LOG(LogPiperLibrary, Error, TEXT(&#34;%s&#34;), *LogMessage); } template &lt;typename... Args&gt; static void warn(const char* FormatString, Args... args) { FString LogMessage = FormatLogMessage(FormatString, args...); UE_LOG(LogPiperLibrary, Warning, TEXT(&#34;%s&#34;), *LogMessage); } template &lt;typename... Args&gt; static void debug(const char* FormatString, Args... args) { FString LogMessage = FormatLogMessage(FormatString, args...); UE_LOG(LogPiperLibrary, Log, TEXT(&#34;%s&#34;), *LogMessage); } template &lt;typename... Args&gt; static void info(const char* FormatString, Args... args) { FString LogMessage = FormatLogMessage(FormatString, args...); UE_LOG(LogPiperLibrary, Log, TEXT(&#34;%s&#34;), *LogMessage); } \/\/ @formatter:off \/\/ Dummy functionality to simulate the spdlog API struct level { constexpr static bool debug = false; }; static bool should_log(bool) { return true; } \/\/ Add more functions as needed \/\/ @formatter:on }; Usage The class lets you use familiar spdlog calls like spdlog::info, spdlog::debug, spdlog::warn, and spdlog::error in your code. Behind the scenes, it routes everything through Unreal&rsquo;s logging system, so you can avoid adding the spdlog dependency to your project.\n","permalink":"https:\/\/georgy.dev\/posts\/spdlog-ue\/","summary":"<p>Need to use third-party libraries that depend on <code>spdlog<\/code> in your Unreal project? Here&rsquo;s how to simulate the <code>spdlog<\/code> API using Unreal&rsquo;s native <code>UE_LOG<\/code> system, to let you avoid adding extra dependencies while keeping your code clean.<\/p>\n<p>We&rsquo;ll create a logging utility class that matches the <code>spdlog<\/code> interface, handling type conversions to <code>FString<\/code> and formatting along the way.<\/p>\n<h3 id=\"implementation\">Implementation<\/h3>\n<p>Here&rsquo;s the code that bridges <code>spdlog<\/code> and Unreal&rsquo;s logging system:<\/p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-cpp\" data-lang=\"cpp\"><span class=\"line\"><span class=\"cl\"><span class=\"cp\">#pragma once\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cp\"><\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cp\">#include<\/span> <span class=\"cpf\">&#34;Containers\/UnrealString.h&#34;<\/span><span class=\"cp\">\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cp\">#include<\/span> <span class=\"cpf\">&#34;Internationalization\/Text.h&#34;<\/span><span class=\"cp\">\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cp\">#include<\/span> <span class=\"cpf\">&#34;Templates\/EnableIf.h&#34;<\/span><span class=\"cp\">\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cp\">#include<\/span> <span class=\"cpf\">&#34;Templates\/IsIntegral.h&#34;<\/span><span class=\"cp\">\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cp\">#include<\/span> <span class=\"cpf\">&#34;Templates\/IsFloatingPoint.h&#34;<\/span><span class=\"cp\">\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cp\">#include<\/span> <span class=\"cpf\">&#34;Logging\/LogMacros.h&#34;<\/span><span class=\"cp\">\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cp\">#include<\/span> <span class=\"cpf\">&lt;string&gt;<\/span><span class=\"cp\">\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cp\"><\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"n\">DEFINE_LOG_CATEGORY_STATIC<\/span><span class=\"p\">(<\/span><span class=\"n\">LogPiperLibrary<\/span><span class=\"p\">,<\/span> <span class=\"n\">Log<\/span><span class=\"p\">,<\/span> <span class=\"n\">All<\/span><span class=\"p\">);<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cm\">\/**\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cm\"> * Class to simulate the spdlog API in Unreal Engine\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cm\"> * spdlog is used in the original piper \n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cm\"> *\/<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"k\">class<\/span> <span class=\"nc\">spdlog<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"k\">private<\/span><span class=\"o\">:<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"c1\">\/\/ Template function for integral types\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span>\t<span class=\"k\">template<\/span> <span class=\"o\">&lt;<\/span><span class=\"k\">typename<\/span> <span class=\"n\">T<\/span><span class=\"p\">,<\/span> <span class=\"k\">typename<\/span> <span class=\"n\">TEnableIf<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">TIsIntegral<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">T<\/span><span class=\"o\">&gt;::<\/span><span class=\"n\">Value<\/span><span class=\"p\">,<\/span> <span class=\"kt\">bool<\/span><span class=\"o\">&gt;::<\/span><span class=\"n\">Type<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">&gt;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"k\">static<\/span> <span class=\"n\">FString<\/span> <span class=\"n\">ToFString<\/span><span class=\"p\">(<\/span><span class=\"n\">T<\/span> <span class=\"n\">value<\/span><span class=\"p\">)<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"c1\">\/\/ Most integral types won&#39;t be larger than signed 64-bit integer\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span>\t\t<span class=\"c1\">\/\/ TODO: Add support for larger integral types supported by UE (uint64)\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span>\t\t<span class=\"k\">return<\/span> <span class=\"n\">FString<\/span><span class=\"o\">::<\/span><span class=\"n\">Printf<\/span><span class=\"p\">(<\/span><span class=\"n\">TEXT<\/span><span class=\"p\">(<\/span><span class=\"s\">&#34;%lld&#34;<\/span><span class=\"p\">),<\/span> <span class=\"k\">static_cast<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">int64<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">(<\/span><span class=\"n\">value<\/span><span class=\"p\">));<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"c1\">\/\/ Template function for floating-point types\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span>\t<span class=\"k\">template<\/span> <span class=\"o\">&lt;<\/span><span class=\"k\">typename<\/span> <span class=\"n\">T<\/span><span class=\"p\">,<\/span> <span class=\"k\">typename<\/span> <span class=\"n\">TEnableIf<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">TIsFloatingPoint<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">T<\/span><span class=\"o\">&gt;::<\/span><span class=\"n\">Value<\/span><span class=\"p\">,<\/span> <span class=\"kt\">bool<\/span><span class=\"o\">&gt;::<\/span><span class=\"n\">Type<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">&gt;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"k\">static<\/span> <span class=\"n\">FString<\/span> <span class=\"n\">ToFString<\/span><span class=\"p\">(<\/span><span class=\"n\">T<\/span> <span class=\"n\">value<\/span><span class=\"p\">)<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"k\">return<\/span> <span class=\"n\">FString<\/span><span class=\"o\">::<\/span><span class=\"n\">Printf<\/span><span class=\"p\">(<\/span><span class=\"n\">TEXT<\/span><span class=\"p\">(<\/span><span class=\"s\">&#34;%f&#34;<\/span><span class=\"p\">),<\/span> <span class=\"k\">static_cast<\/span><span class=\"o\">&lt;<\/span><span class=\"kt\">double<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">(<\/span><span class=\"n\">value<\/span><span class=\"p\">));<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"c1\">\/\/ Specialize for FString\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span>\t<span class=\"k\">static<\/span> <span class=\"n\">FString<\/span> <span class=\"nf\">ToFString<\/span><span class=\"p\">(<\/span><span class=\"k\">const<\/span> <span class=\"n\">FString<\/span><span class=\"o\">&amp;<\/span> <span class=\"n\">Value<\/span><span class=\"p\">)<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"k\">return<\/span> <span class=\"n\">Value<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"c1\">\/\/ Specialize for std::string\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span>\t<span class=\"k\">static<\/span> <span class=\"n\">FString<\/span> <span class=\"nf\">ToFString<\/span><span class=\"p\">(<\/span><span class=\"k\">const<\/span> <span class=\"n\">std<\/span><span class=\"o\">::<\/span><span class=\"n\">string<\/span><span class=\"o\">&amp;<\/span> <span class=\"n\">Value<\/span><span class=\"p\">)<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"k\">return<\/span> <span class=\"n\">StringCast<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">TCHAR<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">(<\/span><span class=\"n\">Value<\/span><span class=\"p\">.<\/span><span class=\"n\">c_str<\/span><span class=\"p\">(),<\/span> <span class=\"n\">Value<\/span><span class=\"p\">.<\/span><span class=\"n\">size<\/span><span class=\"p\">()).<\/span><span class=\"n\">Get<\/span><span class=\"p\">();<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"c1\">\/\/ Convert the format string to UTF-16 for Unreal Engine\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span>\t<span class=\"k\">static<\/span> <span class=\"n\">FText<\/span> <span class=\"nf\">ConvertFormatString<\/span><span class=\"p\">(<\/span><span class=\"k\">const<\/span> <span class=\"kt\">char<\/span><span class=\"o\">*<\/span> <span class=\"n\">FormatString<\/span><span class=\"p\">)<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"c1\">\/\/ Convert the format string to FText\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span>\t\t<span class=\"k\">return<\/span> <span class=\"n\">FText<\/span><span class=\"o\">::<\/span><span class=\"n\">FromString<\/span><span class=\"p\">(<\/span><span class=\"n\">StringCast<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">TCHAR<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">(<\/span><span class=\"n\">FormatString<\/span><span class=\"p\">).<\/span><span class=\"n\">Get<\/span><span class=\"p\">());<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"c1\">\/\/ Convert a single argument to FFormatArgumentValue\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span>\t<span class=\"k\">template<\/span> <span class=\"o\">&lt;<\/span><span class=\"k\">typename<\/span> <span class=\"n\">T<\/span><span class=\"o\">&gt;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"k\">static<\/span> <span class=\"n\">FFormatArgumentValue<\/span> <span class=\"n\">ToFormatArgumentValue<\/span><span class=\"p\">(<\/span><span class=\"k\">const<\/span> <span class=\"n\">T<\/span><span class=\"o\">&amp;<\/span> <span class=\"n\">value<\/span><span class=\"p\">)<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"k\">return<\/span> <span class=\"nf\">FFormatArgumentValue<\/span><span class=\"p\">(<\/span><span class=\"n\">FText<\/span><span class=\"o\">::<\/span><span class=\"n\">FromString<\/span><span class=\"p\">(<\/span><span class=\"n\">ToFString<\/span><span class=\"p\">(<\/span><span class=\"n\">value<\/span><span class=\"p\">)));<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"c1\">\/\/ Specialize for FString\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span>\t<span class=\"k\">static<\/span> <span class=\"n\">FFormatArgumentValue<\/span> <span class=\"nf\">ToFormatArgumentValue<\/span><span class=\"p\">(<\/span><span class=\"k\">const<\/span> <span class=\"n\">FString<\/span><span class=\"o\">&amp;<\/span> <span class=\"n\">value<\/span><span class=\"p\">)<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"k\">return<\/span> <span class=\"n\">FFormatArgumentValue<\/span><span class=\"p\">(<\/span><span class=\"n\">FText<\/span><span class=\"o\">::<\/span><span class=\"n\">FromString<\/span><span class=\"p\">(<\/span><span class=\"n\">value<\/span><span class=\"p\">));<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"c1\">\/\/ Specialize for std::string\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span>\t<span class=\"k\">static<\/span> <span class=\"n\">FFormatArgumentValue<\/span> <span class=\"nf\">ToFormatArgumentValue<\/span><span class=\"p\">(<\/span><span class=\"k\">const<\/span> <span class=\"n\">std<\/span><span class=\"o\">::<\/span><span class=\"n\">string<\/span><span class=\"o\">&amp;<\/span> <span class=\"n\">value<\/span><span class=\"p\">)<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"k\">return<\/span> <span class=\"n\">FFormatArgumentValue<\/span><span class=\"p\">(<\/span><span class=\"n\">FText<\/span><span class=\"o\">::<\/span><span class=\"n\">FromString<\/span><span class=\"p\">(<\/span><span class=\"n\">StringCast<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">TCHAR<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">(<\/span><span class=\"n\">value<\/span><span class=\"p\">.<\/span><span class=\"n\">c_str<\/span><span class=\"p\">(),<\/span> <span class=\"n\">value<\/span><span class=\"p\">.<\/span><span class=\"n\">size<\/span><span class=\"p\">()).<\/span><span class=\"n\">Get<\/span><span class=\"p\">()));<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"cm\">\/**\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cm\">\t * Replace {} placeholders with {0}, {1}, etc\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cm\">\t * This is necessary because FText::Format does not support {} placeholders\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cm\">\t * @param FormatString The format string to convert\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cm\">\t * @return The converted format string\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cm\">\t *\/<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"k\">static<\/span> <span class=\"n\">FString<\/span> <span class=\"nf\">ConvertBracedFormatString<\/span><span class=\"p\">(<\/span><span class=\"k\">const<\/span> <span class=\"n\">FString<\/span><span class=\"o\">&amp;<\/span> <span class=\"n\">FormatString<\/span><span class=\"p\">)<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"n\">FString<\/span> <span class=\"n\">ConvertedFormatString<\/span> <span class=\"o\">=<\/span> <span class=\"n\">FormatString<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"n\">int32<\/span> <span class=\"n\">PlaceholderIndex<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"k\">while<\/span> <span class=\"p\">(<\/span><span class=\"n\">ConvertedFormatString<\/span><span class=\"p\">.<\/span><span class=\"n\">Contains<\/span><span class=\"p\">(<\/span><span class=\"n\">TEXT<\/span><span class=\"p\">(<\/span><span class=\"s\">&#34;{}&#34;<\/span><span class=\"p\">)))<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t<span class=\"n\">FStringBuilderBase<\/span> <span class=\"n\">Builder<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t<span class=\"n\">Builder<\/span> <span class=\"o\">=<\/span> <span class=\"n\">ConvertedFormatString<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t<span class=\"n\">Builder<\/span><span class=\"p\">.<\/span><span class=\"n\">ReplaceAt<\/span><span class=\"p\">(<\/span><span class=\"n\">ConvertedFormatString<\/span><span class=\"p\">.<\/span><span class=\"n\">Find<\/span><span class=\"p\">(<\/span><span class=\"n\">TEXT<\/span><span class=\"p\">(<\/span><span class=\"s\">&#34;{}&#34;<\/span><span class=\"p\">)),<\/span> <span class=\"mi\">2<\/span><span class=\"p\">,<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t                  <span class=\"n\">FString<\/span><span class=\"o\">::<\/span><span class=\"n\">Printf<\/span><span class=\"p\">(<\/span><span class=\"n\">TEXT<\/span><span class=\"p\">(<\/span><span class=\"s\">&#34;{%d}&#34;<\/span><span class=\"p\">),<\/span> <span class=\"n\">PlaceholderIndex<\/span><span class=\"o\">++<\/span><span class=\"p\">));<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t<span class=\"n\">ConvertedFormatString<\/span> <span class=\"o\">=<\/span> <span class=\"n\">Builder<\/span><span class=\"p\">.<\/span><span class=\"n\">ToString<\/span><span class=\"p\">();<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"k\">return<\/span> <span class=\"n\">ConvertedFormatString<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"c1\">\/\/ Format log message with provided arguments\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span>\t<span class=\"k\">template<\/span> <span class=\"o\">&lt;<\/span><span class=\"k\">typename<\/span><span class=\"p\">...<\/span> <span class=\"n\">Args<\/span><span class=\"o\">&gt;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"k\">static<\/span> <span class=\"n\">FString<\/span> <span class=\"n\">FormatLogMessage<\/span><span class=\"p\">(<\/span><span class=\"k\">const<\/span> <span class=\"kt\">char<\/span><span class=\"o\">*<\/span> <span class=\"n\">FormatString<\/span><span class=\"p\">,<\/span> <span class=\"n\">Args<\/span><span class=\"p\">...<\/span> <span class=\"n\">args<\/span><span class=\"p\">)<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"c1\">\/\/ Convert the format string to FText and replace placeholders\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span>\t\t<span class=\"n\">FText<\/span> <span class=\"n\">FormatText<\/span> <span class=\"o\">=<\/span> <span class=\"n\">ConvertFormatString<\/span><span class=\"p\">(<\/span><span class=\"n\">FormatString<\/span><span class=\"p\">);<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"n\">FText<\/span> <span class=\"n\">ConvertedFormatText<\/span> <span class=\"o\">=<\/span> <span class=\"n\">FText<\/span><span class=\"o\">::<\/span><span class=\"n\">FromString<\/span><span class=\"p\">(<\/span><span class=\"n\">ConvertBracedFormatString<\/span><span class=\"p\">(<\/span><span class=\"n\">FormatText<\/span><span class=\"p\">.<\/span><span class=\"n\">ToString<\/span><span class=\"p\">()));<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"c1\">\/\/ Create an array to hold the arguments for formatting\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span>\t\t<span class=\"n\">TArray<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">FFormatArgumentValue<\/span><span class=\"o\">&gt;<\/span> <span class=\"n\">FormatArgs<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"p\">(<\/span><span class=\"n\">FormatArgs<\/span><span class=\"p\">.<\/span><span class=\"n\">Add<\/span><span class=\"p\">(<\/span><span class=\"n\">ToFormatArgumentValue<\/span><span class=\"p\">(<\/span><span class=\"n\">args<\/span><span class=\"p\">)),<\/span> <span class=\"p\">...);<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"c1\">\/\/ Use FText to format the string with arguments\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span>\t\t<span class=\"n\">FText<\/span> <span class=\"n\">FormattedText<\/span> <span class=\"o\">=<\/span> <span class=\"n\">FText<\/span><span class=\"o\">::<\/span><span class=\"n\">Format<\/span><span class=\"p\">(<\/span><span class=\"n\">ConvertedFormatText<\/span><span class=\"p\">,<\/span> <span class=\"n\">FormatArgs<\/span><span class=\"p\">);<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"k\">return<\/span> <span class=\"n\">FormattedText<\/span><span class=\"p\">.<\/span><span class=\"n\">ToString<\/span><span class=\"p\">();<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"k\">public<\/span><span class=\"o\">:<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"c1\">\/\/~ Functions for simulating the spdlog API\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span>\t<span class=\"k\">template<\/span> <span class=\"o\">&lt;<\/span><span class=\"k\">typename<\/span><span class=\"p\">...<\/span> <span class=\"n\">Args<\/span><span class=\"o\">&gt;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"k\">static<\/span> <span class=\"kt\">void<\/span> <span class=\"n\">error<\/span><span class=\"p\">(<\/span><span class=\"k\">const<\/span> <span class=\"kt\">char<\/span><span class=\"o\">*<\/span> <span class=\"n\">FormatString<\/span><span class=\"p\">,<\/span> <span class=\"n\">Args<\/span><span class=\"p\">...<\/span> <span class=\"n\">args<\/span><span class=\"p\">)<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"n\">FString<\/span> <span class=\"n\">LogMessage<\/span> <span class=\"o\">=<\/span> <span class=\"n\">FormatLogMessage<\/span><span class=\"p\">(<\/span><span class=\"n\">FormatString<\/span><span class=\"p\">,<\/span> <span class=\"n\">args<\/span><span class=\"p\">...);<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"n\">UE_LOG<\/span><span class=\"p\">(<\/span><span class=\"n\">LogPiperLibrary<\/span><span class=\"p\">,<\/span> <span class=\"n\">Error<\/span><span class=\"p\">,<\/span> <span class=\"n\">TEXT<\/span><span class=\"p\">(<\/span><span class=\"s\">&#34;%s&#34;<\/span><span class=\"p\">),<\/span> <span class=\"o\">*<\/span><span class=\"n\">LogMessage<\/span><span class=\"p\">);<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"k\">template<\/span> <span class=\"o\">&lt;<\/span><span class=\"k\">typename<\/span><span class=\"p\">...<\/span> <span class=\"n\">Args<\/span><span class=\"o\">&gt;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"k\">static<\/span> <span class=\"kt\">void<\/span> <span class=\"n\">warn<\/span><span class=\"p\">(<\/span><span class=\"k\">const<\/span> <span class=\"kt\">char<\/span><span class=\"o\">*<\/span> <span class=\"n\">FormatString<\/span><span class=\"p\">,<\/span> <span class=\"n\">Args<\/span><span class=\"p\">...<\/span> <span class=\"n\">args<\/span><span class=\"p\">)<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"n\">FString<\/span> <span class=\"n\">LogMessage<\/span> <span class=\"o\">=<\/span> <span class=\"n\">FormatLogMessage<\/span><span class=\"p\">(<\/span><span class=\"n\">FormatString<\/span><span class=\"p\">,<\/span> <span class=\"n\">args<\/span><span class=\"p\">...);<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"n\">UE_LOG<\/span><span class=\"p\">(<\/span><span class=\"n\">LogPiperLibrary<\/span><span class=\"p\">,<\/span> <span class=\"n\">Warning<\/span><span class=\"p\">,<\/span> <span class=\"n\">TEXT<\/span><span class=\"p\">(<\/span><span class=\"s\">&#34;%s&#34;<\/span><span class=\"p\">),<\/span> <span class=\"o\">*<\/span><span class=\"n\">LogMessage<\/span><span class=\"p\">);<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"k\">template<\/span> <span class=\"o\">&lt;<\/span><span class=\"k\">typename<\/span><span class=\"p\">...<\/span> <span class=\"n\">Args<\/span><span class=\"o\">&gt;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"k\">static<\/span> <span class=\"kt\">void<\/span> <span class=\"n\">debug<\/span><span class=\"p\">(<\/span><span class=\"k\">const<\/span> <span class=\"kt\">char<\/span><span class=\"o\">*<\/span> <span class=\"n\">FormatString<\/span><span class=\"p\">,<\/span> <span class=\"n\">Args<\/span><span class=\"p\">...<\/span> <span class=\"n\">args<\/span><span class=\"p\">)<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"n\">FString<\/span> <span class=\"n\">LogMessage<\/span> <span class=\"o\">=<\/span> <span class=\"n\">FormatLogMessage<\/span><span class=\"p\">(<\/span><span class=\"n\">FormatString<\/span><span class=\"p\">,<\/span> <span class=\"n\">args<\/span><span class=\"p\">...);<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"n\">UE_LOG<\/span><span class=\"p\">(<\/span><span class=\"n\">LogPiperLibrary<\/span><span class=\"p\">,<\/span> <span class=\"n\">Log<\/span><span class=\"p\">,<\/span> <span class=\"n\">TEXT<\/span><span class=\"p\">(<\/span><span class=\"s\">&#34;%s&#34;<\/span><span class=\"p\">),<\/span> <span class=\"o\">*<\/span><span class=\"n\">LogMessage<\/span><span class=\"p\">);<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"k\">template<\/span> <span class=\"o\">&lt;<\/span><span class=\"k\">typename<\/span><span class=\"p\">...<\/span> <span class=\"n\">Args<\/span><span class=\"o\">&gt;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"k\">static<\/span> <span class=\"kt\">void<\/span> <span class=\"n\">info<\/span><span class=\"p\">(<\/span><span class=\"k\">const<\/span> <span class=\"kt\">char<\/span><span class=\"o\">*<\/span> <span class=\"n\">FormatString<\/span><span class=\"p\">,<\/span> <span class=\"n\">Args<\/span><span class=\"p\">...<\/span> <span class=\"n\">args<\/span><span class=\"p\">)<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"n\">FString<\/span> <span class=\"n\">LogMessage<\/span> <span class=\"o\">=<\/span> <span class=\"n\">FormatLogMessage<\/span><span class=\"p\">(<\/span><span class=\"n\">FormatString<\/span><span class=\"p\">,<\/span> <span class=\"n\">args<\/span><span class=\"p\">...);<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"n\">UE_LOG<\/span><span class=\"p\">(<\/span><span class=\"n\">LogPiperLibrary<\/span><span class=\"p\">,<\/span> <span class=\"n\">Log<\/span><span class=\"p\">,<\/span> <span class=\"n\">TEXT<\/span><span class=\"p\">(<\/span><span class=\"s\">&#34;%s&#34;<\/span><span class=\"p\">),<\/span> <span class=\"o\">*<\/span><span class=\"n\">LogMessage<\/span><span class=\"p\">);<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"c1\">\/\/ @formatter:off\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"c1\">\/\/ Dummy functionality to simulate the spdlog API\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span>\t<span class=\"k\">struct<\/span> <span class=\"nc\">level<\/span> <span class=\"p\">{<\/span> <span class=\"k\">constexpr<\/span> <span class=\"k\">static<\/span> <span class=\"kt\">bool<\/span> <span class=\"n\">debug<\/span> <span class=\"o\">=<\/span> <span class=\"nb\">false<\/span><span class=\"p\">;<\/span> <span class=\"p\">};<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"k\">static<\/span> <span class=\"kt\">bool<\/span> <span class=\"nf\">should_log<\/span><span class=\"p\">(<\/span><span class=\"kt\">bool<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span> <span class=\"k\">return<\/span> <span class=\"nb\">true<\/span><span class=\"p\">;<\/span> <span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"c1\">\/\/ Add more functions as needed\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"c1\">\/\/ @formatter:on\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span><span class=\"p\">};<\/span>\n<\/span><\/span><\/code><\/pre><\/div><h3 id=\"usage\">Usage<\/h3>\n<p>The class lets you use familiar <code>spdlog<\/code> calls like <code>spdlog::info<\/code>, <code>spdlog::debug<\/code>, <code>spdlog::warn<\/code>, and <code>spdlog::error<\/code> in your code. Behind the scenes, it routes everything through Unreal&rsquo;s logging system, so you can avoid adding the <code>spdlog<\/code> dependency to your project.<\/p>","title":"Simulating spdlog in Unreal Engine"},{"content":"As of UE 5.4, the Common UI button (UCommonButtonBase) still doesn&rsquo;t support direct focus settings. This is because UCommonButtonBase is derived from UUserWidget, which supports focusing, but doesn&rsquo;t direct the focus to the underlying button itself automatically. However, you can still set the focus on the button by performing a &ldquo;deep&rdquo; focus on the Slate button. Here&rsquo;s how you can achieve this:\n\/** * Sets the focus on the button * This function performs the &#34;deep&#34; focus on the Common UI button, which means that it will set the focus on the button itself * This is useful since UCommonButtonBase is derived from UUserWidget, which doesn&#39;t support focus when setting it directly *\/ UFUNCTION(BlueprintCallable, Category = &#34;mod.io|UI|Button&#34;, DisplayName = &#34;Set Button Focus (Common UI)&#34;) void SetCommonUIButtonFocus() { #if UE_VERSION_OLDER_THAN(5, 3, 0) if (bIsFocusable) #else if (IsFocusable()) #endif { if (TSharedPtr&lt;SButton&gt; SlateButton = GetSlateButton()) { if (SlateButton-&gt;SupportsKeyboardFocus()) { FSlateApplication::Get().SetKeyboardFocus(SlateButton, EFocusCause::Mouse); UE_LOG(LogTemp, Log, TEXT(&#34;Set focus on button &#39;%s&#39; (extended way)&#34;), *GetName()); } else { UE_LOG(LogTemp, Warning, TEXT(&#34;Trying to set focus on button &#39;%s&#39; but the button does not support keyboard focus&#34;), *GetName()); } } else { UE_LOG(LogTemp, Warning, TEXT(&#34;Trying to set focus on button &#39;%s&#39; but the slate button could not be found&#34;), *GetName()); } } else { UE_LOG(LogTemp, Warning, TEXT(&#34;Trying to set focus on button &#39;%s&#39; but the button is not focusable&#34;), *GetName()); } } \/** * Gets the Slate button widget * The button is highly encapsulated and this function tries to scan the widget tree to find the button * @return The Slate button widget if found, nullptr otherwise *\/ TSharedPtr&lt;SButton&gt; GetSlateButton() const { if (WidgetTree &amp;&amp; WidgetTree-&gt;RootWidget) { if (UButton* InternalButton = Cast&lt;UButton&gt;(WidgetTree-&gt;RootWidget)) { \/\/ UCommonButtonInternalBase::RebuildWidget() creates a SBox wrapper for the button if (TSharedPtr&lt;SBox&gt; BoxButtonWrapper =\tStaticCastSharedPtr&lt;SBox&gt;(TSharedPtr&lt;SWidget&gt;(InternalButton-&gt;GetCachedWidget()))) { if (BoxButtonWrapper-&gt;GetChildren() &amp;&amp; BoxButtonWrapper-&gt;GetChildren()-&gt;Num() &gt; 0) { if (TSharedPtr&lt;SButton&gt; InternalButtonSlate = StaticCastSharedPtr&lt;SButton&gt;(TSharedPtr&lt;SWidget&gt;(BoxButtonWrapper-&gt;GetChildren()-&gt;GetChildAt(0)))) { return InternalButtonSlate; } } } \/\/ UButton::RebuildWidget() returns the button directly else if (TSharedPtr&lt;SButton&gt; InternalButtonSlate = StaticCastSharedPtr&lt;SButton&gt;(InternalButton-&gt;GetCachedWidget())) { return InternalButtonSlate; } else { UE_LOG(LogTemp, Error, TEXT(&#34;Could not find the Slate button widget for button &#39;%s&#39;&#34;), *GetName()); } } } return nullptr; } ","permalink":"https:\/\/georgy.dev\/posts\/common-ui-button-focus\/","summary":"<p>As of UE 5.4, the Common UI button (<code>UCommonButtonBase<\/code>) still doesn&rsquo;t support direct focus settings. This is because <code>UCommonButtonBase<\/code> is derived from <code>UUserWidget<\/code>, which supports focusing, but doesn&rsquo;t direct the focus to the underlying button itself automatically. However, you can still set the focus on the button by performing a &ldquo;deep&rdquo; focus on the Slate button. Here&rsquo;s how you can achieve this:<\/p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-cpp\" data-lang=\"cpp\"><span class=\"line\"><span class=\"cl\"><span class=\"cm\">\/**\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cm\"> * Sets the focus on the button\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cm\"> * This function performs the &#34;deep&#34; focus on the Common UI button, which means that it will set the focus on the button itself\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cm\"> * This is useful since UCommonButtonBase is derived from UUserWidget, which doesn&#39;t support focus when setting it directly\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cm\"> *\/<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"n\">UFUNCTION<\/span><span class=\"p\">(<\/span><span class=\"n\">BlueprintCallable<\/span><span class=\"p\">,<\/span> <span class=\"n\">Category<\/span> <span class=\"o\">=<\/span> <span class=\"s\">&#34;mod.io|UI|Button&#34;<\/span><span class=\"p\">,<\/span> <span class=\"n\">DisplayName<\/span> <span class=\"o\">=<\/span> <span class=\"s\">&#34;Set Button Focus (Common UI)&#34;<\/span><span class=\"p\">)<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"kt\">void<\/span> <span class=\"n\">SetCommonUIButtonFocus<\/span><span class=\"p\">()<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cp\">#if UE_VERSION_OLDER_THAN(5, 3, 0)\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cp\"><\/span>\t<span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">bIsFocusable<\/span><span class=\"p\">)<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cp\">#else\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cp\"><\/span>\t<span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">IsFocusable<\/span><span class=\"p\">())<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cp\">#endif\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cp\"><\/span>\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">TSharedPtr<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">SButton<\/span><span class=\"o\">&gt;<\/span> <span class=\"n\">SlateButton<\/span> <span class=\"o\">=<\/span> <span class=\"n\">GetSlateButton<\/span><span class=\"p\">())<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t<span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">SlateButton<\/span><span class=\"o\">-&gt;<\/span><span class=\"n\">SupportsKeyboardFocus<\/span><span class=\"p\">())<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t\t<span class=\"n\">FSlateApplication<\/span><span class=\"o\">::<\/span><span class=\"n\">Get<\/span><span class=\"p\">().<\/span><span class=\"n\">SetKeyboardFocus<\/span><span class=\"p\">(<\/span><span class=\"n\">SlateButton<\/span><span class=\"p\">,<\/span> <span class=\"n\">EFocusCause<\/span><span class=\"o\">::<\/span><span class=\"n\">Mouse<\/span><span class=\"p\">);<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t\t<span class=\"n\">UE_LOG<\/span><span class=\"p\">(<\/span><span class=\"n\">LogTemp<\/span><span class=\"p\">,<\/span> <span class=\"n\">Log<\/span><span class=\"p\">,<\/span> <span class=\"n\">TEXT<\/span><span class=\"p\">(<\/span><span class=\"s\">&#34;Set focus on button &#39;%s&#39; (extended way)&#34;<\/span><span class=\"p\">),<\/span> <span class=\"o\">*<\/span><span class=\"n\">GetName<\/span><span class=\"p\">());<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t<span class=\"k\">else<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t\t<span class=\"n\">UE_LOG<\/span><span class=\"p\">(<\/span><span class=\"n\">LogTemp<\/span><span class=\"p\">,<\/span> <span class=\"n\">Warning<\/span><span class=\"p\">,<\/span> <span class=\"n\">TEXT<\/span><span class=\"p\">(<\/span><span class=\"s\">&#34;Trying to set focus on button &#39;%s&#39; but the button does not support keyboard focus&#34;<\/span><span class=\"p\">),<\/span> <span class=\"o\">*<\/span><span class=\"n\">GetName<\/span><span class=\"p\">());<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"k\">else<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t<span class=\"n\">UE_LOG<\/span><span class=\"p\">(<\/span><span class=\"n\">LogTemp<\/span><span class=\"p\">,<\/span> <span class=\"n\">Warning<\/span><span class=\"p\">,<\/span> <span class=\"n\">TEXT<\/span><span class=\"p\">(<\/span><span class=\"s\">&#34;Trying to set focus on button &#39;%s&#39; but the slate button could not be found&#34;<\/span><span class=\"p\">),<\/span> <span class=\"o\">*<\/span><span class=\"n\">GetName<\/span><span class=\"p\">());<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"k\">else<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"n\">UE_LOG<\/span><span class=\"p\">(<\/span><span class=\"n\">LogTemp<\/span><span class=\"p\">,<\/span> <span class=\"n\">Warning<\/span><span class=\"p\">,<\/span> <span class=\"n\">TEXT<\/span><span class=\"p\">(<\/span><span class=\"s\">&#34;Trying to set focus on button &#39;%s&#39; but the button is not focusable&#34;<\/span><span class=\"p\">),<\/span> <span class=\"o\">*<\/span><span class=\"n\">GetName<\/span><span class=\"p\">());<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cm\">\/**\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cm\"> * Gets the Slate button widget\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cm\"> * The button is highly encapsulated and this function tries to scan the widget tree to find the button\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cm\"> * @return The Slate button widget if found, nullptr otherwise\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"cm\"> *\/<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"n\">TSharedPtr<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">SButton<\/span><span class=\"o\">&gt;<\/span> <span class=\"n\">GetSlateButton<\/span><span class=\"p\">()<\/span> <span class=\"k\">const<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">WidgetTree<\/span> <span class=\"o\">&amp;&amp;<\/span> <span class=\"n\">WidgetTree<\/span><span class=\"o\">-&gt;<\/span><span class=\"n\">RootWidget<\/span><span class=\"p\">)<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">UButton<\/span><span class=\"o\">*<\/span> <span class=\"n\">InternalButton<\/span> <span class=\"o\">=<\/span> <span class=\"n\">Cast<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">UButton<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">(<\/span><span class=\"n\">WidgetTree<\/span><span class=\"o\">-&gt;<\/span><span class=\"n\">RootWidget<\/span><span class=\"p\">))<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t<span class=\"c1\">\/\/ UCommonButtonInternalBase::RebuildWidget() creates a SBox wrapper for the button\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span>\t\t\t<span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">TSharedPtr<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">SBox<\/span><span class=\"o\">&gt;<\/span> <span class=\"n\">BoxButtonWrapper<\/span> <span class=\"o\">=<\/span>\t<span class=\"n\">StaticCastSharedPtr<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">SBox<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">(<\/span><span class=\"n\">TSharedPtr<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">SWidget<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">(<\/span><span class=\"n\">InternalButton<\/span><span class=\"o\">-&gt;<\/span><span class=\"n\">GetCachedWidget<\/span><span class=\"p\">())))<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t\t<span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">BoxButtonWrapper<\/span><span class=\"o\">-&gt;<\/span><span class=\"n\">GetChildren<\/span><span class=\"p\">()<\/span> <span class=\"o\">&amp;&amp;<\/span> <span class=\"n\">BoxButtonWrapper<\/span><span class=\"o\">-&gt;<\/span><span class=\"n\">GetChildren<\/span><span class=\"p\">()<\/span><span class=\"o\">-&gt;<\/span><span class=\"n\">Num<\/span><span class=\"p\">()<\/span> <span class=\"o\">&gt;<\/span> <span class=\"mi\">0<\/span><span class=\"p\">)<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t\t\t<span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">TSharedPtr<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">SButton<\/span><span class=\"o\">&gt;<\/span> <span class=\"n\">InternalButtonSlate<\/span> <span class=\"o\">=<\/span> <span class=\"n\">StaticCastSharedPtr<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">SButton<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">(<\/span><span class=\"n\">TSharedPtr<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">SWidget<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">(<\/span><span class=\"n\">BoxButtonWrapper<\/span><span class=\"o\">-&gt;<\/span><span class=\"n\">GetChildren<\/span><span class=\"p\">()<\/span><span class=\"o\">-&gt;<\/span><span class=\"n\">GetChildAt<\/span><span class=\"p\">(<\/span><span class=\"mi\">0<\/span><span class=\"p\">))))<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t\t\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t\t\t\t<span class=\"k\">return<\/span> <span class=\"n\">InternalButtonSlate<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t\t\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t<span class=\"c1\">\/\/ UButton::RebuildWidget() returns the button directly\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span>\t\t\t<span class=\"k\">else<\/span> <span class=\"nf\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">TSharedPtr<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">SButton<\/span><span class=\"o\">&gt;<\/span> <span class=\"n\">InternalButtonSlate<\/span> <span class=\"o\">=<\/span> <span class=\"n\">StaticCastSharedPtr<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">SButton<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">(<\/span><span class=\"n\">InternalButton<\/span><span class=\"o\">-&gt;<\/span><span class=\"n\">GetCachedWidget<\/span><span class=\"p\">()))<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t\t<span class=\"k\">return<\/span> <span class=\"n\">InternalButtonSlate<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t<span class=\"k\">else<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t\t<span class=\"n\">UE_LOG<\/span><span class=\"p\">(<\/span><span class=\"n\">LogTemp<\/span><span class=\"p\">,<\/span> <span class=\"n\">Error<\/span><span class=\"p\">,<\/span> <span class=\"n\">TEXT<\/span><span class=\"p\">(<\/span><span class=\"s\">&#34;Could not find the Slate button widget for button &#39;%s&#39;&#34;<\/span><span class=\"p\">),<\/span> <span class=\"o\">*<\/span><span class=\"n\">GetName<\/span><span class=\"p\">());<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"k\">return<\/span> <span class=\"k\">nullptr<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"p\">}<\/span>\n<\/span><\/span><\/code><\/pre><\/div>","title":"How to focus a Common UI button (UCommonButtonBase) in Unreal Engine"},{"content":"In Unreal Engine, you can determine the currently focused widget in both Slate and UMG UI systems.\nSlate To get the currently focused Slate widget, you can use the following code snippet:\n\/\/ Instead of 0, you can pass the user index if you have multiple users TSharedPtr&lt;SWidget&gt; FocusedSlateWidget = FSlateApplication::Get().GetUserFocusedWidget(0); UMG Getting the currently focused UMG widget is a bit more involved, since there is no direct function to get it or the place where it is stored. However, you can iterate over all UMG widgets and compare their cached Slate widgets to the focused one. This approach is not recommended for performance-critical code, but it can be useful for debugging or testing purposes. Here is an example function that does this:\nUFUNCTION(BlueprintCallable) static UWidget* GetFocusedUMGWidget() { TSharedPtr&lt;SWidget&gt; FocusedSlateWidget = FSlateApplication::Get().GetUserFocusedWidget(0); if (!FocusedSlateWidget.IsValid()) { UE_LOG(LogTemp, Warning, TEXT(&#34;No focused Slate widget found&#34;)); return nullptr; } for (TObjectIterator&lt;UWidget&gt; Itr; Itr; ++Itr) { UWidget* CandidateUMGWidget = *Itr; if (CandidateUMGWidget-&gt;GetCachedWidget() == FocusedSlateWidget) { UE_LOG(LogTemp, Warning, TEXT(&#34;Focused UMG widget found: %s&#34;), *CandidateUMGWidget-&gt;GetName()); return CandidateUMGWidget; } } UE_LOG(LogTemp, Warning, TEXT(&#34;No focused UMG widget found&#34;)); return nullptr; } ","permalink":"https:\/\/georgy.dev\/posts\/get-focused-widget\/","summary":"<p>In Unreal Engine, you can determine the currently focused widget in both Slate and UMG UI systems.<\/p>\n<h1 id=\"slate\">Slate<\/h1>\n<p>To get the currently focused Slate widget, you can use the following code snippet:<\/p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-cpp\" data-lang=\"cpp\"><span class=\"line\"><span class=\"cl\"><span class=\"c1\">\/\/ Instead of 0, you can pass the user index if you have multiple users\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span><span class=\"n\">TSharedPtr<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">SWidget<\/span><span class=\"o\">&gt;<\/span> <span class=\"n\">FocusedSlateWidget<\/span> <span class=\"o\">=<\/span> <span class=\"n\">FSlateApplication<\/span><span class=\"o\">::<\/span><span class=\"n\">Get<\/span><span class=\"p\">().<\/span><span class=\"n\">GetUserFocusedWidget<\/span><span class=\"p\">(<\/span><span class=\"mi\">0<\/span><span class=\"p\">);<\/span>\n<\/span><\/span><\/code><\/pre><\/div><h1 id=\"umg\">UMG<\/h1>\n<p>Getting the currently focused UMG widget is a bit more involved, since there is no direct function to get it or the place where it is stored. However, you can iterate over all UMG widgets and compare their cached Slate widgets to the focused one. This approach is not recommended for performance-critical code, but it can be useful for debugging or testing purposes. Here is an example function that does this:<\/p>","title":"How to get the currently focused widget in Unreal Engine"},{"content":"If you want to execute some code when the scope is exited, you can use the following handy ON_SCOPE_EXIT macro, like this:\nint32 AnyFunction() { ON_SCOPE_EXIT { \/\/ This code will be executed when the scope is exited (after the return statement) }; \/\/ Do smth \/\/ Just return 100 for example return 100; } ","permalink":"https:\/\/georgy.dev\/posts\/on-scope-exit\/","summary":"<p>If you want to execute some code when the scope is exited, you can use the following handy <code>ON_SCOPE_EXIT<\/code> macro, like this:<\/p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-cpp\" data-lang=\"cpp\"><span class=\"line\"><span class=\"cl\"><span class=\"n\">int32<\/span> <span class=\"nf\">AnyFunction<\/span><span class=\"p\">()<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">    <span class=\"n\">ON_SCOPE_EXIT<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">    <span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">        <span class=\"c1\">\/\/ This code will be executed when the scope is exited (after the return statement)\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span>    <span class=\"p\">};<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\">    <span class=\"c1\">\/\/ Do smth\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span>    \n<\/span><\/span><span class=\"line\"><span class=\"cl\">    <span class=\"c1\">\/\/ Just return 100 for example\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span>    <span class=\"k\">return<\/span> <span class=\"mi\">100<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"p\">}<\/span>\n<\/span><\/span><\/code><\/pre><\/div>","title":"How to execute code on scope exit in Unreal Engine"},{"content":"Imagine you&rsquo;re in a situation where you need to save and restore a value. For instance, let&rsquo;s say you have a UMG widget that you want to hide temporarily, do some stuff, and then bring back its previous visibility state, all within a single function. The most straightforward approach might be to store the value in a temporary variable, modify it, and then revert it back, like this:\nESlateVisibility VisibilityState; const ESlateVisibility PreviousVisibility = VisibilityState; VisibilityState = ESlateVisibility::Hidden; \/\/ Do smth. VisibilityState is Hidden here \/\/ Restore the previous visibility state VisibilityState = PreviousVisibility; This method gets the job done, but it&rsquo;s not very scalable. What if you need to manage multiple values? What if there are several exit points in your function? What if multiple functions require saving and restoring the same value? Duplicating this rather verbose code everywhere is not ideal. We can do it better.\nWe can use TGuardValue class from Unreal Engine which is a very simple RAII class that saves the value in its constructor and restores it in its destructor. Here&rsquo;s how you can use it with the previous example:\nESlateVisibility VisibilityState; { TGuardValue&lt;ESlateVisibility&gt; Guard(VisibilityState, ESlateVisibility::Hidden); \/\/ Do smth. VisibilityState is Hidden here } \/\/ Once we exit the scope, Guard will restore the previous value of VisibilityState ","permalink":"https:\/\/georgy.dev\/posts\/guard-value\/","summary":"<p>Imagine you&rsquo;re in a situation where you need to save and restore a value. For instance, let&rsquo;s say you have a UMG widget that you want to hide temporarily, do some stuff, and then bring back its previous visibility state, all within a single function. The most straightforward approach might be to store the value in a temporary variable, modify it, and then revert it back, like this:<\/p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-cpp\" data-lang=\"cpp\"><span class=\"line\"><span class=\"cl\"><span class=\"n\">ESlateVisibility<\/span> <span class=\"n\">VisibilityState<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"k\">const<\/span> <span class=\"n\">ESlateVisibility<\/span> <span class=\"n\">PreviousVisibility<\/span> <span class=\"o\">=<\/span> <span class=\"n\">VisibilityState<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"n\">VisibilityState<\/span> <span class=\"o\">=<\/span> <span class=\"n\">ESlateVisibility<\/span><span class=\"o\">::<\/span><span class=\"n\">Hidden<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\">\/\/ Do smth. VisibilityState is Hidden here\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\">\/\/ Restore the previous visibility state\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span><span class=\"n\">VisibilityState<\/span> <span class=\"o\">=<\/span> <span class=\"n\">PreviousVisibility<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><\/code><\/pre><\/div><p>This method gets the job done, but it&rsquo;s not very scalable. What if you need to manage multiple values? What if there are several exit points in your function? What if multiple functions require saving and restoring the same value? Duplicating this rather verbose code everywhere is not ideal. We can do it better.<\/p>","title":"How to save and restore any value with RAII approach in Unreal Engine"},{"content":"This brief article addresses the question of how to cast one Slate widget to another in Unreal Engine. Imagine you have an SWidget widget wrapped in a shared pointer like this:\nTSharedPtr&lt;SWidget&gt; MyWidgetPtr; If you want to cast it, for example, to an SSpacer, your code might look like this:\nTSharedPtr&lt;SSpacer&gt; MySpacerPtr = StaticCastSharedPtr&lt;SSpacer&gt;(MyWidgetPtr); It&rsquo;s important to keep in mind that you should verify whether the shared pointer is valid before using it.\nThe same approach works for shared references:\nTSharedRef&lt;SWidget&gt; MyWidgetRef; TSharedRef&lt;SSpacer&gt; MySpacerRef = StaticCastSharedRef&lt;SSpacer&gt;(MyWidgetRef); Alternatively, you can perform the casts manually, directly using static_cast or dynamic_cast with the widget. However, be aware that this approach may not be as reliable as the one provided above, so use it with caution.\n","permalink":"https:\/\/georgy.dev\/posts\/cast-slate-widget-pointer\/","summary":"<p>This brief article addresses the question of how to cast one Slate widget to another in Unreal Engine. Imagine you have an <code>SWidget<\/code> widget wrapped in a shared pointer like this:<\/p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-cpp\" data-lang=\"cpp\"><span class=\"line\"><span class=\"cl\"><span class=\"n\">TSharedPtr<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">SWidget<\/span><span class=\"o\">&gt;<\/span> <span class=\"n\">MyWidgetPtr<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><\/code><\/pre><\/div><p>If you want to cast it, for example, to an <code>SSpacer<\/code>, your code might look like this:<\/p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-cpp\" data-lang=\"cpp\"><span class=\"line\"><span class=\"cl\"><span class=\"n\">TSharedPtr<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">SSpacer<\/span><span class=\"o\">&gt;<\/span> <span class=\"n\">MySpacerPtr<\/span> <span class=\"o\">=<\/span> <span class=\"n\">StaticCastSharedPtr<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">SSpacer<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">(<\/span><span class=\"n\">MyWidgetPtr<\/span><span class=\"p\">);<\/span>\n<\/span><\/span><\/code><\/pre><\/div><p>It&rsquo;s important to keep in mind that you should verify whether the shared pointer is valid before using it.<\/p>\n<p>The same approach works for shared references:<\/p>","title":"How to cast Slate widgets in Unreal Engine"},{"content":"In Unreal Engine, it is possible to set a delay for the function to execute after a single tick, which is sometimes useful when working with UMG\/Slate side of things where the widget properties are only available after a complete rebuild, such as obtaining the desired size of the widget&rsquo;s geometry, which is often not accessible right after initializing or constructing the widget. In such cases, you may want to implement a delay, as described in this article , but sometimes even a single tick delay is sufficient and can enhance the overall user experience, preventing visual hitches and abrupt interactions.\nC++ Example if (UWorld* World = GetWorld()) { World-&gt;GetTimerManager().SetTimerForNextTick(FTimerDelegate::CreateWeakLambda(this, [this]() { UE_LOG(LogTemp, Warning, TEXT(&#34;This will be printed in log after 1 tick&#34;)); })); } Blueprints Example P.S. As an alternative approach, you can also use a general Delay node but specify &ldquo;0.0&rdquo; seconds, which is equivalent to 1 tick.\nAlso, if you don&rsquo;t have access to the world or want to avoid this dependency, you can implement the delay using FTSTicker, a thread-safe ticker class. This is mainly used when there&rsquo;s uncertainty regarding the world, and you want a more generic solution.\nFTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([](float DeltaTime) { UE_LOG(LogTemp, Warning, TEXT(&#34;This will be printed in log after 1 tick&#34;)); \/\/ Returning false will remove the ticker from the ticker list return false; })); ","permalink":"https:\/\/georgy.dev\/posts\/settimerfornexttick\/","summary":"<p>In Unreal Engine, it is possible to set a delay for the function to execute after a single tick, which is sometimes useful when working with UMG\/Slate side of things where the widget properties are only available after a complete rebuild, such as obtaining the desired size of the widget&rsquo;s geometry, which is often not accessible right after initializing or constructing the widget. In such cases, you may want to implement a delay, as described in <a href=\"https:\/\/georgy.dev\/posts\/how-to-use-delays\/\" target=\"_blank\">\n    this article\n  <\/a>, but sometimes even a single tick delay is sufficient and can enhance the overall user experience, preventing visual hitches and abrupt interactions.<\/p>","title":"How to set a timer for the next tick in Unreal Engine"},{"content":"Chunk downloading is a technique used to retrieve large binary data from the server in separate parts, ensuring reliability and compatibility across different platforms. Unreal Engine&rsquo;s HTTP module has a limitation of 2GB for binary HTTP response content due to internal restrictions (specifically, TArray&lt;uint8&gt; uses the int32 size type, which has a maximum value of 2,147,483,647, approximately 2 GB in our case). To overcome this limitation, we can use the Range HTTP header supported by most servers, without requiring any file preparation or segmentation.\nAs a more generic example, you can take a look at the recent update of my plugin, Runtime Files Downloader . This plugin provides functionality to download files larger than 2GB to both memory and storage, with the only exception being Blueprints that don&rsquo;t support TArray64&lt;uint8&gt;, and thus, it only has the functionality to save &gt;2GB of data to storage.\nAnother implementation where chunk downloading is used is my recent plugin, Runtime Speech Recognizer , which includes a feature to download language model files larger than 2GB through the Unreal Engine Editor.\nBelow is a simplified code snippet for downloading files by chunks. In this example, the MaxChunkSize is set to 1048576 bytes (1MB), but you can modify it according to your specific requirements.\nSimplified code snippet Header file:\n#include &#34;CoreMinimal.h&#34; #include &#34;Http.h&#34; #include &#34;Templates\/SharedPointer.h&#34; #include &#34;Async\/Future.h&#34; UCLASS(BlueprintType) class YOURMODULE_API UDownloaderExample : public UObject { GENERATED_BODY() public: UFUNCTION(BlueprintCallable) void DownloadFunctionExample_BP(const FString&amp; URL, const FString&amp; SavePath); virtual TFuture&lt;TArray64&lt;uint8&gt;&gt; DownloadFile(const FString&amp; URL); virtual TFuture&lt;TArray64&lt;uint8&gt;&gt; DownloadFileByChunk(const FString&amp; URL, int64 ContentSize, int64 MaxChunkSize, FInt64Vector2 InternalContentRange = FInt64Vector2(), TArray64&lt;uint8&gt;&amp;&amp; InternalResultData = TArray64&lt;uint8&gt;()); protected: TWeakPtr&lt;IHttpRequest, ESPMode::ThreadSafe&gt; HttpRequestPtr; TFuture&lt;int64&gt; GetContentSize(const FString&amp; URL); }; Source file:\nvoid UDownloaderExample::DownloadFunctionExample_BP(const FString&amp; URL, const FString&amp; SavePath) { DownloadFile(URL).Next([this, SavePath](TArray64&lt;uint8&gt;&amp;&amp; ResultData) { UE_LOG(LogTemp, Display, TEXT(&#34;Downloaded %lld bytes&#34;), ResultData.Num()); FFileHelper::SaveArrayToFile(ResultData, *SavePath); }); } TFuture&lt;TArray64&lt;uint8&gt;&gt; UDownloaderExample::DownloadFile(const FString&amp; URL) { TSharedRef&lt;TPromise&lt;TArray64&lt;uint8&gt;&gt;&gt; PromisePtr = MakeShared&lt;TPromise&lt;TArray64&lt;uint8&gt;&gt;&gt;(); GetContentSize(URL).Next([WeakThis = MakeWeakObjectPtr(this), PromisePtr, URL](int64 ContentSize) { if (!WeakThis.IsValid()) { UE_LOG(LogTemp, Error, TEXT(&#34;Failed to download file: this is no longer valid&#34;)); PromisePtr-&gt;SetValue(TArray64&lt;uint8&gt;()); return; } if (ContentSize &lt;= 0) { UE_LOG(LogTemp, Error, TEXT(&#34;Failed to download file: content size is 0&#34;)); PromisePtr-&gt;SetValue(TArray64&lt;uint8&gt;()); return; } \/\/ 1048576 bytes = 1 MB. Just an arbitrary number to use as the chunk size WeakThis-&gt;DownloadFileByChunk(URL, ContentSize, 1048576).Next([PromisePtr](TArray64&lt;uint8&gt;&amp;&amp; ResultData) { PromisePtr-&gt;SetValue(MoveTemp(ResultData)); }); }); return PromisePtr-&gt;GetFuture(); } TFuture&lt;TArray64&lt;uint8&gt;&gt; UDownloaderExample::DownloadFileByChunk(const FString&amp; URL, int64 ContentSize, int64 MaxChunkSize, FInt64Vector2 InternalContentRange, TArray64&lt;uint8&gt;&amp;&amp; InternalResultData) { if (MaxChunkSize &lt;= 0) { UE_LOG(LogTemp, Error, TEXT(&#34;Failed to download chunk from %s: MaxChunkSize is 0&#34;), *URL); return MakeFulfilledPromise&lt;TArray64&lt;uint8&gt;&gt;(TArray64&lt;uint8&gt;()).GetFuture(); } \/\/ If the InternalResultData was not provided, initialize it to the size of the file if (InternalResultData.Num() &lt;= 0) { InternalResultData.SetNumUninitialized(ContentSize); } \/\/ If the InternalContentRange was not provided, set it to the first chunk of size MaxChunkSize if (InternalContentRange.X == 0 &amp;&amp; InternalContentRange.Y == 0) { InternalContentRange.Y = FMath::Min(ContentSize, MaxChunkSize) - 1; } TSharedRef&lt;IHttpRequest, ESPMode::ThreadSafe&gt; HttpRequestRef = FHttpModule::Get().CreateRequest(); HttpRequestRef-&gt;SetVerb(&#34;GET&#34;); HttpRequestRef-&gt;SetURL(URL); const FString RangeHeaderValue = FString::Format(TEXT(&#34;bytes={0}-{1}&#34;), {InternalContentRange.X, InternalContentRange.Y}); HttpRequestRef-&gt;SetHeader(TEXT(&#34;Range&#34;), RangeHeaderValue); TSharedRef&lt;TPromise&lt;TArray64&lt;uint8&gt;&gt;&gt; PromisePtr = MakeShared&lt;TPromise&lt;TArray64&lt;uint8&gt;&gt;&gt;(); HttpRequestRef-&gt;OnProcessRequestComplete().BindLambda([WeakThis = MakeWeakObjectPtr(this), PromisePtr, URL, ContentSize, MaxChunkSize, InternalContentRange, InternalResultData = MoveTemp(InternalResultData)](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) mutable { if (!WeakThis.IsValid()) { UE_LOG(LogTemp, Error, TEXT(&#34;Failed to download chunk from %s: this is no longer valid&#34;), *Request-&gt;GetURL()); PromisePtr-&gt;SetValue(TArray64&lt;uint8&gt;()); return; } if (!bSuccess || !Response.IsValid()) { UE_LOG(LogTemp, Error, TEXT(&#34;Failed to download chunk from %s: request failed&#34;), *Request-&gt;GetURL()); PromisePtr-&gt;SetValue(TArray64&lt;uint8&gt;()); return; } if (Response-&gt;GetContentSize() &lt;= 0) { UE_LOG(LogTemp, Error, TEXT(&#34;Failed to download chunk from %s: content size is 0&#34;), *Request-&gt;GetURL()); PromisePtr-&gt;SetValue(TArray64&lt;uint8&gt;()); return; } const int64 DataOffset = InternalContentRange.X; const TArray&lt;uint8&gt;&amp; ResponseContent = Response-&gt;GetContent(); \/\/ Calculate the overall size of the downloaded content in the result buffer const int64 OverallDownloadedSize = DataOffset + ResponseContent.Num(); \/\/ Check if some values are out of range { if (DataOffset &lt; 0 || DataOffset &gt;= InternalResultData.Num()) { UE_LOG(LogTemp, Error, TEXT(&#34;Failed to download chunk from %s: data offset is out of range&#34;), *Request-&gt;GetURL()); PromisePtr-&gt;SetValue(TArray64&lt;uint8&gt;()); return; } if (OverallDownloadedSize &gt; InternalResultData.Num()) { UE_LOG(LogTemp, Error, TEXT(&#34;Failed to download chunk from %s: overall downloaded size is out of range&#34;), *Request-&gt;GetURL()); PromisePtr-&gt;SetValue(TArray64&lt;uint8&gt;()); return; } } FMemory::Memcpy(InternalResultData.GetData() + DataOffset, ResponseContent.GetData(), ResponseContent.Num()); \/\/ Check if there&#39;s still more content to download if (OverallDownloadedSize &lt; ContentSize) { \/\/ Calculate how much more data needs to be downloaded in the next chunk const int64 BytesRemaining = ContentSize - OverallDownloadedSize; const int64 BytesToDownload = FMath::Min(BytesRemaining, MaxChunkSize); \/\/ Calculate the range of data to download in the next chunk const FInt64Vector2 NewContentRange = FInt64Vector2(OverallDownloadedSize, OverallDownloadedSize + BytesToDownload - 1); \/\/ Initiate the next download chunk WeakThis-&gt;DownloadFileByChunk(URL, ContentSize, MaxChunkSize, NewContentRange, MoveTemp(InternalResultData)).Next([PromisePtr](TArray64&lt;uint8&gt;&amp;&amp; ResultData) { PromisePtr-&gt;SetValue(MoveTemp(ResultData)); }); } else { \/\/ If there is no more content to download, then the download is complete PromisePtr-&gt;SetValue(MoveTemp(InternalResultData)); } }); HttpRequestRef-&gt;ProcessRequest(); HttpRequestPtr = HttpRequestRef; return PromisePtr-&gt;GetFuture(); } TFuture&lt;int64&gt; UDownloaderExample::GetContentSize(const FString&amp; URL) { TSharedPtr&lt;TPromise&lt;int64&gt;&gt; PromisePtr = MakeShared&lt;TPromise&lt;int64&gt;&gt;(); TSharedRef&lt;IHttpRequest, ESPMode::ThreadSafe&gt; HttpRequestRef = FHttpModule::Get().CreateRequest(); HttpRequestRef-&gt;SetVerb(&#34;HEAD&#34;); HttpRequestRef-&gt;SetURL(URL); HttpRequestRef-&gt;SetTimeout(5.0f); HttpRequestRef-&gt;OnProcessRequestComplete().BindLambda([PromisePtr](const FHttpRequestPtr&amp; Request, const FHttpResponsePtr&amp; Response, const bool bSucceeded) { const int64 ContentSize = FCString::Atoi64(Response.IsValid() ? *Response-&gt;GetHeader(&#34;Content-Length&#34;) : TEXT(&#34;0&#34;)); if (ContentSize &lt;= 0) { UE_LOG(LogTemp, Error, TEXT(&#34;Failed to get size: content size is 0&#34;)); PromisePtr-&gt;SetValue(0); return; } PromisePtr-&gt;SetValue(ContentSize); }); if (!HttpRequestRef-&gt;ProcessRequest()) { UE_LOG(LogTemp, Error, TEXT(&#34;Failed to get size: request failed&#34;)); return MakeFulfilledPromise&lt;int64&gt;(0).GetFuture(); } HttpRequestPtr = HttpRequestRef; return PromisePtr-&gt;GetFuture(); } ","permalink":"https:\/\/georgy.dev\/posts\/chunk-download\/","summary":"<p>Chunk downloading is a technique used to retrieve large binary data from the server in separate parts, ensuring reliability and compatibility across different platforms. Unreal Engine&rsquo;s HTTP module has a limitation of 2GB for binary HTTP response content due to internal restrictions (specifically, <code>TArray&lt;uint8&gt;<\/code> uses the <code>int32<\/code> size type, which has a maximum value of 2,147,483,647, approximately 2 GB in our case). To overcome this limitation, we can use the Range HTTP header supported by most servers, without requiring any file preparation or segmentation.<\/p>","title":"Efficient HTTP file download by chunks in Unreal Engine C++"},{"content":"You can save any UObject that is represented as an asset within a UPackage using this method:\nbool SaveToAsset(UObject* ObjectToSave) { UPackage* Package = ObjectToSave-&gt;GetPackage(); const FString PackageName = Package-&gt;GetName(); const FString PackageFileName = FPackageName::LongPackageNameToFilename(PackageName, FPackageName::GetAssetPackageExtension()); FSavePackageArgs SaveArgs; \/\/ This is specified just for example { SaveArgs.TopLevelFlags = RF_Public | RF_Standalone; SaveArgs.SaveFlags = SAVE_NoError; } const bool bSucceeded = UPackage::SavePackage(Package, nullptr, *PackageFileName, SaveArgs); if (!bSucceeded) { UE_LOG(LogTemp, Error, TEXT(&#34;Package &#39;%s&#39; wasn&#39;t saved!&#34;), *PackageName) return false; } UE_LOG(LogTemp, Warning, TEXT(&#34;Package &#39;%s&#39; was successfully saved&#34;), *PackageName) return true; } ","permalink":"https:\/\/georgy.dev\/posts\/save-uobject-to-package\/","summary":"<p>You can save any <code>UObject<\/code> that is represented as an asset within a <code>UPackage<\/code> using this method:<\/p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-cpp\" data-lang=\"cpp\"><span class=\"line\"><span class=\"cl\"><span class=\"kt\">bool<\/span> <span class=\"nf\">SaveToAsset<\/span><span class=\"p\">(<\/span><span class=\"n\">UObject<\/span><span class=\"o\">*<\/span> <span class=\"n\">ObjectToSave<\/span><span class=\"p\">)<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"n\">UPackage<\/span><span class=\"o\">*<\/span> <span class=\"n\">Package<\/span> <span class=\"o\">=<\/span> <span class=\"n\">ObjectToSave<\/span><span class=\"o\">-&gt;<\/span><span class=\"n\">GetPackage<\/span><span class=\"p\">();<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"k\">const<\/span> <span class=\"n\">FString<\/span> <span class=\"n\">PackageName<\/span> <span class=\"o\">=<\/span> <span class=\"n\">Package<\/span><span class=\"o\">-&gt;<\/span><span class=\"n\">GetName<\/span><span class=\"p\">();<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"k\">const<\/span> <span class=\"n\">FString<\/span> <span class=\"n\">PackageFileName<\/span> <span class=\"o\">=<\/span> <span class=\"n\">FPackageName<\/span><span class=\"o\">::<\/span><span class=\"n\">LongPackageNameToFilename<\/span><span class=\"p\">(<\/span><span class=\"n\">PackageName<\/span><span class=\"p\">,<\/span> <span class=\"n\">FPackageName<\/span><span class=\"o\">::<\/span><span class=\"n\">GetAssetPackageExtension<\/span><span class=\"p\">());<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"n\">FSavePackageArgs<\/span> <span class=\"n\">SaveArgs<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"c1\">\/\/ This is specified just for example\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span>\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"n\">SaveArgs<\/span><span class=\"p\">.<\/span><span class=\"n\">TopLevelFlags<\/span> <span class=\"o\">=<\/span> <span class=\"n\">RF_Public<\/span> <span class=\"o\">|<\/span> <span class=\"n\">RF_Standalone<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"n\">SaveArgs<\/span><span class=\"p\">.<\/span><span class=\"n\">SaveFlags<\/span> <span class=\"o\">=<\/span> <span class=\"n\">SAVE_NoError<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"k\">const<\/span> <span class=\"kt\">bool<\/span> <span class=\"n\">bSucceeded<\/span> <span class=\"o\">=<\/span> <span class=\"n\">UPackage<\/span><span class=\"o\">::<\/span><span class=\"n\">SavePackage<\/span><span class=\"p\">(<\/span><span class=\"n\">Package<\/span><span class=\"p\">,<\/span> <span class=\"k\">nullptr<\/span><span class=\"p\">,<\/span> <span class=\"o\">*<\/span><span class=\"n\">PackageFileName<\/span><span class=\"p\">,<\/span> <span class=\"n\">SaveArgs<\/span><span class=\"p\">);<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"o\">!<\/span><span class=\"n\">bSucceeded<\/span><span class=\"p\">)<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"n\">UE_LOG<\/span><span class=\"p\">(<\/span><span class=\"n\">LogTemp<\/span><span class=\"p\">,<\/span> <span class=\"n\">Error<\/span><span class=\"p\">,<\/span> <span class=\"n\">TEXT<\/span><span class=\"p\">(<\/span><span class=\"s\">&#34;Package &#39;%s&#39; wasn&#39;t saved!&#34;<\/span><span class=\"p\">),<\/span> <span class=\"o\">*<\/span><span class=\"n\">PackageName<\/span><span class=\"p\">)<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t\t<span class=\"k\">return<\/span> <span class=\"nb\">false<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"p\">}<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"n\">UE_LOG<\/span><span class=\"p\">(<\/span><span class=\"n\">LogTemp<\/span><span class=\"p\">,<\/span> <span class=\"n\">Warning<\/span><span class=\"p\">,<\/span> <span class=\"n\">TEXT<\/span><span class=\"p\">(<\/span><span class=\"s\">&#34;Package &#39;%s&#39; was successfully saved&#34;<\/span><span class=\"p\">),<\/span> <span class=\"o\">*<\/span><span class=\"n\">PackageName<\/span><span class=\"p\">)<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"k\">return<\/span> <span class=\"nb\">true<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"p\">}<\/span>\n<\/span><\/span><\/code><\/pre><\/div>","title":"How to save UObject that is represented as an asset in Unreal Engine"},{"content":"There is a std::variant class template in the standard library that is essentially a type-safe &nbsp;union. It is generally used when we are not sure in advance which object should be populated from our list of object types, so we assume that one of the specified objects must be there at a time.\nUnreal Engine has an alternative implementation called TVariant that works the same, except that all the types in the declaring template parameter pack must be unique.\nCode example This is an example where either FString or int64 may be filled.\nclass AnyClass { TVariant&lt;FString, int64&gt; StringOrInt64Holder; public: void FillString(const FString&amp; NewString) { StringOrInt64Holder.Set&lt;FString&gt;(NewString); } void FillInt64(int64 NewInt) { StringOrInt64Holder.Set&lt;int64&gt;(NewInt); } }; Note that it is not supported by Unreal Engine&rsquo;s reflection\/property system and hence in Blueprints, garbage collection, save game files, etc.\n","permalink":"https:\/\/georgy.dev\/posts\/variant\/","summary":"<p>There is a <code>std::variant<\/code> class template in the standard library that is essentially a type-safe <style>\n    .tooltip {\n        position: relative;\n        cursor: help;\n        border-bottom: 1px dotted var(--content);\n    }\n\n    .tooltip:hover::before {\n        opacity: 1;\n        visibility: visible;\n    }\n\n    .tooltip::before {\n        font-size: .925em;\n        color: var(--entry);\n        position: absolute;\n        opacity: 0;\n        visibility: hidden;\n        transition: visibility .25s ease-out, opacity .25s ease-in-out;\n        content: attr(data-tooltip);\n        z-index: 2;\n        width: 240px;\n        background: var(--primary);\n        border-radius: 5px;\n        padding: 5px 15px 5px 15px;\n        bottom: 100%;\n        left: 50%;\n        transform: translate(-50%);\n    }\n<\/style>\n\n&nbsp;<span class=\"tooltip\" data-tooltip=\"Union is class type that can hold only one of its data members at a time\">union<\/span>. It is generally used when we are not sure in advance which object should be populated from our list of object types, so we assume that one of the specified objects must be there at a time.<\/p>","title":"How to use variant in Unreal Engine"},{"content":"Unreal Engine has an alternative implementation of std::mutex called FCriticalSection which makes it possible for your data to be safely accessed from different threads, preventing race conditions. This takes the same approach, handling it with one thread locking until the other thread completes the necessary operations.\nThere are two ways to handle lock\/unlock logic.\nThe first is low-level, which is used to directly lock and unlock threads. Use with caution due to possible deadlocks . Just use FCriticalSection::Lock and FCriticalSection::Unlock functions where needed to follow this way.\nClick to see full example This is an example of thread-safe editing of a StringToEdit using the AnyClass::SetString function.\nclass AnyClass { FString StringToEdit; \/\/ Marked as mutable as it&#39;s often warranted by design mutable FCriticalSection DataGuard; public: void SetString(const FString&amp; NewString) { DataGuard.Lock(); StringToEdit = NewString; DataGuard.Unlock(); } }; The second is to use RAII technique , which is much safer since it automatically handles guarding within the specific scope. This is an alternative to std::lock_guard from the standard library. You can use FScopeLock to handle it.\nClick to see full example This is an example of thread-safe editing of a StringToEdit using the AnyClass::SetString function.\nclass AnyClass { FString StringToEdit; \/\/ Marked as mutable as it&#39;s often warranted by design mutable FCriticalSection DataGuard; public: void SetString(const FString&amp; NewString) { FScopeLock Lock(&amp;DataGuard); StringToEdit = NewString; } }; ","permalink":"https:\/\/georgy.dev\/posts\/mutex\/","summary":"<p>Unreal Engine has an alternative implementation of <code>std::mutex<\/code> called <code>FCriticalSection<\/code> which makes it possible for your data to be safely accessed from different threads, preventing race conditions. This takes the same approach, handling it with one thread locking until the other thread completes the necessary operations.<\/p>\n<p>There are two ways to handle lock\/unlock logic.<\/p>\n<p>The first is low-level, which is used to directly lock and unlock threads. Use with caution due to possible <a href=\"https:\/\/wiki.sei.cmu.edu\/confluence\/display\/cplusplus\/BB.&#43;Definitions#BB.Definitions-deadlock\" target=\"_blank\">\n    deadlocks\n  <\/a>. Just use <code>FCriticalSection::Lock<\/code> and <code>FCriticalSection::Unlock<\/code> functions where needed to follow this way.<\/p>","title":"How to use mutex in Unreal Engine"},{"content":"This article addresses the question of how to work with UObjects in a thread-safe way when dealing with workers, async tasks, thread pools, or whatever else using a non-game thread.\nOne critical issue to address is the handling of garbage collection. When passing an UObject, which is not set to root, directly to a background thread, there&rsquo;s a risk that the garbage collector may silently delete the passed UObject. Even frequent validity checks of the UObject (e.g. IsValidLowLevel()) on a background thread cannot guarantee the object&rsquo;s survival even moments after the check.\nExample of incorrect UObject usage on a background thread UObject* CreatedObject = NewObject&lt;UObject&gt;(); AsyncTask(ENamedThreads::AnyThread, [CreatedObject]() { \/\/ Usage of CreatedObject... \/\/ There is uncertainty about the survival of CreatedObject during execution }; Several methods can resolve this issue:\nPlace the created UObject into UPROPERTY\/TStrongObjectPtr If you&rsquo;re confident about the background thread&rsquo;s lifespan tied to the parent UObject&rsquo;s existence, placing the created UObject in a separate UPROPERTY\/TStrongObjectPtr or another strong object reference is a viable solution. However, using regular AsyncTask functionality doesn&rsquo;t inherently ensure the parent UObject&rsquo;s validity throughout the asynchronous scope, which is important to take in mind.\nIn more complex scenarios like employing FAsyncTask with a custom Task, manual implementation of CanAbandon() and Abandon() functions, and designing logic to abandon the task upon UObject destruction, you can be sure about safety in terms of GC. But this approach isn&rsquo;t as straightforward as working with regular AsyncThread. The code below demonstrates an example using regular AsyncTask, but it&rsquo;s important to note that, as mentioned earlier, it isn&rsquo;t entirely foolproof.\nExample of placing UObject in UPROPERTY \/\/ .h file UPROPERTY(Transient) UObject* CreatedObject; \/\/ or &#34;TStrongObjectPtr&lt;UObject&gt; CreatedObject;&#34; without UPROPERTY \/\/ .cpp file CreatedObject = NewObject&lt;UObject&gt;(); AsyncTask(ENamedThreads::AnyThread, [CreatedObject]() { \/\/ Usage of CreatedObject... \/\/ It&#39;s relatively safer to assume CreatedObject won&#39;t be garbage collected in this scope }; Add UObject to root To make sure that the object won&rsquo;t be garbage collected during the whole execution of your background scope, you can control the whole lifetime manually, by directly adding the needed object to the root, and removing it once it&rsquo;s ready for being destroyed by the garbage collector. In this case, you should pay attention to the object&rsquo;s lifetime manually, and avoid forgetting to remove it from root, because otherwise, the object will live forever.\nExample of creating UObject with adding to root UObject* CreatedObject = NewObject&lt;UObject&gt;(); CreatedObject-&gt;AddToRoot(); AsyncTask(ENamedThreads::AnyThread, [CreatedObject]() { \/\/ Usage of CreatedObject... \/\/ Ensures CreatedObject won&#39;t be garbage collected in this scope AsyncTask(ENamedThreads::GameThread, [CreatedObject]() { \/\/ If, during this stage, you don&#39;t want the object to avoid garbage collection, remove it from the root CreatedObject-&gt;RemoveFromRoot(); }; }; Create UObject on a background thread itself Another approach is to create the needed UObject directly on a background thread. When created in a non-game thread, the object automatically receives an Async object flag (in UObjectBase::AddObject), listed under GarbageCollectionKeepFlags, which prevents the object from being garbage collected (see MarkObjectsAsUnreachable in GarbageCollection.cpp, and IsNonGCObject in ReferenceChainSearch.cpp). And once the object is allowed to be garbage collected, you should clear the Async object flag.\nExample of creating UObject on background thread AsyncTask(ENamedThreads::AnyThread, [CreatedObject]() { UObject* CreatedObject = NewObject&lt;UObject&gt;(); \/\/ Not inherently necessary since the Async flag is automatically added once the object is created in the background thread, but just for explicitness CreatedObject-&gt;SetInternalFlags(EInternalObjectFlags::Async); \/\/ Usage of CreatedObject... \/\/ Ensures CreatedObject won&#39;t be garbage collected in this scope AsyncTask(ENamedThreads::GameThread, [CreatedObject]() { \/\/ If, during this stage, you don&#39;t want the object to avoid garbage collection, clear the Async flag CreatedObject-&gt;ClearInternalFlags(EInternalObjectFlags::Async); }; }; That is essentially a similar approach to when adding to root (-&gt;AddToRoot()) and removing from root (-&gt;RemoveFromRoot()), but the Async flag is better suited when the object is created asynchronously, which is the case when creating an object inside background threads\nFGCObjectScopeGuard FGCObjectScopeGuard can be used to protect a UObject from being garbage collected within a scope. However, it is not safe to use on a background thread, due to a potential race condition with the garbage collector. If the guard goes out of scope while the garbage collector is processing the object, the game may crash. This risk exists both in the editor and in packaged builds. For this reason, I do not recommend using FGCObjectScopeGuard in multithreaded contexts.\nExample of FGCObjectScopeGuard UObject* CreatedObject = NewObject&lt;UObject&gt;(); AsyncTask(ENamedThreads::AnyThread, [CreatedObject]() { FGCObjectScopeGuard CreatedObjectGuard(CreatedObject); \/\/ Usage of CreatedObject... \/\/ Ensures CreatedObject won&#39;t be garbage collected in this scope }; ","permalink":"https:\/\/georgy.dev\/posts\/avoid-gc-async\/","summary":"<p>This article addresses the question of how to work with <code>UObjects<\/code> in a thread-safe way when dealing with workers, async tasks, thread pools, or whatever else using a non-game thread.<\/p>\n<p>One critical issue to address is the handling of garbage collection. When passing an <code>UObject<\/code>, which is not set to root, directly to a background thread, there&rsquo;s a risk that the garbage collector may silently delete the passed <code>UObject<\/code>. Even frequent validity checks of the <code>UObject<\/code> (e.g. <code>IsValidLowLevel()<\/code>) on a background thread cannot guarantee the object&rsquo;s survival even moments after the check.<\/p>","title":"How to properly work with UObjects in background threads (GC)"},{"content":"By default, when a non-const reference is specified in a function, it will be treated by Blueprints as an output parameter:\nShow\/hide content UFUNCTION(BlueprintCallable) static void RemoveDots(FString&amp; String) { String.ReplaceInline(TEXT(&#34;.&#34;), TEXT(&#34; &#34;)); } There is a macro called UPARAM which is not widely used, but can control the behavior of parameters specified in UFUNCTIONs. In particular, it can change the behavior of passing non-const references by specifying a ref meta:\nShow\/hide content UFUNCTION(BlueprintCallable) static void RemoveDots(UPARAM(ref) FString&amp; String) { String.ReplaceInline(TEXT(&#34;.&#34;), TEXT(&#34; &#34;)); } This actually works similarly as specifying \u201cPass-by-reference\u201d in a Blueprint-only function ","permalink":"https:\/\/georgy.dev\/posts\/pass-by-ref-cpp-bp\/","summary":"<p>By default, when a <code>non-const reference<\/code> is specified in a function, it will be treated by Blueprints as an output parameter:<\/p>\n\n\n<p><details >\n  <summary markdown=\"span\">Show\/hide content<\/summary>\n  <div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-cpp\" data-lang=\"cpp\"><span class=\"line\"><span class=\"cl\"><span class=\"n\">UFUNCTION<\/span><span class=\"p\">(<\/span><span class=\"n\">BlueprintCallable<\/span><span class=\"p\">)<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"k\">static<\/span> <span class=\"kt\">void<\/span> <span class=\"n\">RemoveDots<\/span><span class=\"p\">(<\/span><span class=\"n\">FString<\/span><span class=\"o\">&amp;<\/span> <span class=\"n\">String<\/span><span class=\"p\">)<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"n\">String<\/span><span class=\"p\">.<\/span><span class=\"n\">ReplaceInline<\/span><span class=\"p\">(<\/span><span class=\"n\">TEXT<\/span><span class=\"p\">(<\/span><span class=\"s\">&#34;.&#34;<\/span><span class=\"p\">),<\/span> <span class=\"n\">TEXT<\/span><span class=\"p\">(<\/span><span class=\"s\">&#34; &#34;<\/span><span class=\"p\">));<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"p\">}<\/span>\n<\/span><\/span><\/code><\/pre><\/div><p><style>\n@media screen and (min-width: 769px) {\n     \n    .post-content input[type=\"checkbox\"]:checked ~ label > img {\n        transform: scale(1.6);\n        cursor: zoom-out;\n        position: relative;\n        z-index: 999;\n    }\n\n    .post-content img.zoomCheck {\n        transition: transform 0.15s ease;\n        z-index: 999;\n        cursor: zoom-in;\n    }\n}\n<\/style>\n\n\n    <img loading=\"lazy\" decoding=\"async\" src=\"\/images\/removedots_wrong.jpg\" \n        alt=\"Removing dots with incorrect reference behavior\"  \/><\/p>","title":"How to pass a variable by reference in C++ function called in Blueprints"},{"content":"Unreal Engine has a useful feature called AsyncTask that allows you to execute code asynchronously via Task Graph system. It functions by running specific code on a specified thread and is primarily used when the task is too heavy to be executed instantly without blocking the game thread.\nAsyncTask(ENamedThreads::AnyThread, []() { \/\/ This code will run asynchronously, without freezing the game thread }); You can also create nested calls to asynchronous tasks, for example:\nAsyncTask(ENamedThreads::AnyThread, []() { \/\/ This code will run asynchronously, without freezing the game thread AsyncTask(ENamedThreads::GameThread, []() { \/\/ This code will be executed on the game thread }); }); You can also combine this with delegates and execute your code asynchronously with Blueprints #include &#34;Async\/Async.h&#34; DECLARE_DYNAMIC_DELEGATE_OneParam(FAsyncDelegateExample, const TArray&lt;float&gt;&amp;, OutData); \/\/ Class, meta, etc UFUNCTION(BlueprintCallable) static void AddNumbersAsync(TArray&lt;float&gt; InData, const FAsyncDelegateExample&amp; Result) { AsyncTask(ENamedThreads::AnyThread, [InData = MoveTemp(InData), Result]() mutable { \/\/ Just for example for (float&amp; InDataElement : InData) { InDataElement += 10; } AsyncTask(ENamedThreads::GameThread, [OutData = MoveTemp(InData), Result]() mutable { Result.ExecuteIfBound(OutData); }); }); } Useful notes:\nGameThread has a dedicated thread, whereas AnyThread, AnyHiPriThread, etc., use available threads from the pool. There&rsquo;s Async template function that is more flexible in some cases compared to AsyncTask (e.g., you can choose to execute the task in the thread pool, or to execute a callback when the task is done by the returned TFuture object), but is more limited to choosing the thread for the Task Graph system. Async provides more flexibility compared to AsyncTask, especially with thread selection and callback handling. Starting from UE 5.0, consider using the new Task Graph system for improved performance and flow in gameplay tasks: Tasks System docs . ","permalink":"https:\/\/georgy.dev\/posts\/async-task\/","summary":"<p>Unreal Engine has a useful feature called <code>AsyncTask<\/code> that allows you to execute code asynchronously via Task Graph system. It functions by running specific code on a specified thread and is primarily used when the task is too heavy to be executed instantly without blocking the game thread.<\/p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-cpp\" data-lang=\"cpp\"><span class=\"line\"><span class=\"cl\"><span class=\"n\">AsyncTask<\/span><span class=\"p\">(<\/span><span class=\"n\">ENamedThreads<\/span><span class=\"o\">::<\/span><span class=\"n\">AnyThread<\/span><span class=\"p\">,<\/span> <span class=\"p\">[]()<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"c1\">\/\/ This code will run asynchronously, without freezing the game thread\n<\/span><\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"><\/span><span class=\"p\">});<\/span>\n<\/span><\/span><\/code><\/pre><\/div><p>You can also create nested calls to asynchronous tasks, for example:<\/p>","title":"How to use async task in Unreal Engine"},{"content":"Unreal Engine has different approaches to parallelize your code and one of them is ParallelFor. It is used when it is necessary to execute code multiple times with different number-based inputs, basically what a regular for loop does. But unlike a regular for loop that executes sequentially, ParallelFor runs on different threads, with possible different order of execution.\nLet&rsquo;s say our task is to simply sum the values from the TArray&lt;int32&gt; array. It&rsquo;s easiest to do this:\nconst TArray&lt;int32&gt; RequiredArray = {12, 43, 76, 23, 54}; int32 Sum = 0; for (int32 Index = 0; Index &lt; RequiredArray.Num(); ++Index) { Sum += RequiredArray[Index]; } If we have a small number of elements and the operation is not time-consuming (such as summing the elements of an array), then this is the best way to do it. But if we have a large number of elements and the operation is performance-critical, then we can use ParallelFor to speed up the process.\nconst TArray&lt;int32&gt; RequiredArray = {12, 43, 76, 23, 54, ...}; std::atomic&lt;int32&gt; Sum; \/\/ UE has a TAtomic template, but it is planned for complete deprecation ParallelFor(RequiredArray.Num(), [&amp;RequiredArray, &amp;Sum](int32 Index) { \/\/ Just for illustration purposes, as summing is too fast to be parallelized Sum += RequiredArray[Index]; }); or this:\nconst TArray&lt;int32&gt; RequiredArray = {12, 43, 76, 23, 54, ...}; int32 Sum; FCriticalSection Mutex; ParallelFor(RequiredArray.Num(), [&amp;RequiredArray, &amp;Sum, &amp;Mutex](int32 Index) { FScopeLock Lock(&amp;Mutex); Sum += RequiredArray[Index]; }); These two options achieve the same goal using different approaches. The first option achieves this through atomic access to the object for modification, the second through mutexes. This is necessary to avoid race conditions , in particular data races .\nOn the one hand, atomic access is usually faster, but it can only be used with trivial types. On the other hand, mutexes are more versatile and can be used for any types, but can have slowdown overhead.\nThere is an article covering more about how to use mutexes in Unreal Engine. ","permalink":"https:\/\/georgy.dev\/posts\/parallel-for-loop\/","summary":"<p>Unreal Engine has different approaches to parallelize your code and one of them is <code>ParallelFor<\/code>. It is used when it is necessary to execute code multiple times with different number-based inputs, basically what a regular for loop does. But unlike a regular for loop that executes sequentially, <code>ParallelFor<\/code> runs on different threads, with possible different order of execution.<\/p>\n<p>Let&rsquo;s say our task is to simply sum the values from the <code>TArray&lt;int32&gt;<\/code> array. It&rsquo;s easiest to do this:<\/p>","title":"How to create a multi-threaded for loop in Unreal Engine"},{"content":"Some developers are wondering about the C++ equivalent of the Delay node in Blueprints.\nThere are two ways to implement Delay that are very similar. Use the option that is more readable and convenient for you.\nLambas Using FTimerDelegate FTimerDelegate TimerDelegate; TimerDelegate.BindLambda([&amp;] { UE_LOG(LogTemp, Warning, TEXT(&#34;This text will appear in the console 3 seconds after execution&#34;)); }); FTimerHandle TimerHandle; GetWorld()-&gt;GetTimerManager().SetTimer(TimerHandle, TimerDelegate, 3, false); Using FTimerHandle only FTimerHandle TimerHandle; GetWorld()-&gt;GetTimerManager().SetTimer(TimerHandle, [&amp;]() { UE_LOG(LogTemp, Warning, TEXT(&#34;This text will appear in the console 3 seconds after execution&#34;)); }, 3, false); Separate functions The second way is to use a delay to execute another function.\nWithout input parameters FTimerHandle TimerHandle; GetWorld()-&gt;GetTimerManager().SetTimer(TimerHandle, this, &amp;UObject::MethodWithDelay, 3, false); With input parameters int32 ParameterToPass = 100; \/\/ You can use any supported variable type FTimerHandle TimerHandle; FTimerDelegate TimerDelegate = FTimerDelegate::CreateUObject(this, &amp;UObject::MethodWithDelay, ParameterToPass); GetWorld()-&gt;GetTimerManager().SetTimer(TimerHandle, TimerDelegate, 3, false); ","permalink":"https:\/\/georgy.dev\/posts\/how-to-use-delays\/","summary":"<p>Some developers are wondering about the C++ equivalent of the <code>Delay<\/code> node in Blueprints.<\/p>\n<p>There are two ways to implement <code>Delay<\/code> that are very similar. Use the option that is more readable and convenient for you.<\/p>\n<h1 id=\"lambas\">Lambas<\/h1>\n<h2 id=\"using-ftimerdelegate\">Using FTimerDelegate<\/h2>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-cpp\" data-lang=\"cpp\"><span class=\"line\"><span class=\"cl\"><span class=\"n\">FTimerDelegate<\/span> <span class=\"n\">TimerDelegate<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"n\">TimerDelegate<\/span><span class=\"p\">.<\/span><span class=\"n\">BindLambda<\/span><span class=\"p\">([<\/span><span class=\"o\">&amp;<\/span><span class=\"p\">]<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"n\">UE_LOG<\/span><span class=\"p\">(<\/span><span class=\"n\">LogTemp<\/span><span class=\"p\">,<\/span> <span class=\"n\">Warning<\/span><span class=\"p\">,<\/span> <span class=\"n\">TEXT<\/span><span class=\"p\">(<\/span><span class=\"s\">&#34;This text will appear in the console 3 seconds after execution&#34;<\/span><span class=\"p\">));<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"p\">});<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"n\">FTimerHandle<\/span> <span class=\"n\">TimerHandle<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"n\">GetWorld<\/span><span class=\"p\">()<\/span><span class=\"o\">-&gt;<\/span><span class=\"n\">GetTimerManager<\/span><span class=\"p\">().<\/span><span class=\"n\">SetTimer<\/span><span class=\"p\">(<\/span><span class=\"n\">TimerHandle<\/span><span class=\"p\">,<\/span> <span class=\"n\">TimerDelegate<\/span><span class=\"p\">,<\/span> <span class=\"mi\">3<\/span><span class=\"p\">,<\/span> <span class=\"nb\">false<\/span><span class=\"p\">);<\/span>\n<\/span><\/span><\/code><\/pre><\/div><h2 id=\"using-ftimerhandle-only\">Using FTimerHandle only<\/h2>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-cpp\" data-lang=\"cpp\"><span class=\"line\"><span class=\"cl\"><span class=\"n\">FTimerHandle<\/span> <span class=\"n\">TimerHandle<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"n\">GetWorld<\/span><span class=\"p\">()<\/span><span class=\"o\">-&gt;<\/span><span class=\"n\">GetTimerManager<\/span><span class=\"p\">().<\/span><span class=\"n\">SetTimer<\/span><span class=\"p\">(<\/span><span class=\"n\">TimerHandle<\/span><span class=\"p\">,<\/span> <span class=\"p\">[<\/span><span class=\"o\">&amp;<\/span><span class=\"p\">]()<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\">\t<span class=\"n\">UE_LOG<\/span><span class=\"p\">(<\/span><span class=\"n\">LogTemp<\/span><span class=\"p\">,<\/span> <span class=\"n\">Warning<\/span><span class=\"p\">,<\/span> <span class=\"n\">TEXT<\/span><span class=\"p\">(<\/span><span class=\"s\">&#34;This text will appear in the console 3 seconds after execution&#34;<\/span><span class=\"p\">));<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"p\">},<\/span> <span class=\"mi\">3<\/span><span class=\"p\">,<\/span> <span class=\"nb\">false<\/span><span class=\"p\">);<\/span>\n<\/span><\/span><\/code><\/pre><\/div><h1 id=\"separate-functions\">Separate functions<\/h1>\n<p>The second way is to use a delay to execute another function.<\/p>","title":"How to use delays in C++ in Unreal Engine"},{"content":"In this quick tutorial, I will show you how to completely disable Tonemapper without using PostProcess\nTo do this, just open the DefaultEngine.ini file (in the [PROJECT_NAME]\/Config folder) and add the following lines to [\/Script\/Engine.RendererSettings] section:\nr.TonemapperGamma = 0 r.TonemapperFilm = 0 r.Tonemapper.Quality = 0 r.ToneCurveAmount = 0 r.Mobile.TonemapperFilm = 0 r.MobileTonemapperUpscale = 0 r.EyeAdaptationQuality = 0 r.EyeAdaptation.ExponentialTransitionDistance = 0 You can also write the same to the engine&rsquo;s command line, but I recommend to directly add these lines to a file since it&rsquo;s more convenient and will always work.\n","permalink":"https:\/\/georgy.dev\/posts\/disable-tonemapper\/","summary":"<p>In this quick tutorial, I will show you how to completely disable Tonemapper without using PostProcess<\/p>\n<p>To do this, just open the <code>DefaultEngine.ini<\/code> file (in the <code>[PROJECT_NAME]\/Config<\/code> folder) and add the following lines to <code>[\/Script\/Engine.RendererSettings]<\/code> section:<\/p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-ini\" data-lang=\"ini\"><span class=\"line\"><span class=\"cl\"><span class=\"na\">r.TonemapperGamma<\/span> <span class=\"o\">=<\/span> <span class=\"s\">0<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"na\">r.TonemapperFilm<\/span> <span class=\"o\">=<\/span> <span class=\"s\">0<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"na\">r.Tonemapper.Quality<\/span> <span class=\"o\">=<\/span> <span class=\"s\">0<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"na\">r.ToneCurveAmount<\/span> <span class=\"o\">=<\/span> <span class=\"s\">0<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"na\">r.Mobile.TonemapperFilm<\/span> <span class=\"o\">=<\/span> <span class=\"s\">0<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"na\">r.MobileTonemapperUpscale<\/span> <span class=\"o\">=<\/span> <span class=\"s\">0<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"na\">r.EyeAdaptationQuality<\/span> <span class=\"o\">=<\/span> <span class=\"s\">0<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"na\">r.EyeAdaptation.ExponentialTransitionDistance<\/span> <span class=\"o\">=<\/span> <span class=\"s\">0<\/span>\n<\/span><\/span><\/code><\/pre><\/div><p>You can also write the same to the engine&rsquo;s command line, but I recommend to directly add these lines to a file since it&rsquo;s more convenient and will always work.<\/p>","title":"How to completely disable Tonemapper in Unreal Engine"},{"content":"There are multiple ways to reduce the build size. But on mobile platforms, this issue is most acute. I will show you how I managed to reduce the size of my Android game from 150MB to 50MB.\nProject settings There are many switches in the project settings that will help reduce the build size.\nThe first thing you can do is set Build Configuration to Shipping and enable For distribution:\nYou can exclude some unused editor assets and compress the required ones like this:\nYou can also enable sharing material shader code and use of shared material native libraries, which will slightly reduce the size:\nIf you are not using moving point lights, set &ldquo;Max Movable Point Lights&rdquo; to 0. This will slightly reduce the size of the shaders and hence the build size:\nDisabling plugins It is likely that your project contains unused plugins that are enabled by default. I recommend going to the Plugins tab and disable the ones you don&rsquo;t need. It depends on each project, but I came up with the following list:\nAndroid media player Android movie player Apple movie player CharacterAI OculusVR Windows Movie player WMF media player SteamVR Kdevelop Integration Cable component AVF media player Audio capture Archvis character Excluding packaged assets You can also remove unused assets that are packaged with your game but are never used. The following example is relevant for the Win64 platform.\nFirst, you need to figure out which specific assets you need to exclude. To do this, you will need to open the main.obb.png file as an archive located in the &ldquo;assets&rdquo; folder inside your game&rsquo;s pak file.\nTo unpack the .pak file and get to the main.obb.png file, open the console in the Engine\/Binaries\/Win64 folder and write the following command:\nUnrealPak.exe [PathToPakFile].pak -extract [PathToExtractPakFile] After that you can walk through the extracted folders\/files and select what is not used in your game.\nOnce you have decided which resources are not required in your game, you need to open (or create) the PakBlacklist-Shipping.txt file in the [PROJECT_NAME]\/Build\/Android directory and specify the folders and files that need to be excluded when building the project. In my case, I got the following list:\n..\/..\/..\/Engine\/Plugins\/Blendables\/ ..\/..\/..\/Engine\/Plugins\/Editor\/ ..\/..\/..\/Engine\/Plugins\/Enterprise\/ ..\/..\/..\/[PROJECT_NAME]\/AssetRegistry.bin ..\/..\/..\/Engine\/Content\/ArtTools\/ ..\/..\/..\/Engine\/Content\/EngineFonts\/Faces\/ ..\/..\/..\/Engine\/Content\/Internationalization\/icudt64l\/coll\/ ..\/..\/..\/Engine\/Content\/Internationalization\/icudt64l\/translit\/ ..\/..\/..\/Engine\/Content\/Internationalization\/icudt64l\/lang\/ ..\/..\/..\/Engine\/Content\/Internationalization\/icudt64l\/unit\/ ..\/..\/..\/Engine\/Content\/Internationalization\/icudt64l\/zone\/ ..\/..\/..\/Engine\/Content\/Internationalization\/icudt64l\/region\/ ..\/..\/..\/Engine\/Content\/Localization\/ ..\/..\/..\/Engine\/Content\/Maps\/ ..\/..\/..\/Engine\/Content\/MobileResources\/ ..\/..\/..\/Engine\/Content\/SlateDebug\/ ..\/..\/..\/Engine\/Content\/Tutorial\/ ..\/..\/..\/Engine\/Content\/Slate\/Fonts\/ ..\/..\/..\/Engine\/Content\/Slate\/Testing\/ ..\/..\/..\/Engine\/Content\/Slate\/Tutorials\/ ..\/..\/..\/Engine\/Content\/Slate\/Icons\/ ..\/..\/..\/Engine\/Content\/Slate\/CrashTracker\/ ..\/..\/..\/Engine\/Content\/Slate\/Old\/ ..\/..\/..\/Engine\/Content\/Slate\/Docking\/ ..\/..\/..\/Engine\/Content\/Slate\/Common\/ ..\/..\/..\/Engine\/Plugins\/Runtime\/LeapMotionController\/ ..\/..\/..\/Engine\/Plugins\/Editor\/SpeedTreeImporter\/Content\/SpeedTree9\/game_wind_noise.ubulk Disabling engine modules If you are still not satisfied with the build size, there is another option - to rebuild the engine with disabling unused but heavy modules, which are also included when the project is packaged.\nTo do this, download the engine sources, generate the project files and open the Unreal Engine solution through the IDE.\nThen open the &nbsp;UnrealGame.Target.cs file in the Source directory and set the following variables:\nbCompileAPEX = false; bCompileICU = false; bBuildDeveloperTools = true; bCompileRecast = false; bCompileSpeedTree = false; bCompileForSize = true; bCompileCEF3 = false; bCompileFreeType = false; bIWYU = true; bUsesSlate = false; bCompileChaos = false; bUseChaos = false; bUseChaosChecked = false; bUseChaosMemoryTracking = false; bCompilePhysX = false; bCompileNvCloth = false; bCompileRecast = false; bCompileNavmeshSegmentLinks = false; bCompileNavmeshClusterLinks = false; bUseDebugLiveCodingConsole = false; bUseLoggingInShipping = false; bLoggingToMemoryEnabled = false; bUseLauncherChecks = false; bEnforceIWYU = true; At the end, you only need to rebuild the engine and your project for the target platform, and that&rsquo;s all.\n","permalink":"https:\/\/georgy.dev\/posts\/reducing-mobile-build-size\/","summary":"<p>There are multiple ways to reduce the build size. But on mobile platforms, this issue is most acute. I will show you how I managed to reduce the size of my Android game from <strong>150MB to 50MB<\/strong>.<\/p>\n<h1 id=\"project-settings\">Project settings<\/h1>\n<p>There are many switches in the project settings that will help reduce the build size.<\/p>\n<p>The first thing you can do is set <code>Build Configuration<\/code> to <code>Shipping<\/code> and enable <code>For distribution<\/code>:<\/p>","title":"Reducing build size of Android or iOS game in Unreal Engine"},{"content":"This article may be helpful for those who are familiar with standard containers and would like to learn Unreal-specific ones, or for those who already have a knowledge of the containers but would like to explore them a little deeper.\nHere is information about the following containers: TArray, TSet, TMap, and a basic overview of the TMultiMap and TSortedMap.\nTArray Let&rsquo;s start with TArray. TArray is a dynamically sized array, similar to std::vector in the standard library.\nThe primary use of TArray is to provide an easy way to iterate over its elements using both traditional and range-based for loops, with good memory and performance.\nTraditional for loop example TArray&lt;int32&gt; IntArray{5, 10, 15, 20, 25}; for (int32 Index = 0; Index &lt; IntArray.Num(); ++Index) { UE_LOG(LogTemp, Log, TEXT(&#34;Traditional for loop iteration. Element value: %d&#34;), IntArray[Index]); } Range-based for loop example TArray&lt;int32&gt; IntArray{5, 10, 15, 20, 25}; for (int32 Element : IntArray) { UE_LOG(LogTemp, Log, TEXT(&#34;Range-based for loop iteration. Element value: %d&#34;), Element); } From a time complexity perspective, most TArray operations have different complexities: indexing is O(1), Add() is O(1) amortized, RemoveLast() is O(1), while operations like Insert() at arbitrary positions or Remove() by value are O(n).\nTSet TSet is a dynamic collection of unique elements stored as keys, similar to the std::unordered_set container in the standard library. It uses a hash table implementation to efficiently store and retrieve the elements, where the actual key is passed through a hash function to generate a numerical representation.\nTechnically, TSet encapsulates a TSparseArray, allowing hashed keys to be optimally stored as indexes.\nAs a result, this container is designed for instant (in constant time O(1)) element search.\nHowever, the elements in this container are likely to have an inconsistent order in memory. And accessing an element using a &nbsp;hash function each time makes it not the preferred option for iterating over all elements.\nExample of searching for an element by key, O(1) TSet&lt;FString&gt; StringSet{TEXT(&#34;First element&#34;), TEXT(&#34;Second element&#34;), TEXT(&#34;Third element&#34;)}; \/\/ The value will be true since there is a &#34;Second element&#34; element bool bFound = StringSet.Contains(TEXT(&#34;Second element&#34;)); TMap TMap is a dynamically sized associative array, similar to TSet, that stores elements as key-value pairs. It is implemented in a similar manner as std::unordered_map and has a structure similar to a hash table, but the main difference is that not keys are hashed, but key-value pairs (though only the key portion is used for hash calculation).\nTechnically, TMap encapsulates a TSet containing a TPair&lt;KeyType, ValueType&gt;.\nThus, all operations, except &nbsp;searching for an element by value, occur in constant time O(1).\nSince this container works &nbsp;similarly to TSet, it is not recommended to use it when iterating over all elements for the same reasons\nDiving even deeper: to find the keys inside TPair&lt;KeyType, ValueType&gt;, TMap uses &nbsp;key functions called TDefaultMapHashableKeyFuncs, which handles the matching and calling the hashing function (GetKeyHash) based on the input key. Internally, it has its own implementation of KeyInitType with replacement of the element type for search from TPair&lt;KeyType, ValueType&gt; to KeyType, which allows you to specify only the key instead of the full pair when searching in TMap. Essentially, you can think of TMap&lt;KeyType, ValueType&gt; as TSet&lt;TPair&lt;KeyType, ValueType&gt;, TDefaultMapHashableKeyFuncs&lt;KeyType, ValueType, false&gt;&gt;, with the final &ldquo;false&rdquo; indicating to disallow duplicate keys, which are not allowed in TMap by design.\nExample of searching for an element by key, O(1) TMap&lt;FString, float&gt; StringFloatMap { {TEXT(&#34;First element&#34;), 0.1f}, {TEXT(&#34;Second element&#34;), 0.2f}, {TEXT(&#34;Third element&#34;), 0.3f} }; \/\/ The value found will be 0.2 float FoundValue = *StringFloatMap.Find(TEXT(&#34;Second element&#34;)); Example of searching for an element by value, O(n) TMap&lt;FString, float&gt; StringFloatMap { {TEXT(&#34;First element&#34;), 0.1f}, {TEXT(&#34;Second element&#34;), 0.2f}, {TEXT(&#34;Third element&#34;), 0.3f} }; \/\/ The key found will be &#34;Third element&#34; FString FoundKey = *StringFloatMap.FindKey(0.3f); Similar containers to TMap TMultiMap operates similarly to TMap, but is closer to the standard std::unordered_multimap, as it allows multiple values to be assigned for each key. To some extent, you can think of TMultiMap&lt;KeyType, ValueType&gt; as TSet&lt;TPair&lt;KeyType, ValueType&gt;, TDefaultMapHashableKeyFuncs&lt;KeyType, ValueType, true&gt;&gt;, where the &ldquo;true&rdquo; argument in TDefaultMapHashableKeyFuncs specifies that duplicate keys are allowed.\nThere is an even more TMap-like container called TSortedMap, which is more efficient than TMap when the number of elements is small. It is similar to std::map in that finding has logarithmic complexity O(log n), since the algorithm uses a binary search, although it is not implemented as a red-black tree , but as a sorted array of key-value pairs (TArray&lt;TPair&lt;KeyType, ValueType&gt;&gt;) maintained in key order, so adding and removing takes linear time O(n).\nAdditional note Note that O(1) complexities for dynamic containers like TArray and TMap are typically amortized, meaning they&rsquo;re O(1) on average over many operations, but individual operations may occasionally be slower due to internal reorganization.\n","permalink":"https:\/\/georgy.dev\/posts\/intro-containers\/","summary":"<p>This article may be helpful for those who are familiar with standard containers and would like to learn Unreal-specific ones, or for those who already have a knowledge of the containers but would like to explore them a little deeper.<\/p>\n<p>Here is information about the following containers: <code>TArray<\/code>, <code>TSet<\/code>, <code>TMap<\/code>, and a basic overview of the <code>TMultiMap<\/code> and <code>TSortedMap<\/code>.<\/p>\n<h2 id=\"tarray\">TArray<\/h2>\n<p>Let&rsquo;s start with <code>TArray<\/code>. <code>TArray<\/code> is a dynamically sized array, similar to <code>std::vector<\/code> in the standard library.<\/p>","title":"Intro to Unreal Engine containers (TArray, TSet, TMap)"},{"content":"This is a short article on how to convert an enumerator to FString in Unreal Engine.\nBlueprints In Blueprints, it is easy to convert all supported enumerators to FString. Simply drag your enumerator to the required pin, which will automatically add the necessary conversion node.\nC++ In C++, the implementation varies based on the type of enum used.\nUnreal Reflection System If you want to use a solution that is compatible with the Unreal Reflection System and accordingly uses UENUM, there is an in-built method named UEnum::GetValueAsString.\nUnreal Reflection System-friendly solution Header file:\n\/\/ This is your enum UENUM() enum class EnumExample : uint8 { EnumValue1, EnumValue2 }; Source file:\n\/\/ This is how you obtain the string representation of the enumerator FString StringRepresentation = UEnum::GetValueAsString(EnumExample::EnumValue2); Pure C++ If you want to use a pure C++ solution, you should create a function to manually convert the enumerator.\nPure C++ solution Header file:\n\/\/ This is your enum enum class EnumExample : uint8 { EnumValue1, EnumValue2 }; class AnyClass { public: static constexpr FString EnumToString(EnumExample Enum); }; Source file:\nconstexpr FString AnyClass::EnumToString(EnumExample Enum) { switch (Enum) { case EnumExample::EnumValue1: return TEXT(&#34;EnumValue1&#34;); case EnumExample::EnumValue2: return TEXT(&#34;EnumValue2&#34;); default: return TEXT(&#34;Invalid&#34;); } } \/\/ This is how you obtain the string representation of the enumerator FString StringRepresentation = EnumToString(EnumExample::EnumValue2); ","permalink":"https:\/\/georgy.dev\/posts\/how-to-convert-enum-to-string\/","summary":"<p>This is a short article on how to convert an <code>enumerator<\/code> to <code>FString<\/code> in Unreal Engine.<\/p>\n<h1 id=\"blueprints\">Blueprints<\/h1>\n<p>In Blueprints, it is easy to convert all supported <code>enumerators<\/code> to <code>FString<\/code>. Simply drag your enumerator to the required pin, which will automatically add the necessary conversion node.<\/p>\n<p><style>\n@media screen and (min-width: 769px) {\n     \n    .post-content input[type=\"checkbox\"]:checked ~ label > img {\n        transform: scale(1.6);\n        cursor: zoom-out;\n        position: relative;\n        z-index: 999;\n    }\n\n    .post-content img.zoomCheck {\n        transition: transform 0.15s ease;\n        z-index: 999;\n        cursor: zoom-in;\n    }\n}\n<\/style>\n\n\n    \n    <input type=\"checkbox\" id=\"zoomCheck-1afb2\" hidden>\n    <label for=\"zoomCheck-1afb2\">\n        <img class=\"zoomCheck\" loading=\"lazy\" decoding=\"async\" \n            src=\"\/images\/enum-to-string-bp.jpg\" alt=\"Removing dots with incorrect reference behavior\" \n             \/>\n    <\/label><\/p>","title":"How to convert Enum to FString in Unreal Engine"},{"content":"Concept There are two main types of libraries: Static and Dynamic (also called Shared).\nA static library is statically linked to a program and is available at compile time. A dynamic (or shared) library, on the other hand, is dynamically linked and available at runtime. Static linking assumes that the library code is built into the final block of code, unlike dynamic linking. But the process of dynamic linking takes some time.\nIt follows that the static library is faster, but the dynamic library takes up less memory.\nDynamic library can also contain an import library which is intended to be statically linked to resolve external references to exported dynamic library functions.\nUnreal Engine has built-in tool called UBT (UnrealBuildTool) to manage the build process. All build related code must be in the appropriate build.cs file of your module (in your project or plugin source code).\nLibrary extensions and prefixes To briefly understand what your library suits for, you can use the following table.\nPlatform Static library Dynamic library Library prefix Windows .lib .dll n\/a UNIX-like (Linux, Android, iOS) .a .so lib OS X (Mac) .a .dylib lib Also keep in mind that the library depends on your architecture. For example, a library built for x32 architecture will not be compatible with x64 architecture\nBefore the implementation The examples below use a plugin named PLUGIN_EXAMPLE and a third party library named lib_[PLATFORM_NAME]_example (where [PLATFORM_NAME] is the name of the platform, e.g. win64, linux, ios, etc) located in the PLUGIN_EXAMPLE\/Source\/ThirdParty\/ directory.\nHow to integrate static library Let&rsquo;s say you have a Windows x64 static library named lib_win64_example.lib. To integrate this library, you need to add its path to PublicAdditionalLibraries in your build.cs:\nPublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, &#34;..&#34;, &#34;ThirdParty&#34;, &#34;lib_win64_example.lib&#34;)); Click to see full example using System; using System.IO; using UnrealBuildTool; public class PLUGIN_EXAMPLE : ModuleRules { public PLUGIN_EXAMPLE(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; PublicDependencyModuleNames.AddRange( new string[] { &#34;Core&#34; } ); PrivateDependencyModuleNames.AddRange( new string[] { &#34;CoreUObject&#34; } ); \/\/ Include the library PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, &#34;..&#34;, &#34;ThirdParty&#34;, &#34;lib_win64_example.lib&#34;)); } } You then need to include the header files in the file you want to call the library functions from. To remove the various warnings\/errors that might cause the build to fail, you can wrap your include with the THIRD_PARTY_INCLUDES_START and THIRD_PARTY_INCLUDES_END macros:\nTHIRD_PARTY_INCLUDES_START #include &#34;ThirdParty\/header_to_include.h&#34; THIRD_PARTY_INCLUDES_END How to integrate dynamic library If the dynamic library comes with import library If you have an import library, then you do not need to separately import the required functions from the dynamic library.\nLet&rsquo;s say you have a Windows x64 dynamic library named lib_win64_example.dll and a corresponding import library named lib_win64_import_example.lib.\nTo include your import library, you need to add its path to PublicAdditionalLibraries in your build.cs:\nPublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, &#34;..&#34;, &#34;ThirdParty&#34;, &#34;lib_win64_import_example.lib&#34;)); You then need to add your dynamic library as a runtime dependency to put it alongside the executable:\nRuntimeDependencies.Add(&#34;$(PluginDir)\/ThirdParty\/lib_win64_example.dll&#34;); Afterwards, on Windows, you will likely need to specify the DLL to be delay-loaded. This is done by adding the following line to your build.cs:\nPublicDelayLoadDLLs.Add(&#34;lib_win64_example.dll&#34;); Click to see full example using System; using System.IO; using UnrealBuildTool; public class PLUGIN_EXAMPLE : ModuleRules { public PLUGIN_EXAMPLE(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; PublicDependencyModuleNames.AddRange( new string[] { &#34;Core&#34; } ); PrivateDependencyModuleNames.AddRange( new string[] { &#34;CoreUObject&#34;, &#34;Projects&#34; } ); \/\/ Include the import library PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, &#34;..&#34;, &#34;ThirdParty&#34;, &#34;lib_win64_import_example.lib&#34;)); \/\/ Put the library along with the executable RuntimeDependencies.Add(&#34;$(PluginDir)\/ThirdParty\/lib_win64_example.dll&#34;); \/\/ Load library PublicDelayLoadDLLs.Add(&#34;lib_win64_example.dll&#34;); } } The next step is to specify when to load and free your dynamic library. In the following example, the lifetime of the library will match the lifetime of the module. That is, the library will be loaded on module startup and freed on module shutdown.\nYou need to open your module&rsquo;s interface file PLUGIN_EXAMPLE.h and add a field that will contain your dynamic library handler:\nvoid* DynamicLibExampleHandle; Click to see full example class FPLUGIN_EXAMPLEModule : public IModuleInterface { public: virtual void StartupModule() override; virtual void ShutdownModule() override; void* DynamicLibExampleHandle; }; Then in your PLUGIN_EXAMPLE.cpp add the following code to StartupModule to load your library:\nconst FString BasePluginDir = IPluginManager::Get().FindPlugin(&#34;PLUGIN_EXAMPLE&#34;)-&gt;GetBaseDir(); const FString LibExamplePath = FPaths::Combine(*BasePluginDir, TEXT(&#34;Source\/ThirdParty\/lib_win64_example.dll&#34;)); DynamicLibExampleHandle = FPlatformProcess::GetDllHandle(*LibExamplePath); if (DynamicLibExampleHandle != nullptr) { UE_LOG(LogTemp, Log, TEXT(&#34;lib_win64_example.dll loaded successfully!&#34;)); } else { UE_LOG(LogTemp, Fatal, TEXT(&#34;lib_win64_example.dll failed to load!&#34;)); } And the following code to ShutdownModule to unload your library:\nFPlatformProcess::FreeDllHandle(DynamicLibExampleHandle); DynamicLibExampleHandle = nullptr; Click to see full example void FPLUGIN_EXAMPLEModule::StartupModule() { const FString BasePluginDir = IPluginManager::Get().FindPlugin(&#34;PLUGIN_EXAMPLE&#34;)-&gt;GetBaseDir(); const FString LibExamplePath = FPaths::Combine(*BasePluginDir, TEXT(&#34;Source\/ThirdParty\/lib_win64_example.dll&#34;)); DynamicLibExampleHandle = FPlatformProcess::GetDllHandle(*LibExamplePath); if (DynamicLibExampleHandle != nullptr) { UE_LOG(LogTemp, Log, TEXT(&#34;lib_win64_example.dll loaded successfully!&#34;)); } else { UE_LOG(LogTemp, Fatal, TEXT(&#34;lib_win64_example.dll failed to load!&#34;)); } } void FPLUGIN_EXAMPLEModule::ShutdownModule() { FPlatformProcess::FreeDllHandle(DynamicLibExampleHandle); DynamicLibExampleHandle = nullptr; } You then need to include the header files in the file you want to call the library functions from. To remove the various warnings\/errors that might cause the build to fail, you can wrap your include with the THIRD_PARTY_INCLUDES_START and THIRD_PARTY_INCLUDES_END macros:\nTHIRD_PARTY_INCLUDES_START #include &#34;ThirdParty\/header_to_include.h&#34; THIRD_PARTY_INCLUDES_END If there is only a dynamic library If you only have a dynamic library, you will need to manually import the required functions.\nIn your build.cs file, you need to add your dynamic library as a runtime dependency to put it along with the executable:\nRuntimeDependencies.Add(&#34;$(PluginDir)\/ThirdParty\/lib_win64_example.dll&#34;); Click to see full example using UnrealBuildTool; public class PLUGIN_EXAMPLE : ModuleRules { public PLUGIN_EXAMPLE(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; PublicDependencyModuleNames.AddRange( new string[] { &#34;Core&#34; } ); PrivateDependencyModuleNames.AddRange( new string[] { &#34;CoreUObject&#34;, &#34;Projects&#34; } ); \/\/ Put the library along with the executable RuntimeDependencies.Add(&#34;$(PluginDir)\/ThirdParty\/lib_win64_example.dll&#34;); } } The next step is to specify when to load and free your dynamic library. In the following example, the lifetime of the library will match the lifetime of the module. That is, the library will be loaded on module startup and freed on module shutdown.\nYou need to open your module&rsquo;s interface file PLUGIN_EXAMPLE.h and add a field that will contain your dynamic library handler:\nvoid* DynamicLibExampleHandle; Click to see full example class FPLUGIN_EXAMPLEModule : public IModuleInterface { public: virtual void StartupModule() override; virtual void ShutdownModule() override; void* DynamicLibExampleHandle; }; Then in your PLUGIN_EXAMPLE.cpp add the following code to StartupModule to load your library:\nconst FString BasePluginDir = IPluginManager::Get().FindPlugin(&#34;PLUGIN_EXAMPLE&#34;)-&gt;GetBaseDir(); const FString LibExamplePath = FPaths::Combine(*BasePluginDir, TEXT(&#34;Source\/ThirdParty\/lib_win64_example.dll&#34;)); DynamicLibExampleHandle = FPlatformProcess::GetDllHandle(*LibExamplePath); if (DynamicLibExampleHandle != nullptr) { UE_LOG(LogTemp, Log, TEXT(&#34;lib_win64_example.dll loaded successfully!&#34;)); } else { UE_LOG(LogTemp, Fatal, TEXT(&#34;lib_win64_example.dll failed to load!&#34;)); } And the following code to ShutdownModule to unload your library:\nFPlatformProcess::FreeDllHandle(DynamicLibExampleHandle); DynamicLibExampleHandle = nullptr; Click to see full example void FPLUGIN_EXAMPLEModule::StartupModule() { const FString BasePluginDir = IPluginManager::Get().FindPlugin(&#34;PLUGIN_EXAMPLE&#34;)-&gt;GetBaseDir(); const FString LibExamplePath = FPaths::Combine(*BasePluginDir, TEXT(&#34;Source\/ThirdParty\/lib_win64_example.dll&#34;)); DynamicLibExampleHandle = FPlatformProcess::GetDllHandle(*LibExamplePath); if (DynamicLibExampleHandle != nullptr) { UE_LOG(LogTemp, Log, TEXT(&#34;lib_win64_example.dll loaded successfully!&#34;)); } else { UE_LOG(LogTemp, Fatal, TEXT(&#34;lib_win64_example.dll failed to load!&#34;)); } } void FPLUGIN_EXAMPLEModule::ShutdownModule() { FPlatformProcess::FreeDllHandle(DynamicLibExampleHandle); DynamicLibExampleHandle = nullptr; } You then need to include the header files in the file you want to call the library functions from. To remove the various warnings\/errors that might cause the build to fail, you can wrap your include with the THIRD_PARTY_INCLUDES_START and THIRD_PARTY_INCLUDES_END macros:\nTHIRD_PARTY_INCLUDES_START #include &#34;ThirdParty\/header_to_include.h&#34; THIRD_PARTY_INCLUDES_END To properly get the required functions to call from your dynamic library, you need to use FPlatformProcess::GetDllExport. To make it easier to get functions, you can create the following macro:\nCALL_LIBEXAMPLE_FUNC(FunctionName) \\ []() { \\ using FunctionType = decltype(&amp;FunctionName); \\ const FName ModuleName = FName(TEXT(&#34;PLUGIN_EXAMPLE&#34;)); \\ const FPLUGIN_EXAMPLEModule&amp; CurrentModule = FModuleManager::GetModuleChecked&lt;FPLUGIN_EXAMPLEModule&gt;(ModuleName); \\ static FunctionType FunctionPtr = (FunctionType)(FPlatformProcess::GetDllExport(CurrentModule.DynamicLibExampleHandle, TEXT(#FunctionName))); \\ return FunctionPtr; \\ }() And then use it as follows:\n\/\/ This is an example of an argument to pass to a function bool ArgumentToPass = true; \/\/ Let&#39;s say there is a function &#34;getInvertedBool(bool)&#34; in the dynamic library CALL_LIBEXAMPLE_FUNC(getInvertedBool)(ArgumentToPass); Support different platforms Here is the example of static library integration with support for Win64, Linux and Mac platforms. You can easily modify it to suit your needs to support the required platforms.\nusing System; using System.IO; using UnrealBuildTool; public class PLUGIN_EXAMPLE : ModuleRules { private string GetExampleLibraryPath(ReadOnlyTargetRules Target) { if (Target.Platform == UnrealTargetPlatform.Win64) { return Path.Combine(ModuleDirectory, &#34;..&#34;, &#34;ThirdParty&#34;, &#34;lib_win64_example.lib&#34;); } if (Target.Platform == UnrealTargetPlatform.Linux) { return Path.Combine(ModuleDirectory, &#34;..&#34;, &#34;ThirdParty&#34;, &#34;lib_linux_example.a&#34;); } if (Target.Platform == UnrealTargetPlatform.Mac) { return Path.Combine(ModuleDirectory, &#34;..&#34;, &#34;ThirdParty&#34;, &#34;lib_mac_example.a&#34;); } return null; } public PLUGIN_EXAMPLE(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; PrivateDependencyModuleNames.AddRange( new string[] { &#34;CoreUObject&#34;, &#34;Engine&#34;, &#34;Core&#34; } ); string LibraryPath = GetExampleLibraryPath(Target); if (LibraryPath != null) { PublicAdditionalLibraries.Add(LibraryPath); } } } ","permalink":"https:\/\/georgy.dev\/posts\/third-party-integration\/","summary":"<h2 id=\"concept\">Concept<\/h2>\n<p>There are two main types of libraries: <strong>Static<\/strong> and <strong>Dynamic<\/strong> (also called <strong>Shared<\/strong>).<\/p>\n<ul>\n<li>A static library is statically linked to a program and is available at compile time.<\/li>\n<li>A dynamic (or shared) library, on the other hand, is dynamically linked and available at runtime.<\/li>\n<\/ul>\n<p>Static linking assumes that the library code is built into the final block of code, unlike dynamic linking. But the process of dynamic linking takes some time.<br>\nIt follows that the static library is faster, but the dynamic library takes up less memory.<\/p>","title":"How to integrate third-party library into Unreal Engine"},{"content":"Hey! This is my first post with the idea of this blog. I previously had a blog at https:\/\/unreal.blog , but I decided to rethink this and create a new one.\nHere I will primarily write about technical stuff related to C++ and Unreal Engine.\nI&rsquo;ll be writing on both simple and complex topics.\nI also plan to write some more general, non-technical posts, but this won&rsquo;t be posted often.\nStay tuned :)\n","permalink":"https:\/\/georgy.dev\/posts\/my-first-post\/","summary":"<p>Hey! This is my first post with the idea of this blog. I previously had a blog at <a href=\"https:\/\/unreal.blog\" target=\"_blank\">\n    https:\/\/unreal.blog\n  <\/a> , but I decided to rethink this and create a new one.<\/p>\n<p>Here I will primarily write about technical stuff related to C++ and Unreal Engine.<\/p>\n<p>I&rsquo;ll be writing on both simple and complex topics.<\/p>\n<p>I also plan to write some more general, non-technical posts, but this won&rsquo;t be posted often.<\/p>","title":"My first post"},{"content":"","permalink":"https:\/\/georgy.dev\/discord\/","summary":"","title":""},{"content":"","permalink":"https:\/\/georgy.dev\/fab\/","summary":"","title":""},{"content":"","permalink":"https:\/\/georgy.dev\/github\/","summary":"","title":""},{"content":"","permalink":"https:\/\/georgy.dev\/linkedin\/","summary":"","title":""},{"content":"","permalink":"https:\/\/georgy.dev\/marketplace\/","summary":"","title":""},{"content":"","permalink":"https:\/\/georgy.dev\/runtime-ai-chatbot-integrator-demo-source\/","summary":"","title":""},{"content":"","permalink":"https:\/\/georgy.dev\/runtime-ai-chatbot-integrator-demo-windows\/","summary":"","title":""},{"content":"","permalink":"https:\/\/georgy.dev\/runtime-local-llm-demo-windows\/","summary":"","title":""},{"content":"","permalink":"https:\/\/georgy.dev\/runtime-metahuman-lip-sync-demo-source\/","summary":"","title":""},{"content":"","permalink":"https:\/\/georgy.dev\/runtime-metahuman-lip-sync-demo-windows\/","summary":"","title":""},{"content":"","permalink":"https:\/\/georgy.dev\/runtime-metahuman-lip-sync-sts-demo-source\/","summary":"","title":""},{"content":"","permalink":"https:\/\/georgy.dev\/runtime-metahuman-lip-sync-sts-demo-windows\/","summary":"","title":""},{"content":"","permalink":"https:\/\/georgy.dev\/runtime-speech-recognizer-demo-windows\/","summary":"","title":""},{"content":"","permalink":"https:\/\/georgy.dev\/runtime-text-to-speech-demo-windows\/","summary":"","title":""},{"content":"","permalink":"https:\/\/georgy.dev\/telegram\/","summary":"","title":""}]