The next feature that will be added in your game is the countdown at the beginning of each level. It should present numbers, decreased every second, until zero is reached. However, during the countdown period, the player should still be able to steer the rocket, as well as start and stop the additional engine. In this section, you will learn how to implement such a feature and use the translations mechanism.
The countdown feature should be activated only when the game is in the STARTING
state. As soon as the countdown is finished, the state should be changed to PLAYING
. During both stages, the rocket is flying towards the planet, as presented in the following diagram:
The countdown presents a text using a specified font. In this case, Pacaya
with size 120 is chosen. You can generate the sprite font by the following command:
MakeSpriteFont.exe "Pacaya" countdown.spritefont /FontSize:120 /CharacterRegion:0x0000-0x0180
Here, you use an additional option named CharacterRegion
. It allows to specify a range of characters which should be available in the sprite font. In this case, it is required to indicate that Polish characters should be included.
The font file should be saved as countdown.spritefont
in the Assets
directory of the SpaceAim3DComp
project and included into it. You should not forget to set the Content property to Yes, as described during creation of the first .spritefont
file.
An implementation of the countdown feature requires an additional class, named Countdown
, as well as some small modifications in the GameRenderer
class. All necessary changes are described in this section, and important parts of code are presented.
At the beginning, add a few constant values to the Constants.h
file:
#define SA3D_COUNTDOWN_DELAY 1.0f #define SA3D_COUNTDOWN_NUMBER 3 #define SA3D_COUNTDOWN_FONT_FILE L"Assets\countdown.spritefont"
The first constant (SA3D_COUNTDOWN_DELAY
) represents a period of time (in seconds) which should elapse to decrease the counter. The second indicates a maximum number used for the countdown. A value equal to three means that the countdown feature will present the 3
, 2
, and 1
numbers. The last constant value (SA3D_COUNTDOWN_FONT_FILE
) stores a path to the sprite font file.
The main part of the countdown implementation is located in the Countdown
class. It initializes required resources, updates countdown number, and draws it on the screen. The definition of the Countdown
class is as follows:
class Countdown { public: void Prepare(ID3D11Device *device); void Update(float time); void Draw(SpriteBatch *spriteBatch, wstring levelTranslation, float height, float width, int level, float scaleX, float scaleY); void SetStartTime(float time) { m_startTime = time; } void SetCountdownNumber(int nr) { m_countdownNumber = nr; } float GetStartTime() { return m_startTime; } int GetCountdownNumber() { return m_countdownNumber; } private: unique_ptr<SpriteFont> m_font; float m_startTime; int m_countdownNumber; };
At the beginning, include string
, SpriteBatch.h
, SpriteFont.h
, and Constants.h
files. Then, add using
directives for std
and DirectX
namespaces.
The Countdown
class has seven public methods, and four of them are defined inline. The SetStartTime
method allows to set a time, which represents a moment when the countdown is started or when the current number is set. It is changed every time when the countdown number is decreased. The second inline method (SetCountdownNumber
) just updates the number that is presented on the screen. Two last methods return the start time (GetStartTime
) and the current number (GetCountdownNumber
).
The class also contains three private fields. The first represents the font (m_font
). The others are used to store the start time and the countdown number.
The Prepare
method initializes the font required to draw the content (the countdown number and the level number). A name of the file is taken from the constant value, as shown in the following code:
void Countdown::Prepare(ID3D11Device *device)
{
m_font = unique_ptr<SpriteFont>(
new SpriteFont(device, SA3D_COUNTDOWN_FONT_FILE));
}
The Update
method changes the countdown number and start time depending on the total time of the game. Its code is as follows:
void Countdown::Update(float time)
{
if (m_startTime == 0) { m_startTime = time; }
if (m_countdownNumber > 0
&& time - m_startTime > SA3D_COUNTDOWN_DELAY)
{
m_startTime = time;
m_countdownNumber--;
}
}
At the beginning, the start time is set if it has not been already set. It means that the countdown display should be restarted and should present the highest possible countdown number.
The following part of this method checks whether the counter is higher than zero and a suitable period of time elapsed since the last number modification. In such a case, the start time is updated and the countdown number is decreased.
The Draw
method draws both the level number and the countdown number on the screen. It uses the DrawString
method and places texts in proper locations on the screen, as shown in the following block of code:
void Countdown::Draw(SpriteBatch *spriteBatch, wstring levelTranslation, float height, float width, int level, float scaleX, float scaleY) { wstring levelString = levelTranslation + L" " + to_wstring(level); const wchar_t *levelText = levelString.c_str(); XMFLOAT3 levelTextSize; XMStoreFloat3(&levelTextSize, m_font->MeasureString(levelText)); int levelX = (int)((SA3D_MAX_SCREEN_WIDTH-levelTextSize.x) / 2); m_font->DrawString(spriteBatch, levelText, XMFLOAT2(levelX * scaleX, 200 * scaleY), Colors::LightGray, 0, XMFLOAT2(0,0), scaleX); wstring countdownString = to_wstring(m_countdownNumber); const wchar_t *countdownText = countdownString.c_str(); XMFLOAT3 countdownTextSize; XMStoreFloat3(&countdownTextSize, m_font->MeasureString(countdownText)); int countdownX = (int)((SA3D_MAX_SCREEN_WIDTH - countdownTextSize.x) / 2); m_font->DrawString(spriteBatch, countdownText, XMFLOAT2(countdownX * scaleX, 325 * scaleY), Colors::White, 0, XMFLOAT2(0,0), scaleX); }
Some additional modifications need to be made in the GameRenderer
class. They are necessary to update the countdown feature and draw it on the screen correctly.
First of all, you need to include the Countdown.h
and LocalizedStrings.h
files, as well as create a new private field, as presented in the following line of code:
Countdown m_countdown;
In the LoadLevel
method, set the current countdown number to the maximum available value, represented by the SA3D_COUNTDOWN_NUMBER
constant. Then, reset the level start time. This means that in the next Update
call (on the Countdown
instance) the countdown will start. The code is as follows:
void GameRenderer::LoadLevel() { (...) m_countdown.SetCountdownNumber(SA3D_COUNTDOWN_NUMBER); m_countdown.SetStartTime(0); }
In the case of the CreateWindowSizeDependentResources
method, you just call the Prepare
method to initialize the font required by the countdown feature:
void GameRenderer::CreateWindowSizeDependentResources() { Direct3DBase::CreateWindowSizeDependentResources(); m_rocketDisplay.Prepare(m_d3dDevice.Get()); m_countdown.Prepare(m_d3dDevice.Get()); (...) }
In the UpdateStarting
method, call Update
on the CountdownDisplay
instance, passing the total game time as parameter. Then, check whether the current countdown number reaches zero. In such a situation, change the current state to PLAYING
. It prevents the game from drawing the countdown feature until next level is loaded, or the same level is restarted. The code is as follows:
void GameRenderer::UpdateStarting(float timeTotal,float timeDelta) { UpdatePlaying(timeTotal, timeDelta); m_countdown.Update(timeTotal); if (m_countdown.GetCountdownNumber() == 0) { m_game.SetState(SA3D_STATE_PLAYING); } }
The last modification is required in the RenderStarting
method. Here, you draw the countdown feature, using localized strings. It is worth mentioning that you can easily obtain a translation by calling the Get
static method of the LocalizedStrings
class, as presented in the following code snippet:
void GameRenderer::RenderStarting() { RenderPlaying(); wstring levelTranslation = LocalizedStrings::Get(L"Level"); m_countdown.Draw(m_sbDrawing.get(), levelTranslation, m_renderTargetSize.Height, m_renderTargetSize.Width, m_game.GetLevel(), m_scaleX, m_scaleY); }
As you could see, the localized Level string is required to draw the countdown. It should be automatically provided in the valid language, consistent with the current language used by the player. Thus, you will read the language code in the managed part of the game and pass it to the native one.
At the beginning, you should include the LocalizedStrings.h
header file in the Direct3DInterop.h
file. Then, add a new public property, just before the WindowBounds
definition, as presented in the following code:
property Platform::String^ LanguageCode
{
void set(Platform::String^ code)
{ LocalizedStrings::Load(code->Data()); }
}
The LanguageCode
property has only the set
accessor. Thus, whenever you set its value, the Load
static method (of the LocalizedStrings
class) is called. However, it requires the wstring
type as a parameter, but the type of code
argument is Platform::String^
. Fortunately, you can get the pointer to constant wchar_t
data by calling the Data
method.
The remaining modifications are necessary in the managed part, thus you should rebuild the project to ensure that no build errors exist in the native part.
The last change is required in the GamePage.xaml.cs
file. Here, you should assign a value to the LanguageCode
property of the Direct3DInterop
class. You can obtain the current language code using the CultureInfo
class, as presented in the following code snippet:
private void DrawingSurface_Loaded(object sender,RoutedEventArgs e) { if (m_d3dInterop == null) { (...) m_d3dInterop.LanguageCode = CultureInfo.CurrentUICulture.Name; } }
Let's launch the game and see the countdown. You can steer the rocket, avoid all asteroids, and reach the planet. In such a situation, the game loads the second level, and you see the countdown with information that you are on the second level.
It is worth mentioning that the mechanism supports localization and works fine on devices with all three available resolutions, as shown in the following screenshot:
The author strongly encourages you to experiment with 2D graphics on your own. For instance, you could equip your game with additional feature that presents basic instructions how to play, at the beginning of the first level. Thus, the player will not need to read the help and can be easily introduced into the game by additional captions and images, for example, showing the steering concept.
18.118.226.66