Exploring the game development world: Unexpected plot twist

Miguel Celedon
6 min readMar 28, 2020

--

Wow, it’s been a very long time since my last post!

A lot happened then that made it difficult to keep going in the series. I found another job, moved to Argentina and worked hard as I started my new life from scratch. Now that I’m in a “stable” position, I can freely continue with gamedev and keep learning and sharing my findings!

This time, I must say that there is a big change I made since last time.

The Big Change

I have moved my project to Epic’s Unreal Engine

If you’re asking why, there is no special reason about it. Last time I used Unity just because it has lower requirements than Unreal, but now that I have a more powerful computer, I’ve made the switch just because I like more Unreal and working with C++. I love C#, it was my favorite language for a long time, but as a programmer I think it would be beneficial to keep close to C++ to keep sharping my skills and to think more in memory and performance. I didn’t found any disadvantage in Unity nor limit at this point, so it was just my preference.

So, if you are taken my samples as guide (although you shouldn’t) and has been following up, this still might work as an exercise to implement in C#/Unity what I share in C++/Unreal, because it’s better to teach you the logic than give you the final code to copy&paste.

The migration from one to the other engine was really, really, REALLY hard if you were not considering it at first. Not only the syntax has it differences (which is not that hard as C++ is a lot like C#), but the engines uses different axes directions! While Unity points Y axis up, Unreal uses Z, so yes, I had to change every part where this had an impact.

Memory layout awareness

Something I took the chance to improve is that, in C# I used multi-dimensional array for storing the blocks and find them by X-Y-Z position:

private BlockData[,,] blocks; // So I can get blocks[x, y, z]

But from my knowledge in C++ and that in Unreal it’s not natural to make an array of arrays, I prefer using a flat array where positions of rows are next to each other, that helps a lot performance when I loop over the matrix (or cube in the case of 3D Arrays) because of CPU cache (although I should, as always, benchmark it to be sure).

UPROPERTY() TArray<UBlockData*> Blocks; // So I can get Blocks[ToArrayPosition(positionVector)]int32 UChunk::ToArrayPosition(const FIntVector& CSBlockPosition) const
{
return CSBlockPosition.X +
ChunkSize.X * CSBlockPosition.Y +
ChunkSize.X * ChunkSize.Y * CSBlockPosition.Z;
}

UPROPERTY is a macro for telling Unreal that Blocks is a property of the classand we want it to show it in editor and be present in Unreal’s visual scripting tool Blueprints. Tarray is the Unreal’s way to manage arrays which handles dynamic size, and other stuff. The type of this array is of pointers to my UBlockData objects (The U there is just for naming convention).

More garbage collection awareness

Some conveniences I had to left behind was of creating objects whenever I wanted and forgetting about them. In Unreal Engine, there is garbage collection too, managed by the engine, not the language’s runtime itself, so we must be a little more responsible of the objects we create dynamically. I can’t create and object in memory, return it and hope for the caller to correctly dispose of it.

For example, as you might know, I don’t draw faces of blocks I know they are occluded by another block, so I have a method where I check if a neighbor of a block is of type Air or not:

private BlockData getBlockAt(int x, int y, int z) {
if (isInside(x, y, z)) {
Vector3i pos = toArrayPosition(x, y, z);
return blocks[pos.x, pos.y, pos.z];
}
else {
return new BlockData(BlockType.Air, new Vector3i(x, y, z));
}
}

As you can see, in this method I used to return a new Air block if I got out of boundaries to simulate that the map ended there. This is OK in C#, as the object will be destroyed at some time by the garbage collector. But in C++, I should not create a pointer and return it because nobody is guaranteed to destroy it, and thinking about it, it’s just a waste of time creating an object just to realize it’s an Air block. So I changed the logic here an instead just return a null value:

UBlockData* UBlockMap::GetBlockAt(const FIntVector& blockPosition)
{
FIntVector blockPositionInChunk = ToChunkPosition(blockPosition);
UChunk* chunk = GetContainerChunk(blockPosition);
if (chunk != nullptr)
{
return chunk->getBlockAt(blockPositionInChunk);
}
return nullptr;
}

This way, if the block is not found (or in this case, the container chunk), I just return a null pointer, which the caller can detect and know it reached the boundary and act as it wants (may it consider it Air or do something else), but no object is created which is much more faster.

Unreal controls most of its objects for me, so I just have to worry about objects I dynamically create and manage, so a rule of thumb in C++ world is: Don’t return a pointer unless necessary! Even if you need it, you could make use of smart pointers, most of the time, TSharedPtr or TSharedRef will suffice, as they will be automatically destroyed when the last user is destroyed.

My PC was not that powerful yet

I’m still happy with my decision, because I really enjoy working with C++ but there is a really big catch in this change.

When you use Unreal Engine, you can develop your game: Visual scripting (Blueprints) or code (C++).

When you use Blueprints, it’s way easier for game designers who has no prior code experience to develop behavior and it’s possible to make a game with only Blueprints.

In the other hand, when you use C++ you have all the power in your hands, what you do there runs faster, you can even create new Blueprints nodes and there are simply things that are more compact in code than in wired nodes.

The best way to work in Unreal, is to mix both, develop base behavior in C++ and inherit it to Blueprints and add the data there (colors, models, etc).

The big disadvantage I have in C++ is that I have an HDD, and compilation time is waaaaay slow. The first compilation in the day is like 5 or 10 minutes, the next “incremental” compilations takes between 1 or 5 mins, that’s a lot of time to wait when you’re just trying things out. That means that, I you’re heavily using C++ in a project, then a SSD is a must.

To mitigate this (and even when you have a SSD), it’s better to implement things first in Blueprints, where changes are immediately present, so you can rapidly prototype things. Later, when you know something will not change anymore, implement it in C++ so you compile it once and take chance of the performance benefits of the code. It there is something you might need to tweak, keep it in Blueprints.

Letting aside all of this, there were no other big change nor difficulty in the migration and both engines resulted to be really helpful for the task and, although Unreal’s UProceduralMeshComponent class it not that well documented, I could still manage to use it:

My first procedural mesh in Unreal

Well, next step is to continue where we left last time, to separate our big map into smaller chunks.

--

--