My name’s Dirk, and I have been working at Fireroot as a developer for a few months. I’ve been hard at work overhauling the systems used in Rogue Reaper to make them ready for its sequel.
Today, Lars has asked me to write a post about the stuff I’ve been working on for Rogue Reaper: Awakening. I’m more than happy to do so, so I hope you’re ready for a more technical post 🙂
Our telekinesis system works by recognizing Targetable objects in the game. If you know how Unity works, you probably know where this is going, but here’s the short version: Unity, the game engine used for RR:A, works by organizing scene elements into Game Objects and letting you attach Components to them. These components define what the game does at any point in time.
One such component is called Targetable. When you look at an object that has a Targetable component (of which we say that it is Targetable), the component that checks what you’re looking at knows that it can be used in some way for telekinesis. But it doesn’t know what it does, nor does it care. When you look at an object, the game tells the object, and then the Targetable may figure out what to do.
We use polymorphism a lot in our code. It’s kind of complicated, but the short version is that Targetable doesn’t actually do anything when you look at it. Rather, we have other components that are derived from Targetable, which means that they are Targetable with some extra features. For example, a MoveableObject is a Targetable that you can pick up and carry around, and then toss it at some of the puzzle Targetables we have. Maybe you need to throw a rock at a BreakableTarget to open a path, which causes an object to fall apart. Or perhaps you need to put a statue back together by throwing the pieces at AutofitTargets, which rotate the pieces into the correct place. Of course, puzzles are their own part of our code.
Our puzzles are built using the aptly-named Puzzle component. This is another component which doesn’t do much on itself, but it lets us link objects to puzzles without the need for the object to know what kind of puzzle it’s dealing with. We have CombatPuzzles, PressurePlatePuzzles, TelekinesisPuzzles (which use one or more Targetables as described above!), etc. Puzzles galore! And thanks to polymorphism, we can create new ones without having to worry about breaking any of the existing ones.
When a puzzle is completed, it sends out a signal about it. We can add other components to the list of recipients for this signal. By linking objects like this, we can do pretty much anything. We can tell the Soul of Life to play a little animation and move through the newly opened path. We could start a cutscene, or make enemies appear. Or we could activate a different puzzle, creating chains of complicated puzzles with many steps that need to be done in order. We could open up the way to perform a wallrun to get to new areas. Wait, wallrun?
A deceptively complicated, but hugely important part of almost any game is movement. If you do movement right, the player will never notice how much effort was put into it. But if you do it wrong, the player will constantly be fighting with the game, which makes for a frustrating experience.
In the current development stage of RR:A, the player can move in two ways: walking, and wallrunning. Walking is obvious. Wallrunning is when you run towards a wall and jump, and transfer to running sideways or upwards along the wall. We have several other means of locomotion planned though! If we’re going to add an entirely new way of moving, it’s important not to break anything in the existing modes, and once again polymorphism comes to the rescue here 🙂
Many games, at least the ones in Unity have a component called a PlayerManager, or a MovementController or something along those lines. It is responsible for responding to player input and applying movement to the player, and playing animations. If you put all of this in the same component, it’s easy to make one minor change somewhere and accidentally mess up something else, in an entirely different area of the code. You would have to constantly test everything, to ensure that didn’t happen. Or, you can be like me, and split the component up into smaller ones, which only care about doing one thing.
Our PlayerController is only concerned with keeping track of the current InputSource and MovementDriver. The InputSource will be one of a few derived components, and it could read the actual player input, or replay a recorded sequence, which is useful during gameplay demos. The MovementDriver likewise can be either a WalkingDriver or a WallrunDriver. In the future, we will add other Drivers without having to worry about breaking wallrunning, for example.
Wallruns are done using math, a lot of math. When you start a sideways wallrun, you will be moving up initially, and slowly you will start to go down. This looks like a very nice curve called a parabola. To give you an idea of what the math looks like, here’s what we need to do before we can start a wallrun.
As you can see this involves all kinds of vector math and quadratic and cubic equations. I totally didn’t have to re-learn all of this when I wrote this 😛
That’s about all the room I have for talking about the technical aspects of the game (that huge block of code certainly didn’t help). I hope you thought it was interesting, and maybe it’ll help you better organize the code of your own games!