Iterators : The proper way

यदा यदा हि धर्मस्य ग्लानिर्भवति भारत

धर्मसंस्थापनार्थाय सम्भवामि युगे युगे ॥7-8, Chapter 4॥

A shalok or verse, from Geeta, that is known to many Hindus, or the gist of which gets reflected in our actions (I am a Hindu) anyway, is the representation of iterative theme, of course, coupled with a right action mentioned above. The meaning implies that Shri Krishna takes birth in an epoch, time again, whenever there is rise of evil, to establish law and order.

Unreal Engine has a way of iterating over the C++ objects which has been used in hundreds of AAA games covering a stretch of couple of decades.

The iteration is done like so

for (TActorIterator<AActor> ActorItr(testWorld); ActorItr; ++ActorItr)
{
	// print name
	KR_INFO("Iterating over actor: {0}", (*ActorItr)->GetName());
}

The TActorIterator template is defined here. The pseudo code for iteration roughly is

  1. Initialize ActorList with the appropriate UObjects (of a world or editor relevant objects for instance) of cached objects.
  2. Appropriately define (overload) the increment (++) operator with desired class filters and appropriate checks

A fruitful thing is to think what remains constant and what doesn’t in this way of iteration, which in turn defines the concept of iteration.

DELEGATES IN UNREAL ENGINE

Delegates are user defined datatypes that can be bound to C++ function(s) (or blueprint event(s)) of an object in a generic way. This means that the object need not be even declared for a particular delegate. Let me demostrate.

In Unreal Engine, a delegate is declared like so:

DECLARE_DYNAMIC_DELEGATE_TwoParams(FAnEvent, float, TimeCoordinate, float, SpaceCoordinate);

This just tells the Engine that we are declaring a user defined datatype (a delegate) which can be bound to a C++ routine with two parameters of type float. Now this delegate can be bound to any member function (with signature void (float, float)) of any object.

For demonstration purposes, consider the following code (TP_PickupComponent.h)

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPickUp, ABPToCodeDemoCharacter*, PickUpCharacter);

Furthermore, we note the blueprint usage property of the delegate (see)

UPROPERTY(BlueprintAssignable, Category = "Interaction")
FOnPickUp OnPickUp;

Finally, we observe the application of the delegate here, like so

void UTP_PickUpComponent::OnSphereBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	// Checking if it is a First Person Character overlapping
	ABPToCodeDemoCharacter* Character = Cast<ABPToCodeDemoCharacter>(OtherActor);
	if(Character != nullptr)
	{
		// Notify that the actor is being picked up
		OnPickUp.Broadcast(Character);

		// Unregister from the Overlap Event so it is no longer triggered
		OnComponentBeginOverlap.RemoveAll(this);
	}
}

This basically translates to the meaning that when the overlap detector of the pickup detects, well, an overlap with player’s character (no pun intended), then notify all the registered events (bound to OnPickUp variable of type delegate defined above) and run the appropriate logic in their respective classes.

The blueprint equivalent of registering event by binding to OnPickUp delegate (.AddDynamic()) is like so (Bind Event On Pick Up node)

The blueprint equivalent of

OnPickUp.Broadcast(Character);

is Call On Pick Up node

A nice property of the delegates is that they can be used in appropriate arbitrary classes (mentioned above). Look at the call to Binding Delegates node, which is a function of class ABPToCodeDemoCharacter (which is not declared or defined where the delegate is declared). The function is defined like so

We can see that an event AnEvent is bound to OnPickUp in the class even though OnPickUp is not declared/defined in the class. The event is like so

Checkout the commit Delegate demonstration (literally :D) to see the logic in action.

A fun fact I saw while working in Sunovatech Pvt. Ltd is that delegate can be passed as a C++ function parameter https://forums.unrealengine.com/t/delegates-as-parameter/299175/13.

AI Behavior In Unreal Engine

Either Unreal Engine 5 AI has become more intuitive or I am spending right time with the AI. Recently I made a zombie game first person shooter prototype with UE 5.4.4. The natively playable prototype binaries can be found at this GitHub page (available for Linux, Mac, and Windows). But, as with several different software, the versioning usually, in some sense, can make it non-intuitive.

Disclaimer: This is not to imply that what follows, the concept of Observer Aborts, is an example of different implementation with different versions of UE. From forums it seems like, but I am not sure.

The story starts with the following image

If you are new to Unreal’s behavior trees, might I refer to here, here, and here. We are interested in the BT_ZombieBehavior window containing the behavior tree for zombie AI. Clearly, as per the decorator in Sequence node (third level from ROOT), the Move To task shouldn’t be executed if Target Enemy (query key) is not set, meaning set to NULL.

This wasn’t happening practically in the game. The zombies were still moving towards the player (chase mode) when Target Enemy was not set. Native logging verified that this is case. Now, following my friend’s noble advice, on doing some forum reading, I stumbled upon https://forums.unrealengine.com/t/ai-blackboard-based-condition-does-not-abort/511343.

Based upon the observation in the forum thread, consider the following image

All we have now is to understand the purpose of setting the field Observer Aborts to self in UE 5.4.4. For that we refer to official UE documentation page. The page mentions that setting the field to Self, aborts self (node) and any subtrees, which is Move To task in this context, running under the node. This means that even if Target Enemy is NULL, Move To task is being executed with whatever Target Enemy was set earlier and, if the task is not completed, AI won’t halt the zombie, leading to an infinite pursuit, even when the pawn vehicle is out of sight.

Upon further forums’ reading, for instance this, seems like an optimization (caching?) for changing decorator conditions, which probably is our case, for re-evaluation of “if Target Enemy is set”, which updates appropriately if Observer Aborts is set to self.

Extra Credit

If you have been observing pedantically enough, in the second level from ROOT, Selector node has been replaced by Sequence which, in practical sense, is a mental exercise only. In this context, there is no practical difference between them. Because, when using Selector (at second level), until the Move To task is completed, the behaviour tree control won’t go to the Sequence with BTTask_Halt child, unless the Target Enemy is set to NULL, in which case, Move To task will be aborted, moving the control to the parent of halt task.

Even simpler version of behaviour tree, in this context, which gives the same result is like so

Since self abort stops the execution of Move To task, BTTask_Halt, which essentially sets the MoveTo actor to NULL, has no need. Probably self abort does that already.

Geometrical representation of the Killing spinors preserving N=4 supersymmetry (I)

In the low energy limit the mysterious M-theory boils down to a much tractable d=11 Supergravity theory (SUGRA). Therefore it is essential to understand the supersymmetric constraints of the theory which have crucial applications in the field of holography.

Supersymmetry is essentially a (very awesome if you ask me!) symmetry which keeps the theory invariant under the bosonic and fermionic variations given by

\delta_\epsilon\Theta =\epsilon\\\delta_\epsilon X^M=i\bar{\epsilon}\Gamma^M\Theta

Here \epsilon is a Killing spinor which satisfies the Killing equation

\nabla_X\epsilon=\lambda X.\epsilon

It becomes covariantly constant for \lambda =0. In the curved solutions of the SUGRA, supersymmetries are broken due to the non trivial covariant derivative. In order to preserve SUSY, the solutions of the Killing equation play essential role. We focus on those spinors which are invariant under the spin lift of the holonomy group of the appropriate manifold.  For d=11 SUGRA, the Killing equation takes the following algebraic form

\nabla_M\epsilon+\frac{1}{288}\left(\Gamma_M^{NPQR}-8\delta^N_M\Gamma^{PQR}\right)G_{NPQR}\epsilon=0

Now the notion of the G-structures essentially classifies the special differential forms which arise in the supersymmetric flux compactifications. As can be deduced from the Killing equation, the solutions characterize the Spin Bundle of the supersymmetries with the metric of the manifold with a spin structure in a very intimate way.

Definition: A spin structure on a manifold (\mathcal{M},g) with signature (s,t) is a principle Spin(s,t)-bundle with Spin(\mathcal{M})\to \mathcal{M} together with a bundle morphism \phi : Spin(\mathcal{M})\to SO(\mathcal{M}).

To define the G-structure, we associate the differential forms with the Killing spinors as follows

\Omega^{ij}_{\mu_1\mu_2\ldots\mu_k}=\bar{\epsilon}^i\Gamma_{\mu_1\mu_2\ldots\mu_k}\epsilon^j

The aim is to show that these differential forms obey the set of the first order differential equations as a natural consequence of the Killing equations. Now it can be shown that for Clabi-Yau manifolds, or manifolds with the G_2 holonomy, one usually finds the Killing spinor bundles trivially defined by an algebraic projection which are some differential forms applied to the complete spin bundle.

So this seems like a good point to start and make an ansatz for the projection operator for the spin bundle structure in the curved spacetime. These projections are essentially the differential forms defined above which give rise to the notion of the G_2 structures.

Here (for the reasons beyond me right now), three projection operators are defined \Pi_j for j=0,1,2 which break the 32 supersymmetries to four. Another factual data is that if there is a holographic dual to the theory with a Coulomb branch, then there is a non-trivial space of the moduli for brane probes. This moduli space will be realized as conformally Kahler section of the metric (for four supersymmetries). And it is on this section of the metric, supersymmetries will satisfy projection conditions \Pi_j\epsilon=0 with the form \Pi_j=\frac{1}{2}(1+\Gamma^{\xi_j}), where \Gamma^{\xi_j} represents the product of gamma matrices parallel to the moduli space of the branes.

Now we can find the equations of motion of the theory by demanding that the fermionic variations vanish, implying the Killing equation! The solution we are considering here essentially has the topology of AdS_4\times S^7. Using the orthonormal frames, https://arxiv.org/pdf/hep-th/0403006.pdf shows the presence of a Kahler structure on the brane-probe moduli space as a conformal multiple of

J_{\text{moduli}}=e^6\wedge e^9+e^7\wedge e^8-e^5\wedge e^{10}

I will continue from here in the next blog-post!