Gamasutra - Features - "The Guerrilla Guide to Game Code"
It's free to join Gamasutra!|Have a question? Want to know who runs this site? Here you go.|Targeting the game development market with your product or service? Get info on advertising here.||For altering your contact information or changing email subscription preferences.
Registered members can log in here.Back to the home page.

Search articles, jobs, buyers guide, and more.

By Jorrit Rouwé
[Author's Bio]

Gamasutra
April 14, 2005

Introduction

The Complete Framework

Printer Friendly Version
   

Change Login/Pwd
Post A Job
Post A Project
Post Resume
Post An Event
Post A Contractor
Post A Product
Write An Article
Get In Art Gallery
Submit News

 


 


Latest Letters to the Editor:
Perpetual Layoffs by Alexander Brandon [09.21.2007]

Casual friendliness in MMO's by Colby Poulson [09.20.2007]

Scrum deals and 'What is Scrum?' by Tom Plunket [08.29.2007]


[Submit Letter]

[View All...]
  



Upcoming Events:
Video Game Expo (VGXPO)
Philadelphia, United States
11.21.08

DIG London Game Conference
London, Canada
11.27.08

5th Australasian Conference on Interactive Entertainment
Brisbane, Australia
12.03.08

IEEE Symposium on Computational Intelligence and Games
Perth, Australia
12.15.08

2K Bot Prize
Perth, Australia
12.15.08

[Submit Event]
[View All...]

 


[Enter Forums...]

Note: Discussion forums for Gamasutra are hosted by the IGDA, which is free to join.
 


Features

The Guerrilla Guide to Game Code

The Complete Framework

In Figure 1 you can see the complete framework used in SSN67.


Figure 1: Class diagram of the most important classes and their relations. The classes needed for a Humanoid are provided as an example.

The EntityManager is a container for all Entities. Entities that participate in the game must be added to the EntityManager. The EntityManager updates Entities at a fixed frequency (see next section) and facilitates searching.

We allow Entities to be updated by other Entities in special cases where the update order matters. When a player mounts a mounted gun it is essential that the player is updated after the mounted gun or else the system will jitter. In this case the mounted gun can set a flag on the player so that it will not receive updates from the EntityManager and it can update the player itself.

The RepresentationManager keeps all EntityRepresentations in a spatial hierarchy suitable for quick visibility determination. It gets a notification from the EntityManager as soon as an Entity is added or removed and will create or destroy the corresponding EntityRepresentation.

When drawing a frame the RepresentationManager draws all GameViews. A GameView draws the world for one player. It keeps track of the active camera for a player and uses the spatial hierarchy in the RepresentationManager to draw all visible EntityRepresentations. It also draws all non-entities like the static world. After the scene is drawn it draws the HUD as a 2D overlay. SSN67 could be played split screen multiplayer but this feature didn't make it in the final game due to time constraints.

Fixed Frequency

The complexity of the scenes in SSN67 didn't allow for a constant 60 frames per second so our target was 30 frames per second on NTSC and 25 on PAL consoles. We chose a constant 15Hz update frequency for all Entities. Using a fixed frequency update ensures that all algorithms run exactly the same on all platforms.

A 15Hz update normally requires an update of all Entities every other frame. The EntityManager could have been used to perform load balancing by updating half of the Entities one frame and the other half the other frame. Instead, we opted for a simpler solution: Updated the high level AI system in frames that we do not need to update the Entities. The high level AI system calculates behaviors, does path planning and a lot of ray casting to determine suitable attack / defend positions for the AI.

The balance between the AI and Entities wasn't exactly ideal in SSN67 as the AI usually needed much less time. This led to a stuttering frame rate when playing the game. To solve this problem we did not wait for the VSYNC interrupt in a loop but instead we would use this time to start updating the entities or AI for the next frame.

If the frame rate drops below 15 frames per second (which can occasionally happen) we slow down the game time so that it increases with max 1/15 th of a second per frame. Not doing this means that you have to do two update cycles in a single frame, which virtually guarantees that this frame is also going to take more than 1/15 th of a second to calculate. The profiler showed that doing multiple updates in a single frame can pull the frame rate down for a couple of seconds afterwards.

Because we update the Entities at a lower rate than the frame rate there is a need for interpolation to make all movements smooth. At first we were using extrapolation to try to make Entity movement smooth but this led to very bad results. The used approach is to remember the previous Entity state and the current state and blend from the previous state to the current state in 1/15 th of a second. This means that there is a constant delay of 1/15 th of a second between what the player does and what the player sees. This time is small and constant so that people don't notice it.

In SSN67 most Entities were using a skinned model. The following pseudo code shows how our system was set up in this case. By choosing a good base or helper class the actual interpolation code only needs to be written once and most Entities do not need to be aware of interpolation.

// Function called by the EntityManager to store the previous state
// of the Entity for interpolation
void ExampleEntity::PreUpdate(float FixedFrequencyTime)
{
  // Store the previous world matrix
  PreviousWorldMatrix = CurrentWorldMatrix

  // Store the previous array of bone matrices
  PreviousBoneMatrices = CurrentBoneMatrices

  // Notify our EntityRepresentation
  EntityRepresentation->PreUpdate(FixedFrequencyTime)

  // Reset changed flag for next frame
  Changed = FALSE
}

// Function called by the EntityManager after PreUpdate to
// update the state of the Entity
void ExampleEntity::Update(float FixedFrequencyTime)
{
  // Update game state (animations, position, etc.)
  …

  // The EnityRepresentation needs to know if anything changed this frame
  // that requires interpolation
  if (CurrentWorldMatrix or CurrentBoneMatrices changed)
  Changed = TRUE
}

// Function called by Entity::PreUpdate to store the previous state
// of the EntityRepresentation for interpolation
void ExampleEntityRepresentation::PreUpdate(float FixedFrequencyTime)
{
  // This flag indicates of for the next 1/15 th second the   EntityRepresentation needs to interpolate
  MustInterpolate = Entity->Changed

  // Note the current time
  LastChangedTime = FixedFrequencyTime
}

// Function called by the Representation manager to update the state
// of the EntityRepresentation
void ExampleEntityRepresentation::Update(float RealTime)
{
  if (MustInterpolate)
  {
    // Calculate the interpolation fractor, the factor will be in the     range [0, 1]
    float Factor = Min((RealTime – LastChangedTime) /                    FixedFrequencyTimeStep, 1)

    // Use spherical linear interpolation for the world matrix
    WorldMatrix = SLERP(Entity->PreviousWorldMatrix,     Entity->CurrentWorldMatrix, Factor)

    // Use linear blending for the array of bone matrices
    BoneMatrices = (1 – Factor) * Entity->PreviousBoneMatrices + Factor *     Entity->CurrentBoneMatrices
  }
  else
  {
    // We're not interpolating so we can simply use the current Entity     // state
    WorldMatrix = Entity->CurrentWorldMatrix
    BoneMatrices = Entity->CurrentBoneMatrices
  }
}

In Figure 2 you can see how these functions are called.


Figure 2: Sequence diagram showing the flow of update and drawing. The PreUpdate function stores the previous state of the Entity / EntityRepresentation for interpolation. The Entity::Update function updates the actual game state. This is all done at 15 Hz. The RepresentationManager updates and draws the representations at full frame rate.

We used spherical linear interpolation (SLERP) for the world matrix and linear interpolation for the bone matrices. Linear interpolation doesn't preserve scale but is a lot cheaper. As long as animations are fairly smooth you won't see the difference.

There are a few simple optimizations to be made to this code to make it practical in real situations. First of all, keeping two sets of world and bone matrices and a reference to the active one saves copying the matrices. Secondly it is possible to do a lazy update so that interpolation is only done when the EntityRepresentation is visible.

Another nice optimization that we used is to update the bounding box for visibility determination at 15Hz as well. The bounding box is the union of the bounding box for the previous state and the current state. In general this makes the bounding box only a little bit bigger but updating the spatial hierarchy only has to be done at 15Hz.

We use the interpolation system also for animation LOD. The Entity doesn't update its animations at 15Hz but at (15 / N)Hz where N is an integer. When the Entity is close to the camera it will use N = 1, when it gets further away N increases. The EntityRepresentation interpolates from its previous animation state to the current animation state in N Entity updates instead of in 1 Entity update. This requires a second interpolation factor to be calculated in the EntityRepresentation. The rest of the algorithm stays exactly the same so the LOD system practically comes for free. As long as the Entity is loosely coupled to its animations there will be no side effects.

Most common problems in SSN67 with the interpolation scheme were when objects had to be attached to a bone of another object. A gun, for example, would jitter in the hand of a soldier because there was a slight difference between the interpolation of the gun (an Entity) and the hand. We solved this problem by allowing EntityRepresentations to override their world matrix with the interpolated bone matrix from another EntityRepresentation.

Another common problem is when sudden changes in position or animation happen, for example, when a character needs to rotate 180 degrees in one frame. To solve these problems simply turn off interpolation for one update by setting PreviousWorldMatrix = CurrentWorldMatrix and PreviousBoneMatrices = CurrentBoneMatrices.

Conclusion

Using the Model-View-Controller mechanism has a lot of benefits when it comes to separating functionality into manageable blocks. The main drawback is that there will be some code and memory overhead to support an Entity and EntityRepresentation class for every game object.

The interpolation system doesn't come for free, but, looking at our profile sessions interpolation it was a lot cheaper than updating the whole system at the full frame rate. Also, interpolation sometimes leads to visual glitches that require some effort to fix them.

The Model-View-Controller mechanism fits nicely with other often used techniques like making a deterministic game. A deterministic game has a fixed set of input parameters (like controller input + random seed) and when given the same inputs it will reproduce the same results every time. In this case the code that needs to conform to this (Entity) is easily separated from the code that doesn't need to conform (EntityRepresentation). The Controller clearly defines the input parameters of the system.

Another thing that the Model-View-Controller architecture is really good at is online multiplayer. Because Entities maintain minimal state it is much easier to extract the state that has to be sent over the network. Experiments that we did after shipping SSN67 show that this is indeed the case.

It is our opinion that this design played no small part in allowing us to meet every single deadline and milestone on the SSN67 project. For our next project(s) we have started using the same system again.

Acknowledgements

I would like to thank Kasper J. Wessing, Remco Straatman, Frank Compagner, Robert Morcus, Stefan Lauwers and all the other coders at Guerrilla that helped create the SSN67 architecture and this article.

References

______________________________________________________

[back to] Introduction



join | contact us | advertise | write | my profile
news | features | companies | jobs | resumes | education | product guide | projects | store



Copyright © 2003 CMP Media LLC

privacy policy
| terms of service