虚幻引擎4:C++代理未被调用

7

我一直在将一些蓝图逻辑转换为C++。其中之一是一个按钮。该按钮可以在VR中按下,并具有调用委托的功能,通知任何已注册函数该按钮按下的情况。以下是AButtonItem.h类中声明委托的方式。

#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;
};

委托的广播函数会在按钮被按下时被调用,代码如下:
ButtonItem_OnPressed.Broadcast();

这个函数一定要调用,因为在调用之前我有一个打印调试信息的语句。需要注意的是,在使用蓝图逻辑时,这个函数一直可以正常工作。

以下是我尝试向委托进行注册以及声明将被调用的函数的方式:

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);
    }

}

我在函数OnNextBladeButtonPressed中设置了断点和打印语句,以便及时检查其是否起作用,但实际上从未生效。我还从头开始重新创建了蓝图,但仍然没有成功。有时编译时会因InvocationList无效而崩溃,但我也没有找到关于该问题的更多信息。最重要的是,在应该调用OnNextBladeButtonPressed时,它并未被调用。
编辑:这里是我的AButtonItem代码中调用广播函数的地方。似乎已被调用,因为我在控制台中看到了UE_LOG输出:
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);
            }
        }
    }
}

1
是的,看起来我已经打中它了,这是一张屏幕截图:https://i.imgur.com/AVrySOw.png - Katianie
1
这是截图:https://i.imgur.com/qCee7ka.png - Katianie
1
AWeaponMaker是一个AActor而不是UObject。在按钮类中,它似乎正在解除绑定(截图:https://i.imgur.com/eE1nTJo.png)?即使在其上调用AddDynamic。我注意到在AddDynamic之后,它说已绑定但对象已过期,这里是一个截图:(https://i.imgur.com/30G2oMs.png)。 - Katianie
1
我再次运行它,看起来在AddDynamic之后它是绑定的,我添加了一个打印语句,它会在AddDynamic之后立即显示出来。但是当它到达按钮类时,它就变成了未绑定状态?有时候它在AddDynamic调用时会像这样崩溃:https://i.imgur.com/lCze5kv.png - Katianie
1
蓝图有一堆子Actor(AButtonItems),该蓝图的父类是AWeaponMaker。该蓝图名为WeaponMakerBlueprint,放置在持久化关卡中。 - Katianie
显示剩余15条评论
2个回答

4

首先:

UPROPERTY(EditAnywhere, Category = Callback)
FButtonItemPressedSignatrue ButtonItem_OnPressed;

这应该是:

UPROPERTY(BlueprintAssignable, Category = Callback)
FButtonItemPressedSignatrue ButtonItem_OnPressed;

为了方便。

其次,由于某些原因,在“begin play”执行之前可能会调用tick函数。如果游戏尚未开始,则不会广播您的事件。为避免这种情况,只需在tick函数中添加一个检查即可。

if(bHasBegunPlay)
{
  // .. your logics ...
}

我尝试过这个,但没有成功。我在上面的评论中链接了一些调试图像,以查看地址是否正确/相等。 - Katianie
我在回答中提到的修复措施很可能不会出现这样的问题。您能否更新您的问题并附上实际的代码? - Playdome.io
没问题,你现在可以看到更新了。我在构造函数中将myHasStarted设置为false,然后在BeginPlay()中将其设置为true。 - Katianie
UPROPERTY(EditAnywhere, Category = Callback) FButtonItemPressedSignatrue ButtonItem_OnPressed; }; }; - Playdome.io
你按照我的答案修改了吗? - Playdome.io
是的,这里有一张截图:https://i.imgur.com/TDugpnc.png。还请查看主问题中的评论,我定期更新它们。 - Katianie

2
有时候在编译过程中会因为InvocationList无效而导致崩溃,但我没有在这个问题上找到太多信息。底线是,当应该被调用时OnNextBladeButtonPressed没有被调用。
我在问题的代码中没有发现问题。就我看来,问题可能出现在不同的位置。我怀疑在广播时AWeaponMaker已经被删除了。

1
有趣的一点,但当我按下按钮时,我可以在游戏中看到WeaponMaker游戏对象在我面前,所以至少世界游戏对象没有被销毁,而且按钮也没有被销毁(至少在视觉上没有)。当我通过调试器进行步骤时,我可以看到“this”和“myNextBladeButton”是在AddDynamic的时候创建的。我们是否有办法测试一下你提到的删除情况? - Katianie
你可以在析构函数中添加一些日志以确保。 - Silvano Cerza
我在tick和析构函数中添加了这些调试语句:https://i.imgur.com/kq4uirX.png。在析构函数中,看起来myNextBladeButton在被调用时为空,但我没有在任何地方删除它,所以为什么在初始化后析构函数会被调用? - Katianie
因为“this”不能是“nullptr”,所以“this == NULL”将始终返回false。 - Silvano Cerza
我也这么认为,但我偶然看到一个奇怪的异常,它说这是空值。 - Katianie
@SilvanoCerza 删掉这个;它可能不是空的,但这将是一次非常艰难的旅程... 或者 ((MyClass*)nullptr)->SomeMethod(); - One Man Monkey Squad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接