AddSpriteFramesWithFile plist

Topics: Sprites and SpriteBatch
Jul 24, 2013 at 5:20 PM
Trying to write up a plist example based on examples from other problems, but calling:
CCSpriteFrameCache.SharedSpriteFrameCache.AddSpriteFramesWithFile("explosion.plist");

Results in:
Additional information: Could not load the contents of explosion.plist as raw text.

Do I need to use a different method for loading a plist file, modify the plist, or?

Thanks,
Jeff
Coordinator
Jul 24, 2013 at 10:43 PM
The plist has to be processed using the PList content processor from the content pipeline extension project.

We recently added the ability to load a plist using a stream: new PListDocument(Stream foo). This is the recommended way to do plists as we are trying to phase out the content pipeline extensions for cocos2d-xna.
Jul 25, 2013 at 8:01 PM
Is the PlistDocument(Stream) in the current release? I don't see that overload, though there is one for string that seems to take the plist OK:
PlistDocument plDoc = new PlistDocument(pListContent);

I'm (obviously) new to Cocos2D - if you have a few lines of code to go from plist to the SharedSpriteFrameCache, that would be super helpful.

Thanks,
Jeff
Coordinator
Jul 25, 2013 at 8:43 PM
Jeff, are you using the codeplex source or the github source?

PListDocument doc = new PListDocument();
doc.LoadFromXmlFile(myStream);

shazam!
Jul 25, 2013 at 8:59 PM
Edited Jul 25, 2013 at 9:06 PM
Sweet, thanks.

I pulled the Visual Studio project template from NuGet that has the basic Cocos-2D/XAML project.

EDIT: Sorry - I actually had this bit working in my code - it was moving from the PlistDocument to the SharedSpriteFrameCache that was giving me headaches - there's an add with file and add with dictionary, no add from PlistDocument. I see AsDictionary, AsArray, etc. on the PlistDocument object, just not connecting the dots yet.

CCSpriteFrameCache.SharedSpriteFrameCache.AddSpriteFramesWithFile
CCSpriteFrameCache.SharedSpriteFrameCache.AddSpriteFramesWithDictionary

Jeff
Coordinator
Jul 25, 2013 at 9:02 PM
very good. those are only 2 weeks old. sometimes VS2012 crashes when I have those installed. if you get a random startup crash of VS2012, don't get scared, just re-launch and see if it works. It typically corrects itself.

we are still only in beta.
Jul 25, 2013 at 9:18 PM
It's been really solid for me - no issues creating the projects or working in them.

Getting over the learning curve is a different issue given not everything translates over from iOS or Android, etc.

My game plan was just to build a bunch of test apps and write up tutorials so there's some C# documentation out there. I started thinking the subject is ripe for a book, but until things click into place for me, that would be an uphill climb. :)

Jeff
Coordinator
Jul 25, 2013 at 9:21 PM
We are starting to formulate a book over here. Just not enough time to do all of the infrastructure and do a book and keep mono from trying to hijack the project.

We have a huge suite of tests though, all of which are undocumented but fully functional on all platforms. The only problems are in the TGA support for the particle tests. Embedded graphics in the particle configuration is always problematic since it has to use the native codecs to decode the image. TGA is hardly supported on most mobile platforms nowadays.
Jul 25, 2013 at 9:30 PM
Yeah, I couldn't see TGA being a really high demand piece, though I guess if someone is rendering sprites out of a 3D package that might be what they want.

You see my edit above? I had the bit of code you sent over - it's the jump from there to the cache that I'm stuck on.
Coordinator
Jul 25, 2013 at 9:39 PM
PListDocument {
    public override PlistDictionary AsDictionary
    {
        get { return this; }
    }
}

The PListDocument is the same as a PListDictionary.
Jul 25, 2013 at 9:57 PM
CCSpriteFrameCache.SharedSpriteFrameCache.AddSpriteFramesWithDictionary(doc.AsDictionary, someTexture);

Throws a not implemented exception.

Am I calling it incorrectly? someTexture is of Type CCTexture2D.
Coordinator
Jul 25, 2013 at 10:36 PM
NotImplementedException or NotSupportedException?

Where did you get your plist? Predator writes out an invalid plist for cocos2d. It does not put in the format indicator.
Jul 25, 2013 at 10:44 PM
An exception of type 'System.NotImplementedException' occurred in cocos2d-xna.DLL but was not handled in user code

Additional information: The method or operation is not implemented.

I've gone through a couple for testing - I think this one came out of Texture Packer.

If you have one you know works, I'd be happy to slap it in there and see if it works/diff it with the one I have and see what is messed up.

Jeff
Coordinator
Jul 25, 2013 at 10:56 PM
After you load the XML from the stream, you need to do this:

PlistDictionary dict = doc.Root.AsDictionary;

That's where the NotImplemented exception is coming from - the root dictionary is attempting to be transformed to a primitive type, like an Int, which is no good.
Jul 26, 2013 at 5:21 PM
OK - am I in the ball park here? I'm compiling clean and running, but don't see the sprite animation on the screen.
CCTexture2D explosionTexture = new CCTexture2D();
explosionTexture.InitWithFile(@"Explosion.png");

PlistDocument doc = new PlistDocument();
doc.LoadFromXmlFile(@"content\Explosion.plist");

PlistDictionary dict = doc.Root.AsDictionary;
CCSpriteFrameCache cache = CCSpriteFrameCache.SharedSpriteFrameCache;
cache.AddSpriteFramesWithDictionary(dict, explosionTexture);

CCSpriteBatchNode spriteSheet = new CCSpriteBatchNode(@"explosion.png");
AddChild(spriteSheet);

List<CCSpriteFrame> explosionFrames = new List<CCSpriteFrame>();
for (int i = 1; i <= 21; i++)
{
    //Explosion_001.png
    explosionFrames.Add(cache.SpriteFrameByName("Explosion_0" + String.Format("{0:00}", i) + ".png"));
}

CCAnimation explosionAnim = new CCAnimation(explosionFrames, .1f);

CCSprite explosion = new CCSprite("Explosion_001.png");
explosion.SetPosition(400, 400);

CCAnimate exAnim = new CCAnimate(explosionAnim);
explosion.RunAction(exAnim);

spriteSheet.AddChild(explosion);
Coordinator
Jul 26, 2013 at 6:03 PM
whoa. that might be the longest and most drawn out way to do a single animation.

Let's say you wanted to do a flame animation atop a torch:
        // Setup the spritesheet texture
        CCTexture2D txFlameAnimationLeft = CCTextureCache.SharedTextureCache.AddImage("images/torchFlames");

        // Setup the frames of the spritesheet - this is where your plist comes in
        var animFramesFlameAnimationLeft = new List<CCSpriteFrame>();
        animFramesFlameAnimationLeft.Add(new CCSpriteFrame(txFlameAnimationLeft, new CCRect(0,0,64,80)));
        animFramesFlameAnimationLeft.Add(new CCSpriteFrame(txFlameAnimationLeft, new CCRect(64,0,64,80)));
        animFramesFlameAnimationLeft.Add(new CCSpriteFrame(txFlameAnimationLeft, new CCRect(128,0,64,80)));
        animFramesFlameAnimationLeft.Add(new CCSpriteFrame(txFlameAnimationLeft, new CCRect(192,0,64,80)));
        animFramesFlameAnimationLeft.Add(new CCSpriteFrame(txFlameAnimationLeft, new CCRect(0,80,64,80)));

        // This is where you setup the frames and frame rate in an animation
        CCAnimation FlameAnimationLeft_Animation = new CCAnimation(animFramesFlameAnimationLeft, 1f/6f);

        // This is where we store the animation to later re-use
        CCAnimationCache.SharedAnimationCache.AddAnimation(FlameAnimationLeft_Animation, "FlameAnimationLeft");

        // This is where we setup the sprite placeholder for the animation, aka animation container.
        FlameAnimationLeft = new CCSprite(animFramesFlameAnimationLeft[0]);
        FlameAnimationLeft.AnchorPoint = new CCPoint(.5f,.5f);
        FlameAnimationLeft.Position = new CCPoint(96+32,328f+16f*3f-40);
        AddChild(FlameAnimationLeft,3,kFlameAnimationLeft);
This was generated from our scene generation tool, which is not public. For the sake of your book, if you have a github account, I can get you access to SceneBuilder so you can generate code from simple markup like:
<animation id="FlameAnimationLeft" zOrder="3" delay="1f/6f" manualPlay="false" visible="false">
    <spriteSheet>images/torchFlames</spriteSheet>
    <frame>0,0,64,80</frame>
    <frame>64,0,64,80</frame>
    <frame>128,0,64,80</frame>
    <frame>192,0,64,80</frame>
    <frame>0,80,64,80</frame>
    <position>96+32,328f+16f*3f-40</position>
    <anchor>.5f,.5f</anchor>
    <scale>0</scale>
    <onEnter>
        <hide/>
        <place>
            <position xy="96+32,328f+16f*3f-40"/>
        </place>
        <wait duration="3"/>
        <show/>
        <parallel>
            <fadeIn duration="4"/>
            <scaleTo duration="2">
                <factors>2,1</factors>
            </scaleTo>
        </parallel>
    </onEnter>
</animation>
Coordinator
Jul 26, 2013 at 6:15 PM
also, I will convert your sample over to the more condensed version shortly.
Coordinator
Jul 26, 2013 at 8:57 PM
This line here in your code:

explosionFrames.Add(cache.SpriteFrameByName("Explosion_0" + String.Format("{0:00}", i) + ".png"));

Make sure that the sprite frame names here match exactly how they are named in the plist file.
Jul 26, 2013 at 9:11 PM
Just doublre-checked, and they match the plist:

[...]
<key>Explosion_001.png</key>
<dict>
<key>frame</key>
<string>{{0,0},{400,400}}</string>
<key>offset</key>
<string>{0,0}</string>
<key>sourceSize</key>
<string>{400,400}</string>
</dict>
[...]
Coordinator
Jul 26, 2013 at 9:24 PM
explosionTexture.InitWithFile(@"Explosion.png"); You should use the shared texture cache:
    CCTexture2D txFlameAnimationLeft = CCTextureCache.SharedTextureCache.AddImage("Explosion");
Coordinator
Jul 26, 2013 at 9:25 PM
CCSprite explosion = new CCSprite("Explosion_001.png");

Here you want to use the first frame from your animation:

new CCSprite(explosionFrames [0])
Jul 26, 2013 at 9:38 PM
Updated my code with those changes, still no go.

Not sure if it's getting added to the scene - debug info still showing no child added.

Thanks,
Jeff
Coordinator
Jul 28, 2013 at 5:33 AM
This line in your code:

CCAnimate exAnim = new CCAnimate(explosionAnim);
explosion.RunAction(exAnim);

spriteSheet.AddChild(explosion);


Add the explosion to the spriteSheet before you run the action on it:

spriteSheet.AddChild(explosion);
CCAnimate exAnim = new CCAnimate(explosionAnim);
explosion.RunAction(exAnim);
Coordinator
Jul 29, 2013 at 6:27 AM
Hi Jeff, I added a method to CCSpriteFrameCache to load the frames from a Stream instance. I then changed our current production game Mayan Epic to use this new method and to use the sprite frames for our game tiles.
    static GameTile()
    {
        CCTexture2D tileSheet = CCTextureCache.SharedTextureCache.AddImage("images/tileSheet");
        try
        {
            Stream s = GameResources.GetEmbeddedResource("tiles.plist");
            CCSpriteFrameCache.SharedSpriteFrameCache.AddSpriteFramesWithFile(s, tileSheet, "tiles");
        }
        catch (Exception)
        {
            // Oops, no sprite sheet
        }
    }
For me, the "tiles.plist" is an embedded resource.

GameResources.GetEmbeddedResource looks like:
    internal static Stream GetEmbeddedResource(string name)
    {
        System.Reflection.Assembly assem = System.Reflection.Assembly.GetExecutingAssembly();
        Stream stream = assem.GetManifestResourceStream(name);
        if (stream == null)
        {
            stream = assem.GetManifestResourceStream("My.Namespace." + name);
        }
        return (stream);
    }
Now when I get my frames out of the cache:
            string frameName = TileManager.Instance.GetTileAssetName(TileCode);
            CCSpriteFrame sf = CCSpriteFrameCache.SharedSpriteFrameCache.SpriteFrameByName(frameName + ".png", "tiles");
            if (sf != null)
            {
                s = new CCSprite(sf);
            }
            else
            {
                s = new CCSprite("images/tiles/" + frameName);
            }
This works as expected. The key is the ".png" file in the frame name.

So at least we know that the texture cache and sprite frame cache are working properly.
Coordinator
Jul 29, 2013 at 6:46 AM
Also, I created the plist using texture packer, with no changes to the plist file. So Texture Packer's cocos2d output is fully compatible with Cocos2d-XNA.
Jul 29, 2013 at 5:41 PM
OK, got it working - it might have been the order I was adding things in.

It's not too code heavy, but at least I have an isolated example as a starting point to go from.

Thanks for the help - I'll buy you a beer next time I'm in the area (I'm sure there will be more stuff coming up).
Coordinator
Jul 29, 2013 at 5:57 PM
sounds great. Glad it's working. let me know if you're ever down this way.
Nov 19, 2013 at 5:19 PM
@totallyevil - I was able to get a CCSpriteFrameCache loaded using a plist stream and the AddSpriteFramesWithFile as you showed above and also pull out individual frames using the SpriteFrameByName method. However, has the prefix parameter (highlighted below) been removed from the AddSpriteFramesWithFile and SpriteFrameByName methods? I'm using the latest code from GitHub but don't see any signatures for those methods that contain the prefix, so I was wondering if it wasn't needed anymore, wasn't added to the source, etc. I'm just learning Cocos2D-XNA (and Cocos2D in general), so I could be going off the rails here. :) Any thoughts are appreciated. Thanks.

CCSpriteFrameCache.SharedSpriteFrameCache.AddSpriteFramesWithFile(s, tileSheet, "tiles");

CCSpriteFrame sf = CCSpriteFrameCache.SharedSpriteFrameCache.SpriteFrameByName(frameName + ".png", "tiles");
Coordinator
Dec 7, 2013 at 6:02 PM
Yes, we removed the prefix from the sprite frame cache. We replaced it with CCSpriteSheet so that you create a sprite sheet object that manages your texture and frames. It handles the name conflicts that arise when you have frames with the same name.