Loading level data from xml file in XNA - c#

I am creating a tile-based game in XNA and will be loading the level information from xml files. I have no problem with loading xml data but I would like feedback on the approach I'm thinking of using for reading the xml file.
The code has a class hierarchy such that:
A Level class contains:
- a collection of TileLayers
- a collection of Tilesets
A TileLayer class contains
- a collection of Tiles
A Tileset class contains:
- a collection of TilsetTiles
A TilesetTile class contains:
- a TileId
- a collection of TileProperties
- a TileRectangle
Each of the above classes requires some information from the xml level file.
When I load a level I would like to simply call Level.Load();
My intention is that each class will have a Load() method that will:
1. Load any specific info it needs from the xml file.
2. Call Load() on its children.
The problem I see is that the code for processing the xml will be scattered around in different files making changes difficult to implement (for instance if I decide to encrypt the xml file), and no doubt breaks several aspects of the SOLID principles.
I have this idea that I could create an xmlLevelReader class whose sole purpose is to read an xml level file.
This class could then expose methods that can be called from the Load() method in each of the classes described above.
For example the TileLayer class Load() method could call xmlLevelReader.GetTiles() which would return an IEnumerable<Tile>
Do you think this approach will work?
Can you foresee any difficulties?
Is this approach too simplistic/complicated?
Any constructive criticism welcomed!
Thanks

Based on your comment, I see that you are using Tiled Map Editor. This lead me to suggest that you use TiledLib. Here is a brief explanation of how you can get up and running with importing your .tmx files for use in game.
Content Pipeline Overview
File -> Content Import -> Content Process -> Content Write -> .xnb -> ContentRead -> Game Object
TiledLib
TiledLib only handles the ContentImporter part of the above diagram. It will essentially handle reading the .tmx XML and allow you to process the data into whatever objects you need at run time. Fortunately, the TiledLib author has provided a Demos section in the download as well.
Basic Tiled Map Processor Demo
BasicDemo main game project which contains the ContentManager.Load call.
BasicDemoContent project which has the .tmx file from Tiled
BasicDemoContentPipeline project which has the ContentProcessor
TiledLib which has the ContentImporter
You really only need to worry about how the ContentProcessor works because TiledLib handles all the importing for you. Although I do suggest looking through the different classes to understand how it is deserializing the XML (for educational purposes).
The example ContentProcessor in the Basic Demo project takes in a MapContent object from the ContentImporter (TiledLib) and outputs a DemoMapContent object which is serialized to .xnb at build time and deserialized to a Map object at run time. Here are the classes that represent the map after being processed completely:
[ContentSerializerRuntimeType("BasicDemo.Map, BasicDemo")]
public class DemoMapContent
{
public int TileWidth;
public int TileHeight;
public List<DemoMapLayerContent> Layers = new List<DemoMapLayerContent>();
}
[ContentSerializerRuntimeType("BasicDemo.Layer, BasicDemo")]
public class DemoMapLayerContent
{
public int Width;
public int Height;
public DemoMapTileContent[] Tiles;
}
[ContentSerializerRuntimeType("BasicDemo.Tile, BasicDemo")]
public class DemoMapTileContent
{
public ExternalReference<Texture2DContent> Texture;
public Rectangle SourceRectangle;
public SpriteEffects SpriteEffects;
}
A Map contains a tile width, tile height, and a list of MapLayers.
A MapLayer contains a width, a height, and a list of Tiles.
A MapTile contains a texture, a source rectangle (proper rectangle in the tileset), and optional sprite effects (I've never used any).
How It's Made
I suggest reading the comments of the ContentProcessor to understand what is happening, but in brief, this is the basics:
Load texture data for tile set
Get source rectangles for each tile from within the texture
Iterate over all layers of the map
For each layer, iterate over all tiles
For each tile, get the proper texture and source rectangle
Assign all data from the input to the output properly
Caveats
As long as you stick to basic types (vague, I know), you also do not need to worry about ContentWriter and ContentReader parts of the content pipeline. It will automatically serialize and deserialize the .xnb files at build and run time, respectively. See a question I asked about this: here.
Also, you'll notice that if you're using object layers from within Tiled, the demo does not show you how to process those. TiledLib does properly import them, you just need to pull the data out and stick it in your proper classes. I'll try to edit this answer later with an example of how to do that.

If you are just wanting to load in XML without manipulating the data at all, you can just use the built in XNA Content Serializer.
Essentially you define a class which maps to your xml format, and then read the XML into an instance of that class.
For example. Here I define the class I want to load into:
SpriteRenderDefinition.cs
I chose this one because it has nested classes like the case you describe. Note that it goes into the ContentDefinitions project of you XNA solution.
Now here is the xml file that fills in the content of a SpriteRenderDefinition:
Sprite.xml
The format of that XML maps directly to the member names of SpriteRenderDefinition.
And finally, the code to actually load that XML data into an actual Object at runtime is very straight forward:
SpriteRenderDefinition def = GameObjectManager.pInstance.pContentManager.Load<SpriteRenderDefinition>(fileName);
After calling that line, you have a SpriteRenderDefintion object populated with all the content of the XML file! That is all the code I wrote. Everything else is built into XNA. It's really quite slick and useful if you take an hour or so to figure it out!

Related

Serializing a game save, how to make this json tree balanced?

I'm writing a game saving function. When serializing the game world, areas has loop reference:
public class Area
{
List<Area> pathArea;
List<int> pathCost;
}
Before area1 is serialized and get an $id, the pathArea gets more area2, area3, that also have area1 as a neighbor.
How to work around this?
I use a Relink function to solve parent back-reference:
public class District
{
private Area parent; //pseudo// parent_Area::Relink { all children => parent = self }
}
Value pair to solve dictionaries:
public List<KeyValuePair<District, Building>> DistrictBasePair
{
get => districtBase.ToList();
set { districtBase = value.ToDictionary(x => x.Key, x => x.Value); }
}
But neighborhood loops... I wonder how to serialize them? Do I have to write a remapping function?
1: Remapping post process from name to obj
foreach(var n in pathAreaName)
{
pathArea.Add(DictName2Area[n];
}
2: An indepedent Path Manager class design. I prefer not to use this in a small game.
What is the common way to solve this problem? Does Newtonsoft or .Net Json provide a function?
Like a ref id option can save you a lot of time writing you own ID mapping functions.
Question 2... The game world are serialized depth first way, to an ugly biased tree.
How can I change some setting or config, to get a breadth first balance tree?
When I simply serialize the world object, it turns into an biased tree, because of the complex class relations above.
If I serialize the world layer by layer, area, district, sector... I can't use the automatic object ref-id system in json.
How do you get a balanced tree? Can I control the json serialization, and make ref to an object that not yet appears?
area1:
"pathArea":{"$ref":"5","$ref":"6"}
...
area2:"$id":"5"
...
area3:"$id":"6"
Or can I get these ID, and use them when I serialize the finer layer.
Areas.json
area:"$id":"123"
Districts.json
district:"parent":{"$ref":"123"}
Edit: I migrated the code from Newtonsoft.Json to System.Text.Json (.Net 6.0). I found that the later can handle this, in area1's neighbor, area2 show area1 as a $ref.
In System.Text.Json, there's no problem 1.
Still, the tree is biased. I want a World -> {area1...} -> {district1_1...} structure. What I got is a tree that put like everything into area1, and this tree is hard to read, may have some problem when I write more info.
I found that:
M1. A [JsonPropertyOrder(10)] decorator can delay some fields being serialized within the object.
M2. A speciel designed helper/manager class can delay the info serialization after the object.
I've already written lots of medium classes in XML serialization, to make moderable prototypes. But the M2. helper classes actually change the model logic.
Should I change the game logic, just to make a prettier object tree?
Or small hacks like M1, and many more, are sufficient to write a good saving function?

How to recreate XNA's SkinnedModelProcessor

I have a model that is rigged and skinned and I would like to be able to move the bones programmatically (as opposed to a preset animation stored int he fbx file)
I can load the model fine, and see that the bones have been loaded, but changing the bone transforms doesnt seem to affect the rendered model.
I am looking at the sample project here:
http://create.msdn.com/en-US/education/catalog/sample/skinned_model
It looks like it uses an extended ModelProcessor (aka SkinnedModelProcessor) to over the DefaultEffect and return a MaterialProcessorDefaultEffect.SkinnedEffect. I am guessing that somewhere along the lines this makes the renderer transform the mesh vertices using the model bones (with a vertex shader???)
I am having problems getting this SkinnedModelProcessor working in my own project. The sample uses the Microsoft.Xna.Framework.Content.Pipeline assembly. In my project I dont see the item in the regular list of .net references. So I added it manually from the filesystem.
The problem there is that as soon as I compile, visual studio says that it cant find that namespace, even though right up until I compile it shows everything working fine
How can I recreate the SkinnedModelProcessor from the sample in my own project
or
How can I get changes to bones reflect in my rendered model?
I've had the same issue and I'm in the works on creating my own simple Skinned Model Manipulator. For the time being the easiest way is to just create a new method in the AnimationPlayer class that will allow you to alter the boneTransforms.
I made something like this for your help:
public void SetPose(Matrix rootTransform, Matrix[] boneAlteration)
{
skinningDataValue.BindPose.CopyTo(boneTransforms, 0);
for (int i = 0; i < boneTransforms.Length; i++)
{
boneTransforms[i] = boneAlteration[i] * boneTransforms[i];
}
UpdateWorldTransforms(rootTransform);
UpdateSkinTransforms();
}
For this method the Matrix Array boneAlteration is simply an array of Identity Matrices for maintaining the original bindPose. Change any of the matrices before passing it into this method to rotate the bone for that corresponding index.
It appears the reason getting the reference to the pipeline assembly was such a problem is that you have to segregate that code into a separate special project using the XNA Content Pipeline Extension Library project template

Xna Content Pipeline Extension - Loading other content within custom processor

I'm currently experimenting with Xna Content Pipeline extensions. Within that experimentation, I'm trying to load a file that contains another 'content item' that is in need of loading. For example:
public class CustomItem
{
public string Name;
public Texture2D Texture;
}
Now, in my content processor, I can create a new instance of 'CustomItem' and initialize the Name field, since it's simply a string. However, I can't load the texture file during content compilation (NOTE: The texture is just an example, ideally I'd like to be able to load any other content type).
What I'm looking for is something like:
// ... start class ...
public override CustomItem Process(SomeInputFormat input, ContentProcessorContext context)
{
return new CustomItem()
{
Name = input.ItemName,
Texture = context.LoadAsset<Texture2D>(input.ItemTexturePath) // I realise LoadAsset<T>() does not exist - it's an example of what would be ideal
};
}
// ... end class ...
Does anyone know if this is actually possible, and if so, how to go about it? I'd rather not go down the route of late loading the other content items if possible, or creating my own, custom content loading using binary readers and writers.
You can't use Texture2D in the content pipeline. You have to use Texture2DContent, which is a proxy-type for the former. In turn, you must have a mechanism in your type for allowing the member to be Texture2DContent at content build time, but Texture2D at run-time. This article gives you three ways of doing this.
You can use ContentProcessorContext.BuildAndLoadAsset to get your Texture2DContent object. This texture data will be embedded into your .xnb file for that asset.
If you don't actually need to use the texture data in the pipeline, and in particular if you intend to share the same texture between multiple assets, you can use ContentProcessorContext.BuildAsset to get an ExternalReference to the texture, which is built into its own .xnb file, external to your asset's .xnb file (and ContentManager will handle the loading for you).

TiledLib for XNA throws ArgumentException on loading

Essentially, what I'm trying to do is to load/draw a map from a Tiled map using Nick Gravelyn's TiledLib. The map is saved in Tiled's XML format. However, when I try loading the map with the line
TiledLib.Map map = Content.Load<TiledLib.Map>("Maps/Map1");
it throws an ArgumentException. The whole thing renders like this in Tiled itself:
The map's XML source looks like this (not posted directly onto StackOverflow for obvious size reasons).
It worked at first (with a pretty simple map using only one tileset) but when I edited it to include a bit more stuff then it suddenly started doing this. Could it be related to my usage of tile objects?
EDIT: I have been able to work out that using tile objects was not the culprit; this map structure still creates the same error.
you get an exception because you didn't name the objects in the map, this will cause ArgumentException in TiledLib. So, to solve this issue you need to go back to Tiled program, (re)edit this map, and select every object in the map and give it a name (not property but name) then export the map again and (re)import it into the game content. This should fix the problem.
BTW: I recommend using regular layer for collide check not object layer.
Like this: Make small png file with a transparent red rectangle tile in it in the same map tile dimensions, add new layer to the map, name it CollideLayer, and in this CollideLayer put that red rectangle tile where you want to be a collide, and then in the game code you can check to see if the CollideLayer cell is empty or not. I find this is simpler.
I',m guessing, but looking at your XML structure in the section, there are a lot of repeats of
<tile gid="0"/>
in the data element. There is no documentation or schema definition on the structure of this file on the official site, and the content pipeline source is not available. Also, the demo of TileLib comes with that section as
<data encoding="base64" compression="gzip">
H4sIAAAAAAAAC2NmYGBgpjKmFkA2jxCNrg+bHD7z0PWSYx428wmZjW4+qf7Fxyc3PnDFJS3jdzCbx4QFg8QZScTUzmcgDACR4mfdwAMAAA==
I'm not sure if you have an option to include encryption or not. But, the "gid" (global id ??) attribute cannot be the same for every tile defined if that is what it is. If I was writing this engine, I would have some way to readily identify each basic defined tile. Like a primary key on a data table. Which has to be unique. That would be the "gid" attribute for me.
Since the exception does not give any information (which is stupid - who deploys public libraries like that??), the argument exception is either that your "gid" attribute value is already defined, or that you're missing the encryption attributes which it expects in the pipeline importer or processor.

Serializing a DrawableGameComponent as XML using XNA's ContentTypeWriter class

I'm writing a small 2D shooter game in XNA, and I've decided that, so that one could implement custom made content in the game, it loads definitions of the game objects from XML files. When I found out that XNA has an easy to use XML serializer, it made it extra easy. The problem is, my objects that I want to serialize are DrawableGameComponents. XNA's XML serializer, the ContentTypeWriter class which you extend to create custom content writers, requires that the object have a constructor with no arguments to default to. DrawableGameComponent, however, requires a Game object in its constructor and will not let you set the game after the object is initialized. I cannot modify the behavior of the ContentTypeWriter enough, however, to accept a non-blank constructor, because the content is loaded by an entirely different method that I cannot overwrite. So essentially I have this:
class Star : DrawableGameComponent{
public Star(Game game)
: base(game)
{
}
}
With the ContentTypeWriter requiring a constructor with no arguments. I can't create one though, because then I have no way to get a Game object into the Star class. I could just not make it a DrawableGameComponent, but I am trying to decouple these objects from the primary game class such that I can reuse them, etc. and without a Game object this is absurdly difficult. My question therefore is, does anyone know how to modify ContentTypeWriter enough to allow a constructor with arguments, or any ways around this?
I also thought about writing my own XML parsing code using XPath or the Linq XML classes but XNA throws a fit if I have any XML files in the project that do not follow the XNA schema and won't build. Would it be reasonable to Write a base class with only the primary fields of the class and a DrawableGameComponent version that uses the decorator pattern, and serialize only the base? I'm pulling out my hair trying to get around this, and wondering what exactly I should be doing in this situation.
I am also building levels via parsing level files, and i use System.Xml to load data. I changed the properties on the Xml file i added to the following:
Build Action: None
Copy To Output Directory: Copy If Newer
then i wrote some code like this:
public static LevelInfo LoadLevel(
string xmlFile,
GraphicsDevice device,
PhysicsSimulator sim,
ContentManager content)
{
FileInfo xmlFileInfo = new FileInfo(xmlFile);
XDocument fileDoc = XDocument.Load(xmlFile);
//this part is game specific
LevelInfo levelData = new LevelInfo();
levelData.DynamicObjects = LevelLoader.LoadDynamicObjects(device, sim, content, xmlFileInfo, fileDoc);
levelData.StaticObjects = LevelLoader.LoadStaticObjects(device, sim, content, xmlFileInfo, fileDoc);
levelData.LevelAreas = LevelLoader.LoadAreas(device, xmlFileInfo, fileDoc);
return levelData;
}
This is just a sample but it lets you build objects however you want with whatever XML data you want.
For those curious, here's the xml file:
<Level>
<Object Type="Custom"
PositionX="400"
PositionY="400"
IsStatic="true"
Rotation="0"
Texture="sampleObj1_geometrymap"
Mass="5"
ColorR="0"
ColorG="255"
ColorB="0">
</Object>
<Object Type="Custom"
PositionX="400"
PositionY="600"
IsStatic="false"
Rotation="0"
Texture="sampleObj2_geometrymap"
Mass="5"
ColorR="230"
ColorG="230"
ColorB="255">
</Object>
<Object Type="Area"
MinPositionX="0"
MinPositionY="0"
MaxPositionX="300"
MaxPositionY="300"
AreaType="Goal">
</Object>
</Level>

Categories

Resources