home

Tutorial index

Unreal Engine 4: Tutorial 5 - Usable Actors

So now you have your mouse enabled, but without something to interact with, it's more of a pain than a feature. It's about time we enhance our game with some interraction.

To get things straight, we will make things react to being at the center of the screen, while in the next couple of tutorials we will also actively interract with them. Thus for this tutorial you can expect the objects we spawned previously to catch fire when in the center of the screen (equivalent to the aiming reticle), and be extinguished when not there. Mouse selection and button functionality will be added in the next two tutorials.

At this point I should send many thanks to Tom Looman, as we will follow closely his tutorial over here. For the sake of being honest and fair, I am replicating that tutorial because
a) there's no point in going in a different direction, he provides all we could wish for and I don't think I can offer much more
b) as this is a tutorial series, it would be inadmissible to say "go do that tutorial and come back later".

That being said, let's start coding! First of all, please move the declaration of the particle effect to the header, but of course the code creating it should remain in the .cpp file. The interaction we will first implement is that the existing item we created will have the particle effect enabled when it's what we are about to select. Thus we shall start in the item's header (I avoid calling it a usable actor or anything as you may have used another name, or I might use another name if I change project while writing tutorials) and add three function declarations:

//MOUSE INTERACTION

bool OnUsed(ACharacter character);

bool StartFocusItem();

bool EndFocusItem();

It's as simple as it looks. The first function is the only one you may not be sure about, OnUsed() is the function that, when a character chooses to use the item, informs the item that it is being used, and thus performs what it's destined to perform. This can be opening a door, starting a particle effect, exploding, or whatever you want it to be. Any interraction at all.

StartFocusItem() and EndFocusItem() are called when the item is in focus and when it stops being in focus, we will currently use these two to start and stop the particle effect, which brings us to step 2, open up the .cpp file and add this code:


bool AUsableActor::StartFocusItem()
{

ParticleSystem->Activate(true);

return true;
}

bool AUsableActor::EndFocusItem()
{

ParticleSystem->Deactivate();

return true;
}

It should be pretty obvious that these functions just start and stop the particle effect.

A final adjustment in this file, remember how we set auto-enabling of the effect to true? Set it to false so the particle effect is not already active. Head to the constructor and change true to false so that you have the following line:

ParticleSystem->bAutoActivate = false;

The usable actor is now ready to be used! Note that as it is, there will be no visible effect other than the lack of fire during gameplay, but we have the functions to start and stop that fire at will!

Now on to the actor himself. Start with the header of your character, we shall add a couple of variables and function prototypes. You may want to add a comment so you know which portion of the code is there due to this tutorial. Now add the following code th the file:

//////////////////////////////////////////////////////////////////////////
// Mouse:

virtual void Tick(float DeltaSeconds) OVERRIDE;

protected:
//big thanks to tomlooman's (I guess) tutorial: http://www.tomlooman.com/tutorial-usableactor-system-in-c/

/* See comments for this commented out code
//APawn interface
//virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) OVERRIDE;
//end of interface

//Get a usable actor from the viewport (subclasses are valid too)
class AUsableActor* GetUsableInView();

//Maximum distance of interaction
float MaxUseDistance;

//Are we looking at another actor in this frame?
bool bHasNewFocus;

//The actor we are looking at
AUsableActor* FocusedActor;

public:
//The act of using the actor
virtual void Use();

First of all I will apologize for the commented out code. Under certain circumstances, depending on how your project was set up and what changes have already been made, you may need to add the line beginning with "virtual void...." from the commented section, try adding it, if it is not accepted you don't need it. I kept it to cover as many possibilities as possible, if you do need it, you will find the function's code at the link provided to Tom Looman's tutorial [hat tip]. As mentioned earlier, we are following in his footsteps.

Now, first of all we override Tick(), which is the update function of the actor. You shall see that in the Tick() function we will be detecting the object to be selected.

Next, having entered a protected section, we declare a function that gives us the usable actor, and that function is GetUsableInView().

MaxUseDistance is the variable that holds how far we can "reach", past that distance no items will be interactable.

bHasNewFocus stores wether or not in the past trace we found another item to be in focus, rather than the one we had earlier. Note: the 'b' letter is used in front of boolean variables.

FocusedActor is pretty self-explanatory, it holds a pointer to the actor we detected as the one in focus.

Finally we switch back to the public section. There we declare tha function Use(), which is the function that will handle the interraction on the side of the character. There you can check wether you can pick up an item, or if it's only interractable, in each case the game does something different, so it's unlikely that you won't need to check for such a distinction.

So, we now have three functions to write, but also to initialize a couple of stuff, so let's open up our .cpp file.

First go to the constructor, since it's already there, and add the following two lines, again, a wise practice is to separate it from the existing code with a recognizable comment:

//Additional Code
MaxUseDistance = 800;
bHasNewFocus = true;

That's a simple bit, isn't it? Note that bHasNewFocus would anyway be changed upon updating, but it's good to give it an initial value, and with that it's good to asume that you are not looking at what you were looking at a while ago, becase a while ago doesn't even exist as you're just entering the game.

Let's move on to something really simple, the Use() function:


void AMyProjectCharacter::Use()
{

}

The reason it's really simple is that we have neither set up the interraction, so we can't interract, but we also don't want to have to do that to see if this works.

Let's go get that actor now, so we can interract with him later. This is what it should look like:

AUsableActor* AMyProjectCharacter::GetUsableInView()
{
FVector camLoc; //camera location
FRotator camRot; //camera rotation - defines where it's looking

if (Controller == NULL)
return NULL;

Controller->GetPlayerViewPoint(camLoc, camRot);
const FVector start_trace = camLoc;
const FVector direction = camRot.Vector();
const FVector end_trace = start_trace + direction*MaxUseDistance;

FCollisionQueryParams TraceParams(FName(TEXT("")), true, this);
TraceParams.bTraceAsyncScene = true;
TraceParams.bReturnPhysicalMaterial = false;
TraceParams.bTraceComplex = true;

FHitResult Hit(ForceInit);
GetWorld()->LineTraceSingle(Hit, start_trace, end_trace, COLLISION_PROJECTILE, TraceParams);

return Cast<AUsableActor>(Hit.GetActor());

}

So, the first thing you will notice is camera location and rotation. The location is pretty much where your virtual eye is, and to specify what direction it's looking at (because the engine really doesn't care what's there, only it's position relative to the camera) you have the rotation.

A small note on the camera in 3D graphics: you may be wandering how you can follow something as it moves in a non-linear way, the answer is that there generally is a LookAt() function which calculates the rotation required for a camera to look at something. You can learn more on these by studying some Linear Algebra, or if you are in a hurry, you can take a look at my 3D tutorials for a liitle more depth without much math.

Next we check if the controller is existant and valid, if it is not, it is safe to assume there is no camera, or the character may not have even spawned, so there is no point in performing the trace and we return NULL (more on tracing in a very short while).

Consequently, we get the player's camera information, specified by the camera's location and rotation which are given as output parameters of the function GetPlayerViewPoint. This is not the same as the viewport, which also includes the Field of View (FOV).

Right below, we specify the information for the ray trace, now is a good time to talk about raytracing.

RayTracing is the act of checking if a "ray" intersects with any objects in the scene. This is the primary way with which we check for line of sight (LOS) which determines whether someone can see another unobstructed, the way we select items, and in general, the way in which we check what intersects with a line for any purpose whatsoever, even for bullet hits.
It is often done by checking every point per a certain distance to see if there is something there, but can also be done by actually checking for intersection between line and surface, how it is done is best left to the engine, which ought to be optimized and thus faster.

So, we set the beginning of the trace to be the location of the camera. For the direction, we get a Vector version of the rotation, the rotation (of type FRotator, as you saw) holds more info. Finally we want the final location of the trace, which is the beginning plus the distance specified as the maximum, in the direction we are looking at.

Note that the direction, in vector form, is a normalized/unary vector in the direction desired, meaning it is of lenght one, so we just multiply that to get the distance in the specified direction.

Moving on, we have the Trace Parameters, which are a structore of their own. We do not give it a name, allow it to perform a complex trace (just leave that as is, if you mess around with traces in an advanced level you'll have time to figure it out, and generally many other stuff should be left as is unless required to do otherwise), and we tell it that the actor to ignore is our own actor, the one calling the function, which, especially in the case of the 3rd Person camera, allows us not to select the actor, especially when we use the cursor rather than the center of the camera.

Any asynchronous task means it won't block another process, so it's not a bad idea - to be honest, I see it this way, I leave it this way - so we leave bTraceAsyncScene at true. The following command however may be important for performance.

bReturnPhysicalMaterial, as the name suggests, defines whether or not we want the physical material to be returned. In the case of vehicle wheels (yes, raytracing is used there too!) you want to know what terrain you're driving on, to sdjust the behavior of the wheels. Another use may be to check whether you hit a bulletproof portion of something or a vulnerable portion, possible to perform a ricochet (damaged is preferably controlled by the one receiving it). But perhaps the most common use of this is to see whether you hit, say, wood or iron, and play the appropriate particle effect, splinters or sparks.

Long story short, we do not care what it's made of (at least that's usually the case for selecting stuff), so we don't want this. We also don't want this because it furthers the process, since once you know what you've hit, you want to know where you've hit it (possibly on a per-polygon collision, which is expensive) and then to determine what material is at that location. So false it is!

bTraceComplex is already set to true in the constructor, but I see it there, I leave it there. I am 99% certain that you can remove this line with absolutely no side-effect. What it seems to do, as far as I could figure out in my searches, is it allows the trace to do a per-poly collision detection, which is rather expensive-ish. Since it is a single trace, we don't mind too much, and even if it were a more frequent one (as if 60 times a second isn't frequent enough), it would only check if it first hit the bounding shape, so again it would only occure a very few times at worst.

Now we get our results. We declare a variable to store the result, and we pass 0 to it's constructor, which is generally the default parameter (ForceInit, apparently meaning Force Initialization, is defined to be zero).

To get the result, we first use GetWorld() so we can access the world/level we are in, and from that world we retrieved we perform a single line trace (as the function name suggests), the parameters being:

the variable where hit result will be stored, the beginning and end of the trace, the trace channel (set to COLLISION_PROJECTILE), and the parameters of the trace.

Finally we take the actor from the result and we explicitly cast it to a usable actor, this explicit cast will not return something usable if it fails, so we conveniently skip the check for now.

A note on COLLISION_PROJECTILE. If it is not recognized straight on, you can #define it as ECC_GameTraceChannel1, which should be in <Engine.h>. If you want to make sure it is defined without checking, use the following code at the beginning:

//additional headers
#include "UsableActor.h"
#ifndef COLLISION_PROJECTILE
#include "Engine.h"
#define COLLISION_PROJECTILE ECC_GameTraceChannel1
#endif

The #ifndef preprocessor directive, standing for "if not defined" is an elegant way of making sure that a code segment has a required constant. Similar preprocessor directives are used to add platform-dependent functionallity and omit functionality that is only supported on another platform.

Now we have one last thing to do, to call that function so we can get that actor. This is done in the Tick() function, called in every game update, which should roughly coincide with each frame, or may even be more frequent.

Add the Tick() function so that it looks like this:

void AMyProjectCharacter::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);

//if we've got a controller
if (Controller && Controller->IsLocalController())
{
//get the actor
AUsableActor* usable = GetUsableInView();

//if it's not the one we had earlier
if (FocusedActor != usable)
{
//if we actually did have one earlier
if (FocusedActor)
{
//inform him that he's no longer in focus
FocusedActor->EndFocusItem();
}
//and declare that we have a new one
bHasNewFocus = true;
}

//assign it. Even if it's not valid, it will work
FocusedActor = usable;

//so is it valid?
if (usable)
{
//did we just focus on it?
if (bHasNewFocus)
{
//then inform it, and reset bHasNewFocus
usable->StartFocusItem();
bHasNewFocus = false;
}
//otherwise we're good as is
}
}
}

Now, my editor really did a work of art on this piece of code! As you now, through Edit->Advanced->Format Selection, you can automatically fix this in Visual Studio.

Let's see what we have then. I hope the comments guided you through the code fairly easily, it's very clean code anyway.

First off, if we don't have a controller, there's no point in doing anything, so we check.

Next, we get a usable actor, through the function we wrote above. That's the actor in the middle of the screen, right after our own.

We check if he's the one we had last time, if not then...

We check if we even had one earlier, in which case we inform it that it is no longer the focused item.

Regardless , we notify that we have a new actor in focus.

Now that that's done, we set our "usable" variable, i.e. the actor in focus, to the actor we retrieved.

We check if it even exists. Remember how we didn't check the ray trace? If it wasn't a usable actor, it returned a NULL, so this will fail.

If however the actor is valid, we check if it is a new actor, in which case we notify it and reset the flag back to false.

And that's it! Well, it's been a long one, but the code turned out to be more straightforward than you expected, right? I know I felt so.

What have we just done? If you compile and test it out, you will see that the balls we earlier spawned will now be set ablaze when we are looking directly at them, but the fire will be extinguished when the ball is no longer in the center of the screen. In the case of a first person game, this is all you need for interraction. Even in third person games, like Fallout 3 or later, by shifting the camera slightly to the side, you are once again ready to continue.

However, for the case of the generic 3rd person game, in the lines of Star Wars: Knights of the Old Republic, we do not have the full flexibility we would desire, as we cannot select but what is in the middle of the screen, which makes the cursor a remaining liability. By allowing selection by cursor, we not only cover that category of games, but we also open the door to the strategy and tactical game family, whether real-time or turn-based, as they revolve around mouse control.

Thus in the next tutorial we will modify this code so we can select with the mouse, and in the one after that we will add new controll bindings, so we can enable and disable the mouse at will, as well as use objects by clicking on them (as we will have bound the toggling of the cursor to a key, similarly you can bind interraction to one).