Weapons

Published June 06, 2009
Advertisement



Windows Demo (.zip) (untested for Vista/7, should work fine on XP) 8.5MB
I'd be interested to know if/how it runs on your system. Any comments welcome.

IMPORTANT: The first line in "config.cfg" needs to be the path of the folder. If it doesn't work, please let me know. Also, a value of '1' for VSYNC doesn't really work yet.

Controls:
E,S,D,F = movement (this is so your pinky can use 'A')
A = dash (too powerful, and not working correctly)
1-5 = cycle camera size
Z,X,C,V,B,N = the different weapons
LMB = fire weapon
mouse wheel up/down, and button = changes the camera between 2-4 (for ease)
Esc = quits
F5 = take screenshot


I've figured out why I'm so slow to implement some things. I tend to write my abstract classes first, and gradually define the more specific classes to fit my class hierarchy. This takes forever because I get really picky if something doesn't fit perfectly with what's established, and I waste a lot of time trying to make everything perfect. My new method (common sense) is to write the classes that actually DO SOMETHING first, so I have new features implemented even if the code design isn't pretty. Then I can consolidate the common functionality afterward.

My goals for this month are to get a weapon/projectiles system going, get sound working, add some content, and if time allows, experiment with gameplay. Gameplay is my highest priority as a designer, but I need to focus on other important things for now.

I've started the weapons system. The system will manage all projectiles within the level, fired from any source. At first I thought it would make sense for each armed character to "own" it's own projectile system, but then enforcing a memory limit would be difficult. So instead the level "owns" the projectile manager and gives the pointer to a static member of the character base class.

// .htypedef enum {	WEAPON_NULL,	WEAPON_BLASTER} weaponType; class WeaponSystem{    public:        WeaponSystem();        ~WeaponSystem();        void Fire( b2World* world, weaponType type, const vec2& origin, const vec2& trajectory, int weaponMode );        void Update(ulong dT);        void Render();    private:        static const int blasterNum = 100;        std::vector< BlasterRound* > blasterRounds;        std::vector< BlasterRound* >::iterator blasterRoundsIt;        BlasterRound* blasterMother;};// .cppWeaponSystem::WeaponSystem(){    blasterMother = new BlasterRound;    blasterMother->LoadXml( "blaster.xml" );    blasterRounds.reserve( blasterNum );    for( int i=0; i < blasterNum; i++ )    {	    blasterRounds.push_back( new BlasterRound( *blasterMother ) );    }    blasterRoundsIt = blasterRounds.begin();    //delete blasterMother;     //blasterMother = NULL;}WeaponSystem::~WeaponSystem(){    for( int i=0; i < blasterNum; i++ )	    delete blasterRounds;    blasterRounds.clear();    ////////////////////////////////////////////////////////////////////////////    // no heap corruption when this is released after the blasterRounds!    delete blasterMother;     ////////////////////////////////////////////////////////////////////////////}void WeaponSystem::Fire( b2World* world, weaponType type, const vec2& origin, const vec2& trajectory, int weaponMode ){    switch( type )    {        case WEAPON_BLASTER:	        for( int i = 0; i < blasterNum; i++, blasterRoundsIt++ )		{			if( blasterRoundsIt == blasterRounds.end() )				blasterRoundsIt = blasterRounds.begin();			if( (*blasterRoundsIt)->IsActive() == false )			{				(*blasterRoundsIt)->Fire( world, origin, trajectory, weaponMode );				break;			}		}	break;	default:	    break;    }}void WeaponSystem::Update(ulong dT){    for( int i=0; i < blasterNum; i++ )	blasterRounds->Update( dT );}void WeaponSystem::Render(){    for( int i=0; i < blasterNum; i++ )	blasterRounds->Render();}


So here's the weapon system class. It just creates a round, called blasterMother, and copies the instance so the XML file is only loaded once. The number of active rounds is limited to 100 at the moment. I was thinking of borrowing an idea from Halo and making some weapons overheat if you shoot them for too long. That would give a reason for why you can't shoot an unlimited number of rounds (other than ammo), and force the player to break for cover once in awhile.

If I delete the blasterMother pointer in the constructor, it causes a heap corruption. If I delete it in the weaponSystem destructor its fine, but I don't want to have unused instances laying around because I can't figure it out. I think my Entity/Sprite/BlasterRound copy contructors are good. Nothing else causes the heap corruption...


// .h#include "Sprite.h"// temporary!enum{    MODE_NORMAL = 0,    MODE_BOUNCY,    MODE_FLAME,    MODE_HEAVY,    MODE_HIGHCALIBER,    MODE_STICKY};class BlasterRound : public Sprite{    public:        BlasterRound();        BlasterRound( const BlasterRound& );        virtual ~BlasterRound();        bool IsActive(){ return active; }        void Fire( b2World* world, const vec2& origin, const vec2& trajectory, int weaponMode );        virtual void onLoad();        virtual void onUpdate( ulong dT );        virtual void onRender( float blend );        virtual void onCollision(const contactPoint& point);    protected:        int damage;        bool active;        bool render;        bool exploded;                        int hitCount;        int timeLeft;        int explodeTime;        b2Vec2 initialPos;        b2Vec2 collidePosition;        // temporary!!!        int mode;        bool stickyFlag;        b2Joint* stickyJoint;        b2Body* stickToBody;        //b2CircleDef expDef;         //b2Shape* expShape;};// .cppBlasterRound::BlasterRound():    damage( 1 ),    active( false ),    render( false ),    exploded( false ),    hitCount( 0 ),    timeLeft( 1000 ),    explodeTime( 0 )        //expShape( NULL ){    mode = MODE_NORMAL;    stickyFlag = false;    stickyJoint = NULL;    stickToBody = NULL;}BlasterRound::BlasterRound( const BlasterRound& rhs ):    Sprite( rhs ),    damage( rhs.damage ),    active( rhs.active ),    render( rhs.render ),    exploded( rhs.exploded ),    hitCount( rhs.hitCount ),    timeLeft( rhs.timeLeft ),    explodeTime( rhs.explodeTime )        //expShape( NULL ){    mode = MODE_NORMAL;    stickyFlag = false;    stickyJoint = NULL;    stickToBody = NULL;    exploded = false;}BlasterRound::~BlasterRound(){}void BlasterRound::Fire( b2World* world, const vec2& origin, const vec2& trajectory, int weaponMode ){    active = true;    hitCount = 0;    explodeTime = 200;    exploded = false;    render = true;    //////////////    //bodies[0]->pointer->SetBullet(true);        // this didn't work    bodies[0]->isBullet = true;    //bodies[0]->shapes[0]->SetCollisionProfile( CB_FRIENDLY );    //bodies[0]->shapes[0]->SetCollisionGroup( 2 );    ////////////    vec2 variance;    variance.x = (random_f()-0.5f) * 0.04f;        // accuracy (between .5 and .01)    variance.y = (random_f()-0.5f) * 0.04f;    float multiplier = 20.0f;    mode = weaponMode;    switch( mode )    {        case MODE_FLAME:            SetCurrentSequence("explode");            multiplier = 20.0f;            timeLeft = 300;        break;        case MODE_HIGHCALIBER:            SetCurrentSequence("idle");            multiplier = 80.0f;            timeLeft = 500;            variance.x = (random_f()-0.5f) * 0.01f;        // accuracy (between .5 and .01)            variance.y = (random_f()-0.5f) * 0.01f;        break;        case MODE_HEAVY:            SetCurrentSequence("idle");            multiplier = 20.0f;            timeLeft = 5000;        break;                case MODE_BOUNCY:            SetCurrentSequence("idle");            multiplier = 30.0f;            timeLeft = 300;        break;        default:            SetCurrentSequence("idle");            multiplier = 40.0f;            timeLeft = 1000;        break;    }    setPosition( origin );    Create( world );    initialPos = b2Vec2( origin.x, origin.y );    variance += trajectory;    b2Vec2 velocity( variance.x, variance.y );    velocity *= multiplier;    bodies[0]->pointer->SetLinearVelocity( velocity );    }void BlasterRound::onLoad(){    timeLeft = 0;    explodeTime = 0;}void BlasterRound::onUpdate( ulong dT ){    if( !active )        return;    if( exploded )    {        if( explodeTime <= 0 )        {            /*switch( mode )    // trying to release the joint apparently deletes twice. Need to find out why.            {                case MODE_STICKY:                    if( stickyJoint )                    {                        world->DestroyJoint( stickyJoint );                        stickyJoint = NULL;                    }                break;                default: break;            }*/            active = false;            render = false;                exploded = false;            DestroyCollisionModel();        }else             explodeTime -= dT;    }    else    {        // the round needs to start the explode/death sequence        if( timeLeft <= 0 )        {            exploded = true;            SetCurrentSequence("explode");            switch( mode )            {                case MODE_BOUNCY:                    DestroyCollisionModel();                break;                case MODE_STICKY:                break;                default:                    setPosition( vec2(collidePosition.x, collidePosition.y) );                break;            }        }        else    // the round is still active, update timer        {            timeLeft -= dT;            switch( mode )            {                case MODE_FLAME:                    bodies[0]->pointer->ApplyForce( b2Vec2(0,(float)dT * 0.05f), bodies[0]->pointer->GetWorldPoint(b2Vec2(0,0)) );                break;                 case MODE_STICKY:                {                    // stuck to something                    if( stickyFlag )                    {                        setPosition( vec2(collidePosition.x, collidePosition.y) );                        bodies[0]->pointer->SetLinearVelocity( b2Vec2(0.0f,0.0f) );                        if( bodies[0]->pointer && stickToBody )                        {                            b2DistanceJointDef stickyDef;                            stickyDef.Initialize( bodies[0]->pointer, stickToBody,                                 bodies[0]->pointer->GetWorldPoint( b2Vec2(0,0) ),                                 collidePosition);                            //stickyDef.body1 = bodies[0]->pointer;                            //stickyDef.body2 = stickToBody;                            //stickyDef.localAnchor1 = bodies[0]->pointer->GetLocalPoint( b2Vec2(0,0) );                            //stickyDef.localAnchor2 = stickToBody->GetLocalPoint( collidePosition );                            //b2Vec2 d = stickyDef.localAnchor1                            //stickyDef.length = 0.0f;                            stickyJoint = world->CreateJoint( &stickyDef );                        }                        stickyFlag = false;                    }                }                break;                //case MODE_HIGHCALIBER:                //break;                default:                break;            }        }    }    if( render )         Sprite::onUpdate( dT );}void BlasterRound::onRender( float blend ){    if( !render )        return;    SetColor( WHITE );    if( exploded && explodeTime > 0 )     {        if( mode == MODE_HEAVY )        {            glPushMatrix();                glScalef( 6.0f, 6.0f, 1.0f );                Sprite::onRender( blend );            glPopMatrix();        }        else            Sprite::onRender( blend );    }else    {        if( bodies[0]->pointer )        {            vec2 velocity( bodies[0]->pointer->GetLinearVelocity().x, bodies[0]->pointer->GetLinearVelocity().y);            b2Vec2 shotDistance = bodies[0]->pointer->GetPosition() - initialPos;            float mag = min( velocity.magnitude() / 10.0f, shotDistance.Length() );            velocity.normalize();            float aim_angle = acosf( velocity.dot(vec2(0,1)) );             aim_angle = ( velocity.x <= 0 )? aim_angle : -aim_angle;            float roundScale = 1.0f;            if( mode == MODE_HEAVY )                roundScale = 3.0f;            glPushMatrix();                glRotatef( RAD_DEG( -render_state.angle + aim_angle ), 0, 0, 1 );                glScalef( roundScale, ( roundScale + mag ), 1.0f );                Sprite::onRender( blend );            glPopMatrix();        }    }}void BlasterRound::onCollision(const contactPoint& point){    if( point.obShape->IsSensor() )         return;    /*std::string shapeName = (char*)point.obShape->GetUserData();    if( shapeName == "blasterShape" )        return;*/    if( ++hitCount == 1 )    {        //m_body->SetBullet(false);        collidePosition = point.position;        switch( mode )        {            case MODE_BOUNCY:            break;            case MODE_STICKY:                if( stickyFlag == false )                {                    stickyFlag = true;                    stickToBody = point.obBody;                }            break;            default:                FlagForDestruction();                timeLeft = 0;            break;        }    }}


Here's the BlasterRound class in it's entirety. I wanted to post it before I break it into separate classes. I added a temporary parameter to the Fire method, "weaponMode". This is to quickly test different projectile behaviors without writing more classes.

The Fire() method creates the collision model and gives it an initial velocity. The timeLeft var puts a timer on the round in case it doesn't hit anything. The current animation sequence is chosen here, too. There are only two sequences for my tests, "idle" and "explode". I realize now that the finished weapon classes won't load from an XML file, they'll just create their collision definitions when constructed.

onUpdate() checks whether the round has exploded and needs to destroy it's collision model. If it doesn't, it updates the timer variable 'timeLeft'. So far, only the flame-thrower (and stickybomb) mode needs to affect the body during update. I'm experimenting with the stickybomb and its not working correctly just yet. It stretches upward after its attached, its weird.

All the weapon "modes" have the same firing interval at the moment, I'll fix that soon. I left it at 30ms so the flame-thrower mode looks okay. All the weapons shoot fast, which sometimes isn't appropriate.

If you playtest in the level editor, it will probably crash when you exit. There's an unsolved bug involving the weapon system. Also, be aware that 'c' toggles collision rendering AND the flame-thrower, 'b' toggles the background/sniper gun. That just means you have to press it twice, I'm not going to change the bindings. I'll probably have the mouse wheel cycle the weapons soon.

Well, there's a lot to improve, but its a start. Next step is audio!
Previous Entry Untitled
Next Entry Weapons Demo
0 likes 5 comments

Comments

mightypigeon
No go here (Windows Server 2008). Both executables just bring up a console window and do nothing. Tried running in compatibility mode, but no luck. Will give it a shot on my XP system when I get it back up and running in the next couple of days.

I have some old code laying around that might come in handy for your filesystem though :)

//----------------------------------------------------------------------------------------------
// Name: OdFileBuildAbsolutePath
// Desc: Build an absolute path (ie "C:\folder\file.ext" based on location of the EXE
//----------------------------------------------------------------------------------------------

char *OdFileBuildAbsolutePath( const char *Filename )
{
	static char Path[16000] = { 0 };
	char AppPath[256];
	char Directory[256] = { 0 };
	int NumSlashes = 0;
	int SlashesFound = 0;

	GetModuleFileName( NULL, AppPath, 256 );

	// Find how many directories deep the file is
	for( unsigned int i = 0; i < strlen( AppPath ); i++ )
	{
		char c = AppPath[ i ];

		if( c == '\\' || c == '/' )
			NumSlashes++;
	}

	// Get the directory
	for( unsigned int i = 0; i < strlen( AppPath ); i++ )
	{
		char c = AppPath[ i ];

		Directory[ i ] = c;

		if( c == '\\' || c == '/' )
			SlashesFound++;

		if( SlashesFound == NumSlashes )
			break;
	}

	sprintf( Path, "%s%s", Directory, Filename );

	return Path;
}


Not the best implementation looking back, but that's a start.
June 07, 2009 06:52 AM
O-san
I Can't run the game, the editor start though. When try to run the Saga City.exe I get this pop up saying:

G:\Saga City\Saga City.exe

This application has failed to start because the application configuration is incorrect. Reinstalling the application may fix this problem.

Running Windows XP x64 on a dual core AMD Athlon.

[edit]
Worked better when I deleted opengl32.dll in the game folder. You should not supply that dll with your executable.
[/edit]
June 07, 2009 02:35 PM
O-san
Oh sorry, forgot to comment on the actual game. Played it a while and it runs very smoothly. A few things I would look into:

-The camera behaves a little bit jerky when you turn from left to right.
-The explosions are pretty but you should have a greater variety of graphics for them when many are present.

The weapons are very entertaining, reminded me of the old Amiga game Turrican. Good job so far :-)

BTW, what physics engine are you using?
June 07, 2009 03:14 PM
Slather
Thank you both. I made some silly mistakes when I prepared the demo. I forgot to add the runtime libraries.

mightypigeon: My "file manager" is a sorry piece of junk. It works ok for development, but I need to replace it with boost::filesystem. Thank you for the code :)

O-san: I'm glad, and suprised you got it to run! I still feel like I'm in a fog when it comes to deployment. I won't include opengl32.dll anymore. About the explosions: I wanted to quickly implement different behaviors with the same sprite sheet, so I'm not going to keep the look of the explosions. I haven't heard of that game, but I'll look it up. A lot of the older games had a charm that's hard to recreate. Oh, I'm using Box2D for physics. Its a great library. Aardvajk is using Box2d for his game too.
June 07, 2009 04:18 PM
Aardvajk
Looking forward to trying this when you repost the demo. Looks ace from the screenshots.
June 07, 2009 05:44 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement