Tiled doesn’t come with any in-built collision data out-of-the-box, so I assume you’re placing something within the Tiled map editor which can be processed at runtime to generate the mesh data needed for your collision system? I’m also assuming this is done at runtime as you’re using an existing library to process and import your map.
I don’t have any understanding of the Monogame.Extended
implementation detail, so @craftworkgames might be able to shed some light on that, but a standard flow through a Content.Pipeline
plugin would be the following:
ContentImporter<Tmx>
ContentProcessor<Tmx, ProcessedTmx>
ContentWriter<ProcessedTmx>
Each of these can (and should) be unit tested completely separately.
I have the following coverage for my TiledMapProcessor
class:
The most complex test in these is the ShouldDecodeAndDecompressTileData
method, but even that is fairly small:
[Theory]
[InlineData("csv", "", "\n1,2,4,\n3,1,2,\n2,4,1\n")]
[InlineData("base64", "", "AQAAAAIAAAAEAAAAAwAAAAEAAAACAAAAAgAAAAQAAAABAAAA")]
[InlineData("base64", "zlib", "eJxjZGBgYAJiFiBmBmJGKB8mBuIDAAGwABU=")]
[InlineData("base64", "gzip", "H4sIAAAAAAAAE2NkYGBgAmIWIGYGYkYoHyYG4gMALN/AqyQAAAA=")]
public void ShouldDecodeAndDecompressTileData(string encoding, string compression, string value)
{
var layer = new TileLayer
{
Width = 3,
Height = 3,
Data = new Data
{
Value = value,
Encoding = encoding,
Compression = compression
}
};
var result = new TiledMapProcessor().DecodeTileLayerData(layer);
Assert.Equal(9, result.Count);
Assert.Equal(1, result[0].Gid);
Assert.Equal(2, result[1].Gid);
Assert.Equal(4, result[2].Gid);
Assert.Equal(3, result[3].Gid);
Assert.Equal(1, result[4].Gid);
Assert.Equal(2, result[5].Gid);
Assert.Equal(2, result[6].Gid);
Assert.Equal(4, result[7].Gid);
Assert.Equal(1, result[8].Gid);
}
I don’t need to load an entire Tiled map from disk to test if my content processor can successfully decode and decompress the various different configurations that Tiled offers - I can isolate the appropriate data, represent it in code, and use that to unit test the DecodeTileLayerData
method.
Likewise, my other tests each have different stripped-down versions of TiledMap
and TileLayer
objects with the minimum amount of properties required to test that single method:
[Fact]
public void ShouldNotProcessInvalidEncoding()
{
Assert.Throws<NotImplementedException>(() =>
{
var layer = new TileLayer
{
Data = new Data
{
Encoding = "invalidEncodingType",
}
};
new TiledMapProcessor().DecodeTileLayerData(layer);
});
}
This method of representing data as code can be used in your collision system. Distil down only what is required from a map to generate the collision meshes.
For me personally, I represent collision in Tiled by painting red tiles on a special Collision
layer. As such, my collision generator only requires I pass in a single TileLayer
instance. Here is a unit test for that:
[Fact]
public void ShouldCreateSeparateColliders()
{
var tileLayer = new TileLayer
{
Width = 3,
Height = 3,
Tiles = new TileLayer.Tile[3, 3]
};
var collisionTile = new TileLayer.Tile { Gid = 1 };
/*
* x 0 x
* 0 x 0
* x 0 x
*/
tileLayer.Tiles[0, 0] = collisionTile;
tileLayer.Tiles[0, 1] = null;
tileLayer.Tiles[0, 2] = collisionTile;
tileLayer.Tiles[1, 0] = null;
tileLayer.Tiles[1, 1] = collisionTile;
tileLayer.Tiles[1, 2] = null;
tileLayer.Tiles[2, 0] = collisionTile;
tileLayer.Tiles[2, 1] = null;
tileLayer.Tiles[2, 2] = collisionTile;
var physicsLayer = 1;
var colliders = CreateColliders(tileLayer, physicsLayer).ToList();
Assert.Equal(5, colliders.Count);
foreach (var collider in colliders)
{
Assert.Equal(1, collider.PhysicsLayer);
Assert.Equal(32, collider.Bounds.Width);
Assert.Equal(32, collider.Bounds.Height);
...
}
}
Then I have other tests to cover other scenarios, such as ensuring that if multiple tiles are next to each other, the system properly merges these in to a single collision box.
Finally, my implementation of the collision system has no knowledge of what a TiledMap
even is - in fact the physics and rendering are separated in to completely different assemblies that cannot access each other.
My pathfinding is unit tested by passing in fabricated Collider
objects which are substantially easier to create than Tiled
objects.
There is still value in ensuring your systems fully work with actual Tmx files and content writers and the such, but those are integration tests, not unit tests. I still haven’t found a good way to do end-to-end integration tests in MonoGame due to the problems with instantiating a Game
class instance in CI.