I’ve been working on an endless runner in Unreal where the main control utilizes the rotation of a mouse scroll wheel or touch-drag movement to move platforms and bridge your character through the course. One thing I noticed right away through prototyping was that true randomness was not fun at all and just seemed hectic and frustrating while also being hard to calculate the possible paths that the player can accomplish. I’d never want the player to feel like there was no way to continue in their run because a block was impossibly far, that’s a great way to get people to quit your game and never come back!
There were also lessons learned in a previous endless runner that I worked on where I developed an almost Minecraft-like random 2D tunnel that you navigate through. The downside of the system was it being very resource hungry for flash, so much that we couldn’t have physics from collisions, so the character felt like a ghost that would go through walls but get damaged. We had some audio and visual feedback to help with making the collision clear but it still did not feel satisfying enough. So I would say that the gameplay did not benefit that greatly from that more complicated random system.
Working out a solution
My random generator solution for this new game was creating a set of block prefabs that I designed and then randomly spawn into the level at reasonable degrees from the last prefab. This allows me to be more in control of the difficulty, and most of all it lets me make different kinds of gameplay sets, like miniature levels, that target a certain skill of the player to accomplish.
If you look to the top left of the video, you’ll notice some console logs that say things like SPAWN short5
this is my tracker telling me what prefab is spawning off screen and so far I’ve separated them into 6 categories:
- Single – a simple single block and the most commonly spawned.
- Short – a short set of blocks, usually of 2-4.
- Long – a longer set of blocks that could reach up to a dozen.
- Branch – a set that branches out into multiple paths where the player has to choose one. It could be a long branch or a short one.
- Trick – a set that has a twist that keeps the player on their toes or has bonus blocks for a risk taker player.
- Jump – a rare and risky set where there’s a gap between two blocks and the player has to set the first block higher than the second, so the character can safely fall on top of the second or they’ll fall into the gap between.
Separating these into these categories has allowed me to focus on unique gameplay setups that I would not be able to get out of a purely random generator and has made development of these sets much more organized for easier balance.
I can do things like control how common each kind of category appears simply by adding or removing more of those kinds of categories in the difficulty set. The beginning starts off easier, so there are only two Trick types in the easy set and they are not that deadly, while the medium set has twelve Tricks so far.
How I ramp up difficulty
I developed multiple difficulty strategies and combine them like a set of puzzle pieces to get the feel that I want for the block prefab.
Length of each block
This is simply making the blocks shorter to increase difficulty. Increasing difficulty this way makes the game feel like you need to make much more cautious movements as you can easily overextend a rotation and collide into a block. One thing that has been important is too keep the lengths organized into quarters, halves, full size, doubles etc. This makes the gameplay have a kind of tempo (I could even see timing this with music as a possibility) but also makes development much simpler.
Rotation distance of each block
Another simple one where the greater distance you have to rotate, the greater the challenge will be. I can increase the challenge even further by having multiple chains of large rotations with a maximum of 180 degrees. The direction of rotation is important for the challenge too, switching from forward to backward tends to be more difficult than a continuous rotation in one direction. It can also be switched up by rotating in one direction multiple times to get you used to that and then suddenly reversing direction.
This kind of difficulty increase from rotation feels intense and hectic and the visual aspect of the large rotations is appealing. Combining this with shorter length blocks will require fast, but accurate reactions from the player but doing this for too long is exhausting for the player.
Layered blocks
This difficulty will usually fall into the Branch and Trick prefabs and I find is where the real fun starts, but again I don’t want to overwhelm the player. This is making areas with more than one block accessible at a time and sometimes can even have blocks stacked up beside each other that you can rotate into one another and lock in more easy points.
The feeling that this kind of difficulty achieves is style. It just feels stylish to move through courses like this, kind of like the feeling of charging up in a racing game. I believe this is because of the larger amount of visual stimulation of the blocks rotating and getting locked in by the player. But I find that it is important to give breathing room to let the player recharge for the next clustered area.
Distractions, red herrings or traps
One of the hardest difficulties I would say is this one, because it requires you to pay attention to not just the block in front of you, but the blocks further up ahead and also the blocks beside the one that you are about to touch. You could find these difficulties in Branch, Trick and Jump prefabs.
I can do things like making a branching path lead to a dead end or have a block that will collide with you just as you lock in a larger block and you quickly have to rotate the smaller one out of the way. In a less difficult scenario, I can make useless blocks that stray off the main path that act as decorations where you would likely end up falling if you tried pursuing them.
The feeling that this difficulty causes is confusion but I find it gives the most satisfaction when you successfully make it through one of these. This difficulty type requires a great amount of balance because I know it will cause a lot of frustration if done incorrectly. Like for a dead end one, I would not make a long branching path lead to a dead end because that would feel quite unfair. There’s a lot of potential of interesting gameplay that this difficulty style gives though, a kind that’s uncommon in endless runners, which could make a fun game if done right.
Putting it all into practice
Instead of creating actual prefab assets that contain all the blocks and their positions and rotations, I opted to make use of Unreal’s data tables. This keeps the size of the game much smaller and gives me a better glance of all the assets. The drawback is I don’t have a visual of all the blocks and sometimes will have to make more complicated ones in the scene first to get a visual and then input all the data. Creating a tool in Unreal to help visualize and create the data tables may be possible, but for now I have found this to be good enough and haven’t had too much trouble visualizing the blocks by just looking at the data.
You can see where I enter the data:
Position 1.0
Rotation 60.0
Scale 1.0
Point Value 1
Has Box no
---
Can Invert yes
Most of if is pretty self explanatory:
- Position, Rotation and Scale control all the 3D data for the platforms.
- Point Value is the amount of points you’d get upon successfully locking-in a block, more difficult blocks will give more points, especially bonus optional blocks.
- Has Box is not used right now, but it’s there as preparation for a potential future feature that I might detail in a later post.
- The bool Can Invert is for the entire set and will tell the spawner if it can spawn a horizontally mirrored version of the blocks for an easy way to add variety. This is important because some trick setups can’t be inverted or it’ll result in an impossible path to get through.
The data tables are then plugged into my platform spawner blueprint which reads the data to perform the correct calculations as the player nears the end of the last platforms. I use fractions in the data tables like 0.25, 0.5, 1, 2 etc. to keep things simple for the scales and positions and then multiply the platform’s world length by those numbers along with a global scale variable while spawning them. The global scale variable allows me to have global tweaks on the platform lengths if needed for a fast way to shorten and lengthen the prefabs.
Closing
I believe endless runners are the most fun when the game’s goal isn’t to be incredibly challenging and get the player to loose as fast as possible, but instead have mechanics that gives the player choices and an edge to survive. One way to do this is in the level design by giving breathing room with different difficulties and pacing, while providing variety to keep the entertainment level up. But it’s not about variety in the positions of the blocks, it’s all about the variety of strategies that the player has to deal with. Those are the kind of things I’m aiming to do with the design of this controlled random system.