As I'm sure you can imagine after all of this talk, this class, and the work implementing its gameplay, will be done almost exclusively in the editor (there are ways you can always hook C++ to blueprint, which will be discussed near the end). So, start off by opening the editor and going in the content browser tab to our Content | FirstPersonCPP | Blueprints and right-click, like we have done before, to make a new class:
Click Actor as the parent class for the blueprint, and name it MovingPlatform. Now, we have a completely bare-bones actor we could place in our world, but it doesn't even have any geometry, of course. Let's quickly fix this. Panning around the level, you'll see two gray rectangular boxes (not the white boxes, but those actually work fine too): click one and then right-click on it and select Browse to Asset (Ctrl + B) (note, you can also find this when editing most assets from the menu bar under Asset), and you should be in Content | Geometry | Meshes now with 1M_Cube selected. So, now that we know how to find this simple piece of geometry, let's go back to our MovingPlatform and double-click it. You may notice, since we made this as a blueprint class from the start, you get the full blueprint editor automatically, and not the minimal interface it normally starts with for native classes that don't use scripting, as we often had before. For our platform object, we now need to add a component. Note that you could just add a simple cube or plane as a static mesh component. It is great that these things are available, again, especially for quick prototyping. Just to get used to the workflow, however, which is more typical of professional games, we will use the static mesh cube we were just looking at. So, type to filter or just scroll for Static Mesh in the components drop-down and add one. Now notice in the hierarchy how this is added under the default scene component (named DefaultSceneRoot). Once we have an actor component in our case, that can be the root component of our actor object here, the default is really just a placeholder. So, rename the mesh component to Platform and drag it on top of the scene component to replace it.
Now we'll do two more quick moves to get this platform looking right, click the mesh component and on the right in its properties, under Static Mesh | Static Mesh, select 1M_Cube, which we found earlier, and click the unlock icon to the right of Transsform | Scale and things should be coming together:
We can now drag these into our scene, but of course, it won't do anything other than block our player, as the step-height is too high for its collision to handle. You will also notice it need to compile and save, good steps any time you are making progress on a blueprint, though if its scripting is not ready to successfully compile, obviously hold off until it is. You can save and use blueprints with broken scripting, but their warning messages can be very distracting and allow people to not notice other broken blueprint issues that may be critical.
Before we can really use and script this platform, however, we need to add one more component. Unreal keeps blocking (hit) and overlap (touch) collisions separate, and in this case we need a bit of both. So, click Add Component, and this time we simply will add a cube from its list. Notice it should be parented to Platform and because of this, its size is the same since the scale from Platform propagates to our new cube. Now, raise it up a few centimeters by either dragging in the viewport along the Z axis or typing in a value in its transform until you can see it is above but is still basically touching our original platform (I found a Z value of 50 worked for 5 cm since our Z scale is 0.1). Now, scroll down until you see the Collision flyout and make sure Generate Overlap Events is checked. Click in the drop-down for preset to OverlapOnlyPawn, and down in the Rendering flyout, uncheck Visible as we don't want to see this piece because it is only there to detect our character walking on it. In the Platform component's collision events, you can leave this as BlockAllDynamic, and note that generating overlap events is irrelevant as hit events supersede overlap, so you will receive events when projectiles or the player hit the platform, but we would never get the overlap events we need with these filters.
This is how everything should look right before clicking the Visible box at the bottom of this screenshot.
And finally, to some blueprint scripting! Click over to the Event Graph tab and you'll see the existing events are grayed out with notes about how to enable them. In our case, we want the overlap box. Click and drag from the blue Other Actor pin (from here on, we'll just call this a pull from the pin), and you will see a list of many things we can add that are context-sorted (so long as the checkbox remains checked) to things that take an actor as input, the way that pin from the event is supplying it as an output. Filter for GetClass, and you see it has been added now. For those familiar with blueprint scripting, just check out the following screenshot for the progress. Here are the steps (after adding GetClass) to have that in place:
- Pull the return value for Get Class, and find Is Child Of in the list. These should now be wired. In the Is Child Of box, in its dropdown, select our MasteringCharacter class as the type so only our player gives a true result here.
- Pull the red result pin from is child of and filter or find Branch, then wire this from the white triangle output pin of the actor begin overlap event, and in to the input white pin of the Branch.
- Pull the white output (which points right) pin from the branch's True result and filter and add a MoveComponentTo block.
- Pull left from the MoveComponentTo block's blue component pin and type to filter for GetPlatform (a reference to our root component).
- Pull left from the TargetRelativeLocation yellow pin and type + or otherwise filter/scroll for vector + vector in the list.
- Pull from its top left pin and type/filter for GetActorLocation, and you can leave the input pin on that node as self (that's what we want).
- Set the bottom vector's destination to what you want. In my test case, a Z value of 300 puts us up even with these other large gray blocks around the map.
The TargetRelativeLocation variable seems a bit poorly named, as what it wants for a proper motion is a world location. And lastly, on that MoveToLocation node, set the time to whatever you like: 4 seconds as here is a little slow and boring, but great for demonstrating that this all works:
Don't forget to compile and save.
Now all that's left to show this off is to drag one of our Moving it into the world on the floor somewhere, and walk over and step on it. Off you go in the air! Note that if you step on it again by hopping off and on, it takes you up further. We'll make this thing a bit better shortly, but right now, this is a good checkpoint for the GitHub version of the project. Here you can see where it is placed in the book's project's map (again, by dragging the MovingPlatform blueprint icon into the main level window itself):
There are many, many more options that we could do with something like this now. It's possible to add all kinds of logic, including other components, allowing this platform to do pathfinding navigation, or adding a spline to the world that in the level blueprint for the platform uses (from the main editor window, click Blueprints | Open Editor Blueprint to access individual blueprints that can reference each other in the level). There is a massive amount of very valuable work that can be done in these areas. For now, we will make the platform head back to its start point when the player steps off, and the same when it reaches the top of its movement:
Note that this uses a couple of blueprint variables added to the class, too, a short reference to which is added in the Further reading section. The short version is under the My Blueprint tab on the left. There is Add New, and under that is Variable. Once you add one, you can change the type and default value. Here, we added one we renamed under My Blueprint | Variables as StartPosition, and on the right under details, set its type to vector, and also add GoingHome as a boolean. You can always then access these types, like C++ exposed variables too, in get/set blocks in blueprint scripting windows. Note, though, that unless you make a specific accessor (and here is an example that is not used, but you could implement) these variables cannot be accessed in C++:
UFUNCTION(BlueprintImplementableEvent) FVector GetStartPosition();
Making this a blueprint-implementable event that means for a hybrid C++/blueprint class you could add this as an event type function in blueprint and then simply have it return StartPosition. This way, a variable that is only defined in blueprint could be accessed by C++. Similarly, to make native functions in C++ that can do work as we have before, be sure to keep in mind BlueprintCallable as a UFUNCTION keyword, as these can be accessed by blueprint any time you are in or using an instance of the class that implements it. Spoiler alert: we will be doing this quite a bit in the next chapter, Chapter 4, U.I. Necessities: Menus, HUD, and Load/Save, similar to this:
UFUNCTION(BlueprintCallable, Category="Appearance") void SetColorAndOpacity(FSlateColor InColorAndOpacity);
These functions do work in C++, but can be directly called by blueprint. Note also that these calls from C++ to blueprint and vice versa have a fairly significant call-stack overhead. On previous hardware this was a major performance issue, but to save you time here and now, on most platforms these days, the overhead really is minimal. Keep this in mind that if you switch between the two frequently, but there is no longer the same level of stress that was caused in UE3's similar systems.
OK, so these asides about how to call back and forth from C++ to blueprint notwithstanding, let's get back to our quick logic to get this elevator in a finished state. As you can see, there are several steps, and once again for brevity, let's just add them here in an ordered list:
- First, we need our two new variables, so as noted earlier, in the My Blueprint tab, click add new twice and pick variable from the list for each.
- For the first one, rename it GoingHome, and its type can remain as-is, as a boolean.
- For the second, also as noted, name it StartPosition and give it the type of a vector.
- Now, pull from the event for BeginPlay in the scripting window and filter to Set Start Position.
- Pull left from its vector and pick GetActorLocation like we did earlier, again leaving self as the object. Now, when we begin playing, this platform will mark its initial location and save it in a blueprint-only variable we just made.
- Now pull from our existing MoveComponentTo node and filter to Set Going Home, and check its box to true.
- Pull from that node, and make a new MoveComponentTo node similar to how we did before. Use Platform again as the input component, but as the TargetRelativeLocation, drag from it and filter to GetStartPosition.
- Pull from the new MoveComponentTo node's output and filter to SetGoingHome, making sure this time the node is unchecked so it's set to false.
- We need a new event: right-click anywhere and filter to ActorEndOverlap.
- Drag from its output and add another branch. Pull that branch's condition and do the same logic we did before, pulling the end-overlap's OtherActor to a GetClass node, then that return to a ClassIsChildOf MasteringCharacter, or just copy and paste those nodes with a multi or shift/control select from the begin-overlap and save yourself some trouble.
- When that branch is true (this means our player's character is no longer on the platform), pull from the true, filter to SetGoingHome, and check its box.
- Pull from that to a branch, pull from its condition to and filter to GetGoingHome.
- Pull from the branch's true and add the same set of logic as before with MoveComponentTo (to StartPosition, clearing GoingHome to false after completion).
What you should have now should match both the preceding screenshot and the final check-in for GitHub in the chapter 3 branch. This elevator pad should now reliably get you up to the next floor, but also return to its start once it arrives there or is abandoned. Ideally there should probably be a timer when reaching the top before it auto-returns, and there's no reason most games could not generally just rely on the end-overlap path to reset the platform. Again, this is just showing a hint of what blueprint can do for you and is a great starting point for further experimentation!