Define the main game object
[This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation]
At this point, we've laid out the basic framework of the sample game, and we implemented a state machine that handles the high-level user and system behaviors. But we haven't examined the part that makes the game sample an actual game: the rules and mechanics, and how they're implemented! Now, we look at the details of the game sample's main object and how the rules it implements translate into interactions with the game world.
Objective
- To apply the basic development techniques when implementing the rules and mechanics of a simple Windows Store game using DirectX.
Note Complete sample code that corresponds to this tutorial is in the Windows Store Direct3D shooting game sample.
Considering the game's flow
In the Windows Store Direct3D shooting game sample, the majority of the game's basic structure is defined in these files:
- DirectXApp.cpp
- Simple3DGame.cpp
In Defining the game's Windows Store app framework, we reviewed the game framework defined in DirectXApp.cpp.
Simple3DGame.cpp provides the code for a class, Simple3DGame, which specifies the implementation of the game play itself. Earlier, we considered the treatment of the sample game as a Windows Store app. Now, we look at the code that makes it a game.
The complete code for Simple3DGame.h/.cpp is provided in Complete sample code for this section.
Let's take a look at the definition of the Simple3DGame class.
Defining the core game object
When the app singleton starts, the view provider's Initialize method creates an instance of the main game class, the Simple3DGame object. This object contains the methods that communicate changes in game state to the state machine defined in the app framework, or from the app to the game object itself. It also contains methods that return info for updating the game's overlay bitmap and heads-up display, and for updating the animations and physics (the dynamics) in the game. The code for obtaining the graphics device resources used by the game is found in GameRenderer.cpp, which we discuss next in Assembling the rendering framework.
The code for Simple3DGame looks like this:
ref class GameRenderer;
ref class Simple3DGame
{
internal:
Simple3DGame();
void Initialize(
_In_ MoveLookController^ controller,
_In_ GameRenderer^ renderer
);
void LoadGame();
concurrency::task<void> LoadLevelAsync();
void FinalizeLoadLevel();
void StartLevel();
void PauseGame();
void ContinueGame();
GameState RunGame();
void OnSuspending();
void OnResuming();
// ... global variable retrieval methods defined here ...
private:
void LoadState();
void SaveState();
void SaveHighScore();
void LoadHighScore();
void InitializeAmmo();
void UpdateDynamics();
// ...
// ... global variables defined here
// ...
};
First, let's review the internal methods defined on Simple3DGame.
Initialize. Sets the starting values of the global variables and initializes the game objects.
LoadGame. Initializes a new level and starts loading it.
LoadLevelAsync. Starts an async task (see the Parallel Patterns Library for more details) to initialize the level and then invoke an async task on the renderer to load the device specific level resources. This method runs in a separate thread; as a result, only ID3D11Device methods (as opposed to ID3D11DeviceContext methods) can be called from this thread. Any device context methods are called in the FinalizeLoadLevel method. For more info about device methods and device context methods in Direct3D 11, read Asynchronous programming (DirectX and C++).
FinalizeLoadLevel. Completes any work for level loading that needs to be done on the main thread. This includes any calls to Direct3D 11 device context (ID3D11DeviceContext) methods.
StartLevel. Starts the game play for a new level.
PauseGame. Pauses the game.
RunGame. Runs an iteration of the game loop. It's called from DirectXApp::Update one time every iteration of the game loop if the game state is Active.
OnSuspending and OnResuming. Suspends and resumes the game's audio, respectively.
And the private methods:
- LoadSavedState and SaveState. Loads and saves the current state of the game, respectively.
- SaveHighScore and LoadHighScore. Saves and loads the high score across games, respectively.
- InitializeAmmo. Resets the state of each sphere object used as ammunition back to its original state for the beginning of each round.
- UpdateDynamics. This is an important method, because it updates all the game objects based on canned animation routines, physics, and control input. This is the heart of the interactivity that defines the game. We talk about it more in the Updating the game section.
The other public methods are property getters that return game play and overlay specific information to the app framework for display.
Defining the game state variables
One function of the game object is to serve as a container for the data that defines a game session, level, or lifetime, depending on how you define your game at a high level. In this case, the game state data is for the lifetime of the game, initialized one time when a user launches the game.
Here's the complete set of definitions for the game object's state variables.
private:
MoveLookController^ m_controller;
GameRenderer^ m_renderer;
Camera^ m_camera;
Audio^ m_audioController;
std::vector<Sphere^> m_ammo;
uint32 m_ammoCount;
uint32 m_ammoNext;
HighScoreEntry m_topScore;
PersistentState^ m_savedState;
GameTimer^ m_timer;
bool m_gameActive;
bool m_levelActive;
int m_totalHits;
int m_totalShots;
float m_levelDuration;
float m_levelBonusTime;
float m_levelTimeRemaining;
std::vector<Level^> m_level;
uint32 m_levelCount;
uint32 m_currentLevel;
Sphere^ m_player;
std::vector<GameObject^> m_object; // object list for intersections
std::vector<GameObject^> m_renderObject; // all objects to be rendered
DirectX::XMFLOAT3 m_minBound;
DirectX::XMFLOAT3 m_maxBound;
At the top of the code example, there are four objects whose instances are updated as the game loop runs.
- The MoveLookController object. This object represents the player input. (For more info about the MoveLookController object, see Adding controls.)
- The GameRenderer object. This object represents the Direct3D 11 renderer derived from the DirectXBase class that handles all the device-specific objects and their rendering. (For more info, see Assembling the rendering pipeline).
- The Camera object. This object represents the player's first-person view of the game world. (For more info about the Camera object, see Assembling the rendering pipeline.)
- The Audio object. This object controls the audio playback for the game. (For more info about the Audio object, see Adding sound.)
The rest of the game variables contain the lists of the primitives and their respective in-game amounts, and game play specific data and constraints. Let's see how the sample configures these variables when the game is initialized.
Initializing and starting the game
When a player starts the game, the game object must initialize its state, create and add the overlay, set the variables that track the player's performance, and instantiate the objects that it will use to build the levels.
void Simple3DGame::Initialize(
_In_ MoveLookController^ controller,
_In_ GameRenderer^ renderer
)
{
// This method is expected to be called as an asynchronous task.
// Make sure that you don't call rendering methods on the
// m_renderer as this would result in the D3D Context being
// used in multiple threads, which is not allowed.
m_controller = controller;
m_renderer = renderer;
m_audioController = ref new Audio;
m_audioController->CreateDeviceIndependentResources();
m_ammo = std::vector<Sphere^>(GameConstants::MaxAmmo);
m_object = std::vector<GameObject^>();
m_renderObject = std::vector<GameObject^>();
m_level = std::vector<Level^>();
m_savedState = ref new PersistentState();
m_savedState->Initialize(ApplicationData::Current->LocalSettings->Values, "Game");
m_timer = ref new GameTimer();
// Create a sphere primitive to represent the player
// The sphere is used to handle collisions and constrain the player in the world
// It's not rendered so it's not added to the list of render objects.
m_player = ref new Sphere(XMFLOAT3(0.0f, -1.3f, 4.0f), 0.2f);
m_camera = ref new Camera;
m_camera->SetProjParams(XM_PI / 2, 1.0f, 0.01f, 100.0f);
m_camera->SetViewParams(
m_player->Position(), // Eye point in world coordinates.
XMFLOAT3 (0.0f, 0.7f, 0.0f), // Look at point in world coordinates.
XMFLOAT3 (0.0f, 1.0f, 0.0f) // The Up vector for the camera.
);
m_controller->Pitch(m_camera->Pitch());
m_controller->Yaw(m_camera->Yaw());
// Add the m_player object to the object list to do intersection calculations
m_object.push_back(m_player);
m_player->Active(true);
// Instantiate the world primitive. This object maintains the geometry and
// material properties of the walls, floor, and ceiling of the enclosing world.
// The TargetId is used to identify the world objects so that the right geometry
// and textures can be associated with them later after those resources have
// been created.
GameObject^ world = ref new GameObject();
world->TargetId(GameConstants::WorldFloorId);
world->Active(true);
m_renderObject.push_back(world);
world = ref new GameObject();
world->TargetId(GameConstants::WorldCeilingId);
world->Active(true);
m_renderObject.push_back(world);
world = ref new GameObject();
world->TargetId(GameConstants::WorldWallsId);
world->Active(true);
m_renderObject.push_back(world);
// Min and max Bound are defining the world space of the game.
// All camera motion and dynamics are confined to this space.
m_minBound = XMFLOAT3(-4.0f, -3.0f, -6.0f);
m_maxBound = XMFLOAT3(4.0f, 3.0f, 6.0f);
// Instantiate the cylinders for use in the various game levels.
// Each cylinder has a different initial position, radius, and direction vector,
// but share a common set of material properties.
for (int a = 0; a < GameConstants::MaxCylinders; a++)
{
Cylinder^ cylinder;
switch (a)
{
case 0:
cylinder = ref new Cylinder(XMFLOAT3(-2.0f, -3.0f, 0.0f), 0.25f, XMFLOAT3(0.0f, 6.0f, 0.0f));
break;
case 1:
cylinder = ref new Cylinder(XMFLOAT3(2.0f, -3.0f, 0.0f), 0.25f, XMFLOAT3(0.0f, 6.0f, 0.0f));
break;
case 2:
cylinder = ref new Cylinder(XMFLOAT3(0.0f, -3.0f, -2.0f), 0.25f, XMFLOAT3(0.0f, 6.0f, 0.0f));
break;
case 3:
cylinder = ref new Cylinder(XMFLOAT3(-1.5f, -3.0f, -4.0f), 0.25f, XMFLOAT3(0.0f, 6.0f, 0.0f));
break;
case 4:
cylinder = ref new Cylinder(XMFLOAT3(1.5f, -3.0f, -4.0f), 0.50f, XMFLOAT3(0.0f, 6.0f, 0.0f));
break;
}
cylinder->Active(true);
m_object.push_back(cylinder);
m_renderObject.push_back(cylinder);
}
MediaReader^ mediaReader = ref new MediaReader;
auto targetHitSound = mediaReader->LoadMedia("hit.wav");
// Instantiate the targets for use in the game.
// Each target has a different initial position, size and orientation,
// but share a common set of material properties.
// The target is defined by a position and two vectors that define both
// the plane of the target in world space and the size of the parallelogram
// based on the lengths of the vectors.
// Each target is assigned a number for identification purposes.
// The Target ID number is 1 based.
// All targets have the same material properties.
for (int a = 1; a < GameConstants::MaxTargets; a++)
{
Face^ target;
switch (a)
{
case 1:
target = ref new Face(XMFLOAT3(-2.5f, -1.0f, -1.5f), XMFLOAT3(-1.5f, -1.0f, -2.0f), XMFLOAT3(-2.5f, 1.0f, -1.5f));
break;
case 2:
target = ref new Face(XMFLOAT3(-1.0f, 1.0f, -3.0f), XMFLOAT3(0.0f, 1.0f, -3.0f), XMFLOAT3(-1.0f, 2.0f, -3.0f));
break;
case 3:
target = ref new Face(XMFLOAT3(1.5f, 0.0f, -3.0f), XMFLOAT3(2.5f, 0.0f, -2.0f), XMFLOAT3(1.5f, 2.0f, -3.0f));
break;
case 4:
target = ref new Face(XMFLOAT3(-2.5f, -1.0f, -5.5f), XMFLOAT3(-0.5f, -1.0f, -5.5f), XMFLOAT3(-2.5f, 1.0f, -5.5f));
break;
case 5:
target = ref new Face(XMFLOAT3(0.5f, -2.0f, -5.0f), XMFLOAT3(1.5f, -2.0f, -5.0f), XMFLOAT3(0.5f, 0.0f, -5.0f));
break;
case 6:
target = ref new Face(XMFLOAT3(1.5f, -2.0f, -5.5f), XMFLOAT3(2.5f, -2.0f, -5.0f), XMFLOAT3(1.5f, 0.0f, -5.5f));
break;
case 7:
target = ref new Face(XMFLOAT3(0.0f, 0.0f, 0.0f), XMFLOAT3(0.5f, 0.0f, 0.0f), XMFLOAT3(0.0f, 0.5f, 0.0f));
break;
case 8:
target = ref new Face(XMFLOAT3(0.0f, 0.0f, 0.0f), XMFLOAT3(0.5f, 0.0f, 0.0f), XMFLOAT3(0.0f, 0.5f, 0.0f));
break;
case 9:
target = ref new Face(XMFLOAT3(0.0f, 0.0f, 0.0f), XMFLOAT3(0.5f, 0.0f, 0.0f), XMFLOAT3(0.0f, 0.5f, 0.0f));
break;
}
target->Target(true);
target->TargetId(a);
target->Active(true);
target->HitSound(ref new SoundEffect());
target->HitSound()->Initialize(
m_audioController->SoundEffectEngine(),
mediaReader->GetOutputWaveFormatEx(),
targetHitSound);
m_object.push_back(target);
m_renderObject.push_back(target);
}
// Instantiate a set of spheres to be used as ammunition for the game
// and set the material properties of the spheres.
auto ammoHitSound = mediaReader->LoadMedia("bounce.wav");
for (int a = 0; a < GameConstants::MaxAmmo; a++)
{
m_ammo[a] = ref new Sphere;
m_ammo[a]->Radius(GameConstants::AmmoRadius);
m_ammo[a]->HitSound(ref new SoundEffect());
m_ammo[a]->HitSound()->Initialize(
m_audioController->SoundEffectEngine(),
mediaReader->GetOutputWaveFormatEx(),
ammoHitSound);
m_ammo[a]->Active(false);
m_renderObject.push_back(m_ammo[a]);
}
// Instantiate each of the game levels. The Level class contains methods
// that initialize the objects in the world for the given level and also
// define any motion paths for the objects in that level.
m_level.push_back(ref new Level1);
m_level.push_back(ref new Level2);
m_level.push_back(ref new Level3);
m_level.push_back(ref new Level4);
m_level.push_back(ref new Level5);
m_level.push_back(ref new Level6);
m_levelCount = static_cast<uint32>(m_level.size());
// Load the top score from disk if it exists.
LoadHighScore();
// Load the currentScore for saved state
LoadState();
m_controller->Active(false);
}
The sample game sets up the components of the game object in this order:
- A new audio playback object is created.
- Arrays for the game's graphic primitives are created, including arrays for the level primitives, ammo, and obstacles.
- A location for saving game state data is created, named Game, and placed in the app data settings storage location specified by ApplicationData::Current.
- A game timer and the initial in-game overlay bitmap are created.
- A new camera is created with a specific set of view and projection parameters.
- The input device (the controller) is set to the same starting pitch and yaw as the camera, so the player has a 1-to-1 correspondence between the starting control position and the camera position.
- The player object is created and set to active. We use a sphere object to detect the player's proximity to walls and obstacles and to keep the camera from getting put in a position that might break immersion.
- The game world primitive is created.
- The cylinder obstacles are created.
- The targets (Face objects) are created and numbered.
- The ammo spheres are created.
- The levels are created.
- The high score is loaded.
- Any prior saved game state is loaded.
The game now has instances of all the key components: the world, the player, the obstacles, the targets, and the ammo spheres. It also has instances of the levels, which represent configurations of all of the above components and their behaviors for each specific level. Let's see how the game builds the levels.
Building and loading the game's levels
Most of the heavy lifting for the level construction is done in the Level.h/.cpp file, which we won't delve into, because it focuses on a very specific implementation. The important thing is that the code for each level is run as a separate LevelN object. If you'd like to extend the game, you can create a Level object that took an assigned number as a parameter and randomly placed the obstacles and targets. Or, you can have it load level configuration data from a resource file, or even the Internet!
The Level.h/.cpp file is included in the Windows Store Direct3D shooting game sample.
Defining the game play
At this point, we have all the components we need to assemble the game. The levels have been constructed in memory from the primitives, and are ready for the player to start interacting with them in some fashion.
Now, the best games react instantly to player input, and provide immediate feedback. This is true for any type of a game, from twitch-action, real-time shoot-em-ups to thoughtful, turn-based strategy games.
In Defining the game's Windows Store app framework, we looked at the overall state machine that governs the flow of the game. Remember, the sample implements this flow as a loop inside the Run method of the DirectXApp class, which itself is an implementation of a DirectX view provider. The important state transitions must be controlled by the player, and must provide clear feedback. Any delay in this feedback breaks the sense of immersion.
Here is a diagram representing the basic flow of the game and its high-level states.
When the sample game starts play, the game object can be in one of three states:
- Waiting for resources. This state is activated when the game object is initialized or when the components of a level are being loaded. If this state was triggered by a request to load a prior game, the game stats overlay is displayed; if it was triggered by a request to play a level, the level start overlay is displayed. The completion of resource loading causes the game to pass through the Resources loaded state and then transition into the Waiting for press state.
- Waiting for press. This state is activated when the game is paused, either by the player or by the system (after, say, loading resources). When the player is ready to exit this state, the player is prompted to load a new game state (LoadGame), start or restart the loaded level (StartLevel), or continue the current level (ContinueGame).
- Dynamics. If a player's press input is completed and the resulting action is to start or continue a level, the game object transitions into the Dynamics state. The game is played in this state, and the game world and player objects are updated here based on animation routines and player input. This state is left when the player triggers a pause event, either by pressing P, by taking an action that deactivates the main window, or by completing a level or the game.
Now, let's look at specific code in the DirectXApp class (see: Defining the game's Windows Store app framework) for the Update method that implements this state machine.
void DirectXApp::Update()
{
static uint32 loadCount = 0;
m_controller->Update();
switch (m_updateState)
{
case UpdateEngineState::WaitingForResources:
// Waiting for initial load. Display an update one time per 60 updates.
loadCount++;
if ((loadCount % 60) == 0)
{
m_loadingCount++;
SetGameInfoOverlay(m_gameInfoOverlayState);
}
break;
case UpdateEngineState::ResourcesLoaded:
switch (m_pressResult)
{
case PressResultState::LoadGame:
SetGameInfoOverlay(GameInfoOverlayState::GameStats);
break;
case PressResultState::PlayLevel:
SetGameInfoOverlay(GameInfoOverlayState::LevelStart);
break;
case PressResultState::ContinueLevel:
SetGameInfoOverlay(GameInfoOverlayState::Pause);
break;
}
m_updateState = UpdateEngineState::WaitingForPress;
SetAction(GameInfoOverlayCommand::TapToContinue);
m_controller->WaitForPress(m_renderer->GameInfoOverlayUpperLeft(), m_renderer->GameInfoOverlayLowerRight());
ShowGameInfoOverlay();
m_renderNeeded = true;
break;
case UpdateEngineState::WaitingForPress:
if (m_controller->IsPressComplete())
{
switch (m_pressResult)
{
case PressResultState::LoadGame:
m_updateState = UpdateEngineState::WaitingForResources;
m_pressResult = PressResultState::PlayLevel;
m_controller->Active(false);
m_game->LoadGame();
SetAction(GameInfoOverlayCommand::PleaseWait);
SetGameInfoOverlay(GameInfoOverlayState::LevelStart);
ShowGameInfoOverlay();
m_game->LoadLevelAsync().then([this]()
{
m_game->FinalizeLoadLevel();
m_updateState = UpdateEngineState::ResourcesLoaded;
}, task_continuation_context::use_current());
break;
case PressResultState::PlayLevel:
m_updateState = UpdateEngineState::Dynamics;
HideGameInfoOverlay();
m_controller->Active(true);
m_game->StartLevel();
break;
case PressResultState::ContinueLevel:
m_updateState = UpdateEngineState::Dynamics;
HideGameInfoOverlay();
m_controller->Active(true);
m_game->ContinueGame();
break;
}
}
break;
case UpdateEngineState::Dynamics:
if (m_controller->IsPauseRequested())
{
m_game->PauseGame();
SetGameInfoOverlay(GameInfoOverlayState::Pause);
SetAction(GameInfoOverlayCommand::TapToContinue);
m_updateState = UpdateEngineState::WaitingForPress;
m_pressResult = PressResultState::ContinueLevel;
ShowGameInfoOverlay();
}
else
{
GameState runState = m_game->RunGame();
switch (runState)
{
case GameState::TimeExpired:
SetAction(GameInfoOverlayCommand::TapToContinue);
SetGameInfoOverlay(GameInfoOverlayState::GameOverExpired);
ShowGameInfoOverlay();
m_updateState = UpdateEngineState::WaitingForPress;
m_pressResult = PressResultState::LoadGame;
break;
case GameState::LevelComplete:
SetAction(GameInfoOverlayCommand::PleaseWait);
SetGameInfoOverlay(GameInfoOverlayState::LevelStart);
ShowGameInfoOverlay();
m_updateState = UpdateEngineState::WaitingForResources;
m_pressResult = PressResultState::PlayLevel;
m_game->LoadLevelAsync().then([this]()
{
m_game->FinalizeLoadLevel();
m_updateState = UpdateEngineState::ResourcesLoaded;
}, task_continuation_context::use_current());
break;
case GameState::GameComplete:
SetAction(GameInfoOverlayCommand::TapToContinue);
SetGameInfoOverlay(GameInfoOverlayState::GameOverCompleted);
ShowGameInfoOverlay();
m_updateState = UpdateEngineState::WaitingForPress;
m_pressResult = PressResultState::LoadGame;
break;
}
}
if (m_updateState == UpdateEngineState::WaitingForPress)
{
// Transitioning state, so enable waiting for the press event
m_controller->WaitForPress(m_renderer->GameInfoOverlayUpperLeft(), m_renderer->GameInfoOverlayLowerRight());
}
if (m_updateState == UpdateEngineState::WaitingForResources)
{
// Transitioning state, so shut down the input controller until resources are loaded
m_controller->Active(false);
}
break;
}
}
The first thing this method does is call the MoveLookController instance's own Update method, which updates the data from the controller. This data includes the direction the player's view (the camera) is facing and the velocity of the player's movement.
When the game is in the Dynamics state, that is, when the player is playing, the work is handled in the RunGame method, with this call:
GameState runState = m_game->RunGame();
RunGame handles the set of data that defines the current state of the game play for the current iteration of the game loop. It flows like this:
- The method updates the timer that counts down the seconds until the level is completed, and tests to see if the level's time has expired. This is one of the rules of the game: when time runs out without all the targets getting shot, the game is over.
- If time has run out, the method sets the TimeExpired game state, and returns to the Update method in the previous code.
- If time remains, the move-look controller is polled for an update to the camera position; specifically, an update to the angle of the view normal projecting from the camera plane (where the player is looking), and the distance that angle has moved from the previous time the controller was polled.
- The camera is updated based on the new data from the move-look controller.
- The dynamics, or the animations and behaviors of objects in the game world independent of player control, are updated. In the game sample, this is the motion of the ammo spheres that have been fired, the animation of the pillar obstacles and the movement of the targets.
- The method checks to see if the criteria for the successful completion of a level have been met. If so, it finalizes the score for the level and checks to see if this is the last level (of 6). If it's the last level, the method returns the GameComplete game state; otherwise, it returns the LevelComplete game state.
- If the level isn't complete, the method sets the game state to Active and returns.
Here's what RunGame, found in Simple3DGame.cpp, looks like in code.
GameState Simple3DGame::RunGame()
{
m_timer->Update();
m_levelTimeRemaining = m_levelDuration - m_timer->PlayingTime();
if (m_levelTimeRemaining <= 0.0f)
{
// Time expired, so the game is over.
m_levelTimeRemaining = 0.0f;
InitializeAmmo();
m_timer->Reset();
m_gameActive = false;
m_levelActive = false;
SaveState();
if (m_totalHits > m_topScore.totalHits)
{
m_topScore.totalHits = m_totalHits;
m_topScore.totalShots = m_totalShots;
m_topScore.levelCompleted = m_currentLevel;
SaveHighScore();
}
return GameState::TimeExpired;
}
else
{
// Time has not expired, so run one frame of game play.
m_player->Velocity(m_controller->Velocity());
m_camera->LookDirection(m_controller->LookDirection());
UpdateDynamics();
// Update the camera with the player position updates from the dynamics calculations.
m_camera->Eye(m_player->Position());
m_camera->LookDirection(m_controller->LookDirection());
if (m_level[m_currentLevel]->Update(m_timer->PlayingTime(), m_timer->DeltaTime(), m_levelTimeRemaining, m_object))
{
// The level has been completed.
m_levelActive = false;
InitializeAmmo();
if (m_currentLevel < m_levelCount-1)
{
// More levels to go so increment the level number.
// Actual level loading will occur in the LoadLevelAsync / FinalizeLoadLevel
// methods.
m_timer->Reset();
m_currentLevel++;
m_levelBonusTime = m_levelTimeRemaining;
SaveState();
return GameState::LevelComplete;
}
else
{
// All levels have been completed.
m_timer->Reset();
m_gameActive = false;
m_levelActive = false;
SaveState();
if (m_totalHits > m_topScore.totalHits)
{
m_topScore.totalHits = m_totalHits;
m_topScore.totalShots = m_totalShots;
m_topScore.levelCompleted = m_currentLevel;
SaveHighScore();
}
return GameState::GameComplete;
}
}
}
return GameState::Active;
}}
Here's the money call: UpdateDynamics ()
. It's what brings the game world to life. Let's review it!
Updating the game world
A fast and fluid game experience is one where the world feels alive, where the game itself is in motion independent of player input. Trees wave in the wind, waves crest along shore lines, machinery smokes and shines, and alien monsters stretch and salivate. Imagine what a game would be like if everything was frozen, with the graphics only moving when the player provided input. It'd be weird and not very, well, immersive. Immersion, for the player, comes from the feeling of being an agent in a living, breathing world.
The game loop should always keep updating the game world and running the animation routines, be they canned or based on physical algorithms or just plain random, except when the game is specifically paused. In the game sample, this principle is called dynamics, and it encompasses the rise and fall of the pillar obstacles, and the motion and physical behaviors of the ammo spheres as they are fired. It also encompasses the interaction between objects, including collisions between the player sphere and the world, or between the ammo and the obstacles and targets.
The code that implements these dynamics looks like this:
void Simple3DGame::UpdateDynamics()
{
float timeTotal = m_timer->PlayingTime();
float timeFrame = m_timer->DeltaTime();
bool fire = m_controller->IsFiring();
#pragma region Shoot Ammo
// Shoot ammo.
if (fire)
{
static float lastFired; // Timestamp of last ammo fired.
if (timeTotal < lastFired)
{
// timeTotal is not guarenteed to be monotomically increasing because it is
// reset at each level.
lastFired = timeTotal - GameConstants::Physics::AutoFireDelay;
}
if (timeTotal - lastFired >= GameConstants::Physics::AutoFireDelay)
{
// Compute ammo firing behavior.
}
}
#pragma endregion
#pragma region Animate Objects
for (uint32 i = 0; i < m_object.size(); i++)
{
if (m_object[i]->AnimatePosition())
{
// Animate targets and cylinders based on level parameters and global constants.
}
}
#pragma endregion
// If the elapsed time is too long, we slice up the time and
// handle physics over several passes.
float timeLeft = timeFrame;
float elapsedFrameTime;
while (timeLeft > 0.0f)
{
elapsedFrameTime = min(timeLeft, GameConstants::Physics::FrameLength);
timeLeft -= elapsedFrameTime;
// Update the player position.
m_player->Position(m_player->VectorPosition() + m_player->VectorVelocity() * elapsedFrameTime);
// Do m_player / object intersections.
for (uint32 a = 0; a < m_object.size(); a++)
{
if (m_object[a]->Active() && m_object[a] != m_player)
{
XMFLOAT3 contact;
XMFLOAT3 normal;
if (m_object[a]->IsTouching(m_player->Position(), m_player->Radius(), &contact, &normal))
{
XMVECTOR oneToTwo;
oneToTwo = -XMLoadFloat3(&normal);
// Player is in contact with Object
float impact;
impact = XMVectorGetX(
XMVector3Dot (oneToTwo, m_player->VectorVelocity())
);
// Make sure that the player is actually headed towards the object at grazing angles; there
// could appear to be an impact when the player is actually already hit and moving away.
if (impact > 0.0f)
{
// Compute the normal and tangential components of the player's velocity.
XMVECTOR velocityOneNormal = XMVector3Dot(oneToTwo, m_player->VectorVelocity()) * oneToTwo;
XMVECTOR velocityOneTangent = m_player->VectorVelocity() - velocityOneNormal;
// Compute post-collision velocity.
m_player->Velocity(velocityOneTangent - velocityOneNormal);
// Fix the positions so that the ball is exactly GameConstants::AmmoRadius from target.
float distanceToMove = m_player->Radius();
m_player->Position(XMLoadFloat3(&contact) - (oneToTwo * distanceToMove));
}
}
}
}
{
// Do collision detection of the player with the bounding world.
XMFLOAT3 position = m_player->Position();
XMFLOAT3 velocity = m_player->Velocity();
float radius = m_player->Radius();
// Check for player collisions with the walls, floor, or ceiling
// and adjust the position.
float limit = m_minBound.x + radius;
if (position.x < limit)
{
position.x = limit;
velocity.x = -velocity.x * GameConstants::Physics::GroundRestitution;
}
limit = m_maxBound.x - radius;
if (position.x > limit)
{
position.x = limit;
velocity.x = -velocity.x + GameConstants::Physics::GroundRestitution;
}
limit = m_minBound.y + radius;
if (position.y < limit)
{
position.y = limit;
velocity.y = -velocity.y * GameConstants::Physics::GroundRestitution;
}
limit = m_maxBound.y - radius;
if (position.y > limit)
{
position.y = limit;
velocity.y = -velocity.y * GameConstants::Physics::GroundRestitution;
}
limit = m_minBound.z + radius;
if (position.z < limit)
{
position.z = limit;
velocity.z = -velocity.z * GameConstants::Physics::GroundRestitution;
}
limit = m_maxBound.z - radius;
if (position.z > limit)
{
position.z = limit;
velocity.z = -velocity.z * GameConstants::Physics::GroundRestitution;
}
m_player->Position(position);
m_player->Velocity(velocity);
}
// Animate the ammo.
if (m_ammoCount > 0)
{
// Check for inter-ammo collision.
#pragma region inter-ammo collision detection
if (m_ammoCount > 1)
{
for (uint32 one = 0; one < m_ammoCount; one++)
{
for (uint32 two = (one + 1); two < m_ammoCount; two++)
{
// Compute checks for collisions for each ammo object with the other active ammo objects.
}
}
}
#pragma endregion
#pragma region Ammo-Object intersections
// Check for intersections with Objects
for (uint32 one = 0; one < m_ammoCount; one++)
{
// Compute ammo collisions with game objects (targets, cylinders, walls).
}
#pragma endregion
#pragma region Apply Gravity and world intersection
// Apply gravity and check for collision against ground and walls.
for (uint32 i = 0; i < m_ammoCount; i++)
{
// Compute the effect of gravity on the ammo, and any ammo collisions with the world objects (walls, floor)
}
}
}
}
(This code example has been abbreviated for readability. The full working code is found in the complete code sample at the bottom of this topic.)
This method deals with four sets of computations:
- The positions of the fired ammo spheres in the world.
- The animation of the pillar obstacles.
- The intersection of the player and the world boundaries.
- The collisions of the ammo spheres with the obstacles, the targets, other ammo spheres, and the world.
The animation of the obstacles is a loop defined in Animate.h/.cpp. The behavior of the ammo and any collisions are defined by simplified physics algorithms, supplied in the previous code and parameterized by a set of global constants for the game world, including gravity and material properties. This is all computed in the game world coordinate space.
Now that we've updated all the objects in the scene and calculated any collisions, we need to use that info to draw the corresponding visual changes. After Update completes in the current iteration of the game loop, the sample immediately calls Render to take the updated object data and generate a new scene to present to the player.
Let's look at the render method now.
Rendering the game world's graphics
We recommend that the graphics in a game update as often as possible, which, at maximum, is every time the main game loop iterates. As the loop iterates, the game is updated, with or without player input. This allows the animations and behaviors that are calculated to be displayed smoothly. Imagine if we had a simple scene of water that only moved when the player pressed a button. That would make for terribly boring visuals. A good game looks smooth and fluid.
Recall the sample game's loop, as shown here. If the game's main window is visible, and isn't snapped or deactivated, the game continues to update and render the results of that update.
void DirectXApp::Run()
{
while (!m_windowClosed)
{
if (m_visible)
{
switch (m_updateState)
{
case UpdateEngineState::Deactivated:
case UpdateEngineState::Snapped:
if (!m_renderNeeded)
{
// The App is not currently the active window, so just wait for events.
CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
break;
}
// Otherwise fall through and do normal processing to get the rendering handled.
default:
CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);
Update();
m_renderer->Render();
m_renderNeeded = false;
}
}
else
{
CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
}
}
m_game->OnSuspending(); // Exiting due to window close. Make sure to save state.
}
The method we examine now renders a representation of that state immediately after the state is updated in Run with a call to Update, which we discussed in the previous section.
void GameRenderer::Render()
{
int renderingPasses = 1;
if (m_stereoEnabled)
{
renderingPasses = 2;
}
for (int i = 0; i < renderingPasses; i++)
{
if (m_stereoEnabled && i > 0)
{
// Doing the Right Eye View
m_d3dContext->OMSetRenderTargets(1, m_renderTargetViewRight.GetAddressOf(), m_depthStencilView.Get());
m_d3dContext->ClearDepthStencilView(m_depthStencilView.Get(), D3D11_CLEAR_DEPTH, 1.0f, 0);
m_d2dContext->SetTarget(m_d2dTargetBitmapRight.Get());
}
else
{
// Doing the Mono or Left Eye View
m_d3dContext->OMSetRenderTargets(1, m_renderTargetView.GetAddressOf(), m_depthStencilView.Get());
m_d3dContext->ClearDepthStencilView(m_depthStencilView.Get(), D3D11_CLEAR_DEPTH, 1.0f, 0);
m_d2dContext->SetTarget(m_d2dTargetBitmap.Get());
}
if (m_game != nullptr && m_gameResourcesLoaded && m_levelResourcesLoaded)
{
// This section is only used after the game state has been initialized and all device
// resources needed for the game have been created and associated with the game objects.
if (m_stereoEnabled)
{
ConstantBufferChangeOnResize changesOnResize;
XMStoreFloat4x4(
&changesOnResize.projection,
XMMatrixMultiply(
XMMatrixTranspose(
i == 0 ?
m_game->GameCamera()->LeftEyeProjection() :
m_game->GameCamera()->RightEyeProjection()
),
XMMatrixTranspose(XMLoadFloat4x4(&m_rotationTransform3D))
)
);
m_d3dContext->UpdateSubresource(
m_constantBufferChangeOnResize.Get(),
0,
nullptr,
&changesOnResize,
0,
0
);
}
// Update variables that change one time per frame.
ConstantBufferChangesEveryFrame constantBufferChangesEveryFrame;
XMStoreFloat4x4(
&constantBufferChangesEveryFrame.view,
XMMatrixTranspose(m_game->GameCamera()->View())
);
m_d3dContext->UpdateSubresource(
m_constantBufferChangesEveryFrame.Get(),
0,
nullptr,
&constantBufferChangesEveryFrame,
0,
0
);
// Setup Pipeline.
m_d3dContext->IASetInputLayout(m_vertexLayout.Get());
m_d3dContext->VSSetConstantBuffers(0, 1, m_constantBufferNeverChanges.GetAddressOf());
m_d3dContext->VSSetConstantBuffers(1, 1, m_constantBufferChangeOnResize.GetAddressOf());
m_d3dContext->VSSetConstantBuffers(2, 1, m_constantBufferChangesEveryFrame.GetAddressOf());
m_d3dContext->VSSetConstantBuffers(3, 1, m_constantBufferChangesEveryPrim.GetAddressOf());
m_d3dContext->PSSetConstantBuffers(2, 1, m_constantBufferChangesEveryFrame.GetAddressOf());
m_d3dContext->PSSetConstantBuffers(3, 1, m_constantBufferChangesEveryPrim.GetAddressOf());
m_d3dContext->PSSetSamplers(0, 1, m_samplerLinear.GetAddressOf());
// Get the all the objects to render from the Game state.
auto objects = m_game->RenderObjects();
for (auto object = objects.begin(); object != objects.end(); object++)
{
(*object)->Render(m_d3dContext.Get(), m_constantBufferChangesEveryPrim.Get());
}
}
else
{
const float ClearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f};
// Only need to clear the background when not rendering the full 3D scene because
// the 3D world is a fully enclosed box, and the dynamics prevents the camera from
// moving outside this space.
if (m_stereoEnabled && i > 0)
{
// Doing the Right Eye View.
m_d3dContext->ClearRenderTargetView(m_renderTargetViewRight.Get(), ClearColor);
}
else
{
// Doing the Mono or Left Eye View.
m_d3dContext->ClearRenderTargetView(m_renderTargetView.Get(), ClearColor);
}
}
m_d2dContext->BeginDraw();
// To handle the swapchain being pre-rotated, set the D2D transformation to include it.
m_d2dContext->SetTransform(m_rotationTransform2D);
if (m_game != nullptr && m_gameResourcesLoaded)
{
// This is only used after the game state has been initialized.
m_gameHud->Render(m_game, m_d2dContext.Get(), m_windowBounds);
}
if (m_gameInfoOverlay->Visible())
{
m_d2dContext->DrawBitmap(
m_gameInfoOverlay->Bitmap(),
D2D1::RectF(
(m_windowBounds.Width - GameInfoOverlayConstant::Width)/2.0f,
(m_windowBounds.Height - GameInfoOverlayConstant::Height)/2.0f,
(m_windowBounds.Width - GameInfoOverlayConstant::Width)/2.0f + GameInfoOverlayConstant::Width,
(m_windowBounds.Height - GameInfoOverlayConstant::Height)/2.0f + GameInfoOverlayConstant::Height
)
);
}
HRESULT hr = m_d2dContext->EndDraw();
if (hr != D2DERR_RECREATE_TARGET)
{
// The D2DERR_RECREATE_TARGET indicates there has been a problem with the underlying
// D3D device. All subsequent rendering will be ignored until the device is recreated.
// This error will be propagated and the appropriate D3D error will be returned from the
// swapchain->Present(...) call. At that point, the sample will recreate the device
// and all associated resources. As a result the D2DERR_RECREATE_TARGET doesn't
// need to be handled here.
DX::ThrowIfFailed(hr);
}
}
Present();
}
The complete code for this method is in Assembling the rendering framework.
This method draws the projection of the 3D world, and then draws the Direct2D overlay on top of it. When completed, it presents the final swap chain with the combined buffers for display.
Be aware that there are two states for the sample game's Direct2D overlay: one where the game displays the game info overlay that contains the bitmap for the pause menu, and one where the game displays the cross hairs along with the rectangles for the touchscreen move-look controller. The score text is drawn in both states.
Next steps
By now, you're probably curious about the actual rendering engine: how those calls to the Render methods on the updated primitives get turned into pixels on your screen. We cover that in detail in Assembling the rendering framework. If you're more interested in how the player controls update the game state, then check out Adding controls.
Complete code sample for this section
Simple3DGame.h
//// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//// PARTICULAR PURPOSE.
////
//// Copyright (c) Microsoft Corporation. All rights reserved
#pragma once
// Game specific Classes
#include "GameConstants.h"
#include "Audio.h"
#include "Camera.h"
#include "Level.h"
#include "GameObject.h"
#include "GameTimer.h"
#include "MoveLookController.h"
#include "PersistentState.h"
#include "Sphere.h"
#include "GameRenderer.h"
//--------------------------------------------------------------------------------------
enum class GameState
{
Waiting,
Active,
LevelComplete,
TimeExpired,
GameComplete,
};
typedef struct
{
Platform::String^ tag;
int totalHits;
int totalShots;
int levelCompleted;
} HighScoreEntry;
typedef std::vector<HighScoreEntry> HighScoreEntries;
//--------------------------------------------------------------------------------------
ref class GameRenderer;
ref class Simple3DGame
{
internal:
Simple3DGame();
void Initialize(
_In_ MoveLookController^ controller,
_In_ GameRenderer^ renderer
);
void LoadGame();
concurrency::task<void> LoadLevelAsync();
void FinalizeLoadLevel();
void StartLevel();
void PauseGame();
void ContinueGame();
GameState RunGame();
void OnSuspending();
void OnResuming();
bool IsActivePlay() { return m_timer->Active(); }
int LevelCompleted() { return m_currentLevel; };
int TotalShots() { return m_totalShots; };
int TotalHits() { return m_totalHits; };
float BonusTime() { return m_levelBonusTime; };
bool GameActive() { return m_gameActive; };
bool LevelActive() { return m_levelActive; };
HighScoreEntry HighScore() { return m_topScore; };
Level^ CurrentLevel() { return m_level[m_currentLevel]; };
float TimeRemaining() { return m_levelTimeRemaining; };
Camera^ GameCamera() { return m_camera; };
std::vector<GameObject^> RenderObjects() { return m_renderObject; };
private:
void LoadState();
void SaveState();
void SaveHighScore();
void LoadHighScore();
void InitializeAmmo();
void UpdateDynamics();
MoveLookController^ m_controller;
GameRenderer^ m_renderer;
Camera^ m_camera;
Audio^ m_audioController;
std::vector<Sphere^> m_ammo;
uint32 m_ammoCount;
uint32 m_ammoNext;
HighScoreEntry m_topScore;
PersistentState^ m_savedState;
GameTimer^ m_timer;
bool m_gameActive;
bool m_levelActive;
int m_totalHits;
int m_totalShots;
float m_levelDuration;
float m_levelBonusTime;
float m_levelTimeRemaining;
std::vector<Level^> m_level;
uint32 m_levelCount;
uint32 m_currentLevel;
Sphere^ m_player;
std::vector<GameObject^> m_object; // object list for intersections
std::vector<GameObject^> m_renderObject; // all objects to be rendered
DirectX::XMFLOAT3 m_minBound;
DirectX::XMFLOAT3 m_maxBound;
};
Simple3DGame.cpp
//// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//// PARTICULAR PURPOSE.
////
//// Copyright (c) Microsoft Corporation. All rights reserved
#include "pch.h"
#include "GameRenderer.h"
#include "DirectXSample.h"
#include "Level1.h"
#include "Level2.h"
#include "Level3.h"
#include "Level4.h"
#include "Level5.h"
#include "Level6.h"
#include "Animate.h"
#include "Sphere.h"
#include "Cylinder.h"
#include "Face.h"
#include "MediaReader.h"
using namespace concurrency;
using namespace DirectX;
using namespace Microsoft::WRL;
using namespace Windows::Storage;
using namespace Windows::UI::Core;
//----------------------------------------------------------------------
Simple3DGame::Simple3DGame():
m_ammoCount(0),
m_ammoNext(0),
m_gameActive(false),
m_levelActive(false),
m_totalHits(0),
m_totalShots(0),
m_levelBonusTime(0.0),
m_levelTimeRemaining(0.0),
m_levelCount(0),
m_currentLevel(0)
{
m_topScore.totalHits = 0;
m_topScore.totalShots = 0;
m_topScore.levelCompleted = 0;
}
//----------------------------------------------------------------------
void Simple3DGame::Initialize(
_In_ MoveLookController^ controller,
_In_ GameRenderer^ renderer
)
{
// This method is expected to be called as an asynchronous task.
// Make sure that you don't call rendering methods on the
// m_renderer, as this would result in the D3D Context being
// used in multiple threads, which is not allowed.
m_controller = controller;
m_renderer = renderer;
m_audioController = ref new Audio;
m_audioController->CreateDeviceIndependentResources();
m_ammo = std::vector<Sphere^>(GameConstants::MaxAmmo);
m_object = std::vector<GameObject^>();
m_renderObject = std::vector<GameObject^>();
m_level = std::vector<Level^>();
m_savedState = ref new PersistentState();
m_savedState->Initialize(ApplicationData::Current->LocalSettings->Values, "Game");
m_timer = ref new GameTimer();
// Create a sphere primitive to represent the player.
// The sphere will be used to handle collisions and constrain the player in the world.
// It is not rendered, so it is not added to the list of render objects.
m_player = ref new Sphere(XMFLOAT3(0.0f, -1.3f, 4.0f), 0.2f);
m_camera = ref new Camera;
m_camera->SetProjParams(XM_PI / 2, 1.0f, 0.01f, 100.0f);
m_camera->SetViewParams(
m_player->Position(), // Eye point in world coordinates.
XMFLOAT3 (0.0f, 0.7f, 0.0f), // Look at point in world coordinates.
XMFLOAT3 (0.0f, 1.0f, 0.0f) // The Up vector for the camera.
);
m_controller->Pitch(m_camera->Pitch());
m_controller->Yaw(m_camera->Yaw());
// Add the m_player object to the object list to do intersection calculations
m_object.push_back(m_player);
m_player->Active(true);
// Instantiate the world primitive. This object maintains the geometry and
// material properties of the walls, floor, and ceiling of the enclosing world.
// The TargetId is used to identify the world objects so that the right geometry
// and textures can be associated with them later after those resources have
// been created.
GameObject^ world = ref new GameObject();
world->TargetId(GameConstants::WorldFloorId);
world->Active(true);
m_renderObject.push_back(world);
world = ref new GameObject();
world->TargetId(GameConstants::WorldCeilingId);
world->Active(true);
m_renderObject.push_back(world);
world = ref new GameObject();
world->TargetId(GameConstants::WorldWallsId);
world->Active(true);
m_renderObject.push_back(world);
// Min and max Bound are defining the world space of the game.
// All camera motion and dynamics are confined to this space.
m_minBound = XMFLOAT3(-4.0f, -3.0f, -6.0f);
m_maxBound = XMFLOAT3(4.0f, 3.0f, 6.0f);
// Instantiate the Cylinders for use in the various game levels.
// Each cylinder has a different initial position, radius and direction vector,
// but share a common set of material properties.
for (int a = 0; a < GameConstants::MaxCylinders; a++)
{
Cylinder^ cylinder;
switch (a)
{
case 0:
cylinder = ref new Cylinder(XMFLOAT3(-2.0f, -3.0f, 0.0f), 0.25f, XMFLOAT3(0.0f, 6.0f, 0.0f));
break;
case 1:
cylinder = ref new Cylinder(XMFLOAT3(2.0f, -3.0f, 0.0f), 0.25f, XMFLOAT3(0.0f, 6.0f, 0.0f));
break;
case 2:
cylinder = ref new Cylinder(XMFLOAT3(0.0f, -3.0f, -2.0f), 0.25f, XMFLOAT3(0.0f, 6.0f, 0.0f));
break;
case 3:
cylinder = ref new Cylinder(XMFLOAT3(-1.5f, -3.0f, -4.0f), 0.25f, XMFLOAT3(0.0f, 6.0f, 0.0f));
break;
case 4:
cylinder = ref new Cylinder(XMFLOAT3(1.5f, -3.0f, -4.0f), 0.50f, XMFLOAT3(0.0f, 6.0f, 0.0f));
break;
}
cylinder->Active(true);
m_object.push_back(cylinder);
m_renderObject.push_back(cylinder);
}
MediaReader^ mediaReader = ref new MediaReader;
auto targetHitSound = mediaReader->LoadMedia("hit.wav");
// Instantiate the targets for use in the game.
// Each target has a different initial position, size and orientation,
// but share a common set of material properties.
// The target is defined by a position and two vectors that define both
// the plane of the target in world space and the size of the parallelogram
// based on the lengths of the vectors.
// Each target is assigned a number for identification purposes.
// The Target ID number is 1 based.
// All targets has the same material properties.
for (int a = 1; a < GameConstants::MaxTargets; a++)
{
Face^ target;
switch (a)
{
case 1:
target = ref new Face(XMFLOAT3(-2.5f, -1.0f, -1.5f), XMFLOAT3(-1.5f, -1.0f, -2.0f), XMFLOAT3(-2.5f, 1.0f, -1.5f));
break;
case 2:
target = ref new Face(XMFLOAT3(-1.0f, 1.0f, -3.0f), XMFLOAT3(0.0f, 1.0f, -3.0f), XMFLOAT3(-1.0f, 2.0f, -3.0f));
break;
case 3:
target = ref new Face(XMFLOAT3(1.5f, 0.0f, -3.0f), XMFLOAT3(2.5f, 0.0f, -2.0f), XMFLOAT3(1.5f, 2.0f, -3.0f));
break;
case 4:
target = ref new Face(XMFLOAT3(-2.5f, -1.0f, -5.5f), XMFLOAT3(-0.5f, -1.0f, -5.5f), XMFLOAT3(-2.5f, 1.0f, -5.5f));
break;
case 5:
target = ref new Face(XMFLOAT3(0.5f, -2.0f, -5.0f), XMFLOAT3(1.5f, -2.0f, -5.0f), XMFLOAT3(0.5f, 0.0f, -5.0f));
break;
case 6:
target = ref new Face(XMFLOAT3(1.5f, -2.0f, -5.5f), XMFLOAT3(2.5f, -2.0f, -5.0f), XMFLOAT3(1.5f, 0.0f, -5.5f));
break;
case 7:
target = ref new Face(XMFLOAT3(0.0f, 0.0f, 0.0f), XMFLOAT3(0.5f, 0.0f, 0.0f), XMFLOAT3(0.0f, 0.5f, 0.0f));
break;
case 8:
target = ref new Face(XMFLOAT3(0.0f, 0.0f, 0.0f), XMFLOAT3(0.5f, 0.0f, 0.0f), XMFLOAT3(0.0f, 0.5f, 0.0f));
break;
case 9:
target = ref new Face(XMFLOAT3(0.0f, 0.0f, 0.0f), XMFLOAT3(0.5f, 0.0f, 0.0f), XMFLOAT3(0.0f, 0.5f, 0.0f));
break;
}
target->Target(true);
target->TargetId(a);
target->Active(true);
target->HitSound(ref new SoundEffect());
target->HitSound()->Initialize(
m_audioController->SoundEffectEngine(),
mediaReader->GetOutputWaveFormatEx(),
targetHitSound);
m_object.push_back(target);
m_renderObject.push_back(target);
}
// Instantiate a set of spheres to be used as ammunition for the game
// and set the material properties of the spheres.
auto ammoHitSound = mediaReader->LoadMedia("bounce.wav");
for (int a = 0; a < GameConstants::MaxAmmo; a++)
{
m_ammo[a] = ref new Sphere;
m_ammo[a]->Radius(GameConstants::AmmoRadius);
m_ammo[a]->HitSound(ref new SoundEffect());
m_ammo[a]->HitSound()->Initialize(
m_audioController->SoundEffectEngine(),
mediaReader->GetOutputWaveFormatEx(),
ammoHitSound);
m_ammo[a]->Active(false);
m_renderObject.push_back(m_ammo[a]);
}
// Instantiate each of the game levels. The Level class contains methods
// that initialize the objects in the world for the given level and also
// define any motion paths for the objects in that level.
m_level.push_back(ref new Level1);
m_level.push_back(ref new Level2);
m_level.push_back(ref new Level3);
m_level.push_back(ref new Level4);
m_level.push_back(ref new Level5);
m_level.push_back(ref new Level6);
m_levelCount = static_cast<uint32>(m_level.size());
// Load the top score from disk if it exists.
LoadHighScore();
// Load the currentScore for saved state.
LoadState();
m_controller->Active(false);
}
//----------------------------------------------------------------------
void Simple3DGame::LoadGame()
{
m_player->Position(XMFLOAT3 (0.0f, -1.3f, 4.0f));
m_camera->SetViewParams(
m_player->Position(), // Eye point in world coordinates.
XMFLOAT3 (0.0f, 0.7f, 0.0f), // Look at point in world coordinates.
XMFLOAT3 (0.0f, 1.0f, 0.0f) // The Up vector for the camera.
);
m_controller->Pitch(m_camera->Pitch());
m_controller->Yaw(m_camera->Yaw());
m_currentLevel = 0;
m_levelTimeRemaining = 0.0f;
m_levelBonusTime = m_levelTimeRemaining;
m_levelDuration = m_level[m_currentLevel]->TimeLimit() + m_levelBonusTime;
InitializeAmmo();
m_totalHits = 0;
m_totalShots = 0;
m_gameActive = false;
m_levelActive = false;
m_timer->Reset();
}
//----------------------------------------------------------------------
task<void> Simple3DGame::LoadLevelAsync()
{
// Initialize the level and spin up the async loading of the rendering
// resources for the level.
// This will run in a separate thread, so for Direct3D 11, only Device
// methods are allowed. Any DeviceContext method calls need to be
// done in FinalizeLoadLevel.
m_level[m_currentLevel]->Initialize(m_object);
m_levelDuration = m_level[m_currentLevel]->TimeLimit() + m_levelBonusTime;
return m_renderer->LoadLevelResourcesAsync();
}
//----------------------------------------------------------------------
void Simple3DGame::FinalizeLoadLevel()
{
// This method is called on the main thread, so Direct3D 11 DeviceContext
// method calls are allowable here.
SaveState();
// Finalize the Level loading.
m_renderer->FinalizeLoadLevelResources();
}
//----------------------------------------------------------------------
void Simple3DGame::StartLevel()
{
m_timer->Reset();
m_timer->Start();
if (m_currentLevel == 0)
{
m_gameActive = true;
}
m_levelActive = true;
m_controller->Active(true);
}
//----------------------------------------------------------------------
void Simple3DGame::PauseGame()
{
m_timer->Stop();
SaveState();
}
//----------------------------------------------------------------------
void Simple3DGame::ContinueGame()
{
m_timer->Start();
m_controller->Active(true);
}
//----------------------------------------------------------------------
GameState Simple3DGame::RunGame()
{
m_timer->Update();
m_levelTimeRemaining = m_levelDuration - m_timer->PlayingTime();
if (m_levelTimeRemaining <= 0.0f)
{
// Time expired, so the game is over.
m_levelTimeRemaining = 0.0f;
InitializeAmmo();
m_timer->Reset();
m_gameActive = false;
m_levelActive = false;
SaveState();
if (m_totalHits > m_topScore.totalHits)
{
m_topScore.totalHits = m_totalHits;
m_topScore.totalShots = m_totalShots;
m_topScore.levelCompleted = m_currentLevel;
SaveHighScore();
}
return GameState::TimeExpired;
}
else
{
// Time has not expired, so run one frame of game play.
m_player->Velocity(m_controller->Velocity());
m_camera->LookDirection(m_controller->LookDirection());
UpdateDynamics();
// Update the Camera with the player position updates from the dynamics calculations.
m_camera->Eye(m_player->Position());
m_camera->LookDirection(m_controller->LookDirection());
if (m_level[m_currentLevel]->Update(m_timer->PlayingTime(), m_timer->DeltaTime(), m_levelTimeRemaining, m_object))
{
// The level has been completed.
m_levelActive = false;
InitializeAmmo();
if (m_currentLevel < m_levelCount-1)
{
// More levels to go so increment the level number.
// Actual level loading will occur in the LoadLevelAsync / FinalizeLoadLevel
// methods.
m_timer->Reset();
m_currentLevel++;
m_levelBonusTime = m_levelTimeRemaining;
SaveState();
return GameState::LevelComplete;
}
else
{
// All levels have been completed.
m_timer->Reset();
m_gameActive = false;
m_levelActive = false;
SaveState();
if (m_totalHits > m_topScore.totalHits)
{
m_topScore.totalHits = m_totalHits;
m_topScore.totalShots = m_totalShots;
m_topScore.levelCompleted = m_currentLevel;
SaveHighScore();
}
return GameState::GameComplete;
}
}
}
return GameState::Active;
}
//----------------------------------------------------------------------
void Simple3DGame::OnSuspending()
{
m_audioController->SuspendAudio();
}
//----------------------------------------------------------------------
void Simple3DGame::OnResuming()
{
m_audioController->ResumeAudio();
}
//--------------------------------------------------------------------------------------
void Simple3DGame::UpdateDynamics()
{
float timeTotal = m_timer->PlayingTime();
float timeFrame = m_timer->DeltaTime();
bool fire = m_controller->IsFiring();
#pragma region Shoot Ammo
// Shoot ammo.
if (fire)
{
static float lastFired; // Timestamp of last ammo fired.
if (timeTotal < lastFired)
{
// timeTotal is not guarenteed to be monotomically increasing because it is
// reset at each level.
lastFired = timeTotal - GameConstants::Physics::AutoFireDelay;
}
if (timeTotal - lastFired >= GameConstants::Physics::AutoFireDelay)
{
// Get inverse view matrix.
XMMATRIX invView;
XMVECTOR det;
invView = XMMatrixInverse(&det, m_camera->View());
// Compute initial velocity in world space from camera space.
XMFLOAT4 initialVelocity(0.0f, 0.0f, 15.0f, 0.0f);
m_ammo[m_ammoNext]->Velocity(XMVector4Transform(XMLoadFloat4(&initialVelocity), invView));
// Populate position.
// Offset from the player to avoid an initial collision with the player object.
XMFLOAT4 initialPosition(0.0f, -0.15f, m_player->Radius() + GameConstants::AmmoSize, 1.0f);
m_ammo[m_ammoNext]->Position(XMVector4Transform(XMLoadFloat4(&initialPosition), invView));
// Initially not laying on ground.
m_ammo[m_ammoNext]->OnGround(false);
m_ammo[m_ammoNext]->Active(true);
// Set position in array of next Ammo to use.
// We will reuse ammo taking the least recently used after we've hit the
// MaxAmmo in use.
m_ammoNext = (m_ammoNext + 1) % GameConstants::MaxAmmo;
m_ammoCount = min(m_ammoCount + 1, GameConstants::MaxAmmo);
lastFired = timeTotal;
m_totalShots++;
}
}
#pragma endregion
#pragma region Animate Objects
for (uint32 i = 0; i < m_object.size(); i++)
{
if (m_object[i]->AnimatePosition())
{
m_object[i]->Position(m_object[i]->AnimatePosition()->Evaluate(timeTotal));
if (m_object[i]->AnimatePosition()->IsFinished(timeTotal))
{
m_object[i]->AnimatePosition(nullptr);
}
}
}
#pragma endregion
// If the elapsed time is too long, we slice up the time and
// handle physics over several passes.
float timeLeft = timeFrame;
float elapsedFrameTime;
while (timeLeft > 0.0f)
{
elapsedFrameTime = min(timeLeft, GameConstants::Physics::FrameLength);
timeLeft -= elapsedFrameTime;
// Update the player position.
m_player->Position(m_player->VectorPosition() + m_player->VectorVelocity() * elapsedFrameTime);
// Do m_player / object intersections.
for (uint32 a = 0; a < m_object.size(); a++)
{
if (m_object[a]->Active() && m_object[a] != m_player)
{
XMFLOAT3 contact;
XMFLOAT3 normal;
if (m_object[a]->IsTouching(m_player->Position(), m_player->Radius(), &contact, &normal))
{
XMVECTOR oneToTwo;
oneToTwo = -XMLoadFloat3(&normal);
// Player is in contact with Object.
float impact;
impact = XMVectorGetX(
XMVector3Dot (oneToTwo, m_player->VectorVelocity())
);
// Make sure that the player is actually headed towards the object at grazing angles Doug, this sentence is odd...please have another look there
// could appear to be an impact when the player is actually already hit and moving away
if (impact > 0.0f)
{
// Compute the normal and tangential components of the player's velocity.
XMVECTOR velocityOneNormal = XMVector3Dot(oneToTwo, m_player->VectorVelocity()) * oneToTwo;
XMVECTOR velocityOneTangent = m_player->VectorVelocity() - velocityOneNormal;
// Compute post-collision velocity.
m_player->Velocity(velocityOneTangent - velocityOneNormal);
// Fix the positions so that the ball is exactly GameConstants::AmmoRadius from target.
float distanceToMove = m_player->Radius();
m_player->Position(XMLoadFloat3(&contact) - (oneToTwo * distanceToMove));
}
}
}
}
{
// Do collision detection of the player with the bounding world.
XMFLOAT3 position = m_player->Position();
XMFLOAT3 velocity = m_player->Velocity();
float radius = m_player->Radius();
// Check for player collisions with the walls, floor, or ceiling
// and adjust the position.
float limit = m_minBound.x + radius;
if (position.x < limit)
{
position.x = limit;
velocity.x = -velocity.x * GameConstants::Physics::GroundRestitution;
}
limit = m_maxBound.x - radius;
if (position.x > limit)
{
position.x = limit;
velocity.x = -velocity.x + GameConstants::Physics::GroundRestitution;
}
limit = m_minBound.y + radius;
if (position.y < limit)
{
position.y = limit;
velocity.y = -velocity.y * GameConstants::Physics::GroundRestitution;
}
limit = m_maxBound.y - radius;
if (position.y > limit)
{
position.y = limit;
velocity.y = -velocity.y * GameConstants::Physics::GroundRestitution;
}
limit = m_minBound.z + radius;
if (position.z < limit)
{
position.z = limit;
velocity.z = -velocity.z * GameConstants::Physics::GroundRestitution;
}
limit = m_maxBound.z - radius;
if (position.z > limit)
{
position.z = limit;
velocity.z = -velocity.z * GameConstants::Physics::GroundRestitution;
}
m_player->Position(position);
m_player->Velocity(velocity);
}
// Animate the ammo.
if (m_ammoCount > 0)
{
// Check for inter-ammo collision.
#pragma region inter-ammo collision detection
if (m_ammoCount > 1)
{
for (uint32 one = 0; one < m_ammoCount; one++)
{
for (uint32 two = (one + 1); two < m_ammoCount; two++)
{
// Check collision between instances One and Two.
// vOneToTwo is the collision normal vector.
XMVECTOR oneToTwo;
oneToTwo = m_ammo[two]->VectorPosition() - m_ammo[one]->VectorPosition();
float distanceSquared;
distanceSquared = XMVectorGetX(
XMVector3LengthSq(oneToTwo)
);
if (distanceSquared < (GameConstants::AmmoSize * GameConstants::AmmoSize))
{
oneToTwo = XMVector3Normalize(oneToTwo);
// Check if the two instances are already moving away from each other.
// If so, skip collision. This can happen when a lot of instances are
// bunched up next to each other.
float impact;
impact = XMVectorGetX(
XMVector3Dot(oneToTwo, m_ammo[one]->VectorVelocity()) -
XMVector3Dot(oneToTwo, m_ammo[two]->VectorVelocity())
);
if (impact > 0.0f)
{
// Compute the normal and tangential components of One's velocity.
XMVECTOR velocityOneNormal = (1 - GameConstants::Physics::BounceLost) *
XMVector3Dot(oneToTwo, m_ammo[one]->VectorVelocity()) * oneToTwo;
XMVECTOR velocityOneTangent = (1 - GameConstants::Physics::BounceLost) *
m_ammo[one]->VectorVelocity() - velocityOneNormal;
// Compute the normal and tangential components of Two's velocity.
XMVECTOR velocityTwoN = (1 - GameConstants::Physics::BounceLost) *
XMVector3Dot(oneToTwo, m_ammo[two]->VectorVelocity()) * oneToTwo;
XMVECTOR velocityTwoT = (1 - GameConstants::Physics::BounceLost) *
m_ammo[two]->VectorVelocity() - velocityTwoN;
// Compute post-collision velocity.
m_ammo[one]->Velocity(velocityOneTangent - velocityOneNormal * (1 - GameConstants::Physics::BounceTransfer) +
velocityTwoN * GameConstants::Physics::BounceTransfer);
m_ammo[two]->Velocity(velocityTwoT - velocityTwoN * (1 - GameConstants::Physics::BounceTransfer) +
velocityOneNormal * GameConstants::Physics::BounceTransfer);
// Fix the positions so that the two balls are exactly GameConstants::AmmoSize apart.
float distanceToMove = (GameConstants::AmmoSize - sqrtf(distanceSquared)) * 0.5f;
m_ammo[one]->Position(m_ammo[one]->VectorPosition() - (oneToTwo * distanceToMove));
m_ammo[two]->Position(m_ammo[two]->VectorPosition() + (oneToTwo * distanceToMove));
// Flag the two instances so that they are not laying on ground.
m_ammo[one]->OnGround(false);
m_ammo[two]->OnGround(false);
m_ammo[one]->PlaySound(impact, m_player->Position());
m_ammo[two]->PlaySound(impact, m_player->Position());
}
}
}
}
}
#pragma endregion
#pragma region Ammo-Object intersections
// Check for intersections with Objects
for (uint32 one = 0; one < m_ammoCount; one++)
{
if (m_object.size() > 0)
{
if (!m_ammo[one]->OnGround())
{
for (uint32 i = 0; i < m_object.size(); i++)
{
if (m_object[i]->Active())
{
XMFLOAT3 contact;
XMFLOAT3 normal;
if (m_object[i]->IsTouching(m_ammo[one]->Position(), GameConstants::AmmoRadius, &contact, &normal))
{
XMVECTOR oneToTwo;
oneToTwo = -XMLoadFloat3(&normal);
// Ball is in contact with Object.
float impact;
impact = XMVectorGetX(
XMVector3Dot (oneToTwo, m_ammo[one]->VectorVelocity())
);
// Make sure that the ball is actually headed towards the object at grazing angles. There
// could appear to be an impact when the ball is actually already hit and moving away
if (impact > 0.0f)
{
// Compute the normal and tangential components of One's velocity.
XMVECTOR velocityOneNormal = (1 - GameConstants::Physics::BounceLost) * XMVector3Dot(oneToTwo, m_ammo[one]->VectorVelocity()) * oneToTwo;
XMVECTOR velocityOneTangent = (1 - GameConstants::Physics::BounceLost) * m_ammo[one]->VectorVelocity() - velocityOneNormal;
// Compute post-collision velocity.
m_ammo[one]->Velocity(velocityOneTangent - velocityOneNormal * (1 - GameConstants::Physics::BounceTransfer));
// Fix the positions so that the ball is exactly GameConstants::AmmoRadius from target.
float distanceToMove = GameConstants::AmmoSize;
m_ammo[one]->Position(XMLoadFloat3(&contact) - (oneToTwo * distanceToMove));
// Flag the Ammo as not laying on ground and mark the object as hit if it is a target.
m_ammo[one]->OnGround(false);
// Play the sound associated with the Ammo hitting something.
m_ammo[one]->PlaySound(impact, m_player->Position());
if (m_object[i]->Target() && !m_object[i]->Hit())
{
m_object[i]->Hit(true);
m_object[i]->HitTime(timeTotal);
m_totalHits++;
// Only play target sound if it was an active hit.
m_object[i]->PlaySound(impact, m_player->Position());
}
}
}
}
}
}
}
}
#pragma endregion
#pragma region Apply Gravity and world intersection
// Apply gravity and check for collision against ground and walls.
for (uint32 i = 0; i < m_ammoCount; i++)
{
m_ammo[i]->Position(m_ammo[i]->VectorPosition() + m_ammo[i]->VectorVelocity() * elapsedFrameTime);
XMFLOAT3 velocity = m_ammo[i]->Velocity();
XMFLOAT3 position = m_ammo[i]->Position();
velocity.x -= velocity.x * 0.1f * elapsedFrameTime;
velocity.z -= velocity.z * 0.1f * elapsedFrameTime;
// Apply gravity if the ball is not resting on the ground.
if (!m_ammo[i]->OnGround())
{
velocity.y -= GameConstants::Physics::Gravity * elapsedFrameTime;
}
// Check bounce on ground.
if (!m_ammo[i]->OnGround())
{
float limit = m_minBound.y + GameConstants::AmmoRadius;
if (position.y < limit)
{
// Align the ball with the ground.
position.y = limit;
// Play sound for impact.
m_ammo[i]->PlaySound(-velocity.y, m_player->Position());
// Invert the Y velocity.
velocity.y = -velocity.y * GameConstants::Physics::GroundRestitution;
// X and Z velocity are reduced because of friction.
velocity.x *= GameConstants::Physics::Friction;
velocity.z *= GameConstants::Physics::Friction;
}
}
else
{
// Ball is resting or rolling on ground.
// X and Z velocity are reduced because of friction.
velocity.x *= GameConstants::Physics::Friction;
velocity.z *= GameConstants::Physics::Friction;
}
// Check bounce on ceiling.
float limit = m_maxBound.y - GameConstants::AmmoRadius;
if (position.y > limit)
{
// Align the ball with the ground.
position.y = limit;
// Play sound for impact.
m_ammo[i]->PlaySound(-velocity.y, m_player->Position());
// Invert the Y velocity.
velocity.y = -velocity.y * GameConstants::Physics::GroundRestitution;
// X and Z velocity are reduced because of friction.
velocity.x *= GameConstants::Physics::Friction;
velocity.z *= GameConstants::Physics::Friction;
}
// If the Y direction motion is below a certain threshold, flag the instance as
// laying on the ground.
limit = m_minBound.y + GameConstants::AmmoRadius;
if ((GameConstants::Physics::Gravity * (position.y - limit) + 0.5f * velocity.y * velocity.y) < GameConstants::Physics::RestThreshold)
{
// Align the ball with the ground.
position.y = limit;
// Y direction velocity becomes 0.
velocity.y = 0.0f;
// Flag it.
m_ammo[i]->OnGround(true);
}
// Check bounce on front and back walls.
limit = m_minBound.z + GameConstants::AmmoRadius;
if (position.z < limit)
{
// Align the ball with the wall.
position.z = limit;
// Play sound for impact.
m_ammo[i]->PlaySound(-velocity.z, m_player->Position());
// Invert the Z velocity.
velocity.z = -velocity.z * GameConstants::Physics::GroundRestitution;
}
limit = m_maxBound.z - GameConstants::AmmoRadius;
if (position.z > limit)
{
// Align the ball with the wall.
position.z = limit;
// Play sound for impact.
m_ammo[i]->PlaySound(-velocity.z, m_player->Position());
// Invert the Z velocity.
velocity.z = -velocity.z * GameConstants::Physics::GroundRestitution;
}
// Check bounce on left and right walls.
limit = m_minBound.x + GameConstants::AmmoRadius;
if (position.x < limit)
{
// Align the ball with the wall.
position.x = limit;
// Play sound for impact.
m_ammo[i]->PlaySound(-velocity.x, m_player->Position());
// Invert the X velocity.
velocity.x = -velocity.x * GameConstants::Physics::GroundRestitution;
}
limit = m_maxBound.x - GameConstants::AmmoRadius;
if (position.x > limit)
{
// Align the ball with the wall.
position.x = limit;
m_ammo[i]->PlaySound(-velocity.x, m_player->Position());
// Invert the X velocity.
velocity.x = -velocity.x * GameConstants::Physics::GroundRestitution;
}
m_ammo[i]->Velocity(velocity);
m_ammo[i]->Position(position);
}
}
}
#pragma endregion
}
//----------------------------------------------------------------------
void Simple3DGame::SaveState()
{
// Save basic state of the game.
m_savedState->SaveBool(":GameActive", m_gameActive);
m_savedState->SaveBool(":LevelActive", m_levelActive);
m_savedState->SaveInt32(":LevelCompleted", m_currentLevel);
m_savedState->SaveInt32(":TotalShots", m_totalShots);
m_savedState->SaveInt32(":TotalHits", m_totalHits);
m_savedState->SaveSingle(":BonusRoundTime", m_levelBonusTime);
m_savedState->SaveXMFLOAT3(":PlayerPosition", m_player->Position());
m_savedState->SaveXMFLOAT3(":PlayerLookDirection", m_controller->LookDirection());
// Save the extended state of the game because it is currently in the middle of a level.
if (m_levelActive)
{
m_savedState->SaveSingle(":LevelDuration", m_levelDuration);
m_savedState->SaveSingle(":LevelPlayingTime", m_timer->PlayingTime());
m_savedState->SaveInt32(":AmmoCount", m_ammoCount);
m_savedState->SaveInt32(":AmmoNext", m_ammoNext);
const int bufferLength = 16;
char16 str[bufferLength];
for (uint32 i = 0; i < m_ammoCount; i++)
{
int len = swprintf_s(str, bufferLength, L"%d", i);
Platform::String^ string = ref new Platform::String(str, len);
m_savedState->SaveBool(Platform::String::Concat(":AmmoActive", string), m_ammo[i]->Active());
m_savedState->SaveXMFLOAT3(Platform::String::Concat(":AmmoPosition", string), m_ammo[i]->Position());
m_savedState->SaveXMFLOAT3(Platform::String::Concat(":AmmoVelocity", string), m_ammo[i]->Velocity());
}
m_savedState->SaveInt32(":ObjectCount", static_cast<int>(m_object.size()));
for (uint32 i = 0; i < m_object.size(); i++)
{
int len = swprintf_s(str, bufferLength, L"%d", i);
Platform::String^ string = ref new Platform::String(str, len);
m_savedState->SaveBool(Platform::String::Concat(":ObjectActive", string), m_object[i]->Active());
m_savedState->SaveBool(Platform::String::Concat(":ObjectTarget", string), m_object[i]->Target());
m_savedState->SaveXMFLOAT3(Platform::String::Concat(":ObjectPosition", string), m_object[i]->Position());
}
m_level[m_currentLevel]->SaveState(m_savedState);
}
}
//----------------------------------------------------------------------
void Simple3DGame::LoadState()
{
m_gameActive = m_savedState->LoadBool(":GameActive", m_gameActive);
m_levelActive = m_savedState->LoadBool(":LevelActive", m_levelActive);
if (m_gameActive)
{
// Loading from the last known state means the Game wasn't finished when it was last played,
// so set the current level.
m_totalShots = m_savedState->LoadInt32(":TotalShots", 0);
m_totalHits = m_savedState->LoadInt32(":TotalHits", 0);
m_currentLevel = m_savedState->LoadInt32(":LevelCompleted", 0);
m_levelBonusTime = m_savedState->LoadSingle(":BonusRoundTime", 0.0f);
m_levelTimeRemaining = m_levelBonusTime;
// Reload the current player position and set both the camera and the controller
// with the current Look Direction.
m_player->Position(
m_savedState->LoadXMFLOAT3(":PlayerPosition", XMFLOAT3(0.0f, 0.0f, 0.0f))
);
m_camera->Eye(m_player->Position());
m_camera->LookDirection(
m_savedState->LoadXMFLOAT3(":PlayerLookDirection", XMFLOAT3(0.0f, 0.0f, 1.0f))
);
m_controller->Pitch(m_camera->Pitch());
m_controller->Yaw(m_camera->Yaw());
}
else
{
// Initialize to the beginning.
m_currentLevel = 0;
m_levelBonusTime = 0;
}
// Initialize the state of the Update and Render engines and load the current level.
m_level[m_currentLevel]->Initialize(m_object);
m_levelDuration = m_level[m_currentLevel]->TimeLimit() + m_levelBonusTime;
if (m_gameActive)
{
if (m_levelActive)
{
// Middle of a level so restart where left off.
m_levelDuration = m_savedState->LoadSingle(":LevelDuration", 0.0f);
m_timer->Reset();
m_timer->PlayingTime(m_savedState->LoadSingle(":LevelPlayingTime", 0.0f));
m_ammoCount = m_savedState->LoadInt32(":AmmoCount", 0);
m_ammoNext = m_savedState->LoadInt32(":AmmoNext", 0);
const int bufferLength = 16;
char16 str[bufferLength];
for (uint32 i = 0; i < m_ammoCount; i++)
{
int len = swprintf_s(str, bufferLength, L"%d", i);
Platform::String^ string = ref new Platform::String(str, len);
m_ammo[i]->Active(
m_savedState->LoadBool(
Platform::String::Concat(":AmmoActive", string),
m_ammo[i]->Active()
)
);
if (m_ammo[i]->Active())
{
m_ammo[i]->OnGround(false);
}
m_ammo[i]->Position(
m_savedState->LoadXMFLOAT3(
Platform::String::Concat(":AmmoPosition", string),
m_ammo[i]->Position()
)
);
m_ammo[i]->Velocity(
m_savedState->LoadXMFLOAT3(
Platform::String::Concat(":AmmoVelocity", string),
m_ammo[i]->Velocity()
)
);
}
int storedObjectCount = 0;
storedObjectCount = m_savedState->LoadInt32(":ObjectCount", 0);
storedObjectCount = min(storedObjectCount, static_cast<int>(m_object.size()));
for (int i = 0; i < storedObjectCount; i++)
{
int len = swprintf_s(str, bufferLength, L"%d", i);
Platform::String^ string = ref new Platform::String(str, len);
m_object[i]->Active(
m_savedState->LoadBool(
Platform::String::Concat(":ObjectActive", string),
m_object[i]->Active()
)
);
m_object[i]->Target(
m_savedState->LoadBool(
Platform::String::Concat(":ObjectTarget", string),
m_object[i]->Target()
)
);
m_object[i]->Position(
m_savedState->LoadXMFLOAT3(
Platform::String::Concat(":ObjectPosition", string),
m_object[i]->Position()
)
);
}
m_level[m_currentLevel]->LoadState(m_savedState);
m_levelTimeRemaining = m_level[m_currentLevel]->TimeLimit() + m_levelBonusTime - m_timer->PlayingTime();
}
}
}
//----------------------------------------------------------------------
void Simple3DGame::SaveHighScore()
{
m_savedState->SaveInt32(":HighScore:LevelCompleted", m_topScore.levelCompleted);
m_savedState->SaveInt32(":HighScore:TotalShots", m_topScore.totalShots);
m_savedState->SaveInt32(":HighScore:TotalHits", m_topScore.totalHits);
}
//----------------------------------------------------------------------
void Simple3DGame::LoadHighScore()
{
m_topScore.levelCompleted = m_savedState->LoadInt32(":HighScore:LevelCompleted", 0);
m_topScore.totalShots = m_savedState->LoadInt32(":HighScore:TotalShots", 0);
m_topScore.totalHits = m_savedState->LoadInt32(":HighScore:TotalHits", 0);
}
//----------------------------------------------------------------------
void Simple3DGame::InitializeAmmo()
{
m_ammoCount = 0;
m_ammoNext = 0;
for (uint32 i = 0; i < GameConstants::MaxAmmo; i++)
{
m_ammo[i]->Active(false);
}
}