using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Drawing; using System.Xml; using System.Xml.Serialization; using System.Resources; using System.IO; using System.Collections; using System.Windows.Forms; namespace SpriteLibrary { internal struct ImageStruct { internal Image TheImage; internal string ImageName; } /// /// Store Sprite information in a database. You can preload your database with sprite definitions, and then /// create the sprites as needed. This can drastically reduce the initial load time of a game or something. /// Though, what it really does is spread out the load time. It still takes the same amount of time to /// load all the sprites, it just loads them on-demand. Using a dictionary often hides any load time issues. /// public class SpriteDatabase { /// /// This is the list of SpriteInfo records that the database knows about. You can create your own list, /// modify this list, or whatever. The database has some reasonable functions for loading and saving a /// sprite database. /// public List SpriteInfoList = new List(); List TheImages = new List(); ResourceManager myResourceManager = null; string Filename = ""; Size SnapGridSize = new Size(5, 5); System.Drawing.Icon LibIcon = null; /// /// The sprite database instantiation function. The filename can either be a file on the computer or it /// can be the string name of a resource (the filename without the extension. If your file is accessed /// by Properties.Resources.MySprites, the "filename" would be "MySprites") /// /// The ResourceManager for your project. Usually /// Properties.Resources.ResourceManager /// Either a path and file (like: @"c:\users\me\Desktop\myfile.xml") or /// the name of a resource (like: "myfile") public SpriteDatabase(ResourceManager theResourceManager, string filename) { myResourceManager = theResourceManager; Filename = filename; Load(); } internal void Load() { LoadSpriteInfo(); } internal ResourceManager GetResourceManager() { return myResourceManager; } /// /// Tell the database to save the sprite definitions. Use this while you are creating your game. /// When you are done, you will usually want to take your sprite definition file and add it to the /// resources of your game. The resources cannot be saved to, so you cannot continue to add new sprites /// once you are loading and saving them from a resources file. But, the resources file is included with /// the program when you build it. /// public void Save() { if(!DoesResourceExist(Filename)) { //we will try to save it as a file try { WriteToXmlFile>(Filename, SpriteInfoList); } catch (Exception e) { throw new Exception("SpriteDatabase failed to save: Filename:" + Filename +"\n" + "ERROR: " + e.ToString(), e); } } } /// /// Change the Icon for the SpriteEntryForm /// /// An icon image public void SetIcon(System.Drawing.Icon toSet) { LibIcon = toSet; } /// /// The SnapGrid is the block-size that your sprite will be. For example, I will often have sprites with /// a snapgrid of 50,50. This means that the sprite can be 50x50, 100x50, or anything with a step-size /// specified in the snap-grid. It takes a "Size" specified by System.Drawing.Size. /// /// The size of the grid space to snap to when dragging public void SetSnapGridSize(Size GridSize) { if (GridSize.Width <= 0) return; if (GridSize.Height <= 0) return; if (GridSize.Width > 500) return; if (GridSize.Height > 500) return; SnapGridSize = GridSize; } //******************************* //**** Sprite Info Functions *** //******************************* #region SpriteInfo Functions void LoadSpriteInfo() { if (DoesResourceExist(Filename)) { //This clears out the old list, as it gets replaced. SpriteInfoList = LoadObjectFromXmlFile>(Filename, myResourceManager); } else { //try loading it from an actual filename if (File.Exists(Filename)) SpriteInfoList = ReadFromXmlFile>(Filename); } //If neither works, we end up with an empty file. //If it fails, SpriteInfoList is null and things explode. if (SpriteInfoList == null) SpriteInfoList = new List(); //make an empty one so things do not explode. } /// /// Return a list of the SpriteNames that this Database knows how to create. /// /// A list of strings, each one is the name of a sprite public List SpriteNames() { List theNames = new List(); foreach (SpriteInfo si in SpriteInfoList) { theNames.Add(si.SpriteName); } return theNames; } internal bool DoesResourceExist(string resourcename) { if (myResourceManager == null) return false; if (myResourceManager.GetObject(resourcename) != null) return true; return false; } /// /// Open a Sprite Edit Window. This window does not let you draw a sprite. What it does is to help /// you define your sprites and makes the process of using Sprites in your program a lot easier. /// /// /// This is an example of how to use a SpriteDatabase. /// When you begin developing your project, you want to start by creating a SpriteDatabase and pointing /// it to a file, and then opening up an EditorWindow. /// /// public partial class MyGameForm : Form /// { /// SpriteController mySpriteController = null; /// SpriteDatabase mySpriteDatabase = null; /// /// public MyGameForm() /// { /// InitializeComponent(); /// MainDrawingArea.BackgroundImage = Properties.Resources.Background; /// MainDrawingArea.BackgroundImageLayout = ImageLayout.Stretch; /// /// string Desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); /// string MyFile = Path.Combine(Desktop, "myFile.xml"); /// mySpriteDatabase = new SpriteDatabase(Properties.Resources.ResourceManager, MyFile); /// /// mySpriteController = new SpriteController(MainDrawingArea, mySpriteDatabase); /// /// mySpriteDatabase.OpenEditWindow(); /// mySpriteDatabase.Save(); /// } /// } /// /// The Editor Window will let you find the sprites that are contained in the various images you have /// as resources in your program, and it will save a file with those sprite templates. Any SpriteController /// that you have instantiated with a Sprite Database (see ) /// will now be able to create named sprites from the templates defined in the database. After the first use, the /// named sprites will be accessible from within that controller just like any other named sprites. /// /// After you have created your SpriteDatabase file, you will want to add your file to your program resources. /// Then, you will change the SpriteDatabase to use the resource instead of a file. If we named the file /// "MySpriteDatabase.xml", and it got added to your resources with the name "MySpriteDatabase", you would /// pass "MySpriteDatabase" to the database instantiation. /// /// public partial class MyGameForm : Form /// { /// SpriteController mySpriteController = null; /// SpriteDatabase mySpriteDatabase = null; /// /// public MyGameForm() /// { /// InitializeComponent(); /// MainDrawingArea.BackgroundImage = Properties.Resources.Background; /// MainDrawingArea.BackgroundImageLayout = ImageLayout.Stretch; /// /// //string Desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); /// //string MyFile = Path.Combine(Desktop, "myFile.xml"); /// //mySpriteDatabase = new SpriteDatabase(Properties.Resources.ResourceManager, MyFile); /// mySpriteDatabase = new SpriteDatabase(Properties.Resources.ResourceManager, "MySpriteDatabase"); /// /// mySpriteController = new SpriteController(MainDrawingArea, mySpriteDatabase); /// /// //mySpriteDatabase.OpenEditWindow(); /// //mySpriteDatabase.Save(); /// } /// } /// /// /// public void OpenEditWindow(int FirstItemIndex=-1) { SpriteEntryForm SEF = new SpriteEntryForm(this, SpriteInfoList, SnapGridSize); SEF.SetInitialSprite(FirstItemIndex); if (LibIcon != null) SEF.SetIcon(LibIcon); SEF.ShowDialog(); //Use the updated list returned from the form. SpriteInfoList.Clear(); SpriteInfoList.AddRange(SEF.GetUpdatedList()); } /// /// Generate a new, named sprite from a sprite template stored in the database. Most of the time you do /// not want to use this yourself. SpriteControllers that are defined with a database will automatically /// look up sprite templates that they do not have sprites for. This function is just a wrapper for SmartDuplicateSprite. /// /// The name of the sprite to load. Names are case-sensitive. /// The sprite controller that will store the sprite in its cache /// A new, named sprite, or null if no such template is found. public Sprite SpriteFromName(string Name, SpriteController ControllerToUse) { return SmartDuplicateSprite(ControllerToUse, Name, true); } #endregion #region General Functions /// /// This function returns an image from the Properties.Resources. If we tell it to UseSmartImages, then /// it caches the image in memory. This makes it a little faster to return. If you have a lot of sprites /// to load, using this system can speed up things a fair bit. But, try to remember not to change the /// image that this returns unless you duplicate it first. Otherwise you will end up changing the image /// for all the other times you reference it. This is usualy a bad thing. /// /// The string name of the image. If your image is usually named /// Properties.Resources.mySpriteImage, you will want to have "mySpriteImage" as the Name passed /// to GetImageFromName /// A parameter stating whether we should cache the image in memory /// or simply retrieve it from the resource manager. /// The resource image with the specified name public Image GetImageFromName(string Name, bool UseSmartImages) { Image MyImage = null; if (UseSmartImages) { foreach (ImageStruct IS in TheImages) { if (IS.ImageName.Equals(Name, StringComparison.InvariantCultureIgnoreCase)) { MyImage = IS.TheImage; break; } } } if (MyImage == null) { ResourceManager rm = myResourceManager; MyImage = (Bitmap)rm.GetObject(Name); if (UseSmartImages) { ImageStruct NewIS = new ImageStruct(); NewIS.ImageName = Name; NewIS.TheImage = MyImage; TheImages.Add(NewIS); } } return MyImage; } /// /// Return a list of the image names in the Properties.Resources /// /// A list of image names in the Properties.Resources public List GetImageNames() { List Names = new List(); if (myResourceManager == null) return Names; ResourceSet Rs = myResourceManager.GetResourceSet(System.Globalization.CultureInfo.CurrentCulture, true, true); foreach (DictionaryEntry entry in Rs) { string resourceKey = entry.Key.ToString(); //The name object resource = entry.Value; //The object itself if (resource is Image) Names.Add(resourceKey); } return Names; } /// /// This code is mostly handled by the sprite controller. If the SpriteController has a SpriteDatabase /// registered, then it will automatically ask the SpriteDatabase to create any sprite it does not already /// have. /// /// The controller that will manage the newly created Sprite /// The name of the sprite to look up and then create /// Whether or not we should cache images to give a very small increase in speed /// internal Sprite SmartDuplicateSprite(SpriteController theController, string SpriteName, bool UseSmartImages = true) { Sprite DestSprite = theController.SpriteFromNameInternal(SpriteName); if (DestSprite != null) return new Sprite(DestSprite); //If it does not exist, make it foreach (SpriteInfo SI in SpriteInfoList) { if (SI.SpriteName == SpriteName) { SI.CreateSprite(theController, this); return theController.DuplicateSprite(SpriteName); } } return null; } #endregion #region Generic XML Funcs /// /// Load in an XML serialized item from the specified ResourceManager. You will usually make one of these by /// creating an object and using SpriteDatabase.WriteToXmlFile to /// save it to a file on your desktop. Then you can drag and drop that file into your project and then use this /// LoadObjectFromXmlFile function. /// /// The type of object to load. It could be something as simple as an int, a class, or a list of classes. /// The resource item to load. If you would access it like: properties.resources.myFile, /// the correct value to put here would be "myFile" /// The resource manager. Usually Properties.Resources.ResourceManager /// An object of the value you specified. Or null if it fails. public static T LoadObjectFromXmlFile(string XMLResourceToLoad, ResourceManager MyManager) where T : new() { //Load in the sprite data XmlSerializer serializer = new XmlSerializer(typeof(T)); // Retrieves String and Image resources. object titem = MyManager.GetObject(XMLResourceToLoad); byte[] item = (byte[])System.Text.Encoding.UTF8.GetBytes((string)titem); try { return (T)serializer.Deserialize(new MemoryStream(item)); } finally { } } /// /// Writes the given object instance to an XML file. /// Only Public properties and variables will be written to the file. These can be any type though, even other classes. /// If there are public properties/variables that you do not want written to the file, decorate them with the [XmlIgnore] attribute. /// Object type must have a parameterless constructor. /// /// The type of object being written to the file. /// The file path to write the object instance to. /// The object instance to write to the file. public static void WriteToXmlFile(string filePath, T objectToWrite) where T : new() { TextWriter writer = null; try { var serializer = new XmlSerializer(typeof(T)); writer = new StreamWriter(filePath); serializer.Serialize(writer, objectToWrite); } finally { if (writer != null) writer.Close(); } } /// /// Reads an object instance from an XML file. /// Object type must have a parameterless constructor. /// /// The type of object to read from the file. /// The file path to read the object instance from. /// Returns a new instance of the object read from the XML file. public static T ReadFromXmlFile(string filePath) where T : new() { TextReader reader = null; try { var serializer = new XmlSerializer(typeof(T)); reader = new StreamReader(filePath); return (T)serializer.Deserialize(reader); } finally { if (reader != null) reader.Close(); } } /// /// This is a generic function which the SpriteDatabase uses. It does XML Serialization of most anything, /// and generates an XML String. XML Serialization will take any public value of a public class and /// make an XML entry for it. It is a very convienent way to save data. You can "Deserialize" the value /// with the ReadFromXMLString function. /// /// The type of the item that you are trying to serialize /// the variable you are trying to turn into XML /// An XML string public static string WriteToXMLString(T toSerialize) { XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType()); using (StringWriter textWriter = new StringWriter()) { xmlSerializer.Serialize(textWriter, toSerialize); return textWriter.ToString(); } } /// /// This is a generic function which the SpriteDatabase uses. It does XML Deserialization of most anything, /// and generates an XML String. XML Serialization will take any public value of a public class and /// make an XML entry for it. It is a very convienent way to save and retrieve data. You can "Serialize" the value /// with the WriteToXMLString function. /// /// The type of the item that you are trying to deserialize /// an XML string, of something you serialized previously /// An object of type T public static T ReadFromXmlString(string toDeserialize) where T : new() { XmlSerializer xmlSerializer = new XmlSerializer(typeof(T)); using (StringReader textReader = new StringReader(toDeserialize)) return (T)xmlSerializer.Deserialize(textReader); } /// /// This is an inefficient, but simple function to clone a class. It works by serializing an item /// to a string, and then deserializing it into a class. The end result is that any value which is /// publically visible is duplicated, but it is a completely separate class from the original. /// /// The type of the item to clone /// The actual object to clone /// A duplicate of the original public static T CloneByXMLSerializing(T ObjectToClone) { XmlSerializer xmlSerializer = new XmlSerializer(typeof(T)); string dest; using (StringWriter textWriter = new StringWriter()) { xmlSerializer.Serialize(textWriter, ObjectToClone); dest = textWriter.ToString(); } using (StringReader textReader = new StringReader(dest)) return (T)xmlSerializer.Deserialize(textReader); } #endregion } }