Jump to content
lucid

SpriterPlusPlus - a C++ Spriter implementation

Recommended Posts

I began to find some bugs in Easing curve. First the obvious ones in getEasingCurveFromKeyElement in spriterdocumentloader.cpp:

                while (i < MAX_CONTROL_POINTS)
                {
                    att = element->getFirstAttribute("c"+std::to_string(i+1));
                    if(att->isValid()) {
                        controlPoints = att->getRealValue();
                    }
                    i++;
                }

After these fixes, Bezier Curves still do not work. The sprites are at completely wrong places by rendering. Without easing they are correct.

I am searching for the source of the bug but if you have any idea, I could save some time.

Share this post


Link to post
Share on other sites
On 5/12/2016 at 6:12 PM, TheNilusss said:

is there a way to replace a single texture ?

Using Spriter's built in character maps feature would be most simple, but if you can put your replacement image into a SpriterEngine::ImageFile (or whatever derived version you're using), you should be able to use:

SpriterEngine::UniversalObjectInterface* myObject = it->getObjectInstance("objectName");

myObject->setImage(replacementImage); // using a SpriterEngine::ImageFile
On 5/29/2016 at 5:01 AM, conceptgame said:

I began to find some bugs in Easing curve. First the obvious ones in getEasingCurveFromKeyElement in spriterdocumentloader.cpp:

                while (i < MAX_CONTROL_POINTS)
                {
                    att = element->getFirstAttribute("c"+std::to_string(i+1));
                    if(att->isValid()) {
                        controlPoints = att->getRealValue();
                    }
                    i++;
                }

After these fixes, Bezier Curves still do not work. The sprites are at completely wrong places by rendering. Without easing they are correct.

I am searching for the source of the bug but if you have any idea, I could save some time.

Thanks for the finding the bug.  I committed the fix to the GitHub.  Aside from what you posted here, in loadinghelpers.cpp - EasingCurveInterface *getNewEasingCurve(CurveType curveType, ControlPointArray *controlPoints)

the ControlPointArray (typedef real ControlPointArray[MAX_CONTROL_POINTS]) was being dereferenced like this:

*controlPoints[0]

instead of this:

(*controlPoints)[0]

 

Share this post


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

Aside from what you posted here, in loadinghelpers.cpp - EasingCurveInterface *getNewEasingCurve(CurveType curveType, ControlPointArray *controlPoints)

the ControlPointArray (typedef real ControlPointArray[MAX_CONTROL_POINTS]) was being dereferenced like this:


*controlPoints[0]

instead of this:


(*controlPoints)[0]

 

Oh yes, thanks. It was exactly the problem.

Share this post


Link to post
Share on other sites

I'm updating my copy to handle the embedded atlas data from R8 (which I'll happily submit as a push request if I can get it to work :) ).

It kind of works, but I fail to grasp was "axoff" and "ayoff" are in there. From the name I guess some offsets but .. hum :)

I loaded GreyGuy both with and without atlas and it does not match exactly. I do assume it's an offset problem and thus linked to axoff/ayoff but I'm unsure what to make of it. Simply adding them to the position makes it go wildly off.

 

I'm outputting to opengl thusly:

In ImageFile::renderSprite, I make a quad with coords from -pivot to size-pivot; then I make a model matrix taht contains the position, scale & rotation and apply it to each vertex and render it. I account for the atlas sprite being rotated and "unrotate" it.

 

In the attached picture you see GreyGuy in pink is the non atlas one and the one in green is the atlas one.

Any ideas of what I'm probably doing stupidly wrong ? :)

wrong.png

Share this post


Link to post
Share on other sites

@DarkGOd the axoff and ayoff are the trimmed offsets, meaning how much transparent pixels were trimmed from the top-left.   I made an image to help explain it better.  It's looks like you got most of this working already.  On the top is the untrimmed image.  The blue dot represents the top left corner of the trimmed image inside the texture atlas.  
embeddedatlasinfo.png

Share this post


Link to post
Share on other sites

Ahh got it to work, I had to offset the pivot with xoff/yoff while computing it from w/h instead of aw/ah.

 

I'll submit a pull request with atlas data.

Which reminds me the scml file does not in any way reference the atlas png file; thus I always assume the name is the same as the scml but replacing the extension, is that safe?

Share this post


Link to post
Share on other sites

One more silly question, I've made a TriggerObjectInfo class, with superloading void playTrigger() and the object factory returning it.

I can see the constructor being called; but playTrigger never is.

If I call playAllTriggers on my instance it fires all right nd getTriggerCount looks ok, returning mostly 0 and once in a while 1 as it should for my test.

 

I've greped the code for playTrigger and it seems to only ever be called by playEventTriggers; thus explainig the things I see.

Is that part not yet implemented or am I missing something obvious ?

Share this post


Link to post
Share on other sites

Addementum: I've toyed with adding:

if (triggerCount) playTrigger();

At line 19 of triggerobjectinfo.cpp; which seems the logical place to put it but I'm not familiar enough to be sure. Is that sane?

Share this post


Link to post
Share on other sites
9 hours ago, DarkGOd said:

Ahh got it to work, I had to offset the pivot with xoff/yoff while computing it from w/h instead of aw/ah.

 

I'll submit a pull request with atlas data.

Which reminds me the scml file does not in any way reference the atlas png file; thus I always assume the name is the same as the scml but replacing the extension, is that safe?

Yes.

1 hour ago, DarkGOd said:

One more silly question, I've made a TriggerObjectInfo class, with superloading void playTrigger() and the object factory returning it.

I can see the constructor being called; but playTrigger never is.

If I call playAllTriggers on my instance it fires all right nd getTriggerCount looks ok, returning mostly 0 and once in a while 1 as it should for my test.

 

I've greped the code for playTrigger and it seems to only ever be called by playEventTriggers; thus explainig the things I see.

Is that part not yet implemented or am I missing something obvious ?

The question isn't silly at all, and at some point I will update the readme with more detailed explanations for things that people had additional questions about.  The rationale behind the playAllTriggers not being built into the normal playback functions is in case your implementation, for whatever reason, requires a certain timing like having events triggered before or after animation playback, or other non-Spriter game functions, etc, since in many cases the events may be used to trigger other game events you might not want to be tied to the same line of code the actual animation is processed.  So yes, you are supposed to manually call the playAllTriggers function, or just playSoundTriggers, or playEventTriggers if you want to use them. 

1 hour ago, DarkGOd said:

Addementum: I've toyed with adding:


if (triggerCount) playTrigger();

At line 19 of triggerobjectinfo.cpp; which seems the logical place to put it but I'm not familiar enough to be sure. Is that sane?

You would create your own TriggerObjectInfo that's returned from your own ObjectFactory.  In some cases you would want your TriggerObjectInfo to store the name that's passed in the constructor, but it's not necessary.   Here's an example usage:
 

	void MyTriggerInfo::playTrigger()
	{
		if (getTriggerCount()) // meaning if triggerCount > 0
		{
			// do something in my game when this thing is triggered
		}
		// else it wasn't triggered this tick, don't do anything
	}

Here I just used triggerCount() as a true/false value, but alternatively you can use the trigger counts actual int value.   The value would be greater than 1 if the amount of time that passed since the last tick was great enough to pass two event keys (either because the keys were very close together, slowdown occurred in the game, the animation playback speed was very high, etc.   You could either treat all values above 0 as a single trigger (like sound effects are in the example), or you could use the triggerCount as a count for a for loop, pass the trigger count to some other game function, etc.

Please let me know if that was all clear.

 

Share this post


Link to post
Share on other sites

Ah I see yes. I do have my own TiggerObjectInfo.

I never care for playTrigger being called without a count > 0 so overloading setTriggerCount on it to call playTrigger only when count is set over 0 would work as I think it seems to ?

Because calling playAllTriggers every frame seems wasteful to me :)

 

edit:

Oh no I cant .. triggerCount is private not protected :/

Share this post


Link to post
Share on other sites
20 hours ago, DarkGOd said:

Ah I see yes. I do have my own TiggerObjectInfo.

I never care for playTrigger being called without a count > 0 so overloading setTriggerCount on it to call playTrigger only when count is set over 0 would work as I think it seems to ?

Because calling playAllTriggers every frame seems wasteful to me :)

 

edit:

Oh no I cant .. triggerCount is private not protected :/

I might be misunderstanding, but isn't looping through each trigger, and checking for a triggerCount() over 0 then calling playTrigger() if it is, basically the same as looping through each trigger, and within playTrigger() checking for a triggerCount() over 0, and acting on it if it is?   Either way, you'd need to check each trigger's triggerCount, and either way you don't have to do any further processing in playTrigger() if the triggerCount is 0;

Share this post


Link to post
Share on other sites

Hum no, if I superload setTriggerCount like that :

void TE4SpriterTriggerObjectInfo::setTriggerCount(int newTriggerCount)
{
	TriggerObjectInfo::setTriggerCount(newTriggerCount);
	if (newTriggerCount) playTrigger();
}

Then it means that playTrigger is automatically called by the spriterengine code when the event actually triggers; effectively making it a callback (which is what I desire) and thus removing any additional need for a polling by checking every triggers in existence for a count > 0

Share this post


Link to post
Share on other sites
On 6/25/2016 at 9:23 PM, DarkGOd said:

Oh no I cant .. triggerCount is private not protected :/

@DarkGOd Sorry for the extremely late reply.  Feel free to change it to protected, and commit the changes, as your method will be much more efficient in situations where you don't need to have control over exactly when the actions are triggered, and there's no good reason not to have the option.

Share this post


Link to post
Share on other sites

Is there already a scon parser? I'm trying to write one but the scon file is a bit different than the scml.

For instance there is no spriter_data element in the Json file.

The object/array structure is also a bit different ant the xml dom structure

Share this post


Link to post
Share on other sites

I opened the GreyGuy scml file and saved it as scon. Now when I try the file in my scon parser, I have no sprites. I only have bones. There are no sprite objects in the obj_info property of the player entity.

In the scon file there is:

			"name": "Player",
			"obj_info": [
				{
					"h": 10,
					"name": "pelvis",
					"type": "bone",
					"w": 200
				},
              ...,
				{
					"h": 10,
					"name": "back_foot",
					"type": "bone",
					"w": 200
				}
			]

 but the scml file has:


        <obj_info name="p_torso_idle" type="sprite">
            <frames>
                <i folder="0" file="0"/>
                <i folder="0" file="1"/>
                <i folder="0" file="2"/>
            </frames>
        </obj_info>
		...
        <obj_info name="p_foot_walk_a" type="sprite">
            <frames>
                <i folder="5" file="1"/>
                <i folder="5" file="5"/>
            </frames>
        </obj_info>
        <obj_info name="pelvis" type="bone" w="200" h="10"/>
		...
        <obj_info name="back_foot" type="bone" w="200" h="10"/>

Is this normal?

Share this post


Link to post
Share on other sites

I also got atlas loading working in an other way as @DarkGOd did because large files will have multiple atlasses.

I load them using the atlas json files found in the project and adding that info to the image files.

I'ver proposed a merge request.

To load the json files I used the scon wrapper that I wrote.

Share this post


Link to post
Share on other sites

Hi all, Im just getting started using the c++ implementation so forgive me if I am missing something obvious. Are continuous blended animations supported? For example a fast walk generated from a mix between run and walk animations.  I have come across something like what I am describing in the construct 2 implementation.

Ideally blending between multiple animations simultaneously is what I am after. This would allow for a blended rotation animation while also transitioning from walk to run, amongst many other versatile applications. Is this something anyone has experience with in the current implementation?

Cheers.

Share this post


Link to post
Share on other sites
On 7/19/2016 at 5:03 PM, labsin said:

I opened the GreyGuy scml file and saved it as scon. Now when I try the file in my scon parser, I have no sprites. I only have bones. There are no sprite objects in the obj_info property of the player entity.

In the scon file there is:...

but the scml file has:...

Is this normal?

Under File|Other File Actions...|Custom Save Options...
There is a checkbox to 'Save sprite object/frame data in obj_info tags'  to save the frame data like that.  It should effect both the scml and scon file saves.

On 7/22/2016 at 6:59 PM, labsin said:

I also got atlas loading working in an other way as @DarkGOd did because large files will have multiple atlasses.

I load them using the atlas json files found in the project and adding that info to the image files.

I'ver proposed a merge request.

To load the json files I used the scon wrapper that I wrote.

Thank you both @DarkGOd and @labsin

On 7/24/2016 at 6:12 PM, Flon said:

Hi all, Im just getting started using the c++ implementation so forgive me if I am missing something obvious. Are continuous blended animations supported? For example a fast walk generated from a mix between run and walk animations.  I have come across something like what I am describing in the construct 2 implementation.

Ideally blending between multiple animations simultaneously is what I am after. This would allow for a blended rotation animation while also transitioning from walk to run, amongst many other versatile applications. Is this something anyone has experience with in the current implementation?

Cheers.

Hi @Flon - Blending between two animations as a transition is possible:

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



I haven't tested it, but the continuous blending between two animations may be possible using:
 

//double blendRatio = a number between 0 and 1 representing a ratio of the first animation (0) and the second animation (1) (0.5 would be exactly halfway between both)
entityInstance->blend(blendRatio, entityInstance->getTimeRatio());


As far as blending between more than two animations (or blending from a blended state) would be much trickier with the current implementation.  I'll be intentionally vague until things are further along, but Spriter 2 (currently in development and a free upgrade for current Spriter Pro owners) will make things like this much easier to implement, and official implementations will support them.

Share this post


Link to post
Share on other sites

Hi Lucid,

I did not follow all the updates but it seems that there are changes that are not applied to comply with new releases.

1) First one of my user has some nodes with "realname" attribute. For example in void SpriterDocumentLoader::getObjectInfoFromEntityElement(SpriterFileElementWrapper *entityElement, Entity *entity, PointMap *defaultBoxPivotMap), no attribute realname is read. Is it as simple as replacing the name by realname if it exists? SOmething like this:

SpriterFileAttributeWrapper *att = objInfoElement->getFirstAttribute("realname");
            if (!att->isValid())
            {
                att = objInfoElement->getFirstAttribute("name");
            }

I suppose that it leads to the problem I have that collision boxes used do not appear anymore. This user released with r7 version. Are there any other nodes which have this "new" attribute?

2) I saw that atlases were added? Do you have any example code which overrides this new feature? I must admit that I did not try this feature in Spriter itself and does not know exactly how it works.

Edited by conceptgame
Added solution suggestion

Share this post


Link to post
Share on other sites

@conceptgame you shouldn't have the 'realname' attribute unless you have "Make object names in obj_info tags unique to the entire file" checked under File|Other File Actions...|Custom Save Options... 
1) If you don't have it checked and it's still appearing, this is a bug.  Let me know and I'll fix it in the upcoming version.

edit:  The purpose of the option is if for whatever reason you need every object to have a unique name.  Since you can have two entities in the same file in Spriter, say "goblin", and "human", and they both may have an sprite object named "head", this will just change the sprite names to "goblin_head" and "human_head" and realname will contain the original unaltered name, mainly for Spriter's use, but also if you want users of your runtime to be able to directly access sprites and other objects using the names they see in Spriter.  If you have no need to make sure there can't be two objects of the same name in two entities, then I recommend just keeping the checkbox off, since there's no need for the extra tag.


2) Atlas tags should also not show up unless you use atlases.  Labsin's most recently committed code implements loading from atlases at runtime.

Share this post


Link to post
Share on other sites

Hi Lucid,

1) I did not generate the file myself. I will forward the answer to the user who is using my extension for Clickteam Fusion 2.5. If he wants to keep this option checked, what should be done?

2) For the atlases, I just merged the last commit and noticed that the code was not retrocompatible anymore. Nothing serious but it makes me wonder what I can do with it.

Thanks for your answer.

Share this post


Link to post
Share on other sites

It is more how can I use atlases in the current implementation. If I understood properly, by creating image file, I can use the sprite sheet image files instead of all single files and then by rendering I can use the AtlasData container from AtlasFile to locate the sprite on the spritesheet and draw it properly.

Did I understood correctly that I only need to change the rendering method to get it work? In other words, is the loading of atlases already implemented. I saw Labsin's commit but I did not check in Details.

This change can improve the performance of the game engine and it is really cool if we can already use it.

 

Share this post


Link to post
Share on other sites

Hi @conceptgame -  What you're saying sounds about right.  It loads everything from a scon file into the atlas data structures, and from there it's up to the implementation to perform the rendering.

From @labsin's commit details:

Load all the info needed from projects with (multiple) atlas files.

The engine reads the json files listed in the project using the scon
wrapper.

It creates an Atlas file for each atlas image. These can be subclassed
in the implementations and are returned from the filefactory.

Each ImageFile gets an AtlasFile and atlasframedata set in setAtlasFile
wich can also be overwritten in the subclass if more action is needed.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...