lucid

SpriterPlusPlus - a C++ Spriter implementation

121 posts in this topic

SpriterPlusPlus

a C++ Implementation for Spriter animations

About

SpriterPlusPlus aims to be as quick as possible playing back animations, and iterating additional characters once loaded. Additional instances are flyweights that take up less ram than the initially the loaded model. Each flyweight instance minimizes cycles spent in random access and iteratating through containers using stored pointers and iterators wherever possible.

Included example

Sfml for display and sound playback. And two examples for loading: TinyXml and PugiXml

Supported Features

Basic animations Bone animations All curve types (Instant, Linear, Quadratic, Cubic, Quartic, Quintic, Bezier) Points Collision Rectangles SubEntities Events (Triggers) Sounds Variables Tags Character Maps Animation Blending

How to use

All necessary files are in the 'spriterengine' folder. The other included files are for example purposes.

Extend (All of the following can be found in spriterengine/override) :

FileFactory

and pass to SpriterModel so it can use your own inherited versions of :
  • SpriterFileDocumentWrapper (and Element and Attribute Wrappers) to create parsing code. You can specify a separate SpriterFileDocumentWrapper for scml (xml) and scon (json), which will be automatically selected upon loading based on the file extension
  • ImageFile to store shared texture/image resources to be used by sprite objects
  • SoundFile to store shared sound resources to be used by sound objects, and create your custom extended version of SoundObjectInfoReference to playback and control volume of the shared sound resources

ObjectFactory

and pass to SpriterModel to enable debug rendering:
  • spriterengine/objectinfo/PointInstanceInfo
  • spriterengine/objectinfo/BoneInstanceInfo
  • spriterengine/objectinfo/BoxInstanceInfo

Settings.cpp has static variables to control the display of points, bones, and boxes:

  • bool Settings::renderDebugPoints;
  • bool Settings::renderDebugBones;
  • bool Settings::renderDebugBoxes;

enableDebugBones must be true while creating a new instance in order for renderDebugBones to have an effect: * bool Settings::enableDebugBones;

  • spriterengine/objectinfo/TriggerObjectInfo if you want to be able to perform a function upon being triggering events ###### (Alternatively you can query a specific trigger for your instance using :
  • getTriggerObject("triggerName")->getTriggerCount() // in case more than one event was triggered in the elapsed time

Creating an instance :

  • include "spriterengine/spriterengine.h"

  • ScmlModel scmlModel(fileName, new customFileFactory, new customObjectFactory/optional/);

  • EntityInstance *entityInstance = scmlModel.getNewEntityInstance(entityName or entityIndex);

Controlling playback :

  • entityInstance->setCurrentAnimation(animationName); // defaults to first animation
  • entityInstance->setCurrentAnimation(animationName, blendTimeInMilliseconds); // blends the current animation to the new one over time
  • entityInstance->setTimeElapsed(inMilliseconds);

  • entityInstance->pausePlayback(); // playback is automatically paused at the end of a non-looping animation
  • entityInstance->startResumePlayback(); // playback automatically resumes on a setCurrentAnimation() command

after setting the elapsed Time:
  • entityInstance->render();
and if you're using sound and/or events:
  • entityInstance->playSoundTriggers();
or
  • entityInstance->playEventTriggers();
or
  • entityInstance->playAllTriggers();

Controlling the entire character

  • entityInstance->setPosition(SpriterEngine::point(x,y))
  • entityInstance->setAngle(SpriterEngine::toRadians(angle));
  • entityInstance->setScale(SpriterEngine::point(w,h));

Character Maps

  • entityInstance->applyCharacterMap("charMapOne");
  • entityInstance->applyCharacterMap("charMapTwo");
  • entityInstance->removeCharacterMap("charMapOne");
  • entityInstance->removeAllCharacterMaps();

Metadata

to retrieve a variable value from an object :
  • entityInstance->getRealValue("objectName","varName"); // or getIntValue, or getStringValue
to retrieve a variable from the entity itself :
  • entityInstance->getRealValue("varName"); // or getIntValue, or getStringValue
if you would like to store the variable to avoid repeated retrieval for performance reasons (should normally not be necessary):
  • UniversalObjectInterface *myVariable = entityInstance->getVariable("objectName", "varName"); // to retrieve from an object or
  • UniversalObjectInterface *myVariable = entityInstance->getVariable("varName"); // to retrieve from the entity itself
and then use
  • myVariable->getRealValue(); // or getIntValue, or getStringValue
to check if a tag is active :
  • bool myTagIsActive = entityInstance->tagIsActive("objectName", "tagName");
or if the tag is on the entity itself :
  • bool myTagIsActive = entityInstance->tagIsActive("tagName");

Additional options

Error output

In Settings.cpp there is a function pointer to a callback function to display error messages

  • ErrorFunctionPointer Settings::errFunction;

Any function with the signature void myErrorFunction(const std::string &errorMessage) can be used here.

Two basic error functions are provided: * nullError is the default, and takes no action * simpleError outputs the error message to std::cerr

In most cases, there should be no errors, unless you are loading an invalid file, attempting to retrieve the wrong type of data from an object or variable (myStringVariable->getIntValue()), or attempting to access a missing variable or object

Loading options

In Settings.cpp you can set loading option, if your engine requires the y, pivotY, or angle to be reversed to display properly (you attempt to load a character in your engine and the positions, pivots, or angles seem reversed). These are all defaulted to true, which is the setting that should work correctly for most engines.

  • Settings::reverseYOnLoad;
  • Settings::reversePivotYOnLoad;
  • Settings::reverseAngleOnLoad;

Feedback

To provide feedback, report errors, or give suggestions, please use this forum thread.

Share this post


Link to post
Share on other sites

Is it complied for windows cause i cant run SpriterPlusPlus.exe , it says its not win32 application.

Share this post


Link to post
Share on other sites

Well, that's awesome!
I'll try integrating it to my SFML-based project right now.
Thanks to the example, I guess it won'tbe so hard :) 

Share this post


Link to post
Share on other sites

Hi!

 

It's finally here, I'm *happy* :)

I'm also sad to be the bearer of bad news though, but it just won't compile with anything but MSVC, at least it doesnt with neither gcc not clang; that means no OSX, no Linux, no Android, no iOS :/ Which I assume is not intended since Spriter itself is so multiplatform friendly :)

 

On my linux (thought I doubt that'd be different on osx or windows) with gcc 4.9: http://hastebin.com/uroxefobis.vhdl

and with clang 3.5: http://hastebin.com/favarilaha.vhdl

 

Also my C++ is very rusty but to me it looks like it's using the rather recent C++14 stuff, which on OSX at least will make it very hard to support "oldish" versions of the system :/

That's something to ponder at least, but obviously the biggest urgency is to actually let it work on non MSVC stuff :)

 

Thanks!

Share this post


Link to post
Share on other sites

Thanks for the feedback everyone. Keep it coming. Reading through this thread and addressing the issues will be the first thing Edgar does when he gets back (in roughly 2 days)

 

cheers.

Share this post


Link to post
Share on other sites

Hello everyone. Had a moment to drop in. As I Mike said I will be able to look into everything further when I get back.

@breush, please let us know how it goes.

 @darkgod - I'll make the needed changes to get it to compile on clang and gcc (looks like they're just being a bit more strict about a few things).   Should be no problem. I'll also look into removing the c++ 11 stuff, which is mostly 'auto's to remove the need for long ugly iterator  names and additional typedefs.  

@bwwd - I may have inadvertently done the final compile for win 64, but it's definitely windows. Will take a look when I get back.  

 Thanks for the feedback, everyone!    

Share this post


Link to post
Share on other sites

@lucid @darkgod As my pull request got merged, the compile-time errors should be fixed.
I work with C++11 and the STL, so I'm OK with the current implementation but others might be concerned these requirements,
notably on small systems like smartphones.

By the way, I had to make a loader using pugixml,
I can share it with you guys later, once I sure that the problems I'm experiencing are not linked to that change.

Share this post


Link to post
Share on other sites

So, here's a quick sum-up from my experience.

Cool:

  • It works great! And that's a good point.
  • It fixes problems I had using scml-pp concerning negative scaling.
  • It's easy to interface with user's pre-existant factories.
  • Question: I see the functions to override for debugging, but I didn't find any option to activate it. (Excepted it to be in global/global.h).

Not so cool:

  • Triggers (sounds at least) do not work properly.
    Either I get infinite of them or none.
  • I spent way too much time writing a XML loader.
    The basic functions and types that are specific to the XML library need to be factored out from the document structure.
    That way one would just need to write the content of predefined basic functions (getAttribute, getFirstChild, etc.).
  • Every time I see a "new ..." in the code I just wonder who should delete it...
    For instance "new SfmlSoundObjectInfoReference" in example/sfmlsoundfile.cpp

Something to do:

  • Functions in global/global.h are "static" but should be "inline" instead.
    I think this is due to a misundertanding of what "static" means outside a class definition.

So, I'm very happy with it!
But right now my biggest issue concern sounds, I hope to understand the problem soon enough. :)

And by the way, "To provide feedback, report errors, or give suggestions, please use the relevant Spriter forum thread",
this is the one, right?

Share this post


Link to post
Share on other sites

@bwwd, do you have any other machines you can test on?  I verified it's 32 bit.
 

On 11/5/2015, 8:39:07, Breush said:

Question: I see the functions to override for debugging, but I didn't find any option to activate it. (Excepted it to be in global/global.h).

Yes, that's something I didn't get to before the deadline.  I do plan to fix it to make it optional.  At the moment, if your objectFactory creates drawable boxes and points, they are automatically drawn.

On 11/5/2015, 8:39:07, Breush said:

Triggers (sounds at least) do not work properly.
Either I get infinite of them or none.

The file I'm testing with is working correctly.  I will get an example file suitable for everyone to test with that includes at least one instance of every feature soon.   I can also check if your scml file has the same issue here (either attach it here or send it to lucid@brashmonkey.com if you'd like me to test it) 

On 11/5/2015, 8:39:07, Breush said:

I spent way too much time writing a XML loader.
The basic functions and types that are specific to the XML library need to be factored out from the document structure.
That way one would just need to write the content of predefined basic functions (getAttribute, getFirstChild, etc.).

Agreed.  This is another thing I just didn't get a chance to get to for the deadline.

On 11/5/2015, 8:39:07, Breush said:

Every time I see a "new ..." in the code I just wonder who should delete it...
For instance "new SfmlSoundObjectInfoReference" in example/sfmlsoundfile.cpp

Anything function that has a name newSomething() or getNewSomething() should be taken as creating a new instance, so:

  • SomeType *myPointer = newSomething();

should be treated the same as if it were:

  • SomeType *myPointer = new Something();

Anything using that function should either take ownership of this new pointer and delete it, or it should immediately pass it to a function which expects a new pointer, which should always have a parameter name reflecting this:

  • myFunction(SomeType *newSomething)

That means this function is expecting a new instance, and will take ownership of it and delete it upon destruction. 
If you notice I deviated from this anywhere, please let me know.

On 11/5/2015, 8:39:07, Breush said:

"To provide feedback, report errors, or give suggestions, please use the relevant Spriter forum thread",
this is the one, right?

yes

On 11/5/2015, 1:50:41, Breush said:

I had to make a loader using pugixml,
I can share it with you guys later, once I sure that the problems I'm experiencing are not linked to that change.

thank you, please do


Thanks for all the excellent and useful feedback.

Share this post


Link to post
Share on other sites

No i dont have other machines, current spriter works so why new one shouldnt ? I ran "SpriterPlusPlus.exe"  from "SpriterPlusPlus-master\Release" folder but it doesnt have icon and it says its not win32 application.I will redownload again but if you say it runs for you i dont know, do i need some libraries to run this new version on windows ? Im on WinXP SP3

That SpriterPlusPlus.exe file is only 160kb in size 

-- 

downloaded new 161kb version and now i have this :

qV96lum.jpg

it says encountered illegal instruction , microsoft website says its command.com mismatch or something.

Share this post


Link to post
Share on other sites

Working on the general loading wrappers now.  Should make adding a new parsing library very simple.

 

@bwwd this is an implementation for Spriter (to run animations in your own game or engine), not a new version of Spriter.  Could you give a more specific error message?  I would try compiling it your end if possible.

Share this post


Link to post
Share on other sites

ah i misinterpreted it , im not using spriter files directly so its not a top priority for me until deformation is available :) for clients im forced to choose spine if they want to use deformation in realtime but for spriter its deformation exported to single frames , too bad cause i prefere to work with deformation in spriter , in spine they dont have auto subdivision and you have to take care of every vert which is insane with more complicated setups, having less points to move verts is better and have auto subdivided verts between points and you cant change density of the grid like in spriter , i hope that idea will stay and we will be able to set different diffent wireframe density for every bodypart, spine is very different in this aspect and every vert counts and has to be moved manually and other verts wont follow and you need a lot of them to create smooth S shape instead of Z shape.just headaches, only weights to bones are saving this feature in spine.

Share this post


Link to post
Share on other sites

Committed some changes that make it so you should only have to override a 3 simple classes - wrappers for documents, elements, and attributes, and now the loader class interfaces with the wrapper so you don't have to worry about the details of the loading code.  If anyone tries to overload these, please let me know how it goes whether it works smoothly, or you run into problems.

@bwwd, I know it's been an extremely long time waiting for the deformation.  It's also my personal most wanted feature.  Trust me I know it's been slow going, but when we finally get there, it's going to be much, better than the proof of concept version that's in there now.

Share this post


Link to post
Share on other sites

I don't like the layout of the repo. I think the spriterengine should be in it's own repository, so I can add it as a git submodule to my project. You could then add another repo with the example.

Adding binary files to the repo is a bit strange. You could add a download link in the README or add them to the Release tab. And this repo is meant for developers that want to build their own implementation, so they'll need to build it anyway.

Only using Visual studio as build option is a bit cumbersome for Unix/Linux devs. CMake would be a great option. It's cross platform and extendable. You could add it to the example and the engine and let them work together. SFML is also using it.

Share this post


Link to post
Share on other sites

I've added a pull request for a gcc change and a fix for header files not found.

I could build the engine now with:

cd spriterengine
LANG=C g++ -std=c++14 -shared -fPIC -I. */*.cpp -o spriterengine.so

(LANG=C only because I like the terminal output in English)

lucid likes this

Share this post


Link to post
Share on other sites

Thanks for the feedback and PugiXML loader examples, @Breush
and thanks for the fixes and the suggestions, @labsin.  I merged your pull request, and I will get to your other suggestions later.

Share this post


Link to post
Share on other sites

I needed to set looping to false for a specific animation.
I made a way to overwrite the default value via entityInstance->currentAnimation->isLooping but that did not work.

Is looping a thing yet? And, I must say I did not find any option to turn looping off in Spriter itself too.

Share this post


Link to post
Share on other sites
1 hour ago, Breush said:

I needed to set looping to false for a specific animation.
I made a way to overwrite the default value via entityInstance->currentAnimation->isLooping but that did not work.

Is looping a thing yet? And, I must say I did not find any option to turn looping off in Spriter itself too.

To set looping off for an animation once its in the game, just click the looping icon at the right side of the time-line palette in Spriter. (the button will go dark to represent "off", the button is orange when "on" by default)
Playback in Spriter is always looping if you left click the play button. BUT, if you right-click the play button the animation will play just once and stop.

 

cheers.

Share this post


Link to post
Share on other sites
57 minutes ago, BrashAdmin said:

To set looping off for an animation once its in the game, just click the looping icon at the right side of the time-line palette in Spriter. (the button will go dark to represent "off", the button is orange when "on" by default)
Playback in Spriter is always looping if you left click the play button. BUT, if you right-click the play button the animation will play just once and stop.

Oh, Okay.
As I left-clicked the play button, I thought this button did not what I expected it to do.
English is not my native language and "Repeat Playback" tooltip was not very eloquent to me.
But I understand now, thanks.
 
So now that I tested it with a file containing a non-looping animation,
I can safely say it's not working in SpriterPlusPlus.
-> Happens when I have a time step that does not divide the length (in time) of the animation.

Share this post


Link to post
Share on other sites

Hi!

I ported this to allegro, quite simple task & works great.

However, i have the same issue that Breush have, i can't seem to find any functions that turns on / off looping, or if the current animation is complete.

(Looping is "off" in the scml - does not matter).

I was thinking that i could use the timing represented in the scml, but that would cause more hard-coding than i want.

Also, i can't find any functions to allow / set flipping (e.g. walking right or left). I could make a "hack" for it, but yet again, more hard-coding than i want.

Did i miss anything, or isn't those functions currently present?

Thanks!

Share this post


Link to post
Share on other sites
13 hours ago, Breush said:

So now that I tested it with a file containing a non-looping animation,
I can safely say it's not working in SpriterPlusPlus.

9 hours ago, ALO said:

However, i have the same issue that Breush have, i can't seem to find any functions that turns on / off looping, or if the current animation is complete.

(Looping is "off" in the scml - does not matter).

I was thinking that i could use the timing represented in the scml, but that would cause more hard-coding than i want.

Correct.  Currently, all SpriterPlusPlus is doing with the looping/nonlooping information is making it so it doesn't tween from the last frame to the first.  I will add in something to keep track of animation playback status, and either allow some type of 'animationFinished' callback and/or allow it to ignore setTimeElapsed() until the animation is restarted.


 

9 hours ago, ALO said:

I ported this to allegro, quite simple task & works great.

Excellent.  If you decide you want to share it, please link here so we can add it to the list once we have one up.

 

9 hours ago, ALO said:

Also, i can't find any functions to allow / set flipping (e.g. walking right or left). I could make a "hack" for it, but yet again, more hard-coding than i want.

For now you can just set scale of the entityinstance to -1,1  (-x,y  or for vertical flip x,-y).
I can add convenience functions to setFlipX and setFlipY that will automatically do that later though, to make the flipping code more readable when using it in your projects

Share this post


Link to post
Share on other sites

Just an update on the latest:
I will update the README with these additions soon, but for anyone who's actively working with it: 
There's a settings.cpp file with some statics so you can set whether to show points and/or boxes for debugging.  Since bones aren't part of the zorder where rendering takes place, I will need to add a couple of things before 'enableDebugBones' does anything.

Also, in settings.cpp, you can specify a callback function to output error messages that will specify if and which function took an unexpected path.  There's an included function simpleError() that just outputs to std::cerr.  The default setting is a nullError() function which does nothing to save on processing error output.   And of course if your authoring tool or engine has a different error output you'd like to use, you can set it here.  You can either set it at the top of settings.cpp, as the initial value for errFunction, or you can set it using setErrorFunction().

Also, there were a couple of bug fixes.  One bug where if you start playback on a trigger (for instance, if you start your animation at the beginning, and you have a sound on the first frame), it wasn't playing the sound or trigger immediately.  I also fixed another bug where entityinstances weren't updating their transformations after angle changes, which would appear most obvious if you had sprites without bone parents, or top level bones that were offset far from 0,0.

Next, I will be adding functions like startAnimation() and pauseAnimation() to automatically handle pausing after a non-looping animation finishes.  Once that's functioning correctly, I will add animation blending.

Lastly, in preparation for animation blending, and to work more easily with the error output, I created a new class PointInstanceInfo.  If you created any child classes of PointObjectInfo to display your point objects, just change them to inherit from PointInstanceInfo instead.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.