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.

No comments:

Post a Comment