One could argue that Half-life is 3D, and the other two are 2D. Someone else would say that Monkey Island is actually 2.5D. And a third guy will say, you’re both idiots, they’re all 3D. So, who’s right? Discussing the logic behind Spark Game Engine.
The confusion stems from not specifying exactly what we mean when we say a game is “3D”. It could either mean this game has the illusion of depth or it could mean that entities in the game have a geometry composed of polygons. So to avoid the confusion, we could now agree on the following:
Half Life 2: 3D world using 3D models
Angry Birds: 3D world using 2D sprites
Monkey Island 1: 3D world using 2D sprites
For Angry Birds, the illusion of depth comes from the parallax effect. When the camera scrolls horizontally, the mountains in the background will move slower than things in the foreground suggesting they’re far away. For Monkey Island, when Guybrush moves over that bridge he’ll get smaller and smaller, again suggesting depth.
The Idea behind Spark Game Engine
When developing Spark, the initial idea was to be able to create both 2D and 3D games. That means the game engine would be able to render 3D geometry (polygons) as well as sprites but also that you can create games that have depth and games that do not. If we stop thinking about graphics for a second, we can create an entire game, create the rules, the gameplay, the interaction between objects, everything, and programming wise we’d be done, even if nothing is displayed on the screen. So now if you knew that your target platform can support hardware accelerated graphics, you can just attach those entities to 3D models. If not, use sprites but either way you’ll only need to write the game once.
To accomplish this, we created a virtual world. This virtual world is a data structure that holds all the information about the game. A “scene” inside that world can have a 3D coordinate system (games width depth), or a 2D one. An FPS game level would use a 3D scene. It’s main menu though would use a 2D scene. Now, logic will be applied to that virtual world to create the gameplay for our game. Later on we can attach a 2D and/or 3D mesh for every entity and let the renderer decide which one to use at realtime.
By using a virtual world as a middle-man and not giving commands directly to meshes (playerModel.x+=50;) we’re decoupling the game logic from the rendering logic, and this can have a lot of advantages. We can provide fallbacks at runtime, we can mix and match renderers without touching the game-code, we could even create a DOM renderer in the future and suddently you could be using Spark to build websites.
Varying Requirements for Every Game
So, this covers the 3D geometry and 2D sprites bit. What about the requirement to create a 3D scene for levels with depth but a 2D scene for levels without. Why not use a 3D scene to create sprites for a 2D game (think Super Mario that doesn’t even have parallax) or a main menu, and just keep the z property for everything the same?
This is a legitimate question, and many game engines, like Unity, are doing 2D this way. The problems with this approach is the overhead and additional complexity that comes from trying to use a renderer that thinks 3D, to be used as a 2D renderer. Let’s talk about the latter first.
In a 3D world, you can’t explicitly define what part of your scene is going to be inside your viewing frustum. You define a size for your view, position your camera, set it’s field of view and then you get a render. But in a 2D world all you really want is to either render the whole thing (e.g. a main menu background) or a part of it (a scrolling game). For 2D renderers this is easy, you explicitly specify the area of your scene you want rendered and pass it to the view for further processing (if the area you wanted doesn’t match the view size you will need to decide on scale-to-fit vs widescreen vs cropping, etc). So, what was once a convenience when rendering 2D scenes (a 2D scene rendered on a 2D screen, a match made in heaven) now becomes a problem.
On the main menu example, your 3D game engine will need to calculate how to position the camera or scale your scene so you don’t have to do trial and error by yourself. On the Super Mario example, the 3D camera can’t tell if it’s rendering past the bounds of your scene so you’ll need to hack a solution yourself (min(camera.x,2184);)
The complexity of how 3D renderers calculate the viewing frustum also doesn’t come without a cost. So you will be spending CPU cycles to produce a behavior that you don’t want in the first place. Also, sprites belonging in a 3D scene have 3D positions (regardless if they share their z value or not) so in order to render them on a view the renderer will be querying the 3D camera and do a lot of transformations (positioning and scaling), a step that can be completely avoided on a 2D scene.
So, to sum up, there is a distinct difference between saying that a game involves a 3D or 2D world, and saying a game uses 3D models or 2D sprites. What we have found while developing Spark is that a game engine can benefit if it makes an effort to be more agnostic when it comes to the entities’ meshes (3D models or 2D sprites), but also make a clear distinction when it comes to rendering a 3D or a 2D world.
If you made it all the way down this article, you now HAVE to leave a comment :P. Give me feedback if this was interesting or not, if I made any mistakes, if you want to propose another article or anything else.
Thanks for reading!