Jekyll2023-01-26T16:37:19+00:00https://snorristurluson.github.io//feed.xmlSnorri SturlusonVeteran game developer (Arctic Theory, CCP, EA, Atari Games)Unreal Tests In Rider2023-01-26T00:00:00+00:002023-01-26T00:00:00+00:00https://snorristurluson.github.io//UnrealTestsInRider<p>Unreal Engine has some decent support for automated testing, and my IDE of choice,
<a href="https://www.jetbrains.com/rider/">JetBrains Rider</a>
allows you to run the tests from the Unit Tests tab.</p>
<p>Unreal Engine is a bit of a beast, so turnaround time for iterating on code is somewhat long for an ideal
TDD cycle, but it still might be an acceptable way to work on some core components.</p>
<p>Let’s take a look at some tests for this incredibly simple component (see
<a href="https://github.com/snorristurluson/UnrealAutomatedTests">here</a> for accompanying source):</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">UCLASS</span><span class="p">(</span><span class="n">ClassGroup</span><span class="o">=</span><span class="p">(</span><span class="n">Custom</span><span class="p">),</span> <span class="n">meta</span><span class="o">=</span><span class="p">(</span><span class="n">BlueprintSpawnableComponent</span><span class="p">))</span>
<span class="k">class</span> <span class="nc">UNREALAUTOMATEDTESTS_API</span> <span class="n">USimpleComponent</span> <span class="o">:</span> <span class="k">public</span> <span class="n">UActorComponent</span>
<span class="p">{</span>
<span class="n">GENERATED_BODY</span><span class="p">()</span>
<span class="nl">public:</span>
<span class="kt">bool</span> <span class="n">bDidSomething</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
<span class="kt">void</span> <span class="n">DoSomething</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">bDidSomething</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">};</span>
</code></pre></div></div>
<p>When starting out, I like to make sure that I can create a test with the test framework:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">constexpr</span> <span class="n">int32</span> <span class="n">Flags</span> <span class="o">=</span> <span class="n">EAutomationTestFlags</span><span class="o">::</span><span class="n">EditorContext</span> <span class="o">|</span> <span class="n">EAutomationTestFlags</span><span class="o">::</span><span class="n">EngineFilter</span><span class="p">;</span>
<span class="n">IMPLEMENT_SIMPLE_AUTOMATION_TEST</span><span class="p">(</span><span class="n">DummyTest</span><span class="p">,</span> <span class="s">"Tests.DummyTest"</span><span class="p">,</span> <span class="n">Flags</span><span class="p">);</span>
<span class="kt">bool</span> <span class="n">DummyTest</span><span class="o">::</span><span class="n">RunTest</span><span class="p">(</span><span class="k">const</span> <span class="n">FString</span><span class="o">&</span> <span class="n">Parameters</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// Make the test pass by returning true, or fail by returning false.</span>
<span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>See the <a href="https://docs.unrealengine.com/5.1/en-US/automation-technical-guide/">Unreal Engine documentation</a>
for a description of the IMPLEMENT_SIMPLE_AUTOMATION_TEST macro.</p>
<p>I put this code in a file under a <em>Tests</em> folder, named SimpleComponentTest.cpp.</p>
<p><img src="/images/UnrealTestsInRider/ProjectStructure.png" alt="ProjectStructure" /></p>
<p>When I bring up the Unit Tests tab in Rider (Alt+8), I see the tests I have defined.</p>
<p><img src="/images/UnrealTestsInRider/UnitTestsTab.png" alt="UnitTestsTab" /></p>
<p>The tests can be run with the buttons on the left. The top one runs all the tests shown (the double Play button),
the single Play button runs the selected test, and finally you can run the selected test under the debugger.</p>
<p>A slightly more useful test is shown here:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">IMPLEMENT_SIMPLE_AUTOMATION_TEST</span><span class="p">(</span><span class="n">DoSomething</span><span class="p">,</span> <span class="s">"Tests.DoSomething"</span><span class="p">,</span> <span class="n">Flags</span><span class="p">);</span>
<span class="kt">bool</span> <span class="n">DoSomething</span><span class="o">::</span><span class="n">RunTest</span><span class="p">(</span><span class="k">const</span> <span class="n">FString</span><span class="o">&</span> <span class="n">Parameters</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">UWorld</span><span class="o">*</span> <span class="n">World</span> <span class="o">=</span> <span class="n">FAutomationEditorCommonUtils</span><span class="o">::</span><span class="n">CreateNewMap</span><span class="p">();</span>
<span class="n">AActor</span><span class="o">*</span> <span class="n">Actor</span> <span class="o">=</span> <span class="n">World</span><span class="o">-></span><span class="n">SpawnActor</span><span class="o"><</span><span class="n">AActor</span><span class="o">></span><span class="p">();</span>
<span class="n">USimpleComponent</span><span class="o">*</span> <span class="n">Comp</span> <span class="o">=</span> <span class="n">NewObject</span><span class="o"><</span><span class="n">USimpleComponent</span><span class="o">></span><span class="p">(</span><span class="n">Actor</span><span class="p">);</span>
<span class="n">Comp</span><span class="o">-></span><span class="n">DoSomething</span><span class="p">();</span>
<span class="n">TestTrue</span><span class="p">(</span><span class="s">"Did something"</span><span class="p">,</span> <span class="n">Comp</span><span class="o">-></span><span class="n">bDidSomething</span><span class="p">);</span>
<span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This test creates a new world, spawns an actor into it and gives it an instance of our component.</p>
<p>I prefer always relying on the <em>Test</em> methods (<em>TestTrue</em>, <em>TestEqual</em>, and so on) rather than the return
value of the RunTest function. The test will correctly be flagged as failing if any of them fail even
if the final return value is true.</p>
<h2 id="running-in-the-unreal-editor">Running in the Unreal Editor</h2>
<p>The tests can also be run from the Session Frontend in the Unreal Editor. This may or may not be more
convenient - once I’m working with more complicated tests that may require setting up something in an
Unreal level it is convenient to stay within the editor, and rely on the Live Coding feature to iterate
on the code without having to restart the editor every time.</p>
<p><img src="/images/UnrealTestsInRider/SessionFrontEnd.png" alt="SessionFrontEnd" /><a href="/images/UnrealTestsInRider/SessionFrontEnd.png">SessionFrontEnd</a></p>
<p>Note that if you add a test while using Live Coding, you have to press the Refresh Tests button in the
session frontend to have it show up in the list of tests.</p>
<h2 id="spec-tests">Spec tests</h2>
<p>The <a href="https://docs.unrealengine.com/5.1/en-US/automation-spec-in-unreal-engine/">Spec</a> approach has some
clear advantages over the simple automation test shown above - unfortunately, they do not work with
Live Coding when running in the editor. Any change you make to a test, or adding new tests, do not
show up in the editor after recompiling - you have to kill the editor and restart it. In my mind, this
makes them not usable. On top of that, I always seem to get an error when running them in Rider - every
other time, that is. Rerunning the test then succeeds in running the test. Nice as they are they simply
don’t work with my workflow.</p>
<h2 id="see-also">See also</h2>
<p>The source for my test project for this is on GitHub: https://github.com/snorristurluson/UnrealAutomatedTests</p>
<p>Some blogs I’ve found on testing in Unreal:</p>
<ul>
<li>https://blog.jetbrains.com/dotnet/2021/10/06/testing-with-rider-for-unreal-engine/</li>
<li>https://zuru.tech/blog/unit-testing-with-unreal-engine-4</li>
<li>https://benui.ca/unreal/unreal-testing-introduction/</li>
</ul>Unreal Engine has some decent support for automated testing, and my IDE of choice, JetBrains Rider allows you to run the tests from the Unit Tests tab.GitHub Pages Locally On Windows2023-01-03T00:00:00+00:002023-01-03T00:00:00+00:00https://snorristurluson.github.io//GitHubPagesLocallyOnWindows<p>My blog runs on GitHub Pages, which uses Jekyll. Usually I just push
new posts to GitHub and check them once they’re active on the site,
but sometimes it’s nice to be able to preview things locally.</p>
<h2 id="install-jekyll">Install Jekyll</h2>
<p>Instructions for installing Jekyll on Windows are available
<a href="https://jekyllrb.com/docs/installation/windows/">here</a>, but I wanted to
see if I could simplify it a bit using
<a href="https://winget.run/">winget</a> - my new goto for installing things on Windows.</p>
<p>I’m running on Windows 11, using PowerShell.</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> RubyInstallerTeam.RubyWithDevKit.3.1
</code></pre></div></div>
<p>Note that you must restart the shell after installing so binaries installed
can be found in the path.</p>
<p>Next step is to run this:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ridk <span class="nb">install</span>
</code></pre></div></div>
<p>I got the following error:</p>
<pre><code class="language-commandline">ridk : File C:\Ruby31-x64\bin\ridk.ps1 cannot be loaded because running scripts is disabled on this system. For more in
formation, see about_Execution_Policies at https:/go.microsoft.com/fwlink/?LinkID=135170.
At line:1 char:1
+ ridk install
+ ~~~~
+ CategoryInfo : SecurityError: (:) [], PSSecurityException
+ FullyQualifiedErrorId : UnauthorizedAccess
</code></pre>
<p>To get around this I set the execution policy for PowerShell scripts to unrestricted:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Set-ExecutionPolicy <span class="nt">-ExecutionPolicy</span> Unrestricted <span class="nt">-Scope</span> Process
</code></pre></div></div>
<p>Setting the execution policy to unrestricted may seem risky, but it’s only
for the current process - it’s not changing any setting permanently.</p>
<p>Trying this again I got further:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ridk install
_____ _ _____ _ _ _ ___
| __ \ | | |_ _| | | | | | |__ \
| |__) | _| |__ _ _ | | _ __ ___| |_ __ _| | | ___ _ __ ) |
| _ / | | | '_ \| | | | | | | '_ \/ __| __/ _` | | |/ _ \ '__/ /
| | \ \ |_| | |_) | |_| |_| |_| | | \__ \ || (_| | | | __/ | / /_
|_| \_\__,_|_.__/ \__, |_____|_| |_|___/\__\__,_|_|_|\___|_||____|
__/ | _
|___/ _|_ _ __ | | o __ _| _ _
| (_) | |^| | | |(_|(_)\^/_>
1 - MSYS2 base installation
2 - MSYS2 system update (optional)
3 - MSYS2 and MINGW development toolchain
Which components shall be installed? If unsure press ENTER [1,3]
</code></pre></div></div>
<p>I selected 3, as per the instructions from the original Jekyll
installation instructions.</p>
<p>Next is to install Jekyll itself (so far I’ve just installed Ruby).</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem <span class="nb">install </span>jekyll bundler
</code></pre></div></div>
<p>To check if Jekyll has been installed properly I can run this:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jekyll <span class="nt">-v</span>
</code></pre></div></div>
<h2 id="previewing-locally">Previewing locally</h2>
<p>I found I needed to add a Gemfile to the project:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>source "https://rubygems.org"
gem 'github-pages'
</code></pre></div></div>
<p>Run bundle install:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bundle <span class="nb">install</span>
</code></pre></div></div>
<p>I also needed to add webrick:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bundle add webrick
</code></pre></div></div>
<p>Then start serving:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bundle <span class="nb">exec </span>jekyll serve
</code></pre></div></div>
<p>It tells you where to point your browser:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PS C:\snorristurluson.github.io> bundle exec jekyll serve
Configuration file: C:/snorristurluson.github.io/_config.yml
Source: C:/snorristurluson.github.io
Destination: C:/snorristurluson.github.io/_site
Incremental build: disabled. Enable with --incremental
Generating...
Jekyll Feed: Generating feed for posts
done in 0.553 seconds.
Auto-regeneration: enabled for 'C:/snorristurluson.github.io'
Server address: http://127.0.0.1:4000/
Server running... press ctrl-c to stop.
</code></pre></div></div>
<p>And what do you know, it works:</p>
<p><img src="/images/GitHubPagesLocallyOnWindows/Screenshot.png" alt="Screenshot" /></p>
<p>Note that I did find I had to add a <em>layout</em> directive to the top of a post:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>---
layout: post
title: GitHub Pages Locally On Windows
tags: blog
---
</code></pre></div></div>
<p>I haven’t needed that so far - it seems that when GitHub generates the pages
it uses the correct layout by default.</p>My blog runs on GitHub Pages, which uses Jekyll. Usually I just push new posts to GitHub and check them once they’re active on the site, but sometimes it’s nice to be able to preview things locally.Activatable Widgets2022-12-09T00:00:00+00:002022-12-09T00:00:00+00:00https://snorristurluson.github.io//ActivatableWidgets<p>The CommonUI plugin for Unreal Engine has some really useful features. One of them is the notion
of activatable widgets, and the activatable widget stack.</p>
<p>The Lyra starter project uses CommonUI and is in some ways a good reference for making the best use of it.
You could copy the CommonGame plugin from it to use in your projects, but I feel it is doing too much,
and pulls in more dependencies than I like. Also, I couldn’t find any documentation on their UI setup, even
though many other things are described
<a href="https://docs.unrealengine.com/5.1/en-US/lyra-sample-game-in-unreal-engine/">here</a>.</p>
<p>So, I’ve started a simple Unreal project to help me better understand CommonUI and allow me to
experiment with it, rather than doing this in the game I’m working on. Hopefully others will benefit
from this as well - it is available on GitHub:
<a href="https://github.com/snorristurluson/CommonUIPlayground">https://github.com/snorristurluson/CommonUIPlayground</a></p>
<p><img src="/images/ActivatableWidgets/Screenshot.png" alt="Screenshot" /></p>
<h2 id="window-manager">Window Manager</h2>
<p>I use a subsystem as a convenient way of accessing the widget stack from anywhere in code
or blueprints. Calling it a window manager may be a stretch at this point, but I have plans
for developing this further in the future.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">COMMONUIPLAYGROUND_API</span> <span class="n">UWindowManager</span> <span class="o">:</span> <span class="k">public</span> <span class="n">UGameInstanceSubsystem</span>
<span class="p">{</span>
<span class="n">GENERATED_BODY</span><span class="p">()</span>
<span class="nl">public:</span>
<span class="n">UFUNCTION</span><span class="p">(</span><span class="n">BlueprintCallable</span><span class="p">)</span>
<span class="kt">void</span> <span class="n">SetBaseLayer</span><span class="p">(</span><span class="n">UCommonActivatableWidgetContainerBase</span><span class="o">*</span> <span class="n">InBaseLayer</span><span class="p">);</span>
<span class="n">UFUNCTION</span><span class="p">(</span><span class="n">BlueprintCallable</span><span class="p">)</span>
<span class="n">UCommonActivatableWidget</span><span class="o">*</span> <span class="n">PushModal</span><span class="p">(</span><span class="n">TSubclassOf</span><span class="o"><</span><span class="n">UCommonActivatableWidget</span><span class="o">></span> <span class="n">ActivatableWidgetClass</span><span class="p">);</span>
<span class="n">UFUNCTION</span><span class="p">(</span><span class="n">BlueprintCallable</span><span class="p">)</span>
<span class="kt">void</span> <span class="n">PopModal</span><span class="p">();</span>
<span class="nl">protected:</span>
<span class="n">UCommonActivatableWidgetContainerBase</span><span class="o">*</span> <span class="n">BaseLayer</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>
<p>The <strong>PushModal</strong> and <strong>PopModal</strong> methods are simple wrappers around the <em>AddWidget</em> and <em>RemoveWidget</em>
methods the
<a href="https://docs.unrealengine.com/5.0/en-US/API/Plugins/CommonUI/Widgets/UCommonActivatableWidgetContaine-/">UCommonActivatableWidgetContainerBase</a>
class.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">UCommonActivatableWidget</span><span class="o">*</span> <span class="n">UWindowManager</span><span class="o">::</span><span class="n">PushModal</span><span class="p">(</span><span class="n">TSubclassOf</span><span class="o"><</span><span class="n">UCommonActivatableWidget</span><span class="o">></span> <span class="n">ActivatableWidgetClass</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">BaseLayer</span><span class="o">-></span><span class="n">AddWidget</span><span class="p">(</span><span class="n">ActivatableWidgetClass</span><span class="p">);</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">UWindowManager</span><span class="o">::</span><span class="n">PopModal</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">UCommonActivatableWidget</span><span class="o">*</span> <span class="n">TopWidget</span> <span class="o">=</span> <span class="n">BaseLayer</span><span class="o">-></span><span class="n">GetActiveWidget</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="n">TopWidget</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">BaseLayer</span><span class="o">-></span><span class="n">RemoveWidget</span><span class="p">(</span><span class="o">*</span><span class="n">TopWidget</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="handling-actions">Handling actions</h2>
<p>I have a main screen that responds to the Escape key by popping up a menu.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">UCLASS</span><span class="p">()</span>
<span class="k">class</span> <span class="nc">COMMONUIPLAYGROUND_API</span> <span class="n">UUIMainScreen</span> <span class="o">:</span> <span class="k">public</span> <span class="n">UMyActivatableWidget</span>
<span class="p">{</span>
<span class="n">GENERATED_BODY</span><span class="p">()</span>
<span class="nl">public:</span>
<span class="n">UFUNCTION</span><span class="p">(</span><span class="n">BlueprintNativeEvent</span><span class="p">)</span>
<span class="kt">void</span> <span class="n">HandleEscapeAction</span><span class="p">();</span>
<span class="nl">protected:</span>
<span class="k">virtual</span> <span class="kt">void</span> <span class="n">NativeOnInitialized</span><span class="p">()</span> <span class="k">override</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>
<p>I set up an action tag in CPP code to handle the Escape button. In <em>NativeOnInitialized</em> an action
binding is registered.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">const</span> <span class="n">FUIActionTag</span> <span class="n">EscapeActionTag</span> <span class="o">=</span> <span class="n">FUIActionTag</span><span class="o">::</span><span class="n">AddNativeTag</span><span class="p">(</span><span class="n">TEXT</span><span class="p">(</span><span class="s">"Escape"</span><span class="p">));</span>
<span class="kt">void</span> <span class="n">UUIMainScreen</span><span class="o">::</span><span class="n">NativeOnInitialized</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">Super</span><span class="o">::</span><span class="n">NativeOnInitialized</span><span class="p">();</span>
<span class="n">RegisterUIActionBinding</span><span class="p">(</span><span class="n">FBindUIActionArgs</span><span class="p">(</span><span class="n">EscapeActionTag</span><span class="p">,</span> <span class="nb">false</span><span class="p">,</span> <span class="n">FSimpleDelegate</span><span class="o">::</span><span class="n">CreateUObject</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="o">&</span><span class="n">ThisClass</span><span class="o">::</span><span class="n">HandleEscapeAction</span><span class="p">)));</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Binding the Escape UI action to the Esc key happens in the Project Settings|Plugins|Common UI Input Settings.</p>
<p><img src="/images/ActivatableWidgets/EscapeActionSetup.png" alt="EscapeActionSetup" />
<a href="/images/ActivatableWidgets/EscapeActionSetup.png">EscapeActionSetup</a></p>
<p>I set up a blueprint class that inherits from the UUIMainScreen class, and implement the <em>HandleEscapeAction</em>
method there:</p>
<p><img src="/images/ActivatableWidgets/HandleEscapeAction.png" alt="HandleEscapeAction" />
<a href="/images/ActivatableWidgets/HandleEscapeAction.png">HandleEscapeAction</a></p>
<p>The blueprint also sets up the container to use for any modal popups:</p>
<p><img src="/images/ActivatableWidgets/OnInitialized.png" alt="OnInitialized" />
<a href="/images/ActivatableWidgets/OnInitialized.png">OnInitialized</a></p>
<h2 id="freeing-the-cursor">Freeing the cursor</h2>
<p>I have the project set up so that the mouse is capture and the cursor is not shown, and the camera
is controlled by the mouse. When I hit escape, I want to show the mouse cursor and detach the camera
from it, so that I can interact with the UI.</p>
<p>This can be accomplished by overriding the <strong>GetDesiredInputConfig</strong> method from
<a href="https://docs.unrealengine.com/5.0/en-US/API/Plugins/CommonUI/UCommonActivatableWidget/">UCommonActivatableWidget</a>.
To make this easier to do from blueprints, I’ve used a similar approach to what is done in Lyra.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">UCLASS</span><span class="p">()</span>
<span class="k">class</span> <span class="nc">COMMONUIPLAYGROUND_API</span> <span class="n">UMyActivatableWidget</span> <span class="o">:</span> <span class="k">public</span> <span class="n">UCommonActivatableWidget</span>
<span class="p">{</span>
<span class="n">GENERATED_BODY</span><span class="p">()</span>
<span class="nl">public:</span>
<span class="k">virtual</span> <span class="n">TOptional</span><span class="o"><</span><span class="n">FUIInputConfig</span><span class="o">></span> <span class="n">GetDesiredInputConfig</span><span class="p">()</span> <span class="k">const</span> <span class="k">override</span><span class="p">;</span>
<span class="nl">protected:</span>
<span class="n">UPROPERTY</span><span class="p">(</span><span class="n">EditAnywhere</span><span class="p">,</span> <span class="n">BlueprintReadWrite</span><span class="p">)</span>
<span class="n">EMyInputMode</span> <span class="n">InputMode</span> <span class="o">=</span> <span class="n">EMyInputMode</span><span class="o">::</span><span class="n">All</span><span class="p">;</span>
<span class="n">UPROPERTY</span><span class="p">(</span><span class="n">EditAnywhere</span><span class="p">,</span> <span class="n">BlueprintReadWrite</span><span class="p">)</span>
<span class="n">EMouseCaptureMode</span> <span class="n">CaptureMode</span> <span class="o">=</span> <span class="n">EMouseCaptureMode</span><span class="o">::</span><span class="n">NoCapture</span><span class="p">;</span>
<span class="n">UPROPERTY</span><span class="p">(</span><span class="n">EditAnywhere</span><span class="p">,</span> <span class="n">BlueprintReadWrite</span><span class="p">)</span>
<span class="kt">bool</span> <span class="n">HideCursor</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">TOptional</span><span class="o"><</span><span class="n">FUIInputConfig</span><span class="o">></span> <span class="n">UMyActivatableWidget</span><span class="o">::</span><span class="n">GetDesiredInputConfig</span><span class="p">()</span> <span class="k">const</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">FUIInputConfig</span><span class="p">(</span><span class="n">ECommonInputMode</span><span class="p">(</span><span class="n">InputMode</span><span class="p">),</span> <span class="n">CaptureMode</span><span class="p">,</span> <span class="n">HideCursor</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>If I base my widgets off of this one, I can now control the mouse behavior whenever that widget is active
by setting the input mode, capture mode and the hide cursor flag.</p>
<p>Note that I had to provide my own input mode enum - if I tried to use ECommonInputMode directly I got
a linker error. Maybe I’m doing something wrong, but I gave up and added my own by copying theirs, and
just casting it.</p>The CommonUI plugin for Unreal Engine has some really useful features. One of them is the notion of activatable widgets, and the activatable widget stack.Unreal Cheat Manager In Multiplayer2022-10-27T00:00:00+00:002022-10-27T00:00:00+00:00https://snorristurluson.github.io//UnrealCheatManager<p>Running cheat commands on the server requires a bit of setup, as the cheat manager is not an actor and therefore
not replicated.</p>
<p>I’ve solved this by creating a component, called <strong>UServerSideCheatsComponent</strong>, that I attach to the player controller.
Whenever I add a command to the cheat manager I add a method with the same signature to this component, mark it
up so that it runs on the server and simply call that from the cheat manager.</p>
<p>The code I’m showing here is accessible on GitHub:
<a href="https://github.com/snorristurluson/UnrealChat">https://github.com/snorristurluson/UnrealChat</a></p>
<h2 id="setting-up-the-cheat-manager">Setting up the cheat manager</h2>
<p>Let’s set up a very simple cheat manager, with the <em>Bingo</em> cheat command that prints <em>Bongo</em> to the log:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">UCLASS</span><span class="p">()</span>
<span class="k">class</span> <span class="nc">UNREALCHAT_API</span> <span class="n">UMyCheatManager</span> <span class="o">:</span> <span class="k">public</span> <span class="n">UCheatManager</span>
<span class="p">{</span>
<span class="n">GENERATED_BODY</span><span class="p">()</span>
<span class="nl">public:</span>
<span class="n">UFUNCTION</span><span class="p">(</span><span class="n">Exec</span><span class="p">)</span>
<span class="kt">void</span> <span class="n">Bingo</span><span class="p">();</span>
<span class="p">};</span>
<span class="kt">void</span> <span class="n">UMyCheatManager</span><span class="o">::</span><span class="n">Bingo</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">UE_LOG</span><span class="p">(</span><span class="n">LogTemp</span><span class="p">,</span> <span class="n">Display</span><span class="p">,</span> <span class="n">TEXT</span><span class="p">(</span><span class="s">"Bongo"</span><span class="p">));</span>
<span class="p">}</span>
</code></pre></div></div>
<p>I have a Blueprint class for the player controller, so I set the cheat manager to use in the editor.</p>
<p><img src="/images/UnrealCheatManager/CheatManagerSetup.png" alt="CheatManagerSetup" /></p>
<p>Let’s start the game up in multiplayer:</p>
<p><img src="/images/UnrealCheatManager/MultiplayerGameSetup.png" alt="MultiplayerGameSetup" /></p>
<p>Using backtick (or whatever key you have configured) to open the console, we can type in <em>Bingo</em> - the console
shows completion of the command as we start typing it, but this shows up in the log:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Cmd: Bingo
Command not recognized: Bingo
</code></pre></div></div>
<p>Obviously the cheat manager is not getting called, even though the console seems to have some knowledge of
the command. Note that if you set the <em>Net Mode</em> to <em>Play Standalone</em> the command will work correctly.</p>
<h2 id="enabling-cheats-in-multiplayer">Enabling cheats in multiplayer</h2>
<p>After a fair bit of head scratching and reading through Unreal Engine source code, I came to the conclusion
that not only is the cheat manager not replicated, but by default, it is not active at all in a multiplayer game.
In order for it to work in a multiplayer game, you have to add a bit of C++ code to the player controller.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="n">AMyPlayerController</span><span class="o">::</span><span class="n">PostInitializeComponents</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">Super</span><span class="o">::</span><span class="n">PostInitializeComponents</span><span class="p">();</span>
<span class="n">EnableCheats</span><span class="p">();</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">AMyPlayerController</span><span class="o">::</span><span class="n">EnableCheats</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">AddCheats</span><span class="p">(</span><span class="nb">true</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This class can then be set as the base class for the Blueprint class for the player controller. This will enable
the cheats for both singleplayer and multiplayer setups.</p>
<h2 id="forwarding-calls">Forwarding calls</h2>
<p>This setup still only allows us to run cheats on the client - a bit more work is needed to forward the calls
to the server. Note that it is not enough to add the <em>Server</em> function specifier to the function declaration
as the cheat manager is not replicated - it is not an actor, and only actors are replicated.</p>
<p>To get around this, I’ve added a component I can attach to the player controller that can forward the calls.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">UCLASS</span><span class="p">(</span><span class="n">ClassGroup</span><span class="o">=</span><span class="p">(</span><span class="n">Custom</span><span class="p">),</span> <span class="n">meta</span><span class="o">=</span><span class="p">(</span><span class="n">BlueprintSpawnableComponent</span><span class="p">))</span>
<span class="k">class</span> <span class="nc">UNREALCHAT_API</span> <span class="n">UServerSideCheatsComponent</span> <span class="o">:</span> <span class="k">public</span> <span class="n">UActorComponent</span>
<span class="p">{</span>
<span class="n">GENERATED_BODY</span><span class="p">()</span>
<span class="nl">public:</span>
<span class="n">UServerSideCheatsComponent</span><span class="p">();</span>
<span class="n">UFUNCTION</span><span class="p">(</span><span class="n">BlueprintCallable</span><span class="p">,</span> <span class="n">Server</span><span class="p">,</span> <span class="n">Reliable</span><span class="p">,</span> <span class="n">Category</span><span class="o">=</span><span class="s">"Cheat Manager"</span><span class="p">)</span>
<span class="k">virtual</span> <span class="kt">void</span> <span class="n">Bingo</span><span class="p">();</span>
<span class="p">};</span>
</code></pre></div></div>
<p>And the .cpp file:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">UServerSideCheatsComponent</span><span class="o">::</span><span class="n">UServerSideCheatsComponent</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">SetIsReplicatedByDefault</span><span class="p">(</span><span class="nb">true</span><span class="p">);</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">UServerSideCheatsComponent</span><span class="o">::</span><span class="n">Bingo_Implementation</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">GetOwner</span><span class="p">()</span><span class="o">-></span><span class="n">HasAuthority</span><span class="p">())</span>
<span class="p">{</span>
<span class="n">UE_LOG</span><span class="p">(</span><span class="n">LogTemp</span><span class="p">,</span> <span class="n">Display</span><span class="p">,</span> <span class="n">TEXT</span><span class="p">(</span><span class="s">"Bongo on the server!"</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This implementation also prints to the log, but only if the owner (the player controller) has authority,
just to show that the call indeed made it to the server.</p>
<p>Then we change the cheat manager to call this function:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">UServerSideCheatsComponent</span><span class="o">*</span> <span class="n">UMyCheatManager</span><span class="o">::</span><span class="n">GetServerSideCheats</span><span class="p">()</span> <span class="k">const</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">Cast</span><span class="o"><</span><span class="n">UServerSideCheatsComponent</span><span class="o">></span><span class="p">(</span>
<span class="n">GetPlayerController</span><span class="p">()</span><span class="o">-></span><span class="n">GetComponentByClass</span><span class="p">(</span><span class="n">UServerSideCheatsComponent</span><span class="o">::</span><span class="n">StaticClass</span><span class="p">()));</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">UMyCheatManager</span><span class="o">::</span><span class="n">Bingo</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">GetServerSideCheats</span><span class="p">()</span><span class="o">-></span><span class="n">Bingo</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>
<p>I’ve extracted the code for getting the server side cheats component into a function - this will be called
by all the cheat commands that end up getting added to the cheat manager and should be forwarded to the
server.</p>
<h2 id="see-also">See also</h2>
<p><a href="https://benui.ca/">benui.ca</a> is an excellent resource for Unreal Engine tutorials - there is a tutorial
there on the <a href="https://benui.ca/unreal/cheatmanager/">cheat manager</a>, but it does not go into multiplayer issues
as I’ve covered here.</p>Running cheat commands on the server requires a bit of setup, as the cheat manager is not an actor and therefore not replicated.Editable Text Box With History2022-10-13T00:00:00+00:002022-10-13T00:00:00+00:00https://snorristurluson.github.io//EditableTextBoxWithHistory<p>In the game I’m working on we use the chat window also to enter cheat commands, so I found myself in need
of an editable text box with history, as you sometimes need enter the same command repeatedly. Or maybe you just
want to spam the chat in the most efficient manner possible.</p>
<h2 id="the-widget">The Widget</h2>
<p>I didn’t find a suitable widget in Unreal Engine, but it’s easy enough to add, as a subclass of
<a href="https://docs.unrealengine.com/5.0/en-US/API/Runtime/UMG/Components/UEditableTextBox/">UEditableTextBox</a>.
I need to maintain a history buffer and handle the up/down arrow keys and set the text to the appropriate entry
from the that buffer.</p>
<p>There is no need to create a separate Slate widget for this - the
<a href="https://docs.unrealengine.com/5.0/en-US/API/Runtime/Slate/Widgets/Input/SEditableTextBox/">SEditableTextBox</a>
widget used by the UEditableTextBox widget has all the functionality I need, and allows me to set a
dynamic handler for OnKeyDown.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">UCLASS</span><span class="p">()</span>
<span class="k">class</span> <span class="nc">UNREALCHAT_API</span> <span class="n">UEditableTextBoxWithHistory</span> <span class="o">:</span> <span class="k">public</span> <span class="n">UEditableTextBox</span>
<span class="p">{</span>
<span class="n">GENERATED_BODY</span><span class="p">()</span>
<span class="nl">protected:</span>
<span class="n">UPROPERTY</span><span class="p">(</span><span class="n">EditAnywhere</span><span class="p">,</span> <span class="n">BlueprintReadWrite</span><span class="p">)</span>
<span class="n">TArray</span><span class="o"><</span><span class="n">FText</span><span class="o">></span> <span class="n">History</span><span class="p">;</span>
<span class="n">int32</span> <span class="n">HistoryIndex</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="k">virtual</span> <span class="kt">void</span> <span class="n">OnWidgetRebuilt</span><span class="p">()</span> <span class="k">override</span><span class="p">;</span>
<span class="n">FReply</span> <span class="n">OnKeyDown</span><span class="p">(</span><span class="k">const</span> <span class="n">FGeometry</span><span class="o">&</span> <span class="n">MyGeometry</span><span class="p">,</span> <span class="k">const</span> <span class="n">FKeyEvent</span><span class="o">&</span> <span class="n">KeyEvent</span><span class="p">);</span>
<span class="k">virtual</span> <span class="kt">void</span> <span class="n">HandleOnTextCommitted</span><span class="p">(</span><span class="k">const</span> <span class="n">FText</span><span class="o">&</span> <span class="n">Text</span><span class="p">,</span> <span class="n">ETextCommit</span><span class="o">::</span><span class="n">Type</span> <span class="n">CommitMethod</span><span class="p">)</span> <span class="k">override</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>
<p>The <em>History</em> buffer is populated when text is committed:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="n">UEditableTextBoxWithHistory</span><span class="o">::</span><span class="n">HandleOnTextCommitted</span><span class="p">(</span><span class="k">const</span> <span class="n">FText</span><span class="o">&</span> <span class="n">InText</span><span class="p">,</span> <span class="n">ETextCommit</span><span class="o">::</span><span class="n">Type</span> <span class="n">CommitMethod</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Super</span><span class="o">::</span><span class="n">HandleOnTextCommitted</span><span class="p">(</span><span class="n">InText</span><span class="p">,</span> <span class="n">CommitMethod</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">CommitMethod</span> <span class="o">==</span> <span class="n">ETextCommit</span><span class="o">::</span><span class="n">OnEnter</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">History</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">InText</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">History</span><span class="p">.</span><span class="n">Num</span><span class="p">()</span> <span class="o">></span> <span class="mi">10</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">History</span><span class="p">.</span><span class="n">RemoveAt</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">HistoryIndex</span> <span class="o">=</span> <span class="n">History</span><span class="p">.</span><span class="n">Num</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The UMG widget doesn’t have an OnKeyDown method - the underlying Slate widget does, so I hook into that in
<strong>OnWidgetRebuilt</strong>:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="n">UEditableTextBoxWithHistory</span><span class="o">::</span><span class="n">OnWidgetRebuilt</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">Super</span><span class="o">::</span><span class="n">OnWidgetRebuilt</span><span class="p">();</span>
<span class="n">MyEditableTextBlock</span><span class="o">-></span><span class="n">SetOnKeyDownHandler</span><span class="p">(</span><span class="n">FOnKeyDown</span><span class="o">::</span><span class="n">CreateUObject</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="o">&</span><span class="n">UEditableTextBoxWithHistory</span><span class="o">::</span><span class="n">OnKeyDown</span><span class="p">));</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Finally, the OnKeyDown method I attached to the Slate widget monitors keystrokes, looking for the up/down
arrow keys:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">FReply</span> <span class="n">UEditableTextBoxWithHistory</span><span class="o">::</span><span class="n">OnKeyDown</span><span class="p">(</span><span class="k">const</span> <span class="n">FGeometry</span><span class="o">&</span> <span class="n">MyGeometry</span><span class="p">,</span> <span class="k">const</span> <span class="n">FKeyEvent</span><span class="o">&</span> <span class="n">KeyEvent</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">History</span><span class="p">.</span><span class="n">IsEmpty</span><span class="p">())</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">FReply</span><span class="o">::</span><span class="n">Unhandled</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">KeyEvent</span><span class="p">.</span><span class="n">GetKey</span><span class="p">()</span> <span class="o">==</span> <span class="n">EKeys</span><span class="o">::</span><span class="n">Up</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">HistoryIndex</span><span class="o">--</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">HistoryIndex</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">HistoryIndex</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">SetText</span><span class="p">(</span><span class="n">History</span><span class="p">[</span><span class="n">HistoryIndex</span><span class="p">]);</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">KeyEvent</span><span class="p">.</span><span class="n">GetKey</span><span class="p">()</span> <span class="o">==</span> <span class="n">EKeys</span><span class="o">::</span><span class="n">Down</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">HistoryIndex</span><span class="o">++</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">HistoryIndex</span> <span class="o">>=</span> <span class="n">History</span><span class="p">.</span><span class="n">Num</span><span class="p">())</span>
<span class="p">{</span>
<span class="n">SetText</span><span class="p">(</span><span class="n">FText</span><span class="p">());</span>
<span class="n">HistoryIndex</span> <span class="o">=</span> <span class="n">History</span><span class="p">.</span><span class="n">Num</span><span class="p">();</span>
<span class="p">}</span> <span class="k">else</span>
<span class="p">{</span>
<span class="n">SetText</span><span class="p">(</span><span class="n">History</span><span class="p">[</span><span class="n">HistoryIndex</span><span class="p">]);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">FReply</span><span class="o">::</span><span class="n">Unhandled</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This widget can be used in place of the regular UEditableTextBox widget, but it remembers its history
and allows you to scroll through with the up/down arrow keys.</p>In the game I’m working on we use the chat window also to enter cheat commands, so I found myself in need of an editable text box with history, as you sometimes need enter the same command repeatedly. Or maybe you just want to spam the chat in the most efficient manner possible.Unreal Chat2022-10-03T00:00:00+00:002022-10-03T00:00:00+00:00https://snorristurluson.github.io//UnrealChat<p>Setting up a basic chat system in an Unreal Engine multiplayer game is quite easy, as UE takes care of all the
networking.</p>
<p>The chat system I’ve implemented is made up of two components - the ChatServer component, and the ChatClient component.</p>
<h2 id="the-server">The server</h2>
<p>The chat server is an actor component that lives on the game mode. This implies it only lives server side,
which makes sense (in my mind, at least). The chat server has a <em>Send</em> method that sends a message to all
chat clients.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="n">UChatServer</span><span class="o">::</span><span class="n">Send_Implementation</span><span class="p">(</span><span class="k">const</span> <span class="n">FText</span><span class="o">&</span> <span class="n">From</span><span class="p">,</span> <span class="k">const</span> <span class="n">FText</span><span class="o">&</span> <span class="n">Message</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="n">It</span> <span class="o">=</span> <span class="n">GetWorld</span><span class="p">()</span><span class="o">-></span><span class="n">GetPlayerControllerIterator</span><span class="p">();</span> <span class="n">It</span><span class="p">;</span> <span class="o">++</span><span class="n">It</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">auto</span> <span class="n">Client</span> <span class="o">=</span> <span class="n">Cast</span><span class="o"><</span><span class="n">UChatClient</span><span class="o">></span><span class="p">((</span><span class="o">*</span><span class="n">It</span><span class="p">)</span><span class="o">-></span><span class="n">GetComponentByClass</span><span class="p">(</span><span class="n">UChatClient</span><span class="o">::</span><span class="n">StaticClass</span><span class="p">()));</span>
<span class="n">Client</span><span class="o">-></span><span class="n">Receive</span><span class="p">(</span><span class="n">From</span><span class="p">,</span> <span class="n">Message</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="the-client">The client</h2>
<p>The client is an actor component that lives on the player controller, and is set to be replicated. It has
both a <em>Send</em> method and a <em>Receive</em> method.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">UChatClient</span><span class="o">::</span><span class="n">UChatClient</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">SetIsReplicatedByDefault</span><span class="p">(</span><span class="nb">true</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The <em>Send</em> method is set to run on the server. Typically, it would be called from a blueprint in a UI widget
on the client, but as it is replicated, and owned by the player controller it gets forwarded to the UE server.
The chat client (now running on the UE server) find the chat server by looking for the component on the game
mode and calls its <em>Send</em> method.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="n">UChatClient</span><span class="o">::</span><span class="n">Send_Implementation</span><span class="p">(</span><span class="k">const</span> <span class="n">FText</span><span class="o">&</span> <span class="n">Message</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ChatServer</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">AGameModeBase</span><span class="o">*</span> <span class="n">GameMode</span> <span class="o">=</span> <span class="n">GetWorld</span><span class="p">()</span><span class="o">-></span><span class="n">GetAuthGameMode</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="n">GameMode</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">ChatServer</span> <span class="o">=</span> <span class="n">Cast</span><span class="o"><</span><span class="n">UChatServer</span><span class="o">></span><span class="p">(</span><span class="n">GameMode</span><span class="o">-></span><span class="n">GetComponentByClass</span><span class="p">(</span><span class="n">UChatServer</span><span class="o">::</span><span class="n">StaticClass</span><span class="p">()));</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="n">UE_LOG</span><span class="p">(</span><span class="n">LogChatClient</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">"No game mode found"</span><span class="p">));</span>
<span class="n">ChatServer</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ChatServer</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">UE_LOG</span><span class="p">(</span><span class="n">LogChatClient</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">"No chat server established"</span><span class="p">));</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">PlayerState</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">PlayerState</span> <span class="o">=</span> <span class="n">Cast</span><span class="o"><</span><span class="n">APlayerController</span><span class="o">></span><span class="p">(</span><span class="n">GetOwner</span><span class="p">())</span><span class="o">-></span><span class="n">GetPlayerState</span><span class="o"><</span><span class="n">APlayerState</span><span class="o">></span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="n">PlayerState</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">MyName</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">PlayerState</span><span class="o">-></span><span class="n">GetPlayerName</span><span class="p">());</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">ChatServer</span><span class="o">-></span><span class="n">Send</span><span class="p">(</span><span class="n">MyName</span><span class="p">,</span> <span class="n">Message</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The <em>Receive</em> method simply forwards the call to an event handler:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="n">UChatClient</span><span class="o">::</span><span class="n">Receive_Implementation</span><span class="p">(</span><span class="k">const</span> <span class="n">FText</span><span class="o">&</span> <span class="n">From</span><span class="p">,</span> <span class="k">const</span> <span class="n">FText</span><span class="o">&</span> <span class="n">Message</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">OnMessageReceived</span><span class="p">.</span><span class="n">Broadcast</span><span class="p">(</span><span class="n">From</span><span class="p">,</span> <span class="n">Message</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="the-ui">The UI</h2>
<p>I’ve set up a very simple main screen:</p>
<p><img src="/images/UnrealChat/Screenshot.png" alt="Screenshot" /></p>
<p>The chat UI is added in <em>BeginPlay</em> of BP_UnrealChatHUD, which is set as the HUD for the game mode.</p>
<p><img src="/images/UnrealChat/Hierarchy.png" alt="Hierarchy" /></p>
<p><strong>ChatEntries</strong> is a VerticalBox that will receive a ChatEntry widget for each message received.</p>
<p><strong>Input</strong> is an EditableTextBox to handle input from the player.</p>
<p>The MainUI has an event handler for <em>On Text Committed</em> on the input box:</p>
<p><img src="/images/UnrealChat/OnTextCommitted.png" alt="OnTextCommitted" />
<a href="/images/UnrealChat/OnTextCommitted.png">OnTextCommitted</a></p>
<p>This takes the contents of the input box and sends it to the <em>Send</em> method of the chat client.</p>
<p>To show the messages, I bind an event to the chat client <em>On Message Received</em> event:</p>
<p><img src="/images/UnrealChat/OnConstruct.png" alt="OnConstruct" />
<a href="/images/UnrealChat/OnConstruct.png">OnConstruct</a></p>
<p>I’ve tried to keep this sample as bare bones as possible - of course the UI needs a lot more work for
a proper game, and the chat system needs features like filtering, blocking and so on, but I hope this
demonstrates how simple the core of it is.</p>
<p>The project I used for this sample is available on Github:</p>
<p><a href="https://github.com/snorristurluson/UnrealChat">https://github.com/snorristurluson/UnrealChat</a></p>Setting up a basic chat system in an Unreal Engine multiplayer game is quite easy, as UE takes care of all the networking.Replicated Door Open in Unreal Engine2021-12-16T00:00:00+00:002021-12-16T00:00:00+00:00https://snorristurluson.github.io//ReplicatedDoorOpen<p>It’s fairly easy to set up a door that opens in Unreal Engine, but having it replicate to all
clients in a multiplayer game gets a little trickier. I’d like to show my take on this and explain
the issues I’ve encountered.</p>
<p>I’ve set up a stand-alone project demonstrating opening doors, based on the third person template in
Unreal Engine (version 4.27) - the project is available on
<a href="https://github.com/snorristurluson/ReplicatedDoorOpen">GitHub</a>.</p>
<p><img src="/images/ReplicatedDoorOpen/ReplicatedDoorOpen.png" alt="Replicated Door Open" /></p>
<p>When testing the multiplayer aspect of an Unreal project, you need to be sure to set the Play settings
properly:</p>
<p><img src="/images/ReplicatedDoorOpen/PlaySettings.png" alt="Play Settings" /></p>
<p>This allows you play with two (or more) game clients on your own machine - I generally play in the selected
viewport - when more clients are requested, each gets their own window and you can Alt-Tab between them.
This setup behaves close enough to the real thing for most things, and still allows blueprint debugging
with no additional fuss.</p>
<h2 id="basic-door">Basic Door</h2>
<p>First, I want to show a bare bones opening door, that still replicates to all clients.</p>
<p><img src="/images/ReplicatedDoorOpen/BasicDoor.png" alt="Basic Door" />
<a href="/images/ReplicatedDoorOpen/BasicDoor.png">Basic Door</a></p>
<p>This is a Blueprint Actor, with two mesh components, one for the frame and one for the door that opens
inside the frame. I’ve added some very basic geometry that I modeled in Blender to the project for
demonstration purposes.</p>
<p>To allow players to interact with the door I’ve created an interface called <strong>Openable</strong> with two
functions, <strong>Open</strong> and <strong>Close</strong>, and assigned this interface to the BP_BasicDoor class. Opening and
closing the door is then simply a matter of changing the rotation on the Door Static Mesh component.
The component is set to replicate, so any movement of the component is replicated to all clients.</p>
<p><img src="/images/ReplicatedDoorOpen/BasicOpenAndClose.png" alt="Basic Open and Close" />
<a href="/images/ReplicatedDoorOpen/BasicOpenAndClose.png">Basic Open and Close</a></p>
<h2 id="input-actions">Input Actions</h2>
<p>In my test project, I’ve added two input actions, Open and Close, and bound them to E and R keys,
respectively.</p>
<p><img src="/images/ReplicatedDoorOpen/InputActions.png" alt="Input Actions" />
<a href="/images/ReplicatedDoorOpen/InputActions.png">Input Actions</a></p>
<p>It is important to remember that these actions must be performed on the server - telling the door to
open on the client won’t replicate to the other clients so handling the input action must do an RPC
over to the server, which in turn will open the door.</p>
<p><img src="/images/ReplicatedDoorOpen/OpenDoorOnServer.png" alt="Open Door on Server" />
<a href="/images/ReplicatedDoorOpen/OpenDoorOnServer.png">Open Door on Server</a></p>
<p>This simply opens any door within range - here it is a one-meter radius so that should only find the
door right in front of you. The nice thing about using an interface here is that I don’t have to do
any casting to tell the door to open.</p>
<h2 id="animating">Animating</h2>
<p>This basic door works in a multiplayer game, but it simply pops open rather than animating properly.
I can fix that using a timeline in the Blueprint. First, I need to track the state of the door with
an enum rather the boolean variable - it can now be in one of four states:</p>
<ul>
<li>Open</li>
<li>Opening</li>
<li>Closing</li>
<li>Closed</li>
</ul>
<p>If the door is opening and gets a Close event, it should reverse direction rather than playing from
the closed state, and if it is closing and gets an open event, it should also reverse.</p>
<p>This leads to a rather messy Blueprint for handling Open and Close events:</p>
<p><img src="/images/ReplicatedDoorOpen/AnimatedOpenAndClose.png" alt="Animated Open and Close" />
<a href="/images/ReplicatedDoorOpen/AnimatedOpenAndClose.png">Animated Open and Close</a></p>
<p>The timeline has one very simple curve:</p>
<p><img src="/images/ReplicatedDoorOpen/DoorMovementCurve.png" alt="Door Movement Curve" /></p>
<p>I set up the timeline to be of length 1.0, and animate from 0.0 to 1.0 - the rotation of the door
is set in the <strong>Update Door State</strong> function that scales this value up to the actual rotation value.
If you want the door to open faster or slower, you can set the play rate on the timeline.</p>
<p><img src="/images/ReplicatedDoorOpen/UpdateDoorState.png" alt="Update Door State" /></p>
<p>This approach seems to work fine at first glance - the door rotates as it opens and closes and this
movement is replicated to the other clients. The problem is that the animation only happens on the
server and it replicates the transform of the component, and when the network connection is less than
ideal you start seeing choppy movement of the door as there is no prediction going on as you get
with character movement.</p>
<h2 id="network-emulation">Network Emulation</h2>
<p>When testing network play locally on your machine it’s easy to overlook such performance issues, but
you can have Unreal Engine emulate bad network connections, so you are more likely to catch such issues
while testing locally. Under the Play menu, select Advanced Settings and enable Network Emulation, setting
Network Emulation Profile to Bad.</p>
<p><img src="/images/ReplicatedDoorOpen/NetworkEmulation.png" alt="Network Emulation" /></p>
<p>With these settings, I start seeing choppy and uneven movement of the door.</p>
<h2 id="animating-client-side">Animating Client-side</h2>
<p>A better approach is to only replicate the state change and have the clients move the door. All
clients will move the door at the same rate, so even if they may be slightly out of sync they will
eventually reach the same state, and this also reduces the load on the replication as the door
will not have to be replicated every frame.</p>
<p>First, I remove the replication flag from the Door Static Mesh component, but enable replication on
the State variable with a RepNotify setting:</p>
<p><img src="/images/ReplicatedDoorOpen/StateVariable.png" alt="State Variable" /></p>
<p>This adds a new function, <strong>OnRep_State</strong> to the class, that gets called whenever a client gets a new
value for the State variable.</p>
<p><img src="/images/ReplicatedDoorOpen/OnRep_State.png" alt="OnRep_State" /></p>
<p>That function simply forwards the call to an event:</p>
<p><img src="/images/ReplicatedDoorOpen/AnimateDoor.png" alt="Animate Door" /></p>
<p>This has to be implemented in an event so that the timeline is available. The timeline is a separate
timeline from the one used on the server to open and close the door, but must be set up with an
identical curve. It is best to use an external curve, and have both timelines reference that curve
to ensure they are always identical. This allows for easier tweaking of the curve, to add ease-in/ease-out
movement, for example.</p>
<p>Now the door animates smoothly on all clients, even with a bad network connection, as I’m only responding
to the changes of the State variable. Any movement of the door, frame to frame, happens on the client, and
I’m not relying on replication of the component transform.</p>
<h2 id="get-the-code">Get the Code!</h2>
<p>The code is available on GitHub:</p>
<p><a href="https://github.com/snorristurluson/ReplicatedDoorOpen">https://github.com/snorristurluson/ReplicatedDoorOpen</a></p>It’s fairly easy to set up a door that opens in Unreal Engine, but having it replicate to all clients in a multiplayer game gets a little trickier. I’d like to show my take on this and explain the issues I’ve encountered.Spline collisions in Unreal2021-12-07T00:00:00+00:002021-12-07T00:00:00+00:00https://snorristurluson.github.io//SplineCollisions<p>A spline component in Unreal Engine doesn’t really provide any collision detection, as far as I can tell.
You can use the spline to add spline mesh components to an actor and those can in turn have collision,
but I ran into a use case where I wanted essentially a spline based volume for detecting overlaps.
So, rather than adding spline mesh components to the actor I simply added Box collision components - this
gave me the results I wanted.</p>
<h2 id="bp_splinecollider">BP_SplineCollider</h2>
<p>I created a Blueprint Actor called BP_SplineCollider, added a SplineComponent to it and added the following
construction script:</p>
<p><img src="/images/SplineCollision/ConstructionScript.png" alt="ConstructionScript" />
<a href="/images/SplineCollision/ConstructionScript.png">ConstructionScript</a></p>
<p>The first part works out how many samples I want to take along the spline - I want it to be approximately
once per meter, adjusted to the length of the spline.</p>
<p><img src="/images/SplineCollision/ConstructionScript_Part1.png" alt="Part 1" /></p>
<p>The second part calculates the center point between two samples, and a rotator that is oriented along
the spline at that point.</p>
<p><img src="/images/SplineCollision/ConstructionScript_Part2.png" alt="Part 2" /></p>
<p>Finally, I set the extent of the box, enlarging it a bit to ensure coverage through the twists and turns.</p>
<p><img src="/images/SplineCollision/ConstructionScript_Part3.png" alt="Part 3" /></p>
<h2 id="the-result">The result</h2>
<p>I can now add the spline collider to a scene, and as I edit the spline, it gets collision boxes added
around it.</p>
<p><img src="/images/SplineCollision/SplineCollider.png" alt="SplineCollider" /></p>
<p>The ActorBeginOverlap event now gets properly triggered if I walk through the spline - mission accomplished!</p>A spline component in Unreal Engine doesn’t really provide any collision detection, as far as I can tell. You can use the spline to add spline mesh components to an actor and those can in turn have collision, but I ran into a use case where I wanted essentially a spline based volume for detecting overlaps. So, rather than adding spline mesh components to the actor I simply added Box collision components - this gave me the results I wanted.Navigation in Unreal2021-11-30T00:00:00+00:002021-11-30T00:00:00+00:00https://snorristurluson.github.io//Navigation<p>I’ve been doing some experiments with
<a href="https://github.com/snorristurluson/Navigation">navigation</a>
in Unreal Engine. The navigation system is pretty powerful but does suffer a bit from a lack of documentation so I’m
finding myself discovering things by trial and error and reading the underlying source code. I’m describing some of
those experiments here primarily for my own benefit but hopefully others may find it useful as well.</p>
<h2 id="the-level">The Level</h2>
<p>I’ve set up a test environment, where I have one NPC and two actors, a source and a sink. The NPC
first moves toward the sphere-shaped source (let’s pretend it’s picking up some resource) - once it gets
there, it moves toward the cube-shaped sink (for dropping off the resource).</p>
<p><img src="/images/Navigation/Level.png" alt="Level" /></p>
<p>There is a <a href="https://docs.unrealengine.com/4.27/en-US/Resources/ContentExamples/NavMesh/">NavMeshBoundsVolume</a>
around the entire level so there is a navigation mesh covering the whole level.</p>
<h2 id="the-npc">The NPC</h2>
<p>The NPC is very simple - a Blueprint class inheriting from Character, with a SkeletalCube from the engine content
for a mesh. The NPC has a State variable - an enum with Source and Sink values, representing the current target
for movement.</p>
<p>I’m using this very simple behavior tree to control the NPC:</p>
<p><img src="/images/Navigation/BehaviorTree.png" alt="Behavior Tree" />
<a href="/images/Navigation/BehaviorTree.png">Behavior Tree</a></p>
<p>The PickTarget task looks at the state of the NPC and sets the Target value on the blackboard to the appropriate
actor.</p>
<p><img src="/images/Navigation/PickTarget.png" alt="PickTarget" />
<a href="/images/Navigation/PickTarget.png">PickTarget</a></p>
<p>The AdvanceState task is run after the target is reached, and toggles the State value of the NPC.</p>
<p><img src="/images/Navigation/AdvanceState.png" alt="AdvanceState" />
<a href="/images/Navigation/AdvanceState.png">AdvanceState</a></p>
<p>When I hit play, the NPC moves toward the sphere (the source). When it gets there, it turns around and moves
toward the cube (the sink). At the cube it turns around again and goes to the sphere, repeating this loop
forever. All the movements are in a straight line, as there is nothing interfering with its movements.</p>
<h2 id="some-obstacles">Some Obstacles</h2>
<p>If I put a few big boxes in the level the NPC navigates around those, as they have blocking collision and
cut holes in the navigation mesh. To see the navigation mesh in the editor, I press <strong>P</strong>.</p>
<p><img src="/images/Navigation/LevelWithObstacles.png" alt="Level with obstacles" /></p>
<p>To make things more interesting, I’m going to add surfaces with different movement speeds. They don’t block
the NPC, but slow it down (the sandpit) or speed it up (the pavement). To accomplish that, I’ve added two
surface types (sand and pavement) and created corresponding physics materials. The NPC then checks for the
underlying surface type on tick and sets its movement speed depending on the surface it is on.</p>
<p><img src="/images/Navigation/LevelWithSandAndPavement.png" alt="Level with sand and pavement" /></p>
<p>Now when the NPC moves along its path, it speeds up on the pavement and slows down in the sand, but it
still picks the same path. If I look at the path in the gameplay debugger I see that those areas don’t
affect the navigation mesh at all.</p>
<p><img src="/images/Navigation/Pathfinding.png" alt="Pathfinding" /></p>
<h2 id="navareas">NavAreas</h2>
<p>Fortunately there is an easy way to address this. First, I create two
<a href="https://docs.unrealengine.com/4.26/en-US/API/Runtime/NavigationSystem/NavAreas/">NavArea</a>
classes to represent the properties of those areas - <strong>NavArea_Sand</strong> and <strong>NavArea_Pavement</strong>,
setting appropriate values for <strong>Default Cost</strong>.</p>
<p><img src="/images/Navigation/NavArea_Pavement.png" alt="NavArea_Pavement" /></p>
<p>The cost for the pavement area is 0.2, as the NPC moves at five times the regular speed in those
areas. The cost for the sand is 5.0, as the movement speed is one fifth of the regular speed.</p>
<p>Then I add a <a href="https://docs.unrealengine.com/4.27/en-US/Basics/Components/Navigation/">NavModifierComponent</a>
to the BP_SandPit class and set its <strong>Area Class</strong> to NavArea_Sand, as well as the BP_Pavement class,
setting its Area Class to NavArea_Pavement.</p>
<p>The NavModifierComponent modifies the NavMesh generation, defining an area according to its Area Class
following the bounds of the owning object. This allows the pathfinding to find a path that takes the
travel speed over those different surfaces into account.</p>
<h2 id="following-a-spline">Following A Spline</h2>
<p>The NavModifierComponent doesn’t play too well with spline mesh components - if I create a simple road
that follows a spline I get a quite jagged approximation of the road surface in the navigation mesh.</p>
<p><img src="/images/Navigation/JaggedRoad.png" alt="Jagged Road" /></p>
<p>I can get around this by adding my own custom nav areas when I’m building up the spline meshes. I made
a component, SplineMeshBuilder, that samples a spline and adds SplineMeshComponents to the owning actor.
This component inherits from <a href="https://docs.unrealengine.com/4.27/en-US/API/Runtime/NavigationSystem/UNavRelevantComponent/">UNavRelevantComponent</a>
and overrides the <strong>GetNavigationData</strong> method.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="n">USplineMeshBuilder</span><span class="o">::</span><span class="n">AddMeshesToOwner</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">Mesh</span> <span class="o">||</span> <span class="o">!</span><span class="n">Spline</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">float</span> <span class="n">SectionLength</span> <span class="o">=</span> <span class="n">Mesh</span><span class="o">-></span><span class="n">GetBounds</span><span class="p">().</span><span class="n">BoxExtent</span><span class="p">.</span><span class="n">X</span> <span class="o">*</span> <span class="mf">2.0</span><span class="n">f</span><span class="p">;</span>
<span class="kt">float</span> <span class="n">SectionHalfWidth</span> <span class="o">=</span> <span class="n">Mesh</span><span class="o">-></span><span class="n">GetBounds</span><span class="p">().</span><span class="n">BoxExtent</span><span class="p">.</span><span class="n">Y</span> <span class="o">*</span> <span class="mf">0.75</span><span class="n">f</span><span class="p">;</span>
<span class="kt">float</span> <span class="n">SplineLength</span> <span class="o">=</span> <span class="n">Spline</span><span class="o">-></span><span class="n">GetSplineLength</span><span class="p">();</span>
<span class="n">int32</span> <span class="n">NumSegments</span> <span class="o">=</span> <span class="n">SplineLength</span> <span class="o">/</span> <span class="n">SectionLength</span><span class="p">;</span>
<span class="n">Bounds</span><span class="p">.</span><span class="n">Init</span><span class="p">();</span>
<span class="k">for</span> <span class="p">(</span><span class="n">int32</span> <span class="n">Index</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">Index</span> <span class="o"><</span> <span class="n">NumSegments</span><span class="p">;</span> <span class="o">++</span><span class="n">Index</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">float</span> <span class="n">Distance</span> <span class="o">=</span> <span class="n">SectionLength</span> <span class="o">*</span> <span class="n">Index</span><span class="p">;</span>
<span class="n">FVector</span> <span class="n">StartPos</span> <span class="o">=</span> <span class="n">Spline</span><span class="o">-></span><span class="n">GetLocationAtDistanceAlongSpline</span><span class="p">(</span><span class="n">Distance</span><span class="p">,</span> <span class="n">ESplineCoordinateSpace</span><span class="o">::</span><span class="n">World</span><span class="p">);</span>
<span class="n">FVector</span> <span class="n">StartTangent</span> <span class="o">=</span> <span class="n">Spline</span><span class="o">-></span><span class="n">GetTangentAtDistanceAlongSpline</span><span class="p">(</span><span class="n">Distance</span><span class="p">,</span> <span class="n">ESplineCoordinateSpace</span><span class="o">::</span><span class="n">World</span><span class="p">);</span>
<span class="n">StartTangent</span> <span class="o">=</span> <span class="n">StartTangent</span><span class="p">.</span><span class="n">GetClampedToMaxSize</span><span class="p">(</span><span class="n">SectionLength</span><span class="p">);</span>
<span class="n">FVector</span> <span class="n">EndPos</span> <span class="o">=</span> <span class="n">Spline</span><span class="o">-></span><span class="n">GetLocationAtDistanceAlongSpline</span><span class="p">(</span><span class="n">Distance</span> <span class="o">+</span> <span class="n">SectionLength</span><span class="p">,</span> <span class="n">ESplineCoordinateSpace</span><span class="o">::</span><span class="n">World</span><span class="p">);</span>
<span class="n">FVector</span> <span class="n">EndTangent</span> <span class="o">=</span> <span class="n">Spline</span><span class="o">-></span><span class="n">GetTangentAtDistanceAlongSpline</span><span class="p">(</span><span class="n">Distance</span> <span class="o">+</span> <span class="n">SectionLength</span><span class="p">,</span> <span class="n">ESplineCoordinateSpace</span><span class="o">::</span><span class="n">World</span><span class="p">);</span>
<span class="n">EndTangent</span> <span class="o">=</span> <span class="n">EndTangent</span><span class="p">.</span><span class="n">GetClampedToMaxSize</span><span class="p">(</span><span class="n">SectionLength</span><span class="p">);</span>
<span class="n">USplineMeshComponent</span><span class="o">*</span> <span class="n">MeshComponent</span> <span class="o">=</span> <span class="n">CreateMeshComponent</span><span class="p">(</span><span class="n">StartPos</span><span class="p">,</span> <span class="n">StartTangent</span><span class="p">,</span> <span class="n">EndPos</span><span class="p">,</span> <span class="n">EndTangent</span><span class="p">);</span>
<span class="n">GetOwner</span><span class="p">()</span><span class="o">-></span><span class="n">AddOwnedComponent</span><span class="p">(</span><span class="n">MeshComponent</span><span class="p">);</span>
<span class="n">MeshComponent</span><span class="o">-></span><span class="n">RegisterComponent</span><span class="p">();</span>
<span class="n">SplineMeshComponents</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">MeshComponent</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">AddNavAreas</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">FVector</span> <span class="n">StartRight</span> <span class="o">=</span> <span class="n">Spline</span><span class="o">-></span><span class="n">GetRightVectorAtDistanceAlongSpline</span><span class="p">(</span><span class="n">Distance</span><span class="p">,</span> <span class="n">ESplineCoordinateSpace</span><span class="o">::</span><span class="n">World</span><span class="p">);</span>
<span class="n">FVector</span> <span class="n">EndRight</span> <span class="o">=</span> <span class="n">Spline</span><span class="o">-></span><span class="n">GetRightVectorAtDistanceAlongSpline</span><span class="p">(</span><span class="n">Distance</span> <span class="o">+</span> <span class="n">SectionLength</span><span class="p">,</span> <span class="n">ESplineCoordinateSpace</span><span class="o">::</span><span class="n">World</span><span class="p">);</span>
<span class="n">FVector</span> <span class="n">ZOffset</span><span class="p">(</span><span class="mf">0.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">200.0</span><span class="n">f</span><span class="p">);</span>
<span class="n">TArray</span><span class="o"><</span><span class="n">FVector</span><span class="o">></span> <span class="n">Points</span><span class="p">;</span>
<span class="n">Points</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">StartPos</span> <span class="o">+</span> <span class="n">SectionHalfWidth</span><span class="o">*</span><span class="n">StartRight</span><span class="p">);</span>
<span class="n">Points</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">StartPos</span> <span class="o">-</span> <span class="n">SectionHalfWidth</span><span class="o">*</span><span class="n">StartRight</span><span class="p">);</span>
<span class="n">Points</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">StartPos</span> <span class="o">+</span> <span class="n">SectionHalfWidth</span><span class="o">*</span><span class="n">StartRight</span> <span class="o">+</span> <span class="n">ZOffset</span><span class="p">);</span>
<span class="n">Points</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">StartPos</span> <span class="o">-</span> <span class="n">SectionHalfWidth</span><span class="o">*</span><span class="n">StartRight</span> <span class="o">+</span> <span class="n">ZOffset</span><span class="p">);</span>
<span class="n">Points</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">EndPos</span> <span class="o">+</span> <span class="n">SectionHalfWidth</span><span class="o">*</span><span class="n">EndRight</span><span class="p">);</span>
<span class="n">Points</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">EndPos</span> <span class="o">-</span> <span class="n">SectionHalfWidth</span><span class="o">*</span><span class="n">EndRight</span><span class="p">);</span>
<span class="n">Points</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">EndPos</span> <span class="o">+</span> <span class="n">SectionHalfWidth</span><span class="o">*</span><span class="n">EndRight</span> <span class="o">+</span> <span class="n">ZOffset</span><span class="p">);</span>
<span class="n">Points</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">EndPos</span> <span class="o">-</span> <span class="n">SectionHalfWidth</span><span class="o">*</span><span class="n">EndRight</span> <span class="o">+</span> <span class="n">ZOffset</span><span class="p">);</span>
<span class="n">NavAreas</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">Points</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="n">Point</span> <span class="o">:</span> <span class="n">Points</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Bounds</span> <span class="o">+=</span> <span class="n">Point</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">bBoundsInitialized</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
<span class="n">RefreshNavigationModifiers</span><span class="p">();</span>
<span class="p">}</span>
<span class="n">USplineMeshComponent</span><span class="o">*</span> <span class="n">USplineMeshBuilder</span><span class="o">::</span><span class="n">CreateMeshComponent</span><span class="p">(</span><span class="n">FVector</span> <span class="n">StartPos</span><span class="p">,</span> <span class="n">FVector</span> <span class="n">StartTangent</span><span class="p">,</span> <span class="n">FVector</span> <span class="n">EndPos</span><span class="p">,</span> <span class="n">FVector</span> <span class="n">EndTangent</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">FName</span> <span class="n">Name</span> <span class="o">=</span> <span class="n">MakeUniqueObjectName</span><span class="p">(</span><span class="n">GetOwner</span><span class="p">(),</span> <span class="n">USplineMeshComponent</span><span class="o">::</span><span class="n">StaticClass</span><span class="p">());</span>
<span class="n">USplineMeshComponent</span><span class="o">*</span> <span class="n">MeshComponent</span> <span class="o">=</span> <span class="n">NewObject</span><span class="o"><</span><span class="n">USplineMeshComponent</span><span class="o">></span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="n">Name</span><span class="p">);</span>
<span class="n">MeshComponent</span><span class="o">-></span><span class="n">SetStaticMesh</span><span class="p">(</span><span class="n">Mesh</span><span class="p">);</span>
<span class="n">MeshComponent</span><span class="o">-></span><span class="n">SetStartAndEnd</span><span class="p">(</span><span class="n">StartPos</span><span class="p">,</span> <span class="n">StartTangent</span><span class="p">,</span> <span class="n">EndPos</span><span class="p">,</span> <span class="n">EndTangent</span><span class="p">);</span>
<span class="n">MeshComponent</span><span class="o">-></span><span class="n">SetCollisionProfileName</span><span class="p">(</span><span class="n">CollisionProfile</span><span class="p">.</span><span class="n">Name</span><span class="p">);</span>
<span class="n">MeshComponent</span><span class="o">-></span><span class="n">SetMobility</span><span class="p">(</span><span class="n">EComponentMobility</span><span class="o">::</span><span class="n">Movable</span><span class="p">);</span>
<span class="n">MeshComponent</span><span class="o">-></span><span class="n">CreationMethod</span> <span class="o">=</span> <span class="n">EComponentCreationMethod</span><span class="o">::</span><span class="n">Instance</span><span class="p">;</span>
<span class="n">MeshComponent</span><span class="o">-></span><span class="n">SetPhysMaterialOverride</span><span class="p">(</span><span class="n">PhysicalMaterial</span><span class="p">);</span>
<span class="k">return</span> <span class="n">MeshComponent</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">USplineMeshBuilder</span><span class="o">::</span><span class="n">GetNavigationData</span><span class="p">(</span><span class="n">FNavigationRelevantData</span><span class="o">&</span> <span class="n">Data</span><span class="p">)</span> <span class="k">const</span>
<span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="n">int32</span> <span class="n">Idx</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">Idx</span> <span class="o"><</span> <span class="n">NavAreas</span><span class="p">.</span><span class="n">Num</span><span class="p">();</span> <span class="n">Idx</span><span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">FAreaNavModifier</span> <span class="n">Area</span> <span class="o">=</span> <span class="n">FAreaNavModifier</span><span class="p">(</span><span class="n">NavAreas</span><span class="p">[</span><span class="n">Idx</span><span class="p">],</span> <span class="n">ENavigationCoordSystem</span><span class="o">::</span><span class="n">Unreal</span><span class="p">,</span> <span class="n">FTransform</span><span class="o">::</span><span class="n">Identity</span><span class="p">,</span> <span class="n">AreaClass</span><span class="p">);</span>
<span class="n">Data</span><span class="p">.</span><span class="n">Modifiers</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">Area</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The GetNavigationData method fills in a
<a href="https://docs.unrealengine.com/4.27/en-US/API/Runtime/Engine/AI/Navigation/FNavigationRelevantData/">FNavigationRelevantData</a>
structure - in particular it adds to the Modifiers array. The data used to create the areas is built up in the
AddMeshesToOwner method, using the spline directly. For each section, the points of each corner are added to an
array, which is later used to build up a convex hull around the section.</p>
<p>I could probably be more clever about this - the nav areas don’t have to use the same step size as the meshes when
setting this up, and should probably use a varying step size, based on the rate of directional change in the spline.
Still, I get a much better fit (and control over it)
than when simply using the UNavModifierComponent.</p>
<p><img src="/images/Navigation/SmoothRoad.png" alt="Smooth Road" /></p>
<h2 id="get-the-code">Get the Code</h2>
<p>The code for this project is on Github: <a href="https://github.com/snorristurluson/Navigation">https://github.com/snorristurluson/Navigation</a></p>
<p>Any feedback and comments are welcome!</p>I’ve been doing some experiments with navigation in Unreal Engine. The navigation system is pretty powerful but does suffer a bit from a lack of documentation so I’m finding myself discovering things by trial and error and reading the underlying source code. I’m describing some of those experiments here primarily for my own benefit but hopefully others may find it useful as well.Animating Properties In Custom Widgets2021-04-19T00:00:00+00:002021-04-19T00:00:00+00:00https://snorristurluson.github.io//AnimatingPropertiesInCustomWidgets<p>In an <a href="/CustomSlateWidgets">earlier blog</a> I showed how to create a custom widget for drawing a slice. It has
properties for the start angle and arc size, but unlike most numeric properties on widgets they can’t be animated
as I left the widget last time. This is easy to fix, though!</p>
<p>If we look at the position properties of the slice (under the Canvas Panel Slot) we see that there is an icon
in front of the input field:</p>
<p><img src="/images/AnimatingPropertiesInCustomWidgets/Position.png" alt="Position" /></p>
<p>This icon is missing from the Angle and ArcSize properties:</p>
<p><img src="/images/AnimatingPropertiesInCustomWidgets/Before.png" alt="Before" /></p>
<p>Clicking this icon adds a keyframe to an animation, so the absence of this icon means the property cannot be
animated. All that is needed for the icon to show up is add a setter for the property:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <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">"Appearance"</span><span class="p">)</span>
<span class="kt">void</span> <span class="nf">SetAngle</span><span class="p">(</span><span class="kt">float</span> <span class="n">InAngle</span><span class="p">);</span>
<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">"Appearance"</span><span class="p">)</span>
<span class="kt">void</span> <span class="nf">SetArcSize</span><span class="p">(</span><span class="kt">float</span> <span class="n">InArcSize</span><span class="p">);</span>
</code></pre></div></div>
<p>The setter should also forward the new value to the underlying Slate widget:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="n">USlice</span><span class="o">::</span><span class="n">SetAngle</span><span class="p">(</span><span class="kt">float</span> <span class="n">InAngle</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Angle</span> <span class="o">=</span> <span class="n">InAngle</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">MySlice</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">MySlice</span><span class="o">-></span><span class="n">SetAngle</span><span class="p">(</span><span class="n">Angle</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">USlice</span><span class="o">::</span><span class="n">SetArcSize</span><span class="p">(</span><span class="kt">float</span> <span class="n">InArcSize</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">ArcSize</span> <span class="o">=</span> <span class="n">InArcSize</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">MySlice</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">MySlice</span><span class="o">-></span><span class="n">SetArcSize</span><span class="p">(</span><span class="n">ArcSize</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>After this change the keyframe animation shows up in the editor:</p>
<p><img src="/images/AnimatingPropertiesInCustomWidgets/After.png" alt="After" /></p>
<p>Now I can set up an animation that animates the angle and/or the arc size of the slice:</p>
<p><img src="/images/AnimatingPropertiesInCustomWidgets/Animation.png" alt="Animation" /></p>
<p><img src="/images/AnimatingPropertiesInCustomWidgets/Result.png" alt="Result" /></p>
<h2 id="the-code">The code</h2>
<p>The code for this project lives on GitHub:
<a href="https://github.com/snorristurluson/CustomWidget">https://github.com/snorristurluson/CustomWidget</a></p>In an earlier blog I showed how to create a custom widget for drawing a slice. It has properties for the start angle and arc size, but unlike most numeric properties on widgets they can’t be animated as I left the widget last time. This is easy to fix, though!