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::vectorBulletList; . . . 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