482 lines
22 KiB
C#
482 lines
22 KiB
C#
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public class SpriteDatabase
|
|
{
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public List<SpriteInfo> SpriteInfoList = new List<SpriteInfo>();
|
|
List<ImageStruct> TheImages = new List<ImageStruct>();
|
|
ResourceManager myResourceManager = null;
|
|
string Filename = "";
|
|
Size SnapGridSize = new Size(5, 5);
|
|
System.Drawing.Icon LibIcon = null;
|
|
|
|
/// <summary>
|
|
/// 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")
|
|
/// </summary>
|
|
/// <param name="theResourceManager">The ResourceManager for your project. Usually
|
|
/// Properties.Resources.ResourceManager</param>
|
|
/// <param name="filename">Either a path and file (like: @"c:\users\me\Desktop\myfile.xml") or
|
|
/// the name of a resource (like: "myfile")</param>
|
|
public SpriteDatabase(ResourceManager theResourceManager, string filename)
|
|
{
|
|
myResourceManager = theResourceManager;
|
|
Filename = filename;
|
|
Load();
|
|
}
|
|
|
|
internal void Load()
|
|
{
|
|
LoadSpriteInfo();
|
|
}
|
|
|
|
internal ResourceManager GetResourceManager()
|
|
{
|
|
return myResourceManager;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public void Save()
|
|
{
|
|
if(!DoesResourceExist(Filename))
|
|
{
|
|
//we will try to save it as a file
|
|
try
|
|
{
|
|
WriteToXmlFile<List<SpriteInfo>>(Filename, SpriteInfoList);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw new Exception("SpriteDatabase failed to save: Filename:" + Filename +"\n" + "ERROR: " + e.ToString(), e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Change the Icon for the SpriteEntryForm
|
|
/// </summary>
|
|
/// <param name="toSet">An icon image</param>
|
|
public void SetIcon(System.Drawing.Icon toSet)
|
|
{
|
|
LibIcon = toSet;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="GridSize">The size of the grid space to snap to when dragging</param>
|
|
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<List<SpriteInfo>>(Filename, myResourceManager);
|
|
}
|
|
else
|
|
{
|
|
//try loading it from an actual filename
|
|
if (File.Exists(Filename))
|
|
SpriteInfoList = ReadFromXmlFile<List<SpriteInfo>>(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<SpriteInfo>(); //make an empty one so things do not explode.
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return a list of the SpriteNames that this Database knows how to create.
|
|
/// </summary>
|
|
/// <returns>A list of strings, each one is the name of a sprite</returns>
|
|
public List<string> SpriteNames()
|
|
{
|
|
List<string> theNames = new List<string>();
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <example>
|
|
/// 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.
|
|
/// <code lang="C#">
|
|
/// 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();
|
|
/// }
|
|
/// }
|
|
/// </code>
|
|
/// 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 <see cref="SpriteController(PictureBox, SpriteDatabase)"/>)
|
|
/// 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.
|
|
/// <code lang="C#">
|
|
/// 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();
|
|
/// }
|
|
/// }
|
|
/// </code>
|
|
/// </example>
|
|
/// <param name="FirstItemIndex"></param>
|
|
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());
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="Name">The name of the sprite to load. Names are case-sensitive.</param>
|
|
/// <param name="ControllerToUse">The sprite controller that will store the sprite in its cache</param>
|
|
/// <returns>A new, named sprite, or null if no such template is found.</returns>
|
|
public Sprite SpriteFromName(string Name, SpriteController ControllerToUse)
|
|
{
|
|
return SmartDuplicateSprite(ControllerToUse, Name, true);
|
|
}
|
|
#endregion
|
|
|
|
#region General Functions
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="Name">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</param>
|
|
/// <param name="UseSmartImages">A parameter stating whether we should cache the image in memory
|
|
/// or simply retrieve it from the resource manager.</param>
|
|
/// <returns>The resource image with the specified name</returns>
|
|
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;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Return a list of the image names in the Properties.Resources
|
|
/// </summary>
|
|
/// <returns>A list of image names in the Properties.Resources</returns>
|
|
public List<string> GetImageNames()
|
|
{
|
|
List<string> Names = new List<string>();
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="theController">The controller that will manage the newly created Sprite</param>
|
|
/// <param name="SpriteName">The name of the sprite to look up and then create</param>
|
|
/// <param name="UseSmartImages">Whether or not we should cache images to give a very small increase in speed</param>
|
|
/// <returns></returns>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Load in an XML serialized item from the specified ResourceManager. You will usually make one of these by
|
|
/// creating an object and using <see cref="WriteToXmlFile{T}(string, T)"/> 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.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of object to load. It could be something as simple as an int, a class, or a list of classes.</typeparam>
|
|
/// <param name="XMLResourceToLoad">The resource item to load. If you would access it like: properties.resources.myFile,
|
|
/// the correct value to put here would be "myFile"</param>
|
|
/// <param name="MyManager">The resource manager. Usually Properties.Resources.ResourceManager</param>
|
|
/// <returns>An object of the value you specified. Or null if it fails.</returns>
|
|
public static T LoadObjectFromXmlFile<T>(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
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of object being written to the file.</typeparam>
|
|
/// <param name="filePath">The file path to write the object instance to.</param>
|
|
/// <param name="objectToWrite">The object instance to write to the file.</param>
|
|
public static void WriteToXmlFile<T>(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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads an object instance from an XML file.
|
|
/// <para>Object type must have a parameterless constructor.</para>
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of object to read from the file.</typeparam>
|
|
/// <param name="filePath">The file path to read the object instance from.</param>
|
|
/// <returns>Returns a new instance of the object read from the XML file.</returns>
|
|
public static T ReadFromXmlFile<T>(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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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 <see cref="SpriteDatabase.ReadFromXmlString{T}(string)">ReadFromXMLString</see> function.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of the item that you are trying to serialize</typeparam>
|
|
/// <param name="toSerialize">the variable you are trying to turn into XML</param>
|
|
/// <returns>An XML string</returns>
|
|
public static string WriteToXMLString<T>(T toSerialize)
|
|
{
|
|
XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType());
|
|
|
|
using (StringWriter textWriter = new StringWriter())
|
|
{
|
|
xmlSerializer.Serialize(textWriter, toSerialize);
|
|
return textWriter.ToString();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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 <see cref="SpriteDatabase.WriteToXMLString{T}(T)">WriteToXMLString</see> function.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of the item that you are trying to deserialize</typeparam>
|
|
/// <param name="toDeserialize">an XML string, of something you serialized previously</param>
|
|
/// <returns>An object of type T</returns>
|
|
public static T ReadFromXmlString<T>(string toDeserialize) where T : new()
|
|
{
|
|
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
|
|
using (StringReader textReader = new StringReader(toDeserialize))
|
|
return (T)xmlSerializer.Deserialize(textReader);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of the item to clone</typeparam>
|
|
/// <param name="ObjectToClone">The actual object to clone</param>
|
|
/// <returns>A duplicate of the original</returns>
|
|
public static T CloneByXMLSerializing<T>(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
|
|
}
|
|
|
|
}
|