Love catching up with Leilani, great work Ishi! Always love how crisp and polished your work is, can't believe I still haven't played it yet - must rectify that as soon as possible! :D
Thanks! And yeah I've been wanting to have another good multiplayer session on Overcooked. Hope to see you at CB2 Indies again soon!
A rideable Maca would be really cool.
Just imagine Leilani riding with that. And her spin dash could be more powerful with the Maca friend she has.
and it would just be adorable.
Just my two cents.
This looks wonderful.
Hehe, yep that would be adorable! It might be a bit too much work to add as a game mechanic, but I do like the idea of Leilani having a buddy
Tiled in C++Someone asked me about how I use Tiled in C++ so here's a quick overview. I don't often go into code stuff so it may be interesting.
For the actual parsing of the XML files I use
tinyxml. It's been a long time since I used tinyxml directly though - I wrote my own wrapper class called EasyXML that really simplifies traversing the files and pulling out values/attributes.
For reading Tiled maps specifically, my gameplay code never deals directly with reading the XML file. I have a whole series of classes that represent the entire Tiled map data, so I have one common bit of code that loads the entire Tiled map at once and fills in all these classes. Various bits of gameplay code can then make use of the data later.
I'm not sure if I 100% support all the features of Tiled yet but my classes are:
TiledBase - most of the other classes derive from this one. This provides common features like width, height, and a property list (most things in the editor can be given arbitrary properties).
TiledMap - contains all tilesets and layers.
TiledImage - basically just a filename.
TiledLayerBase - object layers and tile layers both derive from this.
TiledObjectLayer - a layer containing a list of objects.
TiledObject - these are the objects that are in object layers. Can be rectangle, polyline or polygon.
TiledTileLayer - a layer containing an array of tiles.
TiledTile - contains the ID for a tile.
TiledTileset - contains an image and array of tiles.
TiledProperty - a name + value pair.
Now some examples of bits of code to give an idea how it looks.
Creating the TiledMap: I load the file with EasyXML and pass that in. The EasyXML loader gets passed around to all of the Tiled classes which load their individual bits. You can imagine replacing EasyXML with any xml parsing system.
cEasyXML loader(pGrid->pSections[section].pFilename);
pSection->pTiledMap = new cTiledMap(&loader);
TiledMap constructor: it loops through all the items in the root of the Tiled XML file, and creates tilesets, tile layers or object layers.
cTiledMap::cTiledMap(cEasyXML *pLoader)
: cTiledBase(pLoader)
{
pLoader->ReadyLoop();
while(pLoader->ContinueLoop())
{
const char *pTagName = pLoader->GetTagName();
if(!strcmp(pTagName, "tileset"))
{
cTiledTileset *pTileset = new cTiledTileset(pLoader);
Tilesets.push_back(pTileset);
}
else if(!strcmp(pTagName, "layer"))
{
cTiledTileLayer *pLayer = new cTiledTileLayer(pLoader);
TileLayers.push_back(pLayer);
Layers.push_back(pLayer);
}
else if(!strcmp(pTagName, "objectgroup"))
{
cTiledObjectLayer *pLayer = new cTiledObjectLayer(pLoader);
ObjectLayers.push_back(pLayer);
Layers.push_back(pLayer);
}
}
}
TiledMap (and most of the other classes) inherit from TiledBase. The TiledBase constructor is responsible for loading the properties list.
cTiledBase::cTiledBase(cEasyXML *pLoader)
{
Width = pLoader->ReadInt("width");
Height = pLoader->ReadInt("height");
TileWidth = pLoader->ReadInt("tilewidth");
TileHeight = pLoader->ReadInt("tileheight");
if(pLoader->OpenElement("properties"))
{
pLoader->ReadyLoop();
while(pLoader->ContinueLoop("property"))
{
cTiledProperty *pProperty = new cTiledProperty(pLoader);
Properties.push_back(pProperty);
}
pLoader->Up();
}
}
I make good use of these properties to set all kinds of variables on different game entities. For example to alter timing sequences for things that turn on and off. TiledBase contains a number of functions for searching the properties for a specific value. It's not super fast - just a naive search through the list - but I'm never dealing with huge lists and only get these values when a level is loading.
float cTiledBase::GetFloatProperty(const char *pPropertyName, float defaultValue) const
{
for(unsigned int i(0); i < Properties.size(); ++i)
{
if(!strcmp(Properties[i]->pName, pPropertyName))
{
float result = defaultValue;
sscanf_s(Properties[i]->pValue, "%f", &result);
return result;
}
}
return defaultValue;
}
TiledLayer constructor: I have the Tiled editor configured to save the tile layers in CSV format. So I read the "data" string from the XML file and use strtok to separate the values.
cTiledTileLayer::cTiledTileLayer(cEasyXML *pLoader)
: cTiledLayerBase(pLoader)
{
pTiles = new unsigned int[Width * Height];
if(pLoader->OpenElement("data"))
{
char *pValues = pLoader->CopyTagContents();
char *pNextToken = NULL;
char *pCurrentToken = strtok_s(pValues, ",", &pNextToken);
int x(0);
int y(0);
while(pCurrentToken)
{
unsigned int tileValue = 0;
sscanf(pCurrentToken, "%u", &tileValue);
pTiles[x + y * Width] = tileValue;
++x;
if(x >= Width)
{
x = 0;
++y;
if(y >= Height)
{
break;
}
}
pCurrentToken = strtok_s(NULL, ",", &pNextToken);
}
delete[] pValues;
pLoader->Up();
}
}
The TiledTileLayer::GetTile function is also interesting - Tiled stored the horizontal/vertical flips as bit flags in the tile ID. If you don't remove them then the tile ID will be incorrect.
int cTiledTileLayer::GetTile(int x, int y, bool *pFlippedX, bool *pFlippedY)
{
if (x < 0 || y < 0 || x >= Width || y >= Height)
return 0;
unsigned int tile = pTiles[x + y * Width];
const unsigned FLIPPED_HORIZONTALLY_FLAG = 0x80000000;
const unsigned FLIPPED_VERTICALLY_FLAG = 0x40000000;
const unsigned FLIPPED_DIAGONALLY_FLAG = 0x20000000;
if(pFlippedX)
{
*pFlippedX = (tile & FLIPPED_HORIZONTALLY_FLAG) == FLIPPED_HORIZONTALLY_FLAG;
}
if(pFlippedY)
{
*pFlippedY = (tile & FLIPPED_VERTICALLY_FLAG) == FLIPPED_VERTICALLY_FLAG;
}
// Remove flip flags
tile &= ~(FLIPPED_HORIZONTALLY_FLAG |
FLIPPED_VERTICALLY_FLAG |
FLIPPED_DIAGONALLY_FLAG);
return (int)tile;
}
My Tiled maps use external tilesets - meaning that the tilesets are stored in a separate file, and referenced from many different maps. This makes the loading a little more awkward. What I do is, in the TiledTileset constructor, if the tileset is an external one - which is indicated by the "source" attribute - then I create a new EasyXML loader and use that for the remainder of the constructor.
The Reload function is in TiledBase, and is there to make sure that any properties that are stored in the external tileset are also loaded.
cTiledTileset::cTiledTileset(cEasyXML *pLoader)
: cTiledBase(pLoader)
, pImage(NULL)
{
FirstGID = pLoader->ReadInt("firstgid");
cEasyXML *pAlternateLoader = NULL;
const char *pExternalSourceFilename = pLoader->GetAttributeContents("source");
if(pExternalSourceFilename)
{
// Read tileset from another file
char buffer[1024];
sprintf_s(buffer, 1024, "%s/%s", pLoader->GetDirectory(), pExternalSourceFilename);
pAlternateLoader = new cEasyXML(buffer);
pLoader = pAlternateLoader;
Reload(pLoader);
}
pName = pLoader->CopyAttributeContents("name");
... etc
Phew.. hope some of that provides some useful tips for anyone wanting to load Tiled maps in C++.
Level StartThis post is a bit dry, so here's what you'll see at the start of every level! I'm trying out 60FPS gifs too.