/// <summary>Loads a new sprite from the given binary stream.</summary> /// <param name="animation">The animation this sprite belongs to.</param> /// <param name="reader">The binary stream that contains this sprites data.</param> /// <param name="id">The ID of the sprite in the animation.</param> public SPASprite(SPA animation, SPAReader reader, int id) { ID = id; Animation = animation; // Read some bit flags - these give information about this frame: byte flags = reader.ReadByte(); // Does it also have an alpha frame? // If it does, there are two images in this frame (alpha one second). bool hasAlphaFrame = ((flags & 1) == 1); // Does it have a map? // That says where objects are on this sprite. bool hasMap = ((flags & 2) == 2); // How many frames this sprite holds: FrameCount = reader.ReadUInt16(); // How big is the image, in bytes: int dataSize = reader.ReadInt32(); // Setup the sprite now: Sprite = new Texture2D(0, 0); // And load the image data: Sprite.LoadImage(reader.ReadBytes(dataSize)); Width = Sprite.width; Height = Sprite.height; // Make sure it filters correctly. // This is so we don't see parts of other frames around the edge of the image onscreen: Sprite.filterMode = FilterMode.Point; // Setup the scale: TextureScale = new Vector2((float)Animation.FrameWidth / (float)Width, (float)Animation.FrameHeight / (float)Height); VerticalFrameCount = Height / Animation.FrameHeight; if (hasAlphaFrame) { int alphaImageSize = reader.ReadInt32(); // Setup the temporary alpha texture: Texture2D alphaImage = new Texture2D(0, 0); // And load it's data: alphaImage.LoadImage(reader.ReadBytes(alphaImageSize)); // Next, merge the alpha pixels into our main sprite. Color[] spritePixels = Sprite.GetPixels(); Color[] alphaPixels = alphaImage.GetPixels(); if (spritePixels.Length != alphaPixels.Length) { throw new Exception("Invalid SPA alpha channel image."); } // Set each alpha value from the grayscale of the alpha image: for (int i = spritePixels.Length - 1; i >= 0; i--) { Color pixel = spritePixels[i]; pixel.a = alphaPixels[i].grayscale; spritePixels[i] = pixel; } // Write the pixels back: Sprite.SetPixels(spritePixels); } // Has it got a map? if (hasMap) { // Reset the readers X/Y position: reader.ResetCoordinates(); // Yep! Read map flags: byte mapFlags = reader.ReadByte(); // Acting as a font? bool isFont = ((mapFlags & 1) == 1); // How many entries: int count = (int)reader.ReadCompressed(); // For each map entry.. for (int i = 0; i < count; i++) { SPAMapEntry entry; if (isFont) { // It's a font entry: entry = new SPACharacter(this, reader); } else { // It's a "normal" mapping: entry = new SPAMapEntry(this, reader); } // Add the mapping to the SPA: animation.AddToMap(entry.ID, entry); } // If it's acting as a font, we've also got things like kerning info. if (isFont) { // Got kerning? bool hasKerning = ((mapFlags & 2) == 2); // Got additional charcodes? bool additionalCharcodes = ((mapFlags & 4) == 4); // Font meta? bool fontMeta = ((mapFlags & 8) == 8); if (hasKerning) { // Get the # of kerning pairs: int kernCount = (int)reader.ReadCompressed(); // Previous char - stored relative for better compression. int previousChar = 0; // For each one.. for (int i = 0; i < kernCount; i++) { // Read the first: int firstChar = (int)reader.ReadCompressed() + previousChar; int secondChar = (int)reader.ReadCompressed(); // Advance amount: int advance = (int)reader.ReadCompressedSigned(); // Get the char and add a pair to it: SPACharacter before = animation.GetCharacter(firstChar); SPACharacter after = animation.GetCharacter(secondChar); if (after != null && before != null) { // Add the pair! after.AddKerningPair(before, advance); } // Update previous: previousChar = firstChar; } } // Got additional charcodes? // -> 1 letter with multiple charcodes that use it. if (additionalCharcodes) { // Get the # of additionals: int addCount = (int)reader.ReadCompressed(); // Previous char - stored relative for better compression. int previousChar = 0; // For each one.. for (int i = 0; i < addCount; i++) { // Read the character ID: int firstChar = (int)reader.ReadCompressed() + previousChar; int extraCharcode = (int)reader.ReadCompressed(); // Get the char: SPACharacter character = animation.GetCharacter(firstChar); // Add the CC to it: character.AddCharcode(extraCharcode); // Add as another charcode: animation.AddToMap(extraCharcode, character); // Update previous: previousChar = firstChar; } } if (fontMeta) { // Load the font meta: Animation.FontMeta = new SPAFontMeta(reader); } } } }
/// <summary>Creates a new SPA animation with the given name from the given binary data.</summary> /// <param name="name">The name of the animation. Used for caching purposes so the binary doesn't /// have to be reloaded if the animation is displayed multiple times.</param> /// <param name="binaryData">The raw binary data of the spa file.</param> public SPA(string name, byte[] binaryData) { Instances[name] = this; SPAReader br = new SPAReader(new MemoryStream(binaryData)); if (br.ReadChar() != 'S' || br.ReadChar() != 'P' || br.ReadChar() != 'A') { throw new Exception("This is not an SPA file."); } byte version = br.ReadByte(); if (version != 2 && version != 3) { throw new Exception("This reader supports SPA versions 2 and 3. The file you have given is version " + version + "."); } int spriteFrames; if (version == 2) { // FR: FrameRate = br.ReadByte(); // Total frame count: FrameCount = br.ReadUInt32(); // Frame width: FrameWidth = br.ReadUInt16(); // Frame height: FrameHeight = br.ReadUInt16(); // Sprite frame count: spriteFrames = br.ReadInt32(); } else { // FR: FrameRate = (int)br.ReadCompressed(); // Total frame count: FrameCount = (uint)br.ReadCompressed(); // Frame width: FrameWidth = br.ReadUInt16(); // Frame height: FrameHeight = br.ReadUInt16(); // Sprite frame count: spriteFrames = (int)br.ReadCompressed(); } Sprites = new SPASprite[spriteFrames]; // Next, read each of the sprite frames: for (int i = 0; i < spriteFrames; i++) { Sprites[i] = new SPASprite(this, br, i); } }