diff --git a/Source/MonoGame.Extended.Gui/MonoGame.Extended.Gui.csproj b/Source/MonoGame.Extended.Gui/MonoGame.Extended.Gui.csproj index 8437bc6d..df64a51d 100644 --- a/Source/MonoGame.Extended.Gui/MonoGame.Extended.Gui.csproj +++ b/Source/MonoGame.Extended.Gui/MonoGame.Extended.Gui.csproj @@ -61,7 +61,6 @@ - diff --git a/Source/MonoGame.Extended.Gui/Serialization/GuiControlJsonConverter.cs b/Source/MonoGame.Extended.Gui/Serialization/GuiControlJsonConverter.cs index b88c5740..eb509c8f 100644 --- a/Source/MonoGame.Extended.Gui/Serialization/GuiControlJsonConverter.cs +++ b/Source/MonoGame.Extended.Gui/Serialization/GuiControlJsonConverter.cs @@ -23,7 +23,7 @@ namespace MonoGame.Extended.Gui.Serialization { var skin = _guiSkinService.Skin; var style = (GuiControlStyle) _styleConverter.ReadJson(reader, objectType, existingValue, serializer); - var template = GetControlTempalte(style, _styleProperty); + var template = GetControlTemplate(style); var control = skin.Create(style.TargetType, template); object childControls; @@ -48,7 +48,7 @@ namespace MonoGame.Extended.Gui.Serialization return objectType == typeof(GuiControl); } - private string GetControlTempalte(GuiControlStyle style, string styleProperty) + private static string GetControlTemplate(GuiControlStyle style) { object template; diff --git a/Source/MonoGame.Extended.Gui/Serialization/GuiJsonSerializer.cs b/Source/MonoGame.Extended.Gui/Serialization/GuiJsonSerializer.cs index b64ca0a9..477d244a 100644 --- a/Source/MonoGame.Extended.Gui/Serialization/GuiJsonSerializer.cs +++ b/Source/MonoGame.Extended.Gui/Serialization/GuiJsonSerializer.cs @@ -1,11 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Microsoft.Xna.Framework.Content; using MonoGame.Extended.BitmapFonts; using MonoGame.Extended.Serialization; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; namespace MonoGame.Extended.Gui.Serialization { @@ -23,7 +19,7 @@ namespace MonoGame.Extended.Gui.Serialization Converters.Add(new GuiTextureAtlasJsonConverter(contentManager, textureRegionService)); Converters.Add(new GuiNinePatchRegion2DJsonConverter(textureRegionService)); Converters.Add(new TextureRegion2DJsonConverter(textureRegionService)); - ContractResolver = new GuiJsonContractResolver(); + ContractResolver = new ShortNameJsonContractResolver(); Formatting = Formatting.Indented; } } diff --git a/Source/MonoGame.Extended.Gui/Serialization/GuiTextureRegionService.cs b/Source/MonoGame.Extended.Gui/Serialization/GuiTextureRegionService.cs index 86262186..83fe8382 100644 --- a/Source/MonoGame.Extended.Gui/Serialization/GuiTextureRegionService.cs +++ b/Source/MonoGame.Extended.Gui/Serialization/GuiTextureRegionService.cs @@ -7,31 +7,9 @@ namespace MonoGame.Extended.Gui.Serialization { public interface IGuiTextureRegionService : ITextureRegionService { - IList TextureAtlases { get; } - IList NinePatches { get; } } - public class GuiTextureRegionService : IGuiTextureRegionService + public class GuiTextureRegionService : TextureRegionService, IGuiTextureRegionService { - public GuiTextureRegionService() - { - TextureAtlases = new List(); - NinePatches = new List(); - } - - public IList TextureAtlases { get; } - public IList NinePatches { get; } - - public TextureRegion2D GetTextureRegion(string name) - { - var ninePatch = NinePatches.FirstOrDefault(p => p.Name == name); - - if (ninePatch != null) - return ninePatch; - - return TextureAtlases - .Select(textureAtlas => textureAtlas.GetRegion(name)) - .FirstOrDefault(region => region != null); - } } } \ No newline at end of file diff --git a/Source/MonoGame.Extended.Particles/HslColor.cs b/Source/MonoGame.Extended.Particles/HslColor.cs index f9343727..f21b335e 100644 --- a/Source/MonoGame.Extended.Particles/HslColor.cs +++ b/Source/MonoGame.Extended.Particles/HslColor.cs @@ -217,5 +217,47 @@ namespace MonoGame.Extended.Particles c1.S + t*(c2.S - c1.S), c1.L + t*(c2.L - c2.L)); } + + public static HslColor FromRgb(Color color) + { + // derived from http://www.geekymonkey.com/Programming/CSharp/RGB2HSL_HSL2RGB.htm + var r = color.R / 255f; + var g = color.G / 255f; + var b = color.B / 255f; + var h = 0f; // default to black + var s = 0f; + var l = 0f; + var v = Math.Max(r, g); + v = Math.Max(v, b); + + var m = Math.Min(r, g); + m = Math.Min(m, b); + l = (m + v) / 2.0f; + + if (l <= 0.0) + return new HslColor(h, s, l); + + var vm = v - m; + s = vm; + + if (s > 0.0) + s /= l <= 0.5f ? v + m : 2.0f - v - m; + else + return new HslColor(h, s, l); + + var r2 = (v - r) / vm; + var g2 = (v - g) / vm; + var b2 = (v - b) / vm; + + if (Math.Abs(r - v) < float.Epsilon) + h = Math.Abs(g - m) < float.Epsilon ? 5.0f + b2 : 1.0f - g2; + else if (Math.Abs(g - v) < float.Epsilon) + h = Math.Abs(b - m) < float.Epsilon ? 1.0f + r2 : 3.0f - b2; + else + h = Math.Abs(r - m) < float.Epsilon ? 3.0f + g2 : 5.0f - r2; + + h /= 6.0f; + return new HslColor(h, s, l); + } } } \ No newline at end of file diff --git a/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/ColorInterpolator.cs b/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/ColorInterpolator.cs index 20e46f0b..76821508 100644 --- a/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/ColorInterpolator.cs +++ b/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/ColorInterpolator.cs @@ -1,23 +1,40 @@ -namespace MonoGame.Extended.Particles.Modifiers.Interpolators +using System; + +namespace MonoGame.Extended.Particles.Modifiers.Interpolators { /// /// Defines a modifier which interpolates the color of a particle over the course of its lifetime. /// - public sealed class ColorInterpolator : IInterpolator + public sealed class ColorInterpolator : IInterpolator { /// /// Gets or sets the initial color of particles when they are released. /// - public HslColor InitialColor { get; set; } + public HslColor StartValue { get; set; } /// /// Gets or sets the final color of particles when they are retired. /// - public HslColor FinalColor { get; set; } + public HslColor EndValue { get; set; } + + [Obsolete("Use StartValue instead")] + public HslColor InitialColor + { + get { return StartValue; } + set { StartValue = value; } + } + + [Obsolete("Use EndValue instead")] + public HslColor FinalColor + { + get { return EndValue; } + set { EndValue = value; } + } public unsafe void Update(float amount, Particle* particle) { - particle->Color = HslColor.Lerp(InitialColor, FinalColor, amount); + particle->Color = HslColor.Lerp(StartValue, EndValue, amount); } + } } \ No newline at end of file diff --git a/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/HueInterpolator.cs b/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/HueInterpolator.cs index b9950bd1..49721622 100644 --- a/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/HueInterpolator.cs +++ b/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/HueInterpolator.cs @@ -3,8 +3,8 @@ public class HueInterpolator : IInterpolator { private float _delta; - public float StartValue { get; set; } + public float StartValue { get; set; } public float EndValue { get { return _delta + StartValue; } diff --git a/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/IInterpolator.cs b/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/IInterpolator.cs index 617f1ff5..7d8ded13 100644 --- a/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/IInterpolator.cs +++ b/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/IInterpolator.cs @@ -4,4 +4,10 @@ { unsafe void Update(float amount, Particle* particle); } + + public interface IInterpolator : IInterpolator + { + T StartValue { get; set; } + T EndValue { get; set; } + } } \ No newline at end of file diff --git a/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/OpacityInterpolator.cs b/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/OpacityInterpolator.cs index 0a10f6ab..6442e004 100644 --- a/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/OpacityInterpolator.cs +++ b/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/OpacityInterpolator.cs @@ -3,6 +3,7 @@ public class OpacityInterpolator : IInterpolator { private float _delta; + public float StartValue { get; set; } public float EndValue diff --git a/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/RotationInterpolator.cs b/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/RotationInterpolator.cs index 8d7d0c74..bfb161df 100644 --- a/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/RotationInterpolator.cs +++ b/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/RotationInterpolator.cs @@ -3,6 +3,7 @@ public class RotationInterpolator : IInterpolator { private float _delta; + public float StartValue { get; set; } public float EndValue diff --git a/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/ScaleInterpolator.cs b/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/ScaleInterpolator.cs index 95e4ab4e..6595cc30 100644 --- a/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/ScaleInterpolator.cs +++ b/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/ScaleInterpolator.cs @@ -5,6 +5,7 @@ namespace MonoGame.Extended.Particles.Modifiers.Interpolators public class ScaleInterpolator : IInterpolator { private Vector2 _delta; + public Vector2 StartValue { get; set; } public Vector2 EndValue diff --git a/Source/MonoGame.Extended.Particles/Modifiers/VortexModifier.cs b/Source/MonoGame.Extended.Particles/Modifiers/VortexModifier.cs index 7816e6a6..9f40a625 100644 --- a/Source/MonoGame.Extended.Particles/Modifiers/VortexModifier.cs +++ b/Source/MonoGame.Extended.Particles/Modifiers/VortexModifier.cs @@ -6,6 +6,7 @@ namespace MonoGame.Extended.Particles.Modifiers { // Note: not the real-life one private const float _gravConst = 100000f; + public Vector2 Position { get; set; } public float Mass { get; set; } public float MaxSpeed { get; set; } diff --git a/Source/MonoGame.Extended.Particles/MonoGame.Extended.Particles.csproj b/Source/MonoGame.Extended.Particles/MonoGame.Extended.Particles.csproj index b577ed99..92dc981b 100644 --- a/Source/MonoGame.Extended.Particles/MonoGame.Extended.Particles.csproj +++ b/Source/MonoGame.Extended.Particles/MonoGame.Extended.Particles.csproj @@ -82,11 +82,21 @@ + + + + + + + ..\packages\MonoGame.Framework.Portable.3.6.0.1625\lib\portable-net45+win8+wpa81\MonoGame.Framework.dll + + ..\packages\Newtonsoft.Json.9.0.1\lib\portable-net45+wp80+win8+wpa81\Newtonsoft.Json.dll + diff --git a/Source/MonoGame.Extended.Particles/ParticleEffect.cs b/Source/MonoGame.Extended.Particles/ParticleEffect.cs index 71edcfe2..9bc8b459 100644 --- a/Source/MonoGame.Extended.Particles/ParticleEffect.cs +++ b/Source/MonoGame.Extended.Particles/ParticleEffect.cs @@ -1,5 +1,10 @@ -using System.Linq; +using System.IO; +using System.Linq; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using MonoGame.Extended.Particles.Serialization; +using MonoGame.Extended.Serialization; +using Newtonsoft.Json; namespace MonoGame.Extended.Particles { @@ -26,6 +31,25 @@ namespace MonoGame.Extended.Particles } } + public static ParticleEffect FromFile(ITextureRegionService textureRegionService, string path) + { + using (var stream = TitleContainer.OpenStream(path)) + { + return FromStream(textureRegionService, stream); + } + } + + public static ParticleEffect FromStream(ITextureRegionService textureRegionService, Stream stream) + { + var skinSerializer = new ParticleJsonSerializer(textureRegionService); + + using (var streamReader = new StreamReader(stream)) + using (var jsonReader = new JsonTextReader(streamReader)) + { + return skinSerializer.Deserialize(jsonReader); + } + } + public void Update(float elapsedSeconds) { foreach (var e in Emitters) diff --git a/Source/MonoGame.Extended.Particles/ParticleEmitter.cs b/Source/MonoGame.Extended.Particles/ParticleEmitter.cs index 74d4eba3..9f04fb51 100644 --- a/Source/MonoGame.Extended.Particles/ParticleEmitter.cs +++ b/Source/MonoGame.Extended.Particles/ParticleEmitter.cs @@ -15,8 +15,7 @@ namespace MonoGame.Extended.Particles private float _totalSeconds; - public ParticleEmitter(TextureRegion2D textureRegion, int capacity, TimeSpan term, Profile profile, - bool autoTrigger = true) + public ParticleEmitter(TextureRegion2D textureRegion, int capacity, TimeSpan term, Profile profile, bool autoTrigger = true) { if (profile == null) throw new ArgumentNullException(nameof(profile)); diff --git a/Source/MonoGame.Extended.Particles/ParticleModifierExecutionStrategy.cs b/Source/MonoGame.Extended.Particles/ParticleModifierExecutionStrategy.cs index 7c80efdf..a57ef039 100644 --- a/Source/MonoGame.Extended.Particles/ParticleModifierExecutionStrategy.cs +++ b/Source/MonoGame.Extended.Particles/ParticleModifierExecutionStrategy.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using MonoGame.Extended.Particles.Modifiers; namespace MonoGame.Extended.Particles @@ -7,11 +8,10 @@ namespace MonoGame.Extended.Particles public abstract class ParticleModifierExecutionStrategy { - public static ParticleModifierExecutionStrategy Serial = new SerialModifierExecutionStrategy(); - public static ParticleModifierExecutionStrategy Parallel = new ParallelModifierExecutionStrategy(); + public static readonly ParticleModifierExecutionStrategy Serial = new SerialModifierExecutionStrategy(); + public static readonly ParticleModifierExecutionStrategy Parallel = new ParallelModifierExecutionStrategy(); - internal abstract void ExecuteModifiers(IEnumerable modifiers, float elapsedSeconds, - ParticleBuffer.ParticleIterator iterator); + internal abstract void ExecuteModifiers(IEnumerable modifiers, float elapsedSeconds, ParticleBuffer.ParticleIterator iterator); internal class SerialModifierExecutionStrategy : ParticleModifierExecutionStrategy { @@ -31,5 +31,16 @@ namespace MonoGame.Extended.Particles TPL.Parallel.ForEach(modifiers, modifier => modifier.Update(elapsedSeconds, iterator.Reset())); } } + + public static ParticleModifierExecutionStrategy Parse(string value) + { + if (string.Equals(nameof(Parallel), value, StringComparison.OrdinalIgnoreCase)) + return Parallel; + + if (string.Equals(nameof(Serial), value, StringComparison.OrdinalIgnoreCase)) + return Serial; + + throw new InvalidOperationException($"Unknown particle modifier execution strategy '{value}'"); + } } } \ No newline at end of file diff --git a/Source/MonoGame.Extended.Particles/Profiles/Profile.cs b/Source/MonoGame.Extended.Particles/Profiles/Profile.cs index f3a2da85..bfde371b 100644 --- a/Source/MonoGame.Extended.Particles/Profiles/Profile.cs +++ b/Source/MonoGame.Extended.Particles/Profiles/Profile.cs @@ -2,7 +2,7 @@ namespace MonoGame.Extended.Particles.Profiles { - public abstract class Profile //: ICloneable + public abstract class Profile { public enum CircleRadiation { diff --git a/Source/MonoGame.Extended.Particles/Serialization/HslColorJsonConverter.cs b/Source/MonoGame.Extended.Particles/Serialization/HslColorJsonConverter.cs new file mode 100644 index 00000000..4b65c117 --- /dev/null +++ b/Source/MonoGame.Extended.Particles/Serialization/HslColorJsonConverter.cs @@ -0,0 +1,29 @@ +using System; +using Microsoft.Xna.Framework; +using MonoGame.Extended.Serialization; +using Newtonsoft.Json; + +namespace MonoGame.Extended.Particles.Serialization +{ + public class HslColorJsonConverter : JsonConverter + { + private readonly ColorJsonConverter _colorConverter = new ColorJsonConverter(); + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var color = ((HslColor) value).ToRgb(); + _colorConverter.WriteJson(writer, color, serializer); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var color = (Color)_colorConverter.ReadJson(reader, objectType, existingValue, serializer); + return HslColor.FromRgb(color); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(HslColor); + } + } +} \ No newline at end of file diff --git a/Source/MonoGame.Extended.Particles/Serialization/InterpolatorJsonConverter.cs b/Source/MonoGame.Extended.Particles/Serialization/InterpolatorJsonConverter.cs new file mode 100644 index 00000000..44422d5b --- /dev/null +++ b/Source/MonoGame.Extended.Particles/Serialization/InterpolatorJsonConverter.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using MonoGame.Extended.Particles.Modifiers.Interpolators; +using MonoGame.Extended.Serialization; + +namespace MonoGame.Extended.Particles.Serialization +{ + public class InterpolatorJsonConverter : BaseTypeJsonConverter + { + public InterpolatorJsonConverter() + : base(GetSupportedTypes(), "Interpolator") + { + } + + private static IEnumerable GetSupportedTypes() + { + return typeof(IInterpolator) + .GetTypeInfo() + .Assembly + .DefinedTypes + .Where(type => typeof(IInterpolator).GetTypeInfo().IsAssignableFrom(type) && !type.IsAbstract); + } + } +} \ No newline at end of file diff --git a/Source/MonoGame.Extended.Particles/Serialization/ModifierExecutionStrategyJsonConverter.cs b/Source/MonoGame.Extended.Particles/Serialization/ModifierExecutionStrategyJsonConverter.cs new file mode 100644 index 00000000..da66a4c5 --- /dev/null +++ b/Source/MonoGame.Extended.Particles/Serialization/ModifierExecutionStrategyJsonConverter.cs @@ -0,0 +1,24 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace MonoGame.Extended.Particles.Serialization +{ + public class ModifierExecutionStrategyJsonConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var value = JToken.Load(reader).ToObject(); + return ParticleModifierExecutionStrategy.Parse(value); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(ParticleModifierExecutionStrategy); + } + } +} \ No newline at end of file diff --git a/Source/MonoGame.Extended.Particles/Serialization/ModifierJsonConverter.cs b/Source/MonoGame.Extended.Particles/Serialization/ModifierJsonConverter.cs new file mode 100644 index 00000000..ab3bc670 --- /dev/null +++ b/Source/MonoGame.Extended.Particles/Serialization/ModifierJsonConverter.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using MonoGame.Extended.Particles.Modifiers; +using MonoGame.Extended.Serialization; + +namespace MonoGame.Extended.Particles.Serialization +{ + public class ModifierJsonConverter : BaseTypeJsonConverter + { + public ModifierJsonConverter() + : base(GetSupportedTypes(), "Modifier") + { + } + + private static IEnumerable GetSupportedTypes() + { + return typeof(IModifier) + .GetTypeInfo() + .Assembly + .DefinedTypes + .Where(type => typeof(IModifier).GetTypeInfo().IsAssignableFrom(type) && !type.IsAbstract); + } + } +} \ No newline at end of file diff --git a/Source/MonoGame.Extended.Particles/Serialization/ParticleJsonSerializer.cs b/Source/MonoGame.Extended.Particles/Serialization/ParticleJsonSerializer.cs new file mode 100644 index 00000000..ed15965c --- /dev/null +++ b/Source/MonoGame.Extended.Particles/Serialization/ParticleJsonSerializer.cs @@ -0,0 +1,27 @@ +using MonoGame.Extended.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace MonoGame.Extended.Particles.Serialization +{ + public sealed class ParticleJsonSerializer : JsonSerializer + { + public ParticleJsonSerializer(ITextureRegionService textureRegionService) + { + Converters.Add(new Vector2JsonConverter()); + Converters.Add(new Size2JsonConverter()); + Converters.Add(new ColorJsonConverter()); + Converters.Add(new TextureRegion2DJsonConverter(textureRegionService)); + Converters.Add(new ProfileJsonConverter()); + Converters.Add(new ModifierJsonConverter()); + Converters.Add(new InterpolatorJsonConverter()); + Converters.Add(new TimeSpanJsonConverter()); + Converters.Add(new RangeJsonConverter()); + Converters.Add(new RangeJsonConverter()); + Converters.Add(new HslColorJsonConverter()); + Converters.Add(new ModifierExecutionStrategyJsonConverter()); + ContractResolver = new ShortNameJsonContractResolver(); + Formatting = Formatting.Indented; + } + } +} diff --git a/Source/MonoGame.Extended.Particles/Serialization/ProfileJsonConverter.cs b/Source/MonoGame.Extended.Particles/Serialization/ProfileJsonConverter.cs new file mode 100644 index 00000000..8d980791 --- /dev/null +++ b/Source/MonoGame.Extended.Particles/Serialization/ProfileJsonConverter.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using MonoGame.Extended.Particles.Profiles; +using MonoGame.Extended.Serialization; + +namespace MonoGame.Extended.Particles.Serialization +{ + public class ProfileJsonConverter : BaseTypeJsonConverter + { + public ProfileJsonConverter() + : base(GetSupportedTypes(), nameof(Profile)) + { + } + + private static IEnumerable GetSupportedTypes() + { + return typeof(Profile) + .GetTypeInfo() + .Assembly + .DefinedTypes + .Where(type => type.IsSubclassOf(typeof(Profile)) && !type.IsAbstract); + } + } +} \ No newline at end of file diff --git a/Source/MonoGame.Extended.Particles/Serialization/TimeSpanJsonConverter.cs b/Source/MonoGame.Extended.Particles/Serialization/TimeSpanJsonConverter.cs new file mode 100644 index 00000000..e6029152 --- /dev/null +++ b/Source/MonoGame.Extended.Particles/Serialization/TimeSpanJsonConverter.cs @@ -0,0 +1,28 @@ +using System; +using Newtonsoft.Json; + +namespace MonoGame.Extended.Particles.Serialization +{ + public class TimeSpanJsonConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.ValueType == typeof(double)) + { + var seconds = (double) reader.Value; + return TimeSpan.FromSeconds(seconds); + } + + return TimeSpan.Zero; + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(TimeSpan); + } + } +} \ No newline at end of file diff --git a/Source/MonoGame.Extended.Particles/packages.config b/Source/MonoGame.Extended.Particles/packages.config index 28c01444..43f6cc68 100644 --- a/Source/MonoGame.Extended.Particles/packages.config +++ b/Source/MonoGame.Extended.Particles/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file diff --git a/Source/MonoGame.Extended/MonoGame.Extended.csproj b/Source/MonoGame.Extended/MonoGame.Extended.csproj index bf1c3198..f0b4d031 100644 --- a/Source/MonoGame.Extended/MonoGame.Extended.csproj +++ b/Source/MonoGame.Extended/MonoGame.Extended.csproj @@ -42,6 +42,10 @@ + + + + @@ -53,7 +57,7 @@ - + diff --git a/Source/MonoGame.Extended/Serialization/BaseTypeJsonConverter.cs b/Source/MonoGame.Extended/Serialization/BaseTypeJsonConverter.cs new file mode 100644 index 00000000..24fbf3e7 --- /dev/null +++ b/Source/MonoGame.Extended/Serialization/BaseTypeJsonConverter.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace MonoGame.Extended.Serialization +{ + public abstract class BaseTypeJsonConverter : JsonConverter + { + private readonly string _suffix; + private readonly Dictionary _baseTypes; + + protected BaseTypeJsonConverter(IEnumerable supportedTypes, string suffix) + { + _suffix = suffix; + _baseTypes = supportedTypes + .ToDictionary(t => TrimSuffix(t.Name, suffix), t => t.AsType(), StringComparer.OrdinalIgnoreCase); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var jObject = JObject.Load(reader); + var key = jObject.GetValue("type", StringComparison.OrdinalIgnoreCase).ToObject(); + Type type; + + if (_baseTypes.TryGetValue(key, out type)) + return jObject.ToObject(type, serializer); + + throw new InvalidOperationException($"Unknown {_suffix} type '{key}'"); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(T); + } + + private static string TrimSuffix(string input, string suffix) + { + if (input.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)) + return input.Substring(0, input.Length - suffix.Length); + + return input; + } + } +} \ No newline at end of file diff --git a/Source/MonoGame.Extended/Serialization/ITextureRegionService.cs b/Source/MonoGame.Extended/Serialization/ITextureRegionService.cs deleted file mode 100644 index 8617f778..00000000 --- a/Source/MonoGame.Extended/Serialization/ITextureRegionService.cs +++ /dev/null @@ -1,9 +0,0 @@ -using MonoGame.Extended.TextureAtlases; - -namespace MonoGame.Extended.Serialization -{ - public interface ITextureRegionService - { - TextureRegion2D GetTextureRegion(string name); - } -} \ No newline at end of file diff --git a/Source/MonoGame.Extended/Serialization/JsonReaderExtensions.cs b/Source/MonoGame.Extended/Serialization/JsonReaderExtensions.cs new file mode 100644 index 00000000..e48b5c60 --- /dev/null +++ b/Source/MonoGame.Extended/Serialization/JsonReaderExtensions.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace MonoGame.Extended.Serialization +{ + public static class JsonReaderExtensions + { + private static readonly Dictionary> _stringParsers = new Dictionary> + { + {typeof(int), s => int.Parse(s, CultureInfo.InvariantCulture.NumberFormat)}, + {typeof(float), s => float.Parse(s, CultureInfo.InvariantCulture.NumberFormat)}, + }; + + public static T[] ReadAsMultiDimensional(this JsonReader reader) + { + var tokenType = reader.TokenType; + + switch (tokenType) + { + case JsonToken.StartArray: + var jArray = JArray.Load(reader); + return jArray + .Select(i => i.Value()) + .ToArray(); + + case JsonToken.String: + var value = (string)reader.Value; + var parser = _stringParsers[typeof(T)]; + return value.Split(' ') + .Select(i => parser(i)) + .Cast() + .ToArray(); + + case JsonToken.Integer: + case JsonToken.Float: + return new []{ JToken.Load(reader).ToObject() }; + + default: + throw new NotSupportedException($"{tokenType} is not currently supported in the multi dimensional parser"); + } + } + } +} \ No newline at end of file diff --git a/Source/MonoGame.Extended/Serialization/RangeJsonConverter.cs b/Source/MonoGame.Extended/Serialization/RangeJsonConverter.cs new file mode 100644 index 00000000..ddf0cc36 --- /dev/null +++ b/Source/MonoGame.Extended/Serialization/RangeJsonConverter.cs @@ -0,0 +1,30 @@ +using System; +using Newtonsoft.Json; + +namespace MonoGame.Extended.Serialization +{ + public class RangeJsonConverter : JsonConverter where T : IComparable + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var values = reader.ReadAsMultiDimensional(); + + if (values.Length == 2) + return new Range(values[0], values[1]); + + if (values.Length == 1) + return new Range(values[0], values[0]); + + throw new InvalidOperationException("Invalid range"); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Range); + } + } +} \ No newline at end of file diff --git a/Source/MonoGame.Extended.Gui/Serialization/GuiJsonContractResolver.cs b/Source/MonoGame.Extended/Serialization/ShortNameJsonContractResolver.cs similarity index 85% rename from Source/MonoGame.Extended.Gui/Serialization/GuiJsonContractResolver.cs rename to Source/MonoGame.Extended/Serialization/ShortNameJsonContractResolver.cs index 3b2fe01d..38708a37 100644 --- a/Source/MonoGame.Extended.Gui/Serialization/GuiJsonContractResolver.cs +++ b/Source/MonoGame.Extended/Serialization/ShortNameJsonContractResolver.cs @@ -4,15 +4,15 @@ using System.Reflection; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; -namespace MonoGame.Extended.Gui.Serialization +namespace MonoGame.Extended.Serialization { - public class GuiJsonContractResolver : DefaultContractResolver + public class ShortNameJsonContractResolver : DefaultContractResolver { protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) { var properties = base.CreateProperties(type, memberSerialization); - if (type.GetTypeInfo().IsAbstract) //.IsSubclassOf(typeof(GuiControl))) + if (type.GetTypeInfo().IsAbstract) { properties.Insert(0, new JsonProperty { diff --git a/Source/MonoGame.Extended/Serialization/Size2JsonConverter.cs b/Source/MonoGame.Extended/Serialization/Size2JsonConverter.cs index e23fc6bd..78701999 100644 --- a/Source/MonoGame.Extended/Serialization/Size2JsonConverter.cs +++ b/Source/MonoGame.Extended/Serialization/Size2JsonConverter.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; using Newtonsoft.Json; namespace MonoGame.Extended.Serialization @@ -15,11 +14,15 @@ namespace MonoGame.Extended.Serialization public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { - var value = (string) reader.Value; - var fields = value.Split(' '); - var width = float.Parse(fields[0], CultureInfo.InvariantCulture.NumberFormat); - var height = float.Parse(fields[1], CultureInfo.InvariantCulture.NumberFormat); - return new Size2(width, height); + var values = reader.ReadAsMultiDimensional(); + + if(values.Length == 2) + return new Size2(values[0], values[1]); + + if (values.Length == 1) + return new Size2(values[0], values[1]); + + throw new InvalidOperationException("Invalid size"); } public override bool CanConvert(Type objectType) diff --git a/Source/MonoGame.Extended/Serialization/TextureRegionService.cs b/Source/MonoGame.Extended/Serialization/TextureRegionService.cs new file mode 100644 index 00000000..66ef6faf --- /dev/null +++ b/Source/MonoGame.Extended/Serialization/TextureRegionService.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Linq; +using MonoGame.Extended.TextureAtlases; + +namespace MonoGame.Extended.Serialization +{ + public interface ITextureRegionService + { + IList TextureAtlases { get; } + IList NinePatches { get; } + + TextureRegion2D GetTextureRegion(string name); + } + + public class TextureRegionService : ITextureRegionService + { + public TextureRegionService() + { + TextureAtlases = new List(); + NinePatches = new List(); + } + + public IList TextureAtlases { get; } + public IList NinePatches { get; } + + public TextureRegion2D GetTextureRegion(string name) + { + var ninePatch = NinePatches.FirstOrDefault(p => p.Name == name); + + if (ninePatch != null) + return ninePatch; + + return TextureAtlases + .Select(textureAtlas => textureAtlas.GetRegion(name)) + .FirstOrDefault(region => region != null); + } + } +} \ No newline at end of file diff --git a/Source/MonoGame.Extended/Serialization/Vector2JsonConverter.cs b/Source/MonoGame.Extended/Serialization/Vector2JsonConverter.cs index d536520e..0f4f9cda 100644 --- a/Source/MonoGame.Extended/Serialization/Vector2JsonConverter.cs +++ b/Source/MonoGame.Extended/Serialization/Vector2JsonConverter.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; using Microsoft.Xna.Framework; using Newtonsoft.Json; @@ -16,11 +15,15 @@ namespace MonoGame.Extended.Serialization public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { - var value = (string) reader.Value; - var fields = value.Split(' '); - var x = float.Parse(fields[0], CultureInfo.InvariantCulture.NumberFormat); - var y = float.Parse(fields[1], CultureInfo.InvariantCulture.NumberFormat); - return new Vector2(x, y); + var values = reader.ReadAsMultiDimensional(); + + if(values.Length == 2) + return new Vector2(values[0], values[1]); + + if (values.Length == 1) + return new Vector2(values[0]); + + throw new InvalidOperationException("Invalid Vector2"); } public override bool CanConvert(Type objectType) diff --git a/Source/Tests/Sandbox/Content/particle-effect.json b/Source/Tests/Sandbox/Content/particle-effect.json new file mode 100644 index 00000000..74027a9a --- /dev/null +++ b/Source/Tests/Sandbox/Content/particle-effect.json @@ -0,0 +1,40 @@ +{ + "name": "test", + "emitters": [ + { + "textureRegion": "checkBox_beige_unchecked", + "capacity": 500, + "term": 2.5, + "ModifierExecutionStrategy": "Parallel", + "profile": { + "type": "box", + "width": 700, + "height": 380 + }, + "parameters": { + "quantity": [ 3, 4 ], + "speed": [ 0, 10 ], + "rotation": [ -1, 1 ], + "scale": [ 13, 14 ] + }, + "modifiers": [ + { + "type": "linearGravity", + "direction": [ 0, 1 ], + "strength": 10 + }, + { "type": "opacityFastFade" }, + { + "type": "age", + "interpolators": [ + { + "type": "Color", + "startValue": "#ff0000", + "endValue": "#00ff00" + } + ] + } + ] + } + ] +} diff --git a/Source/Tests/Sandbox/Game1.cs b/Source/Tests/Sandbox/Game1.cs index d9e3e279..0901bd6b 100644 --- a/Source/Tests/Sandbox/Game1.cs +++ b/Source/Tests/Sandbox/Game1.cs @@ -1,9 +1,19 @@ -using Microsoft.Xna.Framework; +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using MonoGame.Extended; using MonoGame.Extended.Gui; using MonoGame.Extended.Gui.Controls; +using MonoGame.Extended.Particles; +using MonoGame.Extended.Particles.Modifiers; +using MonoGame.Extended.Particles.Modifiers.Containers; +using MonoGame.Extended.Particles.Modifiers.Interpolators; +using MonoGame.Extended.Particles.Profiles; +using MonoGame.Extended.Serialization; +using MonoGame.Extended.TextureAtlases; using MonoGame.Extended.ViewportAdapters; +using Newtonsoft.Json; // The Sandbox project is used for experiementing outside the normal demos. // Any code found here should be considered experimental work in progress. @@ -17,6 +27,9 @@ namespace Sandbox private Camera2D _camera; private GuiSystem _guiSystem; + private SpriteBatch _spriteBatch; + private ParticleEffect _particleEffect; + public Game1() { _graphicsDeviceManager = new GraphicsDeviceManager(this); @@ -39,28 +52,90 @@ namespace Sandbox { Controls = { - new GuiLabel { Text = "Hello World" } + //new GuiLabel { Text = "Hello World" } } } }; + _spriteBatch = new SpriteBatch(GraphicsDevice); + + var particleTexture = new Texture2D(GraphicsDevice, 1, 1); + particleTexture.SetData(new[] { Color.White }); + + var textureRegionService = new TextureRegionService(); + textureRegionService.TextureAtlases.Add(Content.Load("adventure-gui-atlas")); + + _particleEffect = ParticleEffect.FromFile(textureRegionService, @"Content/particle-effect.json"); + + //ParticleInit(new TextureRegion2D(particleTexture)); } protected override void Update(GameTime gameTime) { + var deltaTime = gameTime.GetElapsedSeconds(); var keyboardState = Keyboard.GetState(); + var mouseState = Mouse.GetState(); + var p = _camera.ScreenToWorld(mouseState.X, mouseState.Y); if (keyboardState.IsKeyDown(Keys.Escape)) Exit(); + _particleEffect.Update(deltaTime); + + if (mouseState.LeftButton == ButtonState.Pressed) + _particleEffect.Trigger(new Vector2(p.X, p.Y)); + + _particleEffect.Trigger(new Vector2(400, 240)); + _guiSystem.Update(gameTime); } protected override void Draw(GameTime gameTime) { - GraphicsDevice.Clear(Color.CornflowerBlue); + GraphicsDevice.Clear(Color.Black); + + _spriteBatch.Begin(blendState: BlendState.AlphaBlend, transformMatrix: _camera.GetViewMatrix()); + _spriteBatch.Draw(_particleEffect); + _spriteBatch.End(); _guiSystem.Draw(gameTime); } + + private ParticleEffect ParticleInit(TextureRegion2D textureRegion) + { + return _particleEffect = new ParticleEffect + { + Emitters = new[] + { + new ParticleEmitter(textureRegion, 500, TimeSpan.FromSeconds(2.5), Profile.Ring(150f, Profile.CircleRadiation.In)) + { + Parameters = new ParticleReleaseParameters + { + Speed = new Range(0f, 50f), + Quantity = 3, + Rotation = new Range(-1f, 1f), + Scale = new Range(3.0f, 4.0f) + }, + Modifiers = new IModifier[] + { + new AgeModifier + { + Interpolators = new IInterpolator[] + { + new ColorInterpolator + { + InitialColor = new HslColor(0.33f, 0.5f, 0.5f), + FinalColor = new HslColor(0.5f, 0.9f, 1.0f) + } + } + }, + new RotationModifier {RotationRate = -2.1f}, + new RectangleContainerModifier {Width = 800, Height = 480}, + new LinearGravityModifier {Direction = -Vector2.UnitY, Strength = 30f} + } + } + } + }; + } } } diff --git a/Source/Tests/Sandbox/Sandbox.csproj b/Source/Tests/Sandbox/Sandbox.csproj index 6c8134e8..a5071b4b 100644 --- a/Source/Tests/Sandbox/Sandbox.csproj +++ b/Source/Tests/Sandbox/Sandbox.csproj @@ -50,6 +50,9 @@ $(MonoGameInstallDirectory)\MonoGame\v3.0\Assemblies\DesktopGL\MonoGame.Framework.dll + + ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + @@ -108,13 +111,21 @@ PreserveNewest + + PreserveNewest + + {8e425575-378a-4f83-88c9-f8bebad227c9} MonoGame.Extended.Gui + + {6c8b9e29-d09b-4901-80fd-45aaa35882c6} + MonoGame.Extended.Particles + {41724c52-3d50-45bb-81eb-3c8a247eafd1} MonoGame.Extended diff --git a/Source/Tests/Sandbox/packages.config b/Source/Tests/Sandbox/packages.config new file mode 100644 index 00000000..e1fae9c6 --- /dev/null +++ b/Source/Tests/Sandbox/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file