More Adventures in Unreal Engine 4


The adventures in UE4 continue. So far, I’ve found it very much to my liking. This last week, I made something approaching space invaders, just to see if I could. The amount of code I needed was impressively small; probably not even a hundred lines split up over a few files. It’s still unfinished, but here’s the UE4 project on github.

The “oh this is cool” feeling hasn’t gone away, but neither have my gripes about the documentation from the first post. In this post, I’ll explain some of the stuff I’ve learned about C++ in UE4.

Before we begin, a caveat: I am still an absolute newbie at using UE4, so some of the information in this post is bound to be bad practice. I’m documenting what I learn as I learn it. If you’ve got any suggestions about best practices, I’d be happy to hear them; leave them in the comments.

I’ll start off by talking a bit about what I’ve seen of the general architecture of UE4. It’s partially inheritance based - which, I gather, the original Unreal Engine 3 was - but it’s also got components. The ramifications of this are still not totally clear to me; it’ll take some more time before I figure out the pros and cons. So far, it seems to work fine; my general workflow for creating new entities so far has been to subclass the AActor class, which is the general “object in the game” class, and add a few components to it.

When you tell the editor to create you a new C++ class subclassing some basic classes, most notably, it gives you a stub which contains a constructor and two methods, BeginPlay() and Tick(). From what I can tell, the constructor is actually called when you create the actor within the editor, i.e. when you drag it out of the content browser and into the viewport. This is where all your initialization code should go. On the other hand, BeginPlay(), as its name implies, is only called when the game is actually launched. To be honest, I still don’t know what to put in BeginPlay(). Finally, Tick() is your usual function that is called every time the entity should be updated, and where most of the logic goes.

Time to read some code. If you want to follow along, remember all the code is on github.

Right after the stub with the above methods is created, I’ve pretty much always deleted and replaced the constructor. The one that’s provided is your normal default constructor that takes no arguments. The one I replace it with looks like this:

AShip::AShip(const FObjectInitializer &ObjectInitializer;)

Using this ObjectInitializer seems to be the way you initialize components within C++. To declare the components themselves, I do something like this within the header file of the new actor:

UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly)
class UStaticMeshComponent *mesh;

The UPROPERTY macro allows the variable directly following it to show up in the editor; I believe it also does some other things, like enabling garbage collection. In the constructor, I initialize the component using the ObjectInitializer within the constructor:

mesh = ObjectInitializer.CreateDefaultSubobject<ustaticmeshcomponent>(this, TEXT("StaticMesh"));

The AActor class itself is a bit weird. It’s halfway between the normal “bag of components” class in an entity-component system, and a full GameObject-like class. For example, it doesn’t store its own location - it leaves that responsibility to its RootComponent - but it does provide methods for moving itself. I think they are just convenience methods for moving its root component, but it still feels strange. That means, without a root component, the actor doesn’t really have any location information associated with it. You have to explicitly set the root component of the actor:

SetRootComponent(mesh);

Then, any other components should be attached to the root component. This will cause the transform (position, rotation, scale) of the child component to be relative to that of the parent component. In layman’s terms, that just means it’ll follow the root component. For example, in AShip, I attach a box component to the mesh, for detecting collisions:

collision->AttachTo(mesh);

That finishes up the initialization of the actor. A big issue with doing all the initialization in C++ is that it is simply tedious. For example, if you want to place a light just above a lamp, you’re really guessing - Maybe I want the light 50cm above the lamp? No, that’s too low; how about 100? Creating a blueprint of the C++ object is one way to make this easier; you can position it exactly where you’d visually want it, through the UE4 editor. A problem I could see with doing it this way is that you’d never know where to look to change a class’ default properties. Why isn’t the position of the light changing? Oh, that’s right, the blueprint overrides the position, but I’m changing it in the C++ class…

There’s other properties, like the path to a static mesh, or value of light intensity, for which this is a problem. In the end, maybe the right way is to initialize components in C++, and put all the logic for the actor in C++, but initialize all values within the blueprint. It still doesn’t make me completely happy, since you have to do the busywork to create not only a C++ class but also a class blueprint. In any case, I’ll give this method a try for the next time.

So that’s initialization. From there, it’s all about adding whatever logic is appropriate for the class. There are certain services you always want an engine to handle; things like the game loop, collision detection, input, and movement. Let’s talk about some of those. The first one is the easiest; each AActor has a Tick(), which is called every tick of the game loop. Most of your logic goes in there; should be pretty familiar to most anyone who’s developed a game before.

Collision detection takes the form of callbacks that you bind on the AActor. The technical name for these callbacks is Multicast Delegates. I’m normally used to classes being called delegates, but in this case, it looks like the delegates are functions. Binding actions that happen on collisions looks like this:

AEnemy::AEnemy(const FObjectInitializer &ObjectInitializer) {
    // ... Initialization stuff
    OnActorBeginOverlap.AddDynamic(this, &AEnemy;::OnBeginOverlap);
}

void AEnemy::OnBeginOverlap(AActor *otherActor) {
    // ... Logic
}

Note that you should set bGenerateOverlapEvents = true on some child component of the AActor. In my case, I used UBoxComponents.

Input also uses callbacks, but in a looser way. As I understand it, an APawn class will be “possessed” and take input from some source; local player, online player, or AI. When some input is received, the appropriate delegate function will be called, which should handle the input. Here’s what binding those delegates look like:

void AShip::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
    Super::SetupPlayerInputComponent(InputComponent);
    InputComponent->BindAxis("LRAxis", this, &AShip;::Move);
    InputComponent->BindAction("Shoot", IE_Pressed, this, &AShip;::Shoot);
}

void AShip::Move(float axisValue) {
    // ... Logic
}

void AShip::Shoot() {
    // ... Logic
}

Note that only the APawn class has the SetupPlayerInputComponent function. For other AActors, you have to get the InputComponent from the player. Check out the previous post for that.

Movement is still a murky area for me. It looks like there are some movement-specific components, but I still haven’t figured out how to use the concrete classes or how to subclass the abstract ones. However, some simple movement can be done by using the AddActorWorldOffset() function, which basically just adds a vector component-wise onto the actor’s current location.

void ALaser::Tick( float DeltaTime )
{
    Super::Tick( DeltaTime );
    AddActorWorldOffset(FVector(DeltaTime * speed, 0, 0));
}

That is basically the sum of what I know about UE4 right now. I’ll continue writing more posts as I learn more.