Download

Issues with custom class creating crashes and other problems.

Hello all. I have been using Unreal Engine for a few months now, and for the past month or so I’ve started to work on my own project in C++. I’m having issues some custom classes I created, called Item, Equipment (which inherits from Item), and InventoryComponent (Actor Component to attach to player). The first issue I have is with Equipment/Item. I made a child BP from Equipment called BP_Sword with a mesh and put it into the world, but when I try to press play, the editor crashes, due to an access violation with a function called GetInventory() that’s on my player (it just returns the private InventoryComponent* Inventory).
Item.h:

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Item.generated.h"

class UInventoryComponent;
class UStaticMeshComponent;
UCLASS()
class PROJECTFIRSTPASS_API AItem : public AActor
{
	GENERATED_BODY()

private:
	

public:	
	// Sets default values for this actor's properties
	AItem();
	UWorld* World;
	UInventoryComponent* OwningInventory;

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
	UStaticMeshComponent* Mesh;
};

Item.cpp:

#include "Item.h"
#include "InventoryComponent.h"
#include "Components/StaticMeshComponent.h"

// Sets default values
AItem::AItem()
{
	PrimaryActorTick.bCanEverTick = false;
	Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
	Mesh->SetupAttachment(RootComponent);
}

// Called when the game starts or when spawned
void AItem::BeginPlay()
{
	Super::BeginPlay();
	this->Tags.Add(FName("Item"));
}

Equipment.h:

#pragma once

#include "CoreMinimal.h"
#include "Item.h"
#include "Equipment.generated.h"

class AMeleeCharacter;
class UInventoryComponent;
UCLASS()
class PROJECTFIRSTPASS_API AEquipment : public AItem
{
	GENERATED_BODY()
	
private:
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Inventory", meta = (AllowPrivateAccess = "true"))
	FName SocketEquipped;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Inventory", meta = (AllowPrivateAccess = "true"))
	FName SocketUnequipped;
	ACharacter* PlayerCharacter;
	AMeleeCharacter* PlayerRef;
	UInventoryComponent* InventoryRef;
public:
	AEquipment();
	void EquipUnequip(AMeleeCharacter* Character);
protected:
	bool bEquipped;
	virtual void BeginPlay() override;
};

Equipment.cpp:

#include "Equipment.h"
#include "InventoryComponent.h"
#include "MeleeCharacter.h"
#include "Kismet/GameplayStatics.h"

AEquipment::AEquipment()
{
	PlayerCharacter = UGameplayStatics::GetPlayerCharacter(GetWorld(), 0);
	PlayerRef = Cast<AMeleeCharacter>(PlayerCharacter);
}

void AEquipment::EquipUnequip(AMeleeCharacter* Character)
{
	if (!bEquipped)
	{
		this->AttachToActor(Character, FAttachmentTransformRules::SnapToTargetIncludingScale, SocketEquipped);
	}
	else
	{
		if (SocketUnequipped == "")
		{
			this->Destroy();
		}
		this->AttachToActor(Character, FAttachmentTransformRules::SnapToTargetIncludingScale, SocketUnequipped);
	}
}

void AEquipment::BeginPlay()
{
	Super::BeginPlay();
	InventoryRef = PlayerRef->GetInventory();
	if (InventoryRef->Items.Find(this))
	{
		this->bEquipped = false;
		this->AttachToActor(PlayerRef, FAttachmentTransformRules::SnapToTargetIncludingScale, SocketUnequipped);
	}
	this->Tags.Add(FName("Equipment"));
}

InventoryComponent.h:

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Delegates/Delegate.h"
#include "InventoryComponent.generated.h"

//Blueprints will bind this to update the UI
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnInventoryUpdated);

class AItem;
UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class PROJECTFIRSTPASS_API UInventoryComponent : public UActorComponent
{
	GENERATED_BODY()

protected:
	// Called when the game starts
	virtual void BeginPlay() override;

public:
	// Sets default values for this component's properties
	UInventoryComponent();

	bool AddItem(AItem* Item);

	UPROPERTY(BlueprintAssignable, Category = "Inventory")
	FOnInventoryUpdated OnInventoryUpdated;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Items")
	TArray<AItem*> Items;
	UPROPERTY(EditDefaultsOnly, Instanced)
	TArray<AItem*> DefaultItems;
};

InventoryComponent.cpp:

#include "InventoryComponent.h"
#include "Item.h"
#include "Equipment.h"

// Sets default values for this component's properties
UInventoryComponent::UInventoryComponent()
{

}


// Called when the game starts
void UInventoryComponent::BeginPlay()
{
	Super::BeginPlay();
	for (auto& Item : DefaultItems)
	{
		AddItem(Item);
	}
}

bool UInventoryComponent::AddItem(AItem* Item)
{
	if(!Item){ return false;}
	Item->OwningInventory = this;
	Item->World = GetWorld();
	Items.Add(Item);
	OnInventoryUpdated.Broadcast();
	return true;
}

Relevant MeleeCharacter.h:

class UInventoryComponent;

UCLASS()
class PROJECTFIRSTPASS_API AMeleeCharacter : public ACharacter
{
	GENERATED_BODY()

private:
.....
      UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Inventory", meta = (AllowPrivateAccess = "true"))
	UInventoryComponent* Inventory;
......
public:
      UInventoryComponent* GetInventory();

Relevant MeleeCharacter.cpp:

#include "MeleeCharacter.h"
#include "InventoryComponent.h"
#include "Item.h"

// Sets default values
AMeleeCharacter::AMeleeCharacter()
{
 	// Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
	
	Inventory = CreateDefaultSubobject<UInventoryComponent>(TEXT("Equipment"));
}

UInventoryComponent* AMeleeCharacter::GetInventory()
{
	return Inventory;
}

So like I said. Putting the object BP_Sword into the world (which is a child of Equipment) and pressing play crashes the editor. I also cannot add BP_Sword (which is a child of Equipment and therefore technically of type Item) to the TArray DefaultItems that’s in the player character I have in my world. Not sure what to do. And honestly since I’m so new my approach to making an inventory might be pretty terrible in which case I’m open to tossing it if I’m just heading in the completely wrong direction. But if fixable I would like to know how. Thanks to anyone who can help

Are you sure it’s crashing at the line in GetInventory? Can you run it under debugger and crash it and see what’s happening?

This was the real issue:

Those two lines never should have been in the constructor, because if by chance the sword existed in the world before the player, which apparently it did, then the PlayerRef and PlayerCharacter would be nullptr’s, and eventually it would crash. Moving those two lines out of the constructor and to BeginPlay() fixed said issue.

oh, yeah, i didn’t notice the code in constructor. Constructor is called when the object is created, long before it’s ever part of the world. It’s also called in editor. It’s rather rare that you want to do things in constructor.

“Shouldnt”?
No. They can.

They just need to be properly checked before being used…

You know, like when you properly code stuff instead of bumming parts without fully comprehending.

You can PROBABLY

PlayerCharacter = UGameplayStatics::GetPlayerCharacter(GetWorld(), 0);
If (PlayerCharacter) {
PlayerRef = Cast<AMeleeCharacter>(PlayerCharacter);
}

Or

If (PlayerCharacter = UGameplayStatics::GetPlayerCharacter(GetWorld(), 0);
) {
PlayerRef = Cast<AMeleeCharacter>(PlayerCharacter);
}

And another 1001 variations thereof.

Also, If the issue is GetWorld()
You just do this.

If (! GetWorld())
return;

And so on.

Just checking that what you use is Valid before you use it.

Goes the same for ABP, skeletal mesh instance, etc.

And yes most of those should be on BeginPlay, but they still have to be checked for validity instead of possibly crashing out to desktop with no explanations.

Checked your code.
Almost any time you call something you assume that it’s variables exist exist are valid.
Particularly in the case of pointers this leads to issues.

Don’t overly check because it takes computing power, but also don’t just assume that your constructor script was run or that begin play has run before you call other functions.

If you want to fo it peorper check for validity, and instead of returning when invalid, make the variable valid by defining it again.