Marklar
Marklar

Reputation: 21

UE4 UserWidget Button bind with spawning actor in PlayerController

Hello my fellow coders - I am in need of an assistance for this problem

I have MyPlayerController.c (.h), MyGameHUD.c (.h) and MainUIWidget.c (.h)

MyGameHUD is custom class default HUD for MyPlayerController in MyGameMode MainUIWidget is custom class - simple widget with few buttons MyPlayerController is custom class

I want to spawn actor on MyPlayerController when button on MainUIWidget is pressed

Until now I got only access violations because it seems that UWorld is not accesible from MainUIWidget

MainUIWidget.h

UCLASS()
class MY_API UMainUIWidge: public UUserWidget
{
GENERATED_BODY()
void ConstructBuilding(FString BuildingName);
     
MyPlayerController* PlayerControllerPtr;

UPROPERTY(EditDefaultsOnly, BluePrintReadWrite, Category = "Buttons", meta = (BindWidget))
class UButton* Button_1;
};

MainUIWidget.cpp

UMainUIWidget::UMainUIWidget(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
  PlayerControllerPtr = Cast<AMyPlayerController>(GetOwningPlayer());
}
void UMainUIWidget::NativeConstruct()
{
  Super::NativeConstruct();
  // Bind delegates here
  Button_1->OnClicked.AddDynamic(this, &UMainUIWidget::SendSignalBuildBuildingBP_Button1);
}
void UMainUIWidget::ConstructBuilding(FString BuildingName)
{
  PlayerControllerPtr->ConstructBuilding(FString BuildingName);
}  

void UMainUIWidget::SendSignalBuildBuildingBP_Button1()
{
  GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, TEXT("Signal to build!"));
  ConstructBuilding(FString("SmallHouse"));
}

MyGameHUD.h

class MY_API AMyGameHUD : public AHUD
{
    GENERATED_BODY()
public:
  AMyGameHUD ();
  virtual void Tick(float DeltaSeconds) override;
  virtual void BeginPlay() override;
  virtual void DrawHUD() override;
  void DrawUI();

  UPROPERTY(EditAnywhere, BluePrintReadWrite, Category = "Widgets")
  TSubclassOf<UUserWidget> UMainUIWidgetClass;
private:
  UMainUIWidget* UMainUIWidget;

MyGameHUD.cpp

MyGameHUD::MyGameHUD()
{
  PlayerControllerPtr = Cast<AMyPlayerController>(this->GetOwningPlayerController());
}
void MyGameHUD::BeginPlay()
{
  Super::BeginPlay();
  DrawUI();
}
void MyGameHUD::DrawUI()
{
  if (UMainUIWidgetClass)
  {
    UMainUIWidget= CreateWidget<UMainUIWidget>(GetWorld(), UMainUIWidgetClass);
    if (UMainUIWidget)
    {
      UMainUIWidget->AddToViewport();
    }
  }
}
void MyGameHUD::Tick(float DeltaSeconds)
{
  Super::Tick(DeltaSeconds);
}

void MyGameHUD::DrawHUD()
{
  Super::DrawHUD();
}

Expected behavior is to:

  1. click on button1 in widget
  2. Spawn Actor into world from MyPlayerController

Behavior now:

  1. click on button1 in widget
  2. trigger function in MyPlayerController
  3. MyPlayerController->GetWorld() is empty and SpawnActor will crash due to read access violation.

Any ideas please?

EDIT -> added MyPlayerController

MyPlayerController.cpp

AMyPlayerController::AMyPlayerController()
{
  DefaultMouseCursor = EMouseCursor::Crosshairs;
  bShowMouseCursor = true;
  bEnableClickEvents = true;
  bEnableMouseOverEvents = true;

// pre-load class for SpawnActor
      static ConstructorHelpers::FClassFinder<BuildingClass> SmallHouseBPClass(TEXT("Reference/To/Blueprint/Class/BP.BP_C"));
      SmallHouseClass = SmallHouseBPClass.Class;

}
void AMyPlayerController::ConstructBuilding(FString BuildingName)
{
  if (BuildingName.Compare("SmallHouse") == 0)
  {
    GetWorld()->SpawnActor<BuildingClass>(SmallHouseClass);
  }
}

AMyPlayerController.h

UCLASS()
class AMyPlayerController : public APlayerController
{
    GENERATED_BODY()

public:
    AMyPlayerController ();
    void ConstructBuilding(FString BuildingName);

  UPROPERTY(EditAnywhere, BluePrintReadOnly, Category = "BuildingClasses")
    TSubclassOf<class BuildingClass> SmallHouseClass;

Upvotes: 0

Views: 1945

Answers (2)

Marklar
Marklar

Reputation: 21

Thanks for answers all.

Problem solved by :

Moving

PlayerControllerPtr = Cast<AMyPlayerController>(this->GetOwningPlayer());

from widget's constructor to widget's BeginPlay() event.

Upvotes: 1

DevilsD
DevilsD

Reputation: 597

Please provide the exact Crash details.

To me this looks like your attempting to use GetOwningPlayer() to cache in PlayerControllerPtr of your Widget, however when creating the Widget in MyGameHUD::DrawUI() you are not specifying to the Widget what its Owning Player is.

Please call SetOwningPlayer(PlayerControllerPtr) on the Widget when you add it to the Viewport so that it knows which Player owns it. This will cause the GetOwningPlayer() function to actually return a value.

I suspect that your crash is actually occurring inside this function

void UMainUIWidget::ConstructBuilding(FString BuildingName)
{
  PlayerControllerPtr->ConstructBuilding(FString BuildingName);
}  

You should always check validity of Pointers before using them, optionally making some sort of log when it does not pass as expected.

void UMainUIWidget::ConstructBuilding(FString BuildingName)
{
    if(PlayerControllerPtr)
    {
        PlayerControllerPtr->ConstructBuilding(FString BuildingName);
    }
}  

GetWorld() will always be valid when called inside of a PlayerController as it is an AActor and cannot exist without a World.

Upvotes: 2

Related Questions