Chapter 11. Improving Game Experience

In previous chapters, you learned how to create the simple game using both the native and managed development approaches, as well as equip it with some additional features, such as geolocation, augmented reality, and social networks support. However, your game still does not have all the planned functionalities, including a possibility of suspending and resuming the game, as well as supporting audio, video, vibrations, speech synthesis, and recognition. You will get to know how to implement all of them in this chapter.

At the end, you will almost have the final version of your game. Hence, you are quite close to the moment when the game can be submitted to the store!

Saving and loading the game state

The current version of the Game screen does not work correctly in some special circumstances. If you press the Start button or receive a call while playing, the game is stopped and you need to restart it from the first level. In this part, you will learn how to implement a mechanism, which allows saving the current game state. Thus, you will be able to continue playing from the last level.

Concept

The mechanism of saving and loading the game state should allow the user to resume the game from the last saved level. For instance, if the player presses the Start button during the fifth level, he or she can resume the game from the fifth level.

The game state should be saved automatically whenever the level is being loaded. The state needs to be restored when the user navigates to the Game screen, and the file with the game state contains valid data. To prevent the mechanism from loading state when it is unnecessary, the game state is cleared when the game is finished (the Result screen is presented) or when the player presses the Back to menu option on the Pause screen.

Data of the game state will be saved in the simple textual form, which contains the number of the current level, the current score, and the number of remaining rockets. To simplify the mechanism, you assume that the level and the number of remaining rockets are less than 10,000, while the score is less than 1,000,000,000. The format is based on the following pattern: [ level – 4 digits] ; [ score – 9 digits] ; [ number of rockets – 4 digits]. For instance, 0002;000001000;0003 means that the player is currently on the second level, has 1000 points, and possesses three rockets.

Implementation

Creating the mechanism of saving and loading the game state requires modifications in a few files from the native part of the game. At the beginning, you should include the wrl.h header file in the pch.h file. Thus, you will be able to use mechanisms allowing you to save and load the file with the game state. Then, you can proceed to the implementation, which is described in details in this section.

Constants.h

To prepare the mechanism of saving and loading the game state, you require two constant values, which can be defined in the Constants.h file.

The game state data will be stored in the file in the isolated storage. Thus, you specify the file name as a constant value named SA3D_SAVE_FILE. It is worth mentioning that the file has the .txt extension. However, it is not mandatory and you can use other extensions as well. The suitable macro is shown as follows:

#define SA3D_SAVE_FILE L"save.txt"

The second constant value indicates a length of the text with game state data. In this scenario, it is equal to 20, because you will store the level (4 digits), the score (9), the number of rockets (4), as well as semicolon separators (2), and the null character to terminate the string (1). The related line of code is as follows:

#define SA3D_SAVE_FILE_SIZE 20

Game.h

While loading the game state data, you should update values of the level number, the score, as well as the number of remaining rockets. Unfortunately, in the current version of the Game class it is not possible, because suitable fields are private and you do not have additional methods for performing these operations.

To solve this problem, you add three public inline methods that just set values of private fields representing the level number, the score, and the number of rockets:

void SetLevel(int level) { m_level = level; }
void SetScore(int score) { m_score = (float)score; }
void SetRocketsNumber(int rockets) { m_rocketsNumber = rockets; }

GameRenderer.h

All the remaining modifications involve the GameRenderer class. At the beginning, you will modify the header file. Here, you declare three private methods:

void LoadGame();
void SaveGame();
void ClearSavedGame();

The first method (LoadGame) loads the game state from the file, while the second (SaveGame) saves necessary information. The other (ClearSavedGame) removes previously added data from the file to prevent the mechanism from loading the game state when it is not required.

Apart from these three methods, you create a private field (named m_saveFile) representing a path to the file, where all necessary data are saved:

wstring m_saveFile;

At the end, add the using directive regarding the Microsoft::WRL::Wrappers namespace.

GameRenderer.cpp

To create the mechanism of game state saving and loading, you should make some additional modifications in the implementation of the GameRenderer class.

At the beginning, you modify the constructor. Here, a path to the file is created by concatenating three strings: a path to the local folder, "", and a name of the file:

GameRenderer::GameRenderer() : (...)
{
  m_saveFile = Windows::Storage::ApplicationData::Current->LocalFolder->Path->Data();
  m_saveFile.append(L"\");
  m_saveFile.append(SA3D_SAVE_FILE);
}

The SaveGame method is used to save the current state of the game into the file:

void GameRenderer::SaveGame()
{
  FileHandle file(CreateFile2(m_saveFile.c_str(), GENERIC_WRITE,
    FILE_SHARE_WRITE, CREATE_ALWAYS, NULL));
  if (file.Get() != INVALID_HANDLE_VALUE)
  {
    char data[SA3D_SAVE_FILE_SIZE];
    sprintf_s(data, SA3D_SAVE_FILE_SIZE, "%04i;%09i;%04i", 
      m_game.GetLevel(), m_game.GetScore(), 
      m_game.GetRocketsNumber());
    WriteFile(file.Get(), data, ARRAYSIZE(data), nullptr, 
      nullptr);
  }
}

At the beginning, you open the file for writing, using the FileHandler and the CreateFile2 function. Here, you specify a path to the file, as well as additional settings, including access type (GENERIC_WRITE). Then, you verify whether you can open the file. If so, the array for the file content (data) is created.

The sprintf_s function allows you to easily fill the array with the number of current level, the score, and the number of remaining rockets, given in a suitable format. By using the proper format control string (%04i;%09i;%04i), you indicate that three integer values with leading zeros should be used (on 4, 9, and 4 digits, respectively) and separated by semicolons. You can specify these values as the following parameters. In this case, you will write the current level number at the beginning, then (after a semicolon) the score, and (also after a semicolon) the number of rockets. The null character for terminating the string is added automatically.

At the end, you call the WriteFile function to write the created content into the file. This function has a few parameters, including a handle to the file, a pointer to the buffer with data, as well as a number of bytes that should be written.

The LoadGame method loads data from the file. Its code is as follows:

void GameRenderer::LoadGame()
{
  FileHandle file(CreateFile2(m_saveFile.c_str(), GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, NULL));
  if (file.Get() != INVALID_HANDLE_VALUE)
  {
    char data[SA3D_SAVE_FILE_SIZE];
    DWORD readBytes;
    if (ReadFile(file.Get(), data, ARRAYSIZE(data), &readBytes, nullptr) && readBytes == SA3D_SAVE_FILE_SIZE)
    {
      string text(data);
      string levelText = text.substr(0, 4);
      string scoreText = text.substr(5, 9);
      string rocketsText = text.substr(15, 4);
      int level = atoi(levelText.c_str());
      int score = atoi(scoreText.c_str());
      int rockets = atoi(rocketsText.c_str());
      m_game.SetLevel(level);
      m_game.SetScore(score);
      m_game.SetRocketsNumber(rockets);
    }
  }
}

At the beginning, you open the file for reading, by calling the CreateFile2 function. You perform additional operations only if the file is opened successfully. Then, you create the array as a buffer for read data. The second local variable (readBytes) will store the number of read bytes. To read the content of the file, you call the ReadFile function.

You check whether the reading operation is completed successfully and the number of read bytes is equal to the expected one. If so, you create the string instance (based on the content of the file) and three additional ones that represent texts regarding the level number, the score, and the number of rockets. You can easily get them by using the substr method to read only a substring of the text string with a particular length (the second parameter) starting from a given character (its index is specified by the first parameter).

In the following lines, you convert text values into integer numbers, with the usage of the atoi function. At the end, you call SetLevel, SetScore, and SetRocketsNumber methods to update the current game data with ones read from the file.

The ClearSavedGame just saves the empty string to the file. Thus, you indicate that it does not contain enough information, and you cannot load data from it. The code of this method is presented in the following snippet:

void GameRenderer::ClearSavedGame()
{
  FileHandle file(CreateFile2(m_saveFile.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, CREATE_ALWAYS, NULL));
  if (file.Get() != INVALID_HANDLE_VALUE)
  {
    char data[1] = { 0 };
    WriteFile(file.Get(), data, ARRAYSIZE(data), nullptr, nullptr);
  }
}

The game should be saved automatically whenever the new level is loaded. Thus, you call the SaveGame method at the beginning of the LoadLevel one:

void GameRenderer::LoadLevel()
{
  SaveGame(); (...)
}

When the Game screen is opened, you should try to load the previously saved game state. For this reason, the LoadGame method is called at the beginning of the CreateDeviceResources, before loading models, as shown as follows:

void GameRenderer::CreateDeviceResources()
{
  Direct3DBase::CreateDeviceResources();
  LoadGame(); (...)
}

The saved game state should be removed when the player presses the Back to menu option in the Pause screen. Thus, you call the ClearSavedGame in a suitable part of the OnPointerReleasedPause method, as presented in the following code:

SA3D_ACTION GameRenderer::OnPointerReleasedPause(float x, float y)
{
  switch (m_menuPause.OnPress(x, y, m_scaleX, m_scaleY))
  { (...)
    case SA3D_MENU_PAUSE_BACK:
      ClearSavedGame();
      return SA3D_ACTION_BACK_TO_MENU;
  } (...)
}

The last modification is necessary in the Crash method, which is called when the rocket is crashed. When the player runs out of rockets, you will clear the game state as well. The suitable part of code is as follows:

void GameRenderer::Crash()
{ (...)
  if (m_game.GetRocketsNumber() > 0) 
  {
    LoadLevel();
  }
  else 
  { (...)
    ClearSavedGame();
  }
}

The mechanism of loading and saving the game state is now ready. You can launch the game, play a few levels, press the Start button, and then come back to Space Aim 3D. Due to availability of the newly prepared mechanism, you should be able to start playing from the last level, and you do not need to restart the game from the beginning.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.217.164.143