Jekyll2024-01-22T06:11:34+00:00https://benui.ca/feed.xmlben🌱uiUnreal Engine UI programmerben uiKnow, Do, Feel (Slides)2023-03-07T00:00:00+00:002023-03-07T00:00:00+00:00https://benui.ca/ux/know-do-feel-slides<p><a href="/ux/know-do-feel/">Video</a></p>
<p>Slide controls:</p>
<table>
<thead>
<tr>
<th>Input</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Arrow keys</strong></td>
<td>Change slide</td>
</tr>
<tr>
<td><strong>F</strong></td>
<td>Fullscreen (you may first need to click to focus)</td>
</tr>
<tr>
<td><strong>S</strong></td>
<td>Presenter view</td>
</tr>
<tr>
<td><strong>O</strong>/<strong>Escape</strong></td>
<td>Overview mode</td>
</tr>
</tbody>
</table>ben uiA framework for thinking about UIKnow, Do, Feel2023-03-07T00:00:00+00:002023-03-07T00:00:00+00:00https://benui.ca/ux/know-do-feel<p><a href="/ux/know-do-feel-slides/">Slides</a></p>ben uiA framework for thinking about UIAdd UWidgets to a UserWidget using C++ in the Editor2023-03-01T00:00:00+00:002023-03-01T00:00:00+00:00https://benui.ca/unreal/build-widgets-in-editor<p>Have you ever wanted to build the contents of a <code class="language-plaintext highlighter-rouge">UUserWidget</code> in the editor
using C++? For example imagine you want to create a gallery widget and you need
20 buttons with correct names. You could create them by hand, but it's slow and
error-prone.</p>
<p>The example below shows how to create widgets inside a User Widget blueprint,
from C++, and correctly update the widget tree so the new widgets can be
selected.</p>
<p>Huge thanks to <a href="https://twitter.com/Sharundaar">@Sharundaar</a> for sharing how to
do this in my <a href="https://discord.benui.ca/">Discord</a>, and allowing me to share it
here.</p>
<p><strong>Edit:</strong> It's worth mentioning that if you're trying to fill out a list, tree
or grid in the editor, to give designers an idea of how it will look, you
should consider using <a href="/unreal/listview/">ListView</a>.</p>
<figure>
<figcaption>
<p class="figure-title">WBP_EditorTestWidget</p><p>The final result, notice the tree contains the text widgets created, as
well as the "Fill Text Blocks" button in the details panel.</p>
</figcaption>
<div class="figcontent">
<div>
<a href="/assets/unreal/editor-test-widget.png">
<img src="/assets/unreal/editor-test-widget.png" alt="The final result, notice the tree contains the text widgets created, as
well as the "Fill Text Blocks" button in the details panel." />
</a>
</div>
</div>
</figure>
<h2 id="solution">Solution</h2>
<p>First, we'll need to add <code class="language-plaintext highlighter-rouge">"UMGEditor"</code> and <code class="language-plaintext highlighter-rouge">"UnrealEd"</code> to your dependencies in
your <code class="language-plaintext highlighter-rouge">Build.cs</code> file.</p>
<p>We're doing something a little weird here, we're going to be adding some
editor-specific code to a non-editor class. In order to use the editor
functions, we need to add <code class="language-plaintext highlighter-rouge">UnrealEd</code> and <code class="language-plaintext highlighter-rouge">UMGEditor</code> to our list of modules in
<code class="language-plaintext highlighter-rouge">MyProject.Build.cs</code>, but only when building the editor.</p>
<figure>
<figcaption>
<p class="figure-title">MyProject.Build.cs snippet</p>
</figcaption>
<div class="figcontent">
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="n">Target</span><span class="p">.</span><span class="n">bBuildEditor</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">PrivateDependencyModuleNames</span><span class="p">.</span><span class="nf">AddRange</span><span class="p">(</span><span class="k">new</span> <span class="kt">string</span><span class="p">[]</span>
<span class="p">{</span>
<span class="s">"UMGEditor"</span><span class="p">,</span>
<span class="s">"UnrealEd"</span>
<span class="p">});</span>
<span class="p">}</span>
</code></pre></div> </div>
</div>
</figure>
<figure>
<figcaption>
<p class="figure-title">BUIEditorTestWidget.h</p>
</figcaption>
<div class="figcontent">
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#pragma once
</span>
<span class="cp">#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "BUIEditorTestWidget.generated.h"
</span>
<span class="n">UCLASS</span><span class="p">()</span>
<span class="k">class</span> <span class="nc">UBUIEditorTestWidget</span> <span class="o">:</span> <span class="k">public</span> <span class="n">UUserWidget</span>
<span class="p">{</span>
<span class="n">GENERATED_BODY</span><span class="p">()</span>
<span class="nl">protected:</span>
<span class="c1">// Marking a parameterless function with CallInEditor makes a button</span>
<span class="c1">// show up in editor with that name (see screenshot)</span>
<span class="n">UFUNCTION</span><span class="p">(</span><span class="n">CallInEditor</span><span class="p">,</span> <span class="n">Category</span><span class="o">=</span><span class="s">"Editor Fill Test"</span><span class="p">)</span>
<span class="kt">void</span> <span class="n">FillTextBlocks</span><span class="p">();</span>
<span class="cp">#if WITH_EDITORONLY_DATA
</span> <span class="n">UPROPERTY</span><span class="p">(</span><span class="n">EditAnywhere</span><span class="p">,</span> <span class="n">Category</span><span class="o">=</span><span class="s">"Editor Fill Test"</span><span class="p">,</span> <span class="n">meta</span><span class="o">=</span><span class="p">(</span><span class="n">UIMin</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">UIMax</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">ClampMin</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">ClampMax</span><span class="o">=</span><span class="mi">10</span><span class="p">))</span>
<span class="n">int32</span> <span class="n">Width</span> <span class="o">=</span> <span class="mi">2</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">Category</span><span class="o">=</span><span class="s">"Editor Fill Test"</span><span class="p">,</span> <span class="n">meta</span><span class="o">=</span><span class="p">(</span><span class="n">UIMin</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">UIMax</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">ClampMin</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">ClampMax</span><span class="o">=</span><span class="mi">10</span><span class="p">))</span>
<span class="n">int32</span> <span class="n">Height</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
<span class="cp">#endif
</span>
<span class="c1">// We don't use GridPanel directly in this example, but it being BindWidget means Unreal</span>
<span class="c1">// will display an error if there is no correctly-named variable.</span>
<span class="n">UPROPERTY</span><span class="p">(</span><span class="n">meta</span><span class="o">=</span><span class="p">(</span><span class="n">BindWidget</span><span class="p">))</span>
<span class="k">class</span> <span class="nc">UGridPanel</span><span class="o">*</span> <span class="n">GridPanel</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div> </div>
</div>
</figure>
<figure>
<figcaption>
<p class="figure-title">BUIEditorTestWidget.cpp</p>
</figcaption>
<div class="figcontent">
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include "BUIEditorTestWidget.h"
</span>
<span class="cp">#if WITH_EDITOR
#include "WidgetBlueprint.h"
#include "Blueprint/WidgetBlueprintGeneratedClass.h"
#include "Blueprint/WidgetTree.h"
#include "Components/GridPanel.h"
#include "Components/TextBlock.h"
#include "Kismet2/BlueprintEditorUtils.h"
#endif
</span>
<span class="kt">void</span> <span class="n">UBUIEditorTestWidget</span><span class="o">::</span><span class="n">FillTextBlocks</span><span class="p">()</span>
<span class="p">{</span>
<span class="cp">#if WITH_EDITOR
</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">GridPanel</span><span class="p">)</span>
<span class="k">return</span><span class="p">;</span>
<span class="n">UWidgetBlueprintGeneratedClass</span><span class="o">*</span> <span class="n">WidgetBlueprintGeneratedClass</span> <span class="o">=</span> <span class="n">Cast</span><span class="o"><</span><span class="n">UWidgetBlueprintGeneratedClass</span><span class="o">></span><span class="p">(</span><span class="n">GetClass</span><span class="p">());</span>
<span class="n">UPackage</span><span class="o">*</span> <span class="n">Package</span> <span class="o">=</span> <span class="n">WidgetBlueprintGeneratedClass</span><span class="o">-></span><span class="n">GetPackage</span><span class="p">();</span>
<span class="n">UWidgetBlueprint</span><span class="o">*</span> <span class="n">MainAsset</span> <span class="o">=</span> <span class="n">Cast</span><span class="o"><</span><span class="n">UWidgetBlueprint</span><span class="o">></span><span class="p">(</span><span class="n">Package</span><span class="o">-></span><span class="n">FindAssetInPackage</span><span class="p">());</span>
<span class="c1">// We *cannot* use the BindWidget-marked GridPanel, instead we need to get the widget in the asset's widget tree.</span>
<span class="c1">// However thanks to the BindWidget, we can be relatively sure that FindWidget will be successful.</span>
<span class="n">UGridPanel</span><span class="o">*</span> <span class="n">AssetGridPanel</span> <span class="o">=</span> <span class="n">Cast</span><span class="o"><</span><span class="n">UGridPanel</span><span class="o">></span><span class="p">(</span><span class="n">MainAsset</span><span class="o">-></span><span class="n">WidgetTree</span><span class="o">-></span><span class="n">FindWidget</span><span class="p">(</span><span class="s">"GridPanel"</span><span class="p">));</span>
<span class="n">AssetGridPanel</span><span class="o">-></span><span class="n">ClearChildren</span><span class="p">();</span>
<span class="k">for</span> <span class="p">(</span><span class="n">int32</span> <span class="n">Y</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">Y</span> <span class="o"><</span> <span class="n">Height</span><span class="p">;</span> <span class="o">++</span><span class="n">Y</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="n">int32</span> <span class="n">X</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">X</span> <span class="o"><</span> <span class="n">Width</span><span class="p">;</span> <span class="o">++</span><span class="n">X</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">const</span> <span class="n">FString</span> <span class="n">WidgetName</span> <span class="o">=</span> <span class="n">FString</span><span class="o">::</span><span class="n">Printf</span><span class="p">(</span><span class="n">TEXT</span><span class="p">(</span><span class="s">"Text_X%d_Y%x"</span><span class="p">),</span> <span class="n">X</span><span class="p">,</span> <span class="n">Y</span><span class="p">);</span>
<span class="c1">// Create widget with ConstructWidget, not NewObject</span>
<span class="n">UTextBlock</span><span class="o">*</span> <span class="n">Text</span> <span class="o">=</span> <span class="n">MainAsset</span><span class="o">-></span><span class="n">WidgetTree</span><span class="o">-></span><span class="n">ConstructWidget</span><span class="o"><</span><span class="n">UTextBlock</span><span class="o">></span><span class="p">(</span><span class="n">UTextBlock</span><span class="o">::</span><span class="n">StaticClass</span><span class="p">(),</span> <span class="n">FName</span><span class="p">(</span><span class="n">WidgetName</span><span class="p">));</span>
<span class="n">Text</span><span class="o">-></span><span class="n">SetText</span><span class="p">(</span><span class="n">FText</span><span class="o">::</span><span class="n">FromString</span><span class="p">(</span><span class="n">WidgetName</span><span class="p">));</span>
<span class="n">AssetGridPanel</span><span class="o">-></span><span class="n">AddChildToGrid</span><span class="p">(</span><span class="n">Text</span><span class="p">,</span> <span class="n">X</span><span class="p">,</span> <span class="n">Y</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">AssetGridPanel</span><span class="o">-></span><span class="n">Modify</span><span class="p">();</span>
<span class="n">MainAsset</span><span class="o">-></span><span class="n">Modify</span><span class="p">();</span>
<span class="n">FBlueprintEditorUtils</span><span class="o">::</span><span class="n">MarkBlueprintAsStructurallyModified</span><span class="p">(</span><span class="n">MainAsset</span><span class="p">);</span>
<span class="cp">#endif
</span><span class="p">}</span>
</code></pre></div> </div>
</div>
</figure>ben uiHow to programmatically build up a widget in the editor, while keeping the widget tree in sync.Show TMap Entries in the Same Row2023-02-04T00:00:00+00:002023-02-04T00:00:00+00:00https://benui.ca/unreal/tmap-same-row<p>Unreal Engine will in general display <code class="language-plaintext highlighter-rouge">TMap</code> entries with the key and the value
in the same row. For example <code class="language-plaintext highlighter-rouge">TMap</code> with keys that are <code class="language-plaintext highlighter-rouge">FString</code>, <code class="language-plaintext highlighter-rouge">FName</code>,
<code class="language-plaintext highlighter-rouge">int32</code>, even <code class="language-plaintext highlighter-rouge">UTexture</code> will all be displayed with the key and value together.</p>
<figure>
<div class="figcontent">
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">UPROPERTY</span><span class="p">(</span><span class="n">EditAnywhere</span><span class="p">)</span>
<span class="n">TMap</span><span class="o"><</span><span class="n">FString</span><span class="p">,</span> <span class="n">int32</span><span class="o">></span> <span class="n">StringTMap</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">TMap</span><span class="o"><</span><span class="n">FName</span><span class="p">,</span> <span class="n">int32</span><span class="o">></span> <span class="n">NameTMap</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">TMap</span><span class="o"><</span><span class="n">int32</span><span class="p">,</span> <span class="n">int32</span><span class="o">></span> <span class="n">IntTMap</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">TMap</span><span class="o"><</span><span class="n">UTexture</span><span class="o">*</span><span class="p">,</span> <span class="n">int32</span><span class="o">></span> <span class="n">TextureTMap</span><span class="p">;</span>
</code></pre></div> </div>
</div>
</figure>
<figure>
<div class="figcontent">
<div>
<a href="/assets/unreal/tmap-fstring-same-row.png">
<img src="/assets/unreal/tmap-fstring-same-row.png" alt="" />
</a>
</div>
</div>
</figure>
<h2 id="the-problem">The Problem</h2>
<p>However <code class="language-plaintext highlighter-rouge">FGameplayTag</code> and custom structs will be displayed with the key and
value on separate lines. Yuck.</p>
<figure>
<div class="figcontent">
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">USTRUCT</span><span class="p">()</span>
<span class="k">struct</span> <span class="nc">FExampleStruct</span>
<span class="p">{</span>
<span class="n">GENERATED_BODY</span><span class="p">()</span>
<span class="nl">public:</span>
<span class="n">UPROPERTY</span><span class="p">(</span><span class="n">EditAnywhere</span><span class="p">)</span>
<span class="n">FString</span> <span class="n">Name</span><span class="p">;</span>
<span class="c1">// Used so we can have a TMap of this struct</span>
<span class="n">FORCEINLINE</span> <span class="k">friend</span> <span class="n">uint32</span> <span class="n">GetTypeHash</span><span class="p">(</span><span class="k">const</span> <span class="n">FExampleStruct</span><span class="o">&</span> <span class="n">Struct</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="o">::</span><span class="n">GetTypeHash</span><span class="p">(</span><span class="n">Struct</span><span class="p">.</span><span class="n">Name</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="c1">// later...</span>
<span class="n">UPROPERTY</span><span class="p">(</span><span class="n">EditAnywhere</span><span class="p">)</span>
<span class="n">TMap</span><span class="o"><</span><span class="n">FGameplayTag</span><span class="p">,</span> <span class="n">int32</span><span class="o">></span> <span class="n">VanillaGameplayTagTMap</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">TMap</span><span class="o"><</span><span class="n">FExampleStruct</span><span class="p">,</span> <span class="n">int32</span><span class="o">></span> <span class="n">VanillaStructTMap</span><span class="p">;</span>
</code></pre></div> </div>
</div>
</figure>
<figure>
<div class="figcontent">
<div>
<a href="/assets/unreal/fgameplaytag-separate-row.png">
<img src="/assets/unreal/fgameplaytag-separate-row.png" alt="" />
</a>
</div>
</div>
</figure>
<p>Is there a way we can force them to be shown on the same line? Yes! There are
two ways.</p>
<h2 id="solutions">Solutions</h2>
<h3 id="forceinlinerow">ForceInlineRow</h3>
<p>The meta <code class="language-plaintext highlighter-rouge">UPROPERTY()</code> tag <a href="/unreal/uproperty/#ForceInlineRow"><code class="language-plaintext highlighter-rouge">ForceInlineRow</code></a> does exactly what you expect, it forces keys and values to be displayed inline on the same row.</p>
<figure>
<div class="figcontent">
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">UPROPERTY</span><span class="p">(</span><span class="n">EditAnywhere</span><span class="p">)</span>
<span class="n">TMap</span><span class="o"><</span><span class="n">FGameplayTag</span><span class="p">,</span> <span class="n">int32</span><span class="o">></span> <span class="n">Stats</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">meta</span><span class="o">=</span><span class="p">(</span><span class="n">ForceInlineRow</span><span class="p">))</span>
<span class="n">TMap</span><span class="o"><</span><span class="n">FGameplayTag</span><span class="p">,</span> <span class="n">int32</span><span class="o">></span> <span class="n">StatsInlineRow</span><span class="p">;</span>
</code></pre></div> </div>
</div>
</figure>
<figure>
<div class="figcontent">
<div>
<a href="/assets/data/UE-Specifier-Docs/images/uproperty/force-inline-row.png">
<img src="/assets/data/UE-Specifier-Docs/images/uproperty/force-inline-row.png" alt="" />
</a>
</div>
</div>
</figure>
<p>That's all you need to add!</p>
<h3 id="custom-inspector">Custom Inspector</h3>
<p>This is a lot more involved, but it will let us show any sort of data structure
in a key-value TMap-style way.</p>
<p>Imagine we want to let designers specify the contents of a store. There will be
an array of items and how many of them there will be.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">USTRUCT</span><span class="p">()</span>
<span class="k">struct</span> <span class="nc">FShopEntry</span>
<span class="p">{</span>
<span class="n">GENERATED_BODY</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">TSoftObjectPtr</span><span class="o"><</span><span class="n">UItemDataAsset</span><span class="o">></span> <span class="n">ItemDataAsset</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">meta</span><span class="o">=</span><span class="p">(</span><span class="n">UIMin</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">ClampMin</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">UIMax</span><span class="o">=</span><span class="mi">20</span><span class="p">))</span>
<span class="n">int32</span> <span class="n">Count</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>
<p>We want to show this in tabular form, like <code class="language-plaintext highlighter-rouge">ForceInlineRow</code>, but for whatever
reason, <code class="language-plaintext highlighter-rouge">ForceInlineRow</code> doesn't work for the class we're using as a Key.</p>
<p>Here's how you do that.</p>
<ol>
<li>Create an editor module if you don't have one already.</li>
<li>Create a details customization for the struct mentioned above.</li>
<li>In the editor module's <code class="language-plaintext highlighter-rouge">StartupModule()</code> function, register the
customization.</li>
</ol>
<figure>
<figcaption>
<p class="figure-title">ShopEntryCustomization.h</p>
</figcaption>
<div class="figcontent">
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Copyright Brace Yourself Games. All Rights Reserved.</span>
<span class="cp">#pragma once
</span>
<span class="cp">#include "CoreMinimal.h"
#include "IPropertyTypeCustomization.h"
</span>
<span class="k">class</span> <span class="nc">IPropertyHandle</span><span class="p">;</span>
<span class="k">class</span> <span class="nc">FShopEntryCustomization</span> <span class="o">:</span> <span class="k">public</span> <span class="n">IPropertyTypeCustomization</span>
<span class="p">{</span>
<span class="nl">public:</span>
<span class="k">static</span> <span class="n">TSharedRef</span><span class="o"><</span><span class="n">IPropertyTypeCustomization</span><span class="o">></span> <span class="n">MakeInstance</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">MakeShareable</span><span class="p">(</span> <span class="k">new</span> <span class="n">FShopEntryCustomization</span> <span class="p">);</span>
<span class="p">}</span>
<span class="cm">/** IPropertyTypeCustomization interface */</span>
<span class="k">virtual</span> <span class="kt">void</span> <span class="n">CustomizeHeader</span><span class="p">(</span> <span class="n">TSharedRef</span><span class="o"><</span><span class="k">class</span> <span class="nc">IPropertyHandle</span><span class="o">></span> <span class="n">InStructPropertyHandle</span><span class="p">,</span> <span class="k">class</span> <span class="nc">FDetailWidgetRow</span><span class="o">&</span> <span class="n">HeaderRow</span><span class="p">,</span> <span class="n">IPropertyTypeCustomizationUtils</span><span class="o">&</span> <span class="n">StructCustomizationUtils</span> <span class="p">)</span> <span class="k">override</span><span class="p">;</span>
<span class="k">virtual</span> <span class="kt">void</span> <span class="n">CustomizeChildren</span><span class="p">(</span> <span class="n">TSharedRef</span><span class="o"><</span><span class="k">class</span> <span class="nc">IPropertyHandle</span><span class="o">></span> <span class="n">InStructPropertyHandle</span><span class="p">,</span> <span class="k">class</span> <span class="nc">IDetailChildrenBuilder</span><span class="o">&</span> <span class="n">StructBuilder</span><span class="p">,</span> <span class="n">IPropertyTypeCustomizationUtils</span><span class="o">&</span> <span class="n">StructCustomizationUtils</span> <span class="p">)</span> <span class="k">override</span><span class="p">;</span>
<span class="nl">private:</span>
<span class="cm">/** Handle to the struct property being customized */</span>
<span class="n">TSharedPtr</span><span class="o"><</span><span class="n">IPropertyHandle</span><span class="o">></span> <span class="n">StructPropertyHandle</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div> </div>
</div>
</figure>
<figure>
<figcaption>
<p class="figure-title">ShopEntryCustomization.cpp</p>
</figcaption>
<div class="figcontent">
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include "ShopEntryCustomization.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "PropertyHandle.h"
#include "PropertyCustomizationHelpers.h"
</span>
<span class="kt">void</span> <span class="n">FShopEntryCustomization</span><span class="o">::</span><span class="n">CustomizeHeader</span><span class="p">(</span> <span class="n">TSharedRef</span><span class="o"><</span><span class="n">IPropertyHandle</span><span class="o">></span> <span class="n">InStructPropertyHandle</span><span class="p">,</span> <span class="n">FDetailWidgetRow</span><span class="o">&</span> <span class="n">HeaderRow</span><span class="p">,</span> <span class="n">IPropertyTypeCustomizationUtils</span><span class="o">&</span> <span class="n">StructCustomizationUtils</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">StructPropertyHandle</span> <span class="o">=</span> <span class="n">InStructPropertyHandle</span><span class="p">;</span>
<span class="c1">// Gets ItemDataAsset</span>
<span class="n">TSharedPtr</span><span class="o"><</span><span class="n">IPropertyHandle</span><span class="o">></span> <span class="n">Key</span> <span class="o">=</span> <span class="n">StructPropertyHandle</span><span class="o">-></span><span class="n">GetChildHandle</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
<span class="c1">// Gets Count</span>
<span class="n">TSharedPtr</span><span class="o"><</span><span class="n">IPropertyHandle</span><span class="o">></span> <span class="n">Value</span> <span class="o">=</span> <span class="n">StructPropertyHandle</span><span class="o">-></span><span class="n">GetChildHandle</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
<span class="c1">// Setup in the header row so that we still get the TArray dropdown</span>
<span class="n">HeaderRow</span>
<span class="p">.</span><span class="n">NameContent</span><span class="p">()</span>
<span class="p">[</span>
<span class="n">Key</span><span class="o">-></span><span class="n">CreatePropertyValueWidget</span><span class="p">()</span>
<span class="p">]</span>
<span class="p">.</span><span class="n">ValueContent</span><span class="p">()</span>
<span class="p">.</span><span class="n">MaxDesiredWidth</span><span class="p">(</span><span class="mf">0.0</span><span class="n">f</span><span class="p">)</span>
<span class="p">[</span>
<span class="n">Value</span><span class="o">-></span><span class="n">CreatePropertyValueWidget</span><span class="p">()</span>
<span class="p">];</span>
<span class="c1">// This avoids making duplicate reset boxes</span>
<span class="n">StructPropertyHandle</span><span class="o">-></span><span class="n">MarkResetToDefaultCustomized</span><span class="p">();</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">FShopEntryCustomization</span><span class="o">::</span><span class="n">CustomizeChildren</span><span class="p">(</span><span class="n">TSharedRef</span><span class="o"><</span><span class="n">IPropertyHandle</span><span class="o">></span> <span class="n">InStructPropertyHandle</span><span class="p">,</span> <span class="n">IDetailChildrenBuilder</span><span class="o">&</span> <span class="n">StructBuilder</span><span class="p">,</span> <span class="n">IPropertyTypeCustomizationUtils</span><span class="o">&</span> <span class="n">StructCustomizationUtils</span><span class="p">)</span>
<span class="p">{</span>
<span class="p">}</span>
</code></pre></div> </div>
</div>
</figure>
<p>Now here's how we register the details customization.</p>
<figure>
<figcaption>
<p class="figure-title">MyProjectEditorModule.cpp</p>
</figcaption>
<div class="figcontent">
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include "MyProjectEditorModule.h"
#include "Modules/ModuleManager.h"
#include "Modules/ModuleInterface.h"
#include "PropertyTypeCustomization/ShopEntryCustomization.h"
</span>
<span class="cp">#define LOCTEXT_NAMESPACE "FMyProjectEditorModule"
</span>
<span class="kt">void</span> <span class="n">FMyProjectEditorModule</span><span class="o">::</span><span class="n">StartupModule</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">FPropertyEditorModule</span><span class="o">&</span> <span class="n">PropertyModule</span> <span class="o">=</span> <span class="n">FModuleManager</span><span class="o">::</span><span class="n">LoadModuleChecked</span><span class="o"><</span><span class="n">FPropertyEditorModule</span><span class="o">></span><span class="p">(</span><span class="s">"PropertyEditor"</span><span class="p">);</span>
<span class="n">PropertyModule</span><span class="p">.</span><span class="n">RegisterCustomPropertyTypeLayout</span><span class="p">(</span><span class="s">"ShopEntry"</span><span class="p">,</span> <span class="n">FOnGetPropertyTypeCustomizationInstance</span><span class="o">::</span><span class="n">CreateStatic</span><span class="p">(</span><span class="o">&</span><span class="n">FShopEntryCustomization</span><span class="o">::</span><span class="n">MakeInstance</span><span class="p">));</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">FMyProjectEditorModule</span><span class="o">::</span><span class="n">ShutdownModule</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">FModuleManager</span><span class="o">::</span><span class="n">Get</span><span class="p">().</span><span class="n">OnModulesChanged</span><span class="p">().</span><span class="n">RemoveAll</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
<span class="c1">// Unregister customization and callback</span>
<span class="n">FPropertyEditorModule</span><span class="o">*</span> <span class="n">PropertyEditorModule</span> <span class="o">=</span> <span class="n">FModuleManager</span><span class="o">::</span><span class="n">GetModulePtr</span><span class="o"><</span><span class="n">FPropertyEditorModule</span><span class="o">></span><span class="p">(</span><span class="s">"PropertyEditor"</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">PropertyEditorModule</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">PropertyEditorModule</span><span class="o">-></span><span class="n">UnregisterCustomPropertyTypeLayout</span><span class="p">(</span><span class="n">TEXT</span><span class="p">(</span><span class="s">"ShopEntry"</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">IMPLEMENT_MODULE</span><span class="p">(</span><span class="n">FMyProjectEditorModule</span><span class="p">,</span> <span class="n">MyProjectEditor</span><span class="p">)</span>
<span class="cp">#undef LOCTEXT_NAMESPACE
</span></code></pre></div> </div>
</div>
</figure>
<p>Hopefully between <code class="language-plaintext highlighter-rouge">ForceInlineRow</code> and the example above, you should have an
idea of how to do key-value style display</p>ben uiForceInlineRow or a custom inspectorInheritable TMap2023-02-03T00:00:00+00:002023-02-03T00:00:00+00:00https://benui.ca/unreal/inheritable-tmap<p>Imagine you're building an RPG and you want to define stats for different enemy
types like wolves and spiders. You could do this with hierarchical data
like this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>UEnemyDataAsset
- BP_EnemyBase // Blueprint subclass
- BP_WolfBase // Blueprint subclass
- BP_SpiderBase
</code></pre></div></div>
<p>You could define the stats for each enemy type with a simple
<code class="language-plaintext highlighter-rouge">TMap<FGameplayTag, int32></code> and then override the values in child classes.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>BP_EnemyBase
TMap<FGameplayTag, int32> Stats
hp = 10
legs = 2
BP_WolfBase:
TMap<FGameplayTag, int32> Stats (Modified)
hp = 8
legs = 4
BP_SpiderBase:
TMap<FGameplayTag, int32> Stats (Modified)
hp = 4
legs = 8
</code></pre></div></div>
<h2 id="the-problem">The Problem</h2>
<p>Notice in the example above that the <code class="language-plaintext highlighter-rouge">stats</code> variable has been marked as
modified in <code class="language-plaintext highlighter-rouge">BP_WolfBase</code> and <code class="language-plaintext highlighter-rouge">BP_SpiderBase</code>, because we changed the number of
legs and hp. Once you have changed the TMap in a child class, <strong>any changes
made to the parent will no longer be reflected in the child!</strong></p>
<p>Look what happens if we add a new <code class="language-plaintext highlighter-rouge">vision</code> property to the <code class="language-plaintext highlighter-rouge">BP_EnemyBase</code>, the
property does not appear either child Blueprint.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>BP_EnemyBase
TMap<FGameplayTag, int32> Stats
hp = 10
legs = 2
vision = 5 // NEW!
BP_WolfBase:
TMap<FGameplayTag, int32> Stats (Modified)
hp = 8
legs = 4
// <= No vision!
BP_SpiderBase:
TMap<FGameplayTag, int32> Stats (Modified)
hp = 4
legs = 8
// <= No vision!
</code></pre></div></div>
<h2 id="solution">Solution</h2>
<p>How can we make a data structure that lets us:</p>
<ol>
<li>Inherit stats defined in the parent.</li>
<li>Override stats in child classes.</li>
<li>Allow changes made in the parent to be reflected in children.</li>
</ol>
<h3 id="inherited-map">Inherited Map</h3>
<p>Huge thanks to <a href="https://twitter.com/bohdon">Bohdon Sayre</a> for this one. After
describing my problem he said that <code class="language-plaintext highlighter-rouge">FInheritedTagContainer</code> in the
GameplayAbilities module had a solution for this.</p>
<p>The engine example is a bit more complicated for adding and removing tags, this
just allows you to override values and <em>not</em> remove any tags added by the
parent. It also works with Data Assets, not just Blueprint subclasses.</p>
<figure>
<figcaption>
<p class="figure-title">InheritedMap.h</p>
</figcaption>
<div class="figcontent">
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#pragma once
</span>
<span class="cp">#include "CoreMinimal.h"
#include "InheritedMap.generated.h"
</span>
<span class="n">USTRUCT</span><span class="p">(</span><span class="n">BlueprintType</span><span class="p">)</span>
<span class="k">struct</span> <span class="nc">FInheritedMap</span>
<span class="p">{</span>
<span class="n">GENERATED_BODY</span><span class="p">()</span>
<span class="nl">public:</span>
<span class="c1">// Read-only shows the combined attributes of all parent(s)</span>
<span class="n">UPROPERTY</span><span class="p">(</span><span class="n">VisibleAnywhere</span><span class="p">,</span> <span class="n">Transient</span><span class="p">,</span> <span class="n">meta</span><span class="o">=</span><span class="p">(</span><span class="n">ForceInlineRow</span><span class="p">))</span>
<span class="n">TMap</span><span class="o"><</span><span class="n">FGameplayTag</span><span class="p">,</span> <span class="n">int32</span><span class="o">></span> <span class="n">Combined</span><span class="p">;</span>
<span class="c1">// Any overrides we wantAttributes to create for this instance</span>
<span class="n">UPROPERTY</span><span class="p">(</span><span class="n">EditDefaultsOnly</span><span class="p">,</span> <span class="n">Transient</span><span class="p">,</span> <span class="n">meta</span><span class="o">=</span><span class="p">(</span><span class="n">ForceInlineRow</span><span class="p">))</span>
<span class="n">TMap</span><span class="o"><</span><span class="n">FGameplayTag</span><span class="p">,</span> <span class="n">int32</span><span class="o">></span> <span class="n">Overridden</span><span class="p">;</span>
<span class="kt">void</span> <span class="n">UpdateInherited</span><span class="p">(</span><span class="k">const</span> <span class="n">FInheritedMap</span><span class="o">*</span> <span class="n">Parent</span><span class="p">);</span>
<span class="kt">void</span> <span class="n">PostInitProperties</span><span class="p">();</span>
<span class="p">};</span>
</code></pre></div> </div>
</div>
</figure>
<figure>
<figcaption>
<p class="figure-title">InheritedMap.cpp</p>
</figcaption>
<div class="figcontent">
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include "InheritedMap.h"
</span>
<span class="kt">void</span> <span class="n">FInheritedMap</span><span class="o">::</span><span class="n">UpdateInherited</span><span class="p">(</span><span class="k">const</span> <span class="n">FInheritedMap</span><span class="o">*</span> <span class="n">Parent</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// Make sure we've got a fresh start</span>
<span class="n">Combined</span><span class="p">.</span><span class="n">Reset</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="n">Parent</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">Itr</span> <span class="o">=</span> <span class="n">Parent</span><span class="o">-></span><span class="n">Combined</span><span class="p">.</span><span class="n">CreateConstIterator</span><span class="p">();</span> <span class="n">Itr</span><span class="p">;</span> <span class="o">++</span><span class="n">Itr</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Combined</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="o">*</span><span class="n">Itr</span><span class="p">);</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">Itr</span> <span class="o">=</span> <span class="n">Overridden</span><span class="p">.</span><span class="n">CreateConstIterator</span><span class="p">();</span> <span class="n">Itr</span><span class="p">;</span> <span class="o">++</span><span class="n">Itr</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// Remove trumps add for explicit matches but not for parent tags.</span>
<span class="c1">// This lets us remove all inherited tags starting with Foo but still add Foo.Bar</span>
<span class="n">Combined</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="o">*</span><span class="n">Itr</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">FInheritedMap</span><span class="o">::</span><span class="n">PostInitProperties</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// We shouldn't inherit the added and removed tags from our parents</span>
<span class="c1">// make sure that these fields are clear</span>
<span class="n">Overridden</span><span class="p">.</span><span class="n">Reset</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div> </div>
</div>
</figure>
<h3 id="example-usage">Example Usage</h3>
<figure>
<figcaption>
<p class="figure-title">EnemyDataAsset.h</p>
</figcaption>
<div class="figcontent">
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#pragma once
</span>
<span class="cp">#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "InheritedMap.h"
#include "EnemyDataAsset.generated.h"
</span>
<span class="n">UCLASS</span><span class="p">()</span>
<span class="k">class</span> <span class="nc">UEnemyDataAsset</span> <span class="o">:</span> <span class="k">public</span> <span class="n">UDataAsset</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="kt">void</span> <span class="n">PostLoad</span><span class="p">()</span> <span class="k">override</span><span class="p">;</span>
<span class="k">virtual</span> <span class="kt">void</span> <span class="n">PostInitProperties</span><span class="p">()</span> <span class="k">override</span><span class="p">;</span>
<span class="cp">#if WITH_EDITOR
</span> <span class="k">virtual</span> <span class="kt">void</span> <span class="n">PostEditChangeProperty</span><span class="p">(</span><span class="n">FPropertyChangedEvent</span><span class="o">&</span> <span class="n">PropertyChangedEvent</span><span class="p">)</span> <span class="k">override</span><span class="p">;</span>
<span class="cp">#endif
</span>
<span class="nl">protected:</span>
<span class="n">UPROPERTY</span><span class="p">(</span><span class="n">EditDefaultsOnly</span><span class="p">,</span> <span class="n">Category</span><span class="o">=</span><span class="s">"Stats"</span><span class="p">,</span> <span class="n">meta</span><span class="o">=</span><span class="p">(</span><span class="n">ShowOnlyInnerProperties</span><span class="p">))</span>
<span class="n">FInheritedMap</span> <span class="n">Stats</span><span class="p">;</span>
<span class="n">UEnemyDataAsset</span><span class="o">*</span> <span class="n">GetInheritedParent</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div> </div>
</div>
</figure>
<figure>
<figcaption>
<p class="figure-title">EnemyDataAsset.cpp</p>
</figcaption>
<div class="figcontent">
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include "EnemyDataAsset.h"
</span>
<span class="kt">void</span> <span class="n">UEnemyDataAsset</span><span class="o">::</span><span class="n">PostLoad</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">Super</span><span class="o">::</span><span class="n">PostLoad</span><span class="p">();</span>
<span class="k">const</span> <span class="n">UEnemyDataAsset</span><span class="o">*</span> <span class="n">Parent</span> <span class="o">=</span> <span class="n">GetInheritedParent</span><span class="p">();</span>
<span class="n">Stats</span><span class="p">.</span><span class="n">UpdateInherited</span><span class="p">(</span><span class="n">Parent</span> <span class="o">?</span> <span class="o">&</span><span class="n">Parent</span><span class="o">-></span><span class="n">Stats</span> <span class="o">:</span> <span class="nb">nullptr</span><span class="p">);</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">UEnemyDataAsset</span><span class="o">::</span><span class="n">PostInitProperties</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">Super</span><span class="o">::</span><span class="n">PostInitProperties</span><span class="p">();</span>
<span class="n">Stats</span><span class="p">.</span><span class="n">PostInitProperties</span><span class="p">();</span>
<span class="p">}</span>
<span class="cp">#if WITH_EDITOR
</span><span class="kt">void</span> <span class="n">UEnemyDataAsset</span><span class="o">::</span><span class="n">PostEditChangeProperty</span><span class="p">(</span><span class="n">FPropertyChangedEvent</span><span class="o">&</span> <span class="n">PropertyChangedEvent</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Super</span><span class="o">::</span><span class="n">PostEditChangeProperty</span><span class="p">(</span><span class="n">PropertyChangedEvent</span><span class="p">);</span>
<span class="k">const</span> <span class="n">FProperty</span><span class="o">*</span> <span class="n">PropertyThatChanged</span> <span class="o">=</span> <span class="n">PropertyChangedEvent</span><span class="p">.</span><span class="n">MemberProperty</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">PropertyThatChanged</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">PropertyThatChanged</span><span class="o">-></span><span class="n">GetFName</span><span class="p">()</span> <span class="o">==</span> <span class="n">GET_MEMBER_NAME_CHECKED</span><span class="p">(</span><span class="n">UEnemyDataAsset</span><span class="p">,</span> <span class="n">Stats</span><span class="p">))</span>
<span class="p">{</span>
<span class="k">const</span> <span class="n">UEnemyDataAsset</span><span class="o">*</span> <span class="n">Parent</span> <span class="o">=</span> <span class="n">GetInheritedParent</span><span class="p">();</span>
<span class="n">Stats</span><span class="p">.</span><span class="n">UpdateInherited</span><span class="p">(</span><span class="n">Parent</span> <span class="o">?</span> <span class="o">&</span><span class="n">Parent</span><span class="o">-></span><span class="n">Stats</span> <span class="o">:</span> <span class="nb">nullptr</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="cp">#endif
</span>
<span class="n">UEnemyDataAsset</span><span class="o">*</span> <span class="n">UEnemyDataAsset</span><span class="o">::</span><span class="n">GetInheritedParent</span><span class="p">()</span> <span class="k">const</span>
<span class="p">{</span>
<span class="n">UEnemyDataAsset</span><span class="o">*</span> <span class="n">Parent</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">HasAnyFlags</span><span class="p">(</span><span class="n">RF_ClassDefaultObject</span><span class="p">))</span>
<span class="p">{</span>
<span class="n">Parent</span> <span class="o">=</span> <span class="n">Cast</span><span class="o"><</span><span class="n">UEnemyDataAsset</span><span class="o">></span><span class="p">(</span><span class="n">GetClass</span><span class="p">()</span><span class="o">-></span><span class="n">GetSuperClass</span><span class="p">()</span><span class="o">-></span><span class="n">GetDefaultObject</span><span class="p">());</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="c1">// It's a data asset</span>
<span class="n">Parent</span> <span class="o">=</span> <span class="n">Cast</span><span class="o"><</span><span class="n">UEnemyDataAsset</span><span class="o">></span><span class="p">(</span><span class="n">GetClass</span><span class="p">()</span><span class="o">-></span><span class="n">GetDefaultObject</span><span class="p">());</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">Parent</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div> </div>
</div>
</figure>ben uiMake sure changes made in the parent are propagated to children.What's the deal with Verse?2022-12-11T00:00:00+00:002022-12-11T00:00:00+00:00https://benui.ca/unreal/what-is-verse<p class="notice--danger"><strong>Disclaimer:</strong> My aim here is to summarize the most recent Verse information
and translate some of the more technical parts into concrete examples for an
audience familiar with C++. This is based on what they have announced as of
December 2022, and my own limited interpretation of it.</p>
<p class="notice--danger"><strong>tl;dr:</strong> I'm kind of stupid, I get stuff wrong a lot.</p>
<p>Verse is an as-yet unreleased language from Epic. It was first announced in
<a href="https://twitter.com/saji8k/status/1339709691564179464">December 2020</a>, and not
much was known about it until last week one of the authors Simon Peyton-Jones
gave a talk at a Haskell convention, so now we have a lot more information!</p>
<p>If you have an advanced degree in programming language design, check out their
paper <a href="https://simon.peytonjones.org/assets/pdfs/verse-conf.pdf">The Verse Calculus: a core calculus for functional logic
programming</a>. The
<a href="https://simon.peytonjones.org/assets/pdfs/haskell-exchange-22.pdf">slides from the
talk</a> are
a bit easier to understand, but it was aimed at people with a lot of Haskell
experience.</p>
<p>My aim here is to try to translate the slides into something a bit more
familiar to C++ programmers.</p>
<h2 id="why-is-verse-a-thing">Why is Verse a thing?</h2>
<p>Hearing about Verse, a lot of people have the same concerns:</p>
<ul>
<li><em>Is this going to replace C++?</em></li>
<li><em>Is this going to replace Blueprints?</em></li>
<li><em>Do I have to learn this to continue using Unreal?</em></li>
</ul>
<p>The answer to all of these is <em>no</em>.</p>
<p>As far as I can tell, Verse is not intended as a replacement for Blueprints or
C++. It's being described as a "programming language for the Metaverse". In
more concrete terms it's a new language designed to work at scale and make it
easier for players to add user-generated content to games. As we've seen from
a <a href="https://twitter.com/saji8k/status/1339709691564179464">December 2020 Tweet</a>,
it's already being tested out with Fortnite's user-created game modes.</p>
<h2 id="lets-learn-about-verse">Let's learn about Verse</h2>
<p>I've divided this into two parts:</p>
<ol>
<li>The basic features of Verse. Get used to the syntax, before you hit the
really weird stuff.</li>
<li>The really weird stuff.</li>
</ol>
<h2 id="basic-features">Basic Features</h2>
<h3 id="variables">Variables</h3>
<p>Let's start with the basics of syntax. In Verse, there are two different ways
to declare a variable and assign it a value.</p>
<p>Declaring a variable and assigning it a value uses <code class="language-plaintext highlighter-rouge">:=</code></p>
<table class="code-comparison">
<tr>
<th>Verse</th>
<th>C++</th>
</tr>
<tr>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="n">x</span> <span class="p">:</span><span class="o">=</span> <span class="mi">5</span><span class="p">;</span></code></pre></figure>
</td>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">int</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span></code></pre></figure>
</td>
</tr>
</table>
<p>Like C++, you can separate declaring a variable, and giving it a value. In this
case you can "bind" x to the value of 5 with just <code class="language-plaintext highlighter-rouge">x = 5</code>.</p>
<table class="code-comparison">
<tr>
<th>Verse</th>
<th>C++</th>
</tr>
<tr>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="n">x</span><span class="p">:</span><span class="nb">int</span><span class="p">;</span>
<span class="n">x</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span></code></pre></figure>
</td>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">int</span> <span class="n">x</span><span class="p">;</span>
<span class="n">x</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span></code></pre></figure>
</td>
</tr>
</table>
<p>But unlike C++, all variables are effectively <code class="language-plaintext highlighter-rouge">const</code> (once given a value).
This is an important feature of the language both from a philosophical and
practical standpoint.</p>
<p><em>Edit:</em> The December 2022 slides mention that there might be a way to make some
variables mutable, so maybe this isn't a hard-and-fast rule.</p>
<table class="code-comparison">
<tr>
<th>Verse</th>
<th>C++</th>
</tr>
<tr>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="n">x</span><span class="p">:</span><span class="nb">int</span><span class="p">;</span>
<span class="n">x</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
<span class="n">x</span> <span class="o">=</span> <span class="mi">6</span><span class="p">;</span> <span class="c">// Error</span></code></pre></figure>
</td>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">int</span> <span class="n">x</span><span class="p">;</span>
<span class="n">x</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
<span class="n">x</span> <span class="o">=</span> <span class="mi">6</span><span class="p">;</span> <span class="c1">// OK!</span></code></pre></figure>
</td>
</tr>
</table>
<h3 id="tuple">Tuple</h3>
<p>Verse supports tuples/arrays that look a lot like C++ arrays.</p>
<table class="code-comparison">
<tr>
<th>Verse</th>
<th>C++</th>
</tr>
<tr>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="n">ages</span> <span class="p">:</span><span class="o">=</span> <span class="p">(</span><span class="mi">23</span><span class="p">,</span><span class="mi">49</span><span class="p">)</span>
<span class="n">ages</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span> <span class="c">// returns 23</span></code></pre></figure>
</td>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">int</span> <span class="n">ages</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">23</span><span class="p">,</span> <span class="mi">49</span><span class="p">};</span>
<span class="n">ages</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span> <span class="c1">// returns 23</span></code></pre></figure>
</td>
</tr>
</table>
<h3 id="choice">Choice</h3>
<p>A core feature of Verse are "choices".</p>
<p>A variable is only ever bound to a single value. Although that value could be
a tuple</p>
<table class="code-comparison">
<tr>
<th>Verse</th>
<th>C++</th>
</tr>
<tr>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="n">x</span> <span class="p">:</span><span class="o">=</span> <span class="p">(</span><span class="mi">1</span><span class="p">|</span><span class="mi">7</span><span class="p">|</span><span class="mi">2</span><span class="p">);</span>
<span class="n">x</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span> <span class="c">// Executed three times</span>
<span class="c">// With values 2, 8, then 3</span></code></pre></figure>
</td>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">x</span> <span class="o">:</span> <span class="p">{</span><span class="mi">1</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="mi">2</span><span class="p">})</span>
<span class="p">{</span>
<span class="n">x</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">// Do something</span>
<span class="p">}</span></code></pre></figure>
</td>
</tr>
</table>
<p>More complex things are possible by mixing choices and tuples:</p>
<table class="code-comparison">
<tr>
<th>Verse</th>
<th>C++</th>
</tr>
<tr>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="nf">print</span><span class="p">((</span><span class="mi">1</span><span class="p">|</span><span class="mi">2</span><span class="p">),</span> <span class="p">(</span><span class="mi">7</span><span class="p">|</span><span class="mi">8</span><span class="p">));</span>
<span class="c">// This evaluates to 4 separate tuples:</span>
<span class="c">// (1,7)</span>
<span class="c">// (1,8)</span>
<span class="c">// (2,7)</span>
<span class="c">// (2,8)</span></code></pre></figure>
</td>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">int</span> <span class="n">xs</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">};</span>
<span class="kt">int</span> <span class="n">ys</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">7</span><span class="p">,</span><span class="mi">8</span><span class="p">};</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">x</span> <span class="o">:</span> <span class="n">xs</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">y</span> <span class="o">:</span> <span class="n">ys</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// do something with x, y</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
</td>
</tr>
</table>
<h3 id="conditionals-and-comparison">Conditionals and Comparison</h3>
<p>The slides for the talk show conditionals all on the same line, but from other
talks it seems that they are going for a Python-like syntax.</p>
<table class="code-comparison">
<tr>
<th>Verse</th>
<th>C++</th>
</tr>
<tr>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="n">dog_age</span><span class="p">:</span><span class="nb">int</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">dog_age</span> <span class="o"><</span> <span class="mi">2</span><span class="p">):</span>
<span class="c">// it's a puppy</span>
<span class="k">else</span><span class="p">:</span>
<span class="c">// it's a dog</span></code></pre></figure>
</td>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">int</span> <span class="n">dog_age</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">dog_age</span> <span class="o"><</span> <span class="mi">2</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// it's a puppy</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="c1">// it's a dog</span>
<span class="p">}</span></code></pre></figure>
</td>
</tr>
</table>
<p>Confusingly, or interestingly, depending on your position, <code class="language-plaintext highlighter-rouge">x=5</code> can be used to
say that x is equal to the value "5", and also be used in a conditional:</p>
<table class="code-comparison">
<tr>
<th>Verse</th>
<th>C++</th>
</tr>
<tr>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="n">dog_age</span><span class="p">:</span><span class="nb">int</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">dog_age</span> <span class="o">=</span> <span class="mi">0</span><span class="p">):</span>
<span class="c">// it's newborn</span>
<span class="k">else</span><span class="p">:</span>
<span class="c">// it's not</span></code></pre></figure>
</td>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">int</span> <span class="n">dog_age</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">dog_age</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// it's newborn</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="c1">// it's not</span>
<span class="p">}</span></code></pre></figure>
</td>
</tr>
</table>
<p>It's also possible to compare against multiple numbers.</p>
<table class="code-comparison">
<tr>
<th>Verse</th>
<th>C++</th>
</tr>
<tr>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="n">age</span><span class="p">:</span><span class="nb">int</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="mi">13</span> <span class="o"><</span> <span class="n">age</span> <span class="o"><</span> <span class="mi">19</span><span class="p">):</span>
<span class="c">// teenager</span></code></pre></figure>
</td>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">int</span> <span class="n">age</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="mi">13</span> <span class="o"><</span> <span class="n">age</span> <span class="o">&&</span> <span class="n">age</span> <span class="o"><</span> <span class="mi">19</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// teenager</span>
<span class="p">}</span></code></pre></figure>
</td>
</tr>
</table>
<h2 id="weird-stuff">Weird Stuff</h2>
<h3 id="everything-is-math">Everything is Math</h3>
<p>One core philosophical difference between Verse and C++ is that Verse tries to
be way closer to Math.</p>
<p>In C++, when you write <code class="language-plaintext highlighter-rouge">x = 5;</code> you are telling the computer "take the value
5 and store it in the space that we named x".</p>
<p>In Verse, when you write <code class="language-plaintext highlighter-rouge">x := 5;</code> you are telling the computer that for
<strong>all</strong> uses of <code class="language-plaintext highlighter-rouge">x</code>, it should be treated as equal to 5. Note that this applies
for <strong>all</strong> uses of <code class="language-plaintext highlighter-rouge">x</code>, not just <strong>subsequent</strong> values of <code class="language-plaintext highlighter-rouge">x</code>.</p>
<p>Which brings us neatly to the next point.</p>
<h3 id="order-does-not-matter">Order Does not Matter</h3>
<p>The following is completely valid Verse:</p>
<table class="code-comparison">
<tr>
<th>Verse</th>
<th>C++</th>
</tr>
<tr>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="n">y</span> <span class="p">:</span><span class="o">=</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">x</span> <span class="p">:</span><span class="o">=</span> <span class="mi">3</span><span class="p">;</span></code></pre></figure>
</td>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">int</span> <span class="n">y</span> <span class="o">=</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">// ERROR</span>
<span class="kt">int</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span></code></pre></figure>
</td>
</tr>
</table>
<p>As mentioned previously, it's important to remember that Verse is designed not
to be a series of instructions that you give the computer, but more like
a series of conditions or relationships that you are telling the computer hold
true.</p>
<p>First we tell the computer that "y is equal to the value of x, plus one".</p>
<p>Then we tell it x is equal to 3.</p>
<p>This is possible because once the value of a variable has been defined, it
cannot be changed. We know for the rest of that scope that <code class="language-plaintext highlighter-rouge">x = 3</code></p>
<h3 id="there-are-no-booleans">There are no booleans</h3>
<p>This is more of a clickbait title but it got you here.</p>
<p>In Verse, which branch of a conditional is run depends on whether the
expression <strong>succeeds</strong> or <strong>fails</strong>.</p>
<ul>
<li><strong>Success</strong> is defined as "returning one or more values"</li>
<li><strong>Failure</strong> is defined as "returning zero values"</li>
</ul>
<p>Failure has a shorthand of <code class="language-plaintext highlighter-rouge">false?</code>, which we use later.</p>
<p>Why does this matter?</p>
<p>Taking the example of using choices and expanding them out, we can see that the
example below executes statement e1 because the conditional returns one or more
values:</p>
<table class="code-comparison">
<tr>
<th>Verse</th>
<th>C++</th>
</tr>
<tr>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="n">dice_result</span> <span class="p">:</span><span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">dice_result</span><span class="o">=</span><span class="p">(</span><span class="mi">1</span><span class="p">|</span><span class="mi">3</span><span class="p">|</span><span class="mi">5</span><span class="p">)):</span>
<span class="c">// Dice landed on odd-numbered side</span></code></pre></figure>
</td>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">int</span> <span class="n">dice_result</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">dice_result</span> <span class="o">==</span> <span class="mi">1</span>
<span class="o">||</span> <span class="n">dice_result</span> <span class="o">==</span> <span class="mi">3</span>
<span class="o">||</span> <span class="n">dice_result</span> <span class="o">==</span> <span class="mi">5</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// Dice landed on odd-numbered side</span>
<span class="p">}</span></code></pre></figure>
</td>
</tr>
</table>
<h4 id="logical-or">Logical OR</h4>
<p>Logical OR in verse uses the choice operator <code class="language-plaintext highlighter-rouge">|</code>. Remember that branches are
executed if the if condition succeeds, with success being defined as having one
or more values.</p>
<p>So it would make sense that the choice operator, where every part of it is
evaluated, would work as an OR. So long as one of them returns non-null, then
the conditional would evaluate to <strong>success</strong>.</p>
<table class="code-comparison">
<tr>
<th>Verse</th>
<th>C++</th>
</tr>
<tr>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="n">age</span><span class="p">:</span><span class="nb">int</span><span class="p">;</span> <span class="n">grumpiness</span><span class="p">:</span><span class="nb">int</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">age</span> <span class="o">></span> <span class="mi">80</span> <span class="p">|</span> <span class="n">grumpiness</span> <span class="o">></span> <span class="mi">99</span><span class="p">):</span>
<span class="c">// very old or very grumpy</span></code></pre></figure>
</td>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">int</span> <span class="n">age</span><span class="p">;</span> <span class="kt">int</span> <span class="n">grumpiness</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">age</span> <span class="o">></span> <span class="mi">80</span> <span class="o">||</span> <span class="n">grumpiness</span> <span class="o">></span> <span class="mi">99</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// very old or very grumpy</span>
<span class="p">}</span></code></pre></figure>
</td>
</tr>
</table>
<h4 id="logical-and">Logical AND</h4>
<p>Logical AND in Verse uses the tuple comma. This is not quite as clear as with
the choice operator being used like OR.</p>
<p>As shown on slide 25, a tuple with any failures is itself evaluated as
a failure.</p>
<table class="code-comparison">
<tr>
<th>Verse</th>
<th></th>
</tr>
<tr>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="p">(</span><span class="mi">1</span><span class="o"><</span><span class="mi">3</span><span class="p">,</span> <span class="mi">2</span><span class="o"><</span><span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="o"><</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="o"><</span><span class="mi">3</span><span class="p">)</span>
<span class="c">// This then evaluates to the following,</span>
<span class="c">// because comparisons return the left-hand operator</span>
<span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="k">false</span><span class="o">?</span><span class="p">,</span> <span class="k">false</span><span class="o">?</span><span class="p">)</span>
<span class="c">// Which then evaluates to</span>
<span class="k">false</span><span class="o">?</span></code></pre></figure>
</td>
<td class="highlighter-rouge">
</td>
</tr>
</table>
<h3 id="comparison-returns-the-left-hand-value-if-they-succeed">Comparison returns the left-hand value if they succeed</h3>
<p>This is just kind of a weird feature of the language and feeds in to why you
can write some complex conditionals.</p>
<table class="code-comparison">
<tr>
<th>Verse</th>
<th>C++</th>
</tr>
<tr>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="n">x</span> <span class="p">:</span><span class="o">=</span> <span class="mi">10</span><span class="p">;</span>
<span class="n">y</span> <span class="p">:</span><span class="o">=</span> <span class="p">(</span><span class="n">x</span> <span class="o"><</span> <span class="mi">15</span><span class="p">)</span> <span class="o">+</span> <span class="mi">2</span><span class="p">;</span>
<span class="c">// Because x < 15 is true,</span>
<span class="c">// the left-hand operand is returned.</span>
<span class="c">// So y is set to 10 + 2</span></code></pre></figure>
</td>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">int</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">10</span><span class="p">;</span>
<span class="n">y</span> <span class="o">=</span> <span class="p">(</span><span class="n">x</span> <span class="o"><</span> <span class="mi">15</span> <span class="o">?</span> <span class="n">x</span> <span class="o">:</span> <span class="mi">0</span><span class="p">)</span> <span class="o">+</span> <span class="mi">2</span><span class="p">;</span></code></pre></figure>
</td>
</tr>
</table>
<h3 id="type-is-a-function">Type is a Function</h3>
<p>This was dropped at the end and details were kind of scant but it seemed to
imply that it was easy to define new types.</p>
<p><code class="language-plaintext highlighter-rouge">int</code> is a function that returns the value for values that pass the test of
being an integer, and fail otherwise.</p>
<table class="code-comparison">
<tr>
<th>Verse</th>
<th></th>
</tr>
<tr>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="n">price</span> <span class="o">=</span> <span class="mi">200</span><span class="p">;</span>
<span class="n">x</span><span class="p">:</span><span class="nb">int</span><span class="p">;</span>
<span class="n">x</span> <span class="o">=</span> <span class="mf">2.5</span><span class="p">;</span> <span class="c">// This would fail</span></code></pre></figure>
</td>
<td class="highlighter-rouge">
</td>
</tr>
</table>
<p>So you could define a new type called <code class="language-plaintext highlighter-rouge">isPositive</code>, I have no idea what the
syntax would be to do that, but it would let you set up rules for valid uses of
that type.</p>
<table class="code-comparison">
<tr>
<th>Verse</th>
<th></th>
</tr>
<tr>
<td class="highlighter-rouge">
<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="n">hat_price</span><span class="p">:</span><span class="n">isPositive</span><span class="p">;</span>
<span class="n">price</span> <span class="o">=</span> <span class="mi">200</span><span class="p">;</span>
<span class="n">shoe_price</span><span class="p">:</span><span class="n">isPositive</span><span class="p">;</span>
<span class="n">shoe_price</span> <span class="o">=</span> <span class="o">-</span><span class="mi">50</span><span class="p">;</span> <span class="c">// FAIL</span></code></pre></figure>
</td>
<td class="highlighter-rouge">
</td>
</tr>
</table>
<p>This might explain why there's a <code class="language-plaintext highlighter-rouge">bool</code> type in the old Fortnite example Verse
we saw in 2020. Or maybe that's just an old feature of the language that was
removed.</p>
<h2 id="thoughts">Thoughts</h2>
<p>It's still way too early to tell much about Verse (but that doesn't stop us
wildly speculating and getting ourselves worked up!). So far we have
a work-in-progress academic paper that talks about some very cool but unusual
language features, and some screenshots of more "normal"-looking code.</p>
<p>I'm wondering if some of the more "odd" features are things that will not get
used much in day-to-day programming. For example a language like Perl has some
horrendous features that could be described at length, but to write "<em>good
Perl</em>" you just avoid those features.</p>
<p>Being able to "use" variables before they are defined, and those definitions
being far away in the code could be something that we try to avoid as
programmers, just to avoid the cognitive load.</p>
<p>I hope they release a prototype version of the language soon so we can mess
around with it.</p>ben uiNo boolean? Const everything? Out-of-order evaluation?How to Use Mastodon, as an Unreal Engine Dev2022-11-13T00:00:00+00:002022-11-13T00:00:00+00:00https://benui.ca/unreal/mastodon<p>I created my <a href="https://mastodon.gamedev.place/@_benui">main Mastodon account</a>
back in 2020, but I didn't really use it until the last few months. I've
typically shared things I made on Twitter and learned so much there, but as
Twitter has become less and less stable I've been looking around for somewhere
else to make connections.</p>
<p>I think that Mastodon has a lot of potential for building an online Unreal
Engine community. So far I've found it a really different vibe to Twitter,
a lot more small and cozy. A lot of fellow devs have expressed interest but
found Mastodon hard to get into, so hopefully this will help anyone interested
in trying it out.</p>
<h2 id="the-basics">The Basics</h2>
<p>Mastodon is often mis-characterized as "Twitter but there are lots of different
servers", and that's not quite accurate:</p>
<ul>
<li>Mastodon is <strong>software</strong> that you install on a <strong>server</strong>, like <em>Wordpress</em>.</li>
<li>Mastodon is <em>not</em> a monolithic service like Twitter/Tumblr/Facebook.</li>
<li>Copies of Mastodon installed on different servers, known as <strong>Instances</strong>, can
talk to each other.</li>
</ul>
<figure>
<div class="figcontent">
<div>
<a href="/assets/unreal/mastodon-character.png">
<img src="/assets/unreal/mastodon-character.png" alt="" />
</a>
</div>
</div>
</figure>
<h2 id="instances">Instances</h2>
<p>So an <strong>instance</strong> is just a copy of Mastodon installed on a server. What's the
deal with that?</p>
<ul>
<li>Having an account on an instance lets you more easily see its Local Timeline
which can be kind of nice to get the feeling of community and make new
friends.</li>
<li>You can still talk to and see the messages of anyone on <em>any</em> instance. Just
like being able to send email to someone that uses a different email
provider.</li>
<li>You can have accounts on multiple instances, if you want to, but it's not
really necessary.</li>
<li>You can move instances at any time, bringing who you're following and
followers. Note that your messages are <em>not moved</em>.</li>
<li>Instance administrators deal with content moderation and can choose to block
messages from specific instances. For example there is a ban list of servers
associated with far-right and hateful content that are banned by some
servers.</li>
</ul>
<h3 id="how-to-choose-an-instance">How to choose an Instance</h3>
<p>For me, it was a pretty easy decision to join
<a href="https://mastodon.gamedev.place/">mastodon.gamedev.place</a>. It has a large
number of active users and is moderated by <a href="https://mastodon.gamedev.place/@aras">Aras of Unity
fame</a>.</p>
<p>At the time of writing this, there are also
<a href="https://peoplemaking.games/">peoplemaking.games</a> and
<a href="https://gamedev.lgbt/">gamedev.lgbt</a> that are major gamedev-related instances.</p>
<figure>
<figcaption>
<p class="figure-title">mastodon.gamedev.place's mascot and emoji</p>
</figcaption>
<div class="figcontent">
<div>
<a href="/assets/unreal/mastodon-gamedev-mascot.png">
<img src="/assets/unreal/mastodon-gamedev-mascot.png" alt="" />
</a>
</div>
</div>
</figure>
<p>One of the kind of weird things about Mastodon is that you are associated with
a particular instance, but you can talk to anyone. There are a few factors to
consider when choosing an instance:</p>
<ul>
<li>Does the Local Timeline interest you? It may not be a factor but some people
like the feeling of community that comes with a friendly, lively Local Timeline.</li>
<li>Does the administrators' stance on moderation align with your own? Do you
want something more or less heavily moderated?</li>
<li>Is the instance over-subscribed? Things can get slow if an instance has
a sudden influx of new users.</li>
<li>Does it have a <em>cool URL</em>?</li>
<li>Do they have <em>cool emojis</em>?</li>
</ul>
<h2 id="etiquette-and-culture">Etiquette and Culture</h2>
<p>Mastodon has existed since since 2016 and has built up its own culture. Here
are some of the things I've learned that differ from Twitter.</p>
<h3 id="avoid-uploading-videos-to-the-instance">Avoid Uploading Videos to the Instance</h3>
<p>Try to avoid uploading videos as message attachments. Most instances are
relatively small and short on funding and videos can take up a big chunk of
storage. Upload longer videos to a separate video host and link them if you
can.</p>
<h3 id="use-content-warnings">Use Content Warnings</h3>
<p>It seems to be OK to use the Content Warning (CW) preview feature not just for
things that might negatively affect people, but for filtering out off-topic
discussions. For instance I usually talk about Unreal Engine but I've used the
CW header/body formatting to show that a post is about something outside what
I usually talk about.</p>
<h3 id="set-your-messages-to-unlisted-by-default">Set your messages to Unlisted by default</h3>
<p>Mastodon has four different privacy settings for posts:</p>
<ul>
<li><strong>Public:</strong> These will show up on the instance's Local Timeline. It's great to
post things here that will have broad appeal within your instance. For
example tutorials or showing.</li>
<li><strong>Unlisted:</strong> Anything you post will not show up in the local timeline, even if
you add hashtags to it. I use this for anything more "noisy" or only relevant
to people that follow me. It's kind of considered bad manners to flood the
Local Timeline.</li>
<li><strong>Followers Only:</strong> Followers can see posts but cannot re-share them.</li>
<li><strong>Mentioned people only:</strong> Mentioned people can see posts but cannot re-share
them.</li>
</ul>
<p>Technically there is the concept of a "Direct Message" but as previously
documented server admins can see the message content and if you mention someone
in a Direct Message, they can see the message. So I wouldn't use DMs much to be
honest.</p>
<h2 id="user-tips">User Tips</h2>
<h3 id="only-hashtags-are-searchable">Only Hashtags Are Searchable</h3>
<p>It is not possible to search for public messages just by using plain-text
search. Only hashtags are searchable. So if you want your posts to be
discovered, make sure to add a few hashtags to them.</p>
<h3 id="create-a-column-with-all-the-unreal-hashtags">Create a Column with All the Unreal Hashtags</h3>
<p>I used this to set up a single column in the Mastodon web client that shows all
messages with any of the many Unreal-related hashtags that people use.</p>
<ol>
<li>First make sure that you have the Tweetdeck-like column interface enabled.
Go to your Preferences, then under Appearance make sure that <strong>Enabled
advanced web interface</strong> is checked.</li>
<li>Save changes.</li>
<li>At the top-left of the screen, Search for <em>"#UnrealEngine"</em> and click on the
#UnrealEngine entry that pops out.</li>
<li>A new tab should appear, showing all the messages containing #UnrealEngine.</li>
<li>Click the settings button in the top-right of the column, and choose "+ Pin"</li>
<li>The settings should now show more options, including a "Include additional
tags for this column" toggle.</li>
<li>Then add all the other tags, like <em>#UE5</em>, <em>#UE5Tips</em>, <em>#Unreal</em> and anything
else you can think of.</li>
</ol>
<figure>
<div class="figcontent">
<div>
<a href="/assets/unreal/mastodon-search-hashtags.png">
<img src="/assets/unreal/mastodon-search-hashtags.png" alt="" />
</a>
</div>
</div>
</figure>
<h3 id="verify-your-links">Verify your links</h3>
<p>Profiles in Mastodon let you show multiple links that you are associated with.
There is also the concept of verification where you can prove that you are the
owner of the linked website. It doesn't even cost $8!</p>
<p>Just add a link back to your Mastodon page <code class="language-plaintext highlighter-rouge">rel="me"</code> somewhere on your site, and you will get a green check next the link. The snippet is shown on the Profile Settings page.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><a</span> <span class="na">rel=</span><span class="s">"me"</span> <span class="na">href=</span><span class="s">"https://mastodon.gamedev.place/@_benui"</span><span class="nt">></span>Mastodon<span class="nt"></a></span>
</code></pre></div></div>
<figure>
<div class="figcontent">
<div>
<a href="/assets/unreal/mastodon-verified-link.png">
<img src="/assets/unreal/mastodon-verified-link.png" alt="" />
</a>
</div>
</div>
</figure>
<h2 id="what-next">What Next?</h2>
<p>I really like the <a href="https://mastodon.gamedev.place/public">Local Timeline at
mastodon.gamedev.place</a>. There's always
interesting projects that people are sharing and everyone is super friendly.
I can see myself continuing to use <a href="https://twitter.com/_benui">Twitter</a> but
I'm glad that there is an alternative for if or when it goes down.</p>
<p>Come join and say hi
<a href="https://mastodon.gamedev.place/@_benui">@_benui@mastodon.gamedev.place</a>!</p>
<p><strong>Update:</strong> I moved to my own instance because I'm weird like that.</p>ben uiIt's not much like Twitter.How I Stopped Being Terrified of Slate2022-10-27T00:00:00+00:002022-10-27T00:00:00+00:00https://benui.ca/unreal/slate-gamedevday-2022<p>This is a presentation I gave for the JetBrains GameDev Day Online 2022 event.
The slides are embedded above, click the arrows in the bottom-right to
navigate, or us the keyboard shortcuts mentioned below.</p>
<ul>
<li><a href="https://github.com/benui-dev/UE-SlateExample">GitHub repo</a></li>
</ul>
<p>Slide controls:</p>
<table>
<thead>
<tr>
<th>Input</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Arrow keys</strong></td>
<td>Change slide</td>
</tr>
<tr>
<td><strong>F</strong></td>
<td>Fullscreen (you may first need to click to focus)</td>
</tr>
<tr>
<td><strong>S</strong></td>
<td>Presenter view</td>
</tr>
<tr>
<td><strong>O</strong>/<strong>Escape</strong></td>
<td>Overview mode</td>
</tr>
</tbody>
</table>ben uiTalk for JetBrains GameDev Day Online 2022Unreal UI Challenges2022-10-13T00:00:00+00:002022-10-13T00:00:00+00:00https://benui.ca/unreal/ui-challenges<p>The best way to improve in a skill is to practice it. To learn a language, you
read and speak it, to improve at a sport, you spend time playing that sport and
doing exercises. UI programming is no different.</p>
<p>This page has a list of UI elements, sorted by their complexity. The idea is
that you try to implement them and gradually learn by doing.</p>
<h2 id="how-to-use-this-page">How to Use This Page</h2>
<p>First, <a href="https://github.com/benui-dev/UE-UI-Challenges">download the Unreal Project from
GitHub</a>. It contains
some code for easily testing out your widgets, and some placeholders to get you
started. Look at the Readme file for more details on the Unreal implementation.</p>
<p>Share your solutions on GitHub, and tag them with the keyword
<a href="https://github.com/topics/unreal-ui-challenges"><code class="language-plaintext highlighter-rouge">unreal-ui-challenges</code></a></p>
<p>That way others can learn from your example!</p>
<h2 id="challenges">Challenges</h2>
<div class="entries-grid">
<div class="entries-grid">
<h3>Score</h3>
<p>Difficulty easy</p>
<p>Showing the player score can be as simple as a text box with some numbers
in it. It's a great place to start your UI journey!
</p>
<h4 class="no_toc">Implementation<span class="tooltip-base">?<span class="tooltip-text">Suggestions on how to solve this UI challenge.</span></span></h4>
<ul class="implementation">
<li>Display numbers using one or more Text Block widgets</li>
<li>Display, or by setting an Image widget</li>
</ul>
<h4 class="no_toc">Sub-goals<span class="tooltip-base">?<span class="tooltip-text">If you want to, try implementing these features too.</span></span></h4>
<ul>
<li>Make sure that numbers are fixed-width</li>
<li>Pad score so it always shows</li>
<li>Animate numbers changing as score increases. Tween the number from the old value to the new value.</li>
<li>Show score gains as a separate number below the main score.</li>
<li>Use a single image for numbers 0-9 and use material settings to change which number is shown on an array of UImage widgets.</li>
</ul>
<h4 class="no_toc">Widgets<span class="tooltip-base">?<span class="tooltip-text">You will probably want to use these widgets in your solution.</span></span></h4>
<ul>
<li><a href="https://docs.unrealengine.com/5.0/en-US/API/Runtime/UMG/Components/UTextBlock/">UTextBlock</a></li>
<li><a href="https://docs.unrealengine.com/5.0/en-US/API/Runtime/UMG/Components/UImage/">UImage</a></li>
</ul>
<h4 class="no_toc">Data<span class="tooltip-base">?<span class="tooltip-text">Feel free to use this example data when implementing your solution.</span></span></h4>
<pre></pre>
<div class="screenshots">
<img src="/assets/unreal/challenges/Yoshis-Woolly-World_5168.jpg" />
<img src="/assets/unreal/challenges/Super-Mario-Galaxy_17009.jpg" />
<img src="/assets/unreal/challenges/Ratchet-and-Clank-Quest-for-Booty_36485.jpg" />
<img src="/assets/unreal/challenges/New-Super-Mario-Bros-2_38373.jpg" />
</div>
<p><a href="https://www.gameuidatabase.com/index.php?&set=1&scrn=139">More screenshot examples on Game UI Database</a></p>
</div>
<div class="entries-grid">
<h3>Healthbar</h3>
<p>Difficulty easy</p>
<p>Health bars are a great something</p>
<h4 class="no_toc">Implementation<span class="tooltip-base">?<span class="tooltip-text">Suggestions on how to solve this UI challenge.</span></span></h4>
<ul class="implementation">
</ul>
<h4 class="no_toc">Sub-goals<span class="tooltip-base">?<span class="tooltip-text">If you want to, try implementing these features too.</span></span></h4>
<ul>
</ul>
<h4 class="no_toc">Widgets<span class="tooltip-base">?<span class="tooltip-text">You will probably want to use these widgets in your solution.</span></span></h4>
<ul>
<li><a href="https://docs.unrealengine.com/5.0/en-US/API/Runtime/UMG/Components/UProgressBar/">UProgressBar</a></li>
</ul>
<h4 class="no_toc">Data<span class="tooltip-base">?<span class="tooltip-text">Feel free to use this example data when implementing your solution.</span></span></h4>
<pre></pre>
<div class="screenshots">
<img src="/assets/unreal/challenges/Dark-Souls_8192.jpg" />
<img src="/assets/unreal/challenges/Fire-Emblem-Three-Houses_17582.jpg" />
<img src="/assets/unreal/challenges/Ni-no-Kuni-Wrath-of-the-White-Witch_13810.jpg" />
<img src="/assets/unreal/challenges/New-Super-Luckys-Tale_25470.jpg" />
</div>
<p><a href="">More screenshot examples on Game UI Database</a></p>
</div>
</div>ben uiImprove your Unreal Engine UI skills by implementing a series of widgets.Keyboard-only Blueprint Editing2022-10-06T00:00:00+00:002022-10-06T00:00:00+00:00https://benui.ca/unreal/keyboard-only-blueprint-editing<p>I've written a lot of Blueprint logic in the past, to the point of getting RSI
from using a mouse too much. But I much prefer the speed and
keyboard-friendliness of writing my logic in C++.</p>
<p>However there are some plugins that make editing Blueprints a lot faster.</p>
<h2 id="blueprint-assist">Blueprint Assist</h2>
<p>If you have to spend more than 5 minutes a day using Blueprints, you have to
get <a href="https://www.unrealengine.com/marketplace/en-US/product/blueprint-assist">Blueprint
Assist</a>.</p>
<p>Some of the features include:</p>
<ul>
<li>Automatically formatting node arrangement.</li>
<li>Speed up navigation, no more search in blueprints, just Go to the node with
<strong>Ctrl+G</strong>.</li>
<li>Reorder node execution with Shift+Arrow.</li>
<li>Navigate nodes and pins with the keyboard.</li>
<li>Automatically adds calls to parent nodes when adding events. I can't count
how many times I've been bitten by forgetting to call to parent.</li>
</ul>
<figure>
<figcaption>
<p class="figure-title">Blueprint Assist's auto-format function.</p>
</figcaption>
<div class="figcontent">
<div>
<a href="https://www.unrealengine.com/marketplace/en-US/product/blueprint-assist">
<img src="/assets/unreal/blueprint-assist-format.gif" alt="" />
</a>
</div>
</div>
</figure>
<p>It has a huge number of shortcuts but here are some of my favourites.</p>
<table>
<thead>
<tr>
<th><em>Shortcut</em></th>
<th><em>Effect</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>Ctrl+G</td>
<td><strong>Go</strong> to the symbol in the graph</td>
</tr>
<tr>
<td>Tab</td>
<td>Open "add node" window. If node selected, append to exec</td>
</tr>
<tr>
<td>Shift+F</td>
<td><strong>Formats</strong> everything downstream of the selected node</td>
</tr>
<tr>
<td>Ctrl+Shift+A</td>
<td>Quick-<strong>add</strong> (variable, function)</td>
</tr>
<tr>
<td>Arrow keys</td>
<td>Change selected pin</td>
</tr>
<tr>
<td>Ctrl+Arrow keys</td>
<td>Change selected node</td>
</tr>
<tr>
<td>Shift+Arrow keys</td>
<td>Pan view</td>
</tr>
<tr>
<td>Ctrl+Shift+Arrow</td>
<td>Swap order of selected node</td>
</tr>
<tr>
<td>Ctrl+E</td>
<td>Edit value of selected pin, cycle to next editable value</td>
</tr>
<tr>
<td>D</td>
<td>Delete wire for hovered pin/wire</td>
</tr>
<tr>
<td>Alt+O</td>
<td>Swap between Designer/Graph view when editing User Widget Blueprints</td>
</tr>
</tbody>
</table>
<p>For the full list of shortcuts and how to customize them, search "Assist" in
<strong>Editor Preferences > General > Keyboard Shortcuts</strong></p>
<p>Make sure that you <a href="https://github.com/fpwong/BlueprintAssistWiki/wiki/FAQ#sharing-plugin-settings-through-source-control">share your settings with the
team</a>
so everyone has the same formatting.</p>
<h2 id="node-graph-assistant">Node Graph Assistant</h2>
<p><a href="https://www.unrealengine.com/marketplace/en-US/product/node-graph-assistant">Node Graph Assistant</a>
is another must-have plugin. It fixes a bunch of UX nightmares with
Blueprints, and works well with Blueprint Assist.</p>
<figure>
<figcaption>
<p class="figure-title">Node Graph Assistant can auto-connect when nodes are close</p>
</figcaption>
<div class="figcontent">
<div>
<a href="https://www.unrealengine.com/marketplace/en-US/product/node-graph-assistant">
<img src="/assets/unreal/node-graph-assistant-auto-connect.gif" alt="" />
</a>
</div>
</div>
</figure>
<p>Some of the things it fixes:</p>
<ul>
<li>No longer need to drag a wire to an exact tiny pin, you can just drag to the
node (the box itself) and Node Graph Assist will choose the most appropriate
pin within the node.</li>
<li>Drag a node near another and it will automatically connect appropriate pins
from nearby nodes (see animation above).</li>
</ul>
<p>My favourite shortcuts:</p>
<table>
<thead>
<tr>
<th><em>Shortcut</em></th>
<th><em>Effect</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>Alt+C</td>
<td>Connect pins on selected nodes</td>
</tr>
<tr>
<td>Alt+X</td>
<td>Remove node and re-route pins ahead</td>
</tr>
</tbody>
</table>
<h2 id="more">More</h2>
<p>If you have any other suggestions for built-in keyboard shortcuts or other
plugins that help, send me a message on <a href="https://twitter.com/_benui">Twitter</a>.</p>ben uiEdit faster, avoid RSI, improve consistency