Sign in to follow this  
Add

Sprite number creation [resolved]

Recommended Posts

Add    163
Hey guys, this is gonna be a really stupid thing, but I've been starring at this for a while now and I've not sussed it... 1) Yes I have used sprites... no I don't want to use textured quads, I will in future, but not this time... :) 2) This is in D3D 9 3) There isn't one, here's the description... I've written a class, I called it CNumber. It's job is to create a *simple* imaged number (By taking individual number textures and 'building' the number - left aligned). I pass the 'Draw' method a number as an interger, it splits it up into individual components (I've maxed the number at '999 999' - simply as it's impossible to score even near it in the game I'm creating (simple space invaders clone). Also passed to the method is the draw position on the screen x, and y. 'Number.h'
#ifndef NUMBER_H
#define NUMBER_H

#include <d3d9.h>
#include <d3dx9.h>
#include "utils.h"

namespace NTextures {
	typedef D3DXIMAGE_INFO IINFO;

	static IDirect3DTexture9* _p0Texture;
	static IDirect3DTexture9* _p1Texture;
	static IDirect3DTexture9* _p2Texture;
	static IDirect3DTexture9* _p3Texture;
	static IDirect3DTexture9* _p4Texture;
	static IDirect3DTexture9* _p5Texture;
	static IDirect3DTexture9* _p6Texture;
	static IDirect3DTexture9* _p7Texture;
	static IDirect3DTexture9* _p8Texture;
	static IDirect3DTexture9* _p9Texture;

	void Load(LPDIRECT3DDEVICE9, IINFO);
	void Clean();
	IDirect3DTexture9* ChooseTexture(int i);
}

static const int UNITS			= 1;
static const int TENS			= 2;
static const int HUNDREDS		= 3;
static const int THOUSANDS		= 4;
static const int TENS_OF_THOUSANDS	= 5;
static const int HUNDREDS_OF_THOUSANDS	= 6;

class CNumber {
public:
	CNumber(LPDIRECT3DDEVICE9 dev, bool detailed);
	~CNumber();
	void Draw(int number, int xPos, int yPos);
private:
	void CalculateSize(int num);
	void CreateSprites(LPDIRECT3DDEVICE9 dev);
	void CleanSprites();

	int _iSize;

	bool _bIsDetailed;

	short _sUn;
	short _sTe;
	short _sHu;
	short _sTh;
	short _sTeTh;
	short _sHuTh;

	ID3DXSprite* _pUnitsSprite	  ;
	ID3DXSprite* _pTensSprite	  ;
	ID3DXSprite* _pHundredsSprite     ;
	ID3DXSprite* _pThousandsSprite    ;
	ID3DXSprite* _pTensThousandsSprite;
	ID3DXSprite* _pHundThousandsSprite;
};

#endif








'Number.cpp'
#include "Number.h"

void NTextures::Load(LPDIRECT3DDEVICE9 _pd3dDevice, IINFO d3dxImageInfo){
	Util::myLoadTexture(_pd3dDevice, d3dxImageInfo, _p0Texture,	"./Images/Text/0.jpg",	28, 28);
	Util::myLoadTexture(_pd3dDevice, d3dxImageInfo, _p1Texture,	"./Images/Text/1.jpg",	28, 28);
	Util::myLoadTexture(_pd3dDevice, d3dxImageInfo, _p2Texture,	"./Images/Text/2.jpg",	28, 28);
	Util::myLoadTexture(_pd3dDevice, d3dxImageInfo, _p3Texture,	"./Images/Text/3.jpg",	28, 28);
	Util::myLoadTexture(_pd3dDevice, d3dxImageInfo, _p4Texture,	"./Images/Text/4.jpg",	28, 28);
	Util::myLoadTexture(_pd3dDevice, d3dxImageInfo, _p5Texture,	"./Images/Text/5.jpg",	28, 28);
	Util::myLoadTexture(_pd3dDevice, d3dxImageInfo, _p6Texture,	"./Images/Text/6.jpg",	28, 28);
	Util::myLoadTexture(_pd3dDevice, d3dxImageInfo, _p7Texture,	"./Images/Text/7.jpg",	28, 28);
	Util::myLoadTexture(_pd3dDevice, d3dxImageInfo, _p8Texture,	"./Images/Text/8.jpg",	28, 28);
	Util::myLoadTexture(_pd3dDevice, d3dxImageInfo, _p9Texture,	"./Images/Text/9.jpg",	28, 28);
}

void NTextures::Clean(){
	if( _p0Texture != NULL )
		_p0Texture->Release();
	if( _p1Texture != NULL )
		_p1Texture->Release();
	if( _p2Texture != NULL )
		_p2Texture->Release();
	if( _p3Texture != NULL )
		_p3Texture->Release();
	if( _p4Texture != NULL )
		_p4Texture->Release();
	if( _p5Texture != NULL )
		_p5Texture->Release();
	if( _p6Texture != NULL )
		_p6Texture->Release();
	if( _p7Texture != NULL )
		_p7Texture->Release();
	if( _p8Texture != NULL )
		_p8Texture->Release();
	if( _p9Texture != NULL )
		_p9Texture->Release();
}

IDirect3DTexture9* NTextures::ChooseTexture(int i){
	switch(i){
		case 0:
			return _p0Texture;
		case 1:
			return _p1Texture;
		case 2:
			return _p2Texture;
		case 3:
			return _p3Texture;
		case 4:
			return _p4Texture;
		case 5:
			return _p5Texture;
		case 6:
			return _p6Texture;
		case 7:
			return _p7Texture;
		case 8:
			return _p8Texture;
		case 9:
			return _p9Texture;
	}
	return 0;
}

CNumber::CNumber(LPDIRECT3DDEVICE9 dev, bool detailed){
	// do nothing
	_bIsDetailed = detailed;
	CreateSprites(dev);
}

CNumber::~CNumber(){
	// clean up
	CleanSprites();
}

void CNumber::Draw(int num, int x, int y){
	int i = 0; // position counter
	IDirect3DTexture9* tempTexture = 0;

	float xR = 1.0f, yR = 1.0f;
	if(!_bIsDetailed){
		xR = 0.80f; yR = 0.75f;
	}

	CalculateSize(num);
	
	switch(_iSize){
		case HUNDREDS_OF_THOUSANDS:
			tempTexture = NTextures::ChooseTexture(_sHuTh);
			Util::DrawSprite( _pHundThousandsSprite, tempTexture, (int)(28*xR), (int)(28*yR), (float)x+(28.0f*i), (float)y );
			++i;
		case TENS_OF_THOUSANDS:
			tempTexture = NTextures::ChooseTexture(_sTeTh);
			Util::DrawSprite( _pTensThousandsSprite, tempTexture, (int)(28*xR), (int)(28*yR), (float)x+(28.0f*i), (float)y );
			++i;
		case THOUSANDS:
			tempTexture = NTextures::ChooseTexture(_sTh);
			Util::DrawSprite( _pThousandsSprite, tempTexture,   (int)(28*xR), (int)(28*yR), (float)x+(28.0f*i), (float)y );
			++i;
		case HUNDREDS:
			tempTexture = NTextures::ChooseTexture(_sHu);
			Util::DrawSprite( _pHundredsSprite, tempTexture,   (int)(28*xR), (int)(28*yR), (float)x+(28.0f*i), (float)y );
			++i;
		case TENS:
			tempTexture = NTextures::ChooseTexture(_sTe);
			Util::DrawSprite( _pTensSprite, tempTexture,   (int)(28*xR), (int)(28*yR), (float)x+(28.0*i), (float)y );
			++i;
		case UNITS:
			tempTexture = NTextures::ChooseTexture(_sUn);
			Util::DrawSprite( _pUnitsSprite, tempTexture,   (int)(28*xR), (int)(28*yR), (float)x+(28.0*i), (float)y );
		default:
			break;
	}
}


void CNumber::CalculateSize(int num){
	bool bSizeChosen = false;
	if( num >= 100000 ){
		_iSize = HUNDREDS_OF_THOUSANDS;
		 bSizeChosen = true;
		_sHuTh = (short)(num/100000);
		num -= _sHuTh*100000;
	}
	if( num >= 10000 ){
		if(!bSizeChosen){
            _iSize = TENS_OF_THOUSANDS;
			bSizeChosen = true;
		}
		_sTeTh = (short)(num/10000);
		num -= _sTeTh*10000;
	}
	if( num >= 1000 ){
		if(!bSizeChosen){
            _iSize = THOUSANDS;
			bSizeChosen = true;
		}
		_sTh = (short)(num/1000);
		num -= _sTh*1000;
	}
	if( num >= 100 ){
		if(!bSizeChosen){
            _iSize = HUNDREDS;
			bSizeChosen = true;
		}
		_sHu = (short)(num/100);
		num -= _sHu*100;
	}
	if( num >= 10 ){
		if(!bSizeChosen){
            _iSize = TENS;
			bSizeChosen = true;
		}
		_iSize = TENS;
		_sTe = (short)(num/10);
		num -= _sTe*10;
	}
	if( num >= 0 ){
		if(!bSizeChosen){
            _iSize = UNITS;
			bSizeChosen = true;
		}
		_sUn = (short)(num/1);
	}
}

void CNumber::CreateSprites(LPDIRECT3DDEVICE9 dev){
	D3DXCreateSprite( dev, &_pHundThousandsSprite );
	D3DXCreateSprite( dev, &_pTensThousandsSprite );
	D3DXCreateSprite( dev, &_pThousandsSprite     );
	D3DXCreateSprite( dev, &_pHundredsSprite      );
	D3DXCreateSprite( dev, &_pTensSprite	      );
	D3DXCreateSprite( dev, &_pUnitsSprite	      );
}

void CNumber::CleanSprites(){
	if( _pUnitsSprite != NULL          )
		_pUnitsSprite->Release()         ;
	if( _pTensSprite != NULL           )
		_pTensSprite->Release()          ; 
	if( _pHundredsSprite != NULL       )
		_pHundredsSprite->Release()      ;
	if( _pThousandsSprite != NULL      )
		_pThousandsSprite->Release()     ;
	if( _pTensThousandsSprite != NULL  )
		_pTensThousandsSprite->Release() ;
	if( _pHundThousandsSprite != NULL  )
		_pHundThousandsSprite->Release() ;
}







'Utils.h'
#ifndef UTILS_H
#define UTILS_H

#include <d3d9.h>
#include <d3dx9.h>

namespace Util {
	typedef IDirect3DDevice9* PDEVICE;
	typedef LPDIRECT3DTEXTURE9& RPTEXTURE;
	typedef D3DXIMAGE_INFO IINFO;

	// collection of utility functions.
	void MakeRect(RECT* src, int top, int left, int bottom, int right);
	void myLoadTexture(PDEVICE, IINFO, RPTEXTURE, char*, int, int);
	void DrawSprite(ID3DXSprite* pSprite, RPTEXTURE texture, int xSize, int ySize, float xPos, float yPos);
};


#endif








'Utils.cpp'
#include "utils.h"

void Util::MakeRect(RECT *src, int t, int l, int b, int r){
	src->top    = t;
    src->left   = l;
	src->bottom = b;
    src->right  = r;
}

void Util::myLoadTexture(PDEVICE device, IINFO iInfo, RPTEXTURE texture, char* filename, int width, int height){
	D3DXCreateTextureFromFileEx( device,
                                 filename,
                                 width,  
                                 height,  
                                 1,  
                                 D3DPOOL_DEFAULT,
                                 D3DFMT_UNKNOWN,
                                 D3DPOOL_DEFAULT,
                                 D3DX_DEFAULT,
                                 D3DX_DEFAULT,
                                 D3DCOLOR_COLORVALUE(0.0f,0.0f,0.0f,1.0f),
								 &iInfo,
                                 NULL,
                                 &texture ); 
}

void Util::DrawSprite(ID3DXSprite* pSprite, RPTEXTURE texture, int xSize, int ySize, float xPos, float yPos){
	RECT theRect;
	Util::MakeRect(&theRect, 0, 0, ySize, xSize);

	D3DXVECTOR3 vCenter( 0.0f, 0.0f, 0.0f );
	D3DXVECTOR3 vTopCorner( xPos, yPos, 0.0f );

	pSprite->Begin(D3DXSPRITE_ALPHABLEND);
	pSprite->Draw( texture,
							&theRect,
							&vCenter,
							&vTopCorner,
							0xffffffff /* Colour Filter A = ff, R = ff, G = ff, B = ff */ );
	pSprite->End();
}







The util files listed just in-case any questions arise on these, these are fine though all methods tried and tested. (Might not be the best way, but to get a full game working, they work. Comments welcome though :) ) Ok, the numbers draw fine up-to 99, then things start to go wrong... All help appreciated, Many thanks, Add [Edited by - Add on May 25, 2005 2:38:50 PM]

Share this post


Link to post
Share on other sites
Zahlman    1682
Oh. My. God.

You are familiar with loops and arrays? No? How did you get this far without them? O_O

All your code is unrolled all over the place, with everything being copied and pasted. This is a bad sign. Probably something that had to be changed from one copy to the next didn't get changed quite right, causing a subtle problem.

And sure enough, your problem happens to be a copy-paste error. Specifically:


if( num >= 10 ){
if(!bSizeChosen){
_iSize = TENS;
bSizeChosen = true;
}
_iSize = TENS; // <-- OOPS!
_sTe = (short)(num/10);
num -= _sTe*10;
}



Notice the extra setting of _iSize: this happens even if "bSizeChosen", i.e. for any number 10 or greater, overriding the previous setting. Thus all numbers are effectively treated as having at most two digits. When you write a loop, you can't create this kind of error, because there is only one "copy of the code".

Also, whatever book taught you to name your variables, please throw it out. Don't keep type information in your names, unless it's "soft" type information that the compiler can't already check for you (and C++ compilers are far more sophisticated than C compilers in this regard) - it will slow you down later when you need to change something, looks ugly, and is uninformative (since you have to declare it anyway, you can always just look up the declaration). Also, describe what the *purpose* is in variable names - this is more important the wider the variable's scope is.

Quick attempt at cleanup:

'Numbers.h'

#ifndef NUMBER_H
#define NUMBER_H

#include <d3d9.h>
#include <d3dx9.h>
#include "utils.h"

namespace NTextures {
typedef D3DXIMAGE_INFO IINFO;
static IDirect3DTexture9* digitTextures[10];
void Load(LPDIRECT3DDEVICE9, IINFO);
void Clean();
IDirect3DTexture9* ChooseTexture(int digit);
}

// We are not going to need constants to describe number lengths any more.
// But I should have constants for the number of possible digits (i.e. the base
// in which you are writing the numbers), and the maximum length of numbers as
// it stands. I'll let you fix that though :)

class CNumber {
public:
CNumber(LPDIRECT3DDEVICE9 dev, bool detailed);
~CNumber();
void Draw(int number, int xPos, int yPos);
private:
void CalculateSize(int num);
// I manually inlined and removed CreateSprites and CleanSprites because
// they are only used in the one place. You can re-extract them later if you
// need to (which I doubt will happen).

bool isDetailed;
short digits[6];
int digitCount; // was _iSize - counts how many 'digits' are actually present
ID3DXSprite* digitSprites[6];
};

#endif



'Numbers.cpp'

#include "Number.h"

#include <string>
#include <stdexcept> // I think that's the right one

void NTextures::Load(LPDIRECT3DDEVICE9 _pd3dDevice, IINFO d3dxImageInfo){
for (int i = 0; i < 10; ++i) { // each digit
// Construct the file name
std::string filename = "./Images/Text/" + (i + '0') + ".jpg";
// just try doing THAT with char*'s!
// Ok, fine. Here's that version:
// char* filename = "./Images/Text/*.jpg";
// filename[14] = i + '0';
// Of course, that way you need to keep track of the desired string
// position, and it's much less flexible.
Util::myLoadTexture(_pd3dDevice, d3dxImageInfo, digitTextures[i], filename.c_str(), 28, 28);
}
}

void NTextures::Clean(){
for (int i = 0; i < 10; ++i) {
if (digitTextures[i] != NULL ) { digitTextures[i]->Release(); }
}
}

IDirect3DTexture9* NTextures::ChooseTexture(int i) {
// Not too sure about this; not as familiar with C++ exceptions as with those
// in Java or Python. :s
if (i < 0 || i > 9) throw std::out_of_range("Bad texture ID");
return digitTextures[i];
// See the value of using an array here? :) You might even discard this
// function altogether, although it does give you a bit of encapsulation...
}



// Initializer lists are a good thing.
CNumber::CNumber(LPDIRECT3DDEVICE9 dev, bool detailed) : isDetailed(detailed) {
for (int i = 0; i < 6; ++i) {
D3DXCreateSprite( dev, &digitSprites[i] );
// and a curse upon D3D for requiring you to pass by pointer instead of
// by reference - though to be fair, it *is* a C API.
}
}

CNumber::~CNumber(){
for (int i = 0; i < 6; ++i) {
if( digitSprites[i] != NULL ) { digitSprites[i]->Release(); }
}
}

// You really should consider having the actual number be a property of the
// Number class, and just CalculateSize() once when you construct the object
// (and avoid passing the value to Draw()). Either that, or rename the class.
// Actually, rename it anyway. That leading C gets on my nerves. :)
void CNumber::Draw(int num, int x, int y) {
int i = 0; // position counter
float xR = isDetailed ? 1.0f : .80f;
float yR = isDetailed ? 1.0f : .75f;
CalculateSize(num);
for (int i = 0; i < digitCount; ++i) {
IDirect3DTexture9* tempTexture = NTextures::ChooseTexture(digits[i]);
Util::DrawSprite( digitSprites[i], tempTexture, (int)(28*xR), (int)(28*yR), (float)x+(28.0f*(digitCount - i)), (float)y );
}
}

void CNumber::CalculateSize(int num) {
int currDigit = 0; // index of current digit.
// We will store the number "backwards" in the array, with the ones digit in
// position 0, tens in position 1 etc. This is why we need the "digitCount-i"
// business in Draw().
while (num > 0 && currDigit < 6) { // until we run out of digits
digits[currDigit++] = num % 10;
num /= 10;
// After assigning a digit, currDigit points at the *next* array position
// therefore it counts the number of digits that have been written.
}
// Thus, after doing that work, we have the digit count stored in there.
digitCount = currDigit;
}

Share this post


Link to post
Share on other sites
Add    163
Thanks dude,
well spotted... =)

off to try and test, lol, and yes i'm very aware of loops and arrays... :P
Yeah, it was a very quick knock, up... Currently I knock up then break it down...

back in a sec...

Add

Share this post


Link to post
Share on other sites
Add    163
Just for anyone reading...

std::string filename = "./Images/Text/" + ('0' + i) + ".jpg";
Reports an error, cannot add two pointers.

Solution:
std::string filename = "./Images/Text/";
filename += ('0' + i) + ".jpg";

The reason being afaik is that the comiler takes the two quoted sections as char*'s (char* = pointer to a character array).

Also 'c_str()' returns a 'const char*' - simplily changed create method to accept constants.

In reply to Zahlman, thanks very much. The mistake was when I broke down an even messier class.

I do indeed feel like an absolute pleb. I can't think of an excuse of why I didn't do that in the first place I can't use I didn't know, as I did, I think I need to go away and hide in a corner until this post gets lost in time... I sat there reading this over in the mornign, saying, yes, yes, yes, yes, doh! How on earth did I miss that...



Much appreciated though dude !

Many Thanks,
(A frustratedly happy) Add

Share this post


Link to post
Share on other sites
Add    163
While I'm here I do have another question.
Where i is of type int is
'0' +  i
better than
(char)i
if so/not why?

Thanks in advance,
Add

Share this post


Link to post
Share on other sites
Endurion    5408
That's actually two different things:

The first will work for i's value in the range 0 to 9 and produce an actual character representing i's value. This will only work for numbers with 1 digit.

The second simply casts i's value to the corresponding ASCII character. Alas, 0 doesn't equal the ASCII value for '0'.

Share this post


Link to post
Share on other sites
Zahlman    1682
Quote:
Original post by Add
Just for anyone reading...

std::string filename = "./Images/Text/" + ('0' + i) + ".jpg";
Reports an error, cannot add two pointers.

Solution:
std::string filename = "./Images/Text/";
filename += ('0' + i) + ".jpg";

The reason being afaik is that the comiler takes the two quoted sections as char*'s (char* = pointer to a character array).

Also 'c_str()' returns a 'const char*'


Correct on all counts. My turn to make a silly mistake :) String literals are const char*'s (well, they *should* be const, and things can get messed up if you try to modify them at runtime, but C++ lets you get away with labelling them non-const because otherwise it would break huge amounts of existing C code). The std::string provides an operator overload for the +, but that requires a std::string (not a char*) on at least one side ;) I'm a little surprised that the += works there, though - I would expect the + to be evaluated before the +=. :s

Quote:
- simplily changed create method to accept constants.


Better yet, since it's *your* method, change it to accept std::string&. (Since a std::string is an object, you should pass it by reference to avoid making a copy.) You won't have to mark it const in that case, but you *should*, since logically the method shouldn't change the input. Welcome to the world of const correctness ;) Anyway, the best policy is usually to unwrap std::strings to char* at the last possible moment (e.g. when you pass it to an ifstream constructor or some D3D API), and re-wrap returned char*'s right away.

Quote:
In reply to Zahlman, thanks very much. The mistake was when I broke down an even messier class.

I do indeed feel like an absolute pleb. I can't think of an excuse of why I didn't do that in the first place I can't use I didn't know, as I did, I think I need to go away and hide in a corner until this post gets lost in time... I sat there reading this over in the mornign, saying, yes, yes, yes, yes, doh! How on earth did I miss that...


Glad I could help. Anyway, it's just a matter of discipline, really. I'm hard on people when they do things like this because I see it so often (and often from people that *really* should know better) - so I want to nip bad habits in the bud by scolding the beginners (not too harshly I hope, don't want a warning again) ;)

Quote:

Yeah I know this, but in the context they both work. I was wondering if either had a benefit over the other?


Here's the thing: It will "work" - as in compile - because int and char are both numeric types, and thus an implicit conversion is possible. However, that conversion just leaves you with the same numeric value, so you won't get the desired character at runtime, but instead something from ASCII 0-9. (In the 0 case, that will terminate the string as seen by the creation method, even - std::string can hold the \0, but as soon as the .c_str() is invoked, it is a null terminator for the const char*. There's no way around this for the ifstream constructor, but then, the OS won't let you put \0 in a filename anyway I don't think ;) )

So yes. Another reason to use strings: char is a numeric type which is forced to do double duty as a textual type by means of odd C-legacy hackery. (Of course, std::string is represented with a char* underneath - the data has to be stored somehow - but the object creation provides you with a useful abstraction, a real textual type.)

Share this post


Link to post
Share on other sites
SumDude    163
I figured this out not to long ago and i hate MS for not having tutorials up for it, but ID3DXSprite is basically a device for drawing a sprite.

To draw a sprite you only need one ID3DXSprite to draw any sprite you want. The only thing you need to have multiple is the Textures.

Share this post


Link to post
Share on other sites
Add    163
Quote:
Original post by SumDude
I figured this out not to long ago and i hate MS for not having tutorials up for it, but ID3DXSprite is basically a device for drawing a sprite.

To draw a sprite you only need one ID3DXSprite to draw any sprite you want. The only thing you need to have multiple is the Textures.

Quite correct sir,

Add

Share this post


Link to post
Share on other sites
Add    163
Hi again,

Actually no, neither filename methods works (propperly, ie. for what I want) the second I posted compiles, but doesn't convert the integer correctly to a string.

Solution:
std::stringstream ss;
ss << i;
std::string filename = "./Images/Text/";
filename += ss.str() + ".jpg";
ss.flush();

Is the correct working <Spins on chair> file name creation technique. Just if anyone was intersted... (note the flush to clear the stream)
Also I've note my rating has doubled since making a silly mistake, ironic.

Zahlman - I've made changes to the other methods to accept references too now, thanks for that, for me 'scold' away. I agree it is the best way to make people take notice... (Well me at least, feel free to abuse my in-accurate posts without any offence taken [smile])

And thanks to all that posted,
Add.

:EDIT: for those wondering the header for stringstream is... <sstream>

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this