Just built an animation class on the same principle as the rest of my resource management classes and it's not working correctly.
Here is the concept. Basically everything in a game from sounds to textures are all stored in global lists. Each is accessed through the controlling class via an ID number. The ID number is nothing special and is usually the index element into the array or vector.
So instead of each object storing an IDirect3DTexture9 object, it only needs to store and ID value. This ID value is then used to access the texture list. The list is public in the management class which is not fancy, but eliminates the need for a function call just for the sake of hiding data. There is no benefit gained here to just hiding data. A set texture call will look like this in my engine:
Code:
Device->SetTexture(0,TextureSystem->m_pTextures[m_iMyTextureID]);
Ok now for the animation. Essentially since all images are contained in the Texture System vector, animation comes down to flipping ID numbers. The set texture function then becomes:
Code:
if (m_iCurrentTextureID!=m_iCurrentFrameTextureID)
{
m_iCurrentTextureID=m_iCurrentFrameTextureID;
Device->SetTexture(0,TextureSystem->m_pTextures[m_iCurrentTextureID];
}
This code will only call set texture if the current ID does not match the current frame's texture ID. So SetTexture() is only called when absolutely necessary - essentially when we have moved forward or backward by (x) frames.
Now the animation class holds a fixed-size array of TextureIDs that are relative to this animation sequence. It also holds a fixed size array of floats that keep track of how long each frame is supposed to be displayed (in ms). This is all done when the animation sequence is created. Essentially if the animation sequence is ever going to be used in the game, you must create it before the level even loads. This might seem strange, but it's very efficient. No sequences are created on the fly.
The problem is that I'm using an integer to access the elements of both arrays and MSVC doesn't seem to like it. Now I've used this same approach with the music system and pretty much everything in the engine, the only difference here is that it must access a float array as well.
Can't you access the elements of a float array using an integer as the index? I thought this was the only way to do it since you cannot use a float as an array index. I've even tried this with vectors, and the value is still getting fragged.
Here is the code I'm having problems with:
Code:
#ifndef CANIMSEQUENCE
#define CANIMSEQUENCE
#include <d3dx9.h>
#include "CTimer.h"
class CAnimSeq
{
private:
bool m_bPlaying;
bool m_bLooping;
//Not used anymore
//friend class CAnimSeqContainer;
//No need to hold actual texture objects
//IDirect3DTexture9 *CurrentFrame;
//Current frame hold time
float m_fCurFrameTime;
//Current texture number (in local array, not global)
//This indexes into array of IDs to extract global ID
//for access to the texture
unsigned int m_iCurrentFrame;
//Array of global IDs
unsigned int *m_pTextureIDs;
//Max number of frames
unsigned int m_iMaxFrames;
//Frame counter - used when creating sequence
unsigned int m_iFrameCount;
//Array of frame hold times in ms
float *m_pFrameTimes;
//Plays the animation
unsigned int Play(float _fTimeDelta)
{
m_fCurFrameTime+=_fTimeDelta;
//This line is the problem - iCurrentFrame is like 36356678
//Get switch time from array for current frame
float SwitchTime=m_pFrameTimes[m_iCurrentFrame];
//If switch time>elapsed time for this frame - we must switch
if (m_fCurFrameTime>SwitchTime)
{
//Reset elapsed frame time and increment frame number
//Current frame
m_fCurFrameTime=0.0f;
m_iCurrentFrame+=1;
if (m_iCurrentFrame>m_iMaxFrames)
{
//Are we a looping animation, if not act accordingly
if (m_bLooping==false)
{
m_bPlaying=false;
m_iCurrentFrame=0;
return 0xFFFF; //0xFFFF means this sequence is done
}
m_iCurrentFrame=0;
}
}
//This is a test - both values are fragged by now
//And I don't know why
char txt[80];
sprintf(txt,"CAnimSeq::Play(): %u %f",m_iCurrentFrame,m_fCurFrameTime);
::MessageBox(0,txt,0,0);
return m_pTextureIDs[m_iCurrentFrame];
}
public:
//Just a constructor
CAnimSeq(void)
{
m_iCurrentFrame=0;
m_fCurFrameTime=0.0f;
m_bPlaying=false;
m_iFrameCount=0;
}
//Just a destructor
virtual ~CAnimSeq(void)
{
delete [] m_pTextureIDs;
delete [] m_pFrameTimes;
}
//Resets the sequence
void Reset(void)
{
m_bPlaying=false;
m_iCurrentFrame=0;
m_fCurFrameTime=0.0f;
m_bLooping=0;
}
//Sets looping attribute
void SetLooping(bool bcond)
{
m_bLooping=bcond;
}
//Called externally to update animation
//Class that uses this system needs to render using
//the texture ID returned from this function
unsigned int Update(float fTimeDelta)
{
unsigned int value=Play(fTimeDelta);
return value;
}
//Create the sequence
void Create(int iMaxFrames)
{
m_pTextureIDs=new unsigned int[iMaxFrames];
m_pFrameTimes=new float[iMaxFrames];
m_iMaxFrames=iMaxFrames;
m_fCurFrameTime=0.0f;
m_iCurrentFrame=0;
m_iFrameCount=0;
}
//Add an image to the sequence
//This in turn adds the image to the global list
//Calling this populates the global list with the actual texture
void AddImage(CTextureManager &TextureSys,std::string _strFile,float _fFrameTime)
{
if (m_iFrameCount<m_iMaxFrames)
{
//Get our ID from the Texture System
unsigned int tempID=TextureSys.Add(_strFile.c_str());
m_pTextureIDs[m_iFrameCount]=tempID;
m_pFrameTimes[m_iFrameCount]=_fFrameTime;
m_iFrameCount+=1;
//Debug messages
char txt[80];
sprintf(txt,"CAnimSeq::Play(): %u %f",m_pTextureIDs[m_iFrameCount],
m_pFrameTimes[m_iFrameCount]);
::MessageBox(0,txt,0,0);
}
}
//Not used - no need
IDirect3DTexture9 *GetFrame(CTextureManager *TextureSys,unsigned int _ID)
{
return TextureSys->m_pTextures[_ID];
}
};
//Not finished
//Eventually will control all sequences
//Plan is that all valid animations will play by calling
//CAnimSeqContainer::Play()
class CAnimSeqContainer
{
private:
static std::vector<CAnimSeq> AnimSeqs;
public:
CAnimSeqContainer(void) {}
virtual ~CAnimSeqContainer(void) {}
unsigned int AddSeq(CAnimSeq &newSeq)
{
AnimSeqs.push_back(newSeq);
return (AnimSeqs.size()-1);
}
//void Update(float _fTimeDelta)
//{
// std::vector<CAnimSeq>::iterator iterAnimSeq;
//
// while (iterAnimSeq!=AnimSeqs.end())
// {
// iterAnimSeq->Update(_fTimeDelta);
// }
//}
};
#endif
I know its a lot of code and you may not like my method, but trust me there is a reason I'm doing it this way. When you start coding Direct3D you will probably understand why I'm limiting calls to SetTexture.
If you have programmed games you will certainly understand why the timing information for the frames is held separate from the actual textures. Note that with this system you could create thousands of instances of the same sequence, but yet be guaranteed only one copy of each texture involved in the sequences is in memory at one time. You can also be assured that each sequence could hold different timing information.
Note that this class also does not render anything - this class simply returns a texture ID representing the current texture ID for the current frame in the global list.