OnAnyDamageTaken() arguments

Hello,

I am trying to get better at using the unreal Api, even though it really feels lacking in comparison to other Api that I have used before. I have ran into a couple scenarios with these multicast delegate functions like OnAnyDamageTaken() used in Toon Tanks “Apply Damage” video where the documentation does not do a great job at telling me what arguments the bound function has to take, and that is the important part.

Another Example is the overlap delegate functions, I would like to know if there is another place in the api I am not finding for this information. Here is the links I am looking at for the 2 mentioned functions.

OnTakeAnyDamage | Unreal Engine Documentation

OnComponentBeginOverlap | Unreal Engine Documentation

1 Like

Unfortunately this is rather poorly documented, and by that I of course mean - not at all

If you go to the definition of OnTakeAnyDamage you’ll get

UPROPERTY(BlueprintAssignable, Category="Game|Damage")
FTakeAnyDamageSignature OnTakeAnyDamage;

If you go to the definition of FTakeAnyDamageSignature you’ll get

DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_FiveParams( FTakeAnyDamageSignature, AActor, OnTakeAnyDamage, AActor*, DamagedActor, float, Damage, const class UDamageType*, DamageType, class AController*, InstigatedBy, AActor*, DamageCauser );

This macro is what creates the delegate.
The first argument is the delegate class name, then the owning class, then delegate name.
following that is the parameter types and names followed by semicolons

So copy after the third argument and remove every other comma.

3 Likes

Okay, I wanted to provide some more information on this since I went on a deep dive into the internals of Unreal Engine on trying to find the actual implementations of all of this.

So there’s not any documentation that defines the function signature that the pre-defined delegates accept. That’s mostly because the function signatures for these are only created at compile time through macros. You can still learn a bit more about how they’re created to more easily figure it out, but it requires reading through the Unreal Engine code.

Fair warning, this is gonna be a bit long. There’s no easy way to really describe how to find the underlying code for delegates.

The delegates in Unreal Engine have a few macros that allow you to create your own delegates if you want (it’s also these macros that makes this more confusing than it needs to be). Some actual documentation on creating your own delagates can be found here Delegates and Lamba Functions in Unreal Engine | Unreal Engine Documentation.

Unreal Engine provides a few of these delegates created from those macros for you on the AActor class, OnTakeAnyDamage, OnActorBeginOverlap, etc… I’m just gonna try to explain OnTakeAnyDamage, but for the other pre-defined delegates you can follow the same process that I’ll try to explain here in order to find the function signature needed.

As DanM mentioned you can go to the definition of OnTakeAnyDamage and then go the definition of the type for FTakeAnyDamageSignature. That brings you to the macro DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_FiveParams. Once you’re here you can figure out the function signature you need.

Long story short, ignore the first 3 arguments and look at these to get the function signature:

AActor*, DamagedActor, float, Damage, const class UDamageType*, DamageType, class AController*, InstigatedBy, AActor*, DamageCauser

And what about the function return type? According to the docs this defaults to void. Unless a macro is used that specifies _RetVal__ on it. For those the return type will be part of the macro argument list, for example DECLARE_DELEGATE_RetVal(RetValType, DelegateName), RetValType being the return type you’ll use on your function. If it’s DECLARE_DELEGATE_RetVal(float, DelegateName), the return value is a float.

Now we know the return value and the argument list that’s expected by the delegate for functions added to AddDynamic().

That leads us to this signature:

void Function(AActor* DamagedActor, float Damage, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser);

So that’s probably good enough for most people. You now can find the function signature for any of the other delegates that you run into.

Now if you want to understand more about the first 3 arguments continue reading…

So the first 3 arguments just inform the macro the name of the type to create for the delegate and what to create the delegate on. If you hover over the macro you’ll get this signature:

DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_FiveParams( SparseDelegateClass, OwningClass, DelegateName, Param1Type, Param1Name, Param2Type, Param2Name, Param3Type, Param3Name, Param4Type, Param4Name, Param5Type, Param5Name )
  1. SparseDelegateClass - this is the name of the class the macro will create, the class does not exist until compile time when the macro is run
  2. OwningClass - this is the name of the existing class the created delegate will be added to
  3. DelegateName - this is the name of the variable on the OwningClass the delegate will be “assigned” to

Okay, but what is a sparse delegate? The documentation on delegates doesn’t mention anything about _SPARSE_ delegates, so what even are those?

In the case of SparseDelegateClass we can dive a little bit more into the FTakeAnyDamageSignature type to learn more. If you hover over that type you’ll see this:

struct
FTakeAnyDamageSignature : public TSparseDynamicDelegate<FTakeAnyDamageSignature_MCSignature, AActor, FTakeAnyDamageSignatureInfoGetter>

From that we can tell it’s a struct that inherits from TSparseDynamicDelegate. Click into that type and we get this:

struct
TSparseDynamicDelegate<MulticastDelegate, OwningClass, DelegateInfoClass> : public FSparseDelegate
Sparse version of TBaseDynamicDelegate
 SparseDelegate.h

So now we got an actual file location, SparseDelegate.h. Why didn’t FTakeAnyDamageSignature have a file location? Well that’s due to it not having one until the macro generates a class/struct of that name at compile time.

So let’s find where SparseDelegate.h is located. This will require searching on the GitHub source for Unreal Engine. If you don’t have access to the GitHub repository follow this guide, it’s free to get access, just gotta link your GitHub account.

Searching for that file leads us to here . At the top of that file there’s a comment that gives us a little more information on what a sparse delegate is:

/**
*  Sparse delegates can be used for infrequently bound dynamic delegates so that the object uses only 
*  1 byte of storage instead of having the full overhead of the delegate invocation list.
*  The cost to invoke, add, remove, etc. from the delegate is higher than using the delegate
*  directly and thus the memory savings benefit should be traded off against the frequency
*  with which you would expect the delegate to be bound.
*/

So that indicates a sparse delegate is just a memory optimized delegate since it’s expected to not be binded to that frequently (or ever). Since OnTakeAnyDamage is on the AActor class and everything tends to use it as a base class in Unreal Engine that seems like a good thing.

Now at the bottom of that file we actually find the #define for DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_FiveParams here. Sweet, oh wait… It’s a macro that calls other macros :upside_down_face:. I’m not gonna dive into what that macro is actually doing, tbh I don’t understand it, but at least you know which file to look at to learn more if you want.

Okay so that’s a little bit more information on sparse delegates.

Now the second argument OwningClass, well in the case of OnTakeAnyDamage that uses the AActor class. So this is just letting the macro know what existing class to create the delegate on.

Then the third argument DelegateName, that’s just a class variable on the OwningClass, in this case it’s letting the macro know that on AActor there’s a OnTakeAnyDamage variable that the delegate will be created on.

So that takes care of the first 3 arguments, now we get to Param1Type, then Param1Name, and repeated for 5 parameters (as the macro indicates _FiveParams) which indicates the name and types of the 5 parameters that will be passed to the function you add with AddDynamic().

That’s about it for determining what those arguments mean on the macro.

If you want to look at the location where the non-sparse delegates are created you’ll find that here. The macros and base delegate types are all defined there and in the other files under the Delegates/ directory.

So hopefully that at least gives you more information on where to find the implementations for these delegate macros. But most importantly, you can now hopefully understand where to find the function signature of delegates like OnTakeAnyDamage.

3 Likes

This topic was automatically closed 20 days after the last reply. New replies are no longer allowed.

Privacy & Terms