My brain makes things explode.
C++/D3D Pong Tutorial 03: Scene Objects and Sprite Rendering
Tutorial Description
In the last tutorial, we finished getting Direct3D set up, implemented a timer system to allow us to calculate the time between each frame, and set up the framework that will grant us access to our game without interfering with the core engine. In this tutorial, we are going to implement a reusable Scene Object class, which will be the parent for all future classes that we wish to enable in a scene manager. The scope of this tutorial series makes a full-fledged instance management system overkill, so we will not be building one in here. However, as this tutorial series will act as the base for future tutorials that will implement a scene manager, I am going to implement the scene object class here.
Downloads
Binary Only (18.8KB)
Full VS2010 Project Dir (28.4KB)
Starting Words
I just wanted to point out that this tutorial series borrows a lot of the structure of Jonathan Harbour’s Advanced2D game engine, from the book “Advanced 2D Game Development“. While I certainly believe it to be a wonderful book, and an excellent starting point for beginners (in fact, the reason for me borrowing the format for his engine is because I find the method well-suited for beginners), I find that I have a lot of personal quirks with Jonathan’s methods and programming habits, and there are a number of inconsistencies in it. Furthermore, there are some topics that are covered in the book that just aren’t well-suited for beginners, and can cause things to be a bit confusing and convoluted. In this tutorial series, I don’t plan to implement three-dimensional rendering, which he does cover briefly, and have no intention of including game scripting, though several more advanced tutorials will cover these aspects at a later date (read: when I stop being lazy). Anybody looking at learning to program two-dimensional games with DirectX and C++ should look into the book, but should remember that there will always be better ways to implement certain systems than what is shown to you in a book – or an online tutorial. Anybody that wants to gain a solid understanding and truly grasp the depth of these programming systems is highly encouraged to look up information on any subjects that they are unfamiliar with. I strongly recommend Safari Books Online – I read a lot of books there, and it’s certainly helped me improve myself. Advanced 2D Game Development is available on SBO, as well.
Notice: This tutorial continues from where “C++/D3D Pong Tutorial 02” article left off. Please read it if you have not already.
Libraries
You will not need to add any more libraries to your project at this time. As of the last tutorial, the following should be in your project:
d3d9.lib
d3dx9.lib
dxguid.lib
Fixing up some Bugs
While writing this tutorial, I’ve come across a bug in the engine that had remained unnoticed until now. Because of how we’ve set up the game engine to handle the end of the game (specifically, the destruction of the game window), we end up calling our GameEnd function twice, which will cause issues when deleting the game sprites that we are going to be creating in this tutorial. The following changes will fix the problem:
In CGameEngine.h, add:
void SetGameRunning(bool val) { this->_gameRunning = val; }
below the rest of the Mutator functions.
In CGameEngine.cpp, change:
void CGameEngine::End(void)
{
this->_gameRunning = false;
GameEnd();
}
to
void CGameEngine::End(void)
{
GameEnd();
}
In WinMain.cpp, change
case WM_QUIT:
case WM_CLOSE:
case WM_DESTROY:{
GEInstance->End();
} break;
to
case WM_QUIT:
case WM_CLOSE:
case WM_DESTROY:{
GEInstance->SetGameRunning(false);
} break;
and add
// End the Game GEInstance->End();
And our issues should be solved. The problem here is that WM_DESTROY is called a frame after WM_CLOSE/WM_QUIT are called, and the game will still execute a frame. Making these changes will make sure that “D3DPong::CGameEngine::Instance()->_gameRunning” is merely set to false, which we are checking in our Main loop, so that we can just stop execution of the frames.
There is one other error that I’ve uncovered. I’d forgotten to actually clear the scene before we begin rendering. In CGameEngine.cpp, add the following to the BeginRender() method, right after we check the Direct3D Device (and right before we Begin the scene).
this->_device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,0,0), 1.0f, 0);
…Holy hell, I think I wasn’t paying much attention when I first programmed this, but I guess that’s how programming goes. There will always be bugs to fix. Here’s the last one. Our Game Engine’s Update() method wasn’t taking the first frame into account. With how we were handling our delta calculation, that meant that our first frame would have an incredibly high delta. So, replace the update method with the following:
void CGameEngine::Update(void)
{
static float lastFrame = 0.0f;
static bool firstFrame = true;
float delta = ((float)((int)this->_gameTimer->GetTime())) - lastFrame;
lastFrame = ((float)((int)this->_gameTimer->GetTime()));
if(firstFrame)
{
delta = 0;
firstFrame = false;
}
GameUpdate(delta);
this->BeginRender();
GameRender(delta);
this->EndRender();
}
What we are implementing
Since we are starting to get into the juicier parts of the system, I am going to start by providing information on why we are doing things as we are doing them – what the point behind our systems, why certain design decisions are made, etc.
In a game as simple as Pong, we could easily get away with making every scene object a sprite, taking with it all of our rendering capabilities and continuing on with life. However, it is always important to be looking into the future. It’s much better (and easier) to upgrade an engine that you’ve already gotten a lot of work done on than it is to build a new one from scratch. Say, for example, that you are working on a two-dimensional adventure game, such as The Legend of Zelda: The Minish Cap. There are several points in such a game that having scene objects that don’t necessarily have rendering capabilities would be very useful, such as a hidden trap or a trigger area in which the player will constantly lose health. While you could define them and merely not have anything to render for them, they will still have space made for them in the system’s memory for the texture data and all of the little render flags, and rendering methods will always be called on them, creating extra overhead. Therefore, it would be much easier to have a base Scene Object class, without any of the extra rendering data, that would help us to cut down on the game’s resource usage.
For our Scene Object class, we are going to hold basic atomic data, such as the scene object’s position, size, rotation, velocity, acceleration, friction, and a three-dimensional gravity (allowing us to do horizontal “gravity” as well as the usual vertical gravity). We are going to use a three-dimensional vector provided to us by D3DX (the D3DXVECTOR3 struct), rather than defining our own to store this information. Position will be a basic value storing the object’s position, velocity will be a value added to our Position every frame (all of these will be curbed by the frame delta), acceleration will be added to velocity every frame, and friction will then be either added to or subtracted from the velocity before it is finalized. Friction will always go in the direction of a velocity of “zero,” and will have a check to ensure that it is a positive value before modifying the velocity. Gravity will be the last to be applied, and will be the very last thing to be accounted for within the scene object’s velocity.
Code Listing 1: CSceneObject.h
#pragma once
// Global Header Files
#include <Windows.h>
#include <d3d9.h>
#include <d3dx9.h>
namespace D3DPong
{
class CSceneObject
{
private:
// Position, Rotation, and Size data
D3DXVECTOR2 _position;
D3DXVECTOR2 _size;
D3DXVECTOR2 _scale;
float _rotation;
// Velocity and other basic movement data
D3DXVECTOR2 _velocity;
D3DXVECTOR2 _acceleration;
D3DXVECTOR2 _friction;
D3DXVECTOR2 _gravity;
protected:
bool _canUpdate;
bool _canRender;
public:
/** Constructor & Destructor **/
CSceneObject();
~CSceneObject();
/** Function Declarations **/
void Update(float);
/** Accessors & Mutators **/
bool CanRender(void) { return this->_canRender; }
bool CanUpdate(void) { return this->_canUpdate; }
float GetLeft(void) { return this->_position.x; }
float GetRight(void) { return (this->_position.x + (this->_size.x * this->_scale.x)); }
float GetTop(void) { return this->_position.y; }
float GetBottom(void) { return (this->_position.y + (this->_size.y * this->_scale.y)); }
float GetWidth(void) { return this->_size.x; }
float GetHeight(void) { return this->_size.y; }
D3DXVECTOR2 GetCenter(void) { return D3DXVECTOR2(((float)(this->_size.x * this->_scale.x)/2), ((float)(this->_size.y * this->_scale.y)/2)); }
D3DXVECTOR2 GetPosition(void) { return this->_position; }
void SetPosition(D3DXVECTOR2 val) { this->_position = val; }
void SetPositionX(float val) { this->_position.x = val; }
void SetPositionY(float val) { this->_position.y = val; }
D3DXVECTOR2 GetSize(void) { return this->_size; }
void SetSize(D3DXVECTOR2 val) { this->_size = val; }
void SetWidth(float val) { this->_size.x = val; }
void SetHeight(float val) { this->_size.y = val; }
D3DXVECTOR2 GetScale(void) { return this->_scale; }
void SetScale(D3DXVECTOR2 val) { this->_scale = val; }
void SetScaleX(float val) { this->_scale.x = val; }
void SetScaleY(float val) { this->_scale.y = val; }
float GetRotation(void) { return this->_rotation; }
void SetRotation(float val) { this->_rotation = val; }
D3DXVECTOR2 GetVelocity(void) { return this->_velocity; }
void SetVelocity(D3DXVECTOR2 val) { this->_velocity = val; }
void SetVelocityX(float val) { this->_velocity.x = val; }
void SetVelocityY(float val) { this->_velocity.y = val; }
D3DXVECTOR2 GetAcceleration(void) { return this->_acceleration; }
void SetAcceleration(D3DXVECTOR2 val) { this->_acceleration = val; }
void SetAccelerationX(float val) { this->_acceleration.x = val; }
void SetAccelerationY(float val) { this->_acceleration.y = val; }
D3DXVECTOR2 GetFriction(void) { return this->_friction; }
void SetFriction(D3DXVECTOR2 val) { this->_friction = val; }
void SetFrictionX(float val) { this->_friction.x = val; }
void SetFrictionY(float val) { this->_friction.y = val; }
D3DXVECTOR2 GetGravity(void) { return this->_gravity; }
void SetGravity(D3DXVECTOR2 val) { this->_gravity = val; }
void SetGravityX(float val) { this->_gravity.x = val; }
void SetGravityY(float val) { this->_gravity.y = val; }
};
};
It looks big, but it’s not all that hard. Most of the code here is creating the Mutator and Accessor functions. Let’s get cracking, then:
// Position, Rotation, and Size data D3DXVECTOR2 _position; D3DXVECTOR2 _size; D3DXVECTOR2 _scale; float _rotation;
These are three important variables. The first three are all two-dimensional vectors – they hold an X and a Y value. Position will store the object’s position in world space (in this series, world space and screen space are one and the same. In future series, when we implement a sort of two-dimensional camera or viewport system, the two will be different. Size stores the object’s size, in pixels, and will be used later when we implement our basic collision-detection system. The object’s scale represents how large it is. Our sprite class (for two-dimensional images) will scale the image on rendering to this value, and our GetBottom and GetRight take the scale into account. Again, this information will be very important for our collision detection system later. Our final variable, rotation, is a floating-point value representing the number of degrees that our object is rotated by – it can be from zero to three-hundred sixty, though higher (or lower) values will automatically wrap.
// Velocity and other basic movement data D3DXVECTOR2 _velocity; D3DXVECTOR2 _acceleration; D3DXVECTOR2 _friction; D3DXVECTOR2 _gravity;
If these physics variables make you cringe in fear,then let me take this moment to ease your mind by saying that they aren’t even remotely complicated concepts. These are exactly as explained above – velocity is the object’s current, linear velocity, acceleration will be added to the velocity, friction will drag our velocity toward zero, and gravity will drag us toward a certain direction in game space.
protected: bool _canUpdate; bool _canRender;
These protected variables will prove to be very important in future tutorials, when we implement an instance manager. Basically, each sub-class of CSceneObject will set these variables in their constructor. _canUpdate represents whether or not the class implements an Update() method, while _canRender represents whether or not it implements a Render() method.
/** Function Declarations **/ void Update(float);
Finally, we declare an Update() function. We are going to have a default Update() method defined because it is incredibly rare that we will ever need to have a scene object that doesn’t implement updating. We will be able to stop Update from being called, if needed, which can later be checked by an instance manager.
Code Listing 2: CSceneObject.cpp
#include "CSceneObject.h"
namespace D3DPong
{
CSceneObject::CSceneObject(void)
: _position(0,0), _size(0,0),
_scale(1,1), _rotation(0),
_velocity(0,0), _acceleration(0,0),
_friction(0,0), _gravity(0,0),
_canUpdate(true), _canRender(false)
{
}
CSceneObject::~CSceneObject(void)
{
}
void CSceneObject::Update(float delta)
{
// First, add our Acceleration to the object's velocity
this->_velocity += this->_acceleration;
// Now, we need to handle friction. This will be handled based on whether the velocity is positive or negative.
if(this->_velocity.x > 0)
{
this->_velocity.x -= abs(this->_friction.x);
if(this->_velocity.x < 0)
this->_velocity.x = 0;
}
else if(this->_velocity.x < 0)
{
this->_velocity.x += abs(this->_friction.x);
if(this->_velocity.x > 0)
this->_velocity.x = 0;
}
if(this->_velocity.y > 0)
{
this->_velocity.y -= abs(this->_friction.y);
if(this->_velocity.y < 0)
this->_velocity.y = 0;
}
else if(this->_velocity.y < 0)
{
this->_velocity.y += abs(this->_friction.y);
if(this->_velocity.y > 0)
this->_velocity.y = 0;
}
// Finally, it's time to handle gravity
this->_velocity -= this->_gravity;
// And then we add the velocity (multiplied by the frame delta) to the position
this->_position += (this->_velocity * (delta/100));
}
};
This one’s not very large – really, the only important method here is the Update() function, so that’s what I’ll break down:
void CSceneObject::Update(float delta)
{
// First, add our Acceleration to the object's velocity
this->_velocity += this->_acceleration;
// Now, we need to handle friction. This will be handled based on whether the velocity is positive or negative.
if(this->_velocity.x > 0)
{
this->_velocity.x -= abs(this->_friction.x);
if(this->_velocity.x < 0)
this->_velocity.x = 0;
}
else if(this->_velocity.x < 0)
{
this->_velocity.x += abs(this->_friction.x);
if(this->_velocity.x > 0)
this->_velocity.x = 0;
}
if(this->_velocity.y > 0)
{
this->_velocity.y -= abs(this->_friction.y);
if(this->_velocity.y < 0)
this->_velocity.y = 0;
}
else if(this->_velocity.y < 0)
{
this->_velocity.y += abs(this->_friction.y);
if(this->_velocity.y > 0)
this->_velocity.y = 0;
}
// Finally, it's time to handle gravity
this->_velocity -= this->_gravity;
// And then we add the velocity (multiplied by the frame delta) to the position
this->_position += (this->_velocity * (delta/100));
}
The D3DXVECTOR2 structure provides operator support to allow us to easily add and multiply values and the vectors. We can add, multiply, subtract, etc, for example, two vectors, or even a vector and a floating-point value. Doing operations on a vector will perform that operation on both axes dealt with by the vector (X & Y), or on corresponding vectors in the case of vector-to-vector operations (so this->_velocity += this->_acceleration adds acceleration’s X to velocity’s X, and acceleration’s Y to velocity’s Y). My explanation may be a little confusing, but looking at the code should clarify things.
Code Listing 3: CTexture.h
Before we move on to defining the Game Sprite class, we need a class to store our game’s textures. While we could easily just lump this into the Game Sprite class, it makes more sense to move this to its own class – after all, what if we want to implement an object with rendering that doesn’t inherit from the game sprite class? So, here we go. This is pretty simple to implement.
#pragma once
// Global Header Files
#include <Windows.h>
#include <d3d9.h>
#include <d3dx9.h>
namespace D3DPong
{
class CTexture
{
private:
LPDIRECT3DTEXTURE9 _texture;
D3DXIMAGE_INFO _imageInfo;
public:
CTexture(void);
~CTexture(void);
/** Function Declarations **/
bool Load(LPCWSTR);
/** Accessors **/
LPDIRECT3DTEXTURE9 GetTexture(void) { return this->_texture; }
float GetWidth(void) { return (float)this->_imageInfo.Width; }
float GetHeight(void) { return (float)this->_imageInfo.Height; }
};
};
LPDIRECT3DTEXTURE9 _texture; D3DXIMAGE_INFO _imageInfo;
Just a couple small things in the declaration side. The first variable stores the Direct3D texture structure, and will hold the actual image data. The second variable provides us valuable information about the image, and will be populated before we actually load the image. This will allow us to automatically set the texture’s height, width, etc.
bool Load(LPCWSTR);
This function takes one argument – the filename that we want to load. It can be either an absolute location (L”C:/Sprite.png”) or a relative location (“./media/img/Sprite.png”). In any project you run, you’ll generally want to use relative locations. The function will return true if it succeeded in loading the file, or false if it failed.
Code Listing 4: CTexture.cpp
#include "CTexture.h"
#include "CGameEngine.h"
namespace D3DPong
{
CTexture::CTexture(void)
: _texture(NULL)
{
}
CTexture::~CTexture(void)
{
if(this->_texture != NULL)
this->_texture->Release();
}
bool CTexture::Load(LPCWSTR fileName)
{
if(FAILED(D3DXGetImageInfoFromFile(fileName, &_imageInfo)))
{
this->_texture = NULL;
return false;
}
if(FAILED(D3DXCreateTextureFromFileEx(CGameEngine::Instance()->GetDevice(),
fileName, // File Name
this->_imageInfo.Width, this->_imageInfo.Height, 1, // Width, Height, and MIP Level Count
D3DPOOL_DEFAULT, D3DFMT_UNKNOWN, D3DPOOL_DEFAULT, // Usage method, Format, and Pool
D3DX_DEFAULT, D3DX_DEFAULT, // Image and MIP Filters
D3DCOLOR_XRGB(255, 0, 255), // Transparency Key (Magenta)
&this->_imageInfo, NULL, &this->_texture // Image information structure, color pallete, and destination texture
)))
{
this->_texture = NULL;
return false;
}
return true;
}
};
This time, I’m not going to bother re-pasting the code for the Load function, and I’ll just explain it here, since it’s the only thing of importance. Just note that we call the texture structure’s Release() function when we delete the object, to make sure it is cleared from memory.
The first thing we do in the Load function is get the file information from the file. The only parts we’re bothering with are the Width and Height of the image file, but we do get some other information from the file.After that, we actually create the texture from a file, using the aptly-named D3DXCreateTextureFromFileEx. This takes the filename, the image size data, various pieces of information that we’re not going to worry about (usage, format, etc), and we pass the image information and the texture data. The transparency key that we provide will allow us to use a color key transparency method, if we’re not interested in using alpha transparency provided by several formats (such as PNG). Any pixel of Magenta color (Red 255, Green 0, Blue 255) will be completely transparent. D3DX will also automatically, properly handle images that support transparency, so you can save yourself some trouble by using a good image editor that can output alpha transparency (GIMP, Paint.NET, and Photoshop all support these formats).
Code Listing 5: CGameSprite.h
#pragma once
// Global Header Files
#include <Windows.h>
#include <d3d9.h>
#include <d3dx9.h>
// Local Header Files
#include "CSceneObject.h"
#include "CTexture.h"
namespace D3DPong
{
class CGameSprite : public CSceneObject
{
private:
/** Texture Data **/
CTexture* _texture;
bool _visible;
D3DXMATRIX _matRotate, _matScale;
/** Function Declarations **/
void transform(void);
public:
/** Constructor & Destructor **/
CGameSprite(void);
~CGameSprite(void);
/** Function Declarations **/
bool Load(LPCWSTR);
void Render(float);
/** Accessors & Mutators **/
CTexture* GetTexture(void) { return this->_texture; }
void SetTexture(CTexture* val) { this->_texture = val; }
bool IsVisible(void) { return this->_visible; }
void SetVisible(bool val) { this->_visible = val; }
};
};
And here’s our sprite class. Because we’re inheriting from CSceneObject, we don’t need to worry about all of the extra atomic data for the object. We’re going to have to store a texture for the sprite, store whether or not the object is visible (so that we can easily make an object appear and disappear), two Matrices to allow us to apply rotation. When using the D3DXSprite sprite handler, we can easily just provide a position and not worry about matrices at all – which works great for most games. However, because we want to implement scaling and rotation, we will have to use matrices. Matrices are used to provide translation (position), rotation, and scaling in three-dimensional games. They are also used in a number of two-dimensional games, to provide hardware-accelerated graphics functions. The transformation will be performed in the private “transform()” method, which will be called from our Render() method.
Code Listing 6: CGameSprite.cpp
#include "CGameSprite.h"
#include "CGameEngine.h"
namespace D3DPong
{
CGameSprite::CGameSprite(void)
: _texture(NULL), _visible(true)
{
}
CGameSprite::~CGameSprite(void)
{
if(this->_texture != NULL)
delete this->_texture;
}
void CGameSprite::transform(void)
{
D3DXMATRIX mat;
D3DXMatrixTransformation2D(&mat, NULL, 0,
&this->GetScale(), &this->GetCenter(), this->GetRotation(), &this->GetPosition());
CGameEngine::Instance()->GetSpriteHandler()->SetTransform(&mat);
}
bool CGameSprite::Load(LPCWSTR fileName)
{
if(this->_texture != NULL)
delete this->_texture;
this->_texture = new CTexture();
if(this->_texture->Load(fileName))
{
this->SetSize(D3DXVECTOR2(this->_texture->GetWidth(), this->_texture->GetHeight()));
return true;
}
else
return false;
}
void CGameSprite::Render(float)
{
this->transform();
if(this->_texture != NULL && this->_visible)
CGameEngine::Instance()->GetSpriteHandler()->Draw(this->_texture->GetTexture(), NULL, NULL, NULL, D3DCOLOR_XRGB(255, 255, 255));
}
};
It doesn’t look all that difficult, I hope.
void CGameSprite::transform(void)
{
D3DXMATRIX mat;
D3DXMatrixTransformation2D(&mat, NULL, 0,
&this->GetScale(), &this->GetCenter(), this->GetRotation(), &this->GetPosition());
CGameEngine::Instance()->GetSpriteHandler()->SetTransform(&mat);
}
The first method we handle, as explained, creates the transformation matrix, and then sets the current transformation on the sprite handler. We need a D3DXMATRIX variable to store the transformation Matrix, then we create the actual 2D transformation Matrix. We specify that the matrix to output to is our newly-defined mat variable, and we pass our Scale, Rotation, and Position vectors to the object. We also pass a new vector, that returns the center of the object, which will form the base around which our object rotates.
void CGameSprite::Render(float)
{
this->transform();
if(this->_texture != NULL && this->_visible)
CGameEngine::Instance()->GetSpriteHandler()->Draw(this->_texture->GetTexture(), NULL, NULL, NULL, D3DCOLOR_XRGB(255, 255, 255));
}
And here is our rendering method. First, we call our transform, so that we can get the object placed, rotated, and scaled properly. Next, we check to make sure that our texture is loaded, and whether or not our object is actually visible, before we render. This ensures that we don’t try to draw a NULL texture, which would cause our engine to crash. We pass the texture and a color to the Draw() method. The color we pass will saturate the image – we use white, to keep things as they are in the image.
bool CGameSprite::Load(LPCWSTR fileName)
{
if(this->_texture != NULL)
delete this->_texture;
this->_texture = new CTexture();
if(this->_texture->Load(fileName))
{
this->SetSize(D3DXVECTOR2(this->_texture->GetWidth(), this->_texture->GetHeight()));
return true;
}
else
return false;
}
Finally, we delete the old texture (if we want to load a new texture, we want to prevent memory leaks and clean up after ourselves). Then, we recreate the texture object and attempt to load the image, then set the object’s size to match that of the image. Simple, clean, efficient.
Code Listing 7: Game.cpp
We can keep the exact same Game.h that we created in the last project, and provide the following code to test our game engine’s most recent additions:
#include "Game.h"
#include "CGameEngine.h"
#define GEInstance D3DPong::CGameEngine::Instance()
D3DPong::CGameSprite* testSprite;
bool GamePreLoad(void)
{
GEInstance->SetScreenWidth(800);
GEInstance->SetScreenHeight(600);
GEInstance->SetWindowTitle(L"Direct3D Pong Tutorial 03 - Scene Objects and Sprite Rendering");
return true;
}
bool GameInit(void)
{
// Load a test sprite
testSprite = new D3DPong::CGameSprite();
if(!testSprite->Load(L"./media/img/testSprite.png"))
MessageBox(NULL, L"Failed to Load", L":(", MB_OK);
float x = (float)(GEInstance->GetScreenWidth() - testSprite->GetWidth());
float y = (float)(GEInstance->GetScreenHeight() / 2);
testSprite->SetPositionX(x);
testSprite->SetPositionY((y - testSprite->GetCenter().y));
testSprite->SetVelocityX(-10);
return true;
}
void GameUpdate(float delta)
{
testSprite->Update(delta);
if(testSprite->GetLeft() <= 0)
{
testSprite->SetVelocityX(0);
testSprite->SetPositionX(0);
}
}
void GameRender(float delta)
{
testSprite->Render(delta);
}
void GameEnd(void)
{
if(testSprite != NULL)
delete testSprite;
}
All of the code in this test are pretty self-explanatory – we create and load the sprite, then we position it right and centered on the screen, and give the object a negative velocity on the X-axis – making it move to the left until it hits the end and stops.
| Print article | This entry was posted by Samantha on March 21, 2010 at 1:28 PM, and is filed under C++, Development, Pong (C++/DirectX), Series, Tutorials. Follow any responses to this post through RSS 2.0. You can leave a response or trackback from your own site. |

about 5 months ago
Brilliant! So glad I read all of it too. Makes great sense.
Thanks alot.
Ogre.