<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Celdevs]]></title><description><![CDATA[UE5 Engineer and programmer]]></description><link>https://celdevs.com/</link><image><url>https://celdevs.com/favicon.png</url><title>Celdevs</title><link>https://celdevs.com/</link></image><generator>Ghost 5.48</generator><lastBuildDate>Sat, 07 Mar 2026 14:18:33 GMT</lastBuildDate><atom:link href="https://celdevs.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Exposing templated properties to UPROPERTY]]></title><description><![CDATA[<p>Hello everyone!</p><p>While working as a contractor for <a href="https://www.straykitestudios.com/?ref=celdevs.com">Stray Kite Studios</a> I was tasked with exposing <strong>TOptional&lt;PropertyType&gt; </strong>to <strong>UPROPERTY </strong>for use in the details panel in Unreal Engine. </p><p>I learned a lot from exposing this and I wanted to share my knowledge with the community so that</p>]]></description><link>https://celdevs.com/exposing-templated-properties-to-uproperty/</link><guid isPermaLink="false">6513c926a0f28af8b139b4a9</guid><dc:creator><![CDATA[Celeste Neukirchen]]></dc:creator><pubDate>Wed, 27 Sep 2023 08:25:44 GMT</pubDate><content:encoded><![CDATA[<p>Hello everyone!</p><p>While working as a contractor for <a href="https://www.straykitestudios.com/?ref=celdevs.com">Stray Kite Studios</a> I was tasked with exposing <strong>TOptional&lt;PropertyType&gt; </strong>to <strong>UPROPERTY </strong>for use in the details panel in Unreal Engine. </p><p>I learned a lot from exposing this and I wanted to share my knowledge with the community so that maybe you can also expose a templated property for your needs in your project.</p><p>This isn&apos;t possible without a custom engine, so if you&apos;re hoping to do this solely in a plugin you wouldn&apos;t be able to do this.</p><p>Before I continue, it&apos;s worth mentioning that we only supported the details panel, we never added blueprint-graph support, or allowing the property to be created in the blueprints variables panel. We only needed this to work in CPP classes and show up in details panels and therefore never ventured further. If you want to create a custom property and expose it and have it be blueprint supported you will need to add additional support.</p><p>The last version we supported was 5.0 so the code used may be slightly outdated, however the general concept of how to add your own (templated) properties to UPROPERTY still remains the same.</p><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">Just as I was about to start writing this blog post it seems like Unreal Engine is about to support <strong>TOptional </strong>natively in Unreal Engine, however I still want to write about the experience and what it all entails to support a new property.<br><br>Epic Games commit is here: <a href="https://github.com/EpicGames/UnrealEngine/commit/cc9a77a3996ad42a33c65ca3742f0584c29aacdb?ref=celdevs.com">https://github.com/EpicGames/UnrealEngine/commit/cc9a77a3996ad42a33c65ca3742f0584c29aacdb</a></div></div><h2 id="why-exposing-toptional">Why exposing TOptional?</h2><p>When working with a lot data, like we did in the project we worked on for Stray Kite Studios, we had a lot of optional data that had no good &quot;invalid&quot; value as a lot of properties had any possible value of that property type still as a &quot;valid&quot; value, such as -1 still being valid.</p><p>This is where <strong>TOptional </strong>comes in, however we made our own custom <strong>TScriptOptional </strong>and i&apos;ll go into detail later as to why. </p><p>Now, you could do this with the following, already-supported, code for <strong>UPROPERTY</strong>:</p><pre><code class="language-unrealscript">UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Overrides, meta=(PinHiddenByDefault, InlineEditConditionToggle))
uint8 bOverride_FilmGrainIntensity:1;

UPROPERTY(interp, BlueprintReadWrite, Category = &quot;Film Grain&quot;, meta = (UIMin = &quot;0.0&quot;, UIMax = &quot;1.0&quot;, editcondition = &quot;bOverride_FilmGrainIntensity&quot;))
float FilmGrainIntensity;</code></pre><p>The above code works great, you can set a <strong>InlineEditConditionToggle</strong>, which will conditionally enable or disable editing the bottom property. However, this means two single unconnected properties, which makes it prone to forgetting to check that edit condition as well as double the size of your header if you are using a lot of those.</p><p><strong>TScriptOptional </strong>allows you to, in one property, have the edit condition and value without having to add two <strong>UPROPERTIES</strong>, and without being able to forget checking if the condition property was set.</p><h2 id="primary-concepts">Primary concepts</h2><p>There are 3 concepts, or &quot;things&quot;, that you need to do in order to support exposing a property. </p><ul><li>Create the underlying <strong>FProperty </strong>for your property type</li><li>Update UBT to generate generated code for your <strong>FProperty</strong></li><li>Add support in the engine </li></ul><h2 id="creating-the-property">Creating the Property</h2><p>To create the property I closely followed how <strong>FArrayProperty</strong>, <strong>FMapProperty </strong>and <strong>FFieldPathProperty </strong>implemented their properties.</p><pre><code class="language-unrealscript">class COREUOBJECT_API FOptionalProperty : public FProperty
{
	DECLARE_FIELD(FOptionalProperty, FProperty, CASTCLASS_FOptionalProperty)
	
protected:
	FProperty* Inner;</code></pre><p>The <strong>FOptionalProperty </strong>holds a pointer to the inner value property, which is used for functions such as:</p><pre><code>void ExportTextItem(....);
void ImportText_Internal(...);</code></pre><p>These functions export the property value to and from text for purposes such copy and pasting property values in the details panel.</p><p>To make my life easier (for some part) and getting the alignment and size of the property correct I created a templated child of my<strong> FOptionalProperty</strong>, <strong>TOptionalProperty&lt;PropertyValueType&gt;</strong>.</p><p>This templated class allowed me to use template code to more easily handle the data of the underlying property without having to add additional code to ensure the data I copy/move/clear has the correct alignment and sizes.</p><p>Using a templated <strong>FProperty </strong>however was going to prove a bit annoying when it comes to <strong>UBT/UHT</strong>, however this ended up being possible.</p><p>You will also need to add a new cast flag in <strong>ObjectMacros.h</strong>, just make sure it doesn&apos;t override another cast class flag:<br><code>CASTCLASS_FOptionalProperty&#x2003;&#x2003;&#x2003;= 0x0100000000000000,</code></p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">From what I can tell Unreal Engine doesn&apos;t use a templated property type for generating header code. However, I wasn&apos;t able to experience any negative side effects from using a templated FProperty class for generated headers.</div></div><h3 id="tscriptoptional">TScriptOptional</h3><p>Above I mentioned I created <strong>TScriptOptional</strong>, rather than using <strong>TOptional </strong>for exposing to <strong>UPROPERTY</strong>. &#xA0;This allows more designer-friendly experiences without potential data loss.</p><p><strong>TOptional </strong>clears the value when you toggle off the condition. This means that if a designer would toggle off the optional property, the underlying data would be wiped. So if they later wanted to turn it on again, they would have to set that data up again.</p><p><strong>TScriptOptional </strong>is mostly the implementation of <strong>TOptional </strong>but without the data value being reset.</p><p>Worth noting that Unreal Engine uses <strong>TTypeCompatibleBytes&lt;Type&gt;</strong> for their value data while I directly use <strong>Type </strong>as value type, this was in part possible due to the fact I generate a templated <strong>FProperty </strong>at <strong>UBT/UHT</strong> generation time.</p><h2 id="ubt">UBT</h2><p>During my first implementation in UE4.27 the generated header code was still generated by the now-deprecated standalone <strong>UHT (Unreal Header Tool)</strong>. This involved a lot more custom edits in engine code to support a new property. However, with <strong>UBT (Unreal Build Tool)</strong>, adding a new supported <strong>UPROPERTY </strong>was relatively straight forward with no custom edits to <strong>UBT </strong>needed.</p><p>Going forward I refer to <strong>UBT/UHT</strong> as &quot;<strong>UBT</strong>&quot; for simplicity.</p><p>I created a new file called <strong>UhtOptionalProperty.cs</strong> inside <em>Engine\Source\Programs\Shared\EpicGames.UHT\Types\Properties</em></p><p>Here again I closely followed how <strong>TArray</strong>, <strong>TMap </strong>and <strong>TFieldPath </strong>implemented their templated code.</p><p>The main difference here is that I had a templated <strong>FProperty</strong>, which means I had to add the creation of this optional <strong>FProperty </strong>inside the code generation functions:</p><pre><code class="language-cs">public override StringBuilder AppendMemberDef(StringBuilder builder, IUhtPropertyMemberContext context, string name, string nameSuffix, string? offset, int tabs)
		{
			builder.AppendMemberDef(InnerProperty, context, name, GetNameSuffix(nameSuffix, &quot;_Inner&quot;), &quot;0&quot;, tabs);
			AppendMemberDefStart(builder, context, name, nameSuffix, offset, tabs, &quot;FOptionalPropertyParams&quot;, &quot;UECodeGen_Private::EPropertyGenFlags::OptionalProperty&quot;);
			builder.Append(&quot;new TOptionalProperty&lt;&quot;);
				
			InnerProperty.AppendText(builder, UhtPropertyTextType.Generic);

			builder.Append(&quot;&gt;(nullptr, TEXT(\&quot;&quot;)
				.Append(name)
				.Append(&quot;\&quot;), &quot;)
				.Append(ObjectFlags)
				.Append(&quot;, &quot;);
			
			builder.Append(&quot;STRUCT_OFFSET(&quot;).Append(context.OuterStructSourceName).Append(&quot;, &quot;).Append(SourceName).Append(&quot;)), &quot;);
			
			AppendMemberDefEnd(builder, context, name, nameSuffix);
			return builder;
		}</code></pre><p>I adjusted the member definition to construct the <strong>TOptionalProperty </strong>inside the generated header code, rather than constructing the property later in the engine-side of this blog post.</p><p>Note the &quot;<strong>FOptionalPropertyParams</strong>&quot; in the member definition, this is a struct that is used to pass information from generated header code to the FProperty and you will need to make your own later in the engine-side of the blog post.</p><p>To allow <strong>UBT </strong>to generate your new property you will need to tell <strong>UBT </strong>what to expect below <strong>UPROPERTY</strong>:</p><pre><code class="language-cs">[UhtPropertyType(Keyword = &quot;TScriptOptional&quot;)]
		[SuppressMessage(&quot;CodeQuality&quot;, &quot;IDE0051:Remove unused private members&quot;, Justification = &quot;Attribute accessed method&quot;)]
		[SuppressMessage(&quot;Style&quot;, &quot;IDE0060:Remove unused parameter&quot;, Justification = &quot;Attribute accessed method&quot;)]
		private static UhtProperty? OptionalProperty(UhtPropertyResolvePhase resolvePhase, UhtPropertySettings propertySettings, IUhtTokenReader tokenReader, UhtToken matchedToken)
		{
			using UhtMessageContext tokenContext = new(&quot;TScriptOptional&quot;);
			if (!tokenReader.SkipExpectedType(matchedToken.Value, propertySettings.PropertyCategory == UhtPropertyCategory.Member))
			{
				return null;
			}
			
			tokenReader.Require(&apos;&lt;&apos;);
			UhtProperty? value = UhtPropertyParser.ParseTemplateParam(resolvePhase, propertySettings, propertySettings.SourceName, tokenReader);
			if (value == null)
			{
				return null;
			}
			
			tokenReader.Require(&apos;&gt;&apos;);
			
			return CreateOptionalProperty(propertySettings, tokenReader, value);
		}</code></pre><p>The above code checks for <strong>TScriptOptional </strong>and requires a &quot;&lt;&quot; to follow, then it tries to parse a template parameter, aftewards it requires a &quot;&gt;&quot; to close the property definition.</p><h2 id="engine-side-implementation">Engine-side implementation</h2><p>The engine requires a few custom edits to allow using the <strong>TScriptOptional</strong>, simply creating the property and telling <strong>UBT </strong>how to parse and generate the property isn&apos;t enough to support it showing up in details panels.</p><h3 id="core">Core</h3><p>Inside <strong>UObjectGlobals.h</strong> you can add your <code>FOptionalPropertyParams</code>:</p><pre><code class="language-unrealscript">	struct FOptionalPropertyParams // : FPropertyParamsBaseWithOffset
		{
		const char*         NameUTF8;
		const char*         RepNotifyFuncUTF8;
		EPropertyFlags      PropertyFlags;
		EPropertyGenFlags   Flags;
		EObjectFlags        ObjectFlags;
		int32               ArrayDim;
		int32               Offset;
		FProperty*  		GeneratedProperty;
#if WITH_METADATA
		const FMetaDataPairParam* MetaDataArray;
		int32                     NumMetaData;
#endif
		};</code></pre><p>This struct is used to pass information from generated header code down to the property so the created property has the required information to handle the value of the property. If you need additional information for your property to be passed down from the <strong>UPROPERTY </strong>generation you can do that here.</p><p>On the .cpp side of this file you need to add code to handle this struct/property type.</p><pre><code class="language-unrealscript">case EPropertyGenFlags::OptionalProperty:
			{
				const FOptionalPropertyParams* Prop = (const FOptionalPropertyParams*)PropBase;
				NewProp = Prop-&gt;GeneratedProperty;
				NewProp-&gt;PropertyFlags = Prop-&gt;PropertyFlags;
				NewProp-&gt;Owner = Outer;
				NewProp-&gt;Init();

				// Next property is the optional inner
				ReadMore = 1;

#if WITH_METADATA
				MetaDataArray = Prop-&gt;MetaDataArray;
				NumMetaData   = Prop-&gt;NumMetaData;
			
#endif
			}
			break;</code></pre><p>This code is closer related to UE4 implementation than UE5 implemenation as the initial implementation was done in UE4 and I opted to stick to the UE4 implementation to keep my ability to use a templated FProperty.</p><p>The above code uses the GeneratedProperty from the struct created in the <strong>UBT</strong> section of this blog post, rather than constructing the new <strong>FProperty </strong>in this function directly. </p><p>You also see a <strong>EPropertyGenFlags::OptionalProperty</strong> in the above code, you will also need to add your property type to that enum and use that type here as well as in the UBT section. <code>OptionalProperty &#xA0;= 0x21</code></p><h2 id="details-panel">Details panel</h2><p>You will need to create a new <strong>FPropertyNode </strong>for optional property, as well as add a new <strong>FPropertyHandle </strong>for the optional property.</p><h3 id="property-node">Property node</h3><p>First make sure the <strong>FOptionalProperty </strong>is expandable in <strong>ItemPropertyNode.cpp</strong>:</p><pre><code class="language-unrealscript">bool bExpandableType = CastField&lt;FStructProperty&gt;(MyProperty) 
		|| CastField&lt;FArrayProperty&gt;(MyProperty) || CastField&lt;FSetProperty&gt;(MyProperty) || CastField&lt;FMapProperty&gt;(MyProperty)
			// Add this v
			|| CastField&lt;FOptionalProperty&gt;(MyProperty)
			// Add this ^
			;</code></pre><p>This will mark the property as needing expansion so we can add our inner property node in <strong>InitChildNodes </strong>in <strong>ItemPropertyNode.cpp</strong>:</p><p>Add your property <strong>CastField </strong>at the bottom of all the casts:</p><pre><code class="language-unrealscript">FOptionalProperty* OptionalProperty = CastField&lt;FOptionalProperty&gt;(MyProperty);
</code></pre><p>Then add your if statement alongside the if statements below:</p><pre><code class="language-unrealscript">else if( OptionalProperty )
	{
		void* Optional = NULL;
		FReadAddressList Addresses;
		if ( GetReadAddress(!!HasNodeFlags(EPropertyNodeFlags::SingleSelectOnly), Addresses ) )
		{
			Optional = Addresses.GetAddress(0);
		}

		if( Optional )
		{
			TSharedPtr&lt;FItemPropertyNode&gt; NewItemNode( new FItemPropertyNode );

			FPropertyNodeInitParams InitParams;
			InitParams.ParentNode = SharedThis(this);
			InitParams.Property = OptionalProperty-&gt;GetInner();
			InitParams.ArrayOffset = 0;
			InitParams.ArrayIndex = 0;
			InitParams.bAllowChildren = true;
			InitParams.bForceHiddenPropertyVisibility = bShouldShowHiddenProperties;
			InitParams.bCreateDisableEditOnInstanceNodes = bShouldShowDisableEditOnInstance;

			NewItemNode-&gt;InitNode( InitParams );
			NewItemNode-&gt;SetDisplayNameOverride(FText::FromString(&quot;Value&quot;)); // Value name, otherwise it&apos;ll show 0 from &quot;index 0&quot;, but we&apos;re not an array so that&apos;s counterintuitive
			
			AddChildNode(NewItemNode);
		}
	}</code></pre><p>Once again the implementation closely follows that of <strong>FArrayProperty</strong>, etc.</p><h3 id="property-handle">Property handle</h3><p>Next you will need to make a property handle, this class sets and gets the property values from and to the details panel from the <strong>FProperty</strong>:</p><pre><code>class FPropertyHandleOptionalProperty : public FPropertyHandleBase
{
public:
	FPropertyHandleOptionalProperty(TSharedRef&lt;FPropertyNode&gt; PropertyNode, FNotifyHook* NotifyHook, TSharedPtr&lt;IPropertyUtilities&gt; PropertyUtilities);
	static bool Supports(TSharedRef&lt;FPropertyNode&gt; PropertyNode);
	virtual FPropertyAccess::Result GetValue(bool&amp; OutValue) const override;
	virtual FPropertyAccess::Result GetValueAsDisplayText(FText&amp; OutValue) const override;
	virtual FPropertyAccess::Result GetValueAsFormattedText(FText&amp; OutValue) const override;
	virtual FPropertyAccess::Result SetValue(bool const&amp; InValue, EPropertyValueSetFlags::Type Flags = EPropertyValueSetFlags::DefaultFlags) override;
};</code></pre><p>To make the details panel use this property handle, add support for it in <strong>PropertyEditorHelpers.cpp </strong>in <strong>GetPropertyHandle(...)</strong></p><pre><code>else if (FPropertyHandleOptionalProperty::Supports(PropertyNode))
{
	PropertyHandle = MakeShareable(new FPropertyHandleOptionalProperty(PropertyNode, NotifyHook, PropertyUtilities));
}</code></pre><p>Just make sure to implement the <strong>Supports(PropertyNode)</strong> function of the handle you made.</p><pre><code class="language-unrealscript">bool FPropertyHandleOptionalProperty::Supports(TSharedRef&lt;FPropertyNode&gt; PropertyNode)
{
	FProperty* Property = PropertyNode-&gt;GetProperty();

	if (Property == nullptr)
	{
		return false;
	}
	
	return Property-&gt;IsA&lt;FOptionalProperty&gt;();
}</code></pre><p>Here is how I get the value of the optional property handle:</p><pre><code class="language-unrealscript">FPropertyAccess::Result FPropertyHandleOptionalProperty::GetValue(bool&amp; OutValue) const
{
	void* PropValue = nullptr;
	FPropertyAccess::Result Res = Implementation-&gt;GetValueData(PropValue);

	if (Res == FPropertyAccess::Success)
	{
		FProperty* Property = GetProperty();
		check(Property-&gt;IsA(FOptionalProperty::StaticClass()));
		const FScriptOptional* Optional = (const FScriptOptional*)(PropValue);
		OutValue = Optional-&gt;bIsSet;
		return FPropertyAccess::Success;
	}
	
	return FPropertyAccess::Fail;
}</code></pre><p>It&apos;s worth noting that this handle only takes care of the outer optional property, it does not handle the inner property value, so getting the value as a bool here only returns the value of the condition.</p><h3 id="slate">Slate</h3><p>In order to visually allow the designers to tick and untick the condition, and set the value, you need to add a custom property slate editor widget. In my case this is a simple <strong>SCompoundWidget </strong>with a <strong>SCheckBox</strong></p><pre><code class="language-unrealscript">class SPropertyEditorOptionalProperty : public SCompoundWidget
{
public:
	SLATE_BEGIN_ARGS(SPropertyEditorOptionalProperty) {}
	SLATE_END_ARGS()

	static bool Supports( const TSharedRef&lt; class FPropertyEditor &gt;&amp; InPropertyEditor );
	void Construct( const FArguments&amp; InArgs, const TSharedRef&lt; class FPropertyEditor &gt;&amp; InPropertyEditor );

protected:
	void OnCheckStateChanged( ECheckBoxState InNewState );
	ECheckBoxState OnGetCheckState() const;
	
private:
	TSharedPtr&lt; class FPropertyEditor &gt; PropertyEditor;
	TSharedPtr&lt; class SWidget &gt; PrimaryWidget;
	TSharedPtr&lt; class SCheckBox &gt; CheckBox;
};</code></pre><p>Constructing the widget is then:</p><pre><code class="language-unrealscript">
void SPropertyEditorOptionalProperty::Construct(const FArguments&amp; InArgs, const TSharedRef&lt;FPropertyEditor&gt;&amp; InPropertyEditor)
{
	PropertyEditor = InPropertyEditor;
	
	static const FName DefaultForegroundName(&quot;DefaultForeground&quot;);
	
	CheckBox = SNew(SCheckBox).OnCheckStateChanged( this, &amp;SPropertyEditorOptionalProperty::OnCheckStateChanged )
			.IsChecked( this, &amp;SPropertyEditorOptionalProperty::OnGetCheckState )
			.ForegroundColor( FEditorStyle::GetSlateColor(DefaultForegroundName) )
			.Padding(0.0f);

	const TSharedRef&lt; IPropertyHandle &gt; PropertyHandle = PropertyEditor-&gt;GetPropertyHandle();

	ChildSlot
	[
		SNew(SHorizontalBox)
		+SHorizontalBox::Slot()
		[
			CheckBox.ToSharedRef()
		]
	];

	if( InPropertyEditor-&gt;PropertyIsA( FObjectPropertyBase::StaticClass() ) )
	{
		// Object properties should display their entire text in a tooltip
		PrimaryWidget-&gt;SetToolTipText( TAttribute&lt;FText&gt;( InPropertyEditor, &amp;FPropertyEditor::GetValueAsText ) );
	}
}</code></pre><p>And using the property handle I get the checked/unchecked state:</p><pre><code class="language-unrealscript">ECheckBoxState SPropertyEditorOptionalProperty::OnGetCheckState() const
{
	ECheckBoxState ReturnState = ECheckBoxState::Undetermined;

	bool Value;
	const TSharedRef&lt; IPropertyHandle &gt; PropertyHandle = PropertyEditor-&gt;GetPropertyHandle();
	if( PropertyHandle-&gt;GetValue( Value ) == FPropertyAccess::Success )
	{
		if( Value == true )
		{
			ReturnState = ECheckBoxState::Checked;
		}
		else
		{
			ReturnState = ECheckBoxState::Unchecked;
		}
	}

	return ReturnState;
}</code></pre><p>Just like before with the property node and handle you need to tell the details panel to use this widget in <strong>PropertyEditorHelpers.cpp</strong> in <strong>ConstructPropertyEditorWidget(...)</strong>:</p><pre><code class="language-unrealscript">else if ( SPropertyEditorOptionalProperty::Supports(PropertyEditorRef) )
{
	PropertyWidget = SNew( SPropertyEditorOptionalProperty, PropertyEditorRef );
}</code></pre><p>If you also intend to use this property in a <strong>TableRow </strong>then I suggest you also add it to <strong>SPropertyEditorTableRow.cpp</strong> as this class handles its own property widgets itself.</p><h2 id="in-short">In short</h2><p>In short, there&apos;s quite a few things you need to add to the engine to support adding a new property, however, once you know where to look and what all to add it&apos;s quite possible. </p><p>A short list of all the things you need to do:</p><ul><li>Create the custom FProperty for your property</li><li>Tell UBT to look for your property type</li><li>Tell UBT how to generate your FProperty</li><li>Create a <code>FOptionalPropertyParams</code> (named to your property type)</li><li>Check for <code>FOptionalPropertyParams</code> inside UObjectGlobals.h</li><li>Add <code>OptionalProperty &#xA0;= 0x21</code> (0x21 may change in the future if Epic Games adds new properties, just add your last)</li><li>Add a new cast flag for your property</li><li>Make sure your property type is expandable in ItemPropertyNode.cpp</li><li>Use your property type to expand your inner property in InitChildNodes</li><li>Create your own FPropertyHandle for your property</li><li>Tell Unreal Engine to use your property handle</li><li>Create your widget representation of your custom property </li><li>Tell Unreal Engine to use your custom widget for your custom property</li></ul><p>There&apos;s more small steps that would be required, however these are the most important parts you have to do. </p><p>I also want to mention that nothing I did is <strong><em>the</em> </strong>way to add custom property support. I am sure that some things I did are not fully correct or can be done better or more efficiently. </p><p>I am also closely looking at Unreal Engine&apos;s native implementation of their TOptional support in order to see what I could do different in the future should I ever have to expose another new property to UPROPERTY.</p>]]></content:encoded></item><item><title><![CDATA[Render Targets for Shaders without the Render Target Camera Overhead]]></title><description><![CDATA[<div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">Note: This was written with UE 5.2</div></div><p>Hello everyone!</p><figure class="kg-card kg-video-card"><div class="kg-video-container"><video src="https://celdevs.com/content/media/2023/05/UnrealEditor-Win64-DebugGame_IpR7LcLHQG2-1.mp4" poster="https://img.spacergif.org/v1/1794x1220/0a/spacer.png" width="1794" height="1220" loop autoplay muted playsinline preload="metadata" style="background: transparent url(&apos;https://celdevs.com/content/images/2023/05/media-thumbnail-ember1574.jpg&apos;) 50% 50% / cover no-repeat;"></video><div class="kg-video-overlay"><button class="kg-video-large-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button></div><div class="kg-video-player-container kg-video-hide"><div class="kg-video-player"><button class="kg-video-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button><button class="kg-video-pause-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/><rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/></svg></button><span class="kg-video-current-time">0:00</span><div class="kg-video-time">/<span class="kg-video-duration"></span></div><input type="range" class="kg-video-seek-slider" max="100" value="0"><button class="kg-video-playback-rate">1&#xD7;</button><button class="kg-video-unmute-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/></svg></button><button class="kg-video-mute-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/></svg></button><input type="range" class="kg-video-volume-slider" max="100" value="100"></div></div></div></figure><div class="kg-card kg-callout-card kg-callout-card-pink"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">If you don&apos;t want to know how this works, scroll to the bottom, I provided the whole code sample :)</div></div><p>Today, I&apos;ll be writing on how you can create a render</p>]]></description><link>https://celdevs.com/creating-render-targets-for-shaders-without-a-camera/</link><guid isPermaLink="false">646c5e0da0f28af8b139b28e</guid><dc:creator><![CDATA[Celeste Neukirchen]]></dc:creator><pubDate>Tue, 23 May 2023 11:27:58 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">Note: This was written with UE 5.2</div></div><p>Hello everyone!</p><figure class="kg-card kg-video-card"><div class="kg-video-container"><video src="https://celdevs.com/content/media/2023/05/UnrealEditor-Win64-DebugGame_IpR7LcLHQG2-1.mp4" poster="https://img.spacergif.org/v1/1794x1220/0a/spacer.png" width="1794" height="1220" loop autoplay muted playsinline preload="metadata" style="background: transparent url(&apos;https://celdevs.com/content/images/2023/05/media-thumbnail-ember1574.jpg&apos;) 50% 50% / cover no-repeat;"></video><div class="kg-video-overlay"><button class="kg-video-large-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button></div><div class="kg-video-player-container kg-video-hide"><div class="kg-video-player"><button class="kg-video-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button><button class="kg-video-pause-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/><rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/></svg></button><span class="kg-video-current-time">0:00</span><div class="kg-video-time">/<span class="kg-video-duration"></span></div><input type="range" class="kg-video-seek-slider" max="100" value="0"><button class="kg-video-playback-rate">1&#xD7;</button><button class="kg-video-unmute-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/></svg></button><button class="kg-video-mute-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/></svg></button><input type="range" class="kg-video-volume-slider" max="100" value="100"></div></div></div></figure><div class="kg-card kg-callout-card kg-callout-card-pink"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">If you don&apos;t want to know how this works, scroll to the bottom, I provided the whole code sample :)</div></div><p>Today, I&apos;ll be writing on how you can create a render target texture, that can be used for anything from water ripples to snow trails to grass interaction without the GPU overhead of adding a render target camera to your scene. This solution is incredibly cheap on both the CPU and GPU and allows you to bake in even more information than you could get from using a render target camera.</p><p>This solution has limitations, however, unless you require everything in your scene to interact with your world, you won&apos;t be impacted by the limitations but instead will gain more possibilities as you will be able to bake in custom data such as normals. Since you need to explicitely track what you want to paint, it would be impossible to track everything in the world performantly. But if you&apos;re just looking for characters, enemy, specific object interactions this is a great solution.</p><p>To create this you will first need to create a simple manager that keeps tracks of all actors, or components, whatever you want to interact with your scene. This manager can be a UWorldSubsystem, a UComponent on your AGameState, or an AActor in your world. I prefer to use a UWorldSubsystem as this is the cleanest way to keep a state specific to your world.</p><p>The only things this class will need to be able to do is;</p><ul><li>Add relevant actors/components to an array</li><li>Remove actors/components from said array</li><li>Determining the center point</li><li>Tick</li></ul><p>Some other information this class needs in order to work;</p><ul><li>A pointer to a UTextureRenderTarget2D</li><li>A second pointer to a UTextureRenderTarget2D</li><li>A world size FVector2D<br><em>This is used to determine the size around your camera, or center point, that is relevant to the render target.</em></li><li>A pointer to a UMaterialInstanceDynamic the render target</li><li>(Optionally) a pointer to a UMaterialInterface</li><li>A UMaterialParameterCollection</li></ul><div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">You can, if you want, write this code in Blueprints. However, because we are aiming for performance here and there&apos;s quite a bit of math and looping involved I opted for writing this tutorial in C++.</div></div><p>Here&apos;s a header I created that contains all of the above if you don&apos;t want to type it our yourself.</p><pre><code class="language-unrealscript">UCLASS(BlueprintType)
class UWorldInteractionSubsystem : public UTickableWorldSubsystem
{
	GENERATED_BODY()

public:	
	UFUNCTION(BlueprintCallable)
	void RegisterActor(AActor* ActorToRegister);

	UFUNCTION(BlueprintCallable)
	void UnregisterActor(AActor* ActorToUnregister);
    
	UFUNCTION(BlueprintCallable)
	void SetCenterCamera(UCameraComponent* CenterCamera);
	
protected:
	// UTickableWorldSubsystem
	virtual void Tick(float DeltaTime) override;
	virtual TStatId GetStatId() const override;
	// ~UTickableWorldSubsystem

private:
	UPROPERTY(Transient)
	UTextureRenderTarget2D* RenderTarget;

	UPROPERTY(Transient)
	UTextureRenderTarget2D* RenderTargetMemory;

	UPROPERTY(Transient)
	UMaterialInstanceDynamic* RenderTargetMemoryMaterial;

	UPROPERTY(Transient)
	UMaterialInterface* DrawMaterial;
    
	UPROPERTY(Transient)
	UMaterialParameterCollection* ParameterCollection;

	UPROPERTY(Transient)
	FVector2D PaintArea = FVector2D(2048.f);
	
	UPROPERTY(Transient)
	UCameraComponent* CameraComponent;
	
	UPROPERTY(Transient)
	TArray&lt;AActor*&gt; RelevantActors;
};</code></pre><p>The .cpp file would look like this</p><pre><code class="language-unrealscript">void UWorldInteractionSubsystem::RegisterActor(AActor* ActorToRegister)
{
	if (ensure(IsValid(ActorToRegister)))
	{
		RelevantActors.Add(ActorToRegister);
	}
}

void UWorldInteractionSubsystem::UnregisterActor(AActor* ActorToUnregister)
{
	RelevantActors.Remove(ActorToUnregister);
}

void UWorldInteractionSubsystem::SetCenterCamera(UCameraComponent* CenterCamera)
{
	if (ensure(IsValid(CenterCamera)))
	{
		CameraComponent = CenterCamera;
	}
}

void UWorldInteractionSubsystem::Tick(float DeltaTime)
{
	// Tick logic
}

TStatId UWorldInteractionSubsystem::GetStatId() const
{
	return GetStatID();
}</code></pre><p>Now that we have the base of our subsystem ready we can start adding logic to it but before we do that let&apos;s explain why we need 2 materials and 2 render targets.</p><ul><li>RenderTarget (Current)<br><em>This is what is currently painted on your current frame and does not hold any information from any previous frames.</em></li><li>RenderTargetMemory (Memory)<br><em>This is your render target that contains the past few frames&apos; information</em></li><li>RenderTargetMemoryMaterial<br><em>This is what paints your current frame render target onto your memory render target. Used to offset the memory render target and fade out the old frames.</em></li><li>DrawMaterial<br><em>This is what you actually paint per character onto the render target. This can be anything you want, but typically a white sphere or gradient.</em> <em>This is optional as you can also just paint with the paint functions provided by the engine, but materials give more control.</em></li></ul><p>There are some changes you can make, should you require that, such as;</p><ul><li>You can use an actor rather than the camera to specify a center location, the center location is just what you want to be the center of everything painted, which typically is the camera but it can be anywhere you want.</li></ul><p>With that explained let&apos;s start implementing the logic. The logic has a few components to it, namely;</p><ul><li>Determining if the registered actor is relevant for the render target</li><li>Calculating local location of actor location in respect to the camera</li><li>Calculating the scaled local location in respect to the paint area and the texture size</li><li>Painting the locations onto the current render target</li><li>Painting the current render target onto the memory render target</li></ul><p>Start by preparing your tick function:</p><pre><code class="language-unrealscript">	// It&apos;s a good idea to track performance of this function should you ever need to optimize it
	QUICK_SCOPE_CYCLE_COUNTER(STAT_PaintWorldInteraction);
	
    // Check for validity, remove the last two IsValids until you get to the memory render target.
	if (!IsValid(CameraComponent) || !IsValid(RenderTarget) || !IsValid(RenderTargetMemory) || !IsValid(ParameterCollection))
	{
		return;
	}

	// Grab the location of the camera
	const FVector2D centerLocation(CameraComponent-&gt;GetComponentLocation());

	// Divide the paint area by 2 to get half of its size (you can cache this for extra performance)
	const FVector2D halfPaintArea = PaintArea * .5;

	// Calculate the size in units per pixel of the render target, in this case we&apos;re using a square so I can safely use X.
	// If you don&apos;t use a square you&apos;ll want to change this part of the code.
	const float areaPixelSize = (PaintArea / RenderTarget-&gt;SizeX).X;

	// Calculate a relative scale so we can just scale a world size to a paint size
	const float invertedAreaPixelSize = 1.f / FMath::Max(1.f,areaPixelSize);

	// Calculate half the size of the render target (you can cache this for extra performance)
	const FVector2D halfFrameSize = FVector2D(RenderTarget-&gt;SizeX * .5f, RenderTarget-&gt;SizeY * .5f);
    
    // Setup the paint box
	const FBox2D paintBox = FBox2D(-halfPaintArea, halfPaintArea);

	// Prepare to draw
	UCanvas* canvas;
	FVector2D canvasSize;
	FDrawToRenderTargetContext context;
	// Reset the current render target
	UKismetRenderingLibrary::ClearRenderTarget2D(this, RenderTarget);
	// Start drawing a new one
	UKismetRenderingLibrary::BeginDrawCanvasToRenderTarget(this, RenderTarget, canvas, canvasSize, context);</code></pre><p>Now that we have the basic information setup we can start iterating over our registered actors and finally start drawing</p><pre><code class="language-unrealscript">// Start iterating the relevant actors
// Iterate backwards because we can remove actors during the loop
	for (int32 i = RelevantActors.Num() -1 ; i &gt;= 0; --i)
	{
		// Check if the actor is still relevant. You should keep track of the lifecycle of actors you register but this is a good failsafe to prevent crashing
		AActor* actor = RelevantActors[i];
		if (!ensureAlways(IsValid(actor)))
		{
			RelevantActors.Remove(actor);
			continue;
		}

		// If the actor is hidden we don&apos;t want to show it (but you can change that for example if you want a ghost to interact with the scene)
		if (actor-&gt;IsHidden()) continue;

		// Grab the world location of the actor
		const FVector2D actorWorldLocation(actor-&gt;GetActorLocation());

		// Get the delta between camera center and actor
		const FVector2D delta = actorWorldLocation - centerLocation;
		
		// Out of bounds? Ignore
		if (!paintBox.IsInside(delta)) continue;

		// Calculate paint position from the delta
		const FVector2D paintPosition = halfFrameSize + delta * invertedAreaPixelSize;

		// Get the bounds of the actor
		// If you&apos;re using a character it&apos;s probably a good idea to use capsule width
		FVector origin, bounds;
		actor-&gt;GetActorBounds(true, origin, bounds);

		// Calculate the paint size required to cover the bounds of the actor
		const FVector2D paintBounds = FVector2D(bounds) * invertedAreaPixelSize;

		// Draw your material to the current render target
		canvas-&gt;K2_DrawMaterial(DrawMaterial, paintPosition, paintBounds, FVector2D(.5f), FVector2D(.5f));
	}
	
	UKismetRenderingLibrary::EndDrawCanvasToRenderTarget(this, context);</code></pre><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">Here I am simply drawing a sphere using the bounds, which works for most normal characters. If you have complex shapes that don&apos;t resolve to a spherical shape you&apos;ll most likely want to draw a different shape in the DrawMaterial. You may also not want to use the bounds and rather expose a custom radius, use the capsule, or determine bounds from a specific component or mesh.</div></div><p>Now you need to create a simple material that is rendered (DrawMaterial), in my case this is just a SphereGradient.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://celdevs.com/content/images/2023/05/image-3.png" class="kg-image" alt loading="lazy" width="1374" height="722" srcset="https://celdevs.com/content/images/size/w600/2023/05/image-3.png 600w, https://celdevs.com/content/images/size/w1000/2023/05/image-3.png 1000w, https://celdevs.com/content/images/2023/05/image-3.png 1374w" sizes="(min-width: 720px) 720px"><figcaption>A simple material with a SphereGradient2D into emissive and supplying in the texture coords to the gradient. Make sure you plug your gradient in emissive, not base color.</figcaption></figure><p>Now we need to add a function to setup the parameters:</p><pre><code class="language-unrealscript">UFUNCTION(BlueprintCallable)
void Setup(UTextureRenderTarget2D* InRenderTarget, UTextureRenderTarget2D* InRenderTargetMemory, UMaterialInterface* InDrawRenderTargetMemoryMaterial, UMaterialInterface* InDrawMaterial, UMaterialParameterCollection* InParameterCollection);</code></pre><pre><code class="language-unrealscript">void UWorldInteractionSubsystem::Setup(UTextureRenderTarget2D* InRenderTarget, UTextureRenderTarget2D* InRenderTargetMemory, UMaterialInterface* InDrawRenderTargetMemoryMaterial, UMaterialInterface* InDrawMaterial, UMaterialParameterCollection* InParameterCollection)
{
	RenderTarget = InRenderTarget;
	RenderTargetMemory = InRenderTargetMemory;
	RenderTargetMemoryMaterial = UMaterialInstanceDynamic::Create(InDrawRenderTargetMemoryMaterial, this);
	DrawMaterial = InDrawMaterial;
    ParameterCollection = InParameterCollection;
}</code></pre><div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">When testing at this stage you do not need to provide the memory material, render target and parameter collection as we&apos;re not using them yet.</div></div><p>Once we have this ready, you should be able to run the code and see the current render target update with the registered actors if you move your camera around. Below is a gif of that, my settings were;</p><ul><li>Paint area of {2048,2048}<br><em>This doesn&apos;t have to be power of two, but I like power of two numbers</em></li><li>Render target size of 2048x2048<br><em>This also doens&apos;t have to be power of two, but since it&apos;s a texture it&apos;s probably better. They also don&apos;t have to be that big, 512 would be just fine, but for demo purposes I used something higher.</em></li><li>Simple sphere actors in the world with various sizes</li></ul><figure class="kg-card kg-video-card kg-card-hascaption"><div class="kg-video-container"><video src="https://celdevs.com/content/media/2023/05/ApplicationFrameHost_uyNvbKs9Wi-output.mp4" poster="https://img.spacergif.org/v1/2556x1268/0a/spacer.png" width="2556" height="1268" loop autoplay muted playsinline preload="metadata" style="background: transparent url(&apos;https://celdevs.com/content/images/2023/05/media-thumbnail-ember676.jpg&apos;) 50% 50% / cover no-repeat;"></video><div class="kg-video-overlay"><button class="kg-video-large-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button></div><div class="kg-video-player-container kg-video-hide"><div class="kg-video-player"><button class="kg-video-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button><button class="kg-video-pause-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/><rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/></svg></button><span class="kg-video-current-time">0:00</span><div class="kg-video-time">/<span class="kg-video-duration"></span></div><input type="range" class="kg-video-seek-slider" max="100" value="0"><button class="kg-video-playback-rate">1&#xD7;</button><button class="kg-video-unmute-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/></svg></button><button class="kg-video-mute-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/></svg></button><input type="range" class="kg-video-volume-slider" max="100" value="100"></div></div></div><figcaption>Using the grass from the <a href="https://www.unrealengine.com/marketplace/en-US/product/elvish-forest?ref=celdevs.com">Elvish Forest from the Marketplace</a></figcaption></figure><p>Now that we have this you can see we&apos;re drawing sphere the size of the spheres onto our render target. But that only shows current frame information and you can hardly create nice effects based on that.</p><p>So let&apos;s add past frames information to it!</p><p>Let&apos;s start by keeping track of the center location from last frame. Add a new FVector2D to the header and set the last camera location at the end of the tick function.</p><pre><code class="language-unrealscript">UPROPERTY(Transient)
FVector2D LastCameraLocation;</code></pre><p>And at the very bottom of the Tick function:</p><pre><code class="language-unrealscript">LastCameraLocation = centerLocation;</code></pre><p>Now, between the above and our last EndDrawCanvasToRenderTarget let&apos;s paint past frames. Calculate the offset of the camera between last and current frame and set it as an offset in the RenderTargetMemoryMaterial. Also set the paint area size as a scalar parameter. Then draw the material to the memory render target.</p><pre><code class="language-unrealscript">static const FName OffsetName = &quot;Offset&quot;;
static const FName SizeName = &quot;Size&quot;;
static const FName LocationName = &quot;Location&quot;;

// Calculate our offset
const FVector2D offset = LastCameraLocation - centerLocation;

// Set the offset and size onto the render target material
RenderTargetMemoryMaterial-&gt;SetVectorParameterValue(OffsetName, FVector(offset.X, offset.Y, 0.f));
RenderTargetMemoryMaterial-&gt;SetScalarParameterValue(SizeName, PaintArea.X);

// Set parameter collection values for use in your foliage, water, snow, etc.
UKismetMaterialLibrary::SetVectorParameterValue(this, ParameterCollection, LocationName, FLinearColor(centerLocation.X, centerLocation.Y, 0.f));
UKismetMaterialLibrary::SetScalarParameterValue(this, ParameterCollection, SizeName, PaintArea.X);
	
UKismetRenderingLibrary::DrawMaterialToRenderTarget(this, RenderTargetMemory, RenderTargetMemoryMaterial);</code></pre><p>Now we need to create the material that actually renders the past frames.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://celdevs.com/content/images/2023/05/image-5.png" class="kg-image" alt loading="lazy" width="1872" height="938" srcset="https://celdevs.com/content/images/size/w600/2023/05/image-5.png 600w, https://celdevs.com/content/images/size/w1000/2023/05/image-5.png 1000w, https://celdevs.com/content/images/size/w1600/2023/05/image-5.png 1600w, https://celdevs.com/content/images/2023/05/image-5.png 1872w" sizes="(min-width: 720px) 720px"><figcaption>Substract the UVs with the offset divided by size to offset the memory frames, then substract the memory slightly so it fades out, and add the current frame on top.</figcaption></figure><p>I did end up doing something wrong in the math and my result looked, let&apos;s say, incorrect.</p><figure class="kg-card kg-video-card kg-card-hascaption"><div class="kg-video-container"><video src="https://celdevs.com/content/media/2023/05/UnrealEditor_O95VxDfgdh2.mp4" poster="https://img.spacergif.org/v1/2174x1224/0a/spacer.png" width="2174" height="1224" loop autoplay muted playsinline preload="metadata" style="background: transparent url(&apos;https://celdevs.com/content/images/2023/05/media-thumbnail-ember1262.jpg&apos;) 50% 50% / cover no-repeat;"></video><div class="kg-video-overlay"><button class="kg-video-large-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button></div><div class="kg-video-player-container kg-video-hide"><div class="kg-video-player"><button class="kg-video-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button><button class="kg-video-pause-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/><rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/></svg></button><span class="kg-video-current-time">0:00</span><div class="kg-video-time">/<span class="kg-video-duration"></span></div><input type="range" class="kg-video-seek-slider" max="100" value="0"><button class="kg-video-playback-rate">1&#xD7;</button><button class="kg-video-unmute-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/></svg></button><button class="kg-video-mute-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/></svg></button><input type="range" class="kg-video-volume-slider" max="100" value="100"></div></div></div><figcaption>Oops</figcaption></figure><p>Once I fixed the math it did look proper :)</p><figure class="kg-card kg-video-card kg-card-hascaption"><div class="kg-video-container"><video src="https://celdevs.com/content/media/2023/05/UnrealEditor_c9fiMEDvTd2.mp4" poster="https://img.spacergif.org/v1/2548x1266/0a/spacer.png" width="2548" height="1266" loop autoplay muted playsinline preload="metadata" style="background: transparent url(&apos;https://celdevs.com/content/images/2023/05/media-thumbnail-ember1313.jpg&apos;) 50% 50% / cover no-repeat;"></video><div class="kg-video-overlay"><button class="kg-video-large-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button></div><div class="kg-video-player-container kg-video-hide"><div class="kg-video-player"><button class="kg-video-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button><button class="kg-video-pause-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/><rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/></svg></button><span class="kg-video-current-time">0:00</span><div class="kg-video-time">/<span class="kg-video-duration"></span></div><input type="range" class="kg-video-seek-slider" max="100" value="0"><button class="kg-video-playback-rate">1&#xD7;</button><button class="kg-video-unmute-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/></svg></button><button class="kg-video-mute-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/></svg></button><input type="range" class="kg-video-volume-slider" max="100" value="100"></div></div></div><figcaption>Top texture is the current frame, bottom texture is the memory</figcaption></figure><p>Now, because the spheres aren&apos;t moving you&apos;re not actually seeing anything interact, so let&apos;s move the spheres and see what it does!</p><figure class="kg-card kg-video-card kg-card-hascaption"><div class="kg-video-container"><video src="https://celdevs.com/content/media/2023/05/UnrealEditor_dA6xIAyOxe2.mp4" poster="https://img.spacergif.org/v1/2558x1256/0a/spacer.png" width="2558" height="1256" loop autoplay muted playsinline preload="metadata" style="background: transparent url(&apos;https://celdevs.com/content/images/2023/05/media-thumbnail-ember1339.jpg&apos;) 50% 50% / cover no-repeat;"></video><div class="kg-video-overlay"><button class="kg-video-large-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button></div><div class="kg-video-player-container kg-video-hide"><div class="kg-video-player"><button class="kg-video-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button><button class="kg-video-pause-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/><rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/></svg></button><span class="kg-video-current-time">0:00</span><div class="kg-video-time">/<span class="kg-video-duration"></span></div><input type="range" class="kg-video-seek-slider" max="100" value="0"><button class="kg-video-playback-rate">1&#xD7;</button><button class="kg-video-unmute-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/></svg></button><button class="kg-video-mute-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/></svg></button><input type="range" class="kg-video-volume-slider" max="100" value="100"></div></div></div><figcaption>You may see some artifacts and unclean circles on the memory render target, that happens when there&apos;s a frame spike. You can resolve this by not rendering every frame but capping it to 30fps which will decrease single frame spikes.</figcaption></figure><p>But this might be hard to see, so let&apos;s add some color on the trails to show it&apos;s actually trailing on the grass! :)</p><figure class="kg-card kg-video-card kg-card-hascaption"><div class="kg-video-container"><video src="https://celdevs.com/content/media/2023/05/UnrealEditor_OoMuoaijWr2-1.mp4" poster="https://img.spacergif.org/v1/2538x1256/0a/spacer.png" width="2538" height="1256" loop autoplay muted playsinline preload="metadata" style="background: transparent url(&apos;https://celdevs.com/content/images/2023/05/media-thumbnail-ember1456.jpg&apos;) 50% 50% / cover no-repeat;"></video><div class="kg-video-overlay"><button class="kg-video-large-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button></div><div class="kg-video-player-container kg-video-hide"><div class="kg-video-player"><button class="kg-video-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button><button class="kg-video-pause-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/><rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/></svg></button><span class="kg-video-current-time">0:00</span><div class="kg-video-time">/<span class="kg-video-duration"></span></div><input type="range" class="kg-video-seek-slider" max="100" value="0"><button class="kg-video-playback-rate">1&#xD7;</button><button class="kg-video-unmute-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/></svg></button><button class="kg-video-mute-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/></svg></button><input type="range" class="kg-video-volume-slider" max="100" value="100"></div></div></div><figcaption>Success!</figcaption></figure><p>Now the fun part can begin, creating your interactions! Now, I am not a shader artist and I have little knowledge on how to make nice swirly interactions so I won&apos;t be providing a material to do so, however I will provide the minimum you need in order to get the data out of the memory render target.</p><figure class="kg-card kg-image-card"><img src="https://celdevs.com/content/images/2023/05/image-7.png" class="kg-image" alt loading="lazy" width="1328" height="566" srcset="https://celdevs.com/content/images/size/w600/2023/05/image-7.png 600w, https://celdevs.com/content/images/size/w1000/2023/05/image-7.png 1000w, https://celdevs.com/content/images/2023/05/image-7.png 1328w" sizes="(min-width: 720px) 720px"></figure><p>Above material projects the memory render target onto your shader&apos;s world position by using the paint area size and center location of the render target.</p><p>There&apos;s an addition you want to do after you projected the render target onto your shader.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://celdevs.com/content/images/2023/05/image-9.png" class="kg-image" alt loading="lazy" width="1299" height="900" srcset="https://celdevs.com/content/images/size/w600/2023/05/image-9.png 600w, https://celdevs.com/content/images/size/w1000/2023/05/image-9.png 1000w, https://celdevs.com/content/images/2023/05/image-9.png 1299w" sizes="(min-width: 720px) 720px"><figcaption>Prevent this effect from tiling by simply checking if distance between AbsWorldPosition is within half size of the paint area. This does result in a circular clipping, rather than cubic. If you need cubic simply adjust the math.</figcaption></figure><p>Now you should be ready and set to start interacting with your world, without the overhead of an additional camera on your GPU. </p><p>I hope you enjoyed this and should you have questions you can reach out on my <a href="https://twitter.com/CatherineCel?ref=celdevs.com">twitter</a>.</p><h2 id="tldr-all-code">TLDR; All code</h2><pre><code class="language-unrealscript">UCLASS(BlueprintType)
class UWorldInteractionSubsystem : public UWorldSubsystem, public FTickableGameObject
{
	GENERATED_BODY()

public:	
	UWorldInteractionSubsystem();

	UFUNCTION(BlueprintCallable)
	void Setup(UTextureRenderTarget2D* InRenderTarget, UTextureRenderTarget2D* InRenderTargetMemory, UMaterialInterface* InDrawRenderTargetMemoryMaterial, UMaterialInterface* InDrawMaterial, UMaterialParameterCollection* InParameterCollection);
	
	UFUNCTION(BlueprintCallable)
	void RegisterActor(AActor* ActorToRegister);

	UFUNCTION(BlueprintCallable)
	void UnregisterActor(AActor* ActorToUnregister);
    
	UFUNCTION(BlueprintCallable)
	void SetCenterCamera(UCameraComponent* CenterCamera);
	
protected:
	// FTickableGameObjectInterface
	virtual void Tick(float DeltaTime) override;
	virtual TStatId GetStatId() const override;
	virtual bool IsAllowedToTick() const override;
	// ~FTickableGameObjectInterface

protected:
	UPROPERTY(EditDefaultsOnly)
	UTextureRenderTarget2D* RenderTarget;

	UPROPERTY(EditDefaultsOnly)
	UTextureRenderTarget2D* RenderTargetMemory;

	UPROPERTY(EditDefaultsOnly)
	UMaterialInstanceDynamic* RenderTargetMemoryMaterial;

	UPROPERTY(EditDefaultsOnly)
	UMaterialInterface* DrawMaterial;

	UPROPERTY(EditDefaultsOnly)
	UMaterialParameterCollection* ParameterCollection;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	FVector2D PaintArea = FVector2D(4096.f);
	
private:
	UPROPERTY(Transient)
	UCameraComponent* CameraComponent;
	
	UPROPERTY(Transient)
	TArray&lt;AActor*&gt; RelevantActors;

	UPROPERTY(Transient)
	FVector2D LastCameraLocation;
};</code></pre><pre><code class="language-unrealscript">void UWorldInteractionSubsystem::Setup(UTextureRenderTarget2D* InRenderTarget, UTextureRenderTarget2D* InRenderTargetMemory, UMaterialInterface* InDrawRenderTargetMemoryMaterial, UMaterialInterface* InDrawMaterial, UMaterialParameterCollection* InParameterCollection)
{
	RenderTarget = InRenderTarget;
	RenderTargetMemory = InRenderTargetMemory;
	RenderTargetMemoryMaterial = UMaterialInstanceDynamic::Create(InDrawRenderTargetMemoryMaterial, this);
	DrawMaterial = InDrawMaterial;
	ParameterCollection = InParameterCollection;
}

void UWorldInteractionSubsystem::RegisterActor(AActor* ActorToRegister)
{
	if (ensure(IsValid(ActorToRegister)))
	{
		RelevantActors.Add(ActorToRegister);
	}
}

void UWorldInteractionSubsystem::UnregisterActor(AActor* ActorToUnregister)
{
	RelevantActors.Remove(ActorToUnregister);
}

void UWorldInteractionSubsystem::SetCenterCamera(UCameraComponent* CenterCamera)
{
	if (ensure(IsValid(CenterCamera)))
	{
		CameraComponent = CenterCamera;
	}
}

void UWorldInteractionSubsystem::Tick(float DeltaTime)
{
	// It&apos;s a good idea to track performance of this function should you ever need to optimize it
	QUICK_SCOPE_CYCLE_COUNTER(STAT_PaintWorldInteraction);

	if (!IsValid(CameraComponent) || !IsValid(RenderTarget) || !IsValid(RenderTargetMemory) || !IsValid(ParameterCollection))
	{
		return;
	}

	// Grab the location of the camera
	const FVector2D centerLocation(CameraComponent-&gt;GetComponentLocation());

	// Divide the paint area by 2 to get half of its size (you can cache this for extra performance)
	const FVector2D halfPaintArea = PaintArea * .5;

	// Calculate the size in units per pixel of the render target, in this case we&apos;re using a square so I can safely use X.
	// If you don&apos;t use a square you&apos;ll want to change this part of the code.
	const float areaPixelSize = (PaintArea / RenderTarget-&gt;SizeX).X;

	// Calculate a relative scale so we can just scale a world size to a paint size
	const float invertedAreaPixelSize = 1.f / FMath::Max(1.f,areaPixelSize);

	// Calculate half the size of the render target (you can cache this for extra performance)
	const FVector2D halfFrameSize = FVector2D(RenderTarget-&gt;SizeX * .5f, RenderTarget-&gt;SizeY * .5f);

	// Setup the paint box
	const FBox2D paintBox = FBox2D(-halfPaintArea, halfPaintArea);
	
	// Prepare to draw
	UCanvas* canvas;
	FVector2D canvasSize;
	FDrawToRenderTargetContext context;
	// Reset the current render target
	UKismetRenderingLibrary::ClearRenderTarget2D(this, RenderTarget);
	// Start drawing a new one
	UKismetRenderingLibrary::BeginDrawCanvasToRenderTarget(this, RenderTarget, canvas, canvasSize, context);

	// Start iterating the relevant actors
	// Iterate backwards because we can remove actors during the loop
	for (int32 i = RelevantActors.Num() -1 ; i &gt;= 0; --i)
	{
		// Check if the actor is still relevant. You should keep track of the lifecycle of actors you register but this is a good failsafe to prevent crashing
		AActor* actor = RelevantActors[i];
		if (!ensureAlways(IsValid(actor)))
		{
			RelevantActors.Remove(actor);
			continue;
		}

		// If the actor is hidden we don&apos;t want to show it (but you can change that for example if you want a ghost to interact with the scene)
		if (actor-&gt;IsHidden()) continue;

		// Grab the world location of the actor
		const FVector2D actorWorldLocation(actor-&gt;GetActorLocation());

		// Get the delta between camera center and actor
		const FVector2D delta = actorWorldLocation - centerLocation;
		
		// Out of bounds? Ignore
		if (!paintBox.IsInside(delta)) continue;

		// Calculate paint position from the delta
		const FVector2D paintPosition = halfFrameSize + delta * invertedAreaPixelSize;

		// Get the bounds of the actor
		// If you&apos;re using a character it&apos;s probably a good idea to use capsule width
		FVector origin, bounds;
		actor-&gt;GetActorBounds(true, origin, bounds);

		// Calculate the paint size required to cover the bounds of the actor
		const FVector2D paintBounds = FVector2D(bounds) * invertedAreaPixelSize;

		// Draw your material to the current render target
		canvas-&gt;K2_DrawMaterial(DrawMaterial, paintPosition, paintBounds, FVector2D(0.f), FVector2D(1.f));
	}
	
	UKismetRenderingLibrary::EndDrawCanvasToRenderTarget(this, context);

	static const FName OffsetName = &quot;Offset&quot;;
	static const FName SizeName = &quot;Size&quot;;
	static const FName LocationName = &quot;Location&quot;;

	// Calculate our offset
	const FVector2D offset = LastCameraLocation - centerLocation;

	// Set the offset and size onto the render target material
	RenderTargetMemoryMaterial-&gt;SetVectorParameterValue(OffsetName, FVector(offset.X, offset.Y, 0.f));
	RenderTargetMemoryMaterial-&gt;SetScalarParameterValue(SizeName, PaintArea.X);

	// Set parameter collection values for use in your foliage, water, snow, etc.
	UKismetMaterialLibrary::SetVectorParameterValue(this, ParameterCollection, LocationName, FLinearColor(centerLocation.X, centerLocation.Y, 0.f));
	UKismetMaterialLibrary::SetScalarParameterValue(this, ParameterCollection, SizeName, PaintArea.X);
	
	UKismetRenderingLibrary::DrawMaterialToRenderTarget(this, RenderTargetMemory, RenderTargetMemoryMaterial);
	
	LastCameraLocation = centerLocation;
}

TStatId UWorldInteractionSubsystem::GetStatId() const
{
	return GetStatID();
}
</code></pre>]]></content:encoded></item><item><title><![CDATA[Why you should think twice before using Firebase Firestore]]></title><description><![CDATA[<!--kg-card-begin: html-->
<p>Before I get into why you should think twice about Firebase Firestore in production I would like to give you a brief overview of what I personally used Firestore for, and what I do like about Firestore (even for production). </p>



<p>I have been working on the app <a href="https://apparyllis.com/?ref=celdevs.com" target="_blank" rel="noreferrer noopener">Simply Plural</a> for</p>]]></description><link>https://celdevs.com/why-you-should-think-twice-before-using-firebase-firestore/</link><guid isPermaLink="false">6464ea82a0f28af8b139b23b</guid><dc:creator><![CDATA[Celeste Neukirchen]]></dc:creator><pubDate>Fri, 29 Apr 2022 07:45:48 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: html-->
<p>Before I get into why you should think twice about Firebase Firestore in production I would like to give you a brief overview of what I personally used Firestore for, and what I do like about Firestore (even for production). </p>



<p>I have been working on the app <a href="https://apparyllis.com/?ref=celdevs.com" target="_blank" rel="noreferrer noopener">Simply Plural</a> for about a year and a half now, and at time of writing <a href="https://apparyllis.com/?ref=celdevs.com" target="_blank" rel="noreferrer noopener">Apparyllis</a>, my other non-game-development company, have about 17k daily users. Firebase Firestore was a good start for the app, back when I never even intended to release this app to the public and was just making it for a friend. Firestore allowed me to create a prototype of functionality that would otherwise take me a considerably longer time. The integration of Firestore with other Google Cloud products was easy and allowed me to quickly utilize all the different components of Firebase to create a well-functioning small-scale app.</p>



<h2 class="wp-block-heading">Firestore-first design</h2>



<p>A lot of the functionality from Firestore is Firestore-first, not customer first. And this becomes clear in a lot of the limitations they have when you are trying to create basic functionality with Firestore.</p>



<p>Ignoring all the costs associated with Firestore that are specified below, it&#x2019;s still not as an amazing product as it makes it out to be.</p>



<h3 class="wp-block-heading">Case-sensitivity and usernames</h3>



<p>To my surprise, after releasing the app to the public, we found that users were able to name themselves <strong>foo</strong> and <strong>Foo</strong>. This isn&#x2019;t ideal as you (or at least I) typically want usernames to be case-insensitive. Our query to see if a username could be taken was by seeing if a document with a specific name already existed, but this doesn&#x2019;t check for case-insensitivity, neither can you check for case-insensitivity in Firestore. </p>



<p>As a workaround we found that some other developers were storing all usernames as their intended casing, and as full lowercase in the database. This allowed them to query if the name already exists, which is a fair workaround for this issue. But when you&#x2019;re a developer, you want to be able to query on case-insesitivity in a database that is meant to be used for production. This would also not be a feasible solution that truly scales if you have a lot of fields that are case-insensitive.</p>



<h3 class="wp-block-heading">Query limitations</h3>



<p>We had a specific query that required us to get all documents where a user-specified time-range overlaps a time-range stored in the database. In essence we were trying to query all documents with a time range that overlapped with another time range. This however proved to be difficult. You cannot use mixed comparisons (&gt;= and &lt;=) in a compound query (queries with more than one .where()) with more than one field, <a href="https://firebase.google.com/docs/firestore/query-data/queries?ref=celdevs.com#query_limitations" target="_blank" rel="noreferrer noopener">as stated by their own documentation</a>.</p>



<p>The query for this, in our new backend is:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate" title>
{
			$or: [
				{ startTime: { $gte: Number(req.query.startTime) }, endTime: { $gte: Number(req.query.endTime) } }, // starts after start, ends after end
				{ startTime: { $lte: Number(req.query.startTime) }, endTime: { $gte: Number(req.query.startTime) } }, //start before start, ends after start
				{ startTime: { $gte: Number(req.query.startTime) }, endTime: { $lte: Number(req.query.endTime) } }, // start after start, ends before end
				{ startTime: { $lte: Number(req.query.endTime) }, endTime: { $gte: Number(req.query.endTime) } } //Starts before end, ends after end
			]
		}
</pre></div>


<p>This simply isn&#x2019;t possible in Firestore.</p>



<h3 class="wp-block-heading">Exporting data from backups</h3>



<p>Once in a while a user came along that messed up something in their account, or deleted their account by accident. They would contact us and ask if they can restore the mistake they did. We thought, okay we have backups we can do that! However, Firestore does not allow partial restore. You can only restore your full database, you can&#x2019;t restore just a specific collection or document. You can also not read the backup in any of your text editors to try and recover data that way.</p>



<p>We felt sorry for one user in particular and we found one hacky way to retrieve that user&#x2019; data. We downloaded our backup and launched a local Firestore database using the backup, which was then able to show us the entire backed-up database. </p>



<p>Needless to say, this is a horrible way of trying to retrieve a user&#x2019;s data. And on another note to this, once our backup grew to be larger than 2gb, the local instance of Firestore refused to launch and we could no longer restore user&#x2019; data if they messed something up.</p>



<h3 class="wp-block-heading">Document schema and public API</h3>



<p>One thing we wanted to do for a long time is creating a publicly available API for our users to use and extend the app with their own third-party-integrations. When we investigated doing this with Firestore we decided there isn&#x2019;t any feasible approach of doing this. Sure you could make an API using cloud functions, but you would again be paying for compute time on top of read, write and delete access.</p>



<p>Another issue we ran into is that, if we don&#x2019;t use some form of schema validation for the data input we receive from end-users, it is too easy to break your own data. For example if you want to rename your username from &#x201C;<strong>Foo</strong>&#x201D; to &#x201C;<strong>Bar</strong>&#x201C;, you can do this with a document write, but nothing is stopping users from entering a number, an array, an object into the username field. </p>



<p>If a user self-breaks the data, you could argue this is their issue, but in an ideal world an end-user cannot break their own account in an attempt to create a third party integration.</p>



<p>Changing the field from a String to a number on your user document would almost certainly break and bug the app for that specific user, and you would be getting misleading bug reports from your Crashlytics integration.</p>



<h2 class="wp-block-heading">The cost of using Firestore</h2>



<p>The cost of Firestore, at face value, seems reasonable and, in theory, it is. However, <a href="https://www.dottedsquirrel.com/30k-firebase/?ref=celdevs.com" target="_blank" rel="noreferrer noopener">as per other sources</a>, it is easy to scale the cost of your Firestore implementation into the thousands of dollars by human-error. But even if no human-error happened, there are still caveats that are worth mentioning.<br><br>When we left Firestore, we were getting a consistent 200$-300$ monthly bill for the operation of an app that had, at the time, around 10k daily users. And this was a price that seemed fair for the amount of daily users, however there were many ways we could have improved that cost, should Firestore have not been so&#x2026; well you&#x2019;ll read it below.</p>



<h3 class="wp-block-heading">Cost analytics limitations</h3>



<p>When using Firestore, regardless of whether you are creating an API or Cloud Functions, there are no analytics on which documents or common routes are used with Firestore. Firebase only presents you with how many reads happened, not the origin of them.</p>



<p>This caused a spike in cost during one of the months where we used Firestore, <strong>something </strong>in the app was making <strong>considerably </strong>more reads to documents than it should, and we were completely at a loss as to what could cause it.</p>



<p>Firestore itself has no built-in tools to see which parts of your database were often read, the Firestore SDK simply has your typical Read, Write and Delete operations/function calls for whatever document you want, but no way to log it.&#x2019;</p>



<p>The only way we are able to log this is to, for every location in the app where we do a read, write or delete, to do an additional function call to log the document reads, writes, and delete that are being made made to Google Analytics. </p>



<p>Once the Google Analytics usage started rolling in, we had a better idea of where <strong>some</strong> of the cost was coming from, but we still had a considerable unaccouncted-for amount of reads coming from somewhere.</p>



<p>After a lot of research into every location where we make read calls, we found that a Cloud Function was the cause for this. Cloud Functions, however, cannot do logging to Google Analytics, so we found this out purely by combing the code with a toothpick to try and find every possible way we could get too many reads.</p>



<p>Once we attributed the Cloud Function as the source of the reads, we rectified this and the costs of the increased reads stopped coming in. However, there were still some unaccounted reads (more on that below).</p>



<h3 class="wp-block-heading">The fear of abuse from end-users</h3>



<p>Knowing that Firestore charges you every read, a user with malintent can easily cause a spike in reads performed on your database when you don&#x2019;t use an API to route every read, write and delete (Which is one of Firestore&#x2019;s selling points, the lack of an API necessity). </p>



<p>The way you communicate with Firestore is by using the Public API key from your app and query the results you need (access is protected by read/write rules) through the SDK. <a href="https://www.reddit.com/r/Firebase/comments/b2lnt1/can_i_prevent_users_from_abusing_firestore/?ref=celdevs.com" target="_blank" rel="noreferrer noopener">As this user on Reddit</a> rightfully so asks, how do you protect your database from malintent queries?</p>



<p>Most users recommend Cloud Functions to serve as some sort of API to guard yourself against most malintent, this however requires you to:</p>



<blockquote class="wp-block-quote"><p>Create some sort of API after all (which Firestore likes to market as redundant)</p><p>Pay for the usage of Cloud Functions on top of your read, write and deletes</p><p>Update your app to use these API calls</p><p>If access to certain documents is fully restricted to Cloud Functions only, you lose the ability to get Firestore live updates.</p></blockquote>



<p>Even when you protect malintent through API calls, you w still being charged per-read when a malintended user uses your API endpoints by spamming them with a valid authentication.</p>



<h3 class="wp-block-heading">Unexpected charges from Firestore</h3>



<p>Firestore has made me go &#x201C;Are you for real?&#x201D; quite a few times as we navigated all the hidden costs of Firestore. Some hidden costs are for bare-minimum functionality that the usage of Firestore for anything serious can be put into question.</p>



<h4 class="wp-block-heading">Console usage</h4>



<p>In the very first days of my development with Firestore, we were getting <strong>a lot</strong> more reads than we should. We contacted Firestore about this. As a reply we got: &#x201C;Navigating the document database in the Firestore Cloud Console will also incur read, write and delete costs&#x201D;. Which means that by <a href="https://stackoverflow.com/questions/54729505/google-cloud-firestore-console-reading-of-all-documents-and-charges?ref=celdevs.com" target="_blank" rel="noreferrer noopener">purely looking through the database</a> in the online cloud console, we were getting reads from Firestore. This is, in comparison to actual app usage, quite low, however it&#x2019;s still worth mentioning.</p>



<h4 class="wp-block-heading">Backups</h4>



<p>The next unexpected charge we were made aware of is that creating a backup of your document database by exporting all documents into a backup on a daily basis ,<a href="https://stackoverflow.com/questions/65767894/firestore-backups-exportdocuments-costs?ref=celdevs.com" target="_blank" rel="noreferrer noopener"> incurs a read charge per document that is backed up</a>. If you have a chat app, you can only imagine the amount of costs you&#x2019;d incur on a daily basis just by doing the basic thing of backing up your database. In unison with Firestore liking to charge you for everything, should you ever want to restore from a backup, you will be charged one write per document, which are more expensive than reads.</p>



<h4 class="wp-block-heading">Security rules</h4>



<p>Another hidden cost of Firestore is that your security rules for guarding access to your documents also incurs a read charge when you perform <a href="https://stackoverflow.com/questions/53862566/firestore-security-rule-cost-of-using-request-resource-data-field-resour?ref=celdevs.com" target="_blank" rel="noreferrer noopener">any document data access</a> of a document in a security rule.</p>



<p>I am quite sure that there are other hidden costs we have forgotten about (we did the transfer about a year ago). However, these alone should be enough to make you think twice about Firestore.</p>



<h2 class="wp-block-heading">Synopsis</h2>



<p>To summarize, we&#x2019;ve gone through a lot of headache and unexpected costs by deciding to use Firestore for our project. In hindsight we should have done more research on the limitations and costs of Firestore before we went ahead and used it to launch our app with. </p>



<p>Today, without Firestore, we pay less than we did with Firestore and we have the flexibility of our own API, document schema validation, no hidden costs and any analytics you need to maintain the services you provide.</p>



<p>Our current stack is a load balancer that is optimized for production usage, with 2 API servers. A primary API server and a secondary API server to balance the load of the API calls. In addition we run a replica set of Mongodb with 3 servers (so elections can function if one goes down). We also run a separate server that serves the user-uploaded avatars (we currently host 2 million user-uploaded avatars at the time of writing) and a mail server that can send our emails for user-reports. Every server is currently being routed through <a href="https://www.cloudflare.com/?ref=celdevs.com" target="_blank" rel="noreferrer noopener">Cloudflare </a>for DDoS protection.</p>



<p>In total we have around 130.000 registered users in the app, and our servers are able to handle the load quite easily, even during peak times and during unexpected increased user counts.</p>



<p>Every server is currently hosted through <a href="https://m.do.co/c/412c839e73b6?ref=celdevs.com" target="_blank" rel="noreferrer noopener">Digital Ocean</a> and we have to say we&#x2019;re really happy with their service, so if you&#x2019;re looking into getting a similar stack up and running, we strongly recommend them.</p>



<p>If you&#x2019;ve made it this far, thank you for taking the time to read my not-so-short rant. I felt that the experiences we went through should be stated so that maybe, hopefully, at least one other developer can read this and save the time and headache.</p>



<p>If you still decide that Firestore is right for you and your needs, there&#x2019;s nothing wrong with that. Our needs for what we needed Firestore to do for us didn&#x2019;t match up, and as a small-time company, any costs that can&#x2019;t be lowered due to service-limitations is just not an option for us. </p>
<!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Unreal Engine, and the hidden pitfalls of Blueprints]]></title><description><![CDATA[<!--kg-card-begin: html-->
<p>Hello!<br><br>Blueprints are useful, quick, and easy to use once you get used to them. </p>



<p>Event graphs are incredibly helpful to creating certain gameplay, cross-function references are particularly useful in certain scenarios. </p>



<p>However useful and quick Blueprints are, there are quite a few hidden pitfalls that can unintentionally cause avoidable</p>]]></description><link>https://celdevs.com/unreal-engine-and-the-hidden-pitfalls-of-blueprints/</link><guid isPermaLink="false">6464ea82a0f28af8b139b23a</guid><dc:creator><![CDATA[Celeste Neukirchen]]></dc:creator><pubDate>Tue, 14 Sep 2021 15:05:51 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: html-->
<p>Hello!<br><br>Blueprints are useful, quick, and easy to use once you get used to them. </p>



<p>Event graphs are incredibly helpful to creating certain gameplay, cross-function references are particularly useful in certain scenarios. </p>



<p>However useful and quick Blueprints are, there are quite a few hidden pitfalls that can unintentionally cause avoidable massive performance drops. Some pitfalls that Blueprints offer <em>can actually cause bugs</em> that aren&#x2019;t very straightforward to find unless you know of some the inner workings of Blueprints.</p>



<p>Explaining all pitfalls of Blueprints in one blog post would be an unsurmountable feat, thus I have decided to make this a series that will look at specific pitfalls in Blueprints and document each one in detail.</p>



<hr class="wp-block-separator">



<h3 class="wp-block-heading"><strong>Pure Performance Pitfalls with Loops</strong></h3>



<p>When it comes to performance, Blueprints are generally slower than c++, but Blueprints can still be useful, and complex functions can still be created if you know what to look out for. </p>



<p>First and foremost, one of the major causes of performance loss in blueprints: Pure functions (Those without exec pins such as &#x201C;<strong>GetRandomPointInNavigableRadius </strong>&#x201C;).</p>



<p>They&#x2019;re handy, they&#x2019;re neat, and don&#x2019;t require you to hook up an execution pin! But; alas these pure functions come with a lot of pitfalls if used incorrectly. Take the following example:</p>



<figure class="wp-block-image"><img decoding="async" src="https://celdevs.com/content/images/wordpress/2021/09/image-2-1024x329.png" alt class="wp-image-165"><figcaption>A function that checks if the result of GetRandomPointInNavigableRadius is valid and then prints the result. This prints a random point around -100,-100,-100</figcaption></figure>



<p>This looks harmless, but unfortunately, this will call the function &#x201C;<strong>GetRandomPointInNavigableRadius</strong>&#x201D; twice. That is because pure functions run once per connection to a node. It runs once to check the <strong>Return Value</strong> and it runs once to <strong>Print String</strong>.</p>



<p>It seems quite counterintuitive, but it will make more sense if you take a look at the following example:</p>



<figure class="wp-block-image"><img decoding="async" src="https://celdevs.com/content/images/wordpress/2021/09/image-3-1024x276.png" alt class="wp-image-166"><figcaption> A function that checks if the result of GetRandomPointInNavigableRadius is valid, updates Origin, and then prints the result of the pure function.  This prints a random point around 200, 200, 200 </figcaption></figure>



<p>Initially, we set <strong>Origin </strong>to -100,-100,-100, we get a random point in radius, we check if this was successful. It was? Great let&#x2019;s print it, but before we do so, let&#x2019;s change the <strong>Origin </strong>to something else. Now, if we <strong>Print String</strong>, it will run the pure function again</p>



<p>Blueprints don&#x2019;t know in the first example that you didn&#x2019;t change the origin between the <strong>Branch</strong> and<strong> Print String</strong>, neither does it know you changed the <strong>Origin</strong> in the second example to something else before running the <strong>Print String</strong>.</p>



<p>Because Blueprints don&#x2019;t know and don&#x2019;t check, they always run once per connection, regardless if changes were made that would have no consequences to the return values.</p>



<p>Let&#x2019;s put this theory to the test and proof it. </p>



<p>To proof this theory, do the following: Create a pure function that just returns the number 20, but add a print string to the function, so that every time it runs, it will print your string.</p>



<figure class="wp-block-image"><img decoding="async" src="https://celdevs.com/content/images/wordpress/2021/09/image-5-1024x400.png" alt class="wp-image-170"><figcaption>A Const Pure function that prints &#x201C;I was run!&#x201D; and returns the value 20</figcaption></figure>



<p>Now, create a function that has a simple ForLoop and plug the PureProof function into &#x201C;LastIndex&#x201D;. Then print the index you have.</p>



<figure class="wp-block-image"><img decoding="async" src="https://celdevs.com/content/images/wordpress/2021/09/image-6-1024x235.png" alt class="wp-image-171"><figcaption>Am event  that runs ForLoop from 0 to the return value of PureProof.</figcaption></figure>



<p>If you run this function, you will get <strong>43 prints</strong> in the log. <br><strong>21 times</strong> the print string inside the PureProof <br><strong>21 time</strong>s the print string after the loop<br><strong>1 final time</strong> it runs the PureProof after the last index was run to decide not to run anymore because it reached the final index.</p>



<pre class="wp-block-code"><code>LogBlueprintUserMessages: [Untitled_C_4] I was run!
LogBlueprintUserMessages: [Untitled_C_4] 0
LogBlueprintUserMessages: [Untitled_C_4] I was run!
LogBlueprintUserMessages: [Untitled_C_4] 1
LogBlueprintUserMessages: [Untitled_C_4] I was run!
LogBlueprintUserMessages: [Untitled_C_4] 2
LogBlueprintUserMessages: [Untitled_C_4] I was run!
LogBlueprintUserMessages: [Untitled_C_4] 3
LogBlueprintUserMessages: [Untitled_C_4] I was run!
LogBlueprintUserMessages: [Untitled_C_4] 4
LogBlueprintUserMessages: [Untitled_C_4] I was run!
LogBlueprintUserMessages: [Untitled_C_4] 5
LogBlueprintUserMessages: [Untitled_C_4] I was run!
LogBlueprintUserMessages: [Untitled_C_4] 6
LogBlueprintUserMessages: [Untitled_C_4] I was run!
LogBlueprintUserMessages: [Untitled_C_4] 7
LogBlueprintUserMessages: [Untitled_C_4] I was run!
LogBlueprintUserMessages: [Untitled_C_4] 8
LogBlueprintUserMessages: [Untitled_C_4] I was run!
LogBlueprintUserMessages: [Untitled_C_4] 9
LogBlueprintUserMessages: [Untitled_C_4] I was run!
LogBlueprintUserMessages: [Untitled_C_4] 10
LogBlueprintUserMessages: [Untitled_C_4] I was run!
LogBlueprintUserMessages: [Untitled_C_4] 11
LogBlueprintUserMessages: [Untitled_C_4] I was run!
LogBlueprintUserMessages: [Untitled_C_4] 12
LogBlueprintUserMessages: [Untitled_C_4] I was run!
LogBlueprintUserMessages: [Untitled_C_4] 13
LogBlueprintUserMessages: [Untitled_C_4] I was run!
LogBlueprintUserMessages: [Untitled_C_4] 14
LogBlueprintUserMessages: [Untitled_C_4] I was run!
LogBlueprintUserMessages: [Untitled_C_4] 15
LogBlueprintUserMessages: [Untitled_C_4] I was run!
LogBlueprintUserMessages: [Untitled_C_4] 16
LogBlueprintUserMessages: [Untitled_C_4] I was run!
LogBlueprintUserMessages: [Untitled_C_4] 17
LogBlueprintUserMessages: [Untitled_C_4] I was run!
LogBlueprintUserMessages: [Untitled_C_4] 18
LogBlueprintUserMessages: [Untitled_C_4] I was run!
LogBlueprintUserMessages: [Untitled_C_4] 19
LogBlueprintUserMessages: [Untitled_C_4] I was run!
LogBlueprintUserMessages: [Untitled_C_4] 20
LogBlueprintUserMessages: [Untitled_C_4] I was run!</code></pre>



<p>Now, this is quite concerning. But for the purposes of this example, this is not a worrisome scenario, the pure function just returns a value, it doesn&#x2019;t do anything expensive.</p>



<p>Now, imagine you do the following in <strong>PureProof</strong>.</p>



<figure class="wp-block-image"><img decoding="async" src="https://celdevs.com/content/images/wordpress/2021/09/image-8-1024x385.png" alt class="wp-image-174"><figcaption>A pure const function that runs a loop of 0-20 range and adds +1 on Final Value before returning the ReturnValue.</figcaption></figure>



<p>This function, would still be called <strong>21 + 1</strong> times, however; the loop inside of PureProof would also run <strong>21 + 1</strong> times, which can be a major performance issue if you are doing any sort of expensive checks in here, or are doing it to return a filtered result of an array.</p>



<p>The reason it does this is because <strong>ForLoop </strong>is not a function, but a macro, a macro that checks the length of last index every iteration, and each iteration it will run <strong>PureProof </strong>to get the last index value.</p>



<figure class="wp-block-image"><img decoding="async" src="https://celdevs.com/content/images/wordpress/2021/09/image-9.png" alt class="wp-image-188"><figcaption>The inside of the macro ForLoop</figcaption></figure>



<hr class="wp-block-separator is-style-dots">



<p>Now let&#x2019;s take a look at a much more costly pure function.</p>



<figure class="wp-block-image"><img decoding="async" src="https://celdevs.com/content/images/wordpress/2021/09/image.png" alt class="wp-image-162"><figcaption>A loop that gets all characters of a string and prints each character</figcaption></figure>



<p>Now, this initially looks quite harmless, it goes over every character of the array and prints each character. However, this is actually a cause for concern. </p>



<p>As we know now, pure functions call once per connection, and in the case of a <strong>ForEachLoop</strong> it will call <strong>twice </strong>per iteration.</p>



<p>Inside the ForEachLoop there are two main nodes that cause this behavior. &#x201C;Length&#x201D; and &#x201C;Get&#x201D;.</p>



<figure class="wp-block-image"><img decoding="async" src="https://celdevs.com/content/images/wordpress/2021/09/image-1-1024x266.png" alt class="wp-image-164"><figcaption>The inside of the For Each Loop macro</figcaption></figure>



<p>Each iteration, the macro checks if the loop counter inside of the loop is smaller than Length of the array. So every iteration it has to get the length of the array. If the check passed, we do a Get, this gets the array and gets the object from the array at the index.</p>



<p>Take a look at the following example:</p>



<figure class="wp-block-image"><img decoding="async" src="https://celdevs.com/content/images/wordpress/2021/09/image-11-1024x498.png" alt class="wp-image-193"><figcaption>A pure const function that prints &#x201C;I have run!&#x201D; and returns an array of 0-5 range each with their index as value.</figcaption></figure>



<figure class="wp-block-image"><img decoding="async" src="https://celdevs.com/content/images/wordpress/2021/09/image-12.png" alt class="wp-image-194"><figcaption>An event that calls ForEachLoop with PureArrayProof as the input, printing the element each iteration.</figcaption></figure>



<p>Running this code ends up running the PureArrayProof function 13 times. <br>12 times for all iterations, 2 per iteration. One additional time to check if we reached the length of the array (and we did).</p>



<pre class="wp-block-code"><code>LogBlueprintUserMessages: [Untitled_C_8] I have run!
LogBlueprintUserMessages: [Untitled_C_8] I have run!
LogBlueprintUserMessages: [Untitled_C_8] 0
LogBlueprintUserMessages: [Untitled_C_8] I have run!
LogBlueprintUserMessages: [Untitled_C_8] I have run!
LogBlueprintUserMessages: [Untitled_C_8] 1
LogBlueprintUserMessages: [Untitled_C_8] I have run!
LogBlueprintUserMessages: [Untitled_C_8] I have run!
LogBlueprintUserMessages: [Untitled_C_8] 2
LogBlueprintUserMessages: [Untitled_C_8] I have run!
LogBlueprintUserMessages: [Untitled_C_8] I have run!
LogBlueprintUserMessages: [Untitled_C_8] 3
LogBlueprintUserMessages: [Untitled_C_8] I have run!
LogBlueprintUserMessages: [Untitled_C_8] I have run!
LogBlueprintUserMessages: [Untitled_C_8] 4
LogBlueprintUserMessages: [Untitled_C_8] I have run!
LogBlueprintUserMessages: [Untitled_C_8] I have run!
LogBlueprintUserMessages: [Untitled_C_8] 5
LogBlueprintUserMessages: [Untitled_C_8] I have run!</code></pre>



<p>I hope that at this point you can see how loops and pure functions can be a devestating effect on your performance. Let&#x2019;s take this to the extreme and re-create a scenario that I have seen in a project once, which was cause for quite a heavy performance impact.</p>



<figure class="wp-block-image"><img decoding="async" src="https://celdevs.com/content/images/wordpress/2021/09/image-13-1024x372.png" alt class="wp-image-195"><figcaption>A pure const function that runs over x amount of elements, does an arbitrary expensive check and when it passes it adds the property to an array, once complete it returns the array.</figcaption></figure>



<figure class="wp-block-image"><img decoding="async" src="https://celdevs.com/content/images/wordpress/2021/09/image-14-1024x331.png" alt class="wp-image-196"><figcaption>A pure const function that runs over the filtered array and does another expensive check per array element, and adds it to a local array, when done returning the array.</figcaption></figure>



<p>Then running the following:</p>



<figure class="wp-block-image"><img decoding="async" src="https://celdevs.com/content/images/wordpress/2021/09/image-15.png" alt class="wp-image-197"><figcaption> An event that calls ForEachLoop with PureArrayProof as the input, printing the element each iteration. </figcaption></figure>



<p>This causes the function &#x201C;<strong>GetFilteredArray</strong>&#x201D; to run an incredibly high amount of times. The <strong>GetFilteredArray </strong>function loops over 0-40 range, let&#x2019;s say it loop 40 times for the sake of simplicity when calculating the amount of times this runs.</p>



<p>Let&#x2019;s say it filters 50/50, so we end up with 20 filtered results inside &#x201C;<strong>PureArrayProof</strong>&#x201C;. It again filters 50/50, ending up ith 10 results when we run &#x201C;<strong>PureArrayProof</strong>&#x201D; in the <strong>event graph</strong>.</p>



<p>Knowing, that a <strong>ForEachLoop </strong>runs twice per execution + 1, we know that the function &#x201C;<strong>PureArrayProof</strong>&#x201D; will run 21 times before being done with work. </p>



<p>Knowing that <strong>PureArrayProof </strong>runs 21 times, we know that &#x201C;<strong>GetFilteredArray</strong>&#x201D; will run 41 times per iteration of  <strong>PureArrayProof</strong>.</p>



<p>This ends up to running <strong>GetFilteredArray </strong>861 times (21 x 41).</p>



<p>Knowing that <strong>GetFilteredArray </strong>runs 861 times, inside <strong>GetFilteredArray </strong>we run ForLoop 40 loops, which does <strong>ExpensiveCheckFunction</strong>.<br>Knowing it runs 40 loops, it will execute  <strong>ExpensiveCheckFunction</strong> 41 times per time that  <strong>GetFilteredArray </strong> is run.</p>



<p>This results in running <strong>ExpensiveCheckFunction</strong> a whopping <strong>35,301 </strong>(41 x  861 ) times for the &#x201C;simple&#x201D; print string to run 10 times in the event graph.</p>



<hr class="wp-block-separator is-style-dots">



<h3 class="wp-block-heading"><strong>Now how do we solve this?</strong></h3>



<p>Simply cache the result of &#x201C;<strong>GetFilteredArray</strong>&#x201D; and &#x201C;<strong>PureArrayProof </strong>&#x201D; before running the ForEachLoop inside the event graph. It seems like such a simple fix for such a complex problem, but this really is all you have to do.</p>



<p>Alternatively, don&#x2019;t make the function pure and simply let the node have exec pins (I advise doing this for anything that is remotely expensive to avoid accidentally running into this issue).</p>



<figure class="wp-block-image"><img decoding="async" src="https://celdevs.com/content/images/wordpress/2021/09/image-16-1024x203.png" alt class="wp-image-202"></figure>



<figure class="wp-block-image"><img decoding="async" src="https://celdevs.com/content/images/wordpress/2021/09/image-17-1024x282.png" alt class="wp-image-203"></figure>
<!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Execute Function Behavior Tree Task]]></title><description><![CDATA[<!--kg-card-begin: html-->
<p>Tldr: I created a behavior tree task that lets you execute a function of your choosing on the target blackboard key object. Code snippets at the bottom, relevant files <a href="https://github.com/CelPlays/SaltyAI?ref=celdevs.com" target="_blank" rel="noreferrer noopener" aria-label=" (opens in a new tab)">here on GitHub</a>.<br><br>Hey everyone! Yesterday I was working on some AI in Unreal Engine and I found myself often having</p>]]></description><link>https://celdevs.com/execute-function-behavior-tree-task/</link><guid isPermaLink="false">6464ea82a0f28af8b139b238</guid><category><![CDATA[AI]]></category><category><![CDATA[AIModule]]></category><category><![CDATA[Behavior Tree]]></category><category><![CDATA[Editor customization]]></category><category><![CDATA[Property Customization]]></category><category><![CDATA[PropertyModule]]></category><category><![CDATA[UE4]]></category><category><![CDATA[UFunction]]></category><category><![CDATA[Unreal Engine]]></category><category><![CDATA[Unreal Engine 4]]></category><dc:creator><![CDATA[Celeste Neukirchen]]></dc:creator><pubDate>Mon, 26 Aug 2019 10:47:16 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: html-->
<p>Tldr: I created a behavior tree task that lets you execute a function of your choosing on the target blackboard key object. Code snippets at the bottom, relevant files <a href="https://github.com/CelPlays/SaltyAI?ref=celdevs.com" target="_blank" rel="noreferrer noopener" aria-label=" (opens in a new tab)">here on GitHub</a>.<br><br>Hey everyone! Yesterday I was working on some AI in Unreal Engine and I found myself often having to make a task just to do a simple function call in the AI-controlled pawn, which is rather pointless if all you do in that task is execute a function and finish execution. So I decided to make a single task that does this for me.</p>



<figure class="wp-block-video"><video controls src="https://celdevs.com/content/images/wordpress/2019/08/ExecuteFunction.mp4"></video><figcaption>Final result</figcaption></figure>



<p><strong>General behavior of the node:</strong><br>You create the node, select the target class you want to target with this node, and from a dropdown you can select all available UFunctions of this class. I personally filter UFunctions that have a return val or params out to more easily find the function I want to execute.</p>



<p><strong>Technical behavior of the node:</strong><br>I created a new struct FFucntionContext that takes a TSubClassOf&lt;UObject&gt; and an FString, the TSubClassOf&lt;UObject&gt; is used to find the class we want to target and the Fstring is to store the function to execute. <br><br>In an editor plugin, I created a IPropertyTypeCustomization for this struct called FFunctionContextCustomization, for the TSubClassOf&lt;UObject&gt; I just generate the value widget as I don&#x2019;t need to modify that. But for the string, I create a dropdown that has as OptionsSource the available and filtered UFunctions. We can do this by getting the value of the property as a formatted string and find the UClass. From the UClass we can iterate over all UFunctions this class has and get their name and flags, and other useful information to determine whether or not this UFunction is relevant for me.</p>



<p>Full code can be found at: <a href="https://github.com/CelPlays/SaltyAI?ref=celdevs.com">https://github.com/CelPlays/SaltyAI</a>  </p>



<h2 class="wp-block-heading">Customizing the Property</h2>



<p>To get a custom dropdown for the property you need to create an  IPropertyTypeCustomization class for your struct and override CustomizeHeader. Once you did that you have full control over the Slate visuals and behavior of your struct property.<br><br>For the TSubClassOf property, I create the default property widget in the ValueContent of the HeaderRow and for the string property, I create a dropdown which gets filled when you select a class.</p>



<p>I also bind to SetOnPropertyValueChanged so that when the class changes the actual array for the options will update and you can reselect your class.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: cpp; title: ; notranslate" title>
void FFunctionContextCustomization::CustomizeHeader(TSharedRef&lt;class IPropertyHandle&gt; StructPropertyHandle, class FDetailWidgetRow&amp; HeaderRow, IPropertyTypeCustomizationUtils&amp; StructCustomizationUtils)
{
	ClassProperty = StructPropertyHandle-&gt;GetChildHandle(&quot;ContextClass&quot;);
	FunctionProperty = StructPropertyHandle-&gt;GetChildHandle(&quot;FunctionToExecute&quot;);

	FSimpleDelegate OnChange;
	OnChange.BindRaw(this, &amp;FFunctionContextCustomization::OnClassChange);
	ClassProperty-&gt;SetOnPropertyValueChanged(OnChange);

	OnClassChange();

	HeaderRow.NameContent()
	[
		StructPropertyHandle-&gt;CreatePropertyNameWidget(FText::FromString(&quot;Function Context&quot;))
	]
	.ValueContent()
	.MinDesiredWidth(500)
	[
		SNew(SVerticalBox)
		+ SVerticalBox::Slot()
		[
			ClassProperty-&gt;CreatePropertyValueWidget()
		]
		+ SVerticalBox::Slot()
		[
			SAssignNew(ComboBox, SComboBox&lt;FComboItemType&gt;)
			.OptionsSource(&amp;Functions)
			.OnSelectionChanged(this, &amp;FFunctionContextCustomization::OnSelectionChanged)
			.OnGenerateWidget(this, &amp;FFunctionContextCustomization::MakeWidgetForOption)
			[
				SNew(STextBlock)
				.Text(this, &amp;FFunctionContextCustomization::GetActiveFunction)
			]
		]
	];

	UpdateActiveSelectedClass();
}
</pre></div>


<h2 class="wp-block-heading">Iterating UFunctions</h2>



<p>Here I go over all UFunctions of the ContextClass. I only get functions that are BlueprintCallable, have no return value, does not have a native implementation and has no params. Obviously these rules are on a per-project basis and you can make these whatever you want.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: cpp; title: ; notranslate" title>
for (TFieldIterator&lt;UFunction&gt; FuncIt(ContextClass); FuncIt; ++FuncIt)
{
	UFunction* Function = *FuncIt;

	//Only blueprint callable
	if (!Function-&gt;HasAnyFunctionFlags(FUNC_BlueprintCallable))
	{
		continue;
	}

	//Ignore return val function
	if (Function-&gt;GetReturnProperty())
	{
		continue;
	}

	//Ignore native functions
	if (Function-&gt;HasAnyFunctionFlags(FUNC_Native))
	{
		continue;
	}

	//If function has params ignore the function
	if (Function-&gt;NumParms &gt; 0)
	{
		continue;
	}

	Functions.Add(MakeShareable(new FString(Function-&gt;GetName())));
}
</pre></div>


<h2 class="wp-block-heading">Executing the UFunction</h2>



<p>You can execute the UFunction by first finding the UFunction and then processing the event. I also added a boolean for bReturnSuccessIfInvalid, which will return success even if the function call wasn&#x2019;t successful.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: cpp; title: ; notranslate" title>
UBlackboardComponent&amp; BlackboardComp = *OwnerComp.GetBlackboardComponent();
UObject* Object = BlackboardComp.GetValueAsObject(Target.SelectedKeyName);

if (!Object)
{
	return bReturnSuccessIfInvalid ? EBTNodeResult::Succeeded : EBTNodeResult::Failed;
}

UFunction* Func = Object-&gt;FindFunction(FName(*FunctionContext.FunctionToExecute));

if (Func)
{
	Object-&gt;ProcessEvent(Func, nullptr);
}
else
{
	return bReturnSuccessIfInvalid ? EBTNodeResult::Succeeded : EBTNodeResult::Failed;
}

return EBTNodeResult::Succeeded;
</pre></div>


<h2 class="wp-block-heading">Prerequisites</h2>



<p>To get this to work you also have to register your   IPropertyTypeCustomization in StartupModule() of your editor module. You can do this with the following code:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: cpp; title: ; notranslate" title>
FPropertyEditorModule&amp; PropertyModule = FModuleManager::LoadModuleChecked&lt;FPropertyEditorModule&gt;(&quot;PropertyEditor&quot;);

//Custom properties
PropertyModule.RegisterCustomPropertyTypeLayout(&quot;FunctionContext&quot;, FOnGetPropertyTypeCustomizationInstance::CreateStatic(&amp;FFunctionContextCustomization::MakeInstance));
</pre></div>


<p>Also, make sure you add PropertyEditor as a dependency in your editor module. For your Runtime Module you want to add AIModule and GameplayTasks as a dependency. You have to add GamepayTasks as a dependency, otherwise it won&#x2019;t compile.</p>
<!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Classifier - Quickly look up header/module info on classes]]></title><description><![CDATA[<!--kg-card-begin: html-->
<h4 class="wp-block-heading"><strong>EDIT:</strong> I turned the server offline that was running this.</h4>



<p>Tldr: Go here to find module/header info:  <a href="http://classifier.celdevs.com/?ref=celdevs.com">http://classifier.celdevs.com</a>  </p>



<p>Because I was tired of scrolling to the bottom of the API pages of Unreal Engine to copy the module or header path I created a tool that</p>]]></description><link>https://celdevs.com/classifier-quickly-look-up-header-module-info-on-classes/</link><guid isPermaLink="false">6464ea82a0f28af8b139b237</guid><category><![CDATA[Classes]]></category><category><![CDATA[Find]]></category><category><![CDATA[Header]]></category><category><![CDATA[Include]]></category><category><![CDATA[Module]]></category><category><![CDATA[Path]]></category><category><![CDATA[Search]]></category><category><![CDATA[UE4]]></category><category><![CDATA[Unreal Engine]]></category><category><![CDATA[Unreal Engine 4]]></category><dc:creator><![CDATA[Celeste Neukirchen]]></dc:creator><pubDate>Thu, 06 Jun 2019 11:28:29 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: html-->
<h4 class="wp-block-heading"><strong>EDIT:</strong> I turned the server offline that was running this.</h4>



<p>Tldr: Go here to find module/header info:  <a href="http://classifier.celdevs.com/?ref=celdevs.com">http://classifier.celdevs.com</a>  </p>



<p>Because I was tired of scrolling to the bottom of the API pages of Unreal Engine to copy the module or header path I created a tool that does it for me. <br><br><img decoding="async" class="wp-image-121" style="width: 500px;" src="https://celdevs.com/wp-content/uploads/2019/06/chrome_2019-06-06_13-17-57.png" alt></p>



<p>All you need to do is type in a class name AActor, UWorld, FCoreDelegates, TArray and click &#x201C;Gimme dat!&#x201D; and it&#x2019;ll provide you with the proper module/header.</p>



<p>If you wanna see a video of it working you can see that here:  <a href="https://twitter.com/CatherineCel/status/1136344909244436480?ref=celdevs.com">https://twitter.com/CatherineCel/status/1136344909244436480</a> </p>



<p>To actually use the tool go here: <a href="http://classifier.celdevs.com/?ref=celdevs.com">http://classifier.celdevs.com </a></p>
<!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Procedural Island Generation UE4]]></title><description><![CDATA[<!--kg-card-begin: html-->
<p>Hi! Today I&#x2019;m going into details of the <a rel="noreferrer noopener" aria-label="procedural island generation (opens in a new tab)" href="https://twitter.com/CatherineCel/status/1128200279286063104?ref=celdevs.com" target="_blank">procedural island generation</a> I posted on my Twitter  &#x1F642; <br><br>To start off, a few great resources I&#x2019;ve personally read that will help your understanding of terms and tools we&#x2019;re using.</p>



<p>Awesome Blog post on <a rel="noreferrer noopener" aria-label="Perlin Noise (opens in a new tab)" href="https://gpfault.net/posts/perlin-noise.txt.html?ref=celdevs.com" target="_blank">Perlin Noise</a></p>]]></description><link>https://celdevs.com/procedural-island-generation-ue4/</link><guid isPermaLink="false">6464ea82a0f28af8b139b236</guid><category><![CDATA[c++]]></category><category><![CDATA[cpp]]></category><category><![CDATA[Island Generation]]></category><category><![CDATA[Level Generation]]></category><category><![CDATA[Perlin Noise]]></category><category><![CDATA[Procedural Mesh Component]]></category><category><![CDATA[UE4]]></category><category><![CDATA[Unreal Engine]]></category><category><![CDATA[Unreal Engine 4]]></category><dc:creator><![CDATA[Celeste Neukirchen]]></dc:creator><pubDate>Wed, 15 May 2019 20:29:26 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: html-->
<p>Hi! Today I&#x2019;m going into details of the <a rel="noreferrer noopener" aria-label="procedural island generation (opens in a new tab)" href="https://twitter.com/CatherineCel/status/1128200279286063104?ref=celdevs.com" target="_blank">procedural island generation</a> I posted on my Twitter  &#x1F642; <br><br>To start off, a few great resources I&#x2019;ve personally read that will help your understanding of terms and tools we&#x2019;re using.</p>



<p>Awesome Blog post on <a rel="noreferrer noopener" aria-label="Perlin Noise (opens in a new tab)" href="https://gpfault.net/posts/perlin-noise.txt.html?ref=celdevs.com" target="_blank">Perlin Noise</a>.<br>Complete resource on <a rel="noreferrer noopener" aria-label="Hexagonal Grids. (opens in a new tab)" href="https://www.redblobgames.com/grids/hexagons/?ref=celdevs.com" target="_blank">Hexagonal Grids</a>.<br>Resource on <a rel="noreferrer noopener" aria-label="Biome/Elevation (opens in a new tab)" href="http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation/?ref=celdevs.com" target="_blank">Biome/Elevation</a>.</p>



<p>I strongly suggest reading the above resources to get a good background knowledge of what I&#x2019;ve used myself.</p>



<p>For the Perlin Noise I use the <a href="https://github.com/midgen/UnrealFastNoise?ref=celdevs.com" target="_blank" rel="noreferrer noopener" aria-label="UnrealFastNoise  (opens in a new tab)">UnrealFastNoise </a>from Chris Ashworth.</p>



<p>While this is a hexagonal grid, all of the code is easily transformed into square grids, so if you want a natural map that isn&#x2019;t tied to hexagons you can still follow this guide.</p>



<h2 class="wp-block-heading">High-Level process</h2>



<p>The order in which I generate the island is as following:</p>



<ul><li>Generate entire flat hexagonal grid </li><li>Mark edge tiles as ocean</li><li>Divide Island and Ocean (Create island shape)</li><li>Smoothen the island</li><li>Flood-fill to differentiate lake and ocean</li><li>Calculate tile distance from shore </li><li>Create elevation (disabled in my preview)</li><li>Assign biomes</li><li>Smoothen the biomes</li><li>Generate Flora (trees)</li><li>Generate Resources</li></ul>



<p>The order of the generation isn&#x2019;t largely important, however, some are a prerequisite for others such as assigning biomes before generation flora because flora is dependent on biomes. But you can easily first generate resources and then the flora.</p>



<h2 class="wp-block-heading">Noise</h2>



<p>Throughout the post I use a lot of Noise-&gt;GetValue2D(x,y), this is how I get the Noise object.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="cpp" data-enlighter-theme data-enlighter-highlight data-enlighter-linenumbers data-enlighter-lineoffset data-enlighter-title data-enlighter-group>UUFNNoiseGenerator* Noise = UUFNBlueprintFunctionLibrary::CreateNoiseGenerator(this, ENoiseType::Simplex, Euclidean, CellValue, FBM, EInterp::InterpQuintic, SeedValue, 5, FrequencyValue, 2.f, .5f);</pre>



<h2 class="wp-block-heading">Generating the grid</h2>



<p>The generation of the grid is entirely done with the Procedural Mesh Component from Unreal Engine. </p>



<p>To start the generation we iterate over the grid x size and grid y size and calculate the position of that tile, then add 7 vertices (one for the center and one per corner). And after that, we add 6 triangles. We store this information in a struct per tile (indexes of the vertices, indexes of the triangles) so we can, later on, use it to assign biomes and elevate the terrain.</p>



<p>The actual calculation of the location of each vertex is neatly explained on this <a rel="noreferrer noopener" aria-label="blog post (opens in a new tab)" href="https://www.redblobgames.com/grids/hexagons/?ref=celdevs.com" target="_blank">blog post</a>.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="cpp" data-enlighter-theme data-enlighter-highlight data-enlighter-linenumbers data-enlighter-lineoffset data-enlighter-title data-enlighter-group>for (int x = 0; x &lt; xGridSize; x++)
{
      for (int y = 0; y &lt; yGridSize; y++)
      {
        // Calculate hexagon location and offset by actor location
        int32 UseIndex = x * xGridSize + y;

        //Create TSharedPtr of the tile
        Tiles[UseIndex] = MakeShareable(new FLevelTile(UseIndex, x, y));

        //Actually create the tile
        AddTileToMesh(x, y);
    }
}</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="cpp" data-enlighter-theme data-enlighter-highlight data-enlighter-linenumbers data-enlighter-lineoffset data-enlighter-title data-enlighter-group>void ALevelGenerator::AddTileToMesh(const int32&amp; x, const int32&amp; y)
{
	//Get center of the tile
	FVector Center = FVector::ZeroVector;
	GetCenterForTile(x, y, Center);

	//Always add center first
	AllUVS.Add(FVector2D(0.25, 0));
	int32 CenterIndex = AllVertices.Add(Center);

	//Get locations for each corner
	FVector a = GetCornerForTile(Center, NORTH);
	FVector b = GetCornerForTile(Center, NORTHEAST);
	FVector c = GetCornerForTile(Center, SOUTHEAST);
	FVector d = GetCornerForTile(Center, SOUTH);
	FVector e = GetCornerForTile(Center, SOUTHWEST);
	FVector f = GetCornerForTile(Center, NORTHWEST);

	//Add vertice
	int32 A = AddVertice(a);
	int32 B = AddVertice(b);
	int32 C = AddVertice(c);
	int32 D = AddVertice(d);
	int32 E = AddVertice(e);
	int32 F = AddVertice(f);

	//Create triangles
	int32 AT = AddTriangle(B, A, CenterIndex);
	int32 BT = AddTriangle(C, B, CenterIndex);
	int32 CT = AddTriangle(D, C, CenterIndex);
	int32 DT = AddTriangle(E, D, CenterIndex);
	int32 ET = AddTriangle(F, E, CenterIndex);
	int32 FT = AddTriangle(A, F, CenterIndex);
      
  //Add all A-F and AT-FT data into the tile struct
}</pre>



<p>To generate the mesh (after doing biome, resources, and flora) you need to add a ProceduralMeshComponent to your actor and have the following arrays in your class:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="cpp" data-enlighter-theme data-enlighter-highlight data-enlighter-linenumbers data-enlighter-lineoffset data-enlighter-title data-enlighter-group>TArray&lt;FVector&gt; AllVertices;
TArray&lt;int32&gt; AllTriangles;
TArray&lt;FVector2D&gt; AllUVs;
TArray&lt;FLinearColor&gt; AllColors; //I leave this empty</pre>



<p>AllVertices and AllTriangles get filled during the generation of the flat hexagonal grid.</p>



<p>To spawn the generated level use the code below. To be able to do that you need to include &#x201C;ProceduralMeshComponent.h&#x201D; and add &#x201C;ProceduralMeshComponent&#x201D; to your DependencyModuleNames.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="cpp" data-enlighter-theme data-enlighter-highlight data-enlighter-linenumbers data-enlighter-lineoffset data-enlighter-title data-enlighter-group>for (int i = 0; i &lt; AllVertices.Num(); i++)
{
    //I want all normals to be up, but you can change this if you want to
    AllNormals.Add(FVector::UpVector);
}

LevelMesh-&gt;CreateMeshSection_LinearColor(0, AllVertices, AllTriangles, AllNormals, AllUVS, AllColors, TArray&lt;FProcMeshTangent&gt;(), false);</pre>



<h2 class="wp-block-heading">Creating the island shape</h2>



<p>To create the island shape I create a simple Perlin Noise and apply a radial alpha on top of it. This will lerp out the edges of the noise to a darker tone and if we then use a MinAlpha then this will effectively create an island-like shape. Here&#x2019;s the end result of an island noise map.</p>



<p>The dark spots in the center of the noise map become oceans tiles as well and later on we flood-fill to determine if it&#x2019;s a lake. (A lake is defined as no connection with the border without crossing land)</p>



<div class="wp-block-image"><figure class="aligncenter is-resized"><img decoding="async" loading="lazy" src="https://celdevs.com/wp-content/uploads/2019/05/UE4Editor-Win64-DebugGame_2019-05-15_20-45-20.png" alt class="wp-image-72" width="463" height="465"></figure></div>



<pre class="EnlighterJSRAW" data-enlighter-language="cpp" data-enlighter-theme data-enlighter-highlight data-enlighter-linenumbers data-enlighter-lineoffset data-enlighter-title data-enlighter-group>//Map X index to 0 -1 
float UseX = (float)x/ (float)xGridSize; 

//Map Y index to 0 -1 
float UseY = (float)y/ (float)yGridSize; 

//Get Noise at X and Y
float NoiseValue = Noise-&gt;GetNoise2D(UseX , UseY); 

//In our case the noise returns -1 to 1, we want to map it to 0 to 1
float Value = MapRangeClamped(NoiseValue, -1.f, 1.f, 0.f, 1.f);

//Get our distance from the center rounded to nearest whole
int Distance = Round((CenterLocation - FVector2D(x, y)).Size());

//The further away from center the less likely it is to be island
float RadialAlpha = MapRangeClamped(Distance, 0, MaxDistanceFromCenter, 1.f, 0.f);

//Apply this value to our mapped NoiseValue(Value) which will fake a radial alpha 
const float FinalValue = Value * RadialAlpha;

if (value &gt; IslandMinAlpha)
{
  MarkTileAsType(GRASSLAND, TileIndex);
}
else
{
  MarkTileAsType(OCEAN, TileIndex);
}</pre>



<h2 class="wp-block-heading">Generating Biomes</h2>



<p>To generate biomes I use 3 noise maps (One for Tundra, one for Woodland and one for Snowland).</p>



<p>We take the value we get from the noise map (0 to 1)  and round it to nearest whole number(0.0-0.49 = 0, and 0.50-1.0 = 1), this will create very hard edged shapes which is useful for biomes.</p>



<p>For Tundra and Woodland we create an RGB value (Red = Tundra, Green = Woodland, Blue = nothing).</p>



<p>Based on the frequency of the Perlin noise per biome and different weight of the biome it can create different shapes and sizes.</p>



<p> With the RGB value we can determine if this tile is either black(Grassland), green(Woodland) or red(Tundra), and overlap of both colors is still woodland. Example of a biome noise map below.</p>



<div class="wp-block-image"><figure class="aligncenter is-resized"><img decoding="async" loading="lazy" src="https://celdevs.com/wp-content/uploads/2019/05/UE4Editor-Win64-DebugGame_2019-05-15_20-59-16.png" alt class="wp-image-80" width="407" height="404"></figure></div>



<p>For our purposes we want the Snowland biome to always be in the center of the map and always a minimum radius so to do that we get distance from center and if that distance from center is smaller than SnowRadius then it will always be snow, if distance is larger than SnowRadius but smaller than SnowRadius*2 then it will use a radial alpha on the snow noise map to create a non-round falloff around the snow biome.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="cpp" data-enlighter-theme data-enlighter-highlight data-enlighter-linenumbers data-enlighter-lineoffset data-enlighter-title data-enlighter-group>if (Tile-&gt;GetGroundType() == GRASSLAND || Tile-&gt;GetGroundType() == WATER)
{

//Get tundra noise
float fTundraNoise = TundraNoise-&gt;GetNoise2D(x / xGridSize, y / yGridSize);
//Get woodland noise
float fWoodlandNoise = WoodlandNoise-&gt;GetNoise2D(x / xGridSize, y / yGridSize);
//Get snowland noise
float fSnowlandNoise = SnowlandNoise-&gt;GetNoise2D(x / xGridSize, y / yGridSize);

//Tundra final value
float R = MapRangeClamped(TundraNoise , -1.f, 1.f, 0.f, 1.f) * TundraWeight; 
//Woodland final value
float G = MapRangeClamped(fWoodlandNoise , -1.f, 1.f, 0.f, 1.f) * WoodlandWeight; 
//Snowland final value
float A = MapRangeClamped(fSnowlandNoise , -1.f, 1.f, 0.f, 1.f);  

//Round R and G to nearest whole number
R = RoundToInt(R);
G = RoundToInt(G);

//Get Distance from center of the map in tile distance
int Distance = RoundToInt((CenterLocation - FVector2D(x, y)).Size());

//Get radial addition for snowland
float RadialAddition= MapRangeClamped(Distance, SnowRadius, SnowRadius * 2, 1.f, 0.f);

//Get radial alpha for snowland
float RadialAlpha = MapRangeClamped(Distance, SnowRadius, SnowRadius*2, 1.f, 0.f);

//Get ground type from color(Red = Tundra, Green = Forestland, Black = Grassland)
Type = GetBiomeFromColor(FVector(R, G, 0));

//Check if tile is within snow radius and if not if tile has MinSnowAlpha
if ((A+ RadialAddition)*RadialAlpha &gt; MinSnowAlpha)
{
  //If so we want this to be Snowland (override the previous biome)
  Type = SNOWLAND;
}

//If we are snowland
if (Type == SNOWLAND)
{
  //And we are water then this water(lake) should become ice water
  if (Tile-&gt;GetGroundType() == WATER)
  {
      Type = ICEWATER;
   }
}

//If the tile type is already water and not ice water then we want to keep it water
if (Tile-&gt;GetGroundType() == WATER &amp;&amp; Type != ICEWATER)
{
  Type = WATER;
}
//At the end mark this tile as the type
MarkTileAsType(Type, TileIndex);
}</pre>



<h2 class="wp-block-heading">Generating Flora</h2>



<p>To create trees we again create one noise map per biome, this case 4 noise maps (Tundra, Woodland, Grassland and Snowland), each biome has different density and frequency for its trees.</p>



<p>For example, Woodland has dense clusters of trees while grassland has more single standing trees and tundra barely has trees.</p>



<p>For trees, we want to get the noise at the tile index and if it&#x2019;s larger than a minimum alpha then that tile should become the tree for that tile.</p>



<p>Values I used for a 150 x 150 grid for the biomes:</p>



<ul><li>Tundra -&gt; 0.2 Frequency and 0.3 minimum alpha</li><li>Woodland -&gt; 0.2 Frequency and 0.5 minimum alpha </li><li>Grassland -&gt; 0.5 Frequency and 0.5 minimum alpha </li><li>Snowland -&gt; 0.2 Frequency and 0.4 minimum alpha </li></ul>



<p>The lower the frequency the larger the maximum size of a single cluster of trees, the higher the minimum alpha the fewer trees a single tree cluster will be.</p>



<p>Below an example of various woodland settings:<br><br>0.2 Frequency and 0.5 minimum alpha   <br>0.1 Frequency and 0.5 minimum alpha <br>0.2 Frequency and 0.15 minimum alpha    </p>



<ul class="wp-block-gallery columns-3 is-cropped wp-block-gallery-1 is-layout-flex"><li class="blocks-gallery-item"><figure><a href="https://celdevs.com/?attachment_id=97"><img decoding="async" src="https://celdevs.com/wp-content/uploads/2019/05/HighresScreenshot00005-1024x798.png" alt data-id="97" data-link="http://celdevs.com/?attachment_id=97" class="wp-image-97"></a><figcaption>0.2 &#x2013; 0.5</figcaption></figure></li><li class="blocks-gallery-item"><figure><a href="https://celdevs.com/?attachment_id=100"><img decoding="async" src="https://celdevs.com/wp-content/uploads/2019/05/HighresScreenshot00008-1024x798.png" alt data-id="100" data-link="http://celdevs.com/?attachment_id=100" class="wp-image-100"></a><figcaption>0.1 &#x2013; 0.3</figcaption></figure></li><li class="blocks-gallery-item"><figure><a href="https://celdevs.com/?attachment_id=96"><img decoding="async" src="https://celdevs.com/wp-content/uploads/2019/05/HighresScreenshot00007-1024x798.png" alt data-id="96" data-link="http://celdevs.com/?attachment_id=96" class="wp-image-96"></a><figcaption>0.2 &#x2013; 0.15</figcaption></figure></li></ul>



<h2 class="wp-block-heading">Generating Resources</h2>



<p>Creating resources is done with one noise map per resource, we have 3 (Stone, Iron, Gold). For every tile, we get the noise value per resource type and if the value is lower than the minimum alpha for that resource type we set it to 0 so it doesn&#x2019;t spawn at that tile.<br><br>After that, we again use the 3 noise values as RGB data, and we select which is the strongest on that tile by selecting the highest value. If no value is larger than 0 then none of the noise maps has the required minimum alpha.</p>



<p>After it passed the minimum alpha check we do one other check with a weight value, if a random roll between 0-1 is larger than the weight for that resource it wins and can add itself to the level.</p>



<p>Resources are quite rare and would rarely spawn in clusters so we use very high frequency values and high minimum alphas. For our purpose, we have different weights per biome but the same minimum alpha globally (Gold can never spawn in grassland for example).</p>



<p>Values I used for the resources in Snowland biome:</p>



<ul><li>Iron -&gt; 50 Frequency and 0.8 weight</li><li>Stone -&gt; 50 Frequency and 0.9 weight</li><li>Gold -&gt; 50 Frequency and 0.7 weight</li></ul>



<p>Here&#x2019;s an example of a resource noise map. (R= Stone, G = Iron, B = Gold)</p>



<div class="wp-block-image"><figure class="aligncenter is-resized"><img decoding="async" loading="lazy" src="https://celdevs.com/wp-content/uploads/2019/05/UE4Editor-Win64-DebugGame_2019-05-15_21-56-10.png" alt class="wp-image-102" width="426" height="426"></figure></div>



<p></p>



<p></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme data-enlighter-highlight data-enlighter-linenumbers data-enlighter-lineoffset data-enlighter-title data-enlighter-group>//Get Stone noise
const float fStoneNoise = StoneNoise-&gt;GetNoise2D(x / xGridSize, y / GridSize);
//Get Iron noise
const float fIronNoise = IronNoise-&gt;GetNoise2D(x / xGridSize, y / GridSize);
//Get Gold noise
const float fGoldNoise = GoldNoise-&gt;GetNoise2D(x / xGridSize, y / GridSize);

//Stone final value
float R = MapRangeClamped(fStoneNoise, -1.f, 1.f, 0.f, 1.f); 
 //Iron final value
float G = MapRangeClamped(fIronNoise, -1.f, 1.f, 0.f, 1.f);
//Gold  final value
float B = MapRangeClamped(fGoldNoise, -1.f, 1.f, 0.f, 1.f);                

if (R &lt; StoneWeight) //Lower than weight, never pick
{
  R = 0.f;
}
if (G &lt; IronWeight) //Lower than weight, never pick
{
  G = 0.f;
}
if (B &lt; GoldWeight) //Lower than weight, never pick
{
  B = 0.f;
}

NewResourceType = NONE;

if (R &gt; G &amp;&amp; R &gt; B) //Pick stone
{
  NewResourceType = STONE;
}
else if (G &gt; R &amp;&amp; G &gt; B) //Pick iron
{
   NewResourceType = IRON;
}
else if (B &gt; R &amp;&amp; B &gt; G) //Pick Gold
{
  NewResourceType = GOLD;
}

//Did we select a resource?
if (NewResourceType != ESGGridResourceType::GRT_NONE)
{
 if(RandomBoolWithWeightFromStream(GetBiomeResourceWeight(Biome,NewResourceType))
 {
    //Spawn the resource and set tile resource type to NewResourceType
    Tile-&gt;SetResourceType(NewResourceType);
  }
}</pre>



<h2 class="wp-block-heading">UVS and Materials</h2>



<p>Obviously, after you generate the shape and setup all your biomes you need to be able to color your island. Based on the biome for every tile I select a different UV space per biome. I create a triangle shape in the UV per triangle located where the mask for that biome goes. Below is a picture of the UVs per biome. </p>



<div class="wp-block-image"><figure class="aligncenter is-resized"><img decoding="async" loading="lazy" src="https://celdevs.com/wp-content/uploads/2019/05/UE4Editor-Win64-DebugGame_2019-05-15_22-17-48.png" alt class="wp-image-111" width="377" height="374"></figure></div>



<p>To actually color/materialize the mesh I created a mask per location of a triangle and apply a different world aligned texture per mask and then combine everything into a single material end result. This is probably not optimal and can be improved but then again I&#x2019;m not a material artist &#x1F642;</p>



<p>Here&#x2019;s an image of the material I created.</p>



<div class="wp-block-image"><figure class="aligncenter is-resized"><img decoding="async" loading="lazy" src="https://celdevs.com/wp-content/uploads/2019/05/UE4Editor-Win64-DebugGame_2019-05-15_22-20-50.png" alt class="wp-image-112" width="452" height="374"></figure></div>
<!--kg-card-end: html-->]]></content:encoded></item></channel></rss>