Camera system

13 Apr 2014

A combination of laziness, too much sitting behind a computer at work and nice weather outside, resulted in little progress on the game. In fact, I only spent a few hours yesterday since last update. Doesn’t matter though, I don’t have a real deadline anyway ;-)

The single thing I implemented yesterday is a simple camera system that tracks the player ship, to keep it at a fixed location on screen, and move the rest of the world relative to it. Additionally, it allows for zooming in and out by resizing the viewport.

Because the game uses orthographic projection, ie: no perspective effect, the camera system is very simple so I won’t spend too many words on it. I basically added properties describing the viewport size, a zoom factor, and a vector to position the player ship within the viewport. All of these are in world coordinates, just like the planet surface and the rest of the game world.

A projection matrix is constructed using GLKMatrix4MakeOrtho, the Apple-provided equivalent of glOrtho, which isn’t part of the OpenGL ES2 API. The player ship will always be rendered at a fixed location: the viewport center plus the player translation vector. Everything else is translated by the inverse of the player position relative to the screen center:

float aspect = (float) view.drawableHeight / (float) view.drawableWidth;

float left = -(_viewport.width / 2.0) * _zoom;
float right = -left;
float bottom = -(_viewport.height / 2.0) * _zoom * aspect;
float top = -bottom;

GLKMatrix4 ortho = GLKMatrix4MakeOrtho(left, right, bottom, top, 0.01f, 2.0f);

// Calculate player position in eye coordinates and inverse player translation. 
// The player entity is always rendered at the viewport center plus a fixed translation 
// defined by the playerTranslation property. The inverse player translation vector
// basically takes world-coordinates to eye-coordinates.
CGPoint viewport_center = CGPointMake(left + (right - left) / 2.0f, bottom + (top - bottom) / 2.0);
CGPoint player_position = CGPointMake(viewport_center.x + _playerTranslation.dx, viewport_center.y + _playerTranslation.dy);
CGVector inverse_player_translation = CGVectorMake(player_position.x - player.entity.position.x, player_position.y - player.entity.position.y);

// ...

for (K14EntitySnapshot *entity in planet.entitySnapshots.allValues)
{
  // Set entity model-view matrix based on its position. The player entity is always rendered at the
  // center of the viewport, all other entities are translated relative to the player position
  float tx = (entity.id == player.entity.id ? player_position.x : entity.position.x + inverse_player_translation.dx);
  float ty = (entity.id == player.entity.id ? player_position.y : entity.position.y + inverse_player_translation.dy);

  GLKMatrix4 translate =  GLKMatrix4MakeTranslation(tx, ty, -1.0f);
  GLKMatrix4 rotate = GLKMatrix4MakeZRotation(entity.angle);
  
  GLKMatrix4 model_view = GLKMatrix4Multiply(translate, rotate);
  
  glUniformMatrix4fv([_debugPolyShader getUniform:@"u_model_view"], 1, GL_FALSE, model_view.m);

  // ...
}

A slightly cleaner way to implement the world-to-eye coordinate transformation would have been to separate the object-to-world and world-to-eye space transformations into matrices and concatenate them into the modelview (object-to-eye space) matrix, instead of explicitly applying a translation to all world coordinates. For a more complex camera system I would definitely recommend to do so, this works for our simple setup though.

One minor additional detail that deserves mention because it will come up again later, is that I added a class K14Player and accompanying K14PlayerSnapshot. These hold only a reference to the player entity (or its snapshot), but will in the future also get properties for the player fuel level, number of lives, etc. I needed them already though, to be able to distinguish between the player entity and everything else when rendering, so they could be translated differently. Some hacky solution such as looking a the entity class name could have worked as well, but a class representing player state would have been needed in the future anyway. Here’s an updated class diagram:

Just for laughs I also hacked in ghetto infinite-wraparound of the game world, by simply extending the planet surface one screen width at both sides and warping the player position whenever it crosses the midpoint of these extended surface edges. This obviously needs more thought and a cleaner implementation, for example to ensure other entities (like projectiles) also correctly wraparound and collide with other stuff at the other side of the ‘warp coordinate’. It looks quite serviceable already though, here’s a video:

Development scoreboard

Implementing the camera system took about 4 hours, which could have been a lot less if I wouldn’t get distracted so easily all the time ;-). This makes a total of about 23 hours of development time. SLOC count now sits at 568, an increase of only 30, most of which isn’t even in the camera code itself, but in the K14Player and K14PlayerSnapshot classes.