Unreal Engine 4: C++ Delegate not being called
Asked Answered
M

2

7

I've been working on converting some blueprint logic over to C++. One of the things I have is a button. The button can be pressed in VR and has a delegate that is called to notify any registered functions that the button press occurred. Here is how the delegate is declared in the AButtonItem.h class.

#pragma once
#include "BaseItem.h"
#include "ButtonItem.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FButtonItemPressedSignatrue);

UCLASS()
class AButtonItem : public ABaseItem
{
    GENERATED_BODY()

protected:
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Touch)
    float myMaxButtonPress;

public:

    UPROPERTY(EditAnywhere, Category = Callback)
    FButtonItemPressedSignatrue ButtonItem_OnPressed;
};

The delegate's broadcast function is then being called when the button is pressed like so:

ButtonItem_OnPressed.Broadcast();

(This function should defiantly be called because I have a debug statement that prints right before the call. Its also important to note this was all working when it was blueprint logic.)

Here is where I try to register with the delegate and how I declared the function that will be called:

WeaponMaker.h:

UFUNCTION()
void OnNextBladeButtonPressed();

WeaponMaker.cpp:

void AWeaponMaker::BeginPlay()
{
    Super::BeginPlay();

    TArray<USceneComponent*> weaponMakerComponents;
    this->GetRootComponent()->GetChildrenComponents(true, weaponMakerComponents);

    for (int componentIndex = 0; componentIndex < weaponMakerComponents.Num(); componentIndex++)
    {
        if (weaponMakerComponents[componentIndex]->GetName().Equals("NextBladeButton") == true)
        {
            myNextBladeButton = (AButtonItem*)weaponMakerComponents[componentIndex];
            break;
        }
    }

    if (myNextBladeButton != NULL)
    {
        myNextBladeButton->ButtonItem_OnPressed.AddDynamic(this, &AWeaponMaker::OnNextBladeButtonPressed);
    }

}

I put a breakpoint and a print statement in the function OnNextBladeButtonPressed so I should immediately know when it works but its never happening. I also re-created the blueprint itself from scratch but still no luck. Sometimes on compile I get a crash due to the InvocationList being invalid but I haven't found much info on that issue either. Bottom line is, OnNextBladeButtonPressed is not getting called when it should be.

Edit: Here is where I call the broadcast function in my AButtonItem code. It seems to be getting called since i see the UE_LOG output in the console:

void AButtonItem::Tick(float deltaTime)
{
    FTransform buttonWorldTransform;
    FVector buttonLocalSpacePos;
    FVector ownerLocalSpacePos;
    FVector localDiff;
    float buttonPressAmount;

    if (myHasStarted == true)
    {
        Super::Tick(deltaTime);

        if (myButtonComponent != NULL)
        {
            if (myPrimaryHand != NULL)
            {
                //Get the world space location of the button.
                buttonWorldTransform = myButtonComponent->GetComponentTransform();

                //Convert the location of the button and the location of the hand to local space.
                buttonLocalSpacePos = buttonWorldTransform.InverseTransformPosition(myInitialOverlapPosition);
                ownerLocalSpacePos = buttonWorldTransform.InverseTransformPosition(myPrimaryHand->GetControllerLocation() + (myPrimaryHand->GetControllerRotation().Vector() * myPrimaryHand->GetReachDistance()));

                //Vector distance between button and hand in local space.
                localDiff = ownerLocalSpacePos - buttonLocalSpacePos;

                //Only interested in the z value difference.
                buttonPressAmount = FMath::Clamp(FMath::Abs(localDiff.Z), 0.0f, myMaxButtonPress);
                localDiff.Set(0.0f, 0.0f, buttonPressAmount);

                //Set the new relative position of button based on the hand and the start button position.
                myButtonComponent->SetRelativeLocation(myButtonInitialPosition - localDiff);

                //UE_LOG(LogTemp, Error, TEXT("buttonPressAmount:%f"), buttonPressAmount);
                if (buttonPressAmount >= myMaxButtonPress)
                {
                    if (myHasBeenTouchedOnce == false)
                    {
                        //Fire button pressed delegate
                        if (ButtonItem_OnPressed.IsBound() == true)
                        {
                            ButtonItem_OnPressed.Broadcast();
                            AsyncTask(ENamedThreads::GameThread, [=]()
                            {
                                ButtonItem_OnPressed.Broadcast();
                            });
                        }

                        myHasBeenTouchedOnce = true;
                        myButtonComponent->SetScalarParameterValueOnMaterials("State", 1.0f);
                        Super::VibrateTouchingHands(EVibrationType::VE_TOUCH);
                    }
                }
            }
            else
            {
                //Slowly reset the button position back to the initial position when not being touched.
                FVector newPosition = FMath::VInterpTo(myButtonComponent->GetRelativeTransform().GetLocation(), myButtonInitialPosition, deltaTime, 10.0f);
                myButtonComponent->SetRelativeLocation(newPosition);
            }
        }
    }
}
Matheson answered 29/6, 2019 at 22:21 Comment(20)
if you put a breakpoint on the AddDynamic line, do you successfully hit that breakpoint?Nomadic
Yes it looks like I'm hitting it, here is a screen shot: i.imgur.com/AVrySOw.pngMatheson
I am wondering if there is somehow more than one component with a name of NextBladeButton and it's finding the wrong one. What happens if you remove the break; and put a breakpoint in that if block and see how many times it goes into that block?Nomadic
Stepped though with the debugger and it looks like it only happened once, ill get you a screenshot shortly.Matheson
Here is the screenshot: i.imgur.com/qCee7ka.pngMatheson
What's the signature FButtonItemPressedSignatrue?Spree
Also where and how is ButtonItem_OnPressed assigned?Spree
@SilvanoCerza You can see how I declared FButtonItemPressedSignatrue in the ButtonItem.h, AddDelegate is called on ButtonItem_OnPressed in the above BeginPlay function. I added the code to the original question for where broadcast is being called (Tick function in the ButtonItem class)Matheson
Is OnNextBladeButtonPressed marked UFUNCTION? What's its definition?Spree
I double checked and yes I think it is, here is a screenshot of the declaration: i.imgur.com/49FKARG.png and the implementation: i.imgur.com/l02szkt.pngMatheson
What does ButtonItem_OnPressed.IsBound() returns right after AddDynamic() and right before Broadcast()? Also is AWeaponMaker a UObject? I guess it is but just want to be sure.Spree
AWeaponMaker is an AActor not a UObject. In the button class it looks like it is getting unbound (screenshot: i.imgur.com/eE1nTJo.png)? Even though AddDynamic is being called on it. I noticed right after AddDynamic that it says bound but the object is stale, here is a screenshot: (i.imgur.com/30G2oMs.png).Matheson
I ran it again and it looks like it is bound after AddDynamic, i added a print statement and it is showing up right after AddDynamic. But by the time it gets to the button class its unbound? It also sometimes crashes on the AddDynamic call like this: i.imgur.com/lCze5kv.pngMatheson
@Matheson Who's the owner of AWeaponMaker and how and where is instantiated?Spree
The blueprint has a bunch of child actors (AButtonItems) and that blueprint's parent class is AWeaponMaker. The blueprint is called WeaponMakerBlueprint The blueprint is placed in the persistent level.Matheson
Have you verified that you're calling Broadcast on the same instance of AButtonItem that has a bound delegate?Spree
I have to imagine so, I removed all the other buttons except for one and I checked to make sure the name was "NextBladeButton".Matheson
Try printing the address of ButtonItem_OnPressed before AddDynamic and Broadcast and verify they're equal.Spree
@SilvanoCerza Sorry for the delay, I took screenshots of what the addresses are at the time of AddDynamic and Broadcast. Keep in mind that the ButtonItem is a blueprint so in the WeaponMaker, I have a child actor for that button item, here are the screenshots: Button Before Add Dynamic: i.imgur.com/TP5eJXH.png ButtonComponent Before Broadcast: i.imgur.com/ibvVAZS.png this button item Before Broadcast: i.imgur.com/UyzUhPw.pngMatheson
Lost a total of 150 bounty points on this.Matheson
R
4

First of all:

UPROPERTY(EditAnywhere, Category = Callback)
FButtonItemPressedSignatrue ButtonItem_OnPressed;

This should be:

UPROPERTY(BlueprintAssignable, Category = Callback)
FButtonItemPressedSignatrue ButtonItem_OnPressed;

For convenience.

Secondly the tick function may be called before begin play is executed for a number of reasons. Your even't won't be broadcasted if the game hasn't begin play yet. So to avoid just add a check in your tick function.

if(bHasBegunPlay)
{
  // .. your logics ...
}
Rime answered 16/7, 2019 at 6:46 Comment(6)
I tried this but no luck. I linked some debug images in the above comments to see if the addresses are correct/equal.Matheson
It's highly unlikely that you would get such issues with the fixes i mentioned in my answer. Would you be able to update your question with the actual code?Rime
No problem, you can now see the update. I set myHasStarted to false in the constructor and then I set it to true in BeginPlay().Matheson
UPROPERTY(EditAnywhere, Category = Callback) FButtonItemPressedSignatrue ButtonItem_OnPressed; };Rime
Did you change this as in my answer?Rime
Yea, here's a screenshot: i.imgur.com/TDugpnc.png . Also take a look at the comments in the main question, I've been updating them periodically.Matheson
G
2

Sometimes on compile I get a crash due to the InvocationList being invalid but I haven't found much info on that issue either. Bottom line is, OnNextBladeButtonPressed is not getting called when it should be.

I don't see any issue in the code from the question. At my glance, the issue could be in different location. I would suspect that AWeaponMaker had been deleted at moment of broadcasting.

Gramnegative answered 5/7, 2019 at 12:34 Comment(6)
Interesting point but I can see the WeaponMaker game object in front of me in the game when I press the button so at least the world game object is not being destroyed, also the button is not being destroyed ether (at least not visually). When I stepped through the debugger I could see "this" and "myNextBladeButton" were created at the point of AddDynamic. Is there a way we can test to see if it is being deleted like you mentioned?Matheson
You could add some logging in the destructor to be sure.Spree
I added these debug statements: i.imgur.com/kq4uirX.png in both the tick and the destructor. In the destructor it looks like myNextBladeButton is null at the time it is called, but I'm not deleting it anywhere so why is the destructor getting called after initializing it?Matheson
this == NULL will always return false since this can't be a nullptr.Spree
I would think so too but I saw a weird exception randomly where it was saying this is null.Matheson
@SilvanoCerza delete this; It may not be null, but it's doing to be one hell of a ride... or ((MyClass*)nullptr)->SomeMethod();Gushy

© 2022 - 2024 — McMap. All rights reserved.