Simple Animation

The following sections explain how to create a simple animation system on the Xbox One dev kit:

What is Animation?

Animation is the process of rapidly cycling through individual still frames to create the appearance of motion. In computer graphics, animation usually applies to the motion of a single object. The spinning wheels of a moving car, or motions of a living creature are examples of graphics animation.

Animation in video games is achieved by swapping between different sets of vertices with similar positions and characteristics so that the object appears to be animated. This is different from moving an object around in the game world, which is translation, not animation. Typically a single game object will have several different animations. (walking, running, jumping, crouching)

Timing

Time keeping is essential for making sure an animation remains in sync and updates properly. Poor timing can make an animation appear too fast or slow, or the animation can appear jittery/laggy.

Incremental Animation Timing

Incremental timing works by tracking how long an individual frame has been displayed. Once the frame has been onsceen for a set duration, reset the timer and switch to the next frame of animation. Repeat until the animation is complete.

Define the number of frames that the animation has, and the duration for each frame to be displayed. Use a timer to count how much time has elapsed. When the timer exceeds the individual frame time, increment to the next frame and reset the timer. After the final frame has been displayed for long enough, the animation is over. (Restart animation, start new animation, destroy, etc)

C++

void Game::SetFrameIncremental()
{
  // Wait until the current frame has been displayed long enough
  if( m_AnimationTimer > m_PerFrameTime )
  {
    // Increment frame and reset animation timer
    m_AnimationTimer = 0.0f;
    ++m_CurrentFrameIndex;

    // Reset to initial frame if we have reached the end of the animation
    if( m_CurrentFrameIndex >= m_NumFrames )
    {
      m_CurrentFrameIndex = 0;
    }
  }
}  

Dynamic Animation Timing

Dynamic timing works by taking the number of frames and total duration of the animation, and using them to calculate which frame should currently be displayed based on elapsed time.

Define the time duration of the entire animation, as well as how many frames the animation has. Use a timer to track how much time has elapsed. Calculate the duration of a single frame by dividing AnimationDuration by TotalFrameCount. Determine which frame should currently be displayed by dividing the AnimationTimer by FrameDuration, and flooring the result.

C++

void Game::SetFrameDynamic()
{
  if( m_AnimationTimer > ( m_PerFrameTime * m_NumFrames ) )
  {
    // The animation is complete. Reset timer and frame index
    m_AnimationTimer = 0.0f;
    m_CurrentFrameIndex = 0;
    return;
  }
  // Use simple arithmetic to calculate which frame to display
  float CurrFrame = m_AnimationTimer / m_PerFrameTime;
  m_CurrentFrameIndex = (int)(floor( CurrFrame ));
}  

Animation Methods

3D models are defined by two sets of data: the vertex buffer, and the index buffer. You can animate by storing each frame as a separate vertex buffer and cycle through them, or you can animate by having one large vertex buffer and cycle through different index buffers. As a third option you can cycle through multiple vertex and index buffers, although this method is significantly more complicated and requires the most overhead.

Vertex-Based Animation

Use a single index buffer, and cycle through multiple vertex buffers to animate the object. Each vertex buffer must have the same number of elements arranged in the same order so that the index buffer can display them correctly.

Pros:

Cons:

Creating the Vertex Arrays: Create an array of vertices and populate the array with data. Arrange the data into equally sized sub-arrays, each sub-array should contain every vertex necessary to render a single frame of animation. The order of vertices must be identical for every frame, or the index array will not be able to display them properly. After all of the data has been initialized, remember to create a D3D11_BUFFER_DESC and a D3D11_SUBRESOURCE_DATA structure. Create the vertex buffer with a call to ID3D11Device::CreateBuffer()

Note In most real-world applications, the vertex/index data will be initialized from a file, not hardcoded as it is here. These vertices are hardcoded purely for demonstrative purposes.

C++

// Create a vertex array to contain the animation vertices. Arrange the vertices into frames, keeping the order of vertices intact.
SimpleVertex vertices[] =
{
  // Frame 0
  { XMFLOAT4( -1.0f, 1.0f, -1.0f, 1.0f ), XMFLOAT4( 0.0f, 0.0f, 1.0f, 1.0f ) },
  { XMFLOAT4( 1.0f, 1.0f, -1.0f, 1.0f ), XMFLOAT4( 0.0f, 1.0f, 0.0f, 1.0f ) },
  { XMFLOAT4( 1.0f, 1.0f, 1.0f, 1.0f ), XMFLOAT4( 0.0f, 1.0f, 1.0f, 1.0f ) },
  { XMFLOAT4( -1.0f, 1.0f, 1.0f, 1.0f ), XMFLOAT4( 1.0f, 0.0f, 0.0f, 1.0f ) },
  { XMFLOAT4( -1.0f, -1.0f, -1.0f, 1.0f ), XMFLOAT4( 1.0f, 0.0f, 1.0f, 1.0f ) },
  { XMFLOAT4( 1.0f, -1.0f, -1.0f, 1.0f ), XMFLOAT4( 1.0f, 1.0f, 0.0f, 1.0f ) },
  { XMFLOAT4( 1.0f, -1.0f, 1.0f, 1.0f ), XMFLOAT4( 1.0f, 1.0f, 1.0f, 1.0f ) },
  { XMFLOAT4( -1.0f, -1.0f, 1.0f, 1.0f ), XMFLOAT4( 0.0f, 0.0f, 0.0f, 1.0f ) },
  // Frame1
  { XMFLOAT4( -0.95f, 1.0f, -0.95f, 1.0f ), XMFLOAT4( 0.0f, 0.0f, 1.0f, 1.0f ) },
  { XMFLOAT4( 0.95f, 1.0f, -0.95f, 1.0f ), XMFLOAT4( 0.0f, 1.0f, 0.0f, 1.0f ) },
  { XMFLOAT4( 0.95f, 1.0f, 0.95f, 1.0f ), XMFLOAT4( 0.0f, 1.0f, 1.0f, 1.0f ) },
  { XMFLOAT4( -0.95f, 1.0f, 0.95f, 1.0f ), XMFLOAT4( 1.0f, 0.0f, 0.0f, 1.0f ) },
  { XMFLOAT4( -1.0f, -1.0f, -1.0f, 1.0f ), XMFLOAT4( 1.0f, 0.0f, 1.0f, 1.0f ) },
  { XMFLOAT4( 1.0f, -1.0f, -1.0f, 1.0f ), XMFLOAT4( 1.0f, 1.0f, 0.0f, 1.0f ) },
  { XMFLOAT4( 1.0f, -1.0f, 1.0f, 1.0f ), XMFLOAT4( 1.0f, 1.0f, 1.0f, 1.0f ) },
  { XMFLOAT4( -1.0f, -1.0f, 1.0f, 1.0f ), XMFLOAT4( 0.0f, 0.0f, 0.0f, 1.0f ) },
  // Additional frames...  

Creating the Index Array: If the vertex buffers are properly arranged, one index array is enough to render the entire animation. Just like the vertex buffer, you must create a D3D11_BUFFER_DESC and a D3D11_SUBRESOURCE_DATA structure to describe the array. Finally, create the index buffer with a call to ID3D11Device::CreateBuffer()

C++

//Create index buffer for a cube
WORD indices[] =
{
  3,1,0,  2,1,3,  
  0,5,4,  1,5,0,
  3,4,7,  0,4,3,
  1,6,5,  2,6,1,
  2,7,6,  3,7,2,
  6,4,5,  7,4,6,
};  

Rendering: Use ID3D11DeviceContext::DrawIndexed() to render the model onscreen. Use a frame index to determine which frame to render.

C++

m_d3dContext->DrawIndexed( m_IndicesPerFrame, 0, m_CurrentFrameIndex * m_VertsPerFrame );  

Index-Based Animation

Use a single, large vertex array that contains every vertex for all frames of animation. Cycle through a sequence of index buffers, each one containing the required sequence of vertices to render a single frame.

Pros:

Cons:

Creating the Vertex Array: Create an array of vertices and populate the array with data. Pay special attention to the arrangement of vertices, the index arrays must be able to reference them effectively. After all of the data has been initialized, remember to create a D3D11_BUFFER_DESC and a D3D11_SUBRESOURCE_DATA structure. Create the vertex buffer with a call to ID3D11Device::CreateBuffer()

C++

// Create a vertex array to contain the animation vertices. Arrange the vertices into frames, keeping the order of vertices intact.
SimpleVertex vertices[] =
{
  // Static vertices: These four vertices will be re-used in every frame of the animation
  { XMFLOAT4( -1.0f, -1.0f, -1.0f, 1.0f ), XMFLOAT4( 1.0f, 0.0f, 1.0f, 1.0f ) },
  { XMFLOAT4( 1.0f, -1.0f, -1.0f, 1.0f ), XMFLOAT4( 1.0f, 1.0f, 0.0f, 1.0f ) },
  { XMFLOAT4( 1.0f, -1.0f, 1.0f, 1.0f ), XMFLOAT4( 1.0f, 1.0f, 1.0f, 1.0f ) },
  { XMFLOAT4( -1.0f, -1.0f, 1.0f, 1.0f ), XMFLOAT4( 0.0f, 0.0f, 0.0f, 1.0f ) },
  // Frame 0
  { XMFLOAT4( -1.0f, 1.0f, -1.0f, 1.0f ), XMFLOAT4( 0.0f, 0.0f, 1.0f, 1.0f ) },
  { XMFLOAT4( 1.0f, 1.0f, -1.0f, 1.0f ), XMFLOAT4( 0.0f, 1.0f, 0.0f, 1.0f ) },
  { XMFLOAT4( 1.0f, 1.0f, 1.0f, 1.0f ), XMFLOAT4( 0.0f, 1.0f, 1.0f, 1.0f ) },
  { XMFLOAT4( -1.0f, 1.0f, 1.0f, 1.0f ), XMFLOAT4( 1.0f, 0.0f, 0.0f, 1.0f ) },
  // Frame1
  { XMFLOAT4( -0.95f, 1.0f, -0.95f, 1.0f ), XMFLOAT4( 0.0f, 0.0f, 1.0f, 1.0f ) },
  { XMFLOAT4( 0.95f, 1.0f, -0.95f, 1.0f ), XMFLOAT4( 0.0f, 1.0f, 0.0f, 1.0f ) },
  { XMFLOAT4( 0.95f, 1.0f, 0.95f, 1.0f ), XMFLOAT4( 0.0f, 1.0f, 1.0f, 1.0f ) },
  { XMFLOAT4( -0.95f, 1.0f, 0.95f, 1.0f ), XMFLOAT4( 1.0f, 0.0f, 0.0f, 1.0f ) },
  // Additional frame data...  

Creating the Index Array: Create an array of indices, and populate the array with data. Arrange the data into sub-arrays, each sub-array contains all of the indices to render an individual frame of animation. This method has the additional advantage of allowing you to use a unique amount of indices for each animation frame.

C++

//Create array of index buffers
 WORD indices[] =
{
  // First Frame: each row of indices represents 1 face of a cube
  0,2,3,  0,1,2,
  3,2,6,  3,6,7,
  0,3,7,  0,7,4,
  2,5,6,  2,1,5,
  1,4,5,  1,0,4,
  7,6,5,  7,5,4,
  // Second Frame:
  0,2,3,  0,1,2,
  3,2,10,  3,10,11,
  0,3,11,  0,11,8,
  2,9,10,  2,1,9,
  1,8,9,  1,0,8,
  11,10,9,  11,9,8,
  // Additional frames...  

Rendering: Use ID3D11DeviceContext::DrawIndexed() to render the model onscreen. Use a frame index to determine which frame to render. Just like the vertex buffer, you must create a D3D11_BUFFER_DESC and a D3D11_SUBRESOURCE_DATA structure to describe the array. Finally, create the index buffer with a call to ID3D11Device::CreateBuffer()

If the index count varies for each frame of the animation, then the index count for each frame must be stored separately. A simple way to accomplish this is with an integer array. Each element of the array corresponds to a specific animation frame, and stores the index count for that frame. Increment through the array as you increment through animation frames.

C++

// NOTE: The implementation shown here assumes that every frame has the same number of indices.
m_d3dContext->DrawIndexed( m_IndicesPerFrame, m_CurrentFrameIndex * m_IndicesPerFrame, 0);  

Note Evaluate your engine to determine if Index-based or Vertex-based animation works best for you. If your vertex class is fairly small, then Vertex-based animation may be more space efficient than Index-based animation. For example, a simple cube requires 8 vertices and 36 indices to render. If your vertex struct has a small enough memory footprint, then the 8 vertices will occupy less space than the 36 indices.

See also

DirectX