1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-11-22 12:58:33 +01:00

finished the xml documentation for all packages

This commit is contained in:
Ellpeck 2020-05-22 20:32:38 +02:00
parent f12284169e
commit 3f60b2460c
21 changed files with 623 additions and 4 deletions

View file

@ -5,14 +5,29 @@ using MLEM.Data.Json;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace MLEM.Data { namespace MLEM.Data {
/// <summary>
/// A set of extensions for dealing with <see cref="ContentManager"/>
/// </summary>
public static class ContentExtensions { public static class ContentExtensions {
private static readonly Dictionary<ContentManager, JsonSerializer> Serializers = new Dictionary<ContentManager, JsonSerializer>(); private static readonly Dictionary<ContentManager, JsonSerializer> Serializers = new Dictionary<ContentManager, JsonSerializer>();
/// <summary>
/// Adds a <see cref="JsonSerializer"/> to the given content manager, which allows <see cref="LoadJson{T}"/> to load JSON-based content.
/// Note that <see cref="GetJsonSerializer"/> calls this method implicitly if no serializer exists.
/// </summary>
/// <param name="content">The content manager to add the json serializer to</param>
/// <param name="serializer">The json serializer to add</param>
public static void SetJsonSerializer(this ContentManager content, JsonSerializer serializer) { public static void SetJsonSerializer(this ContentManager content, JsonSerializer serializer) {
Serializers[content] = serializer; Serializers[content] = serializer;
} }
/// <summary>
/// Returns the given content manager's json serializer.
/// This method sets a new json serializer using <see cref="SetJsonSerializer"/> if the given content manager does not yet have one.
/// </summary>
/// <param name="content">The content manager whose serializer to get</param>
/// <returns>The content manager's serializer</returns>
public static JsonSerializer GetJsonSerializer(this ContentManager content) { public static JsonSerializer GetJsonSerializer(this ContentManager content) {
if (!Serializers.TryGetValue(content, out var serializer)) { if (!Serializers.TryGetValue(content, out var serializer)) {
serializer = JsonConverters.AddAll(new JsonSerializer()); serializer = JsonConverters.AddAll(new JsonSerializer());
@ -21,11 +36,24 @@ namespace MLEM.Data {
return serializer; return serializer;
} }
/// <summary>
/// Adds a <see cref="JsonConverter"/> to the given content manager's <see cref="JsonSerializer"/>.
/// </summary>
/// <param name="content">The content manager to add the converter to</param>
/// <param name="converter">The converter to add</param>
public static void AddJsonConverter(this ContentManager content, JsonConverter converter) { public static void AddJsonConverter(this ContentManager content, JsonConverter converter) {
var serializer = GetJsonSerializer(content); var serializer = GetJsonSerializer(content);
serializer.Converters.Add(converter); serializer.Converters.Add(converter);
} }
/// <summary>
/// Loads any kind of JSON data using the given content manager's <see cref="JsonSerializer"/>.
/// </summary>
/// <param name="content">The content manager to load content with</param>
/// <param name="name">The name of the file to load</param>
/// <param name="extension">The file extension of the file to load, or ".json" by default</param>
/// <typeparam name="T">The type of asset to load</typeparam>
/// <returns>The loaded asset</returns>
public static T LoadJson<T>(this ContentManager content, string name, string extension = ".json") { public static T LoadJson<T>(this ContentManager content, string name, string extension = ".json") {
using (var stream = File.OpenText(Path.Combine(content.RootDirectory, name + extension))) { using (var stream = File.OpenText(Path.Combine(content.RootDirectory, name + extension))) {
using (var reader = new JsonTextReader(stream)) { using (var reader = new JsonTextReader(stream)) {

View file

@ -3,12 +3,15 @@ using MLEM.Misc;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace MLEM.Data.Json { namespace MLEM.Data.Json {
/// <inheritdoc />
public class Direction2Converter : JsonConverter<Direction2> { public class Direction2Converter : JsonConverter<Direction2> {
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, Direction2 value, JsonSerializer serializer) { public override void WriteJson(JsonWriter writer, Direction2 value, JsonSerializer serializer) {
writer.WriteValue(value.ToString()); writer.WriteValue(value.ToString());
} }
/// <inheritdoc />
public override Direction2 ReadJson(JsonReader reader, Type objectType, Direction2 existingValue, bool hasExistingValue, JsonSerializer serializer) { public override Direction2 ReadJson(JsonReader reader, Type objectType, Direction2 existingValue, bool hasExistingValue, JsonSerializer serializer) {
Enum.TryParse<Direction2>(reader.Value.ToString(), out var dir); Enum.TryParse<Direction2>(reader.Value.ToString(), out var dir);
return dir; return dir;

View file

@ -4,12 +4,23 @@ using System.Reflection;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace MLEM.Data.Json { namespace MLEM.Data.Json {
/// <summary>
/// A helper class that stores all of the <see cref="JsonConverter"/> types that are part of MLEM.Data.
/// </summary>
public class JsonConverters { public class JsonConverters {
/// <summary>
/// An array of all of the <see cref="JsonConverter"/>s that are part of MLEM.Data
/// </summary>
public static readonly JsonConverter[] Converters = typeof(JsonConverters).Assembly.GetExportedTypes() public static readonly JsonConverter[] Converters = typeof(JsonConverters).Assembly.GetExportedTypes()
.Where(t => t.IsSubclassOf(typeof(JsonConverter))) .Where(t => t.IsSubclassOf(typeof(JsonConverter)))
.Select(t => t.GetConstructor(Type.EmptyTypes).Invoke(null)).Cast<JsonConverter>().ToArray(); .Select(t => t.GetConstructor(Type.EmptyTypes).Invoke(null)).Cast<JsonConverter>().ToArray();
/// <summary>
/// Adds all of the <see cref="JsonConverter"/> objects that are part of MLEM.Data to the given <see cref="JsonSerializer"/>
/// </summary>
/// <param name="serializer">The serializer to add the converters to</param>
/// <returns>The given serializer, for chaining</returns>
public static JsonSerializer AddAll(JsonSerializer serializer) { public static JsonSerializer AddAll(JsonSerializer serializer) {
foreach (var converter in Converters) foreach (var converter in Converters)
serializer.Converters.Add(converter); serializer.Converters.Add(converter);

View file

@ -4,12 +4,15 @@ using Microsoft.Xna.Framework;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace MLEM.Data.Json { namespace MLEM.Data.Json {
/// <inheritdoc />
public class PointConverter : JsonConverter<Point> { public class PointConverter : JsonConverter<Point> {
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, Point value, JsonSerializer serializer) { public override void WriteJson(JsonWriter writer, Point value, JsonSerializer serializer) {
writer.WriteValue(value.X.ToString(CultureInfo.InvariantCulture) + " " + value.Y.ToString(CultureInfo.InvariantCulture)); writer.WriteValue(value.X.ToString(CultureInfo.InvariantCulture) + " " + value.Y.ToString(CultureInfo.InvariantCulture));
} }
/// <inheritdoc />
public override Point ReadJson(JsonReader reader, Type objectType, Point existingValue, bool hasExistingValue, JsonSerializer serializer) { public override Point ReadJson(JsonReader reader, Type objectType, Point existingValue, bool hasExistingValue, JsonSerializer serializer) {
var value = reader.Value.ToString().Split(' '); var value = reader.Value.ToString().Split(' ');
return new Point(int.Parse(value[0], CultureInfo.InvariantCulture), int.Parse(value[1], CultureInfo.InvariantCulture)); return new Point(int.Parse(value[0], CultureInfo.InvariantCulture), int.Parse(value[1], CultureInfo.InvariantCulture));

View file

@ -4,17 +4,21 @@ using MLEM.Content;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace MLEM.Data.Json { namespace MLEM.Data.Json {
/// <inheritdoc />
public class RawJsonReader : RawContentReader { public class RawJsonReader : RawContentReader {
/// <inheritdoc />
public override bool CanRead(Type t) { public override bool CanRead(Type t) {
return true; return true;
} }
/// <inheritdoc />
public override object Read(RawContentManager manager, string assetPath, Stream stream, Type t, object existing) { public override object Read(RawContentManager manager, string assetPath, Stream stream, Type t, object existing) {
using (var reader = new JsonTextReader(new StreamReader(stream))) using (var reader = new JsonTextReader(new StreamReader(stream)))
return manager.GetJsonSerializer().Deserialize(reader); return manager.GetJsonSerializer().Deserialize(reader);
} }
/// <inheritdoc />
public override string[] GetFileExtensions() { public override string[] GetFileExtensions() {
return new[] {"json"}; return new[] {"json"};
} }

View file

@ -4,14 +4,17 @@ using Microsoft.Xna.Framework;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace MLEM.Data.Json { namespace MLEM.Data.Json {
/// <inheritdoc />
public class RectangleConverter : JsonConverter<Rectangle> { public class RectangleConverter : JsonConverter<Rectangle> {
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, Rectangle value, JsonSerializer serializer) { public override void WriteJson(JsonWriter writer, Rectangle value, JsonSerializer serializer) {
writer.WriteValue( writer.WriteValue(
value.X.ToString(CultureInfo.InvariantCulture) + " " + value.Y.ToString(CultureInfo.InvariantCulture) + " " + value.X.ToString(CultureInfo.InvariantCulture) + " " + value.Y.ToString(CultureInfo.InvariantCulture) + " " +
value.Width.ToString(CultureInfo.InvariantCulture) + " " + value.Height.ToString(CultureInfo.InvariantCulture)); value.Width.ToString(CultureInfo.InvariantCulture) + " " + value.Height.ToString(CultureInfo.InvariantCulture));
} }
/// <inheritdoc />
public override Rectangle ReadJson(JsonReader reader, Type objectType, Rectangle existingValue, bool hasExistingValue, JsonSerializer serializer) { public override Rectangle ReadJson(JsonReader reader, Type objectType, Rectangle existingValue, bool hasExistingValue, JsonSerializer serializer) {
var value = reader.Value.ToString().Split(' '); var value = reader.Value.ToString().Split(' ');
return new Rectangle( return new Rectangle(

View file

@ -4,14 +4,17 @@ using MLEM.Misc;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace MLEM.Data.Json { namespace MLEM.Data.Json {
/// <inheritdoc />
public class RectangleFConverter : JsonConverter<RectangleF> { public class RectangleFConverter : JsonConverter<RectangleF> {
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, RectangleF value, JsonSerializer serializer) { public override void WriteJson(JsonWriter writer, RectangleF value, JsonSerializer serializer) {
writer.WriteValue( writer.WriteValue(
value.X.ToString(CultureInfo.InvariantCulture) + " " + value.Y.ToString(CultureInfo.InvariantCulture) + " " + value.X.ToString(CultureInfo.InvariantCulture) + " " + value.Y.ToString(CultureInfo.InvariantCulture) + " " +
value.Width.ToString(CultureInfo.InvariantCulture) + " " + value.Height.ToString(CultureInfo.InvariantCulture)); value.Width.ToString(CultureInfo.InvariantCulture) + " " + value.Height.ToString(CultureInfo.InvariantCulture));
} }
/// <inheritdoc />
public override RectangleF ReadJson(JsonReader reader, Type objectType, RectangleF existingValue, bool hasExistingValue, JsonSerializer serializer) { public override RectangleF ReadJson(JsonReader reader, Type objectType, RectangleF existingValue, bool hasExistingValue, JsonSerializer serializer) {
var value = reader.Value.ToString().Split(' '); var value = reader.Value.ToString().Split(' ');
return new RectangleF( return new RectangleF(

View file

@ -4,12 +4,15 @@ using Microsoft.Xna.Framework;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace MLEM.Data.Json { namespace MLEM.Data.Json {
/// <inheritdoc />
public class Vector2Converter : JsonConverter<Vector2> { public class Vector2Converter : JsonConverter<Vector2> {
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, Vector2 value, JsonSerializer serializer) { public override void WriteJson(JsonWriter writer, Vector2 value, JsonSerializer serializer) {
writer.WriteValue(value.X.ToString(CultureInfo.InvariantCulture) + " " + value.Y.ToString(CultureInfo.InvariantCulture)); writer.WriteValue(value.X.ToString(CultureInfo.InvariantCulture) + " " + value.Y.ToString(CultureInfo.InvariantCulture));
} }
/// <inheritdoc />
public override Vector2 ReadJson(JsonReader reader, Type objectType, Vector2 existingValue, bool hasExistingValue, JsonSerializer serializer) { public override Vector2 ReadJson(JsonReader reader, Type objectType, Vector2 existingValue, bool hasExistingValue, JsonSerializer serializer) {
var value = reader.Value.ToString().Split(' '); var value = reader.Value.ToString().Split(' ');
return new Vector2(float.Parse(value[0], CultureInfo.InvariantCulture), float.Parse(value[1], CultureInfo.InvariantCulture)); return new Vector2(float.Parse(value[0], CultureInfo.InvariantCulture), float.Parse(value[1], CultureInfo.InvariantCulture));

View file

@ -5,12 +5,20 @@ using Lidgren.Network;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace MLEM.Data { namespace MLEM.Data {
/// <summary>
/// A net buffer serializer allows easily writing generic objects into a Lidgren.Network <see cref="NetBuffer"/>.
/// It can be used both for serialization of outgoing packets, and deserialization of incoming packets.
/// Before serializing and deserializing an object, each of the object's fields has to have a handler. New handlers can be added using <see cref="AddHandler{T}(System.Action{Lidgren.Network.NetBuffer,T},System.Func{Lidgren.Network.NetBuffer,T})"/> or <see cref="AddHandler{T}(Newtonsoft.Json.JsonSerializer)"/>.
/// </summary>
public class NetBufferSerializer { public class NetBufferSerializer {
private readonly Dictionary<Type, Action<NetBuffer, object>> writeFunctions = new Dictionary<Type, Action<NetBuffer, object>>(); private readonly Dictionary<Type, Action<NetBuffer, object>> writeFunctions = new Dictionary<Type, Action<NetBuffer, object>>();
private readonly Dictionary<Type, Func<NetBuffer, object>> readFunctions = new Dictionary<Type, Func<NetBuffer, object>>(); private readonly Dictionary<Type, Func<NetBuffer, object>> readFunctions = new Dictionary<Type, Func<NetBuffer, object>>();
private readonly Dictionary<Type, FieldInfo[]> fieldCache = new Dictionary<Type, FieldInfo[]>(); private readonly Dictionary<Type, FieldInfo[]> fieldCache = new Dictionary<Type, FieldInfo[]>();
/// <summary>
/// Create a new net buffer serializer with some default serialization and deserialization implementations for various types.
/// </summary>
public NetBufferSerializer() { public NetBufferSerializer() {
foreach (var method in typeof(NetBuffer).GetMethods(BindingFlags.Instance | BindingFlags.Public)) { foreach (var method in typeof(NetBuffer).GetMethods(BindingFlags.Instance | BindingFlags.Public)) {
if (method.GetParameters().Length == 0 && method.Name.StartsWith("Read", StringComparison.Ordinal) && method.Name.Substring(4) == method.ReturnType.Name) if (method.GetParameters().Length == 0 && method.Name.StartsWith("Read", StringComparison.Ordinal) && method.Name.Substring(4) == method.ReturnType.Name)
@ -28,6 +36,14 @@ namespace MLEM.Data {
this.AddHandler((buffer, o) => buffer.Write(o), buffer => buffer.ReadDirection()); this.AddHandler((buffer, o) => buffer.Write(o), buffer => buffer.ReadDirection());
} }
/// <summary>
/// Serializes the given object into the given net buffer.
/// Note that each field in the object has to have a handler (<see cref="AddHandler{T}(System.Action{Lidgren.Network.NetBuffer,T},System.Func{Lidgren.Network.NetBuffer,T})"/>)
/// </summary>
/// <param name="buffer">The buffer to serialize into</param>
/// <param name="o">The object to serialize</param>
/// <param name="flags">The binding flags to search for fields in the object by</param>
/// <exception cref="ArgumentException">If any of the object's fields has no writer</exception>
public void Serialize(NetBuffer buffer, object o, BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) { public void Serialize(NetBuffer buffer, object o, BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) {
foreach (var field in this.GetFields(o.GetType(), flags)) { foreach (var field in this.GetFields(o.GetType(), flags)) {
if (!this.writeFunctions.TryGetValue(field.FieldType, out var func)) if (!this.writeFunctions.TryGetValue(field.FieldType, out var func))
@ -36,6 +52,14 @@ namespace MLEM.Data {
} }
} }
/// <summary>
/// Deserializes the net buffer's content into the given object.
/// If this is used for packet serialization, a new instance of the required type has to be created before this method is called.
/// </summary>
/// <param name="buffer">The buffer to read the data from</param>
/// <param name="o">The object to serialize into</param>
/// <param name="flags">The binding flags to search for fields in the object by</param>
/// <exception cref="ArgumentException">If any of the object's fields has no reader</exception>
public void Deserialize(NetBuffer buffer, object o, BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) { public void Deserialize(NetBuffer buffer, object o, BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) {
foreach (var field in this.GetFields(o.GetType(), flags)) { foreach (var field in this.GetFields(o.GetType(), flags)) {
if (!this.readFunctions.TryGetValue(field.FieldType, out var func)) if (!this.readFunctions.TryGetValue(field.FieldType, out var func))
@ -53,11 +77,23 @@ namespace MLEM.Data {
return fields; return fields;
} }
/// <summary>
/// Adds a manually created deserialization and serialization handler to this net buffer serializer.
/// </summary>
/// <param name="write">The function to write the given object into the net buffer</param>
/// <param name="read">The function to read the given object out of the net buffer</param>
/// <typeparam name="T">The type that will be serialized and deserialized</typeparam>
public void AddHandler<T>(Action<NetBuffer, T> write, Func<NetBuffer, T> read) { public void AddHandler<T>(Action<NetBuffer, T> write, Func<NetBuffer, T> read) {
this.writeFunctions.Add(typeof(T), (buffer, o) => write(buffer, (T) o)); this.writeFunctions.Add(typeof(T), (buffer, o) => write(buffer, (T) o));
this.readFunctions.Add(typeof(T), buffer => read(buffer)); this.readFunctions.Add(typeof(T), buffer => read(buffer));
} }
/// <summary>
/// Adds a JSON-based deserialization and serialization handler to this net buffer serializer.
/// Objects that are serialized in this way are converted to JSON, and the resulting JSON is compressed.
/// </summary>
/// <param name="serializer">The JSON serializer to use</param>
/// <typeparam name="T">The type that will be serialized and deserialized</typeparam>
public void AddHandler<T>(JsonSerializer serializer) { public void AddHandler<T>(JsonSerializer serializer) {
this.AddHandler((buffer, o) => buffer.WriteObject(o, serializer), buffer => buffer.ReadObject<T>(serializer)); this.AddHandler((buffer, o) => buffer.WriteObject(o, serializer), buffer => buffer.ReadObject<T>(serializer));
} }

View file

@ -9,33 +9,73 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Bson; using Newtonsoft.Json.Bson;
namespace MLEM.Data { namespace MLEM.Data {
/// <summary>
/// A set of extensions for dealing with <see cref="NetBuffer"/>.
/// </summary>
public static class NetExtensions { public static class NetExtensions {
/// <summary>
/// Writes a <see cref="Vector2"/> to the given net buffer
/// </summary>
/// <param name="buffer">The buffer to write to</param>
/// <param name="vector">The vector to write</param>
public static void Write(this NetBuffer buffer, Vector2 vector) { public static void Write(this NetBuffer buffer, Vector2 vector) {
buffer.Write(vector.X); buffer.Write(vector.X);
buffer.Write(vector.Y); buffer.Write(vector.Y);
} }
/// <summary>
/// Reads a <see cref="Vector2"/> from the given net buffer
/// </summary>
/// <param name="buffer">The buffer to read from</param>
/// <returns>The read vector</returns>
public static Vector2 ReadVector2(this NetBuffer buffer) { public static Vector2 ReadVector2(this NetBuffer buffer) {
return new Vector2(buffer.ReadFloat(), buffer.ReadFloat()); return new Vector2(buffer.ReadFloat(), buffer.ReadFloat());
} }
/// <summary>
/// Writes a <see cref="Guid"/> to the given net buffer
/// </summary>
/// <param name="buffer">The buffer to write to</param>
/// <param name="guid">The guid to write</param>
public static void Write(this NetBuffer buffer, Guid guid) { public static void Write(this NetBuffer buffer, Guid guid) {
buffer.Write(guid.ToByteArray()); buffer.Write(guid.ToByteArray());
} }
/// <summary>
/// Reads a <see cref="Guid"/> from the given net buffer
/// </summary>
/// <param name="buffer">The buffer to read from</param>
/// <returns>The read guid</returns>
public static Guid ReadGuid(this NetBuffer buffer) { public static Guid ReadGuid(this NetBuffer buffer) {
return new Guid(buffer.ReadBytes(16)); return new Guid(buffer.ReadBytes(16));
} }
/// <summary>
/// Writes a <see cref="Direction2"/> to the given net buffer
/// </summary>
/// <param name="buffer">The buffer to write to</param>
/// <param name="direction">The direction to write</param>
public static void Write(this NetBuffer buffer, Direction2 direction) { public static void Write(this NetBuffer buffer, Direction2 direction) {
buffer.Write((short) direction); buffer.Write((short) direction);
} }
/// <summary>
/// Reads a <see cref="Direction2"/> from the given net buffer
/// </summary>
/// <param name="buffer">The buffer to read from</param>
/// <returns>The read direction</returns>
public static Direction2 ReadDirection(this NetBuffer buffer) { public static Direction2 ReadDirection(this NetBuffer buffer) {
return (Direction2) buffer.ReadInt16(); return (Direction2) buffer.ReadInt16();
} }
/// <summary>
/// Writes a generic object to the given net buffer using a <see cref="JsonSerializer"/>.
/// </summary>
/// <param name="buffer">The buffer to write to</param>
/// <param name="obj">The object to write</param>
/// <param name="serializer">The JSON serializer to use</param>
/// <typeparam name="T">The type of object written</typeparam>
public static void WriteObject<T>(this NetBuffer buffer, T obj, JsonSerializer serializer) { public static void WriteObject<T>(this NetBuffer buffer, T obj, JsonSerializer serializer) {
if (EqualityComparer<T>.Default.Equals(obj, default)) { if (EqualityComparer<T>.Default.Equals(obj, default)) {
buffer.Write(0); buffer.Write(0);
@ -50,6 +90,13 @@ namespace MLEM.Data {
} }
} }
/// <summary>
/// Reads a generic object from the given buffer using a <see cref="JsonSerializer"/>.
/// </summary>
/// <param name="buffer">The buffer to read from</param>
/// <param name="serializer">The JSON serializer to use</param>
/// <typeparam name="T">The type of object read</typeparam>
/// <returns>The read object</returns>
public static T ReadObject<T>(this NetBuffer buffer, JsonSerializer serializer) { public static T ReadObject<T>(this NetBuffer buffer, JsonSerializer serializer) {
var length = buffer.ReadInt32(); var length = buffer.ReadInt32();
if (length <= 0) if (length <= 0)

View file

@ -1,12 +1,25 @@
using MonoGame.Extended; using MonoGame.Extended;
namespace MLEM.Extended.Extensions { namespace MLEM.Extended.Extensions {
/// <summary>
/// A set of extension methods that convert MLEM types to MonoGame.Extended types and vice versa.
/// </summary>
public static class NumberExtensions { public static class NumberExtensions {
/// <summary>
/// Converts a MLEM <see cref="Misc.RectangleF"/> to a MonoGame.Extended <see cref="RectangleF"/>.
/// </summary>
/// <param name="rect">The rectangle to convert</param>
/// <returns>The converted rectangle</returns>
public static RectangleF ToExtended(this Misc.RectangleF rect) { public static RectangleF ToExtended(this Misc.RectangleF rect) {
return new RectangleF(rect.X, rect.Y, rect.Width, rect.Height); return new RectangleF(rect.X, rect.Y, rect.Width, rect.Height);
} }
/// <summary>
/// Converts a MonoGame.Extended <see cref="RectangleF"/> to a MLEM <see cref="Misc.RectangleF"/>.
/// </summary>
/// <param name="rect">The rectangle to convert</param>
/// <returns>The converted rectangle</returns>
public static Misc.RectangleF ToMlem(this RectangleF rect) { public static Misc.RectangleF ToMlem(this RectangleF rect) {
return new Misc.RectangleF(rect.X, rect.Y, rect.Width, rect.Height); return new Misc.RectangleF(rect.X, rect.Y, rect.Width, rect.Height);
} }

View file

@ -3,16 +3,38 @@ using Microsoft.Xna.Framework;
using MonoGame.Extended; using MonoGame.Extended;
namespace MLEM.Extended.Extensions { namespace MLEM.Extended.Extensions {
/// <summary>
/// A set of extension methods for dealing with <see cref="Random"/>
/// </summary>
public static class RandomExtensions { public static class RandomExtensions {
/// <summary>
/// Returns a random number in the given range.
/// </summary>
/// <param name="random">The random to use for generation</param>
/// <param name="range">The range in which numbers will be generated</param>
/// <returns>A number in the given range</returns>
public static int Range(this Random random, Range<int> range) { public static int Range(this Random random, Range<int> range) {
return random.Next(range.Min, range.Max); return random.Next(range.Min, range.Max);
} }
/// <summary>
/// Returns a random number in the given range.
/// </summary>
/// <param name="random">The random to use for generation</param>
/// <param name="range">The range in which numbers will be generated</param>
/// <returns>A number in the given range</returns>
public static float Range(this Random random, Range<float> range) { public static float Range(this Random random, Range<float> range) {
return random.NextSingle(range.Min, range.Max); return random.NextSingle(range.Min, range.Max);
} }
/// <summary>
/// Returns a random vector whose x and y values are in the given range.
/// </summary>
/// <param name="random">The random to use for generation</param>
/// <param name="min">The minimum value for each coordinate</param>
/// <param name="max">The maximum value for each coordinate</param>
/// <returns>A random vector in the given range</returns>
public static Vector2 NextVector2(this Random random, float min, float max) { public static Vector2 NextVector2(this Random random, float min, float max) {
return new Vector2(random.NextSingle(min, max), random.NextSingle(min, max)); return new Vector2(random.NextSingle(min, max), random.NextSingle(min, max));
} }

View file

@ -6,16 +6,22 @@ using MonoGame.Extended;
using MonoGame.Extended.BitmapFonts; using MonoGame.Extended.BitmapFonts;
namespace MLEM.Extended.Extensions { namespace MLEM.Extended.Extensions {
/// <summary>
/// A set of extension methods for dealing with <see cref="SpriteBatch"/> and <see cref="RectangleF"/> in combination.
/// </summary>
public static class SpriteBatchExtensions { public static class SpriteBatchExtensions {
/// <inheritdoc cref="SpriteBatch.Draw(Texture2D,Rectangle,Rectangle?,Color,float,Vector2,SpriteEffects,float)"/>
public static void Draw(this SpriteBatch batch, Texture2D texture, RectangleF destinationRectangle, Rectangle? sourceRectangle, Color color, float rotation, Vector2 origin, SpriteEffects effects, float layerDepth) { public static void Draw(this SpriteBatch batch, Texture2D texture, RectangleF destinationRectangle, Rectangle? sourceRectangle, Color color, float rotation, Vector2 origin, SpriteEffects effects, float layerDepth) {
batch.Draw(texture, destinationRectangle.ToMlem(), sourceRectangle, color, rotation, origin, effects, layerDepth); batch.Draw(texture, destinationRectangle.ToMlem(), sourceRectangle, color, rotation, origin, effects, layerDepth);
} }
/// <inheritdoc cref="SpriteBatch.Draw(Texture2D,Rectangle,Rectangle?,Color)"/>
public static void Draw(this SpriteBatch batch, Texture2D texture, RectangleF destinationRectangle, Rectangle? sourceRectangle, Color color) { public static void Draw(this SpriteBatch batch, Texture2D texture, RectangleF destinationRectangle, Rectangle? sourceRectangle, Color color) {
batch.Draw(texture, destinationRectangle, sourceRectangle, color, 0, Vector2.Zero, SpriteEffects.None, 0); batch.Draw(texture, destinationRectangle, sourceRectangle, color, 0, Vector2.Zero, SpriteEffects.None, 0);
} }
/// <inheritdoc cref="SpriteBatch.Draw(Texture2D,Rectangle,Color)"/>
public static void Draw(this SpriteBatch batch, Texture2D texture, RectangleF destinationRectangle, Color color) { public static void Draw(this SpriteBatch batch, Texture2D texture, RectangleF destinationRectangle, Color color) {
batch.Draw(texture, destinationRectangle, null, color); batch.Draw(texture, destinationRectangle, null, color);
} }

View file

@ -3,20 +3,43 @@ using MLEM.Textures;
using MonoGame.Extended.TextureAtlases; using MonoGame.Extended.TextureAtlases;
namespace MLEM.Extended.Extensions { namespace MLEM.Extended.Extensions {
/// <summary>
/// A set of extensions for converting texture-related types between MLEM and MonoGame.Extended.
/// </summary>
public static class TextureExtensions { public static class TextureExtensions {
/// <summary>
/// Converts a MLEM <see cref="NinePatch"/> to a MonoGame.Extended <see cref="NinePatchRegion2D"/>.
/// </summary>
/// <param name="patch">The nine patch to convert</param>
/// <returns>The converted nine patch</returns>
public static NinePatchRegion2D ToExtended(this NinePatch patch) { public static NinePatchRegion2D ToExtended(this NinePatch patch) {
return new NinePatchRegion2D(patch.Region.ToExtended(), patch.Padding.Left.Floor(), patch.Padding.Top.Floor(), patch.Padding.Right.Floor(), patch.Padding.Bottom.Floor()); return new NinePatchRegion2D(patch.Region.ToExtended(), patch.Padding.Left.Floor(), patch.Padding.Top.Floor(), patch.Padding.Right.Floor(), patch.Padding.Bottom.Floor());
} }
/// <summary>
/// Converts a MLEM <see cref="TextureRegion"/> to a MonoGame.Extended <see cref="TextureRegion2D"/>.
/// </summary>
/// <param name="region">The nine patch to convert</param>
/// <returns>The converted nine patch</returns>
public static TextureRegion2D ToExtended(this TextureRegion region) { public static TextureRegion2D ToExtended(this TextureRegion region) {
return new TextureRegion2D(region.Texture, region.U, region.V, region.Width, region.Height); return new TextureRegion2D(region.Texture, region.U, region.V, region.Width, region.Height);
} }
/// <summary>
/// Converts a MonoGame.Extended <see cref="NinePatchRegion2D"/> to a MLEM <see cref="NinePatch"/>.
/// </summary>
/// <param name="patch">The nine patch to convert</param>
/// <returns>The converted nine patch</returns>
public static NinePatch ToMlem(this NinePatchRegion2D patch) { public static NinePatch ToMlem(this NinePatchRegion2D patch) {
return new NinePatch(new TextureRegion(patch.Texture, patch.Bounds), patch.LeftPadding, patch.RightPadding, patch.TopPadding, patch.BottomPadding); return new NinePatch(new TextureRegion(patch.Texture, patch.Bounds), patch.LeftPadding, patch.RightPadding, patch.TopPadding, patch.BottomPadding);
} }
/// <summary>
/// Converts a MonoGame.Extended <see cref="TextureRegion2D"/> to a MLEM <see cref="TextureRegion"/>.
/// </summary>
/// <param name="region">The nine patch to convert</param>
/// <returns>The converted nine patch</returns>
public static TextureRegion ToMlem(this TextureRegion2D region) { public static TextureRegion ToMlem(this TextureRegion2D region) {
return new TextureRegion(region.Texture, region.Bounds); return new TextureRegion(region.Texture, region.Bounds);
} }

View file

@ -7,51 +7,73 @@ using MLEM.Font;
using MonoGame.Extended.BitmapFonts; using MonoGame.Extended.BitmapFonts;
namespace MLEM.Extended.Font { namespace MLEM.Extended.Font {
/// <inheritdoc/>
public class GenericBitmapFont : GenericFont { public class GenericBitmapFont : GenericFont {
/// <summary>
/// The <see cref="BitmapFont"/> that is being wrapped by this generic font
/// </summary>
public readonly BitmapFont Font; public readonly BitmapFont Font;
/// <inheritdoc/>
public override GenericFont Bold { get; } public override GenericFont Bold { get; }
/// <inheritdoc/>
public override GenericFont Italic { get; } public override GenericFont Italic { get; }
/// <inheritdoc/>
public override float LineHeight => this.Font.LineHeight; public override float LineHeight => this.Font.LineHeight;
/// <summary>
/// Creates a new generic font using <see cref="BitmapFont"/>.
/// Optionally, a bold and italic version of the font can be supplied.
/// </summary>
/// <param name="font">The font to wrap</param>
/// <param name="bold">A bold version of the font</param>
/// <param name="italic">An italic version of the font</param>
public GenericBitmapFont(BitmapFont font, BitmapFont bold = null, BitmapFont italic = null) { public GenericBitmapFont(BitmapFont font, BitmapFont bold = null, BitmapFont italic = null) {
this.Font = font; this.Font = font;
this.Bold = bold != null ? new GenericBitmapFont(bold) : this; this.Bold = bold != null ? new GenericBitmapFont(bold) : this;
this.Italic = italic != null ? new GenericBitmapFont(italic) : this; this.Italic = italic != null ? new GenericBitmapFont(italic) : this;
} }
/// <inheritdoc/>
public override Vector2 MeasureString(string text) { public override Vector2 MeasureString(string text) {
if (text.Length == 1 && this.SingleCharacterWidthFix(text, out var size)) if (text.Length == 1 && this.SingleCharacterWidthFix(text, out var size))
return size; return size;
return this.Font.MeasureString(text); return this.Font.MeasureString(text);
} }
/// <inheritdoc/>
public override Vector2 MeasureString(StringBuilder text) { public override Vector2 MeasureString(StringBuilder text) {
if (text.Length == 1 && this.SingleCharacterWidthFix(text.ToString(), out var size)) if (text.Length == 1 && this.SingleCharacterWidthFix(text.ToString(), out var size))
return size; return size;
return this.Font.MeasureString(text); return this.Font.MeasureString(text);
} }
/// <inheritdoc/>
public override void DrawString(SpriteBatch batch, string text, Vector2 position, Color color) { public override void DrawString(SpriteBatch batch, string text, Vector2 position, Color color) {
batch.DrawString(this.Font, text, position, color); batch.DrawString(this.Font, text, position, color);
} }
/// <inheritdoc/>
public override void DrawString(SpriteBatch batch, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects effects, float layerDepth) { public override void DrawString(SpriteBatch batch, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects effects, float layerDepth) {
batch.DrawString(this.Font, text, position, color, rotation, origin, scale, effects, layerDepth); batch.DrawString(this.Font, text, position, color, rotation, origin, scale, effects, layerDepth);
} }
/// <inheritdoc/>
public override void DrawString(SpriteBatch batch, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth) { public override void DrawString(SpriteBatch batch, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth) {
batch.DrawString(this.Font, text, position, color, rotation, origin, scale, effects, layerDepth); batch.DrawString(this.Font, text, position, color, rotation, origin, scale, effects, layerDepth);
} }
/// <inheritdoc/>
public override void DrawString(SpriteBatch batch, StringBuilder text, Vector2 position, Color color) { public override void DrawString(SpriteBatch batch, StringBuilder text, Vector2 position, Color color) {
batch.DrawString(this.Font, text, position, color); batch.DrawString(this.Font, text, position, color);
} }
/// <inheritdoc/>
public override void DrawString(SpriteBatch batch, StringBuilder text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects effects, float layerDepth) { public override void DrawString(SpriteBatch batch, StringBuilder text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects effects, float layerDepth) {
batch.DrawString(this.Font, text, position, color, rotation, origin, scale, effects, layerDepth); batch.DrawString(this.Font, text, position, color, rotation, origin, scale, effects, layerDepth);
} }
/// <inheritdoc/>
public override void DrawString(SpriteBatch batch, StringBuilder text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth) { public override void DrawString(SpriteBatch batch, StringBuilder text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth) {
batch.DrawString(this.Font, text, position, color, rotation, origin, scale, effects, layerDepth); batch.DrawString(this.Font, text, position, color, rotation, origin, scale, effects, layerDepth);
} }

View file

@ -2,12 +2,17 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using MLEM.Cameras;
using MLEM.Extensions; using MLEM.Extensions;
using MLEM.Misc; using MLEM.Misc;
using MonoGame.Extended.Tiled; using MonoGame.Extended.Tiled;
using RectangleF = MonoGame.Extended.RectangleF; using RectangleF = MonoGame.Extended.RectangleF;
namespace MLEM.Extended.Tiled { namespace MLEM.Extended.Tiled {
/// <summary>
/// A tiled map renderer that renders each tile individually, while optionally supplying a depth for each tile.
/// Rendering in this manner allows for entities to be behind or in front of buildings based on their y height.
/// </summary>
public class IndividualTiledMapRenderer { public class IndividualTiledMapRenderer {
private TiledMap map; private TiledMap map;
@ -15,11 +20,21 @@ namespace MLEM.Extended.Tiled {
private GetDepth depthFunction; private GetDepth depthFunction;
private List<TiledMapTilesetAnimatedTile> animatedTiles; private List<TiledMapTilesetAnimatedTile> animatedTiles;
/// <summary>
/// Creates a new individual tiled map renderer using the given map and depth function
/// </summary>
/// <param name="map">The map to use</param>
/// <param name="depthFunction">The depth function to use</param>
public IndividualTiledMapRenderer(TiledMap map = null, GetDepth depthFunction = null) { public IndividualTiledMapRenderer(TiledMap map = null, GetDepth depthFunction = null) {
if (map != null) if (map != null)
this.SetMap(map, depthFunction); this.SetMap(map, depthFunction);
} }
/// <summary>
/// Sets this individual tiled map renderer's map and depth function
/// </summary>
/// <param name="map">The map to use</param>
/// <param name="depthFunction">The depth function to use</param>
public void SetMap(TiledMap map, GetDepth depthFunction = null) { public void SetMap(TiledMap map, GetDepth depthFunction = null) {
this.map = map; this.map = map;
this.depthFunction = depthFunction ?? ((tile, layer, layerIndex, position) => 0); this.depthFunction = depthFunction ?? ((tile, layer, layerIndex, position) => 0);
@ -43,6 +58,12 @@ namespace MLEM.Extended.Tiled {
} }
} }
/// <summary>
/// Updates the rendering information for the tile in the given layer, x and y.
/// </summary>
/// <param name="layerIndex">The index of the layer in <see cref="TiledMap.TileLayers"/></param>
/// <param name="x">The x coordinate of the tile</param>
/// <param name="y">The y coordinate of the tile</param>
public void UpdateDrawInfo(int layerIndex, int x, int y) { public void UpdateDrawInfo(int layerIndex, int x, int y) {
var layer = this.map.TileLayers[layerIndex]; var layer = this.map.TileLayers[layerIndex];
var tile = layer.GetTile(x, y); var tile = layer.GetTile(x, y);
@ -57,6 +78,13 @@ namespace MLEM.Extended.Tiled {
this.drawInfos[layerIndex, x, y] = new TileDrawInfo(this, tile, tileset, tilesetTile, pos, depth); this.drawInfos[layerIndex, x, y] = new TileDrawInfo(this, tile, tileset, tilesetTile, pos, depth);
} }
/// <summary>
/// Draws this individual tiled map renderer.
/// Optionally, a frustum can be supplied that determines which positions, in pixel space, are visible at this time. <see cref="Camera"/> provides <see cref="Camera.GetVisibleRectangle"/> for this purpose.
/// </summary>
/// <param name="batch">The sprite batch to use</param>
/// <param name="frustum">The area that is visible, in pixel space.</param>
/// <param name="drawFunction">The draw function to use, or null for default</param>
public void Draw(SpriteBatch batch, RectangleF? frustum = null, DrawDelegate drawFunction = null) { public void Draw(SpriteBatch batch, RectangleF? frustum = null, DrawDelegate drawFunction = null) {
for (var i = 0; i < this.map.TileLayers.Count; i++) { for (var i = 0; i < this.map.TileLayers.Count; i++) {
if (this.map.TileLayers[i].IsVisible) if (this.map.TileLayers[i].IsVisible)
@ -64,6 +92,14 @@ namespace MLEM.Extended.Tiled {
} }
} }
/// <summary>
/// Draws the given layer of this individual tiled map renderer.
/// Optionally, a frustum can be supplied that determines which positions, in pixel space, are visible at this time. <see cref="Camera"/> provides <see cref="Camera.GetVisibleRectangle"/> for this purpose.
/// </summary>
/// <param name="batch">The sprite batch to use</param>
/// <param name="layerIndex">The index of the layer in <see cref="TiledMap.TileLayers"/></param>
/// <param name="frustum">The area that is visible, in pixel space.</param>
/// <param name="drawFunction">The draw function to use, or null for default</param>
public void DrawLayer(SpriteBatch batch, int layerIndex, RectangleF? frustum = null, DrawDelegate drawFunction = null) { public void DrawLayer(SpriteBatch batch, int layerIndex, RectangleF? frustum = null, DrawDelegate drawFunction = null) {
var frust = frustum ?? new RectangleF(0, 0, float.MaxValue, float.MaxValue); var frust = frustum ?? new RectangleF(0, 0, float.MaxValue, float.MaxValue);
var minX = Math.Max(0, frust.Left / this.map.TileWidth).Floor(); var minX = Math.Max(0, frust.Left / this.map.TileWidth).Floor();
@ -79,25 +115,65 @@ namespace MLEM.Extended.Tiled {
} }
} }
/// <summary>
/// Update all of the animated tiles in this individual tiled map renderer
/// </summary>
/// <param name="time">The game's time</param>
public void UpdateAnimations(GameTime time) { public void UpdateAnimations(GameTime time) {
foreach (var animation in this.animatedTiles) foreach (var animation in this.animatedTiles)
animation.Update(time); animation.Update(time);
} }
/// <summary>
/// A delegate method used for <see cref="IndividualTiledMapRenderer.depthFunction"/>.
/// The idea is to return a depth (between 0 and 1) for the given tile that determines where in the sprite batch it should be rendererd.
/// Note that, for this depth function to take effect, the sprite batch needs to begin with <see cref="SpriteSortMode.FrontToBack"/> or <see cref="SpriteSortMode.BackToFront"/>.
/// </summary>
/// <param name="tile">The tile whose depth to get</param>
/// <param name="layer">The layer the tile is on</param>
/// <param name="layerIndex">The index of the layer in <see cref="TiledMap.TileLayers"/></param>
/// <param name="position">The tile position of this tile</param>
public delegate float GetDepth(TiledMapTile tile, TiledMapTileLayer layer, int layerIndex, Point position); public delegate float GetDepth(TiledMapTile tile, TiledMapTileLayer layer, int layerIndex, Point position);
/// <summary>
/// A delegate method used for drawing an <see cref="IndividualTiledMapRenderer"/>.
/// </summary>
/// <param name="batch">The sprite batch to use for drawing</param>
/// <param name="info">The <see cref="TileDrawInfo"/> to draw</param>
public delegate void DrawDelegate(SpriteBatch batch, TileDrawInfo info); public delegate void DrawDelegate(SpriteBatch batch, TileDrawInfo info);
/// <summary>
/// A tile draw info contains information about a tile at a given map location.
/// It caches a lot of data that is required for drawing a tile efficiently.
/// </summary>
public class TileDrawInfo : GenericDataHolder { public class TileDrawInfo : GenericDataHolder {
/// <summary>
/// The renderer used by this info
/// </summary>
public readonly IndividualTiledMapRenderer Renderer; public readonly IndividualTiledMapRenderer Renderer;
/// <summary>
/// The tiled map tile to draw
/// </summary>
public readonly TiledMapTile Tile; public readonly TiledMapTile Tile;
/// <summary>
/// The tileset that <see cref="Tile"/> is on
/// </summary>
public readonly TiledMapTileset Tileset; public readonly TiledMapTileset Tileset;
/// <summary>
/// The tileset tile that corresponds to <see cref="Tile"/>
/// </summary>
public readonly TiledMapTilesetTile TilesetTile; public readonly TiledMapTilesetTile TilesetTile;
/// <summary>
/// The position, in tile space, of <see cref="Tile"/>
/// </summary>
public readonly Point Position; public readonly Point Position;
/// <summary>
/// The depth calculated by the depth function
/// </summary>
public readonly float Depth; public readonly float Depth;
public TileDrawInfo(IndividualTiledMapRenderer renderer, TiledMapTile tile, TiledMapTileset tileset, TiledMapTilesetTile tilesetTile, Point position, float depth) { internal TileDrawInfo(IndividualTiledMapRenderer renderer, TiledMapTile tile, TiledMapTileset tileset, TiledMapTilesetTile tilesetTile, Point position, float depth) {
this.Renderer = renderer; this.Renderer = renderer;
this.Tile = tile; this.Tile = tile;
this.Tileset = tileset; this.Tileset = tileset;
@ -106,6 +182,11 @@ namespace MLEM.Extended.Tiled {
this.Depth = depth; this.Depth = depth;
} }
/// <summary>
/// Draws this tile draw info with the default settings.
/// </summary>
/// <param name="batch">The sprite batch to use for drawing</param>
/// <param name="drawFunction">The draw function used to draw, or null if there is no override</param>
public void Draw(SpriteBatch batch, DrawDelegate drawFunction) { public void Draw(SpriteBatch batch, DrawDelegate drawFunction) {
if (drawFunction == null) { if (drawFunction == null) {
var region = this.Tileset.GetTextureRegion(this.TilesetTile); var region = this.Tileset.GetTextureRegion(this.TilesetTile);

View file

@ -1,38 +1,62 @@
using System.Runtime.Serialization; using System.Runtime.Serialization;
using MonoGame.Extended.Tiled;
namespace MLEM.Extended.Tiled { namespace MLEM.Extended.Tiled {
/// <summary>
/// A struct that represents a position on a <see cref="TiledMap"/> with multiple layers.
/// </summary>
[DataContract] [DataContract]
public struct LayerPosition { public struct LayerPosition {
/// <summary>
/// The name of the layer that this position is on
/// </summary>
[DataMember] [DataMember]
public string Layer; public string Layer;
/// <summary>
/// The x coordinate of this position
/// </summary>
[DataMember] [DataMember]
public int X; public int X;
/// <summary>
/// The y coordinate of this position
/// </summary>
[DataMember] [DataMember]
public int Y; public int Y;
/// <summary>
/// Creates a new layer position with the given settings
/// </summary>
/// <param name="layerName">The layer name</param>
/// <param name="x">The x coordinate</param>
/// <param name="y">The y coordinate</param>
public LayerPosition(string layerName, int x, int y) { public LayerPosition(string layerName, int x, int y) {
this.Layer = layerName; this.Layer = layerName;
this.X = x; this.X = x;
this.Y = y; this.Y = y;
} }
/// <inheritdoc cref="Equals(LayerPosition)"/>
public static bool operator ==(LayerPosition left, LayerPosition right) { public static bool operator ==(LayerPosition left, LayerPosition right) {
return left.Equals(right); return left.Equals(right);
} }
/// <inheritdoc cref="Equals(LayerPosition)"/>
public static bool operator !=(LayerPosition left, LayerPosition right) { public static bool operator !=(LayerPosition left, LayerPosition right) {
return !left.Equals(right); return !left.Equals(right);
} }
/// <inheritdoc cref="Equals(object)"/>
public bool Equals(LayerPosition other) { public bool Equals(LayerPosition other) {
return this.Layer == other.Layer && this.X == other.X && this.Y == other.Y; return this.Layer == other.Layer && this.X == other.X && this.Y == other.Y;
} }
/// <inheritdoc />
public override bool Equals(object obj) { public override bool Equals(object obj) {
return obj is LayerPosition other && this.Equals(other); return obj is LayerPosition other && this.Equals(other);
} }
/// <inheritdoc />
public override int GetHashCode() { public override int GetHashCode() {
var hashCode = this.Layer.GetHashCode(); var hashCode = this.Layer.GetHashCode();
hashCode = (hashCode * 397) ^ this.X; hashCode = (hashCode * 397) ^ this.X;
@ -40,6 +64,7 @@ namespace MLEM.Extended.Tiled {
return hashCode; return hashCode;
} }
/// <inheritdoc />
public override string ToString() { public override string ToString() {
return $"{nameof(this.Layer)}: {this.Layer}, {nameof(this.X)}: {this.X}, {nameof(this.Y)}: {this.Y}"; return $"{nameof(this.Layer)}: {this.Layer}, {nameof(this.X)}: {this.X}, {nameof(this.Y)}: {this.Y}";
} }

View file

@ -9,46 +9,109 @@ using MonoGame.Extended.Tiled;
using ColorExtensions = MLEM.Extensions.ColorExtensions; using ColorExtensions = MLEM.Extensions.ColorExtensions;
namespace MLEM.Extended.Tiled { namespace MLEM.Extended.Tiled {
/// <summary>
/// A set of extensions for dealing with MonoGame.Extended tiled maps
/// </summary>
public static class TiledExtensions { public static class TiledExtensions {
private static readonly Dictionary<int, TiledMapTilesetTile> StubTilesetTiles = new Dictionary<int, TiledMapTilesetTile>(); private static readonly Dictionary<int, TiledMapTilesetTile> StubTilesetTiles = new Dictionary<int, TiledMapTilesetTile>();
/// <summary>
/// Gets the property with the given key, or null if there is none.
/// </summary>
/// <param name="properties">The set of properties</param>
/// <param name="key">The key by which to get a property</param>
/// <returns>The property, or null if there is none</returns>
public static string Get(this TiledMapProperties properties, string key) { public static string Get(this TiledMapProperties properties, string key) {
properties.TryGetValue(key, out var val); properties.TryGetValue(key, out var val);
return val; return val;
} }
/// <summary>
/// Gets a boolean property with the given key, or null if there is none.
/// </summary>
/// <param name="properties">The set of properties</param>
/// <param name="key">The key by which to get a property</param>
/// <returns>The boolean property, or false if there is none</returns>
public static bool GetBool(this TiledMapProperties properties, string key) { public static bool GetBool(this TiledMapProperties properties, string key) {
bool.TryParse(properties.Get(key), out var val); bool.TryParse(properties.Get(key), out var val);
return val; return val;
} }
/// <summary>
/// Gets a Color property with the given key, or null if there is none.
/// </summary>
/// <param name="properties">The set of properties</param>
/// <param name="key">The key by which to get a property</param>
/// <returns>The color property</returns>
public static Color GetColor(this TiledMapProperties properties, string key) { public static Color GetColor(this TiledMapProperties properties, string key) {
return ColorExtensions.FromHex(properties.Get(key)); return ColorExtensions.FromHex(properties.Get(key));
} }
/// <summary>
/// Gets a float property with the given key, or null if there is none.
/// </summary>
/// <param name="properties">The set of properties</param>
/// <param name="key">The key by which to get a property</param>
/// <returns>The float property, or 0 if there is none</returns>
public static float GetFloat(this TiledMapProperties properties, string key) { public static float GetFloat(this TiledMapProperties properties, string key) {
float.TryParse(properties.Get(key), NumberStyles.Number, NumberFormatInfo.InvariantInfo, out var val); float.TryParse(properties.Get(key), NumberStyles.Number, NumberFormatInfo.InvariantInfo, out var val);
return val; return val;
} }
/// <summary>
/// Gets an int property with the given key, or null if there is none.
/// </summary>
/// <param name="properties">The set of properties</param>
/// <param name="key">The key by which to get a property</param>
/// <returns>The int property, or 0 if there is none</returns>
public static int GetInt(this TiledMapProperties properties, string key) { public static int GetInt(this TiledMapProperties properties, string key) {
int.TryParse(properties.Get(key), NumberStyles.Number, NumberFormatInfo.InvariantInfo, out var val); int.TryParse(properties.Get(key), NumberStyles.Number, NumberFormatInfo.InvariantInfo, out var val);
return val; return val;
} }
/// <summary>
/// Gets the tileset for the given map tile on the given map.
/// </summary>
/// <param name="tile">The tile</param>
/// <param name="map">The map the tile is on</param>
/// <returns>The tileset that the tile came from</returns>
public static TiledMapTileset GetTileset(this TiledMapTile tile, TiledMap map) { public static TiledMapTileset GetTileset(this TiledMapTile tile, TiledMap map) {
return map.GetTilesetByTileGlobalIdentifier(tile.GlobalIdentifier); return map.GetTilesetByTileGlobalIdentifier(tile.GlobalIdentifier);
} }
/// <summary>
/// Gets the local tile identifier for the given tiled map tile.
/// The local tile identifier is the identifier within the tile's tileset.
/// </summary>
/// <param name="tile">The tile whose identifier to get</param>
/// <param name="tileset">The tileset the tile is from</param>
/// <param name="map">The map the tile is on</param>
/// <returns>The local identifier</returns>
public static int GetLocalIdentifier(this TiledMapTile tile, TiledMapTileset tileset, TiledMap map) { public static int GetLocalIdentifier(this TiledMapTile tile, TiledMapTileset tileset, TiledMap map) {
return tile.GlobalIdentifier - map.GetTilesetFirstGlobalIdentifier(tileset); return tile.GlobalIdentifier - map.GetTilesetFirstGlobalIdentifier(tileset);
} }
/// <summary>
/// Gets the global tile identifier for the given tiled map tileset tile.
/// The global tile identifier is the identifier within all of the tile sets that the map has.
/// </summary>
/// <param name="tile">The tile whose global identifier to get</param>
/// <param name="tileset">The tileset the tile is from</param>
/// <param name="map">The map the tile is on</param>
/// <returns>The global identifier</returns>
public static int GetGlobalIdentifier(this TiledMapTilesetTile tile, TiledMapTileset tileset, TiledMap map) { public static int GetGlobalIdentifier(this TiledMapTilesetTile tile, TiledMapTileset tileset, TiledMap map) {
return map.GetTilesetFirstGlobalIdentifier(tileset) + tile.LocalTileIdentifier; return map.GetTilesetFirstGlobalIdentifier(tileset) + tile.LocalTileIdentifier;
} }
/// <summary>
/// Gets the tileset tile on the given tileset for the given tile.
/// </summary>
/// <param name="tileset">The tileset</param>
/// <param name="tile">The tile</param>
/// <param name="map">The map the tile is on</param>
/// <param name="createStub">If a tileset tile has no special properties, there is no pre-made object for it. If this boolean is true, a stub object with no extra data will be created instead of returning null.</param>
/// <returns>null if the tile is blank or the tileset tile if there is one or createStub is true</returns>
public static TiledMapTilesetTile GetTilesetTile(this TiledMapTileset tileset, TiledMapTile tile, TiledMap map, bool createStub = true) { public static TiledMapTilesetTile GetTilesetTile(this TiledMapTileset tileset, TiledMapTile tile, TiledMap map, bool createStub = true) {
if (tile.IsBlank) if (tile.IsBlank)
return null; return null;
@ -64,6 +127,14 @@ namespace MLEM.Extended.Tiled {
return tilesetTile; return tilesetTile;
} }
/// <summary>
/// Gets the tileset tile on the given tileset for the given tile.
/// If the tileset is already known, you should use <see cref="GetTilesetTile(MonoGame.Extended.Tiled.TiledMapTileset,MonoGame.Extended.Tiled.TiledMapTile,MonoGame.Extended.Tiled.TiledMap,bool)"/> instead for performance.
/// </summary>
/// <param name="tile">The tile</param>
/// <param name="map">The map the tile is on</param>
/// <param name="createStub">If a tileset tile has no special properties, there is no pre-made object for it. If this boolean is true, a stub object with no extra data will be created instead of returning null.</param>
/// <returns>null if the tile is blank or the tileset tile if there is one or createStub is true</returns>
public static TiledMapTilesetTile GetTilesetTile(this TiledMapTile tile, TiledMap map, bool createStub = true) { public static TiledMapTilesetTile GetTilesetTile(this TiledMapTile tile, TiledMap map, bool createStub = true) {
if (tile.IsBlank) if (tile.IsBlank)
return null; return null;
@ -71,22 +142,51 @@ namespace MLEM.Extended.Tiled {
return tileset.GetTilesetTile(tile, map, createStub); return tileset.GetTilesetTile(tile, map, createStub);
} }
/// <summary>
/// Gets the layer index of the layer with the given name in the <see cref="TiledMap.Layers"/> array.
/// </summary>
/// <param name="map">The map</param>
/// <param name="layerName">The name of the layer</param>
/// <returns>The resulting index</returns>
public static int GetTileLayerIndex(this TiledMap map, string layerName) { public static int GetTileLayerIndex(this TiledMap map, string layerName) {
var layer = map.GetLayer<TiledMapTileLayer>(layerName); var layer = map.GetLayer<TiledMapTileLayer>(layerName);
return map.TileLayers.IndexOf(layer); return map.TileLayers.IndexOf(layer);
} }
/// <summary>
/// Returns the tiled map tile at the given location on the layer with the given name.
/// </summary>
/// <param name="map">The map</param>
/// <param name="layerName">The name of the layer the tile is on</param>
/// <param name="x">The x coordinate of the tile</param>
/// <param name="y">The y coordinate of the tile</param>
/// <returns>The tile at the given location, or default if the layer does not exist</returns>
public static TiledMapTile GetTile(this TiledMap map, string layerName, int x, int y) { public static TiledMapTile GetTile(this TiledMap map, string layerName, int x, int y) {
var layer = map.GetLayer<TiledMapTileLayer>(layerName); var layer = map.GetLayer<TiledMapTileLayer>(layerName);
return layer != null ? layer.GetTile(x, y) : default; return layer != null ? layer.GetTile(x, y) : default;
} }
/// <summary>
/// Sets the tiled map tile at the given location to the given global tile identifier.
/// </summary>
/// <param name="map">The map</param>
/// <param name="layerName">The name of the layer</param>
/// <param name="x">The x coordinate</param>
/// <param name="y">The y coordinate</param>
/// <param name="globalTile">The tile's global identifier to set</param>
public static void SetTile(this TiledMap map, string layerName, int x, int y, int globalTile) { public static void SetTile(this TiledMap map, string layerName, int x, int y, int globalTile) {
var layer = map.GetLayer<TiledMapTileLayer>(layerName); var layer = map.GetLayer<TiledMapTileLayer>(layerName);
if (layer != null) if (layer != null)
layer.SetTile((ushort) x, (ushort) y, (uint) globalTile); layer.SetTile((ushort) x, (ushort) y, (uint) globalTile);
} }
/// <summary>
/// For an x and y coordinate, returns an enumerable of all of the tiles on each of the map's <see cref="TiledMap.TileLayers"/>.
/// </summary>
/// <param name="map">The map</param>
/// <param name="x">The x coordinate</param>
/// <param name="y">The y coordinate</param>
/// <returns>All of the tiles on the map at the given location</returns>
public static IEnumerable<TiledMapTile> GetTiles(this TiledMap map, int x, int y) { public static IEnumerable<TiledMapTile> GetTiles(this TiledMap map, int x, int y) {
foreach (var layer in map.TileLayers) { foreach (var layer in map.TileLayers) {
var tile = layer.GetTile(x, y); var tile = layer.GetTile(x, y);
@ -95,24 +195,59 @@ namespace MLEM.Extended.Tiled {
} }
} }
/// <summary>
/// Returns the tiled map at the given location on the given layer
/// </summary>
/// <param name="layer">The layer to get the tile from</param>
/// <param name="x">The tile's x coordinate</param>
/// <param name="y">The tile's y coordinate</param>
/// <returns>The tiled map tile at the location, or default if the location is out of bounds</returns>
public static TiledMapTile GetTile(this TiledMapTileLayer layer, int x, int y) { public static TiledMapTile GetTile(this TiledMapTileLayer layer, int x, int y) {
return !layer.IsInBounds(x, y) ? default : layer.GetTile((ushort) x, (ushort) y); return !layer.IsInBounds(x, y) ? default : layer.GetTile((ushort) x, (ushort) y);
} }
/// <summary>
/// Returns the area that a tiled map object covers.
/// The area returned is in percent, meaning that an area that covers a full tile has a size of 1,1.
/// </summary>
/// <param name="obj">The object whose area to get</param>
/// <param name="map">The map</param>
/// <param name="position">The position to add to the object's position</param>
/// <returns>The area that the tile covers</returns>
public static RectangleF GetArea(this TiledMapObject obj, TiledMap map, Vector2? position = null) { public static RectangleF GetArea(this TiledMapObject obj, TiledMap map, Vector2? position = null) {
var tileSize = map.GetTileSize(); var tileSize = map.GetTileSize();
var pos = position ?? Vector2.Zero; var pos = position ?? Vector2.Zero;
return new RectangleF(obj.Position / tileSize + pos, obj.Size / tileSize); return new RectangleF(obj.Position / tileSize + pos, obj.Size / tileSize);
} }
/// <summary>
/// Returns the width and height of a tile on the given map, as a vector.
/// </summary>
/// <param name="map">The map</param>
/// <returns>The width and height of a tile</returns>
public static Vector2 GetTileSize(this TiledMap map) { public static Vector2 GetTileSize(this TiledMap map) {
return new Vector2(map.TileWidth, map.TileHeight); return new Vector2(map.TileWidth, map.TileHeight);
} }
/// <summary>
/// Returns whether the given position is in the bounds of the layer (that is, if each coordinate is >= 0 and if they are both smaller than the layer's width and height).
/// </summary>
/// <param name="layer">The layer</param>
/// <param name="x">The x coordinate</param>
/// <param name="y">The y coordinate</param>
/// <returns>Whether the position is in bounds of the layer</returns>
public static bool IsInBounds(this TiledMapTileLayer layer, int x, int y) { public static bool IsInBounds(this TiledMapTileLayer layer, int x, int y) {
return x >= 0 && y >= 0 && x < layer.Width && y < layer.Height; return x >= 0 && y >= 0 && x < layer.Width && y < layer.Height;
} }
/// <summary>
/// Returns all of the objects by the given name, or by the given type, in an object layer.
/// </summary>
/// <param name="layer">The layer whose objects to search</param>
/// <param name="id">The name or type name of the objects to find</param>
/// <param name="searchName">Whether to search object names</param>
/// <param name="searchType">Whether to search object types</param>
/// <returns>An enumerable of tiled map objects that match the search</returns>
public static IEnumerable<TiledMapObject> GetObjects(this TiledMapObjectLayer layer, string id, bool searchName = true, bool searchType = false) { public static IEnumerable<TiledMapObject> GetObjects(this TiledMapObjectLayer layer, string id, bool searchName = true, bool searchType = false) {
foreach (var obj in layer.Objects) { foreach (var obj in layer.Objects) {
if (searchName && obj.Name == id || searchType && obj.Type == id) if (searchName && obj.Name == id || searchType && obj.Type == id)
@ -120,13 +255,27 @@ namespace MLEM.Extended.Tiled {
} }
} }
public static IEnumerable<TiledMapObject> GetObjects(this TiledMap map, string name, bool searchName = true, bool searchType = false) { /// <summary>
/// Returns all of the objects by the given name, or by the given type, on the given map.
/// </summary>
/// <param name="map">The layer whose objects to search</param>
/// <param name="id">The name or type name of the objects to find</param>
/// <param name="searchName">Whether to search object names</param>
/// <param name="searchType">Whether to search object types</param>
/// <returns>An enumerable of tiled map objects that match the search</returns>
public static IEnumerable<TiledMapObject> GetObjects(this TiledMap map, string id, bool searchName = true, bool searchType = false) {
foreach (var layer in map.ObjectLayers) { foreach (var layer in map.ObjectLayers) {
foreach (var obj in layer.GetObjects(name, searchName, searchType)) foreach (var obj in layer.GetObjects(id, searchName, searchType))
yield return obj; yield return obj;
} }
} }
/// <summary>
/// Returns the texture region, as a rectangle, that the given tile uses for rendering.
/// </summary>
/// <param name="tileset">The tileset the tile is on</param>
/// <param name="tile">The tile</param>
/// <returns>The tile's texture region, in pixels.</returns>
public static Rectangle GetTextureRegion(this TiledMapTileset tileset, TiledMapTilesetTile tile) { public static Rectangle GetTextureRegion(this TiledMapTileset tileset, TiledMapTilesetTile tile) {
var id = tile.LocalTileIdentifier; var id = tile.LocalTileIdentifier;
if (tile is TiledMapTilesetAnimatedTile animated) if (tile is TiledMapTilesetAnimatedTile animated)
@ -134,6 +283,11 @@ namespace MLEM.Extended.Tiled {
return tileset.GetTileRegion(id); return tileset.GetTileRegion(id);
} }
/// <summary>
/// Converts a tile's flip settings into <see cref="SpriteEffects"/>.
/// </summary>
/// <param name="tile">The tile whose flip settings to convert</param>
/// <returns>The tile's flip settings as sprite effects</returns>
public static SpriteEffects GetSpriteEffects(this TiledMapTile tile) { public static SpriteEffects GetSpriteEffects(this TiledMapTile tile) {
var flipping = SpriteEffects.None; var flipping = SpriteEffects.None;
if (tile.IsFlippedHorizontally) if (tile.IsFlippedHorizontally)

View file

@ -9,17 +9,30 @@ using MonoGame.Extended.Tiled;
using RectangleF = MonoGame.Extended.RectangleF; using RectangleF = MonoGame.Extended.RectangleF;
namespace MLEM.Extended.Tiled { namespace MLEM.Extended.Tiled {
/// <summary>
/// A collision handler for a MonoGame.Extended tiled tile map.
/// The idea behind this collision handler is that, on the map's tileset, each tile is assigned a certain rectangular area. That area is converted into a collision map that is dealt with in tile units, where each tile's covered area is 1x1 units big.
/// </summary>
public class TiledMapCollisions { public class TiledMapCollisions {
private TiledMap map; private TiledMap map;
private TileCollisionInfo[,,] collisionInfos; private TileCollisionInfo[,,] collisionInfos;
private CollectCollisions collisionFunction; private CollectCollisions collisionFunction;
/// <summary>
/// Creates a new tiled map collision handler for the given map
/// </summary>
/// <param name="map">The map</param>
public TiledMapCollisions(TiledMap map = null) { public TiledMapCollisions(TiledMap map = null) {
if (map != null) if (map != null)
this.SetMap(map); this.SetMap(map);
} }
/// <summary>
/// Sets this collision handler's handled map
/// </summary>
/// <param name="map">The map</param>
/// <param name="collisionFunction">The function used to collect the collision info of a tile, or null for the default handling</param>
public void SetMap(TiledMap map, CollectCollisions collisionFunction = null) { public void SetMap(TiledMap map, CollectCollisions collisionFunction = null) {
this.map = map; this.map = map;
this.collisionFunction = collisionFunction ?? ((collisions, tile) => { this.collisionFunction = collisionFunction ?? ((collisions, tile) => {
@ -44,6 +57,12 @@ namespace MLEM.Extended.Tiled {
} }
} }
/// <summary>
/// Updates the collision info for the tile at the given position.
/// </summary>
/// <param name="layerIndex">The index of the tile's layer in <see cref="TiledMap.TileLayers"/></param>
/// <param name="x">The tile's x coordinate</param>
/// <param name="y">The tile's y coordinate</param>
public void UpdateCollisionInfo(int layerIndex, int x, int y) { public void UpdateCollisionInfo(int layerIndex, int x, int y) {
var layer = this.map.TileLayers[layerIndex]; var layer = this.map.TileLayers[layerIndex];
var tile = layer.GetTile(x, y); var tile = layer.GetTile(x, y);
@ -55,6 +74,13 @@ namespace MLEM.Extended.Tiled {
this.collisionInfos[layerIndex, x, y] = new TileCollisionInfo(this.map, new Vector2(x, y), tile, layer, tilesetTile, this.collisionFunction); this.collisionInfos[layerIndex, x, y] = new TileCollisionInfo(this.map, new Vector2(x, y), tile, layer, tilesetTile, this.collisionFunction);
} }
/// <summary>
/// Returns an enumerable of tile collision infos that intersect the given area.
/// Optionally, a predicate can be supplied that narrows the search.
/// </summary>
/// <param name="area">The area to check for collisions in</param>
/// <param name="included">A function that determines if a certain info should be included or not</param>
/// <returns>An enumerable of collision infos for that area</returns>
public IEnumerable<TileCollisionInfo> GetCollidingTiles(RectangleF area, Func<TileCollisionInfo, bool> included = null) { public IEnumerable<TileCollisionInfo> GetCollidingTiles(RectangleF area, Func<TileCollisionInfo, bool> included = null) {
var inclusionFunc = included ?? (tile => tile.Collisions.Any(c => c.Intersects(area))); var inclusionFunc = included ?? (tile => tile.Collisions.Any(c => c.Intersects(area)));
var minX = Math.Max(0, area.Left.Floor()); var minX = Math.Max(0, area.Left.Floor());
@ -74,22 +100,55 @@ namespace MLEM.Extended.Tiled {
} }
} }
/// <summary>
/// Returns whether there are any colliding tiles in the given area.
/// Optionally, a predicate can be supplied that narrows the search.
/// </summary>
/// <param name="area">The area to check for collisions in</param>
/// <param name="included">A function that determines if a certain info should be included or not</param>
/// <returns>True if there are any colliders in the area, false otherwise</returns>
public bool IsColliding(RectangleF area, Func<TileCollisionInfo, bool> included = null) { public bool IsColliding(RectangleF area, Func<TileCollisionInfo, bool> included = null) {
return this.GetCollidingTiles(area, included).Any(); return this.GetCollidingTiles(area, included).Any();
} }
/// <summary>
/// A delegate method used to override the default collision checking behavior.
/// </summary>
/// <param name="collisions">The list of collisions to add to</param>
/// <param name="tile">The tile's collision information</param>
public delegate void CollectCollisions(List<RectangleF> collisions, TileCollisionInfo tile); public delegate void CollectCollisions(List<RectangleF> collisions, TileCollisionInfo tile);
/// <summary>
/// A tile collision info stores information about a tile at a given location on a given layer, including its objects and their bounds.
/// </summary>
public class TileCollisionInfo : GenericDataHolder { public class TileCollisionInfo : GenericDataHolder {
/// <summary>
/// The map the tile is on
/// </summary>
public readonly TiledMap Map; public readonly TiledMap Map;
/// <summary>
/// The position of the tile, in tile units
/// </summary>
public readonly Vector2 Position; public readonly Vector2 Position;
/// <summary>
/// The tiled map tile
/// </summary>
public readonly TiledMapTile Tile; public readonly TiledMapTile Tile;
/// <summary>
/// The layer that this tile is on
/// </summary>
public readonly TiledMapTileLayer Layer; public readonly TiledMapTileLayer Layer;
/// <summary>
/// The tileset tile for this tile
/// </summary>
public readonly TiledMapTilesetTile TilesetTile; public readonly TiledMapTilesetTile TilesetTile;
/// <summary>
/// The list of colliders for this tile
/// </summary>
public readonly List<RectangleF> Collisions; public readonly List<RectangleF> Collisions;
public TileCollisionInfo(TiledMap map, Vector2 position, TiledMapTile tile, TiledMapTileLayer layer, TiledMapTilesetTile tilesetTile, CollectCollisions collisionFunction) { internal TileCollisionInfo(TiledMap map, Vector2 position, TiledMapTile tile, TiledMapTileLayer layer, TiledMapTilesetTile tilesetTile, CollectCollisions collisionFunction) {
this.TilesetTile = tilesetTile; this.TilesetTile = tilesetTile;
this.Layer = layer; this.Layer = layer;
this.Tile = tile; this.Tile = tile;

View file

@ -1,9 +1,18 @@
using Coroutine; using Coroutine;
namespace MLEM.Startup { namespace MLEM.Startup {
/// <summary>
/// This class contains a set of events for the coroutine system that are automatically fired in <see cref="MlemGame"/>.
/// </summary>
public static class CoroutineEvents { public static class CoroutineEvents {
/// <summary>
/// This event is fired in <see cref="MlemGame.Update"/>
/// </summary>
public static readonly Event Update = new Event(); public static readonly Event Update = new Event();
/// <summary>
/// This event is fired in <see cref="MlemGame.Draw"/>
/// </summary>
public static readonly Event Draw = new Event(); public static readonly Event Draw = new Event();
} }

View file

@ -1,5 +1,6 @@
using Coroutine; using Coroutine;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input;
using MLEM.Input; using MLEM.Input;
@ -7,20 +8,54 @@ using MLEM.Ui;
using MLEM.Ui.Style; using MLEM.Ui.Style;
namespace MLEM.Startup { namespace MLEM.Startup {
/// <summary>
/// MlemGame is an extension of MonoGame's default <see cref="Game"/> class.
/// It features the default template setup, as well as an <see cref="InputHandler"/>, a <see cref="UiSystem"/> and some additional callback events.
/// It also runs all of the <see cref="CoroutineHandler"/> callbacks which can be used through <see cref="CoroutineEvents"/>.
/// </summary>
public class MlemGame : Game { public class MlemGame : Game {
private static MlemGame instance; private static MlemGame instance;
/// <summary>
/// The static game instance's input handler
/// </summary>
public static InputHandler Input => instance.InputHandler; public static InputHandler Input => instance.InputHandler;
/// <summary>
/// This game's graphics device manager, initialized in the constructor
/// </summary>
public readonly GraphicsDeviceManager GraphicsDeviceManager; public readonly GraphicsDeviceManager GraphicsDeviceManager;
/// <summary>
/// This game's sprite batch
/// </summary>
public SpriteBatch SpriteBatch { get; protected set; } public SpriteBatch SpriteBatch { get; protected set; }
/// <summary>
/// This game's input handler. This can easily be accessed through <see cref="Input"/>.
/// </summary>
public InputHandler InputHandler { get; protected set; } public InputHandler InputHandler { get; protected set; }
/// <summary>
/// This game's ui system
/// </summary>
public UiSystem UiSystem { get; protected set; } public UiSystem UiSystem { get; protected set; }
/// <summary>
/// An event that is invoked in <see cref="LoadContent"/>
/// </summary>
public GenericCallback OnLoadContent; public GenericCallback OnLoadContent;
/// <summary>
/// An event that is invoked in <see cref="Update"/>
/// </summary>
public TimeCallback OnUpdate; public TimeCallback OnUpdate;
/// <summary>
/// An event that is invoked in <see cref="Draw"/>
/// </summary>
public TimeCallback OnDraw; public TimeCallback OnDraw;
/// <summary>
/// Creates a new MlemGame instance with some default settings
/// </summary>
/// <param name="windowWidth">The default window width</param>
/// <param name="windowHeight">The default window height</param>
public MlemGame(int windowWidth = 1280, int windowHeight = 720) { public MlemGame(int windowWidth = 1280, int windowHeight = 720) {
instance = this; instance = this;
@ -33,6 +68,7 @@ namespace MLEM.Startup {
this.Content.RootDirectory = "Content"; this.Content.RootDirectory = "Content";
} }
/// <inheritdoc />
protected override void LoadContent() { protected override void LoadContent() {
this.SpriteBatch = new SpriteBatch(this.GraphicsDevice); this.SpriteBatch = new SpriteBatch(this.GraphicsDevice);
this.InputHandler = new InputHandler(); this.InputHandler = new InputHandler();
@ -42,6 +78,7 @@ namespace MLEM.Startup {
this.OnLoadContent?.Invoke(this); this.OnLoadContent?.Invoke(this);
} }
/// <inheritdoc />
protected sealed override void Update(GameTime gameTime) { protected sealed override void Update(GameTime gameTime) {
this.DoUpdate(gameTime); this.DoUpdate(gameTime);
this.OnUpdate?.Invoke(this, gameTime); this.OnUpdate?.Invoke(this, gameTime);
@ -49,6 +86,7 @@ namespace MLEM.Startup {
CoroutineHandler.RaiseEvent(CoroutineEvents.Update); CoroutineHandler.RaiseEvent(CoroutineEvents.Update);
} }
/// <inheritdoc />
protected sealed override void Draw(GameTime gameTime) { protected sealed override void Draw(GameTime gameTime) {
this.UiSystem.DrawEarly(gameTime, this.SpriteBatch); this.UiSystem.DrawEarly(gameTime, this.SpriteBatch);
this.DoDraw(gameTime); this.DoDraw(gameTime);
@ -57,20 +95,46 @@ namespace MLEM.Startup {
CoroutineHandler.RaiseEvent(CoroutineEvents.Draw); CoroutineHandler.RaiseEvent(CoroutineEvents.Draw);
} }
/// <summary>
/// This method is called in <see cref="Draw"/>.
/// It is the version that should be overridden by implementors to draw game content.
/// </summary>
/// <param name="gameTime">The game's time</param>
protected virtual void DoDraw(GameTime gameTime) { protected virtual void DoDraw(GameTime gameTime) {
base.Draw(gameTime); base.Draw(gameTime);
} }
/// <summary>
/// This method is called in <see cref="Update"/>.
/// It is the version that should be overridden by implementors to update game content.
/// </summary>
/// <param name="gameTime">The game's time</param>
protected virtual void DoUpdate(GameTime gameTime) { protected virtual void DoUpdate(GameTime gameTime) {
base.Update(gameTime); base.Update(gameTime);
} }
/// <summary>
/// Static helper method for <see cref="ContentManager.Load{T}"/>.
/// This just invokes the game instance's load method.
/// </summary>
/// <param name="name">The name of the content file to load</param>
/// <typeparam name="T">The type of content to load</typeparam>
/// <returns>The loaded content</returns>
public static T LoadContent<T>(string name) { public static T LoadContent<T>(string name) {
return instance.Content.Load<T>(name); return instance.Content.Load<T>(name);
} }
/// <summary>
/// A delegate method used by <see cref="MlemGame.OnLoadContent"/>.
/// </summary>
/// <param name="game">The game in question</param>
public delegate void GenericCallback(MlemGame game); public delegate void GenericCallback(MlemGame game);
/// <summary>
/// A delegate method used by <see cref="MlemGame.OnUpdate"/> and <see cref="MlemGame.OnDraw"/>.
/// </summary>
/// <param name="game">The game in question</param>
/// <param name="time">The game's current time</param>
public delegate void TimeCallback(MlemGame game, GameTime time); public delegate void TimeCallback(MlemGame game, GameTime time);
} }