Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases now! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Learning Windows 8 Game Development

You're reading from   Learning Windows 8 Game Development Windows 8 brings touchscreens to the tablet and PC. This book will show you how to develop games for both by following clear, hands-on examples. Takes your C++ skills into exciting areas of 3D development.

Arrow left icon
Product type Paperback
Published in Oct 2013
Publisher Packt
ISBN-13 9781849697446
Length 244 pages
Edition 1st Edition
Languages
Tools
Arrow right icon
Author (1):
Arrow left icon
Michael Quandt Michael Quandt
Author Profile Icon Michael Quandt
Michael Quandt
Arrow right icon
View More author details
Toc

Understanding the game loop

The core of our game happens in the game loop. This is where all of the logic and subsystems are processed, and the frame is rendered, many times per second. The game loop consists of two major actions: updating and drawing.

Updating the simulation

Now we can get to the action. A game is a simulation of a world, and just like a movie simulates motion by displaying multiple frames per second, games simulate a living world by advancing the simulation many times per second, each time stopping to draw a view into the current state of the game world.

The simplest update will just advance the timer, which is provided to us through the BasicTimer class. The timer keeps track of the amount of time that has elapsed since the previous frame, or since the timer was started—usually the start of the game. Keeping track of the time is important because we need the frame delta (time since the last frame) to correctly advance the simulation by just the right amount. We also work at a millisecond level, sometimes even a fraction of a millisecond, so we need to make use of the floating point data types to appropriately track these values.

Once the time has been updated, most games will accept and parse player input, as this often has the most important effect on the world. Doing this before the rest of the processing means you can act on input immediately rather than delaying by a frame. The amount of time between an input by the player and a reaction in the game world appearing on screen is called latency. Many games that require fast input need to reduce or eliminate latency to ensure the player has a great experience. High latency can make the game feel sluggish or laggy, and frustrate players.

Once we've processed the time and input, we need to process everything else required to make the game world seem alive. This can include (but is not limited to) the following:

  • Physics
  • Networking
  • Artificial Intelligence
  • Gameplay
  • Pathfinding
  • Audio

Drawing the world

Once we have an up-to-date game world, we need to display a view of the world to the players so that they can receive some kind of feedback for their actions. This is done during the draw stage, which can either be the cheapest or most expensive part of the frame, depending on what you want to render.

The Draw() method (sometimes also called the Render() method) is commonly broken down into three sections: clearing the screen, drawing the world, and presenting the frame to the monitor for display.

Clearing the screen

During the rendering of each frame, the same render target is reused. Just like any data, it remains there unless we clear it away before trying to make use of the texture. Clearing the screen is paramount if you use a depth buffer. If you do not clear the depth buffer, the old data will be used and can prevent certain visuals from rendering when they should, as the GPU believes that something has already been drawn in front.

Clearing the render target and depth buffer allows us to reinitialize the data in each pixel to a clean value, ready for use in the new frame.

To clear both the buffers we need to issue two commands, one for each. This is the first time we will use the views that we created earlier. Using our ID3D11DeviceContext, we will call both the ClearRenderTargetView() and ClearDepthStencilView() methods to clear the buffers. For the first method you need to pass a color that will be set across the buffer. In most cases setting black (0, 0, 0, 1 in RGBA) will be enough; however, you may want to set a different color for debug purposes, which you can do here with a simple float array.

Clearing the depth just needs a single floating point value, in this case 1.0f, which represents the farthest distance from the camera. Data in the depth buffer is represented by values between 0 and 1, with 0 being the closest to the camera. We also need to tell the command which parts of the depth buffer we want to clear. We won't use the stencil buffer, so we will just clear the depth buffer using D3D11_CLEAR_DEPTH, and leave the default of 0 for the stencil value.

The auto keyword is a new addition to C++ in the C++11 specifi cation. It allows the compiler to determine the data type, instead of requiring the programmer to specify it explicitly.

auto rtvs = m_renderTargetView.Get();
m_d3dContext->OMSetRenderTargets(
1,
&rtvs,
m_depthStencilView.Get()
);
const float clearCol[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
m_d3dContext->ClearRenderTargetView(
  rtvs,
  clearCol
  );
m_d3dContext->ClearDepthStencilView(
  m_depthStencilView.Get(),
  D3D11_CLEAR_DEPTH,
  1.0f,
  0);

You'll notice that aside from clearing the depth buffer, we're also setting some render targets at the start. This is because in Windows 8 the render target is unbound when we present the frame, so at the start of the new frame we need to rebind the render target so that the GPU has somewhere to draw to.

Now that we have a clean slate, we can start rendering the world. We'll get into this in the next chapter, but this is where we will draw our textures onto the screen to create the world. The user interface is also drawn at this point, and once this stage is complete you should end up with the finished frame, ready for display.

Presenting the back buffer

Once we have a frame ready to display, we need to tell Direct3D that we are done drawing and it can flip the back buffer with the front buffer. To do this we tell the API to present the frame.

When we present the frame, we can indicate to DXGI that it should wait until the next vertical retrace before swapping the buffers. The vertical retrace is the period of time where the monitor is not refreshing the screen. It comes from the days of CRT monitors where the electron beam would return to the top of the screen to start displaying a new frame.

We previously looked at tearing, and how it can impact the visual quality of the game. To fix this issue we use VSync. Try turning off VSync in a modern game and watch for lines in the display where the frame is broken by the new frame.

Another thing we can do when we present is define a region that has changed, so that we do not waste power updating all of the screen when only part of it has changed. If you're working on a game you probably won't need this; however, many other Direct3D applications only need to update part of the screen and this can be a useful optimization in an increasing mobile and low-power world.

To get started, we need to define a DXGI_PRESENT_PARAMETERS structure in which we will define the region that we want to present, as follows:

DXGI_PRESENT_PARAMETERS parameters = {0};
parameters.DirtyRectsCount = 0;
parameters.pDirtyRects = nullptr;
parameters.pScrollRect = nullptr;
parameters.pScrollOffset = nullptr;

In this case we want to clear the entire screen, so Direct3D lets us indicate that by presenting with zero dirty regions.

Now we can commit by using the Present1() method in the swap chain:

m_swapChain->Present1(1, 0, &parameters);

The first parameter defines the sync interval, and this is where you would enable or disable VSync. The interval can be any positive integer, and refers to how many refreshes should complete before the swap occurs. A value of zero here will result in VSync being disabled, while a value of one would lock the presentation rate to the monitor refresh rate. You can also use a value above one, which will result in the refresh rate being divided by the provided interval. For example, if you have a monitor with a 60 Hz refresh rate, a value of one would present at 60 Hz, while a value of two would present at 30 Hz.

At this point, we've done everything we need to initialize and render a frame; however, you'll notice the generated code adds some more lines, as shown in the following code snippet:

m_d3dContext->DiscardView(m_renderTargetView.Get());
m_d3dContext->DiscardView(m_depthStencilView.Get());

These two lines allow the driver to apply some optimizations by hinting that we will not be using the contents of the back buffer after this frame. You can get away with not including these lines if you want, but it doesn't hurt to add them and maybe reap the benefits later.

You have been reading a chapter from
Learning Windows 8 Game Development
Published in: Oct 2013
Publisher: Packt
ISBN-13: 9781849697446
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime