Saturday, October 29, 2011

Quick Update...

I haven't done anything real major lately, just tying some loose ends up, doing some under-the-cover work to get ready to integrate Enemies...the real hard part. Anyway, here's a screenshot and a crappy video of what I have so far, with some ground tiles added:

And the video:

video

Thursday, October 27, 2011

Inheriting From Physical Object

As I mentioned in the last post, I realized I should be inheriting from a Super Class for classes that represent a physical object.  So, I made the PhysicalObject class:
PhysicalObject::PhysicalObject(sf::RenderWindow *pApp, cpSpace *pSpace, std::string ImageName, std::string GlowImageName, cpVect Location, cpFloat Angle, cpFloat Speed, cpFloat Depth, cpFloat RotSpeed) : mpApp(pApp), mpSpace(pSpace), mpImage(NULL), mpGlowImage(NULL), mpSprite(NULL), mpGlowSprite(NULL), mDepth(Depth) { // Load the image mpImage = Utilities::ImageGet(ImageName.c_str()); mpSprite = new sf::Sprite(); mpSprite->SetImage(*mpImage); mpSprite->SetOrigin(mpImage->GetWidth()/2, mpImage->GetHeight()/2); // Set to middle of screen mpSprite->SetPosition(pApp->GetWidth()/2, pApp->GetHeight()/2); if (GlowImageName.size() > 0) { mpGlowImage = Utilities::ImageGet(GlowImageName.c_str()); mpGlowSprite = new sf::Sprite(); mpGlowSprite->SetImage(*mpGlowImage); mpGlowSprite->SetOrigin(mpGlowImage->GetWidth()/2, mpGlowImage->GetHeight()/2); // Set to middle of screen mpGlowSprite->SetPosition(pApp->GetWidth()/2, pApp->GetHeight()/2); // for glow, set to additive blend mpGlowSprite->SetBlendMode(sf::Blend::Add); } mpBody = cpBodyNew(1.0f, 1.0f); cpBodySetPos(mpBody, Location); // Set the Velocity mpBody->v.x=cos( Angle)*Speed; mpBody->v.y=sin( Angle)*Speed; cpBodySetAngle(mpBody, Angle); mpBody->w = RotSpeed; mpShape = cpCircleShapeNew(mpBody, (mpImage->GetWidth() + mpImage->GetHeight())/4, cpvzero); mpShape->e = 0.0f; // not elatics, no bouncing mpShape->u = 0.0f; // 0.0 is frictionless cpSpaceAddBody(mpSpace, mpBody); cpSpaceAddShape(mpSpace, mpShape); }

The constructor handles all the physics stuff (except defining collision types and callbacks) and the image stuff.

I also made a Draw in the PhysicalObject class:
BOOL PhysicalObject::Draw(cpVect PlayerLoc, sf::Color GlowColor) { cpFloat Angle = cpBodyGetAngle(mpBody); cpFloat Itemx = (cpFloat)mpApp->GetWidth()/2; cpFloat Itemy = (cpFloat)mpApp->GetHeight()/2; BOOL bDraw = TRUE; mpSprite->SetRotation(Angle*(180.0f/PI)); if (!cpveql(PlayerLoc, cpvzero)) { // Locate the sprite based off player's location Itemx = (cpFloat)(mpApp->GetWidth()/2) + (mpBody->p.x - PlayerLoc.x); Itemy = (cpFloat)(mpApp->GetHeight()/2) + (mpBody->p.y - PlayerLoc.y); if (!((Itemx > -(cpFloat)mpImage->GetWidth()) && (Itemx < (cpFloat)mpApp->GetWidth() + (cpFloat)mpImage->GetWidth()) && (Itemy > -(cpFloat)mpImage->GetHeight()) && (Itemy < (cpFloat)mpApp->GetHeight() + (cpFloat)mpImage->GetHeight()))) { bDraw = FALSE; } } if (bDraw) { mpSprite->SetPosition(Itemx, Itemy); // Draw shadow 1st if depth if (mDepth > 0.001f) { cpFloat Radius = cpCircleShapeGetRadius(mpShape); sf::Shape oCircle = sf::Shape::Circle(Itemx+5.0, Itemy+5.0, mDepth*Radius*0.80f, sf::Color(0, 0, 0, 150)); mpApp->Draw(oCircle); } mpApp->Draw(*mpSprite); // If we get glow alpha and we have glow image if (GlowColor.a && mpGlowSprite) { if (!cpveql(PlayerLoc, cpvzero)) { mpGlowSprite->SetPosition(Itemx, Itemy); } else { mpGlowSprite->SetPosition(mpApp->GetWidth()/2, mpApp->GetHeight()/2); } mpGlowSprite->SetRotation(Angle*(180.0f/PI)); mpGlowSprite->SetColor(GlowColor); mpApp->Draw(*mpGlowSprite); } } return TRUE; }

As you can see, we handle the images and the glow images here.  We also added a Depth member to draw shadows under objects.

Here's the latest screenshot:

Wednesday, October 26, 2011

Added Level and Item introduction

So far, we've been using a hard-coded map, with 4 walls.  Well, we need some way to create a real map, with multiple walls, items, etc.  To accomplish this, I decided to use the tool Gleed2d (Gleed2d Site).  The tool allows me to lay out a map and it outputs it as an xml file.  using tinyxml, we can parse this and use SmashPcMap to render it.

Here's the source code:
SmashPc 10-26-11 Code

First, a picture of the level from gleed2d:
As you can see, it's not much more than 4 walls.  I added a block in the middle. a little circular wall with armor inside, some Enemy Generator Platforms (the things in the corner, will spawn enemies), and the little circles are the players spawn points.

As I said, this creates a xml file.  So, I created the GameLevel.h and .cpp to parse that xml file and generate data we can understand.

The code is below, you can expand them to see it if you want.  It's got a lot of xml parsing. I'll explain a little below.
GamLevel::GameLevel():
GameLevel::GameLevel(char *pcFileToLoad) : mbValid(FALSE) { TiXmlDocument doc(pcFileToLoad); // load and check if it was successful if (doc.LoadFile()) { TiXmlElement *root = doc.RootElement(); // extract level name, can check at some point printf("Level name %s\n", root->Attribute("Name")); for(TiXmlElement* LayersElem = root->FirstChildElement(); LayersElem; LayersElem = LayersElem->NextSiblingElement()) { printf("LayersElem %s\n", LayersElem->ValueStr().c_str()); // this should be Layers if (LayersElem->ValueStr() == string("Layers")) { for(TiXmlElement* LayerElem = LayersElem->FirstChildElement(); LayerElem; LayerElem = LayerElem->NextSiblingElement()) { printf("LayerElem %s\n", LayerElem->ValueStr().c_str()); // This should be Layer if (LayerElem->ValueStr() == string("Layer")) { for(TiXmlElement* ItemsElem = LayerElem->FirstChildElement(); ItemsElem; ItemsElem = ItemsElem->NextSiblingElement()) { printf("ItemsElem %s\n", ItemsElem->ValueStr().c_str()); // This should be Items if (ItemsElem->ValueStr() == string("Items")) { for(TiXmlElement* ItemElem = ItemsElem->FirstChildElement(); ItemElem; ItemElem = ItemElem->NextSiblingElement()) { // This should be Item if (ItemElem->ValueStr() == string("Item")) { ParseItem(ItemElem); } } } else { // it's scoll speed, we can ignore printf("Skip Scroll\n"); } } } } } } mbValid = TRUE; } else { printf("Failed opening %s, reason: %s\n", pcFileToLoad, doc.ErrorDesc()); } }

GameLevel::ParseItem()
void GameLevel::ParseItem(TiXmlElement* ItemElem) { tLevelWall LevelWall; tLevelItem LevelItem; map SpecValues; // store off position cpVect Position; Position.x = atof(ItemElem->FirstChild("Position")->FirstChild("X")->FirstChild()->Value()); Position.y = atof(ItemElem->FirstChild("Position")->FirstChild("Y")->FirstChild()->Value()); // check for custom properties and handle if found if (ItemElem->FirstChildElement("CustomProperties")->FirstChild() != NULL) { string Key, Value; for(TiXmlElement* PropElem = ItemElem->FirstChildElement("CustomProperties")->FirstChildElement(); PropElem; PropElem = PropElem->NextSiblingElement()) { Key = PropElem->Attribute("Name"); Value = PropElem->FirstChild()->ValueStr(); printf("SpecField Key %s Val %s\n", Key.c_str(), Value.c_str()); SpecValues[Key] = Value; } } // check for spawn 1st if (strncmp(ItemElem->Attribute("Name"), "Spawn", 5) == 0) { mSpawns.push_back(Position); } /* If it's a rectangle, it's made as a wall or spawn point */ else if (strcmp(ItemElem->Attribute("xsi:type"), "RectangleItem") == 0) { LevelWall.StartingPoint = Position; LevelWall.EndingPoint.x = atof(ItemElem->FirstChild("Width")->FirstChild()->Value()) + Position.x; LevelWall.EndingPoint.y = atof(ItemElem->FirstChild("Height")->FirstChild()->Value()) + Position.y; LevelWall.bIsRect = TRUE; printf("Rect Start x %f end %f\n", LevelWall.StartingPoint.x, LevelWall.EndingPoint.x); printf("Rect Start y %f end %f\n", LevelWall.StartingPoint.y, LevelWall.EndingPoint.y); // add to vector mWalls.push_back(LevelWall); } else if (strcmp(ItemElem->Attribute("xsi:type"), "TextureItem") == 0) { string TempString; cpVect SecondLoc = cpvzero; LevelItem.Location = Position; printf("SpriteName %s\n", ItemElem->FirstChild("texture_filename")->FirstChild()->Value()); // get name up to _ TempString = string(ItemElem->Attribute("Name")); LevelItem.ItemName = TempString.substr(0, TempString.find("_")); cout << "LevelItem name " << LevelItem.ItemName << endl; // load the texture here LevelItem.pImage = Utilities::ImageGet(ItemElem->FirstChild("texture_filename")->FirstChild()->Value()); mItems.push_back(LevelItem); } else if (strcmp(ItemElem->Attribute("xsi:type"), "PathItem") == 0) { // Parse the Path starting at WorldPoints ParsePath(ItemElem->Attribute("Name"), ItemElem->FirstChild("WorldPoints")->FirstChild("Vector2")); } }

GameLevel::ParsePath()
void GameLevel::ParsePath(const char */*pName*/, const TiXmlNode *WorldCoordNode) { tLevelWall LevelWall; BOOL bFirst = TRUE; // Might use name if we use paths for more than walls in the future for(const TiXmlNode* CoordNode = WorldCoordNode; CoordNode; CoordNode = CoordNode->NextSibling()) { if (bFirst) { LevelWall.StartingPoint.x = atof(CoordNode->FirstChild("X")->FirstChild()->Value()); LevelWall.StartingPoint.y = atof(CoordNode->FirstChild("Y")->FirstChild()->Value()); bFirst = FALSE; } else { cpFloat SwapVal; LevelWall.EndingPoint.x = atof(CoordNode->FirstChild("X")->FirstChild()->Value()); LevelWall.EndingPoint.y = atof(CoordNode->FirstChild("Y")->FirstChild()->Value()); LevelWall.bIsRect = FALSE; mWalls.push_back(LevelWall); printf("Path Start x %f end %f\n", LevelWall.StartingPoint.x, LevelWall.EndingPoint.x); printf("Path Start y %f end %f\n", LevelWall.StartingPoint.y, LevelWall.EndingPoint.y); LevelWall.StartingPoint = LevelWall.EndingPoint; } } }
Basically, we open the xml, and read each "Item."  In gleed2d speak, anything in the map is an item.  For us, an Item is Either a "RectangleItem", which is just a Wall, a "TextureItem", which is an item in the game, a "PathItem", which is a connected wall, or a Spawn (our name).  We read each of these out into a WallList, ItemList, or SpawnList.

In order to use these, we modify SmashPcMap to load the Level, and it reads the lists out and draws the walls and items.

Here's the new CreateSpace, which puts the walls into the physics space:
void SmashPcMap::CreateSpace(void) { GameLevel::tLevelItem LevelItem; GameLevel::tLevelWall LevelWall; cpShape *pShape; std::vector::iterator it; /* loop through all the walls 1st and draw them */ for (it = mpLevel->mWalls.begin(); it != mpLevel->mWalls.end(); it++) { if (it->bIsRect) { cpVect Verts[4] = {{it->StartingPoint.x, it->StartingPoint.y}, {it->StartingPoint.x, it->EndingPoint.y}, {it->EndingPoint.x, it->EndingPoint.y}, {it->EndingPoint.x, it->StartingPoint.y} }; pShape = cpSpaceAddShape(mpSpace, cpPolyShapeNew(&mpSpace->staticBody, 4, Verts, cpvzero)); } else { pShape = cpSpaceAddShape(mpSpace, cpSegmentShapeNew(&mpSpace->staticBody, it->StartingPoint, it->EndingPoint, 10.0f)); } pShape->e = 0.0f;//9f; pShape->u = 0.0f;//3f; pShape->collision_type = WALL_COL_TYPE; pShape->data = this; // Walls occupy all layers } }

And here's the SmashPcMap::Draw that draws the walls and items:
BOOL SmashPcMap::Draw(cpBody *PlayerBody) { cpFloat LeftX = PlayerBody->p.x - mu32ScreenWidth/2; cpFloat TopY = PlayerBody->p.y - mu32ScreenHeight/2; std::vector::iterator it; /* loop through all the walls 1st and draw them */ for (it = mpLevel->mWalls.begin(); it != mpLevel->mWalls.end(); it++) { sf::Shape oShape; if (it->bIsRect) { oShape = sf::Shape::Rectangle (it->StartingPoint.x - LeftX, it->StartingPoint.y - TopY, it->EndingPoint.x - it->StartingPoint.x, it->EndingPoint.y - it->StartingPoint.y, sf::Color(128, 128, 128, 255)); } else { oShape = sf::Shape::Line(it->StartingPoint.x - LeftX, it->StartingPoint.y - TopY, it->EndingPoint.x - LeftX, it->EndingPoint.y - TopY, 10, sf::Color(128, 128, 128, 255)); } mpApp->Draw(oShape); } // draw all the items std::vector::iterator ItemIt; for (ItemIt = ItemList.begin(); ItemIt != ItemList.end(); ItemIt++) { (*ItemIt)->Draw(PlayerBody->p); } return TRUE; }
You can see we created a new class, "SmashPcItem" which doesn't do alot yet, but it will Draw().   I have implemented the beginnings of collision between Player an Items.  Here's how I have the SmashPcItem::CreatePhysicsObject():
void SmashPcItem::CreatePhysicsObjects(void) { if (mbPhysical) { cpFloat Inertia; mpBody = cpBodyNew(500.0f, 500.0f); cpBodySetPos(mpBody, mItemInfo.Location); mpBody->v = cpvzero; /* teleporter doesn't spin */ mpBody->w = 2.0f; mpShape = cpCircleShapeNew(mpBody, (cpFloat)((mItemInfo.pImage->GetWidth() + mItemInfo.pImage->GetHeight())/4), cpvzero); Inertia = cpMomentForCircle(5.0f, 0.0f, (cpFloat)((mItemInfo.pImage->GetWidth() + mItemInfo.pImage->GetHeight())/4), cpvzero); cpBodySetMoment(mpBody, Inertia); mpShape->e = 0.0f; // not elatics, no bouncing mpShape->u = 1.0f; // 0.0 is frictionless mpShape->collision_type = ITEM_COL_TYPE; mpShape->layers = MAP_ITEM_LAYER; mpShape->data = static_cast<void *>(this); cpSpaceAddBody(mpSpace, mpBody); cpSpaceAddShape(mpSpace, mpShape); // Create Collision handler here, only player notifies the item // data can be NULL since we have class stored in Shape cpSpaceAddCollisionHandler(mpSpace, ITEM_COL_TYPE, PLAYER_COL_TYPE, SmashPcItem::PlayerCollision, NULL, NULL, NULL, NULL); } }
You see at the end, I create a callback to SmashPcItem::PlayerCollision() when an item of collision type PLAYER_COL_TYPE and ITEM_COL_TYPE collide.  That callback does this:
int SmashPcItem::PlayerCollision(cpArbiter *arb, struct cpSpace *space, void *data) { SmashPcItem *pItem; SmashPcPlayer *pPlayer; cpShape *pItemShape, *pPlayerShape; cpArbiterGetShapes(arb, &pItemShape, &pPlayerShape); pPlayer = reinterpret_cast(pPlayerShape->data); pItem = reinterpret_cast(pItemShape->data); // We have the player that hit, and the item he hit pItem->NotifyHit(pPlayer); }

Basically, it get the player and item objects that collided and notifies that item of being hit.  Currently NotifyHit() doesn't do anything.

Next time, I'm going to do a little re-design.  I notice the classes SmashPcPlayer, SmashPcItem, and SmashPcBullet all have physicsal properties and can eb Drawn, which I should inherit from one class to stop from using the same code over and over.

Tuesday, October 25, 2011

Added Bullet/Wall Collision...

OK, I found a major no-no I was doing in my SmashPcBullet deconstructor; I wasn't removing and freeing the physics body/shape from the world. And, that was bad. So, I've fixed that.

Get the code here:
SmashPC 11-25-11_2 Collision

Here's the fixed deconstructor:
SmashPcBullet::~SmashPcBullet()
{
    delete mpSprite;

    if (mBulletDetails.pGlowImage)
    {
        delete mpGlowSprite;
    }

    cpSpaceRemoveBody(mpSpace, mpBody);
    cpSpaceRemoveShape(mpSpace, mpShape);

    cpBodyFree(mpBody);
    cpShapeFree(mpShape);

}

And, I added this line in the constructor to add a collision handler:
    // Create Collision handler here, only wall notifies the bullet
    // other handlers get notifed of hit by bullet
    // data can be NULL since we have class stored in Shape
    cpSpaceAddCollisionHandler(mpSpace, BULLET_COL_TYPE, WALL_COL_TYPE, SmashPcBullet::WallCollision, NULL, NULL, NULL, NULL);

This call sets the function SmashPcBullet::WallCollision as the callback for collisions between type BULLET_COL_TYPE (which is all bullets) and WALL_COL_TYPE (all walls).
And, here's WallCollision (defined static in header file):
int SmashPcBullet::WallCollision(cpArbiter *arb, struct cpSpace *space, void *data)
{
    SmashPcBullet *pBullet;
    cpShape *pBulletShape, *pWallShape;
    cpArbiterGetShapes(arb, &pBulletShape, &pWallShape);

    printf("Callback!\n");

    pBullet = reinterpret_cast(pBulletShape->data);

    // Mark bullet for removal
    pBullet->SetDead();
}
If you recall, in the SmashPcBullet constructor, we assign mpShape->data = this.  So, in the callback, we can extract the Bullet class and set it to Dead, so it will be removed.

Here's a in-game picture shooting bullets against the wall:

Bullets Added!

Last time I promised Bullets and collision detection.  Well, I'm providing 1/2 of that.  I haven't put in the chipmunk-physics driven collision detection callbacks yet, but I did provide bullets.  In doing so, I also now point the player towards the mouse, and fire the bullets in that direction.

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!

Sunday, October 23, 2011

Adding 2d Physics Engine

For this update, I started adding the 2d physics engine, Chipmunk Physics.  As mentioned earlier, the 2d physics engine does so much work for a 2d programmer.  It performs all the movement and collision detection for every object in the game.   This update just adds some walls, and handles moving the player through the 2d physics world.
I also added a new class, SmashPcMap, which handles the loading and drawing the level.

You can get the source code here:
SmashPc 11-23 code

First, in SmashPc.cpp, main(), here's the new function:

int main(int argc, char *argv[])
{
    sf::Event Event;
    BOOL bFullScreen;

    GameSound::Init("SmashPcSounds.xml");
    Utilities::LoadConfig("SmashPcCfg.xml");

    Utilities::ScreenResolutionGet(gu32ScreenX, gu32ScreenY, bFullScreen);

 sf::RenderWindow App(sf::VideoMode(gu32ScreenX, gu32ScreenY, 32), "SmashPC", (bFullScreen ? sf::Style::Fullscreen : sf::Style::Default));

    cpInitChipmunk();

    gpMap = new SmashPcMap("", gu32ScreenX, gu32ScreenY, &App);

    // Create location; Typically, use location from level, but fake for now
    gpOurPlayer = new SmashPcPlayer(&App, gpMap->GetSpace(), cpv(200, 200));

    srand(time(NULL));

    App.ShowMouseCursor(true);
    App.SetCursorPosition(gu32ScreenX/2, gu32ScreenX/2);

    App.SetFramerateLimit(60);

    // main game loop
    while (App.IsOpened())
    {
        while (App.PollEvent(Event))
        {
            // Window closed
            if (Event.Type == sf::Event::Closed)
            {
                App.Close();
                GameSound::Shutdown();
            }

            // Escape key pressed
            if (Event.Type == sf::Event::KeyPressed)
            {
                if (Event.Key.Code == sf::Key::Escape)
                {
                    App.Close();
                    GameSound::Shutdown();
                }
            }
        }

        gpOurPlayer->CheckInput();

        cpSpaceStep(gpMap->GetSpace(), 1.0f/60.0f);

        /* clear screen and draw map */
        App.Clear();

        gpMap->Draw(gpOurPlayer->GetBody());
        gpOurPlayer->Draw();

        App.Display();
    }

    return 0;
}
You see we added the call to cpInitChipmunk(), which kicks off the physics engine.
 Later we call cpSpaceStep(). This function simulates all the objects defined in the space at a speed of 60 frames per second.
We also call create an instance of SmashPcMap(), which will create the physics space, loads the level, and draws it. Currently, we're not loading levels yet, so we just pass it an empty level.

 In SmashPcMap, we create and init the space:
    mpSpace = cpSpaceNew();
    cpSpaceInit(mpSpace);
    mpSpace->iterations = 10;

 And, In CreateSpace() we create fake walls that just surrounds the world:
       // use static body and add this wall shape
        pShape = cpSpaceAddShape(mpSpace, cpPolyShapeNew(&mpSpace->staticBody, 4, Verts, cpvzero));

        pShape->e = 0.9f;
        pShape->u = 0.3f;
        pShape->collision_type = WALL_COL_TYPE;
        pShape->data = this;

Looking in SmashPcPlayer, We added the starting world location of the player and the physics space to the constructor.  Here is is:

SmashPcPlayer::SmashPcPlayer(sf::RenderWindow *pApp, cpSpace *pSpace,
                             cpVect Location) :
mpApp(pApp),
mpSpace(pSpace),
mu32Health(100),
mu32Armor(0),
mu32LastFire(0),
mu32LastSpecialFire(0)
{

    // Load the temp image for this player
    mpImage = Utilities::ImageGet("Gfx/Player.bmp");

    mpSprite = new sf::Sprite();
    mpSprite->SetImage(*mpImage);
    mpSprite->SetOrigin(mpImage->GetWidth()/2, mpImage->GetHeight()/2);

    // Set to middle of screen
    mpSprite->SetPosition(pApp->GetWidth()/2, pApp->GetHeight()/2);

    mpBody = cpBodyNew(1.0f, 1.0f);

 cpBodySetPos(mpBody, Location);
 mpBody->v = cpvzero;
 mpBody->v_limit = PLAYER_SPEED;

 cpBodySetAngle(mpBody, PI/2.0f);
 mpBody->w = 0.0f;

 mpShape = cpCircleShapeNew(mpBody, mpImage->GetWidth()/2, cpvzero);
 mpShape->e = 0.0f;  // not elatics, no bouncing
 mpShape->u = 1.0f; // 0.0 is frictionless
 mpShape->collision_type = PLAYER_COL_TYPE;
 mpShape->data = (void *)this;

    mpShape->layers = PLAYER_LAYER;

 cpSpaceAddBody(mpSpace, mpBody);
 cpSpaceAddShape(mpSpace, mpShape);

 // Create Collision handler here

    // Sound notifying a player has spawned
    GameSound::Play("PlayerSpawn");

}

You can see the code that creates the player's body and shape in the physics space (world).  The body is basically for collisions and holds the velocity, force, and position, and it's connected to the shape which defines the bodies we...shape.  We use circle for player.

For Draw(), we're just drawing the player in the middle of the screen, and moving the world around him.

CheckInput() has been modified and it probably needs some explanation.  Here it is:
void SmashPcPlayer::CheckInput(void)
{
    const sf::Input& Input = mpApp->GetInput();
    cpFloat xForce = 0, yForce = 0;
    cpVect Vel;
    U32 u32Time = timeGetTime();

    // Set all forces to 0
    mpBody->f = cpvzero;

    Vel = cpBodyGetVel(mpBody);

    // We'll just assign velocities to a hardcoded value for now
    if (Utilities::KeyIsDown(Input, GAME_KEY_LEFT))
    {
        xForce -= PLAYER_FORCE;
    }
    else if (Vel.x < 0.01f)
    {
        mpBody->v.x = 0;
    }

    if (Utilities::KeyIsDown(Input, GAME_KEY_RIGHT))
    {
        xForce += PLAYER_FORCE;
    }
    else if (Vel.x > 0.01f)
    {
        mpBody->v.x = 0;
    }

    if (Utilities::KeyIsDown(Input, GAME_KEY_DOWN))
    {
        yForce += PLAYER_FORCE;
    }
    else if (Vel.y > 0.01f)
    {
        mpBody->v.y = 0;
    }

    if (Utilities::KeyIsDown(Input, GAME_KEY_UP))
    {
        yForce -= PLAYER_FORCE;
    }
    else if (Vel.y < 0.01f)
    {
        mpBody->v.y = 0;
    }

    mpBody->f = cpv(xForce, yForce);

    // Check for fire
    if (Utilities::KeyIsDown(Input, GAME_KEY_FIRE))
    {
        // we'll fake a re-fire speed check, 400 mS
        if (mu32LastFire + 400 <= u32Time )
        {
            // Play Fake Sound, repeat
            GameSound::Play("PeaShooter", TRUE);
            mu32LastFire = u32Time;
        }
    }
}

First, a mention about moving things around in a physics engine.  Typically, in games, you just give something a velocity, and say go.  Well, that kind of work in physics engines, but problems occur when you have collisions.  If you constantly give an object a velocity, and it's colliding with an object, the physics engine will keep  moving it that way, whether it's collided with something or not.

So, to "fake" it, we 1st give a v_limit to the body, (see the Constructor).  This is the maximum velocity the body can have.  Then when we want the player to move a direction, we just give it a massive force in the direction it's moving.  If it's too low, the body will accelerate to the velocity we want, but for this game, we want full speed or no speed.

Also, we have to add some code where we actually set the velocity to 0; otherwise, if we're moving right, and let up on the left key, the player will continue to move right until friction stops it.

Next time we'll add bullets to the system, and how to handle collisions.

Till then!

Edit 11-24-11:
I forgot to mention the other change in SmashPcMap, how it draws.  Here is thespecific code:
BOOL SmashPcMap::Draw(cpBody *PlayerBody)
{
    cpFloat LeftX  = PlayerBody->p.x - mu32ScreenWidth/2;
    cpFloat TopY   = PlayerBody->p.y - mu32ScreenHeight/2;

    // Soon we will draw all items loaded from the level
    // but, now it's only the fake walls, so draw them
    // fake corners removed...

    for (int i = 0; i < 4; i++)
    {
        cpVect Verts[4] = // ... removed from snippet

        sf::Shape oShape;

        oShape = sf::Shape::Rectangle (Verts[0].x - LeftX, Verts[0].y - TopY,
                                       Verts[2].x  - Verts[0].x, Verts[2].y  - Verts[0].y,
                                       sf::Color(128, 128, 128, 255));

        mpApp->Draw(oShape);

    }

    return TRUE;
}

We now pass in the player's body so we can get the location of him within the physics world. We then draw the rest of the world around the player (since we want to keep the player in the middle of the screen and scroll the rest of the world around him). That's why it is all based on LeftX and Top Y of the player (again, in world coordinates).

SmashPC is Started!

So, I've decided specifically what type of game I'll be making.  It'll be a top-down Alien Swarm type game (for those old guys, think SmashTV).  In fact, I've named it SmashPC.

What I've done for this initial code drop is implement the basics as far as SFML window, simple sprite drawing, playing Sounds, and Input and input configuration.

You can get the full source code and project here:
(Link Removed, get newer version)

First, we need to create a project.  So, using Code::Blocks, I made a project, and pointed at all the libraries I would need.

Here's my settings for the Compiler and Linker:



(Check out the links for sfml, tinyxml, and chipmunk-physics in the 1st blog entry to get those libraries.  I use sfml2.0, but 1.6 should work fine).

Next, I add the main source code (in SmashPc.cpp), and a simple main loop.  Here's what I have so far:

int main(int argc, char *argv[])
{
    sf::Event Event;
    BOOL bFullScreen;

    GameSound::Init("SmashPcSounds.xml");
    Utilities::LoadConfig("SmashPcCfg.xml");

    Utilities::ScreenResolutionGet(gu32GravScreenX, gu32GravScreenY, bFullScreen);

 sf::RenderWindow App(sf::VideoMode(gu32GravScreenX, gu32GravScreenY, 32), "SmashPC", (bFullScreen ? sf::Style::Fullscreen : sf::Style::Default));

    gpOurPlayer = new SmashPcPlayer(&App);

    srand(time(NULL));

    App.ShowMouseCursor(true);
    App.SetCursorPosition(gu32GravScreenX/2, gu32GravScreenX/2);

    // main game loop
    while (App.IsOpened())
    {
        float FrameTime;
        const sf::Input& Input = App.GetInput();

        while (App.PollEvent(Event))
        {
            // Window closed
            if (Event.Type == sf::Event::Closed)
            {
                App.Close();
                GameSound::Shutdown();
            }

            // Escape key pressed
            if (Event.Type == sf::Event::KeyPressed)
            {
                if (Event.Key.Code == sf::Key::Escape)
                {
                    App.Close();
                    GameSound::Shutdown();
                }
            }
        }

        gpOurPlayer->CheckInput();

        FrameTime = App.GetFrameTime();

        if (FrameTime > 1/60.0f)
        {
            //printf("Frametime %f expected %f\n", FrameTime, 1/60.0f);
        }
        else
        {
            Sleep((U32)((1/60.0f - FrameTime)*1000.0f));
            FrameTime = 1/60.0f;
        }

        /* clear screen and draw map */
        App.Clear();

        gpOurPlayer->Draw();

        App.Display();
    }

    return 0;
}

You see some simple SFML setup, but we're also calling the functions GameSound::Init() and Utilities::LoadConfig().  

Gamesound::Init loads the sounds stored in SmashPcSounds.xml.  Here's that function:

void GameSound::Init(char *pcFilename)
{
    if (bNeedsInit)
    {
        TiXmlDocument doc(pcFilename);

        // load and check if it was successful
        if (doc.LoadFile())
        {
            TiXmlElement *root = doc.RootElement();

            for(TiXmlElement* example = root->FirstChildElement(); example;
                example = example->NextSiblingElement())
            {
                char cFile[32];
                std::string SoundStr  = example->ValueStr();

                printf("Found SoundName %s .. ", SoundStr.c_str());

                SoundMap[SoundStr] = new sf::SoundBuffer();

                sprintf(cFile, "Sfx/%s", example->GetText());

                if (!SoundMap[SoundStr]->LoadFromFile(cFile))
                {
                    printf("Failed loading sound %s!\n", cFile);
                }
            }
        }
        else
        {
            printf("Failed opening GravithonSounds.xml\n");
        }

        bNeedsInit = FALSE;

    }

}

This is using tinyxml to parse the xml file, and it uses std::map to associated a string to a sfml sound buffer.  This way, you play a sound you'd just pass in the string to play, like GameSound::Play("PeaShooter");

The next piece you saw was the call to Utilities::LoadConfig().  This function loads the SmashPcCfg.xml file and setups the users prefrences with respect to controls, mouse sensitivty, and screen resolution.  Take a look at the Utilities.cpp and.h for more info on how it does that.

Finally, there's SmashPcPlayer class. Here's the constructor:

SmashPcPlayer::SmashPcPlayer(sf::RenderWindow *pApp) :
mpApp(pApp),
mu32Health(100),
mu32Armor(0),
mu32LastFire(0),
mu32LastSpecialFire(0)
{

    // Load the temp image for this player
    mpImage = Utilities::ImageGet("Gfx/Player.bmp");

    mpSprite = new sf::Sprite();
    mpSprite->SetImage(*mpImage);
    mpSprite->SetOrigin(mpImage->GetWidth()/2, mpImage->GetHeight()/2);

    // Set to middle of screen
    mpSprite->SetPosition(pApp->GetWidth()/2, pApp->GetHeight()/2);

    // Sound notifying a player has spawned
    GameSound::Play("PlayerSpawn");

}

We make a call to load the image (we use the Utilities function since we want to make sure we won't load the image more than once), and create a sprite from the image, set the sprite to the middle of the screen, and play a sound, noting the player has spawned in the world.

The SmashPcPlayer->Draw () currently just draws the sprite where the payer is.
And, SmashPcPlayer->CheckInput() simply moves the player around and plays a gunshot sound when you press the Fire button (configured as left mouse button).

Next time we'll add in chipmunk-physics 2d engine to handle all the big work for us as far as movement and collision detection.