Our approach and our team are not what you would typically find in the world of game dev. While most games are motivated by profit or passion whether AAA or indie, ours is a showcase game, meant to demonstrate our company's tech and know-how to our prospective clients. Most game development teams include a mix of artists, designers and gameplay specialists, but none of us can pretend to such titles; we are engineers from a small consulting startup and our passion is to get stuff working efficiently and optimally.
What could go wrong?
We drew out a rough plan for our gameplay. A cube-shaped hero would run across a 2D level and clear a series of obstacles with some well-timed jumps and backflips. Gameplay would be fast, with short, rapid-fire levels and each one would only last a few seconds. The casual style of gameplay would match well the smartphone platforms we were aiming for. We brainstormed a name and christened it Jelly Arcade. So far so good.
We came across talented (and thankfully very patient) artists who produced some amazing sound (thanks Yoshiki!) and graphics (thanks Sandrine!) and who saved our game from looking like a drawing from a 3-year-old accompanied by the theme from Nyan Cat. We wrote most of our core code over a few months in between client projects. The gameplay was working, the UI was coming along, our server code was mostly done. Soon enough, all that was left was to ship... no, wait a minute, where’s our game content?
Unlike our game dev projects with our clients, we didn't have an army of content designers or experience with preferred method for developing and laying out a level. We realized this would be a departure from our job descriptions as engineers, and if we wanted the levels to be fun and interesting, we’d have to be able to experiment, play around and iterate quickly and cheaply. We were going to have to engineer our way to creativity and "science the s*** " out of this by making ourselves a nice custom tool.
Editing and testing a level should be done efficiently without having to compromise the quality or quantity of levels. At the same time, we did not want to design a game-specific tool. We wanted to build a level editor that could work well with any of our future Unity games. If we remained as game-agnostic as possible, our editor tool could become a serious asset for all our future projects.
Making a level editor à la Unity
The key to our level editor design was to borrow from Unity the notion of components (MonoBehaviour). In Unity, there is a key concept where any game object in the scene can be attributed additional components in order to enrich and extend their behavior. For example, imagine you need a moving platform. Rather than implementing a "horizontally moving platform", you would implement a simple "platform" first, then develop a "horizontally moving” behavior that can be attached to that generic platform. The direct consequence of encouraging this generic design is that variants such as "vertically moving platform", "rotating platform", "disappearing platform" and so on can be implemented literally within a few lines of code, opening endless game design possibilities (not to talk about code maintainability).
Alright, as mapping how game components are organised in Unity to an editor is the way to go, we designed our interface to reflect this approach.
You’ll note the game objects are at the top right. We can drag and drop them wherever we’d like them to appear in the level layout. Next, we pick certain behaviors from our list of components on the left. These are dragged and dropped onto the game objects in the scene. We can tweak the component and behavior variables on the right, allowing us to, let’s say, increase the value of a speed parameter or add a collider to game object. And that’s it: the obstacle is complete and ready come to life.
As we mentioned, we would like our editor to stay as generic as possible, so it's important to be able to painlessly edit our editor as well. We needed a simple way to create a game-specific interface. This was easily solved with a project-specific settings file (XML) that describes all of the game objects and behaviors, and all of their parameters. The editor is able to generate its interface tools accordingly, and we’ll see how this makes adding newly created components a breeze.
When we hit the save button, the editor outputs binary files containing the data for all of the level’s game components with their behaviors and any data that has been entered, such as their sizes, position, speed, etc.
Great! Now we just have to find a nice clean way of getting this level data into the game.
So how do we take all these binary files with the components, behaviors and property values and put it back into Unity?
Reading a file and extracting basic types of data is relatively straight-forward, but it's difficult to write the code in a way that keeps up with the often rapidly changing requirements and experimentations of game design. The most straightforward approach would be to have massive switch statements, where we read in names and parameters one by one, and instantiate objects. This would work, but this would be a nightmare to maintain. But wait, we are engineers, we should be able to figure this out, right? There’s got to be a better way.
Thankfully, C# and .NET support a little thing called reflection. There’s a ton of reasons that make it useful, and among them is the ability to take a string, such as “VerticallyMovingBehavior”, and translate that into a reference to a class VerticallyMovingBehavior. So, as long as the names of our classes match the names of our components in the XML file for the level editor, we can easily read in our data and bind its properties at runtime.
Here is our approach:
For each game object described in the level file, the game instantiates a GameObject.
(See step 1 of the editor screenshot)
For each component of a game object, the game instantiates the appropriate class
(See step 2)
The component type is deduced from its name (which matches the class name)
The instantiation is handled through Unity using [GameObject].AddComponent(type). We don't even need to use the Activator.
Thanks reflection! This way the data is loaded by the game at runtime, from our files or from asset bundles. And even though one might hesitate to use reflection and .NET given its reputation for issues when deploying on iOS, there’s no need to worry; even when using IL2CPP builds, reflections were still handled properly.
As all of the levels are easily created thanks to reflection, creating the components, attaching their behaviors, configuring their properties is only a matter of a few lines of code. No need to iterate through multiple levels of loops and crawl across massive switch statements.
For instance, imagine we want to create a new behavior and have our platforms rotate. We’d first add a new class to our Unity project called “RotationBehavior” and define its update method, i.e. how fast it should spin every frame. Then, we add its definition in the Level Editor XML project file, with a definition for a ‘speed’ parameter, if necessary. And there you go, it’s ready to run.
Now that this level creation pipeline is set up, there’s a lot we can do in our level editor through extension plugins. For example, in Jelly Arcade, we implemented the alignment grid plugin and the orange ground plugin to help get our bearing while editing. We even added a interactive player plugin so that we could test some basic gameplay directly in the level editor. One of the most useful plugins was an export that automatically lays out all of the levels and saves them as a JPEG. We could print them all out automatically, put them on a board, take a step back, make notes, share thoughts, and iterate.
Sure there are some engineering costs upfront, but investing our time in making a proper level editor definitely paid off so much that we consider it to be an essential part of a game development project. And using a generic, extendable approach was a huge advantage. We’ve already even used the editor for a completely different showcase Unity game: a infinite vertical platformer.
Even though the specs were quite different, the component/behavior design principles didn’t change, and the editor was effortlessly versatile. If you think that it could work for you and your Unity game too, then I’ve got good news. We’ve just made it open source and published it on github, so please take a look. If you have any questions or would like check it out, feel free to give us a shout!