Download this version of code here:
SmashPC 10-25-11 Code
Right now the bullets pool in a corner until the TimeToLive expires, but you can see the Pysics engine doing it's work. Since the Bullets and Walls are in the same collision layer, they get stopped by the walls. Let's get to the code.
First, here's my SmashPcWeapons.xml file which defines the weapons:
<smashpcweapons> <peashooter> <image>Bullet.bmp</image> <glowimage>Bullet_glow.bmp</glowimage> <velocity>1000</velocity> <damage>10</damage> <refire>290</refire> <timetolive>3000</timetolive> </peashooter> </smashpcweapons>
This only defines one weapon for now, the PeaShooter. The SmashPcWeapon class will read through that file and load all the available weapons and their attributes. We will be adding attributes as the weapons get mroe complex.
Now, Let's look at SmashPcWeapon. This is designed to only be instanced once as we don't want to continually be reading off disk for the xml file. The constructor opens the xml and stores the details in a map:
SmashPcWeapon::SmashPcWeapon(char *pcFilename, sf::RenderWindow *pApp,
cpSpace *pSpace) :
mu32LastFireTime(0),
mpApp(pApp),
mpSpace(pSpace)
{
SmashPcBullet::tBulletDetails Bullet;
TiXmlDocument doc(pcFilename);
// load and check if it was successful
if (doc.LoadFile())
{
// root is GravithonWeapons
TiXmlElement *root = doc.RootElement();
for(TiXmlElement* WeapElem = root->FirstChildElement(); WeapElem;
WeapElem = WeapElem->NextSiblingElement())
{
char cFile[64];
Bullet.u32TimeToLive = 0;
Bullet.pGlowImage = NULL;
// Assign the other pieces
Bullet.Speed =
atof(WeapElem->FirstChild("Velocity")->FirstChild()->Value());
Bullet.u32Damage =
atoi(WeapElem->FirstChild("Damage")->FirstChild()->Value());
Bullet.u32Refire =
atoi(WeapElem->FirstChild("Refire")->FirstChild()->Value());
// load the image
sprintf(cFile, "Gfx/%s",
WeapElem->FirstChild("Image")->FirstChild()->Value());
Bullet.pImage = Utilities::ImageGet(cFile);
// Check for optional values
if (WeapElem->FirstChild("TimeToLive"))
{
Bullet.u32TimeToLive =
atoi(WeapElem->FirstChild("TimeToLive")->FirstChild()->Value());
}
if (WeapElem->FirstChild("GlowImage"))
{
sprintf(cFile, "Gfx/%s",
WeapElem->FirstChild("GlowImage")->FirstChild()->Value());
Bullet.pGlowImage = Utilities::ImageGet(cFile);
}
// Store off in map
mBulletHash[WeapElem->ValueStr()] = Bullet;
}
}
else
{
printf("Failed to open %s\n", pcFilename);
}
// Set the 1st weapon as the default
mSelectedBullet = mBulletHash.begin()->first;
}
The function SelectBullet simply sets which type of bullet the weapon will fire.
The last function, FireBullet:
SmashPcBullet *SmashPcWeapon::FireBullet(cpBody *pPlayerBody, BOOL bOurBullet)
{
SmashPcBullet *pBullet = NULL;
U32 u32Time = timeGetTime();
// Check if it's time to fire
if (mu32LastFireTime + mBulletHash[mSelectedBullet].u32Refire <= u32Time )
{
pBullet = new SmashPcBullet(mpApp, mBulletHash[mSelectedBullet],
pPlayerBody, mpSpace, bOurBullet);
// Play Sound (use name of bullet for sound)
GameSound::Play(mSelectedBullet, TRUE);
mu32LastFireTime = u32Time;
}
return pBullet;
}
You can see we only fire when the refire rate is past, and we return a bullet. You'll see why we return a bullet in a sec.
Next we'll take a look at SmashPcBullet class. Here's the constructor:
SmashPcBullet::SmashPcBullet(sf::RenderWindow *pApp,
tBulletDetails &BulletDetails,
cpBody *pPlayerBody, cpSpace *pSpace,
BOOL bOurBullet) :
mpApp(pApp),
mBulletDetails(BulletDetails),
mpSpace(pSpace),
mbDead(FALSE)
{
// Set Time to Live to when it should die
if (mBulletDetails.u32TimeToLive)
{
mBulletDetails.u32TimeToLive = timeGetTime() + mBulletDetails.u32TimeToLive;
}
mpSprite = new sf::Sprite();
mpSprite->SetImage(*mBulletDetails.pImage);
mpSprite->SetOrigin(mBulletDetails.pImage->GetWidth()/2,
mBulletDetails.pImage->GetHeight()/2);
// Rotate sprite
mpSprite->SetRotation(pPlayerBody->a*(180.0f/PI));
if (mBulletDetails.pGlowImage)
{
mpGlowSprite = new sf::Sprite();
mpGlowSprite->SetImage(*mBulletDetails.pGlowImage);
mpGlowSprite->SetOrigin(mBulletDetails.pGlowImage->GetWidth()/2,
mBulletDetails.pGlowImage->GetHeight()/2);
// for glow, set to additive blend
mpGlowSprite->SetBlendMode(sf::Blend::Add);
// Rotate sprite
mpGlowSprite->SetRotation(pPlayerBody->a*(180.0f/PI));
}
mpBody = cpBodyNew(0.1f, 0.1f);
cpFloat Angle = pPlayerBody->a;
mpBody->p = pPlayerBody->p;
mpBody->v.x=cos( Angle)*mBulletDetails.Speed;
mpBody->v.y=sin( Angle)*mBulletDetails.Speed;
cpBodySetAngle(mpBody, Angle);
mpBody->w = 0.0f;
mpShape = cpCircleShapeNew(mpBody,
(mBulletDetails.pImage->GetWidth() + mBulletDetails.pImage->GetHeight())/4,
cpvzero);
mpShape->e = 0.0f; // not elatics, no bouncing
mpShape->u = 1.0f; // 0.0 is frictionless
mpShape->collision_type = BULLET_COL_TYPE;
mpShape->data = (void *)this;
if (bOurBullet)
{
mpShape->layers = BULLET_LAYER;
}
else
{
mpShape->layers = ENEMY_BULLET_LAYER;
}
cpSpaceAddBody(mpSpace, mpBody);
cpSpaceAddShape(mpSpace, mpShape);
// Create Collision handler here
}
Here, we create the sprite based on the image, but we also create the "glow" sprite if a glow image is passed in. This will put a glow around the bullet image, which is a nice effect.
Then, we create the physics body and shape. We assign the location the same as the player's body, and the same thing with the angle. We do a little trig to set the X and Y velocities based on the given Speed and Angle the player's facing.
The layers is the collision layer. Only objects whos collision layer mask's overlapp will collide. So, we don't want our player to collide with is own bullets, so we want to make sure the player's layer doesn't over lap his own bullets.
The other major function in SmashPcBullet is Draw:
BOOL SmashPcBullet::Draw(cpVect PlayerLoc)
{
// Compute where on the screen the bullet should be drawn
cpFloat Bullx = (cpFloat)(mpApp->GetWidth()/2) + (mpBody->p.x - PlayerLoc.x);
cpFloat Bully = (cpFloat)(mpApp->GetHeight()/2) + (mpBody->p.y - PlayerLoc.y);
if (((Bullx > 0) && (Bullx < (cpFloat)mpApp->GetWidth())) &&
((Bully > 0) && (Bully < (cpFloat)mpApp->GetHeight())))
{
mpSprite->SetPosition(Bullx, Bully);
mpSprite->SetRotation(mpBody->a*(180.0f/PI));
if (mBulletDetails.u32TimeToLive)
{
if (timeGetTime() > mBulletDetails.u32TimeToLive)
{
// Mark bullet for deletion
mbDead = TRUE;
return TRUE;
}
}
mpApp->Draw(*mpSprite);
if (mBulletDetails.pGlowImage)
{
mpGlowSprite->SetPosition(Bullx, Bully);
mpGlowSprite->SetRotation(mpBody->a*(180.0f/PI));
mpGlowSprite->SetColor(sf::Color(255, 0, 0, 180));
mpApp->Draw(*mpGlowSprite);
}
}
return TRUE;
}
We check the bullet's world location against the player location and we only draw the bullet if it's on the visible screen. And, if there's a glow image, we draw it, but with a Red tint, and partial alpha value.
Now, in SmashPcPlayer::CheckInput(), when the Fire key is pressed, we just tell the weapon to fire. I've also added the code to position the player towards the mouse:
// Check for fire
if (Utilities::KeyIsDown(Input, GAME_KEY_FIRE))
{
pBullet = mpWeapon->FireBullet(mpBody, TRUE);
}
else if (Utilities::KeyIsDown(Input, GAME_KEY_SPECIAL_FIRE))
{
// Handle later
}
// Finally, find location of Mouse and point player towards it
cpFloat MouseX = (cpFloat)Input.GetMouseX();
cpFloat MouseY = (cpFloat)Input.GetMouseY();
cpFloat Angle = atan2(MouseY - (cpFloat)(mpApp->GetHeight()/2),
MouseX - (cpFloat)(mpApp->GetWidth()/2));
cpBodySetAngle(mpBody, Angle);
Finally in the main loop, we have to keep track of the bullets, so we add them to a vector. We then draw every bullet in the vector. If a Bullet needs to be removed, we do that before drawing. Here's the added code:
SmashPcBullet *pBullet;
std::vector BulletList;
.
.
.
pBullet = pOurPlayer->CheckInput();
if (pBullet)
{
BulletList.push_back(pBullet);
}
.
.
.
// Check for bullet removal, else Draw all the Bullets
for (std::vector::iterator it = BulletList.begin();
it != BulletList.end(); )
{
if ((*it)->IsDead())
{
delete *it;
it = BulletList.erase(it);
if (it == BulletList.end())
{
break;
}
continue;
}
else
{
(*it)->Draw(pOurPlayer->GetBody()->p);
}
it++;
}
Next time I'll add Collisions, I (almost) promise!
No comments:
Post a Comment