Thank you nkast.
The last bit is what i was missing as well as a reference to … pipeline… graphics which had me stumped for hours. Well that was extremely hard to get working.
Might as well post up the whole thing for reference for the next person.
So here they are in sequence if you see anything im doing wrong here let me know.
Importer
Grabs the source rectangles .spr description file for the texture that represents our spritesheet with the same name and reads them.
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
namespace SpriteSheetPipeLine
{
///
/// ContentImporter how we tell the processor to import files from the content folder.
/// ContentImporter(".spr", This tells the Pipeline tool to use this content importer for files with the .spr extension which ill be generating.
/// It also tells the Pipeline tool to use SpriteSheetProcessor as the default processor.
///
[ContentImporter(".spr", DefaultProcessor = "SpriteSheetProcessor", DisplayName = "SpriteSheetImporter")]
public class SpriteSheetImporter : ContentImporter<SpriteSheetContent>
{
public override SpriteSheetContent Import(string filename, ContentImporterContext context)
{
// Debug line that shows up in the Pipeline tool or your build output window.
context.Logger.LogMessage("Importing SpriteSheet file: {0}", filename);
SpriteSheetContent ssc = new SpriteSheetContent();
using (BinaryReader input = new BinaryReader(File.OpenRead(filename)))
{
ssc.name = input.ReadString();
ssc.sheetWidth = input.ReadInt32();
ssc.sheetHeight = input.ReadInt32();
int spritesLength = input.ReadInt32();
for (int i = 0; i < spritesLength; i++)
{
var s = new SpriteContent();
s.nameOfSprite = input.ReadString();
s.sourceRectangle = new Rectangle(input.ReadInt32(), input.ReadInt32(), input.ReadInt32(), input.ReadInt32());
ssc.sprites.Add(s);
}
TextureImporter texImporter = new TextureImporter();
var texfilename = ssc.name;
//var fullFilePath = Path.Combine(Environment.CurrentDirectory, Path.GetDirectoryName(texfilename));
var fullFilePath = Path.ChangeExtension(texfilename,".png");
context.Logger.LogMessage("Importing SpriteSheet file: {0}", fullFilePath);
var textureContent = (Texture2DContent)texImporter.Import(fullFilePath, context);
textureContent.Name = Path.GetFileNameWithoutExtension(fullFilePath);
ssc.textureSheet = textureContent;
for (int i = 0; i < spritesLength; i++)
{
ssc.sprites[i].texture = ssc.textureSheet;
}
}
return ssc;
}
}
}
Processor
handles options and other stuff mine is empty here.
using System;
using Microsoft.Xna.Framework.Content.Pipeline;
//
// We pass the result the spritesheet to the ContentWriter to make the xnb.
//
namespace SpriteSheetPipeLine
{
/// <summary>
/// We process Data from the spr file in the content folder.
/// The content processor takes data such as sprite rectangles and textures from the importers i thinks.
/// public class SpriteSheetProcessor : ContentProcessor Input, Output
/// </summary>
[ContentProcessor(DisplayName = "SpriteSheetProcessor")]
public class SpriteSheetProcessor : ContentProcessor<SpriteSheetContent, SpriteSheetContent>
{
//[DisplayName("Scale")]
//[DefaultValue(1)]
//[Description("Set the scale of the model.")]
//public float Scale { get; set; }
public SpriteSheetProcessor()
{
// Maybe you want to do some default preprocessing things.
// For example,
// You may add parameters to the Pipeline process.
// Optional variables that you can change in the Pipeline tool can be added here:
// then process things accordingly.
//..
}
public override SpriteSheetContent Process(SpriteSheetContent input, ContentProcessorContext context)
{
try
{
context.Logger.LogMessage("Processing SpriteSheet");
return input;
}
catch (Exception ex)
{
context.Logger.LogMessage("Error {0}", ex);
throw;
}
}
}
}
Writer
makes the xnb.
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler;
using SpriteSheetData;
namespace SpriteSheetPipeLine
{
// We write files as a xnb file.
[ContentTypeWriter]
public class SpriteSheetDataWriter : ContentTypeWriter<SpriteSheetContent>
{
protected override void Write(ContentWriter output, SpriteSheetContent ss)
{
output.Write(ss.name);
output.Write(ss.sheetWidth);
output.Write(ss.sheetHeight);
output.Write(ss.sprites.Count);
for (int i = 0; i < ss.sprites.Count; i++)
{
output.Write(ss.sprites[i].nameOfSprite);
output.Write(ss.sprites[i].sourceRectangle.X);
output.Write(ss.sprites[i].sourceRectangle.Y);
output.Write(ss.sprites[i].sourceRectangle.Width);
output.Write(ss.sprites[i].sourceRectangle.Height);
// skip texture we only write one and it is already written.
}
output.WriteRawObject((Texture2DContent)ss.textureSheet);
}
public override string GetRuntimeType(TargetPlatform targetPlatform)
{
return typeof(SpriteSheetContent).AssemblyQualifiedName;
}
public override string GetRuntimeReader(TargetPlatform targetPlatform)
{
return typeof(SpriteSheetReader).AssemblyQualifiedName;
}
}
}
SpriteSheetContent,
mockup SpriteSheet class, this stands in during the (importing processing and writeing) part of the workflow.
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
namespace SpriteSheetPipeLine
{
public class SpriteSheetContent : Texture2DContent
{
public string name;
public int sheetWidth = 0;
public int sheetHeight = 0;
public TextureContent textureSheet;
public List<SpriteContent> sprites = new List<SpriteContent>();
}
public class SpriteContent
{
public string nameOfSprite;
public TextureContent texture;
public Rectangle sourceRectangle;
}
}
Reader
when we load content from game1 well be using this to load the xnb data we wrote into the spritesheet class we will be using in game1.
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
namespace SpriteSheetData
{
// We read in the xnb.
public class SpriteSheetReader : ContentTypeReader<SpriteSheet>
{
protected override SpriteSheet Read(ContentReader input, SpriteSheet existingInstance)
{
SpriteSheet ss = new SpriteSheet();
ss.name = input.ReadString();
ss.sheetWidth = input.ReadInt32();
ss.sheetHeight = input.ReadInt32();
int spritesLength = input.ReadInt32();
for (int i =0;i< spritesLength; i++)
{
var s = new SpriteSheet.Sprite();
s.nameOfSprite = input.ReadString();
s.sourceRectangle = new Rectangle(input.ReadInt32(), input.ReadInt32(), input.ReadInt32(), input.ReadInt32());
ss.sprites.Add(s);
}
// from nkasts ex.
IGraphicsDeviceService graphicsDeviceService = (IGraphicsDeviceService)input.ContentManager.ServiceProvider.GetService(typeof(IGraphicsDeviceService));
var device = graphicsDeviceService.GraphicsDevice;
Texture2D sst = new Texture2D(device, ss.sheetWidth, ss.sheetHeight);
sst = ReadTexture2D(input, sst); //input.ReadRawObject<Texture2D>();
ss.textureSheet = sst;
for (int i = 0; i < spritesLength; i++)
{
ss.sprites[i].texture = sst;
}
return ss;
}
// nkasts read method
private Texture2D ReadTexture2D(ContentReader input, Texture2D existingInstance)
{
Texture2D output = null;
try
{
output = input.ReadRawObject<Texture2D>(existingInstance);
}
catch (NotSupportedException)
{
var assembly = typeof(Microsoft.Xna.Framework.Content.ContentTypeReader).Assembly;
var texture2DReaderType = assembly.GetType("Microsoft.Xna.Framework.Content.Texture2DReader");
var texture2DReader = (ContentTypeReader)Activator.CreateInstance(texture2DReaderType, true);
output = input.ReadRawObject<Texture2D>(texture2DReader, existingInstance);
}
return output;
}
}
}
SpriteSheet
the class that game1 will use.
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace SpriteSheetData
{
public class SpriteSheet
{
public string name = "None";
public int sheetWidth = 0;
public int sheetHeight = 0;
public Texture2D textureSheet;
public List<Sprite> sprites = new List<Sprite>();
public void Add(string name, Texture2D texture, Rectangle source)
{
sprites.Add(new Sprite(name, texture, source));
//return sprites[sprites.Count - 1];
}
public void Remove(Sprite s)
{
sprites.Remove(s);
}
public Rectangle GetSourceRectangle(Sprite s)
{
return s.sourceRectangle;
}
public Texture2D GetTexture(Sprite s)
{
return s.texture;
}
public SpriteSheet() { }
public class Sprite
{
public Sprite() { }
public Sprite(string name, Texture2D texture, Rectangle source)
{
nameOfSprite = name;
this.texture = texture;
sourceRectangle = source;
}
public string nameOfSprite;
public Texture2D texture;
public Rectangle sourceRectangle;
}
}
}
game1 load
//MakeSpriteSheet("spriteSheetTest01.spr", 512, 512, UiAssets.Texture2DList, out spriteSheet,true, false, savepath);
spriteSheet = Content.Load<SpriteSheet>("spriteSheetTest01");
Aadditionally the mgcb file looks like this with the two references added.
though i think i should only need the one for the reader and the spritesheet for loading.
#-------------------------------- References --------------------------------#
/reference:…\SpriteSheetData\bin\Debug\SpriteSheetData.dll
/reference:…\SpriteSheetPipeLine\bin\Debug\SpriteSheetPipeLine.dll
#---------------------------------- Content ---------------------------------#
If anyone wants to see the sprite packing algorithm and methods that generated the .spr and png from multiple images, then ill post em.
Its really pretty simple but the extra stuff that goes with it is kinda big.
I might make a function that just packs all this up as a class file like i did with the font thing i sort of like having a couple default classes around that basically create default images fonts ect from a class, plus its cool and fun.
https://drive.google.com/file/d/1fTDCsNITasKCqlqi9Sa1hODTJDTbIRM7/view?usp=sharing