diff options
author | chai <215380520@qq.com> | 2024-06-03 10:15:45 +0800 |
---|---|---|
committer | chai <215380520@qq.com> | 2024-06-03 10:15:45 +0800 |
commit | acea7b2e728787a0d83bbf83c8c1f042d2c32e7e (patch) | |
tree | 0bfec05c1ca2d71be2c337bcd110a0421f19318b /Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline | |
parent | 88febcb02bf127d961c6471d9e846c0e1315f5c3 (diff) |
+ plugins project
Diffstat (limited to 'Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline')
45 files changed, 1913 insertions, 0 deletions
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Animations/AstridAnimatorAnimation.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Animations/AstridAnimatorAnimation.cs new file mode 100644 index 0000000..14638d4 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Animations/AstridAnimatorAnimation.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace MonoGame.Extended.Content.Pipeline.Animations +{ + public class AstridAnimatorAnimation + { + public string Name { get; set; } + public int FramesPerSecond { get; set; } + public List<string> Frames { get; set; } + public bool IsLooping { get; set; } + public bool IsReversed { get; set; } + public bool IsPingPong { get; set; } + + public AstridAnimatorAnimation(string name, int framesPerSecond) + { + Name = name; + FramesPerSecond = framesPerSecond; + Frames = new List<string>(); + IsLooping = true; + IsReversed = false; + IsPingPong = false; + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Animations/AstridAnimatorFile.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Animations/AstridAnimatorFile.cs new file mode 100644 index 0000000..765f2f2 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Animations/AstridAnimatorFile.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace MonoGame.Extended.Content.Pipeline.Animations +{ + public class AstridAnimatorFile + { + public string TextureAtlas { get; set; } + public List<AstridAnimatorAnimation> Animations { get; set; } + + public AstridAnimatorFile() + { + Animations = new List<AstridAnimatorAnimation>(); + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Animations/AstridAnimatorImporter.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Animations/AstridAnimatorImporter.cs new file mode 100644 index 0000000..855a0d3 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Animations/AstridAnimatorImporter.cs @@ -0,0 +1,18 @@ +using System.IO; +using System.Text.Json; +using Microsoft.Xna.Framework.Content.Pipeline; + +namespace MonoGame.Extended.Content.Pipeline.Animations +{ + [ContentImporter(".aa", DefaultProcessor = "AstridAnimatorProcessor", + DisplayName = "Astrid Animator Importer - MonoGame.Extended")] + public class AstridAnimatorImporter : ContentImporter<ContentImporterResult<AstridAnimatorFile>> + { + public override ContentImporterResult<AstridAnimatorFile> Import(string filename, ContentImporterContext context) + { + var json = File.ReadAllText(filename); + var data = JsonSerializer.Deserialize<AstridAnimatorFile>(json); + return new ContentImporterResult<AstridAnimatorFile>(filename, data); + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Animations/AstridAnimatorProcessor.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Animations/AstridAnimatorProcessor.cs new file mode 100644 index 0000000..e22d65b --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Animations/AstridAnimatorProcessor.cs @@ -0,0 +1,24 @@ +using System.IO; +using System.Linq; +using Microsoft.Xna.Framework.Content.Pipeline; + +namespace MonoGame.Extended.Content.Pipeline.Animations +{ + [ContentProcessor(DisplayName = "Astrid Animator Processor - MonoGame.Extended")] + public class AstridAnimatorProcessor : + ContentProcessor<ContentImporterResult<AstridAnimatorFile>, AstridAnimatorProcessorResult> + { + public override AstridAnimatorProcessorResult Process(ContentImporterResult<AstridAnimatorFile> input, + ContentProcessorContext context) + { + var data = input.Data; + var directory = Path.GetDirectoryName(input.FilePath); + var frames = data.Animations + .SelectMany(i => i.Frames) + .OrderBy(f => f) + .Distinct(); + + return new AstridAnimatorProcessorResult(directory, data, frames); + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Animations/AstridAnimatorProcessorResult.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Animations/AstridAnimatorProcessorResult.cs new file mode 100644 index 0000000..de92ec3 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Animations/AstridAnimatorProcessorResult.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.IO; + +namespace MonoGame.Extended.Content.Pipeline.Animations +{ + public class AstridAnimatorProcessorResult + { + public string TextureAtlasAssetName { get; private set; } + public string Directory { get; private set; } + public AstridAnimatorFile Data { get; private set; } + public List<string> Frames { get; private set; } + + public AstridAnimatorProcessorResult(string directory, AstridAnimatorFile data, IEnumerable<string> frames) + { + Directory = directory; + Data = data; + Frames = new List<string>(frames); + TextureAtlasAssetName = Path.GetFileNameWithoutExtension(data.TextureAtlas); + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Animations/AstridAnimatorWriter.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Animations/AstridAnimatorWriter.cs new file mode 100644 index 0000000..6f56f3f --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Animations/AstridAnimatorWriter.cs @@ -0,0 +1,40 @@ +using Microsoft.Xna.Framework.Content.Pipeline; +using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler; + +namespace MonoGame.Extended.Content.Pipeline.Animations +{ + [ContentTypeWriter] + public class AstridAnimatorWriter : ContentTypeWriter<AstridAnimatorProcessorResult> + { + public override string GetRuntimeReader(TargetPlatform targetPlatform) + { + return "MonoGame.Extended.Animations.SpriteSheets.SpriteSheetAnimationFactoryReader, MonoGame.Extended.Animations"; + } + + protected override void Write(ContentWriter writer, AstridAnimatorProcessorResult input) + { + var data = input.Data; + + writer.Write(input.TextureAtlasAssetName); + writer.Write(input.Frames.Count); + + foreach (var frame in input.Frames) + writer.Write(frame); + + writer.Write(data.Animations.Count); + + foreach (var animation in data.Animations) + { + writer.Write(animation.Name); + writer.Write(animation.FramesPerSecond); + writer.Write(animation.IsLooping); + writer.Write(animation.IsReversed); + writer.Write(animation.IsPingPong); + writer.Write(animation.Frames.Count); + + foreach (var frame in animation.Frames) + writer.Write(input.Frames.IndexOf(frame)); + } + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontChar.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontChar.cs new file mode 100644 index 0000000..c012da4 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontChar.cs @@ -0,0 +1,41 @@ +using System.Xml.Serialization; + +namespace MonoGame.Extended.Content.Pipeline.BitmapFonts +{ + // ---- AngelCode BmFont XML serializer ---------------------- + // ---- By DeadlyDan @ deadlydan@gmail.com ------------------- + // ---- There's no license restrictions, use as you will. ---- + // ---- Credits to http://www.angelcode.com/ ----------------- + public class BitmapFontChar + { + [XmlAttribute("id")] + public int Id { get; set; } + + [XmlAttribute("x")] + public int X { get; set; } + + [XmlAttribute("y")] + public int Y { get; set; } + + [XmlAttribute("width")] + public int Width { get; set; } + + [XmlAttribute("height")] + public int Height { get; set; } + + [XmlAttribute("xoffset")] + public int XOffset { get; set; } + + [XmlAttribute("yoffset")] + public int YOffset { get; set; } + + [XmlAttribute("xadvance")] + public int XAdvance { get; set; } + + [XmlAttribute("page")] + public int Page { get; set; } + + [XmlAttribute("chnl")] + public int Channel { get; set; } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontCommon.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontCommon.cs new file mode 100644 index 0000000..6247fcf --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontCommon.cs @@ -0,0 +1,41 @@ +using System.Xml.Serialization; + +namespace MonoGame.Extended.Content.Pipeline.BitmapFonts +{ + // ---- AngelCode BmFont XML serializer ---------------------- + // ---- By DeadlyDan @ deadlydan@gmail.com ------------------- + // ---- There's no license restrictions, use as you will. ---- + // ---- Credits to http://www.angelcode.com/ ----------------- + public class BitmapFontCommon + { + [XmlAttribute("lineHeight")] + public int LineHeight { get; set; } + + [XmlAttribute("base")] + public int Base { get; set; } + + [XmlAttribute("scaleW")] + public int ScaleW { get; set; } + + [XmlAttribute("scaleH")] + public int ScaleH { get; set; } + + [XmlAttribute("pages")] + public int Pages { get; set; } + + [XmlAttribute("packed")] + public int Packed { get; set; } + + [XmlAttribute("alphaChnl")] + public int AlphaChannel { get; set; } + + [XmlAttribute("redChnl")] + public int RedChannel { get; set; } + + [XmlAttribute("greenChnl")] + public int GreenChannel { get; set; } + + [XmlAttribute("blueChnl")] + public int BlueChannel { get; set; } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontFile.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontFile.cs new file mode 100644 index 0000000..db77270 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontFile.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace MonoGame.Extended.Content.Pipeline.BitmapFonts +{ + // ---- AngelCode BmFont XML serializer ---------------------- + // ---- By DeadlyDan @ deadlydan@gmail.com ------------------- + // ---- There's no license restrictions, use as you will. ---- + // ---- Credits to http://www.angelcode.com/ ----------------- + [XmlRoot("font")] + public class BitmapFontFile + { + [XmlElement("info")] + public BitmapFontInfo Info { get; set; } + + [XmlElement("common")] + public BitmapFontCommon Common { get; set; } + + [XmlArray("pages")] + [XmlArrayItem("page")] + public List<BitmapFontPage> Pages { get; set; } + + [XmlArray("chars")] + [XmlArrayItem("char")] + public List<BitmapFontChar> Chars { get; set; } + + [XmlArray("kernings")] + [XmlArrayItem("kerning")] + public List<BitmapFontKerning> Kernings { get; set; } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontImporter.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontImporter.cs new file mode 100644 index 0000000..ea4c528 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontImporter.cs @@ -0,0 +1,22 @@ +using System.IO; +using System.Xml.Serialization; +using Microsoft.Xna.Framework.Content.Pipeline; + +namespace MonoGame.Extended.Content.Pipeline.BitmapFonts +{ + [ContentImporter(".fnt", DefaultProcessor = "BitmapFontProcessor", + DisplayName = "BMFont Importer - MonoGame.Extended")] + public class BitmapFontImporter : ContentImporter<BitmapFontFile> + { + public override BitmapFontFile Import(string filename, ContentImporterContext context) + { + context.Logger.LogMessage("Importing XML file: {0}", filename); + + using (var streamReader = new StreamReader(filename)) + { + var deserializer = new XmlSerializer(typeof(BitmapFontFile)); + return (BitmapFontFile)deserializer.Deserialize(streamReader); + } + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontInfo.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontInfo.cs new file mode 100644 index 0000000..1f50cf8 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontInfo.cs @@ -0,0 +1,47 @@ +using System.Xml.Serialization; + +namespace MonoGame.Extended.Content.Pipeline.BitmapFonts +{ + // ---- AngelCode BmFont XML serializer ---------------------- + // ---- By DeadlyDan @ deadlydan@gmail.com ------------------- + // ---- There's no license restrictions, use as you will. ---- + // ---- Credits to http://www.angelcode.com/ ----------------- + public class BitmapFontInfo + { + [XmlAttribute("face")] + public string Face { get; set; } + + [XmlAttribute("size")] + public int Size { get; set; } + + [XmlAttribute("bold")] + public int Bold { get; set; } + + [XmlAttribute("italic")] + public int Italic { get; set; } + + [XmlAttribute("charset")] + public string CharSet { get; set; } + + [XmlAttribute("unicode")] + public int Unicode { get; set; } + + [XmlAttribute("stretchH")] + public int StretchHeight { get; set; } + + [XmlAttribute("smooth")] + public int Smooth { get; set; } + + [XmlAttribute("aa")] + public int SuperSampling { get; set; } + + [XmlAttribute("padding")] + public string Padding { get; set; } + + [XmlAttribute("spacing")] + public string Spacing { get; set; } + + [XmlAttribute("outline")] + public int OutLine { get; set; } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontKerning.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontKerning.cs new file mode 100644 index 0000000..77caf13 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontKerning.cs @@ -0,0 +1,20 @@ +using System.Xml.Serialization; + +namespace MonoGame.Extended.Content.Pipeline.BitmapFonts +{ + // ---- AngelCode BmFont XML serializer ---------------------- + // ---- By DeadlyDan @ deadlydan@gmail.com ------------------- + // ---- There's no license restrictions, use as you will. ---- + // ---- Credits to http://www.angelcode.com/ ----------------- + public class BitmapFontKerning + { + [XmlAttribute("first")] + public int First { get; set; } + + [XmlAttribute("second")] + public int Second { get; set; } + + [XmlAttribute("amount")] + public int Amount { get; set; } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontPage.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontPage.cs new file mode 100644 index 0000000..3841ff5 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontPage.cs @@ -0,0 +1,17 @@ +using System.Xml.Serialization; + +namespace MonoGame.Extended.Content.Pipeline.BitmapFonts +{ + // ---- AngelCode BmFont XML serializer ---------------------- + // ---- By DeadlyDan @ deadlydan@gmail.com ------------------- + // ---- There's no license restrictions, use as you will. ---- + // ---- Credits to http://www.angelcode.com/ ----------------- + public class BitmapFontPage + { + [XmlAttribute("id")] + public int Id { get; set; } + + [XmlAttribute("file")] + public string File { get; set; } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontProcessor.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontProcessor.cs new file mode 100644 index 0000000..a859eb5 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontProcessor.cs @@ -0,0 +1,33 @@ +using System; +using System.IO; +using Microsoft.Xna.Framework.Content.Pipeline; + +namespace MonoGame.Extended.Content.Pipeline.BitmapFonts +{ + [ContentProcessor(DisplayName = "BMFont Processor - MonoGame.Extended")] + public class BitmapFontProcessor : ContentProcessor<BitmapFontFile, BitmapFontProcessorResult> + { + public override BitmapFontProcessorResult Process(BitmapFontFile bitmapFontFile, ContentProcessorContext context) + { + try + { + context.Logger.LogMessage("Processing BMFont"); + var result = new BitmapFontProcessorResult(bitmapFontFile); + + foreach (var fontPage in bitmapFontFile.Pages) + { + var assetName = Path.GetFileNameWithoutExtension(fontPage.File); + context.Logger.LogMessage("Expected texture asset: {0}", assetName); + result.TextureAssets.Add(assetName); + } + + return result; + } + catch (Exception ex) + { + context.Logger.LogMessage("Error {0}", ex); + throw; + } + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontProcessorResult.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontProcessorResult.cs new file mode 100644 index 0000000..5841acc --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontProcessorResult.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace MonoGame.Extended.Content.Pipeline.BitmapFonts +{ + public class BitmapFontProcessorResult + { + public List<string> TextureAssets { get; private set; } + public BitmapFontFile FontFile { get; private set; } + + public BitmapFontProcessorResult(BitmapFontFile fontFile) + { + FontFile = fontFile; + TextureAssets = new List<string>(); + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontWriter.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontWriter.cs new file mode 100644 index 0000000..343c40c --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontWriter.cs @@ -0,0 +1,52 @@ +using Microsoft.Xna.Framework.Content.Pipeline; +using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler; + +namespace MonoGame.Extended.Content.Pipeline.BitmapFonts +{ + [ContentTypeWriter] + public class BitmapFontWriter : ContentTypeWriter<BitmapFontProcessorResult> + { + protected override void Write(ContentWriter writer, BitmapFontProcessorResult result) + { + writer.Write(result.TextureAssets.Count); + + foreach (var textureAsset in result.TextureAssets) + writer.Write(textureAsset); + + var fontFile = result.FontFile; + writer.Write(fontFile.Common.LineHeight); + writer.Write(fontFile.Chars.Count); + + foreach (var c in fontFile.Chars) + { + writer.Write(c.Id); + writer.Write(c.Page); + writer.Write(c.X); + writer.Write(c.Y); + writer.Write(c.Width); + writer.Write(c.Height); + writer.Write(c.XOffset); + writer.Write(c.YOffset); + writer.Write(c.XAdvance); + } + + writer.Write(fontFile.Kernings.Count); + foreach(var k in fontFile.Kernings) + { + writer.Write(k.First); + writer.Write(k.Second); + writer.Write(k.Amount); + } + } + + public override string GetRuntimeType(TargetPlatform targetPlatform) + { + return "MonoGame.Extended.BitmapFonts.BitmapFont, MonoGame.Extended"; + } + + public override string GetRuntimeReader(TargetPlatform targetPlatform) + { + return "MonoGame.Extended.BitmapFonts.BitmapFontReader, MonoGame.Extended"; + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/ContentImporterContextExtensions.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/ContentImporterContextExtensions.cs new file mode 100644 index 0000000..7f63815 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/ContentImporterContextExtensions.cs @@ -0,0 +1,15 @@ +using System.IO; +using Microsoft.Xna.Framework.Content.Pipeline; + +namespace MonoGame.Extended.Content.Pipeline; + +public static class ContentImporterContextExtensions +{ + public static string AddDependencyWithLogging(this ContentImporterContext context, string filePath, string source) + { + source = Path.Combine(Path.GetDirectoryName(filePath), source); + ContentLogger.Log($"Adding dependency '{source}'"); + context.AddDependency(source); + return source; + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/ContentImporterResult.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/ContentImporterResult.cs new file mode 100644 index 0000000..e302ca7 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/ContentImporterResult.cs @@ -0,0 +1,14 @@ +namespace MonoGame.Extended.Content.Pipeline +{ + public class ContentImporterResult<T> + { + public ContentImporterResult(string filePath, T data) + { + FilePath = filePath; + Data = data; + } + + public string FilePath { get; } + public T Data { get; } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/ContentItem.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/ContentItem.cs new file mode 100644 index 0000000..e69d48e --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/ContentItem.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using Microsoft.Xna.Framework.Content.Pipeline; + +namespace MonoGame.Extended.Content.Pipeline +{ + public interface IExternalReferenceRepository + { + ExternalReference<TInput> GetExternalReference<TInput>(string source); + } + + public class ContentItem<T> : ContentItem, IExternalReferenceRepository + { + public ContentItem(T data) + { + Data = data; + } + + public T Data { get; } + + private readonly Dictionary<string, ContentItem> _externalReferences = new Dictionary<string, ContentItem>(); + + public void BuildExternalReference<TInput>(ContentProcessorContext context, string source, OpaqueDataDictionary parameters = null) + { + var sourceAsset = new ExternalReference<TInput>(source); + var externalReference = context.BuildAsset<TInput, TInput>(sourceAsset, "", parameters, "", ""); + _externalReferences.Add(source, externalReference); + } + + public ExternalReference<TInput> GetExternalReference<TInput>(string source) + { + if (source is not null && _externalReferences.TryGetValue(source, out var contentItem)) + return contentItem as ExternalReference<TInput>; + + return null; + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/ContentLogger.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/ContentLogger.cs new file mode 100644 index 0000000..83848a1 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/ContentLogger.cs @@ -0,0 +1,14 @@ +using Microsoft.Xna.Framework.Content.Pipeline; + +namespace MonoGame.Extended.Content.Pipeline +{ + public class ContentLogger + { + public static ContentBuildLogger Logger { get; set; } + + public static void Log(string message) + { + Logger?.LogMessage(message); + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/ContentWriterExtensions.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/ContentWriterExtensions.cs new file mode 100644 index 0000000..b8cf9d2 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/ContentWriterExtensions.cs @@ -0,0 +1,79 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler; + +namespace MonoGame.Extended.Content.Pipeline +{ + public static class ContentWriterExtensions + { + public static void Write(this ContentWriter contentWriter, Color value) + { + contentWriter.Write(value.R); + contentWriter.Write(value.G); + contentWriter.Write(value.B); + contentWriter.Write(value.A); + } + + public static void Write(this ContentWriter contentWriter, Matrix value) + { + contentWriter.Write(value.M11); + contentWriter.Write(value.M12); + contentWriter.Write(value.M13); + contentWriter.Write(value.M14); + contentWriter.Write(value.M21); + contentWriter.Write(value.M22); + contentWriter.Write(value.M23); + contentWriter.Write(value.M24); + contentWriter.Write(value.M31); + contentWriter.Write(value.M32); + contentWriter.Write(value.M33); + contentWriter.Write(value.M34); + contentWriter.Write(value.M41); + contentWriter.Write(value.M42); + contentWriter.Write(value.M43); + contentWriter.Write(value.M44); + } + + public static void Write(this ContentWriter contentWriter, Quaternion value) + { + contentWriter.Write(value.X); + contentWriter.Write(value.Y); + contentWriter.Write(value.Z); + contentWriter.Write(value.W); + } + + public static void Write(this ContentWriter contentWriter, Vector2 value) + { + contentWriter.Write(value.X); + contentWriter.Write(value.Y); + } + + public static void Write(this ContentWriter contentWriter, Vector3 value) + { + contentWriter.Write(value.X); + contentWriter.Write(value.Y); + contentWriter.Write(value.Z); + } + + public static void Write(this ContentWriter contentWriter, Vector4 value) + { + contentWriter.Write(value.X); + contentWriter.Write(value.Y); + contentWriter.Write(value.Z); + contentWriter.Write(value.W); + } + + public static void Write(this ContentWriter contentWriter, BoundingSphere value) + { + contentWriter.Write(value.Center); + contentWriter.Write(value.Radius); + } + + public static void Write(this ContentWriter contentWriter, Rectangle value) + { + contentWriter.Write(value.X); + contentWriter.Write(value.Y); + contentWriter.Write(value.Width); + contentWriter.Write(value.Height); + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Json/JsonContentImporter.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Json/JsonContentImporter.cs new file mode 100644 index 0000000..437d7eb --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Json/JsonContentImporter.cs @@ -0,0 +1,15 @@ +using System.IO; +using Microsoft.Xna.Framework.Content.Pipeline; + +namespace MonoGame.Extended.Content.Pipeline.Json +{ + [ContentImporter(".json", DefaultProcessor = nameof(JsonContentProcessor), DisplayName = "JSON Importer - MonoGame.Extended")] + public class JsonContentImporter : ContentImporter<ContentImporterResult<string>> + { + public override ContentImporterResult<string> Import(string filename, ContentImporterContext context) + { + var json = File.ReadAllText(filename); + return new ContentImporterResult<string>(filename, json); + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Json/JsonContentProcessor.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Json/JsonContentProcessor.cs new file mode 100644 index 0000000..6be4ac3 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Json/JsonContentProcessor.cs @@ -0,0 +1,31 @@ +using System; +using System.ComponentModel; +using Microsoft.Xna.Framework.Content.Pipeline; + +namespace MonoGame.Extended.Content.Pipeline.Json +{ + [ContentProcessor(DisplayName = "JSON Processor - MonoGame.Extended")] + public class JsonContentProcessor : ContentProcessor<ContentImporterResult<string>, JsonContentProcessorResult> + { + [DefaultValue(typeof(Type), "System.Object")] + public string ContentType { get; set; } + + public override JsonContentProcessorResult Process(ContentImporterResult<string> input, ContentProcessorContext context) + { + try + { + var output = new JsonContentProcessorResult + { + ContentType = ContentType, + Json = input.Data + }; + return output; + } + catch (Exception ex) + { + context.Logger.LogMessage("Error {0}", ex); + throw; + } + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Json/JsonContentProcessorResult.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Json/JsonContentProcessorResult.cs new file mode 100644 index 0000000..eaef99e --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Json/JsonContentProcessorResult.cs @@ -0,0 +1,8 @@ +namespace MonoGame.Extended.Content.Pipeline.Json +{ + public class JsonContentProcessorResult + { + public string ContentType { get; set; } + public string Json { get; set; } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Json/JsonContentTypeWriter.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Json/JsonContentTypeWriter.cs new file mode 100644 index 0000000..6efa696 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Json/JsonContentTypeWriter.cs @@ -0,0 +1,27 @@ +using Microsoft.Xna.Framework.Content.Pipeline; +using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler; + +namespace MonoGame.Extended.Content.Pipeline.Json +{ + [ContentTypeWriter] + public class JsonContentTypeWriter : ContentTypeWriter<JsonContentProcessorResult> + { + private string _runtimeType; + + protected override void Write(ContentWriter writer, JsonContentProcessorResult result) + { + _runtimeType = result.ContentType; + writer.Write(result.Json); + } + + public override string GetRuntimeReader(TargetPlatform targetPlatform) + { + return _runtimeType;// "MonoGame.Extended.Serialization.SpriteFactoryContentTypeReader, MonoGame.Extended"; + } + + public override string GetRuntimeType(TargetPlatform targetPlatform) + { + return _runtimeType;// "MonoGame.Extended.Serialization.SpriteFactoryContentTypeReader, MonoGame.Extended"; + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/MonoGame.Extended.Content.Pipeline.csproj b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/MonoGame.Extended.Content.Pipeline.csproj new file mode 100644 index 0000000..70b81e8 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/MonoGame.Extended.Content.Pipeline.csproj @@ -0,0 +1,25 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <ItemGroup> + <Content Include="$(ArtifactsPath)/bin/MonoGame.Extended.Content.Pipeline/release/*.dll" Pack="True" PackagePath="tools" /> + </ItemGroup> + + <PropertyGroup> + <Description>Content Pipeline importers and processors to make MonoGame more awesome.</Description> + <PackageTags>monogame content importer processor reader tiled texturepacker bmfont animations</PackageTags> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Autofac" Version="5.2.0" /> + + <PackageReference Include="MonoGame.Framework.Content.Pipeline" + Version="3.8.1.303" + PrivateAssets="All" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\MonoGame.Extended.Tiled\MonoGame.Extended.Tiled.csproj" /> + <ProjectReference Include="..\MonoGame.Extended\MonoGame.Extended.csproj" /> + </ItemGroup> + +</Project> diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/PathExtensions.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/PathExtensions.cs new file mode 100644 index 0000000..b85afb5 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/PathExtensions.cs @@ -0,0 +1,14 @@ +using System; +using System.IO; + +namespace MonoGame.Extended.Content.Pipeline +{ + public static class PathExtensions + { + public static string GetApplicationFullPath(params string[] pathParts) + { + var path = Path.Combine(pathParts); + return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, path); + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/SpriteFactory/SpriteFactoryContentImporter.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/SpriteFactory/SpriteFactoryContentImporter.cs new file mode 100644 index 0000000..3c4607d --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/SpriteFactory/SpriteFactoryContentImporter.cs @@ -0,0 +1,10 @@ +using Microsoft.Xna.Framework.Content.Pipeline; +using MonoGame.Extended.Content.Pipeline.Json; + +namespace MonoGame.Extended.Content.Pipeline.SpriteFactory +{ + [ContentImporter(".sf", DefaultProcessor = nameof(SpriteFactoryContentProcessor), DisplayName = "Sprite Factory Importer - MonoGame.Extended")] + public class SpriteFactoryContentImporter : JsonContentImporter + { + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/SpriteFactory/SpriteFactoryContentProcessor.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/SpriteFactory/SpriteFactoryContentProcessor.cs new file mode 100644 index 0000000..4920f33 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/SpriteFactory/SpriteFactoryContentProcessor.cs @@ -0,0 +1,15 @@ +using Microsoft.Xna.Framework.Content.Pipeline; +using MonoGame.Extended.Content.Pipeline.Json; + +namespace MonoGame.Extended.Content.Pipeline.SpriteFactory +{ + [ContentProcessor(DisplayName = "Sprite Factory Processor - MonoGame.Extended")] + public class SpriteFactoryContentProcessor : JsonContentProcessor + { + public SpriteFactoryContentProcessor() + { + ContentType = "MonoGame.Extended MonoGame.Extended.Animations.SpriteFactory.SpriteFactoryFileReader, MonoGame.Extended.Animations"; + } + + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/TextureAtlases/TexturePackerJsonImporter.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/TextureAtlases/TexturePackerJsonImporter.cs new file mode 100644 index 0000000..a80e654 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/TextureAtlases/TexturePackerJsonImporter.cs @@ -0,0 +1,18 @@ +using System.IO; +using System.Text.Json; +using Microsoft.Xna.Framework.Content.Pipeline; +using MonoGame.Extended.TextureAtlases; + + +namespace MonoGame.Extended.Content.Pipeline.TextureAtlases +{ + [ContentImporter(".json", DefaultProcessor = "TexturePackerProcessor", DisplayName = "TexturePacker JSON Importer - MonoGame.Extended")] + public class TexturePackerJsonImporter : ContentImporter<TexturePackerFile> + { + public override TexturePackerFile Import(string filename, ContentImporterContext context) + { + var json = File.ReadAllText(filename); + return JsonSerializer.Deserialize<TexturePackerFile>(json); + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/TextureAtlases/TexturePackerProcessor.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/TextureAtlases/TexturePackerProcessor.cs new file mode 100644 index 0000000..1f14ee7 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/TextureAtlases/TexturePackerProcessor.cs @@ -0,0 +1,24 @@ +using System; +using Microsoft.Xna.Framework.Content.Pipeline; +using MonoGame.Extended.TextureAtlases; + +namespace MonoGame.Extended.Content.Pipeline.TextureAtlases +{ + [ContentProcessor(DisplayName = "TexturePacker Processor - MonoGame.Extended")] + public class TexturePackerProcessor : ContentProcessor<TexturePackerFile, TexturePackerProcessorResult> + { + public override TexturePackerProcessorResult Process(TexturePackerFile input, ContentProcessorContext context) + { + try + { + var output = new TexturePackerProcessorResult {Data = input}; + return output; + } + catch (Exception ex) + { + context.Logger.LogMessage("Error {0}", ex); + throw; + } + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/TextureAtlases/TexturePackerProcessorResult.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/TextureAtlases/TexturePackerProcessorResult.cs new file mode 100644 index 0000000..a996259 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/TextureAtlases/TexturePackerProcessorResult.cs @@ -0,0 +1,9 @@ +using MonoGame.Extended.TextureAtlases; + +namespace MonoGame.Extended.Content.Pipeline.TextureAtlases +{ + public class TexturePackerProcessorResult + { + public TexturePackerFile Data { get; set; } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/TextureAtlases/TexturePackerWriter.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/TextureAtlases/TexturePackerWriter.cs new file mode 100644 index 0000000..3fd15ff --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/TextureAtlases/TexturePackerWriter.cs @@ -0,0 +1,45 @@ +using System.Diagnostics; +using System.IO; +using Microsoft.Xna.Framework.Content.Pipeline; +using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler; + +namespace MonoGame.Extended.Content.Pipeline.TextureAtlases +{ + [ContentTypeWriter] + public class TexturePackerWriter : ContentTypeWriter<TexturePackerProcessorResult> + { + protected override void Write(ContentWriter writer, TexturePackerProcessorResult result) + { + var data = result.Data; + var metadata = data.Metadata; + + var assetName = Path.GetFileNameWithoutExtension(metadata.Image); + Debug.Assert(assetName != null, "assetName != null"); + + writer.Write(assetName); + writer.Write(data.Regions.Count); + + foreach (var region in data.Regions) + { + var regionName = Path.ChangeExtension(region.Filename, null); + Debug.Assert(regionName != null, "regionName != null"); + + writer.Write(regionName); + writer.Write(region.Frame.X); + writer.Write(region.Frame.Y); + writer.Write(region.Frame.Width); + writer.Write(region.Frame.Height); + } + } + + public override string GetRuntimeType(TargetPlatform targetPlatform) + { + return "MonoGame.Extended.TextureAtlases.TextureAtlas, MonoGame.Extended"; + } + + public override string GetRuntimeReader(TargetPlatform targetPlatform) + { + return "MonoGame.Extended.TextureAtlases.TextureAtlasReader, MonoGame.Extended"; + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/ContentWriterExtensions.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/ContentWriterExtensions.cs new file mode 100644 index 0000000..28ded17 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/ContentWriterExtensions.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler; +using MonoGame.Extended.Tiled.Serialization; + +namespace MonoGame.Extended.Content.Pipeline.Tiled +{ + public static class ContentWriterExtensions + { + // ReSharper disable once SuggestBaseTypeForParameter + public static void WriteTiledMapProperties(this ContentWriter writer, IReadOnlyCollection<TiledMapPropertyContent> value) + { + if (value == null) + { + writer.Write(0); + return; + } + writer.Write(value.Count); + foreach (var property in value) + { + writer.Write(property.Name); + writer.Write(property.Value ?? string.Empty); + WriteTiledMapProperties(writer, property.Properties); + } + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledContentItem.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledContentItem.cs new file mode 100644 index 0000000..b376252 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledContentItem.cs @@ -0,0 +1,22 @@ +using Microsoft.Xna.Framework.Content.Pipeline; +using Microsoft.Xna.Framework.Content.Pipeline.Graphics; +using MonoGame.Extended.Tiled.Serialization; + +namespace MonoGame.Extended.Content.Pipeline.Tiled; + +public class TiledContentItem<T>: ContentItem<T> +{ + public TiledContentItem(T data) : base(data) + { + } + + public void BuildExternalReference<T>(ContentProcessorContext context, TiledMapImageContent image) + { + var parameters = new OpaqueDataDictionary + { + { "ColorKeyColor", image.TransparentColor }, + { "ColorKeyEnabled", true } + }; + BuildExternalReference<Texture2DContent>(context, image.Source, parameters); + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapContentItem.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapContentItem.cs new file mode 100644 index 0000000..b0b7b5d --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapContentItem.cs @@ -0,0 +1,12 @@ +using MonoGame.Extended.Tiled.Serialization; + +namespace MonoGame.Extended.Content.Pipeline.Tiled +{ + public class TiledMapContentItem : TiledContentItem<TiledMapContent> + { + public TiledMapContentItem(TiledMapContent data) + : base(data) + { + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapImporter.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapImporter.cs new file mode 100644 index 0000000..70e2bee --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapImporter.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml.Serialization; +using Microsoft.Xna.Framework.Content.Pipeline; +using MonoGame.Extended.Tiled.Serialization; + +namespace MonoGame.Extended.Content.Pipeline.Tiled +{ + [ContentImporter(".tmx", DefaultProcessor = "TiledMapProcessor", DisplayName = "Tiled Map Importer - MonoGame.Extended")] + public class TiledMapImporter : ContentImporter<TiledMapContentItem> + { + public override TiledMapContentItem Import(string filePath, ContentImporterContext context) + { + try + { + if (filePath == null) + throw new ArgumentNullException(nameof(filePath)); + + ContentLogger.Logger = context.Logger; + ContentLogger.Log($"Importing '{filePath}'"); + + var map = DeserializeTiledMapContent(filePath, context); + + if (map.Width > ushort.MaxValue || map.Height > ushort.MaxValue) + throw new InvalidContentException($"The map '{filePath} is much too large. The maximum supported width and height for a Tiled map is {ushort.MaxValue}."); + + ContentLogger.Log($"Imported '{filePath}'"); + + return new TiledMapContentItem(map); + + } + catch (Exception e) + { + context.Logger.LogImportantMessage(e.StackTrace); + throw; + } + } + + private static TiledMapContent DeserializeTiledMapContent(string mapFilePath, ContentImporterContext context) + { + using (var reader = new StreamReader(mapFilePath)) + { + var mapSerializer = new XmlSerializer(typeof(TiledMapContent)); + var map = (TiledMapContent)mapSerializer.Deserialize(reader); + + map.FilePath = mapFilePath; + + for (var i = 0; i < map.Tilesets.Count; i++) + { + var tileset = map.Tilesets[i]; + + string getTilesetSource(string source) + => Path.GetFullPath(Path.Combine(Path.GetDirectoryName(mapFilePath), source)); + + if (!string.IsNullOrWhiteSpace(tileset.Source)) + { + tileset.Source = getTilesetSource(tileset.Source); + ContentLogger.Log($"Adding dependency for {tileset.Source}"); + // We depend on the tileset. If the tileset changes, the map also needs to rebuild. + context.AddDependency(tileset.Source); + } + else + { + tileset.Image.Source = getTilesetSource(tileset.Image.Source); + ContentLogger.Log($"Adding dependency for {tileset.Image.Source}"); + context.AddDependency(tileset.Image.Source); + } + } + + ImportLayers(context, map.Layers, Path.GetDirectoryName(mapFilePath)); + + map.Name = mapFilePath; + return map; + } + } + + private static void ImportLayers(ContentImporterContext context, List<TiledMapLayerContent> layers, string path) + { + for (var i = 0; i < layers.Count; i++) + { + if (layers[i] is TiledMapImageLayerContent imageLayer) + { + imageLayer.Image.Source = Path.Combine(path, imageLayer.Image.Source); + ContentLogger.Log($"Adding dependency for '{imageLayer.Image.Source}'"); + + // Tell the pipeline that we depend on this image and need to rebuild the map if the image changes. + // (Maybe the image is a different size) + context.AddDependency(imageLayer.Image.Source); + } + if (layers[i] is TiledMapObjectLayerContent objectLayer) + foreach (var obj in objectLayer.Objects) + if (!String.IsNullOrWhiteSpace(obj.TemplateSource)) + { + obj.TemplateSource = Path.Combine(path, obj.TemplateSource); + ContentLogger.Log($"Adding dependency for '{obj.TemplateSource}'"); + // Tell the pipeline that we depend on this template and need to rebuild the map if the template changes. + // (Templates are loaded into objects on process, so all objects which depend on the template file + // need the change to the template) + context.AddDependency(obj.TemplateSource); + } + if (layers[i] is TiledMapGroupLayerContent groupLayer) + // Yay recursion! + ImportLayers(context, groupLayer.Layers, path); + } + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapObjectTemplateImporter.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapObjectTemplateImporter.cs new file mode 100644 index 0000000..eeddcaa --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapObjectTemplateImporter.cs @@ -0,0 +1,54 @@ +using Microsoft.Xna.Framework.Content.Pipeline; +using System; +using System.IO; +using System.Xml.Serialization; +using MonoGame.Extended.Tiled.Serialization; + +namespace MonoGame.Extended.Content.Pipeline.Tiled +{ + [ContentImporter(".tx", DefaultProcessor = "TiledMapObjectTemplateProcessor", DisplayName = "Tiled Map Object Template Importer - MonoGame.Extended")] + public class TiledMapObjectTemplateImporter : ContentImporter<TiledMapObjectTemplateContent> + { + public override TiledMapObjectTemplateContent Import(string filePath, ContentImporterContext context) + { + try + { + if (filePath == null) + throw new ArgumentNullException(nameof(filePath)); + + ContentLogger.Logger = context.Logger; + ContentLogger.Log($"Importing '{filePath}'"); + + var template = DeserializeTileMapObjectTemplateContent(filePath, context); + + ContentLogger.Log($"Imported '{filePath}'"); + + return template; + } + catch (Exception e) + { + context.Logger.LogImportantMessage(e.StackTrace); + return null; + } + } + + private static TiledMapObjectTemplateContent DeserializeTileMapObjectTemplateContent(string filePath, ContentImporterContext context) + { + using (var reader = new StreamReader(filePath)) + { + var templateSerializer = new XmlSerializer(typeof(TiledMapObjectTemplateContent)); + var template = (TiledMapObjectTemplateContent)templateSerializer.Deserialize(reader); + + if (!string.IsNullOrWhiteSpace(template.Tileset?.Source)) + { + template.Tileset.Source = Path.Combine(Path.GetDirectoryName(filePath), template.Tileset.Source); + ContentLogger.Log($"Adding dependency '{template.Tileset.Source}'"); + // We depend on this tileset. + context.AddDependency(template.Tileset.Source); + } + + return template; + } + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapProcessor.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapProcessor.cs new file mode 100644 index 0000000..9668a72 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapProcessor.cs @@ -0,0 +1,324 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.Xna.Framework.Content.Pipeline; +using Microsoft.Xna.Framework.Content.Pipeline.Graphics; +using MonoGame.Extended.Tiled; +using MonoGame.Extended.Tiled.Serialization; +using MonoGame.Framework.Utilities.Deflate; +using CompressionMode = System.IO.Compression.CompressionMode; +using GZipStream = System.IO.Compression.GZipStream; + +namespace MonoGame.Extended.Content.Pipeline.Tiled +{ + public static class TiledMapContentHelper + { + public static void Process(TiledMapObjectContent obj, ContentProcessorContext context) + { + if (!string.IsNullOrWhiteSpace(obj.TemplateSource)) + { + var externalReference = new ExternalReference<TiledMapObjectLayerContent>(obj.TemplateSource); + var template = context.BuildAndLoadAsset<TiledMapObjectLayerContent, TiledMapObjectTemplateContent>(externalReference, ""); + + // Nothing says a template can't reference another template. + // Yay recusion! + Process(template.Object, context); + + if (!obj._globalIdentifier.HasValue && template.Object._globalIdentifier.HasValue) + obj.GlobalIdentifier = template.Object.GlobalIdentifier; + + if (!obj._height.HasValue && template.Object._height.HasValue) + obj.Height = template.Object.Height; + + if (!obj._identifier.HasValue && template.Object._identifier.HasValue) + obj.Identifier = template.Object.Identifier; + + if (!obj._rotation.HasValue && template.Object._rotation.HasValue) + obj.Rotation = template.Object.Rotation; + + if (!obj._visible.HasValue && template.Object._visible.HasValue) + obj.Visible = template.Object.Visible; + + if (!obj._width.HasValue && template.Object._width.HasValue) + obj.Width = template.Object.Width; + + if (!obj._x.HasValue && template.Object._x.HasValue) + obj.X = template.Object.X; + + if (!obj._y.HasValue && template.Object._y.HasValue) + obj.Y = template.Object.Y; + + if (obj.Ellipse == null && template.Object.Ellipse != null) + obj.Ellipse = template.Object.Ellipse; + + if (string.IsNullOrWhiteSpace(obj.Name) && !string.IsNullOrWhiteSpace(template.Object.Name)) + obj.Name = template.Object.Name; + + if (obj.Polygon == null && template.Object.Polygon != null) + obj.Polygon = template.Object.Polygon; + + if (obj.Polyline == null && template.Object.Polyline != null) + obj.Polyline = template.Object.Polyline; + + foreach (var tProperty in template.Object.Properties) + { + if (!obj.Properties.Exists(p => p.Name == tProperty.Name)) + obj.Properties.Add(tProperty); + } + + if (string.IsNullOrWhiteSpace(obj.Type) && !string.IsNullOrWhiteSpace(template.Object.Type)) + obj.Type = template.Object.Type; + + if (string.IsNullOrWhiteSpace(obj.Class) && !string.IsNullOrWhiteSpace(template.Object.Class)) + obj.Class = template.Object.Class; + } + } + } + + + [ContentProcessor(DisplayName = "Tiled Map Processor - MonoGame.Extended")] + public class TiledMapProcessor : ContentProcessor<TiledMapContentItem, TiledMapContentItem> + { + public override TiledMapContentItem Process(TiledMapContentItem contentItem, ContentProcessorContext context) + { + try + { + ContentLogger.Logger = context.Logger; + var map = contentItem.Data; + + if (map.Orientation == TiledMapOrientationContent.Hexagonal || map.Orientation == TiledMapOrientationContent.Staggered) + throw new NotSupportedException($"{map.Orientation} Tiled Maps are currently not implemented!"); + + foreach (var tileset in map.Tilesets) + { + if (string.IsNullOrWhiteSpace(tileset.Source)) + { + // Load the Texture2DContent for the tileset as it will be saved into the map content file. + contentItem.BuildExternalReference<Texture2DContent>(context, tileset.Image); + } + else + { + // Link to the tileset for the content loader to load at runtime. + //var externalReference = new ExternalReference<TiledMapTilesetContent>(tileset.Source); + //tileset.Content = context.BuildAsset<TiledMapTilesetContent, TiledMapTilesetContent>(externalReference, ""); + contentItem.BuildExternalReference<TiledMapTilesetContent>(context, tileset.Source); + } + } + + ProcessLayers(contentItem, map, context, map.Layers); + + return contentItem; + } + catch (Exception ex) + { + context.Logger.LogImportantMessage(ex.Message); + throw; + } + } + + private static void ProcessLayers(TiledMapContentItem contentItem, TiledMapContent map, ContentProcessorContext context, List<TiledMapLayerContent> layers) + { + foreach (var layer in layers) + { + switch (layer) + { + case TiledMapImageLayerContent imageLayer: + ContentLogger.Log($"Processing image layer '{imageLayer.Name}'"); + contentItem.BuildExternalReference<Texture2DContent>(context, imageLayer.Image); + ContentLogger.Log($"Processed image layer '{imageLayer.Name}'"); + break; + + case TiledMapTileLayerContent tileLayer when tileLayer.Data.Chunks.Count > 0: + throw new NotSupportedException($"{map.FilePath} contains data chunks. These are currently not supported."); + + case TiledMapTileLayerContent tileLayer: + var data = tileLayer.Data; + var encodingType = data.Encoding ?? "xml"; + var compressionType = data.Compression ?? "xml"; + + ContentLogger.Log($"Processing tile layer '{tileLayer.Name}': Encoding: '{encodingType}', Compression: '{compressionType}'"); + var tileData = DecodeTileLayerData(encodingType, tileLayer); + var tiles = CreateTiles(map.RenderOrder, map.Width, map.Height, tileData); + tileLayer.Tiles = tiles; + ContentLogger.Log($"Processed tile layer '{tileLayer}': {tiles.Length} tiles"); + break; + + case TiledMapObjectLayerContent objectLayer: + ContentLogger.Log($"Processing object layer '{objectLayer.Name}'"); + + foreach (var obj in objectLayer.Objects) + TiledMapContentHelper.Process(obj, context); + + ContentLogger.Log($"Processed object layer '{objectLayer.Name}'"); + break; + + case TiledMapGroupLayerContent groupLayer: + ProcessLayers(contentItem, map, context, groupLayer.Layers); + break; + } + } + } + + private static List<TiledMapTileContent> DecodeTileLayerData(string encodingType, TiledMapTileLayerContent tileLayer) + { + List<TiledMapTileContent> tiles; + + switch (encodingType) + { + case "xml": + tiles = tileLayer.Data.Tiles; + break; + case "csv": + tiles = DecodeCommaSeperatedValuesData(tileLayer.Data); + break; + case "base64": + tiles = DecodeBase64Data(tileLayer.Data, tileLayer.Width, tileLayer.Height); + break; + default: + throw new NotSupportedException($"The tile layer encoding '{encodingType}' is not supported."); + } + + return tiles; + } + + private static TiledMapTile[] CreateTiles(TiledMapTileDrawOrderContent renderOrder, int mapWidth, int mapHeight, List<TiledMapTileContent> tileData) + { + TiledMapTile[] tiles; + + switch (renderOrder) + { + case TiledMapTileDrawOrderContent.LeftDown: + tiles = CreateTilesInLeftDownOrder(tileData, mapWidth, mapHeight).ToArray(); + break; + case TiledMapTileDrawOrderContent.LeftUp: + tiles = CreateTilesInLeftUpOrder(tileData, mapWidth, mapHeight).ToArray(); + break; + case TiledMapTileDrawOrderContent.RightDown: + tiles = CreateTilesInRightDownOrder(tileData, mapWidth, mapHeight).ToArray(); + break; + case TiledMapTileDrawOrderContent.RightUp: + tiles = CreateTilesInRightUpOrder(tileData, mapWidth, mapHeight).ToArray(); + break; + default: + throw new NotSupportedException($"{renderOrder} is not supported."); + } + + return tiles.ToArray(); + } + + private static IEnumerable<TiledMapTile> CreateTilesInLeftDownOrder(List<TiledMapTileContent> tileLayerData, int mapWidth, int mapHeight) + { + for (var y = 0; y < mapHeight; y++) + { + for (var x = mapWidth - 1; x >= 0; x--) + { + var dataIndex = x + y * mapWidth; + var globalIdentifier = tileLayerData[dataIndex].GlobalIdentifier; + if (globalIdentifier == 0) + continue; + var tile = new TiledMapTile(globalIdentifier, (ushort)x, (ushort)y); + yield return tile; + } + } + } + + private static IEnumerable<TiledMapTile> CreateTilesInLeftUpOrder(List<TiledMapTileContent> tileLayerData, int mapWidth, int mapHeight) + { + for (var y = mapHeight - 1; y >= 0; y--) + { + for (var x = mapWidth - 1; x >= 0; x--) + { + var dataIndex = x + y * mapWidth; + var globalIdentifier = tileLayerData[dataIndex].GlobalIdentifier; + if (globalIdentifier == 0) + continue; + var tile = new TiledMapTile(globalIdentifier, (ushort)x, (ushort)y); + yield return tile; + } + } + } + + private static IEnumerable<TiledMapTile> CreateTilesInRightDownOrder(List<TiledMapTileContent> tileLayerData, int mapWidth, int mapHeight) + { + for (var y = 0; y < mapHeight; y++) + { + for (var x = 0; x < mapWidth; x++) + { + var dataIndex = x + y * mapWidth; + var globalIdentifier = tileLayerData[dataIndex].GlobalIdentifier; + if (globalIdentifier == 0) + continue; + var tile = new TiledMapTile(globalIdentifier, (ushort)x, (ushort)y); + yield return tile; + } + } + } + + private static IEnumerable<TiledMapTile> CreateTilesInRightUpOrder(List<TiledMapTileContent> tileLayerData, int mapWidth, int mapHeight) + { + for (var y = mapHeight - 1; y >= 0; y--) + { + for (var x = mapWidth - 1; x >= 0; x--) + { + var dataIndex = x + y * mapWidth; + var globalIdentifier = tileLayerData[dataIndex].GlobalIdentifier; + if (globalIdentifier == 0) + continue; + var tile = new TiledMapTile(globalIdentifier, (ushort)x, (ushort)y); + yield return tile; + } + } + } + + private static List<TiledMapTileContent> DecodeBase64Data(TiledMapTileLayerDataContent data, int width, int height) + { + var tileList = new List<TiledMapTileContent>(); + var encodedData = data.Value.Trim(); + var decodedData = Convert.FromBase64String(encodedData); + + using (var stream = OpenStream(decodedData, data.Compression)) + { + using (var reader = new BinaryReader(stream)) + { + data.Tiles = new List<TiledMapTileContent>(); + + for (var y = 0; y < width; y++) + { + for (var x = 0; x < height; x++) + { + var gid = reader.ReadUInt32(); + tileList.Add(new TiledMapTileContent + { + GlobalIdentifier = gid + }); + } + } + } + } + + return tileList; + } + + private static List<TiledMapTileContent> DecodeCommaSeperatedValuesData(TiledMapTileLayerDataContent data) + { + return data.Value + .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(uint.Parse) + .Select(x => new TiledMapTileContent { GlobalIdentifier = x }) + .ToList(); + } + + private static Stream OpenStream(byte[] decodedData, string compressionMode) + { + var memoryStream = new MemoryStream(decodedData, false); + + return compressionMode switch + { + "gzip" => new GZipStream(memoryStream, CompressionMode.Decompress), + "zlib" => new ZlibStream(memoryStream, Framework.Utilities.Deflate.CompressionMode.Decompress), + _ => memoryStream + }; + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapTilesetContentItem.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapTilesetContentItem.cs new file mode 100644 index 0000000..d4b2221 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapTilesetContentItem.cs @@ -0,0 +1,14 @@ +using Microsoft.Xna.Framework.Content.Pipeline; +using Microsoft.Xna.Framework.Content.Pipeline.Graphics; +using MonoGame.Extended.Tiled.Serialization; + +namespace MonoGame.Extended.Content.Pipeline.Tiled +{ + public class TiledMapTilesetContentItem : TiledContentItem<TiledMapTilesetContent> + { + public TiledMapTilesetContentItem(TiledMapTilesetContent data) + : base(data) + { + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapTilesetImporter.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapTilesetImporter.cs new file mode 100644 index 0000000..c848bde --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapTilesetImporter.cs @@ -0,0 +1,60 @@ +using Microsoft.Xna.Framework.Content.Pipeline; +using System; +using System.IO; +using System.Xml.Serialization; +using MonoGame.Extended.Tiled.Serialization; + +namespace MonoGame.Extended.Content.Pipeline.Tiled +{ + [ContentImporter(".tsx", DefaultProcessor = "TiledMapTilesetProcessor", DisplayName = "Tiled Map Tileset Importer - MonoGame.Extended")] + public class TiledMapTilesetImporter : ContentImporter<TiledMapTilesetContentItem> + { + public override TiledMapTilesetContentItem Import(string filePath, ContentImporterContext context) + { + try + { + if (filePath == null) + throw new ArgumentNullException(nameof(filePath)); + + ContentLogger.Logger = context.Logger; + ContentLogger.Log($"Importing '{filePath}'"); + + var tileset = DeserializeTiledMapTilesetContent(filePath, context); + + ContentLogger.Log($"Imported '{filePath}'"); + + return new TiledMapTilesetContentItem(tileset); + } + catch (Exception e) + { + context.Logger.LogImportantMessage(e.StackTrace); + throw; + } + } + + private TiledMapTilesetContent DeserializeTiledMapTilesetContent(string filePath, ContentImporterContext context) + { + using (var reader = new StreamReader(filePath)) + { + var tilesetSerializer = new XmlSerializer(typeof(TiledMapTilesetContent)); + var tileset = (TiledMapTilesetContent)tilesetSerializer.Deserialize(reader); + + if (tileset.Image is not null) + tileset.Image.Source = context.AddDependencyWithLogging(filePath, tileset.Image.Source); + + foreach (var tile in tileset.Tiles) + { + foreach (var obj in tile.Objects) + { + if (!string.IsNullOrWhiteSpace(obj.TemplateSource)) + obj.TemplateSource = context.AddDependencyWithLogging(filePath, obj.TemplateSource); + } + if (tile.Image is not null) + tile.Image.Source = context.AddDependencyWithLogging(filePath, tile.Image.Source); + } + + return tileset; + } + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapTilesetProcessor.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapTilesetProcessor.cs new file mode 100644 index 0000000..5682602 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapTilesetProcessor.cs @@ -0,0 +1,44 @@ +using Microsoft.Xna.Framework.Content.Pipeline; +using Microsoft.Xna.Framework.Content.Pipeline.Graphics; +using System; + +namespace MonoGame.Extended.Content.Pipeline.Tiled +{ + [ContentProcessor(DisplayName = "Tiled Map Tileset Processor - MonoGame.Extended")] + public class TiledMapTilesetProcessor : ContentProcessor<TiledMapTilesetContentItem, TiledMapTilesetContentItem> + { + public override TiledMapTilesetContentItem Process(TiledMapTilesetContentItem contentItem, ContentProcessorContext context) + { + try + { + var tileset = contentItem.Data; + + ContentLogger.Logger = context.Logger; + ContentLogger.Log($"Processing tileset '{tileset.Name}'"); + + // Build the Texture2D asset and load it as it will be saved as part of this tileset file. + if (tileset.Image is not null) + contentItem.BuildExternalReference<Texture2DContent>(context, tileset.Image); + + foreach (var tile in tileset.Tiles) + { + foreach (var obj in tile.Objects) + { + TiledMapContentHelper.Process(obj, context); + } + if (tile.Image is not null) + contentItem.BuildExternalReference<Texture2DContent>(context, tile.Image); + } + + ContentLogger.Log($"Processed tileset '{tileset.Name}'"); + + return contentItem; + } + catch (Exception ex) + { + context.Logger.LogImportantMessage(ex.Message); + throw ex; + } + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapTilesetWriter.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapTilesetWriter.cs new file mode 100644 index 0000000..48690ad --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapTilesetWriter.cs @@ -0,0 +1,145 @@ +using Microsoft.Xna.Framework.Content.Pipeline; +using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler; +using MonoGame.Extended.Tiled; +using System; +using System.Globalization; +using Microsoft.Xna.Framework.Content.Pipeline.Graphics; +using MonoGame.Extended.Tiled.Serialization; + +namespace MonoGame.Extended.Content.Pipeline.Tiled +{ + [ContentTypeWriter] + public class TiledMapTilesetWriter : ContentTypeWriter<TiledMapTilesetContentItem> + { + public override string GetRuntimeReader(TargetPlatform targetPlatform) => "MonoGame.Extended.Tiled.TiledMapTilesetReader, MonoGame.Extended.Tiled"; + + public override string GetRuntimeType(TargetPlatform targetPlatform) => "MonoGame.Extended.Tiled.TiledMapTileset, MonoGame.Extended.Tiled"; + + protected override void Write(ContentWriter writer, TiledMapTilesetContentItem contentItem) + { + try + { + WriteTileset(writer, contentItem.Data, contentItem); + } + catch (Exception ex) + { + ContentLogger.Logger.LogImportantMessage(ex.StackTrace); + throw; + } + } + + public static void WriteTileset(ContentWriter writer, TiledMapTilesetContent tileset, IExternalReferenceRepository externalReferenceRepository) + { + var externalReference = externalReferenceRepository.GetExternalReference<Texture2DContent>(tileset.Image?.Source); + writer.WriteExternalReference(externalReference); + writer.Write(tileset.Class ?? tileset.Type ?? string.Empty); + writer.Write(tileset.TileWidth); + writer.Write(tileset.TileHeight); + writer.Write(tileset.TileCount); + writer.Write(tileset.Spacing); + writer.Write(tileset.Margin); + writer.Write(tileset.Columns); + writer.Write(tileset.Tiles.Count); + + foreach (var tilesetTile in tileset.Tiles) + WriteTilesetTile(writer, tilesetTile, externalReferenceRepository); + + writer.WriteTiledMapProperties(tileset.Properties); + } + + private static void WriteTilesetTile(ContentWriter writer, TiledMapTilesetTileContent tilesetTile, + IExternalReferenceRepository externalReferenceRepository) + { + var externalReference = externalReferenceRepository.GetExternalReference<Texture2DContent>(tilesetTile.Image?.Source); + writer.WriteExternalReference(externalReference); + + writer.Write(tilesetTile.LocalIdentifier); + writer.Write(tilesetTile.Type); + writer.Write(tilesetTile.Frames.Count); + writer.Write(tilesetTile.Objects.Count); + + foreach (var @object in tilesetTile.Objects) + WriteObject(writer, @object); + + foreach (var frame in tilesetTile.Frames) + { + writer.Write(frame.TileIdentifier); + writer.Write(frame.Duration); + } + + writer.WriteTiledMapProperties(tilesetTile.Properties); + } + + private static void WriteObject(ContentWriter writer, TiledMapObjectContent @object) + { + var type = GetObjectType(@object); + + writer.Write((byte)type); + + writer.Write(@object.Identifier); + writer.Write(@object.Name ?? string.Empty); + writer.Write(@object.Class ?? @object.Type ?? string.Empty); + writer.Write(@object.X); + writer.Write(@object.Y); + writer.Write(@object.Width); + writer.Write(@object.Height); + writer.Write(@object.Rotation); + writer.Write(@object.Visible); + + writer.WriteTiledMapProperties(@object.Properties); + + switch (type) + { + case TiledMapObjectType.Rectangle: + case TiledMapObjectType.Ellipse: + break; + case TiledMapObjectType.Tile: + writer.Write(@object.GlobalIdentifier); + break; + case TiledMapObjectType.Polygon: + WritePolyPoints(writer, @object.Polygon.Points); + break; + case TiledMapObjectType.Polyline: + WritePolyPoints(writer, @object.Polyline.Points); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + // ReSharper disable once SuggestBaseTypeForParameter + private static void WritePolyPoints(ContentWriter writer, string @string) + { + var stringPoints = @string.Split(' '); + + writer.Write(stringPoints.Length); + + foreach (var stringPoint in stringPoints) + { + var xy = stringPoint.Split(','); + var x = float.Parse(xy[0], CultureInfo.InvariantCulture.NumberFormat); + writer.Write(x); + var y = float.Parse(xy[1], CultureInfo.InvariantCulture.NumberFormat); + writer.Write(y); + } + } + + public static TiledMapObjectType GetObjectType(TiledMapObjectContent content) + { + if (content.GlobalIdentifier > 0) + return TiledMapObjectType.Tile; + + if (content.Ellipse != null) + return TiledMapObjectType.Ellipse; + + if (content.Polygon != null) + return TiledMapObjectType.Polygon; + + // ReSharper disable once ConvertIfStatementToReturnStatement + if (content.Polyline != null) + return TiledMapObjectType.Polyline; + + return TiledMapObjectType.Rectangle; + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapWriter.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapWriter.cs new file mode 100644 index 0000000..126debb --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapWriter.cs @@ -0,0 +1,227 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content.Pipeline; +using Microsoft.Xna.Framework.Content.Pipeline.Graphics; +using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler; +using MonoGame.Extended.Tiled; +using MonoGame.Extended.Tiled.Serialization; + +namespace MonoGame.Extended.Content.Pipeline.Tiled +{ + [ContentTypeWriter] + public class TiledMapWriter : ContentTypeWriter<TiledMapContentItem> + { + private TiledMapContentItem _contentItem; + + protected override void Write(ContentWriter writer, TiledMapContentItem contentItem) + { + _contentItem = contentItem; + + var map = contentItem.Data; + + try + { + WriteMetaData(writer, map); + WriteTilesets(writer, map.Tilesets); + WriteLayers(writer, map.Layers); + } + catch (Exception ex) + { + ContentLogger.Logger.LogImportantMessage(ex.StackTrace); + throw; + } + } + + private static void WriteMetaData(ContentWriter writer, TiledMapContent map) + { + writer.Write(map.Class ?? map.Type ?? string.Empty); + writer.Write(map.Width); + writer.Write(map.Height); + writer.Write(map.TileWidth); + writer.Write(map.TileHeight); + writer.Write(ColorHelper.FromHex(map.BackgroundColor)); + writer.Write((byte)map.RenderOrder); + writer.Write((byte)map.Orientation); + writer.WriteTiledMapProperties(map.Properties); + } + + private void WriteTilesets(ContentWriter writer, IReadOnlyCollection<TiledMapTilesetContent> tilesets) + { + writer.Write(tilesets.Count); + + foreach (var tileset in tilesets) + WriteTileset(writer, tileset); + } + + private void WriteTileset(ContentWriter writer, TiledMapTilesetContent tileset) + { + writer.Write(tileset.FirstGlobalIdentifier); + + if (!string.IsNullOrWhiteSpace(tileset.Source)) + { + writer.Write(true); + writer.WriteExternalReference(_contentItem.GetExternalReference<TiledMapTilesetContent>(tileset.Source)); + } + else + { + writer.Write(false); + TiledMapTilesetWriter.WriteTileset(writer, tileset, _contentItem); + } + } + + private void WriteLayers(ContentWriter writer, IReadOnlyCollection<TiledMapLayerContent> layers) + { + writer.Write(layers.Count); + + foreach (var layer in layers) + WriteLayer(writer, layer); + } + + private void WriteLayer(ContentWriter writer, TiledMapLayerContent layer) + { + writer.Write((byte)layer.LayerType); + + writer.Write(layer.Name ?? string.Empty); + writer.Write(layer.Class ?? layer.Type ?? string.Empty); + writer.Write(layer.Visible); + writer.Write(layer.Opacity); + writer.Write(layer.OffsetX); + writer.Write(layer.OffsetY); + writer.Write(layer.ParallaxX); + writer.Write(layer.ParallaxY); + + writer.WriteTiledMapProperties(layer.Properties); + + switch (layer.LayerType) + { + case TiledMapLayerType.ImageLayer: + WriteImageLayer(writer, (TiledMapImageLayerContent)layer); + break; + case TiledMapLayerType.TileLayer: + WriteTileLayer(writer, (TiledMapTileLayerContent)layer); + break; + case TiledMapLayerType.ObjectLayer: + WriteObjectLayer(writer, (TiledMapObjectLayerContent)layer); + break; + case TiledMapLayerType.GroupLayer: + WriteLayers(writer, ((TiledMapGroupLayerContent)layer).Layers); + break; + default: + throw new ArgumentOutOfRangeException(nameof(layer.LayerType)); + } + } + + private void WriteImageLayer(ContentWriter writer, TiledMapImageLayerContent imageLayer) + { + var externalReference = _contentItem.GetExternalReference<Texture2DContent>(imageLayer.Image.Source); + writer.WriteExternalReference(externalReference); + writer.Write(new Vector2(imageLayer.X, imageLayer.Y)); + } + + // ReSharper disable once SuggestBaseTypeForParameter + private static void WriteTileLayer(ContentWriter writer, TiledMapTileLayerContent tileLayer) + { + writer.Write(tileLayer.Width); + writer.Write(tileLayer.Height); + + writer.Write(tileLayer.Tiles.Length); + + foreach (var tile in tileLayer.Tiles) + { + writer.Write(tile.GlobalTileIdentifierWithFlags); + writer.Write(tile.X); + writer.Write(tile.Y); + } + } + + private static void WriteObjectLayer(ContentWriter writer, TiledMapObjectLayerContent layer) + { + writer.Write(ColorHelper.FromHex(layer.Color)); + writer.Write((byte)layer.DrawOrder); + + writer.Write(layer.Objects.Count); + + foreach (var @object in layer.Objects) + WriteObject(writer, @object); + } + + + private static void WriteObject(ContentWriter writer, TiledMapObjectContent @object) + { + var type = GetObjectType(@object); + + writer.Write((byte)type); + + writer.Write(@object.Identifier); + writer.Write(@object.Name ?? string.Empty); + writer.Write(@object.Class ?? @object.Type ?? string.Empty); + writer.Write(@object.X); + writer.Write(@object.Y); + writer.Write(@object.Width); + writer.Write(@object.Height); + writer.Write(@object.Rotation); + writer.Write(@object.Visible); + + writer.WriteTiledMapProperties(@object.Properties); + + switch (type) + { + case TiledMapObjectType.Rectangle: + case TiledMapObjectType.Ellipse: + break; + case TiledMapObjectType.Tile: + writer.Write(@object.GlobalIdentifier); + break; + case TiledMapObjectType.Polygon: + WritePolyPoints(writer, @object.Polygon.Points); + break; + case TiledMapObjectType.Polyline: + WritePolyPoints(writer, @object.Polyline.Points); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + // ReSharper disable once SuggestBaseTypeForParameter + private static void WritePolyPoints(ContentWriter writer, string @string) + { + var stringPoints = @string.Split(' '); + + writer.Write(stringPoints.Length); + + foreach (var stringPoint in stringPoints) + { + var xy = stringPoint.Split(','); + var x = float.Parse(xy[0], CultureInfo.InvariantCulture.NumberFormat); + writer.Write(x); + var y = float.Parse(xy[1], CultureInfo.InvariantCulture.NumberFormat); + writer.Write(y); + } + } + + public static TiledMapObjectType GetObjectType(TiledMapObjectContent content) + { + if (content.GlobalIdentifier > 0) + return TiledMapObjectType.Tile; + + if (content.Ellipse != null) + return TiledMapObjectType.Ellipse; + + if (content.Polygon != null) + return TiledMapObjectType.Polygon; + + // ReSharper disable once ConvertIfStatementToReturnStatement + if (content.Polyline != null) + return TiledMapObjectType.Polyline; + + return TiledMapObjectType.Rectangle; + } + + public override string GetRuntimeType(TargetPlatform targetPlatform) => "MonoGame.Extended.Tiled.TiledMap, MonoGame.Extended.Tiled"; + + public override string GetRuntimeReader(TargetPlatform targetPlatform) => "MonoGame.Extended.Tiled.TiledMapReader, MonoGame.Extended.Tiled"; + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/readme.txt b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/readme.txt new file mode 100644 index 0000000..54f1cda --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/readme.txt @@ -0,0 +1,15 @@ +# MonoGame.Extended.Content.Pipeline + +This NuGet package is intended to be used with the MonoGame Content Pipeline tool. You'll need to +make some changes to your Content Pipline file (usually Content.mgcb) for everything to work +properly. + +Add a reference to the `MonoGame.Extended.Content.Pipeline.dll` now installed in your `packages` folder +to the [MonoGame Content Pipeline tool](http://www.monogame.net/documentation/?page=Pipeline). + +You can do this either with the Reference Editor in the GUI or by manually adding a reference line to +your `Content.mgcb` file using a text editor. + +**Remember**: the versions need to match exactly for everything to work. + +For more information, take a look at the [installation guide](http://craftworkgames.github.io/MonoGame.Extended/installation/). |