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).
Related
I built a dynamic Survey type application where users can set up their template of questions/attributes that need to be captured. A template can have multiple sections, where each section contains multiple questions/attributes.
We write out the data in JSON according to the template, to be consumed or sent by webhooks, for external systems. Rather than developing a layer for each external integration to deserialize our system's object and transform it for the external system's consumption, I was thinking of writing dynamic mapper that uses a configuration schema(maybe JSON) that will map your source to your destination. Which will be great, as soon as either the source or destination changes, the configuration can just be adjusted accordingly, without having to change code and re-publish.
I'm using this system as an example, but I'm seeing multiple applications for different use cases, with the main one being a source API that needs to integrate with multiple external API's.
Source JSON example:
{
"Section1": {
"S1Attribute1": "S1Attribute1Answer",
"S1Attribute2": "S1Attribute2Answer",
"S1Attribute3": "S1Attribute3Answer"
},
"Section2": {
"S2Attribute1": "S2Attribute1Answer",
"S2Attribute2": "S2Attribute2Answer"
}
}
Destination 1 Example:
{
"SectionsFlattened": {
"S1Attribute1": "S1Attribute1Answer",
"S1Attribute2": "S1Attribute2Answer",
"S1Attribute3": "S1Attribute3Answer",
"S2Attribute1": "S2Attribute1Answer",
"S2Attribute2": "S2Attribute2Answer"
}
}
Destination 2 Example:
{
"NewSection1Name": {
"NewS1Attribute1Name": "S1Attribute1Answer",
"NewS1Attribute2Name": "S1Attribute2Answer",
"NewS1Attribute3Name": "S1Attribute3Answer",
"NewSection2Name": {
"NewS2Attribute1Name": "S2Attribute1Answer",
"NewS2Attribute2Name": "S2Attribute2Answer"
}
}
}
This is pretty simple examples of just moving properties around, but the possibilities are endless of how one would need to transform it.
It feels like a problem that somebody would have investigated or solved maybe, but in all my research I don't seem to find anything concrete, or I'm struggling to find a good starting point - maybe I'm approaching the problem incorrectly. Any suggestions/guidance/articles/libraries would be appreciated.
I wanted to ask if it's possible to save preferences in unity editor and use them when project is built and access them from other platform.
For example, I have set n=5 then saved it using playerprefs in unity editor and then I want to get the same integer on other platform when the project is build.
Is there any possible way to achieve this type of problem?
Sounds like you'd be better off using JSON and serializing your data. In the desktop, Unity saves PlayerPrefs to the registry. In Android, it is saved to a manifest file. So, in other words.. No, PlayerPrefs is not a cross-platform kinda thing. Furthermore, if this is "save-game" data, you should know that PlayerPrefs is used to store simple data like an Options menu. For saving an entire save game file you are going to want to use something a little more robust.
Sounds like what you want is turning your data (the value of n in your example) into assets in your builds.
PlayerPrefs does not fit your need because it does not create assets at the build time that could be loaded back during run time. Instead, it is used to serialize data generated during run time to users' devices and use across application sessions (by session I mean the time from launch to quit an application).
My suggestions would be finding someway to serialize your data into assets that Unity can read. There are at least two ways of doing such thing:
Using ScriptableObject. ScriptableObject is a class that inherits from UnityEngine.Object that is designed for serializing data as assets. Here is a simple example:
[CreateAssetMenu("my_data.asset", "My Data")]
public class Data : ScriptableObject
{
[SerializeField]
private int _n;
[SerializeField]
private string _someString;
public int N { get { return _n; }}
public string SomeString { get { return _someString; }}
}
The normal usage would be defining your data class , say Data, that inherits from ScriptableObject. Then, you put desired data into it as serialized fields (_n and _someString here). The attribute CreateAssetMenu is used to create a menu item in the Unity editor for you to create the asset.
After doing this, you can use the data in your MonoBehaviour by referencing it through a field marked with [SerializeField] just like any other Unity assets (like materials or sprites):
public class SomeMono : MonoBehaviour
{
[SerializeField]
private Data _data;
void Awake()
{
Debug.Log("Data.SomeString is " + _data.SomeString);
}
}
Serialize the data into your own format (JSON, BSON, XML, whatever you like) and save them and read back as TextAsset. Here is an example:
public class SomeMono2 : MonoBehaviour
{
[SerializeField]
private TextAsset _text;
void Awake()
{
// Assumes that the data is just a plain string sequence.
// However, it could be any text data you like.
Debug.Log(_text.text);
// Or you can serialize your data as bytes
// byte[] byteData = _text.bytes
}
}
Please note that Unity only automatically identifies files with extension txt (and maybe json? not quite sure about this one) as text assets.
I've written a SaveLoad class, which contains a Savegame class that has a bunch of ints, doubles, bools but also more complex things like an array of self-written class objects.
That savegame object is being created, serialized and AES encrypted on save and vice versa on load - so far, so good.
The problem I'm facing now is that if there are new variables (in a newer version of the game) that have to be stored and loaded, the game crashes on load, because the new variables can't be loaded correctly (because they are not contained in the old save file). E.g. ints and doubles contain the default 0 while an array is not initialized, thus null.
My current "solution": For each variable that is being loaded, check if it doesn't contain a specific value (which I set in the Savegame class).
For example: In Savegame I set
public int myInt = int.MinValue;
and when loading, I check:
if(savegame.myInt != int.MinValue){
//load successful
}else{
//load failed
};
This works so far for int and double, but once I hit the first bool, I realized, that for every variable I have to find a value that makes "no sense"(not reachable usually), thus was a failed load. => Shitty method for bools.
I could now go ahead and convert all bools to int, but this is getting ugly...
There must be a cleaner and/or smarter solution to this. Maybe some sort of savegame migrator? If there is a well done, free plugin for this, that would also be fine for me, but I'd prefer a code-solution, which may also be more helpful for other people with a similar problem.
Thanks in advance! :)
Your issue is poor implementation.
If you are going to be having changes like this, you should be following Extend, Deprecate, Delete (EDD).
In this case, you should be implementing new properties/fields as nullables until you can go through and data repair your old save files. This way, you can check first if the loaded field is null or has a value. If it has a value, you're good to go, if it's null, you don't have a value, you need to handle that some way.
e.g.
/*We deprecate the old one by marking it obsolete*/
[Obsolete("Use NewSaveGameFile instead")]
public class OldSaveGameFile
{
public int SomeInt { get; set; }
}
/*We extend by creating a new class with old one's fields*/
/*and the new one's fields as nullables*/
public class NewSaveGameFile
{
public int SomeInt { get; set; }
public bool? SomeNullableBool { get; set; }
}
public class FileLoader
{
public SavedGame LoadMyFile()
{
NewSaveGameFile newFile = GetFileFromDatabase(); // Code to load the file
if (newFile.SomeNullableBool.HasValue)
{
// You're good to go
}
else
{
// It's missing this property, so set it to a default value and save it
}
}
}
Then once everything has been data repaired, you can fully migrate to the NewSaveGameFile and remove the nullables (this would be the delete step)
So one solution would be to store the version of the save file system in the save file itself. So a property called version.
Then when initially opening the file, you can call the correct method to load the save game. It could be a different method, an interface which gets versioned, different classes, etc but then you would require one of these for each save file version you have.
After loading it in file's version, you could then code migration objects/methods that would populate the default values as it becomes a newer version in memory. Similar to your checks above, but you'd need to know which properties/values need to be set between each version and apply the default. This would give you the ability to migrate forward to each version of the save file, so a really old save could be updated to the newest version available.
I'm facing the same problem and trying to build a sustainable solution. Ideally someone should be able to open the game in 10 years and still access their save, even if the game has changed substantially.
I'm having a hard time finding a library that does this for me, so I may build my own (please let me know if you know of one!)
The way that changing schemas is generally handled in the world of web-engineering is through migrations-- if an old version of a file is found, we run it through sequential schema migrations until it's up-to-date.
I can think of two ways to do this:
Either you could save all saved files to the cloud, say, in MongoDB, then change their save data for them whenever they make updates or
You need to run old save data through standardized migrations on the client when they attempt to load an old version of the save file
If I wanted to make the client update stale saved states then, every time I need to change the structure of the save file (on a game that's been released):
Create a new SavablePlayerData0_0_0 where 0_0_0 is using semantic versioning
Make sure every SavablePlayerData includes public string version="0_0_0"
We'll maintain static Dictionary<string, SavedPlayerData> versionToType = {"0_0_0": typeof(SavablePlayerData0_0_0)} and a static string currentSavedDataVersion
We'll also maintain a list of migration methods which we NEVER get rid of, something like:
Something like
public SavablePlayerData0_0_1 Migration_0_0_0_to_next(SavablePlayerData0_0_0 oldFile)
{
return new SavablePlayerData0_0_1(attrA: oldFile.attrA, attrB: someDefault);
}
Then you'd figure out which version they were on from the file version, the run their save state through sequential migrations until it matches the latest, valid state.
Something like (total pseudocode)
public NewSavedDataVersion MigrateToCurrent(PrevSavedDataVersion savedData)
{
nextSavedData = MigrationManager.migrationDict[GetVersion(savedData)]
if (GetVersion(nextSavedData) != MigrationManager.currentVersion) {
return MigrateToCurrent(nextSavedData, /* You'd keep a counter to look up the next one */)
}
}
Finally, you'd want to make sure you use a type alias and [Obsolete] to quickly shift over your codebase to the new save version
It might all-in-all be easier to just work with save-file-in-the-cloud so you can control migration. If you do this, then when a user tries to open the game with an older version, you must block them and force them to update the game to match the saved version stored in the cloud.
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!
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>