• Runtimes
  • Upgrade from cocos2d-x v2 to v3 scale issue

  • تم التحرير
Related Discussions
...

Hi Nate,

I'm in the process of migrating from cocos2d-x v2 to v3 on my project and I've an issue with spine.
I've grabbed the latest spine-runtime from git and put it in my project.

Animations and events are working fine...but I've a scaling issue.
All my skeletons are half the size they should be. I've regenerated my json using the latest spine editor but no changes.

I'm simply creating my skeletons using SkeletonAnimationBox::createWithFile, without specifying a scale value (using 1.0f default).

My cocos2d-v2 version is using spine 579564906d0893c8326c45e992bcaa5ea5226b55
My cocos2d-v3 version is using spine 968f408445f5a5dde3a5bb11324ebda73c8b7820

I've inspected the values in a call to spRegionAttachment_computeWorldVertices, and the values in the float* array 'self->offset' in my v3 project are half the values of my v2 project, and I don't know why.

Do you have any idea what could be wrong?
Have you change some critical computation somewhere around those versions of spine, of is it because of cocos-v3?
Thanks for any help you can provide!

Kiki


Ok I might have found why (easier than expected to find the culprit line):

my old spine version:

CCSkeleton::CCSkeleton (const char* skeletonDataFile, const char* atlasFile, float scale) {
...
json->scale = scale == 0 ? (1 / CCDirector::sharedDirector()->getContentScaleFactor()) : scale;
...

my new spine version:

SkeletonRenderer::SkeletonRenderer (const char* skeletonDataFile, const char* atlasFile, float scale) {
...
json->scale = scale / Director::getInstance()->getContentScaleFactor();
...

in both cases, the 'scale' parameter is 1.0f (default value), and getContentScaleFactor is 2.0f

In previous spine version, director's getContetnScaleFactor is not called, 'scale' parameter is directly applied to the skeleton.
In new spine version, the passed 'scale' is always corrected by the director's scaleFactor.

Can you explain why this change (bug or needed)?
Thanks!

kiki

The director's scale is always applied now. This is because it was not easy to figure out the previous way.

Hum ok but then, how do you handle multiple-resolution assets now?

All my assets have been designed at maximum resolution (iPad retina: 2048x1536).
I'm then using TexturePacker to generate SD assets (for iPad: 1024x768) with a scale factor of 0.5

I've designed my spine animations using maximum assets, and I have applied a 0.5 scale factor on the root bone, so the skeleton is now on a non-retina scale (1024x768).

My game uses a "design resolution" of 1024x768.
The director's contentScale is then configured depending on the device :

  • iPad retina uses the iPadRetina assets (2048x1536) so the contentScale is set to 2.0f (since designResolution is 1024x768)
  • iPad uses the iPad assets (1024x768) so the contentScale is set to 1.0 (designResolution is still 1024x768)

It used to work properly that way with old spine behaviour (json->scale not altered automatically based on director's ContentSize).

Now take a look at the sample project I made:
The skeleton has been designed using Retina assets and only has one bone and image of 200x200px size.
The designResolution of the project is 1024x768, so my skeleton should be on screen a 100x100px size box whatever the device (a Retina Ipad should use the 200x200 image but the director's contentScale is set to 2.0f, while the iPad uses the 100x100 image with a contentScale of 1.0f).

To run the sample as an iPad, do not pass any argument to the program :
You should see a blue box in the bottom left corner of the screen (it's a simpe sprite to show the size of a 100x100px box)
You'll see that the skeleton in the middle of the screen is twice the size if should be (200x200 pixels).

To run the sample as a Retina iPad, pass any argument to the program :
You should see a red box in the bottom left corner of the screen (it's a simple sprite to show the size of a 200x200px box rendered with a contentScaleFactor of 2.0f to simulate retina assets).
You'll see that the skeleton in the middle of the screen is now at the correct size.

Please, tell me how to properly make a multi-resolution game with this change in the runtime.
Thanks a lot, as usual!
Kiki

The image in your Spine project is 200x200. You pass 1 as a scale to SkeletonAnimation::createWithFile.

When passing no args to your program, I see getContentScaleFactor=1 and the skeleton drawn on screen is 200x200.

Image removed due to the lack of support for HTTPS. | Show Anyway

When passing an arg to your program, I see getContentScaleFactor=2 and the skeleton drawn on screen is 100x100:

Image removed due to the lack of support for HTTPS. | Show Anyway

I believe these are both correct.

do not pass any argument to the program: You'll see that the skeleton in the middle of the screen is twice the size if should be (200x200 pixels).

Why do you think it should be 100x100? It is 200x200 in your Spine project, you pass 1 as a scale to createWithFile, and getContentScaleFactor=1 so it should be drawn at 200x200 (first screenshot above). When getContentScaleFactor=2 it should be drawn at 100x100 (second screenshot above).

If you want to use 200x200 in Spine and 100x100 at runtime, then pass 0.5 as scale to createWithFile or set the root bone scaleX and scaleY to 0.5. When getContentScaleFactor=1 it will be drawn at 100x100. When getContentScaleFactor=2 it will be drawn at 50x50.

This is not how cocos2d handles multiple resolution assets.

In one's cocos2d code, you always use points (not pixels) to specify node position and size.
You setup a "designResolution" for your game, and it's the same for all devices (I'm using here an example of only 2 devices for simplicity).
That way, a node of 100x100 points that you place in the center of the designResolution box (I'm using 1024x768 as designResolution in my example), should have a position of 512x384 (whatever the device actual screen resolution).

How it is handled by the game engine is:
You setup the glView's designResolution to 1024x768 and it internally computes a scale (based on the view's real size) to be applied to all objects.
In the case of an iPad with a 1024x768 screen resolution, that scale (named 'viewScale') is 1.0f
In the case of an iPadRetina with a 2048x1536 screen resolution, that scale is 2.0f

This alone allows the same code to display your game objects at the same place on both devices.

Now to pervent the upscale of the sprites on the iPadRetina, we create multiple assets.
One at 1.0f scale (named iPadAssets) and one at 2.0f scale (named iPadRetinaAssets).
A good practice is to store those assets in a different folder (like you did in your example project), and setup cocos2d search path according to the assets you want to load. Then you have to set the director's contentScaleFactor to match the assets you loaded, based on your designResolution.

So in the case of an iPad, you load the 1.0f assets, and set a 1.0f contentScaleFactor. That way, a sprite of 100x100 points will be displayed using a 100x100 pixels image (stored in the 'ipad' folder).
In the case of an iPadRetina, you load the 2.0f assets and set a 2.0f contentScaleFactor. That way a sprite of 100x100 points will be displayed using a 200x200 pixels image (stored in the 'ipad-retina' folder) but this is 'normal' since we have a 2.0f contentScaleFactor.
This method prevents dynamic upscaling of the textures (at runtime).

Now the perfered method to create your assets, is to make the maximum size assets, and downscale all sub-resolutions.
So I create a 200x200 pixels image that I save in the ipad-retina folder, then downscale (TexturePacker does that automatically for me) the 100x100 pixels image to be saved in the ipad folder.

So (sorry for the long explanation), whatever the real screen size, my sprite should always be displayed with the same points size.
This is what my blue and red boxes show in the bottom left corner of the screen.
The skeleton should do the same thing automatically for me (it used to do that correctly in the old runtime version I used), the easiest way being to create the skeleton using the maximum resolution images of course (in previous runtime version, I had to put a 0.5 scale to the root bone, but that not an issue if I have to do that again).

Do you understand what I mean, or my explanation is not clear?
Thanks,
Kiki

Yikes, wall of text! There may be a problem with the Spine runtime, but I haven't seen proof yet. Check out this image:

Image removed due to the lack of support for HTTPS. | Show Anyway

The 100x100 image is 100x100 pixels. The 200x200 image (which is the Spine skeleton) is 200x200 pixels. This is correct because you passed 1 to createWithFile which means your skeleton will be the same size it was in Spine, which is 200x200. If you want your "design resolution" skeleton to be 100x100, then you need to pass 0.5 to createWithFile. Doing that results in:

Image removed due to the lack of support for HTTPS. | Show Anyway

Now your skeleton is displayed at 100x100 "points" when contentScale=1. Here is the same app when contentScale=2:

Image removed due to the lack of support for HTTPS. | Show Anyway

This is also correct. The red box on the left is 200x200 points. The red box on the right is your skeleton and is 100x100 points, which you'll note is the same point size when contentScale=1.

Note passing 1 to createWithFile and setting the root bone scaleX and scaleY to 0.5 is the same as passing 0.5 to createWithFile and leaving the root bone scaleX and scaleY alone. The difference is described here:
http://esotericsoftware.com/spine-using ... s/#Scaling

Yes, but that is not correct.

In both cases, the displayed image for the skeleton should be exactly the same (exactly like my sprite cubes are displayed with the same size).

My cubes uses the multiple-resolution code recommended for cocos2d applications, and they both have the same "proportional" size on the iPad and iPadRetina screen. This is the correct behaviour (and this is the behaviour the old spine runtime had).

While I understand I do not pass any value to the createWithFile call, based on cocos2d recommended code, I should always pass the same value whatever the real screen resolution is (I don't mind passing 0.5f, but I should do that in all cases).

Carefully look at my cubes in the bottom-left part of the screen and you'll see that their proportional size (relative to the viewport of the sample) is always the same. This is what the designResolution code is for.
You might wonder why the actual window size are the same, this is because I applied a viewport scale of 0.5f on the retina version of it would go beyond my screen's size. But you can disable this by removing the glview's scale in main.cpp.
Another thing to notice, the skeleton's image is always the red cube with the 200x200 text in it, because I used spine's texturePacker to generate the skeleton's atlas file and texture in the 0.5 scale, so I couldn't modify the image to put the 100x100 text in it (well... I could have edited the texture afterwards, it would have cause less confusion maybe).

I don't know yet how to solve this issue, but I don't think the spine-runtime should do anything with the director's scale value, because it's already been taken into account internally.

Ok, if that is how it should work I'm ok with it. I've committed it for all cocos2d-based runtimes. Sorry it took so much explaining before I gave in! 🙂

Not a problem, happy to help improve this great piece of software 😃
I'll give it a try later today.

Edit:
Works perfectly once again, thanks a lot!