Initializing a 3d Array

Hi everyone…

I made a 3d array which contains an array inside a struct inside an array inside a struct inside an array inside a struct.

Basically I should be able to access things by


LA.X*.Y[j].Z[k]

But problem is I crash the game if I put values in the array something like that. I assign the initial values to the array by a triple for loop


	LA.X.SetNumUninitialized(MaxSize);
	for (int i = 0; i <= MaxSize; i++)
	{
		LA.X*.Y.SetNumUninitialized(MaxSize);
		for (int j = 0; j <= MaxSize; j++)
		{

			LA.X*.Y[j].Z.SetNumUninitialized(MaxSize);
			for (int k = 0; k <= MaxSize; k++)
			{

				LA.X*.Y[j].Z[k] = FIntVector(CurrentXSize, CurrentYSize, CurrentZSize);
			}
		}
	}

I use SetNumUninitialized to initializes the number of values in an array. But this also crashes the editor.

I would recommend actually adding the uninitialized yourself

I did a 2D maze grid USTRUCT setup this way and it works great, but I was getting crashes too until I initialized everything myself

here is my code for you!



USTRUCT()
struct FMazeUnit
{
	GENERATED_USTRUCT_BODY()

	UPROPERTY()
	FVector Location;
	
	UPROPERTY()
	FRotator Rotation;
	
	UPROPERTY()
	UClass* BPToSpawn;

	UPROPERTY()
	AVictoryWall* SpawnedMesh;
	
	void Clear()
	{
		BPToSpawn = NULL;
		if(IsValid())
		{
			SpawnedMesh->Destroy();
			SpawnedMesh = NULL;
		}
	}
	bool IsValid() const
	{
		return (SpawnedMesh != NULL);
	}
	void Spawn(UWorld* TheWorld)
	{
		SpawnedMesh = UVictoryCore::SpawnBP<AVictoryWall>(TheWorld, BPToSpawn, Location, Rotation);
	}
	
	//Default Constructor
	FMazeUnit(){}
	
	FMazeUnit(const FVector& Loc, const FRotator& Rot, UClass* ToSpawn) 
		: 	Location(Loc), 
			Rotation(Rot), 
			BPToSpawn(ToSpawn)
	{
	}
};
//Maze Grid Tests
USTRUCT()
struct FMazeGridRow
{
	GENERATED_USTRUCT_BODY()

	UPROPERTY()
	TArray<FMazeUnit> Columns;
	
	void AddNewColumn()
	{
		Columns.Add(FMazeUnit());
	}
	//default properties
	FMazeGridRow()
	{
		
	}
};
USTRUCT()
struct FMazeGrid
{
	GENERATED_USTRUCT_BODY()

	UPROPERTY()
	TArray<FMazeGridRow> Rows;
	
	void AddNewRow()
	{
		Rows.Add(FMazeGridRow());
	}
	
	void AddUninitialized(const int32 RowCount, const int32 ColCount)
	{
		//Add Rows
		for(int32 v = 0; v < RowCount; v++)
		{
			AddNewRow();
		}
		
		//Add Columns
		for(int32 v = 0; v < RowCount; v++)
		{
			for(int32 b = 0; b < ColCount; b++)
			{
				Rows[v].AddNewColumn();
			}
		}
	}
	
	//Actually Create the Maze Meshes
	void SpawnMazeMeshes(UWorld* TheWorld)
	{
		if(Rows.Num() <= 0) return;
		//~~~~~~~~~~~~~~~
		
		const int32 RowTotal = Rows.Num();
		const int32 ColTotal 	= Rows[0].Columns.Num();
		
		for(int32 v = 0; v < RowTotal; v++)
		{
			for(int32 b = 0; b < ColTotal; b++)
			{
				Rows[v].Columns**.Spawn(TheWorld);
			}
		}
	}
	
	void Clear()
	{
		if(Rows.Num() <= 0) return;
		//~~~~~~~~~~~~~~~
		
		//Destroy any walls
		const int32 RowTotal = Rows.Num();
		const int32 ColTotal = Rows[0].Columns.Num();
		
		for(int32 v = 0; v < RowTotal; v++)
		{
			for(int32 b = 0; b < ColTotal; b++)
			{
				Rows[v].Columns**.Clear();
			}
		}
		
		//Empty
		for(int32 v = 0; v < Rows.Num(); v++)
		{
			Rows[v].Columns.Empty();
		}
		Rows.Empty();
	}
	//default properties
	FMazeGrid()
	{
		
	}
};



**The Most Relevant Code**

I am initializing everything myself (Dynamic Arrays)



```


void AddUninitialized(const int32 RowCount, const int32 ColCount)
	{
		//Add Rows
		for(int32 v = 0; v < RowCount; v++)
		{
			AddNewRow();
		}
		
		//Add Columns
		for(int32 v = 0; v < RowCount; v++)
		{
			for(int32 v = 0; v < ColCount; v++)
			{
				Rows[v].AddNewColumn();
			}
		}
	}


```



Initializing the maze



//Reset
JoyMazeGrid.Clear();
JoyMazeGrid.AddUninitialized(tileX,tileY);

//at this point all the array data has been made so you can use
//LA.X*.Y[j] without crashing the game


now if you take my general setup and make 3D you are set , or just take the idea of initializing stuff yourself :slight_smile:

for other viewers: this is all in the Dynamic Array case, not static arrays.

:heart:

Rama

Hi envenger,
Your problem is with this:



LA.X.SetNumUninitialized(MaxSize);
for (int i = 0; i <= MaxSize; i++) {
...
}


You are setting the size of your array to MaxSize but you are looping from 0 to MaxSize. C++ is 0 indexed so the maximum index of your array is MaxSize - 1.

Thanks didn’t notice that for some reason.

Anyway thank for sharing the code Rama. I had done something similar to it. But your code is better :slight_smile:

Hello guys, why not use vector array or pointer? As these:


int array3D[2][2][2] = {
		{
			{ 1, 2 },
			{ 3, 4 }
		},
		{
			{ 5, 6 },
			{ 7, 8 }
		}
	};

or using vectors:


vector<vector<vector<int>>> array3D;

Hello!

Another way to do it is to just use a plain array and then create get set methods. I feel that it is simpler, and you also get rid of the object overhead that c++ classes and structs generate, especially in UE4 since they have custom adorn data as well. This helps with stuff like cache performance and might make a difference if it represents some data that you access very often. The biggest advantage I feel is of course that it is more straight forward. The downside is that you need to add in some more effort if you want to be able to use the index operator, but if you just encapsulate it nicely in a class that is pretty easy to achieve anyways :slight_smile:

You basically just create one array like so:

header:




private:
	FIntVector UObjectMatrixDimensions;

	//The matrix is laid out sequentially in memory,
	UPROPERTY() //Don't forget to make the array a UPROPERTY if you are storing stuff that could get GCd!
		TArray<UObject*> UObjectsMatrix;

	FORCEINLINE void SetUObjectToMatrix(UObject* NewObject, FIntVector Location)
	{
		//Location.Z * UObjectMatrixDimensions.X * UObjectMatrixDimensions.Y    walks us one XY block at a time
		//Location.Y * UObjectMatrixDimensions.X Then walks us to the correct line in the XY block we want
		//And then we just walk Location.X forward to the element we want.
		UObjectsMatrix[Location.Z * UObjectMatrixDimensions.X * UObjectMatrixDimensions.Y + Location.Y * UObjectMatrixDimensions.X + Location.X] = NewObject;
	}

	FORCEINLINE UObject* GetUObjectFromMatrix(FIntVector Location)
	{
		return UObjectsMatrix[Location.Z * UObjectMatrixDimensions.X * UObjectMatrixDimensions.Y + Location.Y * UObjectMatrixDimensions.X + Location.X];
	}



And for the ctor in your cpp you can do something like:



	AMyActor::AMyActor(const FObjectInitializer& ObjectInitializer)
		: Super(ObjectInitializer)
	{
		UObjectMatrixDimensions = FIntVector(10, 10, 10);
		UObjectsMatrix.SetNumZeroed(UObjectMatrixDimensions.X * UObjectMatrixDimensions.Y * UObjectMatrixDimensions.Z);
		SetUObjectToMatrix(NULL, FIntVector(0, 4, 9));
	}


I didn’t add any bound checking to the inlined functions right now since that might be controlled by your invariants anyways. And if they aren’t, it’s easy enough to add yourself :slight_smile:

Like I mentioned above, it might of course also be a good idea to make this into its own (template) class if you decide to use it.

Best regards,
Temaran