Tuesday, December 20, 2011

New Site Location...

I've decided to move my blog to GameDev, and make it a developer;s Journal, which is exactly what this is.  So, go on over, and see what I have planned.

2d Game Making, the Easy Way


Wednesday, November 16, 2011

SmashPc: The Postmortem

When I set out to make SmashPC, the main point was to try and share the ease of making a 2D game using SFML, chipmunk-physics, tinyxml, and Gleed2d.

For this post, I'll go over the main points, and cover some of the improvements I could have done.

Overview of the Main Points

To me, the biggest point I want to make is how a 2d physics engine makes writing games a snap.  No longer do I have to handle moving every object within the world, checking all the collision possibilities, handling the odd collision detection issues we used to have, or any other number of things.  Sure, there was a time when it was "fun" to handle all that stuff, but it seriously impeded making a cool game.

Instead, we tell the physics engine what object are in the world; their mass, force, angle, rotational velocity, friction  and all the other physical properties.  We tell the physics engine to notify us when certain objects collide, so we can do fun stuff with them.  Then, we basically say, "Go", and it goes.  With little effort, I had 200 enemies all running towards me, pushing each other around, in a natural group.  I think it's really cool stuff.

I also really like the power SFML provides.  It makes doing anything you want with sprites a snap (scaling, rotation, etc.).  Input is handled easily, and sound is very well done.  I didn't even bother with the spatial sound available, but it's another strong reason to use SFML.

Throw in tinyxml, to handle parsing all the data, and gleed2d, for creating the levels, and saving them in XML, and you have the recipe for making games very easily.

What I could have done better

The first thing I want to point out is the lack of a proper design before I began coding.  I had an idea of what I wanted, but nothing concrete.  I didn't always write code with the end result in mind, and that caused me to do some re-writes a few times (ie, adding the PhysicalObject parent class, and moving all objects to the SmashPcData class).  This also added to my other issues.

I should have used xml to detail enemies; instead, I just wrote the properties in the SmashPcEnemy class (Their speed, movement update time, etc.).  This would also have been used to define what items are released by the "special" enemy; instead, I just hard-coded it in the SmashPcData class.

I would re-do the GameLevel class.  Initially, I included the option to add items to the map, but I didn't once use it.  That caused me to waste some time and code.

I would like to re-do the weapons class.  How I handled switching between bullets was poor.

Final Word

In the end, I just wanted to get it out, and I took some short-cuts to get there, but, I think the main point of this blog was fulfilled.  I hope I showed how you can use the tools to make interesting 2D games.

I may use this blog for other games I make.  I'm thinking of a side-scroller type game, kinda like the Contra games.  We'll just have to see.

Until then!

Tuesday, November 15, 2011

Tying up loose SmashPc ends

Last night I put my game as it sits on the blog.  I wanted to tie up the last few steps I added.

Here's the latest source code:
SmashPc 11-15-11

First, the Rocket, I added Smoke trails. I added the <SmokeTrails> tag to the SmashPcWeapons.xml.
I also added this Smoke element to the SmashPcItems.xml:
<Smoke Type="Fadeout"> <Image>smoke-small.png</Image> <Interact>No</Interact> <Value>0</Value> <TimeToLive>500</TimeToLive> <Rotate>0</Rotate> </Smoke>

In SmashPcBullet::Draw(), I added this check at the end:
// If we're drawing smoketrails, then lay one down, and set it to fade out if (mbSmokeTrail) { cpVect Loc = mpBody->p; GameLevel::tLevelItem LevelItem; LevelItem.ItemName = "Smoke"; // Set location at back of bullet Loc.x -= cos(mpBody->a)*mpImage->GetWidth()/2; Loc.y -= sin(mpBody->a)*mpImage->GetHeight()/2; LevelItem.Location = Loc; LevelItem.ImageName = ""; // get it from xml SmashPcItem *pSmokeItem = new SmashPcItem(mGameData, mpApp, LevelItem, mpSpace); mGameData.AddActiveItem(pSmokeItem); }

Basically, we add a "Smoke" item every time we update drawing the rocket.  Since the TimeToLive is short, though, it won't be on the screen long.

Now, if we check in SmashPcItem::Draw(), we do this:
// if the type is FadeOut, then lessen tghe alpha if (mpItemDetails->Type == "Fadeout") { sf::Color AlphaColor(0, 0, 0, 255); U32 u32TimeDiff = (timeGetTime() - mu32StartTime)+1; AlphaColor.a = 255 - ((255*u32TimeDiff)/mu32TimeToLive); PhysicalObject::Draw(PlayerLoc, AlphaColor); }

This basically continually lowers the alpha on the image, based on the time to live, and the amount of time it has left to live.  So, this give the "Smoke Trail" as seen in this screen shot:

The other stuff I did was handle changing levels, releasing an item when you kill a specific enemy, game over, and game won.  This wasn't actually a big deal, but I took short cuts.  I really should have created another configuration file that defines the levels, and modified the EnemySpawn items to detail what item to release.

Here's the code, in the main loop before pOurPlayer->CheckInput(), that handles these pieces:

if (pOurPlayer->mu32Health == 0) { // Check for Lives? if (u32Lives > 0) { Sleep(1000); u32Lives--; delete pOurPlayer; pOurPlayer = new SmashPcPlayer(GameData, &App, pMap->GetSpace(), pMap->GetSpawnLocation(), &Weapon); } else { // just quit I guess GameSound::Play("GameOver"); Sleep(4000); break; } } if (GameData.IsLevelOver()) { U32 u32OldArmor = pOurPlayer->mu32Armor; BOOL bGameOver = FALSE; // Change Level if (Level == NumLevels) { bGameOver = TRUE; } else { Level++; } // Call the intermission function // also handles end-game Intermission(&App, pMap, pOurPlayer, &GameData, bGameOver, u32ScreenX, u32ScreenY); // Reset GameData to make sure everything is cleared GameData.Reset(); delete pOurPlayer; delete pMap; // Create new amp with new level data pMap = new SmashPcMap(Levelnames[Level-1].c_str(), GameData, &App); Weapon.AssignSpace(pMap->GetSpace()); // Start player at new spawn locations pOurPlayer = new SmashPcPlayer(GameData, &App, pMap->GetSpace(), pMap->GetSpawnLocation(), &Weapon); // reset old armor pOurPlayer->mu32Armor = u32OldArmor; }

We check 1st if our guy is dead.  if so, delay a second (the SmashPcPlayer->NotifyHit() handles playing the death sound), then restart the guy.  If we're out of lives, play the Game Over sound and then quit.

Then, I check if we've killed all the enemies in the level, and if so, Call the Intermission() function, which basically continues to draw the level, plays music, and puts up the "Level Complete" (or the "You've Won") status.  Reset the GameData (which clears off all items and bullets from the active lists), deletes the player and map, and re-creates the map with the next level.

We tell the Weapon class there's a new Physics Space, create a new player in the new map, and reset the armor.

That's basically it!  The next post will be a kind of postmortem, going of the specifics, and what I could've done better.

SmashPC 1.0 Ready!

I managed to cobble together enough code, and make a few levels to be able to release an actual game.  It's late, so I'll wait to go over the specific's tomorrow.  For now, download and extract the .zip (windows only), check the readme.txt, modify the SmashPcCfg.xml to configure your controls if you wish, and go!

Let me know if there are any issues.

SmashPc 1.0

EDIT: The previous version didn't have all the .dll's required to play.  If it failed to run previously, try this one.

Saturday, November 12, 2011

Some Flamethrowing goodness!

I just wanted to post a video and picture of the enemies, and the flame-thrower I added in action.

In the SmashPcWeapons.xml, I added this:
<FlameThrower> <Image>flame.png</Image> <Velocity>500</Velocity> <Damage>6</Damage> <Refire>10</Refire> <TimeToLive>700</TimeToLive> <Fadeout>Yes</Fadeout> <DegreesOff>10</DegreesOff> <ContinuousSound>Yes</ContinuousSound> </FlameThrower>

Fadeout means I want the bullet to fadeout during it's time to live.  DegreesOff means I want it to fire at a random angle of 10 degrees off the straight angle.
And, I had to modify SmashPcBullet.cpp to handle these changes:

In SmashPcBullet Constructor, I added this:
// If this gun shoots off a bit, modify the directions if (BulletDetails.u32DegreesOff) { cpFloat RandomDegrees = (cpFloat)(((S32)rand() % (S32)BulletDetails.u32DegreesOff) - (S32)BulletDetails.u32DegreesOff/2); mpBody->a += RandomDegrees*(PI/180.0f); mpBody->v.x=cos( mpBody->a)*BulletDetails.Speed; mpBody->v.y=sin( mpBody->a)*BulletDetails.Speed; }

And, in the SmashPcDraw(), I added this check:
if (mbFadeout) { U32 u32TimeDiff = (timeGetTime() - mu32StartTime)+1; GlowColor.a = 255 - ((255*u32TimeDiff)/mu32TimeToLive); // Change glow color to just alpha and make fade GlowColor.r = 0; } PhysicalObject::Draw(PlayerLoc, GlowColor);

Anyway, here's the picture and video!


Friday, November 11, 2011

I'm not dead! Enemies added!

I apologize, it's been a while since I updated.  I have been busy, but I finally am able to get back to this.

And, the big news is, I've added our 1st enemies!  OK, they're REALLY stupid, they just kind of move towards the player, shooting occasionally.  Anyway, onto the code.

Here's a link to the code:
SmashPc Code 11-12-11

Before I get to the good stuff, I did a little more house cleaning..  I moved all the objects that live in the game (Items, Bullets, and Enemies) into the SmashPcData object, and let it keep the lists and call the update functions.  So, now my main loop looks like this:

// main game loop while (App.IsOpened()) { pOurPlayer->CheckInput(); cpSpaceStep(pMap->GetSpace(), 1.0f/60.0f); /* clear screen and draw map */ App.Clear(sf::Color(200, 200, 200, 255)); pMap->Draw(pOurPlayer->GetBody()); // Update all the bullets in the world GameData.UpdateBullets(pOurPlayer->GetBody()); // check for items as well GameData.UpdateItems(pOurPlayer->GetBody()); // Finaly update all enemies GameData.UpdateEnemies(pOurPlayer->GetBody()); pOurPlayer->Draw(); App.Display(); if (CheckGameEnd(&App)) { App.Close(); break; } }

You can see it's pretty simple, and probably won't have much more to add.  I suppose handling a dead player and changing levels.

So, how do Enemies get added?  In the level, I have "EnemySpawn" items, and they spawn enemies at a given rate.  The plan is to give these spawn points levels, and the different enemy levels will make the enemies smarter, faster, tougher, etc.

Here's the piece from SmashPcItem.xml for EnemySpawn1 (for level 1 enemies):
<EnemySpawn1 Type="EnemySpawn"> <Interact>No</Interact> <Value>50</Value> <ReleaseRate>5000</ReleaseRate> <EnemyLevel>1</EnemyLevel> <Rotate>0</Rotate> </EnemySpawn1>

Which means we release a level 1 enemy every 5 seconds until 50 have been released.

So, in SmashPcItem::Update(), we have this (Let me say, I really need to inherit from SmashPcItem, and make a SmashPcEnemySpawn class, but that'll be later):

BOOL SmashPcItem::Update(void) { if (mu32TimeToLive) { if (timeGetTime() > mu32TimeToLive) { // Mark Item for deletion mbActive = FALSE; GameSound::Play("Despawn"); return FALSE; } } // Release Enemies into the level if (mpItemDetails->Type == "EnemySpawn") { if (mu32NumReleaseLeft && (mu32LastRelease + mpItemDetails->u32ReleaseRate < timeGetTime())) { SmashPcEnemy *pEnemy; mu32LastRelease = timeGetTime(); mu32NumReleaseLeft--; // Create an enemy pEnemy = new SmashPcEnemy(mGameData, mpApp, mpSpace, mpBody->p, mpItemDetails->u32EnemyLevel); // Check if this enemy should release something mGameData.AddActiveEnemy(pEnemy); } } return mbActive; }

And, our enemy class looks like this (I'm including it all here):
/****************************************************************************** * * SmashPcEnemy() - Creates a player, and places him in the space * ******************************************************************************/ SmashPcEnemy::SmashPcEnemy(SmashPcData &GameData, sf::RenderWindow *pApp, cpSpace *pSpace, cpVect Location, U32 u32Level) : PhysicalObject(pApp, pSpace, "Gfx/blueenemy.bmp", "", Location, PI/2.0f, 0.0f, TRUE), mu32Health(20*u32Level), mu32LastFire(timeGetTime()), mu32LastMoveUpdate(0), mGameData(GameData) { printf("Enemy created!\n"); // Set the Velocity limit to our fake movement mpBody->v_limit = ENEMY_SPEED; mpShape->collision_type = ENEMY_COL_TYPE; mpShape->layers = ENEMY_PLAYER_LAYER; mpShape->data = (void *)this; // Create Collision handler here cpSpaceAddCollisionHandler(mpSpace, ENEMY_COL_TYPE, BULLET_COL_TYPE, SmashPcEnemy::BulletCollision, NULL, NULL, NULL, NULL); // Sound notifying a player has spawned GameSound::Play("EnemySpawn"); } /****************************************************************************** * * ~SmashPcEnemy() - Removes a player * ******************************************************************************/ SmashPcEnemy::~SmashPcEnemy() { } /****************************************************************************** * * Update() - Do enemy things * ******************************************************************************/ void SmashPcEnemy::Update(cpBody *pPlayerBody) { U32 u32Time = timeGetTime(); // check if we need to update the movement if (mu32LastMoveUpdate + ENEMY_UPDATE_MOVE < u32Time) { cpFloat Angle = atan2(pPlayerBody->p.y - mpBody->p.y, pPlayerBody->p.x - mpBody->p.x); // add randomization to angle cpFloat RandomDegrees = (cpFloat)(((S32)rand() % 20) - 10); Angle += (RandomDegrees*PI)/180.0f; cpBodySetAngle(mpBody, Angle); // Change force towards player mForce = cpv(ENEMY_FORCE*cos(Angle), ENEMY_FORCE*sin(Angle)); //mpBody->f = cpv(ENEMY_FORCE*cos(Angle), ENEMY_FORCE*sin(Angle)); mu32LastMoveUpdate = u32Time; } mpBody->f = mForce; if (mu32LastFire + ENEMY_REFIRE_TIME < u32Time) { SmashPcData::tBulletList BulletList; mGameData.GetBulletList(BulletList); SmashPcBullet *pBullet = new SmashPcBullet(mpApp, BulletList[0], mpBody, mpSpace, FALSE); mGameData.AddActiveBullet(pBullet); mu32LastFire = u32Time; } } /****************************************************************************** * * NotifyHit() - Notify the enemy it has been hit by a bullet * ******************************************************************************/ void SmashPcEnemy::NotifyHit(SmashPcBullet *pBullet) { if (mu32Health < pBullet->GetDamage()) { // Enemy Dead mu32Health = 0; // Play dead sound } else { GameSound::Play("PlayerHit"); mu32Health -= pBullet->GetDamage(); } pBullet->SetDead(); } /****************************************************************************** * * BulletCollision() - Callback for when Enemy hits a bullet * ******************************************************************************/ int SmashPcEnemy::BulletCollision(cpArbiter *arb, struct cpSpace *space, void *data) { SmashPcBullet *pBullet; SmashPcEnemy *pEnemy; cpShape *pBulletShape, *pEnemyShape; cpArbiterGetShapes(arb, &pEnemyShape, &pBulletShape); pEnemy = reinterpret_cast<SmashPcEnemy*>(pEnemyShape->data); pBullet = reinterpret_cast<SmashPcBullet*>(pBulletShape->data); // We have the enemy that hit, and the bullet he hit pEnemy->NotifyHit(pBullet); return 0; }

OK, some explanation. In the constructor, we setup the specific physics attributes, including the collision handler.  We're basically saying we want the enemy to get a callback when it's hit by the player's bullet.  The Layer is set so it will be in the same collision layer as the player and the player bullets, even though we only want to get notified of hitting bullets (see chipmunk-physics for more on collision layers).

In SmashPcEnemy::Update(), we move the enemy towards the player every so often.  Notice we don't reset the velocity when we change the force, so the enemy will somewhat slowly change direction, continuing on it's previous path until the force can change it's direction fully.  This will change for different "levels" of enemies, but this is it for now.

The BulletCollision() simply calls the enemies NotifyHit(), with the bullet it hit, and in NotifyHit(), we reduce the health of the enemy.

Finally, there is no specific enemy Draw, it uses the PhysicsObject::Draw() by default (the inherited class).

The other thing I did was add a Collision Handler to the SmashPcPlayer class, which I didn't have before.  It's basically identical to the Enemy's, except we subtract the damage from the armor 1st if the player has armor.

Well, this results in some bad enemies.  I'll have to adjust the AI some, but here's some pics.  The first one, I just let the enemies pile up, and watched em run around.  It's def a group mentality.

OK, until next time!

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:


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.
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()); } }

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")); } }

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.