Jump to content
  • Advertisement
Sign in to follow this  
garyfletcher

Cursor control problems with SDL - Almost done..just one more thing

This topic is 4899 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi all. I'm having problems with my cursor control modules. I've decided that my engine will take over cursor processign when ever it is instansiated. The reasons for this is that SDL (my API of choice at the moment) doesn't automatically provide cursor processing in fullscreen mode. Anyway. I've got a cursor class that is dervived from my base sprite class. I'm calling the cursor control during each iteration of the game loop, clearing the old position, calling pumpevents and the SDL_GetRelativeMouseState() for the X and Y deltas, adding the new X and Y to the current X and Y values and then redrawing the cursor sprite. The old position of the cursor isn't being removed, the new cursors Y coordinates doesn't seem to change and the cursor movement isn't smooth at all. I've also noticed that if something is happening on screen, ie. I'm drawing something else, then that stops while the cursor is being moved. I'm assuming that the get mouse state functions wait until there is no mouse movement/action before returning? Does anyone know of any tutorials regarding SDL and cursor controls. The SDL documentation is pretty scant for this? [Edited by - garyfletcher on May 23, 2005 5:26:23 PM]

Share this post


Link to post
Share on other sites
Advertisement
Well, SDL kind of is too low level to support that stuff.

You will have to post some sort of source code for any help...my only idea from what i can see is that the SDL mouse positions are right (the delta's it returns with SDL_GetRelative...() ), but since it has to wait to draw it, the delta's just keep adding up...like the mouse is moving, it just doesnt draw until everything else is done.

But then again, some code would help...

Share this post


Link to post
Share on other sites
Okay. I'll post code. I was trying to avoid this coz it's pretty long.

I'll Add an explination of how the sprites are used as well. Code 1st though..:)

Cursor class:

#ifndef INCLUDE_CURSOR_H
#define INCLUDE_CURSOR_H

#include "SiSEBaseSprite.h"
#include "SiSEVector2D.h"

const std::string CURSKEY = "CURSKEY";
const std::string CLEARKEY = "CURSCLEARKEY";

class SiSECursor : public SiSEBaseSprite
{
public:
SiSECursor(SDL_Surface *screen, float x = 0.0f, float y = 0.0f, float speed = 0.0f);
~SiSECursor();
void mProcessCursor(void);
inline void mEnableCurs(void){mEnabled = true;}
inline void mDisableCurs(void){mEnabled = false;}
inline void mToggleCurs(void){mEnabled = !mEnabled;}
inline bool mIsEnabled(void){return mEnabled;}
inline void mSetClearKey(std::string& clrKey){mClearKey = clrKey;}
inline void mSetCursKey(std::string& cursKey){mCursAnimKey = cursKey;}
inline void mSetDefaults(void){mClearKey = CLEARKEY; mCursAnimKey = CLEARKEY;}

private:
void mSetCursPos(void);
void mAddRelCursPos(void);
void mGetCursPos(void);
bool mEnabled;
int mouseAX;
int mouseAY;
int mouseRX;
int mouseRY;
int button;
std::string mClearKey;
std::string mCursAnimKey;

};

#endif

#include "SiSECursor.h"
#include "SDL/SDL.h"

enum MBUTTONS{NONPRESSED,LPRESSED,RPRESSED};

SiSECursor::SiSECursor(SDL_Surface *screen, float x, float y, float speed) : SiSEBaseSprite(screen,x,y,speed)
{
SDL_ShowCursor(SDL_DISABLE);

mEnabled = true;

mouseAX=0;
mouseAY=0;
mouseRX=0;
mouseRY=0;
button=NONPRESSED;
mCursAnimKey = CURSKEY;
mClearKey = CLEARKEY;
}

SiSECursor::~SiSECursor()
{
mEnabled = false;

SDL_ShowCursor(SDL_ENABLE);

}

void SiSECursor::mProcessCursor()
{

if (mEnabled)
{
mGetCursPos();
mAddRelCursPos();
mAnimKey = mClearKey;
clearBG();
mAnimKey = mCursAnimKey;
draw();
}

return;
}

void SiSECursor::mAddRelCursPos()
{
std::cerr << "mAddRelCursPos() - mouseRX: " << mouseRX << std::endl;
std::cerr << "mAddRelCursPos() - mouseRY: " << mouseRY << std::endl;

xyAdd(mouseRX,mouseRY);
}

void SiSECursor::mSetCursPos()
{
xySet(mouseAX,mouseAY);
}

void SiSECursor::mGetCursPos()
{
mouseAX=0;
mouseAY=0;
mouseRX=0;
mouseRY=0;
button=NONPRESSED;

int height = GetHeight();
int width = GetWidth();

SDL_PumpEvents();

SDL_GetMouseState(&mouseAX,&mouseAY);

if (mouseAX < 0)
{
mouseAX = 0;
}
else if (mouseAX > height)
{
mouseAX = height;
}

if (mouseAY < 0)
{
mouseAY = 0;
}
else if (mouseAY > width)
{
mouseAY = width;
}

SDL_PumpEvents();

SDL_GetRelativeMouseState(&mouseRX,&mouseRY);

if (SDL_GetMouseState(NULL,NULL)&SDL_BUTTON(SDL_BUTTON_LEFT))
{
button = LPRESSED;
}
else if (SDL_GetMouseState(NULL,NULL)&SDL_BUTTON(SDL_BUTTON_RIGHT))
{
button = RPRESSED;
}
}





Base sprite class:

#ifndef INCLUDE_BSPRITE_H
#define INCLUDE_BSPRITE_H

#include <map>
#include <string>
#include "SiSEAnimation.h"
#include "SiSEVector2D.h"

enum SpriteState{NORMAL,HIT,COLLISION,CRITICAL,FIRE,JUMP,LEFT,RIGHT,UP,DOWN,STRAFFE,SPIN,SPINRIGHT,SPINLEFT};

class SiSEBaseSprite
{
public:
SiSEBaseSprite(SDL_Surface *screen, float x = 0.0f, float y = 0.0f, float speed = 0.0f); // Constructor
~SiSEBaseSprite(); // Destructor

void m_addAnimation(SiSEAnimation* SAnim);

// Drawing functions
void draw();
void clearBG();
void updateBG();
void DrawSpriteIMG(SDL_Surface *img, float x, float y);
void mDrawSprite(void);

// Accessor functions
Vector2D* Pos(void){return pos;}
Vector2D* OldPos(void){return oldPos;}
int GetHeight(void){return m_getAnimation()->mGetHeight();}
int GetWidth(void){return m_getAnimation()->mGetWidth();}
void SetFrame(int nr){mFrame = nr;}
int GetFrame(void){return mFrame;}
void SetSpeed(float nr){mSpeed = nr;}
float GetSpeed(void){return mSpeed;}
void SetScreen(SDL_Surface *aScreen){mScreen = aScreen;}
SDL_Surface* GetScreen(void){return mScreen;}
SpriteState GetState(void){return mState;}

// Animations control functions
void ToggleAnim(void){mAnimating = !mAnimating;}
void StartAnim(void){mAnimating = true;}
void StopAnim(void){mAnimating = false;}
void Rewind(void){mFrame = 0;}
bool isAnimating();
void setAnimKey(const std::string& animKey){mAnimKey = animKey;}

// COORD control functions
void xAdd(float nr);
void yAdd(float nr);
void xyAdd(float x, float y);
void xSet(float nr);
void ySet(float nr);
void xySet(float x, float y);

protected:
int mFrame; // Animation frame tracker

// Coordinate vectors
Vector2D* pos;
Vector2D* oldPos;
std::string mAnimKey; // Animation key
bool mDrawn; // Drawn flag
bool mAnimating;
float mSpeed; // Speed indicator (pause multiplier)
long mLastUpdate; // Time sprite was last animated
SiSEAnimation* mSpriteAnim; // Pointer to animations (sprite frames)
typedef std::map<std::string, SiSEAnimation*> mAnimMap;
typedef std::pair<std::string, SiSEAnimation*> mAnimPair;
mAnimMap mAnimations;
SDL_Surface* mScreen; // Pointer to the surface to animate on
SiSEAnimation* m_getAnimation();
SpriteState mState;
};

#endif

#include <cmath>
#include "SiSEBaseSprite.h"
#include "SiSEException.h"

SiSEBaseSprite::SiSEBaseSprite(SDL_Surface *screen, float x, float y, float speed)
{
mFrame = 0; // Animation frame tracker
mDrawn = false; // Drawn flag
mSpeed = speed; // Speed indicator (pause multiplier)
mLastUpdate = 0L; // Time sprite was last animated
mSpriteAnim = 0; // Pointer to animations (sprite frames)
mState = NORMAL; // Set Sprite state
mScreen = screen; // Set screen for output
mAnimating = false;

// Coordinaye vectors
pos = new Vector2D(x,y);
oldPos = new Vector2D();
}

SiSEBaseSprite::~SiSEBaseSprite()
{
mSpriteAnim = 0;

delete pos;
pos = 0;

delete oldPos;
oldPos = 0;

mAnimMap::iterator animIter = mAnimations.begin();
mAnimMap::const_iterator animEnd = mAnimations.end();

while(animIter != animEnd)
{
delete animIter->second;
animIter->second = 0;

++animIter;
}
}

void SiSEBaseSprite::m_addAnimation(SiSEAnimation* SAnim)
{
mAnimMap::iterator animIter = mAnimations.find(mAnimKey);

if (animIter == mAnimations.end())
{
mAnimations.insert(mAnimPair(mAnimKey,SAnim));

if (SAnim->mGetFramesNum() > 1)
mAnimating = true;

}
else
{
throw SiSEException("SiSEBaseSprite() ERROR: Duplicate Animation key.");
}

return;
}

bool SiSEBaseSprite::isAnimating()
{
mAnimMap::iterator animIter = mAnimations.find(mAnimKey);

if (animIter != mAnimations.end())
{
return animIter->second->isAnimating();
}
else
{
throw SiSEException("SiSEBaseSprite() isAnimating() ERROR: Cannot find Animation key.");
}
}

void SiSEBaseSprite::xyAdd(float x, float y)
{
oldPos = pos;
pos->SetX(pos->GetX()+x);
pos->SetX(pos->GetY()+y);
};

void SiSEBaseSprite::xAdd(float nr)
{
oldPos = pos;
pos->SetX(pos->GetX()+nr);

return;
}

void SiSEBaseSprite::yAdd(float nr)
{
oldPos = pos;
pos->SetY(pos->GetY()+nr);

return;
}

void SiSEBaseSprite::xSet(float nr)
{
oldPos = pos;
pos->SetX(nr);

return;
}

void SiSEBaseSprite::ySet(float nr)
{
oldPos = pos;
pos->SetY(nr);

return;
}

void SiSEBaseSprite::xySet(float x, float y)
{
oldPos = pos;
pos->SetXY(x,y);

return;
}

SiSEAnimation* SiSEBaseSprite::m_getAnimation()
{
mAnimMap::iterator mapIter = mAnimations.find(mAnimKey);

if (mapIter != mAnimations.end())
{
return mapIter->second;
}
else
{
std::string errMsg = "SiSEBaseSprite draw() ERROR: Unable to find animation key " + mAnimKey;
throw SiSEException(errMsg);
}
}

// Drawing functions
void SiSEBaseSprite::draw()
{
mSpriteAnim = m_getAnimation();

int numFrames = mSpriteAnim->mGetFramesNum();

if(numFrames > 1 && mAnimating)
{
if((mLastUpdate+mSpriteAnim->mAnim[mFrame]->pause*mSpeed) < SDL_GetTicks())
{
if (mFrame > (numFrames-1))
{
mFrame = 0;
}

mLastUpdate = SDL_GetTicks();
}
}

DrawSpriteIMG(mSpriteAnim->mAnim[mFrame]->image, pos->GetX(), pos->GetY());

if(!mDrawn)
{
mDrawn = true;
}

return;
}

void SiSEBaseSprite::DrawSpriteIMG(SDL_Surface *img, float x, float y)
{
SDL_Rect dest;

dest.x = (Sint16)x;
dest.y = (Sint16)y;

SDL_BlitSurface(img, NULL, mScreen, &dest);

return;
}

void SiSEBaseSprite::clearBG()
{
if(mDrawn)
{
mSpriteAnim = m_getAnimation();

SDL_Rect dest;

dest.x = (Sint16)oldPos->GetX();
dest.y = (Sint16)oldPos->GetY();
dest.w = mSpriteAnim->mGetWidth();
dest.h = mSpriteAnim->mGetHeight();

SDL_BlitSurface(mSpriteAnim->mAnim[0]->image, NULL, mScreen, &dest);

//DrawSpriteIMG(mSpriteAnim->mAnim[mFrame]->image, pos->GetX(), pos->GetY());
}

return;
}

void SiSEBaseSprite::updateBG()
{
SDL_Rect srcRect;

mSpriteAnim = m_getAnimation();

srcRect.w = mSpriteAnim->mGetWidth();
srcRect.h = mSpriteAnim->mGetHeight();
srcRect.x = (Sint16)pos->GetX();
srcRect.y = (Sint16)pos->GetY();
oldPos = pos;

SDL_BlitSurface(mScreen, &srcRect, mSpriteAnim->mAnim[mFrame]->image, NULL);
//DrawSpriteIMG(mSpriteAnim->mAnim[mFrame]->image, pos->GetX(), pos->GetY());

return;
}

void SiSEBaseSprite::mDrawSprite()
{
//clearBG();
//updateBG();
draw();

return;
}





animation class:

#ifndef INCLUDE_SISEANIM_H
#define INCLUDE_SISEANIM_H

#include <string>
#include <vector>
#include "SDL/SDL.h"

class SiSEAnimation
{
private:
typedef struct
{
SDL_Surface *image; // Pointer to frame image
int pause; // Milliseconds to pause after drawing image
} SiSEFrame;

bool mBuilt;
bool mAnimating;
int mNumFrames;
int mWidth;
int mHeight;

public:
SiSEAnimation(const std::string& SpriteFile); // Constructor
~SiSEAnimation(); // Destructor

// Pointer to animations frames
std::vector<SiSEFrame*> mAnim;
typedef std::vector<SiSEFrame*>::iterator animIter;
typedef std::vector<SiSEFrame*>::const_iterator canimIter;

// Accessor functions
inline std::vector<SiSEFrame*>& mGetFrames(void){return mAnim;}
inline bool mIsBuilt(void){return mBuilt;}
inline int mGetFramesNum(void){return mNumFrames;}
inline int mGetWidth(void){return mWidth;}
inline int mGetHeight(void){return mHeight;}
inline animIter mAnimStart(){return mAnim.begin();}
inline animIter mAnimEnd(){return mAnim.end();}
inline bool isAnimating(void){return mAnimating;}

};

#endif

#include <cstdlib>
#include <iostream>
#include <fstream>
#include "SiSEAnimation.h"
#include "SiSEException.h"

using std::string;
using std::cerr;
using std::endl;
using std::ifstream;

SiSEAnimation::SiSEAnimation(const string& SpriteFile)
{
mNumFrames = 0;
string buffer;
string fileName;
string name;
int pause = 0;
int red = 0;
int green = 0;
int blue = 0;
FILE *fp = 0;
mWidth = 0;
mHeight = 0;
mAnimating = false;

// Get our information file for this sprite animation

fileName = SpriteFile;

ifstream inFile(fileName.c_str());

if (!inFile)
{
std::string ErrMsg = "SiSEAnimation() ERROR: Cannot open " + fileName;
throw SiSEException(ErrMsg);
}

getline(inFile,buffer);

sscanf(buffer.c_str(),"FILES: %d",&mNumFrames);

//mAnim = new SiSEFrame[mNumFrames];
mAnim.reserve(mNumFrames);
mBuilt = true;

int frameCount = 0;

while(getline(inFile,buffer))
{

// check for a valid line
if (buffer[0] != '#' && buffer[0] != '\r' && buffer[0] != '\0' &&
buffer[0] != '\n' && buffer.size() != 0)
{
char tmpName[50];

memset(tmpName,0,sizeof(tmpName));

// Read our animation data
sscanf(buffer.c_str(),"%s %d %d %d %d",tmpName,&pause,&red,&green,&blue);

name = tmpName;

// set the sprite file for the current animation frame
fileName = name;

// Read in the current frame
SDL_Surface *temp = 0;

if ((temp = SDL_LoadBMP(fileName.c_str())) == NULL)
{
std::string ErrMsg = "SiSEAnimation() ERROR: " + static_cast<string>(SDL_GetError());
throw SiSEException(ErrMsg);
}

// Check the transprancy colour for the animation
if (red >= 0)
{
SDL_SetColorKey(temp, SDL_SRCCOLORKEY,SDL_MapRGB(temp->format,red,green,blue));
}

// Set the current animation frame to the currently read sprite
// Just set the formats to help speed things up a little when blitting

SiSEFrame* tempFrame = new SiSEFrame;

tempFrame->image = SDL_DisplayFormat(temp);

// Free the temp memory
SDL_FreeSurface(temp);

// Set the pause value
tempFrame->pause = pause;

// Set the width and height if we need to
if (!mWidth)
{
mWidth = tempFrame->image->w;
}

if (!mHeight)
{
mHeight = tempFrame->image->h;
}

mAnim.push_back(tempFrame);

// No dangling
tempFrame = 0;

// Update the count for next one
frameCount++;
}

}

if (frameCount > 1)
mAnimating = true;

inFile.close();

}

SiSEAnimation::~SiSEAnimation()
{
animIter aIter = mAnim.begin();
canimIter animEnd = mAnim.end();

while (aIter != animEnd)
{
delete *aIter;
*aIter = 0;
++aIter;
}

}






game loop (called from stack):

void SiSE::m_gameLoop(void)
{
/* The game loop. Loop until the game stack is empty */
while(!m_StateStack.empty())
{
if ((SDL_GetTicks() - m_Timer) >= FRAME_RATE)
{
/* In order to use the function pointed to on top of the stack */
/* it needs to be called with an object of the appropriate type */
/* In this case a SiSE object. Pass the function pointer */
/* to a member function that will execute it as (this->*funcPtr)() */
ExecuteFunc(m_StateStack.top().funcPtr);

// Do cursor processing
cerr << "About to process cursor" << endl;
cursor->mProcessCursor();
cerr << "Cursor processed" << endl;

m_Timer = SDL_GetTicks();
}
}

return;

}





In order to load multiple animations for a sprite I use a map of animations using a key to indicate which animations we are currently working on. So to inistansiate a sprite an key is set and a set of animations or a single frame loaded into the sprite:

/* Cursor */
baseCursor = new SiSEAnimation("cursor/CursInfo");
clearCursor = new SiSEAnimation("cursor/ClearCursInfo");
cursor = new SiSECursor(m_Window,250.0f,250.0f);
cursor->setAnimKey(CLEARKEY);
cursor->m_addAnimation(clearCursor);
cursor->setAnimKey(CURSKEY);
cursor->m_addAnimation(baseCursor);

Hope someone can help..:)

For anyone interested the Vector2D class is shown below:


#ifndef INCLUDE_VEC2D_H
#define INCLUDE_VEC2D_H

typedef float SCALAR;

class Vector2D
{
public:
Vector2D();
Vector2D(const SCALAR& a, const SCALAR& b);
~Vector2D();
SCALAR& operator[](const long i);
const bool operator==(const Vector2D& v) const;
const bool operator!=(const Vector2D& v) const;
const Vector2D operator-(void) const;
const Vector2D& operator=(const Vector2D& v);
const Vector2D& operator+=(const Vector2D& v);
const Vector2D& operator-=(const Vector2D& v);
const Vector2D& operator*=(const SCALAR& s);
const Vector2D& operator/=(const SCALAR& s);
const Vector2D operator+(const Vector2D& v) const;
const Vector2D operator-(const Vector2D& v) const;
const Vector2D operator*(const SCALAR& s) const;
friend inline const Vector2D operator* (const SCALAR& s, const Vector2D v){return v*s;}
const Vector2D operator/(const SCALAR& s) const;
const Vector2D perpen(void) const;
const SCALAR dot(const Vector2D& v) const;
const SCALAR length(void) const;
const Vector2D unit(void) const;
void normalise(void);
const bool nearlyEquals(const Vector2D& v, const SCALAR e) const;
void SetX(SCALAR xPos){x=xPos; calcMagnitude();}
SCALAR GetX(void){return x;}
void SetY(SCALAR yPos){y=yPos; calcMagnitude();}
SCALAR GetY(void){return y;}
void SetXY(SCALAR xPos, SCALAR yPos){x=xPos; y=yPos; calcMagnitude();}
double GetMagSQRD(void){return magnitude;}

private:
void calcMagnitude(void);
SCALAR x;
SCALAR y;
double magnitude;
};

typedef Vector2D POINT;

#endif

#include "SiSEVector2D.h"
#include <cmath>

Vector2D::Vector2D():x(0), y(0), magnitude(0.0)
{
}

Vector2D::Vector2D(const SCALAR& a, const SCALAR& b):x(a), y(b)
{
calcMagnitude();
}

Vector2D::~Vector2D()
{
}

void Vector2D::calcMagnitude(void)
{
magnitude = (fabs(x*x)+fabs(y*y));
}

SCALAR& Vector2D::operator[](const long i)
{
return *((&x)+i);
}

const bool Vector2D::operator==(const Vector2D& v) const
{
return (v.x==x && v.y==y);
}

const bool Vector2D::operator!=(const Vector2D& v) const
{
return !(v==*this);
}

const Vector2D Vector2D::operator-(void) const
{
return Vector2D(-x,-y);
}

const Vector2D& Vector2D::operator=(const Vector2D& v)
{
x=v.x;
y=v.y;

return *this;
}

const Vector2D& Vector2D::operator+=(const Vector2D& v)
{
x+=v.x;
y+=v.y;

return *this;
}

const Vector2D& Vector2D::operator-=(const Vector2D& v)
{
x-=v.x;
y-=v.y;

return *this;
}

const Vector2D& Vector2D::operator*=(const SCALAR& s)
{
x*=s;
y*=s;

return *this;
}

const Vector2D& Vector2D::operator/=(const SCALAR& s)
{
const SCALAR r = 1/s;

x*=r;
y*=r;

return *this;
}

const Vector2D Vector2D::operator+(const Vector2D& v) const
{
return Vector2D(x+v.x,x+v.y);
}

const Vector2D Vector2D::operator-(const Vector2D& v) const
{
return Vector2D(x-v.x,y-v.y);
}

const Vector2D Vector2D::operator*(const SCALAR& s) const
{
return Vector2D(x*s, y*s);
}

const Vector2D Vector2D::operator/(const SCALAR& s) const
{
SCALAR t=1/s;

return Vector2D(t*x,t*y);
}

const Vector2D Vector2D::perpen(void) const
{
return Vector2D(this->y, -(this->x));
}

const SCALAR Vector2D::dot(const Vector2D& v) const
{
return x*v.x + y*v.y;
}

const SCALAR Vector2D::length(void) const
{
return (SCALAR)sqrt((double)this->dot(*this));
}

const Vector2D Vector2D::unit(void) const
{
return (*this)/length();
}

void Vector2D::normalise(void)
{
(*this)/=length();
}

const bool Vector2D::nearlyEquals(const Vector2D& v, const SCALAR e) const
{
return fabs(x-v.x)<e && fabs(y-v.y) < e;
}




[Edited by - garyfletcher on May 23, 2005 1:45:49 AM]

Share this post


Link to post
Share on other sites
Slightly unrelated, what sort of problems were you having with the mouse in fullscreen mode? I've had no problems with SDL in fullscreen with the mouse in my tests (both Windows and Linux).

Share this post


Link to post
Share on other sites
It might be different under Linux but with windows cursor control isn't provided with SDL.

Not too sure why...think I read somewhere it's to do with problems with directly accessing hardware, but some cards provide support.

Although don't quote me on that..:)

Share this post


Link to post
Share on other sites
Okay I'm almost there.

I've now got a cursor on screen and moving and got mouse events being detected...hooray.

I have noticed though that when a mouse even is taking place, such as mouse motion, background rendering starts slowing down.

Is there anything that I can do to prevent this?

My code is now quite simple:


void SiSECursor::mGetCursPos()
{
SDL_Event event;

button = NONPRESSED;

if (SDL_PollEvent(&event))
{
if (event.type == SDL_MOUSEMOTION )
{
SDL_GetMouseState(&mouseAX,&mouseAY);
}
else if (event.type == SDL_MOUSEBUTTONDOWN )
{
if (SDL_GetMouseState(NULL,NULL)&SDL_BUTTON(SDL_BUTTON_LEFT))
{
button = LPRESSED;
}
else if (SDL_GetMouseState(NULL,NULL)&SDL_BUTTON(SDL_BUTTON_RIGHT))
{
button = RPRESSED;
}
}
}
}



Any suggestions?

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!