Over the last several years, Unity has been quietly revolutionizing the gaming industry. Unity combines a high quality game engine and development environment into a very affordable package, accessible to indie game shops and even starving students.
As part of an internship at Ayogo, I learned Unity and built my first professional quality game over the course of just a couple of months. It isn't exactly a triple-AAA hit, but I think it far surpasses the average "casual game" produced by a single programmer in a short time frame. I feel like I owe a lot of the polish to Unity, so I thought I would highlight some of the tips and tricks that I used to make Lightspeeder.
I should clarify that there is not space available for this to be a detailed Unity tutorial. It's more of a description of the architecture of a specific Unity game. I'll discuss some of the conceptual challenges that I worked through on my path from Unity newbie to game developer.
What Goes Where?
To start, a hard point for me when getting started on Unity was concerning when to do things through the Unity inspector/interface and when to do it by script. This is probably a matter of taste, since at first I found myself uncomfortable assigning values through the variable inspector, but I eventually found myself using it more and more often. The only way to get over something like this though, I believe, is through experience and familiarity. So keep at it and don't worry too much about which solutions you use at first. It's relatively easy to change your mind on these decisions later. I also felt a little overwhelmed by the apparent myriad of approaches to any single problem I encountered on the way. I think that (hopefully) after reading this it might help you when you encounter similar dilemmas in your game.
Early on you have to decide which part of your game will be handled by the physics engine and how much is going to be moved by custom scripting. In our case the decision was pretty easy, because Lightspeeder physics are not like real-world physics at all. For example, we have 90 degree turns, whereas nature prefers to obey Newton's laws. So most movement is handled by custom-made scripts. This also helps to give more control over the feel of the gameplay as you decide how objects speed up or speed down. On the other hand, I did use Unity's built in collision detection to determine when bikes have collided with an obstacle and to measure how far obstacles are from the bike. This lets the AI and other game objects handle situations appropriately.
Making the Arena
The arena was built completely through the unity interface. It began simply as a plane with a texture. Since I am not using any of the built in physics for the bikes, this plane is only a visual entity (i.e. no mesh collider) with the floor texture. You do, however, need to worry about keeping bikes inside the arena. To do this I made four cube colliders which I scaled accordingly to act as walls for the arena and placed them by hand. It is a good idea to make these colliders children of the arena, so that they'll always be in place if you move the arena for any reason or in case it gets scaled up or down to change the size. (footnote: speaking of children...we had a beta test group composed of some children who were very enthusiastic about early versions of the game that allowed jumping beyond the arena walls!) Finally, I made an empty object which I renamed to "spawn point". I placed four copies in the locations that I wanted the bikes to spawn (also as children objects of the arena). I also had the local Z-axis facing towards where I wanted each bike to be facing when spawning at these points.
So that's it, the arena consists in: a plane with a floor texture, four wall colliders at the edges and four spawn points placed at the desired points inside the arena. Later, our 3D artist, Kevin, incorporated all the visual assets around this basic model, which consist on a collection of textured meshes, none of them collidable with anything. I did have to re adjust the size of the wall colliders to better fit the visual models.
Local Coordinates vs. World Coordinates
Understanding the difference between local coordinates vs. world coordinates is of critical importance when working with Unity. I wanted to add this section, because this was probably the single most important conceptual breakthrough for me when learning Unity. And certainly, if you plan on making 3D games you will need to learn your self some analytic geometry! If you are not familiar with vectors and how to visualize them in 3D space I recommend you just skip this section, but I would suggest starting to fill this gap immediately (internet is your friend).
World coordinates are the coordinates where the positions of every object of your scene are located. For each individual object in the scene however, it is useful to think in terms of their own reference frame. The 3D bike model in Lightspeeder, for example, is oriented in such a way that its Z-axis is always facing forward, its X-axis is always facing left and its Y-axis is always facing up. If we move and rotate the bike around the world space, this local reference frame moves and rotates with the bike. For the bike, the vector (0,0,1) will always be a vector facing "forward". The important thing to understand then is how and when to go about between these two coordinate systems.
For example, each bike at some point calls
transform.Translate( 0, 0, someDistance );
There are two outcomes one could expect from this: the bike moves in its forward direction (since in local coordinates Z is always the forward direction) OR the bike moves along the world Z-axis. How do you know which one happens? Well, go ahead and look up the Translate method in Unity's scripting reference: http://unity3d.com/support/documentation/ScriptReference/)
You'll notice that it actually takes one extra argument (called relativeTo in the scripting reference) which is the reference frame. If we leave that argument out then it is set to 'Space.Self' by default, and so it reads the coordinate using the local frame coordinate and it moves the bike forward, according to the bike's facing!
This actually becomes second nature with some practice, but it is very important to always be aware of it when you are doing your first scripts. Be very conscious every time you move an object or refer to a direction if you mean this thinking in world or in local coordinates.
Unity has all the right tools to work with this. I found the methods Transform.TransformPoint and Transform.TransformDirection particularly important and useful.
Let me close this section with some examples. For simplicity's sake, lets imagine the bike is simply a 3D cube with a side length of one. Thinking in local coordinates these are the coordinates of special positions in the bike:
- Bike center - ( 0, 0, 0 )
- Bike forward middle height point - ( 0, 0, 0.5 )
- Bike right middle height point - ( 0, -0.5, 0 )
- Bike forward top height point - ( 0, 0.5, 0.5 )
If we want to access any of these positions in world coordinates, we need to ask the bike transform to convert them to world coordinates:
- Bike center in world coordinates - (bikeTransform).TransformPoint( 0,0,0 )