A Basic Interaction System
Welcome to the somewhat final leg of the Mounting System Tutorial. Here I will show you how to setup a very basic interaction system just so you can get started using the mounting system in your game, should you not have an interaction system of your own designed and developed.
For our mounting system and our Interaction system we will be changing some of the UE4 default collision presets. This will solve two problems. The first is in its default state a mounted passenger’s camera will be blocked by the mount’s capsule and mesh components causing it to be trapped usually inside the mesh or center of the mount. The Second is it will allow our visibility trace to hit the mesh component of our mount.
So there are other ways of doing this but for demonstration purposes this is the easiest way to do it. You could create a whole new Preset just for your mounts, you could create a new Object Channel or Trace Channel. But getting into the details of setting that up is beyond the scope of this tutorial and this plugin. It is very dependent upon the type of game you want to make.
Go to File / Project Settings. Then go to Engine / Collision Settings.
Open the “Preset” tab. The ones we will be most interested in are “Pawn” and “Character Mesh”.
Double click on the “Pawn” Preset and and change the Visibility and Camera settings to ignore. That’s it, Now all objects with this collision preset will now ignore the Camera and Visibility trace types. Why? Because this preset is used for the capsule component and we want our whole character mesh to be what is interactable. It will also prevent the camera for passengers from being stuck inside the mount capsule and allow it to freely rotate as normal.
Double click on “Character Mesh” preset. We want to set it up as the image by setting the camera trace value to ignore. This preset is used by the Character’s Mesh Component and will prevent the mount's mesh from interfering with passenger cameras.
First thing we will do is create our interaction Interface. In the MountingContent/Blueprints folder create a new Blueprint Interface and call it “BPI_InteractableActor”. This interface will be implemented by any actor in the game that can be interacted with.
The first function we will define is the “interact” function. By default UE4 should be asking you to enter a name for the function. Call it “Interact” then set the value of the category to “BPI Interactable Actor” so that it appears organized in the actor that implements this interface.
Give it an Actor input parameter. Call it “InstigatingActor” this will be your player controller. We could have the type for this be a Controller Type but I wanted to maximize the flexibility of this interaction system to fit with any game so this could be the player pawn, also thanks to the interfaces we do not need any specific function of the controller so an Actor will do.
Now give it an output of the boolean type and call it success. This will return true if the interaction succeeded or false if it failed for any circumstance deemed appropriate by you.
This may seem silly but I personally love to add a simple “Is Interface Type” function to my Unreal Engine functions. The reason is by default these functions always return false. So any object that does not implement this interface you can still call this function on and it will return false. It’s a great little cheat for UE4 Blueprint Interfaces.
Create a new function and call it “IsInteractable”. Set it’s Category to “BPI Interactable Actor”. Create a return variable and call it “IsInteractable”
And we are officially finished with our Interaction Interface.
The primary functions of our interaction system will be held in our PlayerController. I prefer to put such systems in the player controller because that allows us to customize the interaction systems differently for Players and AI. Players tend to interact with things they are immediately focused on while the AI tends to need to collect data around them and then decide on what to interact with.
Open up the “BP_MountingPlayerController”. First thing I always do is create an InputGraph. I like to keep my graphs separate because they can become very cluttered very quickly. If you separate the graphs into different purposes it makes things easier when working with a team. Create a new Graph called “InputGraph” there should already be an “EventGraph” by default. If it is not already there I highly suggest you create a third called “RpcGraph” that will hold our RPC events we will need to send to the server or client.
Now Create a new Variable of type Actor and name it “FocusedActor”. This variable will be used to hold the current actor that our pawn is looking at.
Create Function Stubs
We’ll begin by creating the subs for the functions we will need. Begin by going to the functions panel and add a new function. Call it “OnInteractionPressed”. Then Create another one called “InteractWithFocusedActor” and finally “TraceForInteractable”. Put these three functions into a category called “Interaction”.
Next create Two more function called “Dismount” and “Change Seats” and put it into a category called “Mounting”.
Double click to open the “InteractWithFocusedActor” function. Create an input parameter of type AActor and call it “InteractableActor”
Double click on the RpcGraph and open it. We will create three custom Events to support the mounting system. In order to execute mounting, dismounting, and seat changing commands we need to send our functions to the server to start the process.
Create the first custom event and name it SERVER_InteractWithFocusedActor. Add a parameter of the Actor type and call it “InteractableActor”. Now Drag the “InteractWithFocusedActor” function we created under the “Interaction” category and connect the execution line to it. Then connect the parameter.
Now create the Second custom event and name it “SERVER_Dismount”. Drag the “Dismount” function we created under the “Mounting” category into the graph and connect it to the SERVER_Dismount function.
Now create the Third custom event and name it “SERVER_ChangeSeats”. Create an input variable called “Seat Index”. Drag the “ChangeSeats function we created under the “Mounting” category into the graph and connect it to the SERVER_ChangeSeats execution line. Connect the SeatIndex parameter to the “NewSeatId” of ChangeSeats function.
Select “SERVER_InteractWithFocusedActor” event. In the details panel set the “replicates” property to “Run On Server” and then check the “Reliable” flag. In a real game you'll want to do some kind of validation here that the player is within interactable range or something along those lines. You could also have the trace be executable on the server but depending on how accurate your rotations are between the client and server that could cause more frustration than happy players.
Do the same for “SERVER_Dismount” and “SERVER_ChangeSeats”
We will now implement the TraceForInteractable function. For Sanity sake I’m simply going to link the code from BlueprintUE rather than attempt to create a picture for it all.
We want to return true or false if we succeed in hitting something or fail in hitting something. So we’ll create an output variable for the “TraceForInteractable” function.
In brevity this function checks if the character is currently mounted.
If it is not mounted it performs a line trace by the visibility channel. It first gets the current pawns location and adds 90 units on the Z axis to simulate the location of the eyes of the character. As the blueprint explains this can be different from pawn to pawn but adjusting this is outside the scope of this tutorial. This value gets set as the Starting point of the line trace. Then we get the rotation of the controller and transform that into a forward unit vector. It multiplies that unit vector by 300 units giving it a length of 300. Finally we add that to the start position of the trace to get the end point of the line.
We then check if the line trace hit something. If it did we retrieve the hit actor and call the BPI_InteractableActor function “IsInteractable”. If this is an interactable actor than it will return true, so long as we implement this function to do so.
If we have detected an interactable actor than we set it to the “FocusedActor” variable we setup earlier. If we do not detect an actor then we clear the “FocusedActor” variable.
Going back to the test for if the current pawn is mounted. On the True Execution line we we want to make sure that we currently have no FocusActor Referenced. So we use a Validated Get to test if it has a reference to something and if it does we clear that reference, other wise we return false.
We will now implement the OnInteractionPressed function. A link to copy and paste the code from BlueprintUE is available.
We’ll briefly go over how this function works. First we determine if our character is currently mounted or not.
If we are not currently mounted than the false execution branch tests if we currently have a “focused actor”. If we do have a focused actor than we simply pass it through to the “InteractWithFocusedActor” function and return success. If we do not have a focused actor than we return false.
If we are currently mounted than the true execution branch gets the Current Character Pawn and tests if it is fully seated on the mount.
If we are not seated on the mount than we invalidate the input and ignore it. If we are seated on the mount than we call the “Dismount” function we created under the Mounting category and return true.
Now we implement the InteractWithFocusedActor function. A link to copy and paste the content of this function is available via BlueprintUE:
Start checking that the supplied parameter “InteractableActor” is valid. Then use the build in UE4 BP macro “Switch Has Authority”. If this is the Authority Than we want to call the BPI_InteractableActor function “Interact” which will have the (Message) text text to it. We want the “target” of that function to be the “InteractableActor” which should be our potential mount. Then we want the “Instigating Actor” to be our controller, which we’ll pass a reference to “self” in.
If this is not the Authority then we want to call our Server function “SERVER_InteractWithFocusedActor” and we’ll pass our desired focused actor into it as a parameter up to the server. When “SERVER_InteractWithFocusedActor” is called on the server it will simply call this function but because it is already on the authority it will pass through to the Interact logic in the Authority execution line.
Now we will implement the Dismount function. A link to copy and paste the content of this function is available through BlueprintUE in the link below.
First thing we do is check that we are on the Authority. If so than we Get the CharacterMount and check that it is valid. If the character mount is valid we call the controllers “PrepareToDismount” function and pass the retrieved mount into it as a parameter. Finally return the result of the PrepareToDismount function.
If this is not the authority than we want to call our “SERVER_Dismount” function and return false. When SERVER_Dismount is called on the server it will execute the Dismount function and take the authority execution line.
Implement Change Seats
Now we will implement the Change Seats function. A link to copy and paste the content of this function is now available through BlueprintUE in the link below.
First thing we do is check that we are mounted, and if we are not than we ignore this action and return false.
Next we detect if we are on the authority or not. If this is the authority than we check we have a valid mount. If we do then we check that the desired seat we want to move to is not occupied. If the seat is not occupied than we get our character pawn, get the MountRiderComponent, and then call the component’s ChangeSeatById, sending in the “NewSeatId” parameter to “SeatId”
If any of the validity checks fail we simply return false.
If we are not on the authority than this function calls “SERVER_ChangeSeats” on the server.
In the Event Graph we will call our TraceForInteractable on tick as shown in the image. For demonstration purposes I’m not going to bother adding any optimizations such as not doing this on the server.
Go to the input graph and in there right click and search for the “E Key”. If you already have an Input Setup for this by all means use that but again for demonstration i’m going to hard wire this to E. From the “Pressed” execution line drag it out and call “OnInteractionPressed”.
We will bind the number 1 and 2 keys to our seat changing functions. We will set the 1 key to be our driver seat change and the 2 key to be our passenger seat change. This setup is easily expanded to more seats.
That is it we are finished with the controller. On tick it will scan wherever the player is currently looking. If they have a valid interactable in their sights by pressing E they will trigger the interaction process for it.
Setup the Mount
Now we must set up our Mount for interaction. Open up your mount, in this tutorial I am using the “BP_Horse”.
We want to add our “BPI_InteractableActor” interface to our mount.
Press the “Add” button in the Interfaces list on the details panel and find our “BPI_InteractableActor” interface.
Now under the Interfaces list in the Blueprint Panel expand “BPI Interactable Actor”. Double click on “Interact” function.
We will implement this very simply. For our interaction system the Instigating actor is our controller. But since this is an generic interaction system we do not know this for sure so we’ll call “GetCharacterControlller” from the instigating actor. Now that we know we have a controller we’ll check that it is a Valid object. Finally we will call “PrepareToMount” as a function message with the target set to the Controller and the “Mount or Rider” parameter set to “self” which is our mount. Then we return true for success.
Now implement the IsInteractable function. We are simply going to return true for this function. Though you could set up any bit of logic you want such as setting this to false if the mount is full.
Well that is it, our interaction system for mounting is setup and ready to go. You can now run the game and interact with your mount and ride it around.