Welcome to the first article in a new Gamasutra series called Games Demystified. In this series I'll be deconstructing games with unique or distinguishing mechanics that may have left some folks scratching their heads, including reproducing effects with code samples, examining -- just how did they accomplish that? The first game we'll be covering is Super Mario Galaxy for the Nintendo Wii.
Upon release, Super Mario Galaxy enchanted the gaming community because it allowed gamers to walk upside down and inside out -- all while feeling completely intuitive. Especially geeky message boards and blogs have lit up with people attempting to apply real-world physics equations for gravity to each of the game's tiny planetoids.
Obviously, real world physics have a place in today's games. However, they take a backseat to psychology when it comes to making real world gameplay. In reality, mass distorts the fabric of time and space causing gravity, the force that keeps us stuck to Earth.
People such as Isaac Newton have studied and derived a great deal about gravity, but no one actually knows the true mechanics behind the force. The important thing for the game is that we expect to get pulled toward planets -- and in fact, Mario does get pulled to each and every strangely shaped planetoid in the game.
Normally, gravity is calculated as a force between two objects that is directly proportional to the product of their respective masses and inversely proportional to the square of the distance from the objects' centers.
Force of Gravity = Gravitational Constant * ( ( mass1 * mass2 ) / distance2)
To simplify, gravity follows the inverse square law.
Intensity of Gravity = 1 / Distance2
This means that as an object moves closer to a planet, the gravity between them
increases dramatically due to the exponential effect applied by distance. Conversely, as an object moves away from a planet the gravity between them weakens very quickly.
Traditionally, the force of gravity is applied using the radius of the more massive body. For instance, if we were to calculate the gravity holding a person to Earth we'd plug the Earth's radius into the above equation. Additionally, if the mass is great enough to hold a person down, the celestial body will be round. This is where reality and Super Mario Galaxy differ.
In Galaxy, players are pulled toward each and every arbitrarily shaped planetoid they encounter. At this point all real world physics equations break down and tell us nothing about the gravity in the game, which as in most cases is a trick. Much like the magicians in The Prestige, game developers are always attempting to one up each other with new tricks. As we all know, Nintendo stole the show with its latest.
To accomplish this "trick", the developers use the surface normals of whatever planetoid Mario inhabits to keep him running and jumping happily along. This is how gravity is distributed through completely irregular bodies. A surface normal is a ray or unit vector that runs perpendicular to a plane.
In the illustration above we can see an arrow pointing up from a plane, the arrow represents the surface normal of a plane that points straight up. If we're using a Y-up right handed coordinate system, that normal would be a (0, 1, 0) which means it's not pointing along the x or z axes at all and that the full length of the vector is all in the up direction.
A unit vector always has a length of 1, which is then distributed amongst the x, y, and z directions. The longer one direction is the shorter the others must become.
As stated above the shapes in the game are often strange and in real life the effects of gravity would probably cause Mario to fall all over himself. In the game however, he always comes out a hero by landing on his feet and in general looking really slick as he traverses the strange surfaces, such as the one below.
Calculating real gravity on a surface like this would be a task best left to Stephen Hawking. However, for the game to play correctly, the developers merely had to cast a ray relatively down from Mario's local center and grab the nearest surface normal.
Inside any 3D game engine worth its salt, there is a list of all polygons being rendered on screen and we can retrieve the first polygon that our ray cast intersects.
That polygon in this case represents our plane and the inverse of its surface normal represents the direction of gravity that will pull Mario smoothly back to the planetoids surface. That polygon's surface normal is also used to align Mario to the curvature of the planetoid.
In the illustration above we can see that the character is aligned to the surface normal of the previously jumped on polygon but the ray cast (dotted line) now intersects another polygon as the player crosses the border from one polygon to the next. In Galaxy, Mario's orientation is adjusted extremely smoothly due to a combination of factors.
First and easiest to understand is that each planetoid in the game is fairly high polygon so the change from one surface normal to another isn't too steep. Additionally, interpolation can be used to smoothly transition from one orientation to the next. So, the surface normals in a Galaxy planetoid are analogous to the key frames of an animation in that regard.
To demonstrate these concepts we'll be looking at some high-level source code we've created - written in Blitz3D, which is an excellent tool for prototyping concepts just like this. You'll need to download a demo or full version of Blitz3D to edit and re-compile the code.
Here's the specially created executable/source code package for Gamasutra readers to try that demonstrates a lot of the concepts used in Galaxy. The source code does a number of things including setup, camera tethering, and rendering.
The component we'll be focusing on is the updatePhysics() function - here's a code sample from that section:
;====== UPDATE PHYSICS =======================================
player.playerClass = Object.playerClass( localPlayer )
planetoid.planetoidClass = Object.planetoidClass( player\planetoid )
LinePick( EntityX( player\character ), EntityY( player\character ), EntityZ( player\character ), EntityX( planetoid\mesh ) - EntityX( player\character ), EntityY( planetoid\mesh ) - EntityY( player\character ), EntityZ( planetoid\mesh ) - EntityZ( player\character ) )
AlignToVector( player\character, PickedNX(), PickedNY(), PickedNZ(), 2, 1 )
player\nX# = PickedNX()
player\nY# = PickedNY()
player\nZ# = PickedNZ()
If (EntityCollided( player\character, STATIC ) )
player\jumpTimer = MilliSecs()
player\vX# = 0
player\vY# = 0
player\vZ# = 0
player\vX# = player\vX# - PickedNX()
player\vY# = player\vY# - PickedNY()
player\vZ# = player\vZ# - PickedNZ()
TranslateEntity( player\character, player\vX#, player\vY#, player\vZ# )
distance = EntityDistance( player\character, planetoid \mesh )
For planetoid.planetoidClass = Each planetoidClass
If ( EntityDistance( player\character, planetoid\mesh ) < distance ) Then player\planetoid = Handle( planetoid )
The first two lines in this function retrieve our player and the planetoid he inhabits. This sets us up to apply the calculations, which will allow gravity to occur between them. Next we cast a ray from our character's local center to the planetoids local center.
This call requires 6 parameters. The first 3 are the starting point of the ray and the last 3 are the distance along the (x, y, z) axes to cast our ray. We don't use the ray itself to calculate gravity. Instead we only use the ray to find a polygon between the player and the planetoid and extract its surface normal.
The first thing we do with the surface normal is character alignment. The call to AlignToVector() has 6 parameters; the object to be aligned, the vector to align to, the axis to align along, and the speed at which we interpolate or snap to this alignment.
The first 4 parameters are pretty easy to understand. We pass our player character and the 3 components of the surface normal to the function. The final 2 components are a bit trickier. The first of the two represents the axis we want to align along. For our implementation we want to pass a 2 for the Y-axis.
This means we want to align the character to this vector along his local up-down axis. The final parameter should be a floating-point number between 0 and 1, which represents the speed at which the character will snap to the vector.
In this case a 1 has been passed to make the alignment immediate. Smaller values will allow for interpolation between the previous alignment and the new alignment.
Next, we check to see if we are colliding with the planetoid or if we are above it because we jumped or are in orbit.
The EntityCollided() call requires two parameters which are the player character entity and the collision type mask we want to search for.
At the top of the program we created two types of collision masks. The first was a DYNAMIC type and the second was a STATIC type.
Obviously, the player character is dynamic and the planetoids are static because they don't move. So this call tells us if the player character of collision type DYNAMIC has collided with some other object (any other object) of type STATIC.
If we are colliding then we set our jumpTimer to the current time and we zero out our velocity. In the updateControl() code we allow the character 250 milliseconds of boost along the surface normal for jumping.
Once those 250 milliseconds are exceeded (or the player lets go of the jump button) the jump force will no longer be applied and the character must touch base to jump again. Finally back in the updatePhysics() call if we aren't colliding then we subtract the current surface normal from our velocity and translate our character back toward the planetoid.
The final component of the updatePhysics() function checks the planetoidClass list to see if we're actually closer to some other planetoid. If we are then we switch planetoids so the next time updatePhysics() is called we'll check with this new planetoid to calculate gravity.
In closing, it's obvious that there is much more to Super Mario Galaxy than the cool gravity mechanic the developers employed. Although the included source code covers things like camera tethering and control, it only scratches the surface of what went into making Super Mario Galaxy a polished gem and an achievement in gameplay.
Of course, the one feature that wasn't included in other Mario games was the gravity effect and that's what distinguished Galaxy from the rest of the series and indeed the rest of the industry this past holiday season.
Hopefully, this segment of Games Demystified offered a useful peek inside the underpinnings of game development. Until next time... Waahoo!