Creating In−Game Cinematic Sequences
Using time−based animation is crucial to achieving smooth playback, but what
good could using time−based movement possibly do? Sure, moving a few objects
around a set path is neat, but is that all you can do? The answer is a
resounding no! There's much more you can do with time−based movement, including
creating in−game cinematic sequences, like those from games such as Silicon
Knights' Eternal Darkness: Sanity's Requiem In Eternal Darkness, the player is
treated to animation sequences that play out using the game's 3D engine.
To use a cinematic camera, you can rely on the techniques you read about
earlier in this chapter, and you can use the pre−calculated animation sequences,
it's only a matter of plotting out the path that your camera will follow over
time. Mix that with a complete
pre−calculated animation, and you've got yourself a complete in−game cinematic
engine!
Rather than reiterate what you already saw in this chapter, I'll leave it up
to you to check out the Cinematic demo, which shows a small cinematic sequence.
In a nutshell, the demo merely loads a series of keys (using the .X path parser
class) that represent the paths the camera follows. In every frame, the position
of the camera is calculated using the keys, and the viewport is oriented. Then
the pre−calculated animation is updated and the entire scene is rendered.
As shown in Figure 2.12, you get to see how complex routes can be applied to
cameras in order to traverse a 3D scene in real time. This technique of moving a
camera is perfect to use for an in−game cinematic system.
Figure 2.12: The cinematic camera demo adds a moving camera to the Route
demo.
Route.x:
xof 0303txt 0032
template Path {
<F8569BED-53B6-4923-AF0B-59A09271D556>
DWORD Type; // 0=straight, 1=curved
Vector Start;
Vector Point1;
Vector Point2;
Vector End;
}
template Route {
<18AA1C92-16AB-47a3-B002-6178F9D2D12F>
DWORD NumPaths;
array Path Paths[NumPaths];
}
Route Robot {
5; // 5 paths
0; // Straight path type
0.0, 10.0, 0.0; // Start
0.0, 0.0, 0.0; // Unused
0.0, 0.0, 0.0; // Unused
0.0, 10.0, 150.0;, // End
1; // Curved path type
0.0, 10.0, 150.0; // Start
75.0, 10.0, 150.0; // Point1
150.0, 10.0, 75.0; // Point2
150.0, 10.0, 0.0;, // End
1; // Curved path type
150.0, 10.0, 0.0; // Start
150.0, 10.0, -75.0; // Point1
75.0, 10.0, -150.0; // Point2
0.0, 10.0, -150.0;, // End
0; // Straight path type
0.0, 10.0, -150.0; // Start
0.0, 0.0, 0.0; // Unused
0.0, 0.0, 0.0; // Unused
-150.0, 10.0, 75.0;, // End
0; // Straight path type
-150.0, 10.0, 75.0; // Start
0.0, 10.0, 0.0; // Unused
0.0, 10.0, 0.0; // Unused
0.0, 10.0, 0.0;; // End
}
Route Camera {
4; // 4 paths
1; // Curved path type
0.0, 80.0, 300.0; // Start
150.0, 80.0, 300.0; // Point1
300.0, 80.0, 150.0; // Point2
300.0, 80.0, 0.0;, // End
1; // Curved path type
300.0, 80.0, 0.0; // Start
300.0, 80.0, -150.0; // Point1
151.0, 80.0, -300.0; // Point2
0.0, 80.0, -300.0;, // End
1; // Curved path type
0.0, 80.0, -300.0; // Start
-150.0, 80.0, -300.0; // Point1
-300.0, 80.0, -150.0; // Point2
-300.0, 80.0, 0.0;, // End
1; // Curved path type
-300.0, 80.0, 0.0; // Start
-300.0, 80.0, 150.0; // Point1
-150.0, 80.0, 300.0; // Point2
0.0, 80.0, 300.0;; // End
}
Route Target {
5; // 5 paths
0; // Straight path type
0.0, 10.0, 0.0; // Start
0.0, 0.0, 0.0; // Unused
0.0, 0.0, 0.0; // Unused
0.0, 10.0, 150.0;, // End
1; // Curved path type
0.0, 10.0, 150.0; // Start
75.0, 10.0, 150.0; // Point1
150.0, 10.0, 75.0; // Point2
150.0, 10.0, 0.0;, // End
1; // Curved path type
150.0, 10.0, 0.0; // Start
150.0, 10.0, -75.0; // Point1
75.0, 10.0, -150.0; // Point2
0.0, 10.0, -150.0;, // End
0; // Straight path type
0.0, 10.0, -150.0; // Start
0.0, 0.0, 0.0; // Unused
0.0, 0.0, 0.0; // Unused
-150.0, 10.0, 75.0;, // End
0; // Straight path type
-150.0, 10.0, 75.0; // Start
0.0, 10.0, 0.0; // Unused
0.0, 10.0, 0.0; // Unused
0.0, 10.0, 0.0;; // End
}
WinMain.cpp:
#include <windows.h>
#include <d3d9.h>
#include <d3dx9.h>
#include "Direct3D.h"
#include "route.h"
struct sBackdropVertex
{
float x, y, z, rhw;
float u, v;
};
#define BACKDROP_FVF (D3DFVF_XYZRHW | D3DFVF_TEX1)
////////////////////////////////////////////////////////////////////////////////////////////////
IDirect3D9* g_d3d;
IDirect3DDevice9* g_device;
IDirect3DVertexBuffer9* g_backdrop_vb;
IDirect3DTexture9* g_backdrop_texture;
D3DXMESHCONTAINER_EX* g_robot_mesh_container;
D3DXMESHCONTAINER_EX* g_ground_mesh_container;
cXRouteParser g_route_parser;
D3DXVECTOR3 g_robot_pos, g_robot_last_pos;
const char CLASS_NAME[] = "CinematicClass";
const char CAPTION[] = "Cinematic Demo";
////////////////////////////////////////////////////////////////////////////////////////////////
LRESULT FAR PASCAL window_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
bool do_init(HWND hwnd);
void do_shutdown();
void do_frame();
//////////////////////////////////////////////////////////////////////////////////////////////
int PASCAL WinMain(HINSTANCE inst, HINSTANCE, LPSTR, int cmd_show)
{
CoInitialize(NULL); // Initialize the COM system
// Create the window class here and register it
WNDCLASSEX win_class;
win_class.cbSize = sizeof(win_class);
win_class.style = CS_CLASSDC;
win_class.lpfnWndProc = window_proc;
win_class.cbClsExtra = 0;
win_class.cbWndExtra = 0;
win_class.hInstance = inst;
win_class.hIcon = LoadIcon(NULL, IDI_APPLICATION);
win_class.hCursor = LoadCursor(NULL, IDC_ARROW);
win_class.hbrBackground = NULL;
win_class.lpszMenuName = NULL;
win_class.lpszClassName = CLASS_NAME;
win_class.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if(!RegisterClassEx(&win_class))
return -1;
// Create the main window
HWND hwnd = CreateWindow(CLASS_NAME, CAPTION, WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
0, 0, 640, 480, NULL, NULL, inst, NULL);
if(hwnd == NULL)
return -1;
ShowWindow(hwnd, cmd_show);
UpdateWindow(hwnd);
// Call init function and enter message pump
if(do_init(hwnd))
{
MSG msg;
ZeroMemory(&msg, sizeof(MSG));
// Start message pump, waiting for user to exit
while(msg.message != WM_QUIT)
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
do_frame(); // Render a single frame
}
}
do_shutdown();
UnregisterClass(CLASS_NAME, inst);
CoUninitialize();
return 0;
}
LRESULT FAR PASCAL window_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// Only handle window destruction messages
switch(msg)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_KEYDOWN:
if(wParam == VK_ESCAPE)
DestroyWindow(hwnd);
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
bool do_init(HWND hwnd)
{
init_d3d(&g_d3d, &g_device, hwnd, false, false);
if(FAILED(load_mesh(&g_robot_mesh_container, g_device, "..\\Data\\robot.x", "..\\Data\\", 0, 0)))
return false;
if(FAILED(load_mesh(&g_ground_mesh_container, g_device, "..\\Data\\ground.x", "..\\Data\\", 0, 0)))
return false;
// create the backdrop
sBackdropVertex backdrop_verts[4] =
{
{ 0.0f, 0.0, 1.0, 1.0f, 0.0f, 0.0f },
{ 640.0f, 0.0, 1.0, 1.0f, 1.0f, 0.0f },
{ 0.0f, 480.0, 1.0, 1.0f, 0.0f, 1.0f },
{ 640.0f, 480.0, 1.0, 1.0f, 1.0f, 1.0f }
};
g_device->CreateVertexBuffer(sizeof(backdrop_verts), D3DUSAGE_WRITEONLY, BACKDROP_FVF, D3DPOOL_DEFAULT,
&g_backdrop_vb, NULL);
char* ptr;
g_backdrop_vb->Lock(0, 0, (void**)&ptr, 0);
memcpy(ptr, backdrop_verts, sizeof(backdrop_verts));
g_backdrop_vb->Unlock();
D3DXCreateTextureFromFile(g_device, "..\\Data\\Backdrop.bmp", &g_backdrop_texture);
// setup a directional light
D3DLIGHT9 light;
ZeroMemory(&light, sizeof(D3DLIGHT9));
light.Type = D3DLIGHT_DIRECTIONAL;
light.Diffuse.r = light.Diffuse.g = light.Diffuse.b = light.Diffuse.a = 1.0f;
light.Direction = D3DXVECTOR3(0.0f, -0.5f, 0.5f);
g_device->SetLight(0, &light);
g_device->LightEnable(0, TRUE);
if(! g_route_parser.load("..\\Data\\Route.x"))
return false;
return true;
}
void do_shutdown()
{
// free mesh data
delete g_robot_mesh_container; g_robot_mesh_container = NULL;
delete g_ground_mesh_container; g_ground_mesh_container = NULL;
release_com(g_backdrop_vb);
release_com(g_backdrop_texture);
// release D3D objects
release_com(g_device);
release_com(g_d3d);
}
void do_frame()
{
static DWORD start_time = timeGetTime();
DWORD curr_time = timeGetTime();
// calculate the position in which to place the robot along the path based on time and robot_route_length of route.
float robot_route_length = g_route_parser.get_length("Robot");
DWORD robot_dist = (curr_time - start_time) / 10;
robot_dist %= ((DWORD)robot_route_length + 1);
// get the camera's position
float camera_route_length = g_route_parser.get_length("Camera");
DWORD camera_dist = (curr_time - start_time) / 20;
camera_dist %= ((DWORD) camera_route_length + 1);
// get the target's position
float target_route_length = g_route_parser.get_length("Target");
DWORD target_dist = (curr_time - start_time) / 10;
target_dist %= ((DWORD) target_route_length + 1);
// update the positions of the robot
g_robot_last_pos = g_robot_pos;
g_route_parser.locate("Robot", (float)robot_dist, &g_robot_pos);
// get camera and target position
D3DXVECTOR3 camera_pos, target_pos;
g_route_parser.locate("Camera", (float)camera_dist, &camera_pos);
g_route_parser.locate("Target", (float)target_dist, &target_pos);
// set a view transformation matrix
D3DXMATRIX mat_view;
D3DXVECTOR3 up(0.0f, 1.0f, 0.0f);
D3DXMatrixLookAtLH(&mat_view, &camera_pos, &target_pos, &up);
g_device->SetTransform(D3DTS_VIEW, &mat_view);
// clear the device and start drawing the scene
g_device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(0, 0, 0, 255), 1.0f, 0);
g_device->BeginScene();
// draw the backdrop
g_device->SetFVF(BACKDROP_FVF);
g_device->SetStreamSource(0, g_backdrop_vb, 0, sizeof(sBackdropVertex));
g_device->SetTexture(0, g_backdrop_texture);
g_device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
g_device->SetRenderState(D3DRS_LIGHTING, TRUE);
// draw the ground mesh
D3DXMATRIX mat_world;
D3DXMatrixIdentity(&mat_world);
g_device->SetTransform(D3DTS_WORLD, &mat_world);
draw_mesh(g_ground_mesh_container);
// calculate the rotation of the robots based on last known position, and update last position once done.
D3DXVECTOR3 diff = g_robot_pos - g_robot_last_pos;
float rot_x = atan2(diff.y, diff.z);
float rot_y = -atan2(diff.z, diff.x);
// rotate the robot to point in direction of movement
D3DXMatrixRotationYawPitchRoll(&mat_world, rot_y, rot_x, 0.0f);
// position the robot by setting the coordinates directly in the world transformation matrix
mat_world._41 = g_robot_pos.x;
mat_world._42 = g_robot_pos.y;
mat_world._43 = g_robot_pos.z;
g_device->SetTransform(D3DTS_WORLD, &mat_world);
draw_mesh(g_robot_mesh_container);
g_device->SetRenderState(D3DRS_LIGHTING, FALSE);
g_device->EndScene();
g_device->Present(NULL, NULL, NULL, NULL);
}
download source file