Accessing functions and variable members of another class

Background

So I’m trying to make a higher level actor that manages a turn based game that I’ve dubbed my “GameManager”. From here I’ve created two sub actors which are the “GridManager” and “UnitManager” respectively.

I’ve been able to use the GameManager to spawn a GridManager Actor and call it’s GenerateTileMap function. Using this same principle I was hoping to move on to spawning a Unit via my UnitManager.

Problem

When I attempt to spawn a Unit I need to place that unit on a Tile, so I created a FindTile function within my GridManager to return the Tile that I randomly selected. Then I made my GameManager Spawn a UnitManager which then, during spawning, calls my GridManager to fetch that Tile form the TileMap. This causes my crash.

Upon debugging, it seems like when I attempt to spawn a unit, I call FindTile to designate where to spawn it. Then
I navigate my TileMap(TArray) with a ForEach loop, I get an exception that resembles the following…

Read Access Violation

I can see that my TileMap exists during GridManager construction, however after making a call to my UnitManager and from there calling my GridManager, I must have lost a reference to my TileMap somewhere.

Question

So my question is what is considered a good/strong practice for accessing member variables and functions from a higher level (conceptually speaking) class (actor in this case)? What is the fix to the problem described above?

I’ve considered some things like how to properly initialize an instance of these classes and call their functions but that understanding may be incorrect, I could really use some insights since I feel like I’m just not remembering a c++ concept and I’m trying to relearn things that feel so much easier to do in other languages or other tools like Unity.

I’m posting sections that I thought were good critical sections of code for brevity, I apologize for formatting as I did my best here.

###GameManager

void AGameManager::BeginPlay()
{
	Super::BeginPlay();
	
	UWorld* world = GetWorld();
	if (world)
	{
		GridManager = world->SpawnActor<AGridManager>();
		UE_LOG(LogTemp, Warning, TEXT("Spawned a GridManager"));

        // Moved to BeginPlay when I spawn GridManager
		//GridManager->GenerateTileMap();
	}

	if (world)
	{
		UnitManager = world->SpawnActor<AUnitManager>();
		UE_LOG(LogTemp, Warning, TEXT("Spawned a UnitManager"));

		UnitManager->SpawnTest();
	}
}

###GridManager

// Called when the game starts or when spawned
void AGridManager::BeginPlay()
{
	Super::BeginPlay();

	GenerateTileMap();

	// FindTile tests
	FindTile(FVector2D(2, 2));
	FindTile(FVector2D(-2, -2));
}



ATile* AGridManager::FindTile(FVector2D TargetTileCoordinates)
{
	for (ATile* TempTile : TileMap)
	{
		if (TempTile->GetTileCoordinates() == TargetTileCoordinates)
		{
			UE_LOG(LogTemp, Warning, TEXT("Tile found at: %s"), 
                        *TargetTileCoordinates.ToString());
			return TempTile;
		}
	}
	UE_LOG(LogTemp, Warning, TEXT("No tile at specified coordinates"));

	return NULL;
}

###UnitManager

void AUnitManager::SpawnTest()
{	
	// first two rows on the bottom left
	int randomX = FMath::RandRange(0, 7);
	int randomY = FMath::RandRange(0, 1);
	UE_LOG(LogTemp, Warning, TEXT("Random Coords: (%d, %d)"), randomX, randomY);
	
	// Search our tilemap for their respective coords and return their respective Tile
	ATile* SpawnTile;
	SpawnTile = GridManager->FindTile(FVector2D(randomX, randomY));

	
	FVector Location = FVector(
		GetActorLocation().X,
		GetActorLocation().Y,
		0.0f);

	AUnit* TempUnit = GetWorld()->SpawnActor<AUnit>(
		Location,
		GetActorRotation());
}

Need to see the GameManager and GridManager headers

Thanks for taking the time to reply, I appreciate that. I’ve attached them below here.

###GridManager

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

class ATile;

UCLASS()
class SAMPLEPROJECT_API AGridManager : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AGridManager();

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

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Holds our entire tilemap
	UPROPERTY(VisibleAnywhere)
	TArray<ATile*> TileMap;

	// Fetches our tilemap
	TArray<ATile*> GetTileMap();

	// Search-like function will be called often for game logic
	ATile* FindTile(FVector2D);

	// Fetch the dimensions of our tilemap
	int GetTileMapXDimension();
	int GetTileMapYDimension();

	// Generates the board
	void GenerateTileMap();

private:
	static const int MAX_X_GRID_SIZE;
	static const int MAX_Y_GRID_SIZE;
};

###GameManager

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

class AGridManager;
class AUnitManager;

UCLASS()
class SAMPLEPROJECT_API _API AGameManager : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AGameManager();

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

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	UPROPERTY(VisibleAnywhere)
	AGridManager* GridManager;

	UPROPERTY(VisibleAnywhere)
	AUnitManager* UnitManager;
};

So here is what I think is happening. Your UnitManager and GridManager classes are spawned by a command within the GameManager but they are not subclasses of the GameManager class. They are, I think objects that belong to the persistent level within your world.

Your GameManager just happens to posses pointers to those two classes.

With this being said, the question that arises from your code here:

SpawnTile = GridManager->FindTile(FVector2D(randomX, randomY));

is how does UnitManager have a populated GridManager pointer?

I would have thought it would need to obtain that pointer from GameManager or from the world first.

So I within my UnitManager I have a pointer to GridManager however I’ve been having some trouble determining how to set it up properly without having multiple pointers to the same thing which would be a waste.

I’m going to post the header just in case. I was trying to get an instance of the class and was trying variations of using a pointer versus the TSubclassOf to make that happen. I’m a bit out of touch with this and could definitely use some pointers (hah) on the proper practice to achieve what I’m trying to do here.

Lastly when is it best to use this, or rather does this produce an instance of a class? Reference a class object? Does all creation happen with new UObject or FObjectInitializer within/out the constructor? I’ve been having the worst time with this since I’ve been learning bits and pieces all over from different places that say different things, Thank you for your reply.

static ConstructorHelpers::FClassFinder<AGridManager>GridManagerClass(TEXT("Class'/Script/Imperator.GridManager'"));
	if (GridManagerClass.Succeeded())
	{
		GridManager = GridManagerClass.Class;
	}

###UnitManager.h

UCLASS()
class SAMPLEPROJECT_API AUnitManager : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AUnitManager();

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

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Holds all units
	UPROPERTY(VisibleAnywhere, Category = "Components")
	TArray<AUnit*> Units;

	// Reference to our grid manager object
	UPROPERTY(VisibleAnywhere)
	AGridManager* GridManager;
	//TSubclassOf<AGridManager> GridManagerClass;

	void SpawnTest();

	// Mutators/Accessors
	TArray<AUnit*> GetPlayerUnits();
	TArray<AUnit*> GetEnemyUnits();

private:

};

For something that seems as significant to your game like your Game Manager I would do the following:

  1. Add a pointer to your GameManager class that you store in your GameMode (or if you have multiple levels, your GameInstance) and create a getter and a setter there.

2). In the constructor of GameManager, use the setter to store the pointer to GameManager (“this”).

  1. Then, for example, in your UnitManager, you can use your getter to get a pointer to GameManager and then use that pointer to obtain the pointer to GridManager. If you have to do that a lot in your UnitManager, you could get those pointers set up as variables in UnitManager.

Ah okay I understand what you’re asking. However I’m a bit confused as to why this would need to be in the GameMode? Is it because the GameMode is a more “high level” concept than actors (I hope I’m saying that correctly and it makes sense)?

The GameMode is part of the UE4 Game Framework that includes other classes like GameInstance, GameState and PlayerState etc.

The advantage of using them is that they are centrally available to all of the other classes that might need information to run the game and can be manipulated from both C++ and Blueprint. I use the term “centrally available” a bit loosely because some of the Game Framework classes are designed for a network environment (like Fortnite) so availability for some of the classes has some nuances in a network game environment (and some are only relevant to a network environment). I’ve found it helpful, even if you aren’t building a network game to look pages 8 and 9 of the following Network Compendium because it helps me keep track of which classes exist for what general purpose.

Right now your GameManager, GridManager and UnitManager are Actors owned by your Persistent Level. You may ultimately conclude that these classes have no physical presence in the Level (they are just game management code) so they don’t need to be Actors and are better declared and instantiated within one of the core Game Framework classes.

Which class you choose to extend depends a bit on the design of your game. For example if your game has multiple Levels (ie you switch maps), GameInstance is designed to survive those switches whereas a new instance of GameMode is created every time you make a map switch. In this hypothetical multi-map game you might conclude that your GridManager should be refreshed in every map (new map = new grid) but your GameManager should not so one might go in the GameInstance and the other in the GameMode.

Alright I think I understand, Thanks for replying and assisting me with this, its greatly appreciated. I’m going to try what you suggested above and see how it goes.

Edit:
Upon thinking about it a bit more, how would, from another class, assign a pointer to my existing GridManager. I don’t want to create a new GridManager with no TileMap I want to find syntax to reference my existing GridManager with an already created TileMap. I’m not sure how to do this and while doing this in GameMode would be good practice, I think its the same problem I’m faced with now… How do I do this?

// GridManager.h
AGridManager* GetGridManager();

// GridManager.cpp
AGridManager::GetGridManager()
{
   return this;
}

// UnitManager.cpp
AGridManager* GridManager; //<-- Does this need to be in the header or could it be within the function?
GridManager->GetGridManager();

/// OR does there exist another way of doing this, how would I assign the pointer with & from another class

Apologies, I’m in this weird place where I understand the problem and how to solve it but I’m not familiar enough or haven’t had enough c++ experience to find the correct syntax to solve this

Let’s assume you stick with your Actor-based classes and you are going to extend your GameMode class for the sole purpose of storing a pointer to your existing GameManager.

I would do the following:

  1. Create a new GameMode class and in your NewGameMode class header:

    • #include your GameManager header
    • Create a GameManager* variable ( private)
    • Create a Getter and a Setter (public)
      You will need to change your project settings to use the NewGameMode
  2. In your GameManager Actor ( I think it will work from the constructor) use GetWorld()->GetAuthGameMode() to get a pointer to your NewGameMode and use that pointer to call your setter (e.g. SetGameManagerPointer(this) ) to store the pointer to your GameManager in the NewGameMode

  3. Whenever you need to find your GameManager, GridManager or TileManager classes, use the same approach as #2 above to get your NewGameMode pointer and then use the Getter to get the GameManager pointer and in the case of your FindTile call, get the GridManager pointer from the GameManager so you can make the call to the GridManager with a valid pointer.

I had a chance to poke in and try this between some holiday bbq (Happy Labor Day).

I tried to call the setter to establish the pointer and the setter function was not in scope, it says when calling this on a client that it returns a nullptr as it expects to return a pointer to the server which I don’t have.

I feel like this should be a simple solution and can’t help but wonder what i"m doing wrong… either way I appreciate the continued feedback and help, learning UE is such a process.

Did you put an #include for your GameMode in your GameManager from which you call the setter?

Ahhhh! That did it.

I think I missed the include in my UnitManager and in my haste I did not think to cast the result of GetAuthGameMode() from GameModeBase to MyGameModeBase since my game mode inherits from it and in my mind that was enough to ignore that fact.

I feel a bit ashamed since I could have figured this out if I spent more than a few minutes the other day, and now in hindsight I feel like I wasted a bit of your time so I apologies for not doing the due diligence. This took a bit of a backseat recently until I decided to post my question.

If you’ve noticed other issues with what I’ve posted or have any resources that helped you ramp up I’m open to any other links/resources/ideas, most of my learning has been by forum posts and answerhub questions like this.

Anyway, thank you for the feedback and continued replies, its easy to think asking questions online is a shot in the dark. This will help me a lot moving forward, even if the way I’m using Actors might seem unusual or if there is a better way, I’m unblocked now so I can keep going.

Cheers

Quick Edit: In response to this line you wrote…

…you have to drag the C++ class into the level…
I’ve been converting my c++ classes into Blueprints for what I’m assuming would be good practice in preparation for working in a team. I’m wondering if this line of thought is correct or not in that context? or if it even matters?

Here is code that works.

To make it work:

  • Because I haven’t spawned GameManager anywhere in the code, you have to drag the C++ class into the level.
  • You also need to go to project settings and change your game mode to MyGameModeBase
  • If your game is networked, GameMode isn’t replicated to clients (that may be the issue you are having) in which case you should extend GameState instead (which is replicated).

I ended up putting the call to the MyGameModeBase setter in BeginPlay in the GameManager. It wouldn’t work in the constructor.

I broke the calls to get to the GameManager pointer getter into all of its individual components on individual lines so you can see all of the steps (which could be combined into fewer lines)

MyGameModeBase.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "GameManager.h"
#include "MyGameModeBase.generated.h"

UCLASS()
class GAMEMODETEST_API AMyGameModeBase : public AGameModeBase
{
	GENERATED_BODY()

private:
	AGameManager* GameMgr;

public:
	
	AGameManager* GetGameMgr() const { return GameMgr; }
	void SetGameMgr(AGameManager* val) { GameMgr = val; }
};

GameManager.h

#include "UnitManager.h"
#include "GameManager.generated.h"

UCLASS()
class GAMEMODETEST_API AGameManager : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AGameManager();

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

private:
	AGridManager* GridMgr;
	AUnitManager* UnitMgr;


public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;
	
	AGridManager* GetGridMgr() const { return GridMgr; }
};

GameManager.cpp

#include "GameManager.h"
#include "UnitManager.h"
#include "GridManager.h"
#include "MyGameModeBase.h"

// Sets default values
AGameManager::AGameManager()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

}

// Called when the game starts or when spawned
void AGameManager::BeginPlay()
{
	Super::BeginPlay();

	UWorld* world = GetWorld();
	AGameModeBase* TheGameMode = world->GetAuthGameMode();  // Get the AGameMode (generic parent class
	AMyGameModeBase* MyTheGameMode = (AMyGameModeBase*)TheGameMode;  // Cast to AMyGameModeBase
	MyTheGameMode->SetGameMgr(this);

	
	if (world)
	{
		GridMgr = world->SpawnActor<AGridManager>();
		UE_LOG(LogTemp, Warning, TEXT("Spawned a GridManager"));

	}

	if (world)
	{
		UnitMgr = world->SpawnActor<AUnitManager>();
		UE_LOG(LogTemp, Warning, TEXT("Spawned a UnitManager"));

		UnitMgr->SpawnTest();
	}
}
	


// Called every frame
void AGameManager::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

GridManager.h

#pragma once

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

UCLASS()
class GAMEMODETEST_API AGridManager : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AGridManager();

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

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	int32 SpawnTile();

};

GridManager.cpp

#include "GridManager.h"

// Sets default values
AGridManager::AGridManager()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

}

// Called when the game starts or when spawned
void AGridManager::BeginPlay()
{
	Super::BeginPlay();
	
}

// Called every frame
void AGridManager::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

int32 AGridManager::SpawnTile()
{
	// Return a "secret number"
	int32 anumber = 111;
	return anumber;
}

UnitManager.h

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

UCLASS()
class GAMEMODETEST_API AUnitManager : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AUnitManager();

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

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	void SpawnTest();

};

UnitManager.cpp

#include "UnitManager.h"
#include "MyGameModeBase.h"
#include "GameManager.h"
#include "GridManager.h"

// Sets default values
AUnitManager::AUnitManager()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

}

// Called when the game starts or when spawned
void AUnitManager::BeginPlay()
{
	Super::BeginPlay();
	
}

// Called every frame
void AUnitManager::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

void AUnitManager::SpawnTest()
{
	AGameModeBase* GameMode = GetWorld()->GetAuthGameMode();  // Get the AGameMode (generic parent class
	AMyGameModeBase* MyGameMode = (AMyGameModeBase*)GameMode;  // Cast to AMyGameModeBase
	AGameManager* GameMgrPtr = MyGameMode->GetGameMgr();  // Get the AGAmeManager pointer from MyGameMode
	AGridManager* GridMgrPtr = GameMgrPtr->GetGridMgr();  // Get the AGridManager pointer from GameManager
	
	int32 secretnumber = 999;

	secretnumber = GridMgrPtr->SpawnTile();  // Call GridManager::SpawnTile()

	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("The Secret Number is %d"), secretnumber));
}

The result:

As far as I know, there is no harm in your approach of converting to blueprints.