• Editor
  • Retrieve Slot position during animation?

It is possible to extract the position and size of a slot during animation from a CCSkeleton node? Given that I could use it as a bounding-box. That would be another amazing feature! :clap:

BTW we're using the the Cocos2d-iphone runtime.

Related Discussions
...

Slots don't have a size or position. You can get the world position, rotation, scale of a bone or the size and position of a region attachment in a slot.

CCSkeleton* node = ...

Cocos2dRegionAttachment* attachment = (Cocos2dRegionAttachment*)Skeleton_getAttachmentForSlotName(node->skeleton, "slotname", "attachmentname");
// Do something with attachment->quad

Bone* bone = Skeleton_findBone(node->skeleton, "bonename");
// Bone has worldX, worldY, worldRotation, worldScaleX, worldScaleY
// Also has m00, m01, m10, m11 which is 2x2 rotation matrix.

Thanks, and sorry for the delay!!

I finally made it, but I'm not very sure that is correct way. Let me show:

I use this function to find the slot index because we have different attachments in it, so we want the size of the slot every time:

ccV3F_C4B_T2F_Quad* quad2 = &((Cocos2dRegionAttachment*)Skeleton_findSlot(skeleton, "head")->attachment)->quad;

But the problem comes on when I use this other function, if I use it in my own class:

Skeleton *mySkeleton = skeletonNode->skeleton; //SkeletonNode is init before 
Slot *slot = Skeleton_findSlot(mySkeleton, "head");
ccV3F_C4B_T2F_Quad* quad = &((Cocos2dRegionAttachment*)slot->attachment)->quad;

I get a correct position except when attachment change.

But if I get the position in the draw method of spine-cocos2d-iphone.m

- (void) draw {
	CC_NODE_DRAW_SETUP();

	ccGLBlendFunc(blendFunc.src, blendFunc.dst);
	ccColor3B color = self.color;
	skeleton->r = color.r / (float)255;
	skeleton->g = color.g / (float)255;
	skeleton->b = color.b / (float)255;
	skeleton->a = self.opacity / (float)255;

	quadCount = 0;
	for (int i = 0, n = skeleton->slotCount; i < n; i++){
		if (skeleton->slots[i]->attachment) Attachment_draw(skeleton->slots[i]->attachment, skeleton->slots[i]);
    }
    if (atlas) [atlas drawNumberOfQuads:quadCount]; // in this function the quad position change?

    //this is my modification to test that quad is correct
    ccDrawColor4B(255, 0, 255, 255);
    glLineWidth(1);
    CGPoint points2[4];
    ccV3F_C4B_T2F_Quad* quad2 = &((Cocos2dRegionAttachment*)Skeleton_findSlot(skeleton, "head")->attachment)->quad;
    points2[0] = ccp(quad2->bl.vertices.x, quad2->bl.vertices.y);
    ...//rest

the result is perfect:

 Loading Image

I hope you could understand my explanation... my english is not very good... :sweat:

I've added RegionAttachment_updateQuad(attachment, slot). This updates the quad vertices like drawing does, and returns it. So you can change an attachment and then get the quad position without drawing first. You can also position a skeleton (with an animation or manually) and call skeleton update transform, then get the updated quad position without waiting for a draw.

Nate, you are much faster than me :bang:

Thanks :rofl:

un mois plus tard

I'm trying to get the literal world position of a slot, in this case to locate the tip of my gun sprite. I've dug into the code in this thread, but I can't figure out exactly what the "world" and quad positions mean. Are they relative to the bone they're on? Any suggestions how to find the position of a slot in "cocos2d space"?

Slots don't have a position, they are just a named placeholder for an attachment. All attachments for a slot are relative to the slot's bone. You can however get a bone position:

Bone* bone = Skeleton_findBone(skeletonNode.skeleton, "boneName")
// Do something with bone->worldX and bone->worldY.

The worldX and worldY are in the coordinate system of the CCSkeleton. If you have just posed your skeleton with an animation or otherwise modified the local transform of any bones, you can call Skeleton_updateWorldTransform before using the bone's worldX/Y so your local changes are reflected. You don't need to mess with RegionAttachment_updateQuad unless you want the world vertices for an image.

The easiest way to position something at the tip of your gun image is to place a bone there (as a child of the bone the gun image is attached to), then get the "gun tip" bone and use its worldX/Y.

If you have many different guns it isn't feasible to have a bone for each one. In this case, for each gun you can have an offset from the bone the gun image is attached to. Use the bone's worldX/Y and also it's worldRotation and optionally worldScaleX/Y to transform the offset. I can help with this if needed.

3 mois plus tard

Hi All,

I need to achieve something similar: I'm using cocos2dx and I want to add a CCSprite on top of a bone (I don't have z-order issues in-between the parts of my skeleton, I want the sprite always visible).

I've read this thread and I almost managed to achieve this by manually setting my sprite position using the worldX/Y of the bone during the update event. Then I set the rotation (the addition of the worldRotation of the bone, and the rotation of the skeleton itself).

But I have a slight issue, if you look closely, you'll see that the sprite is not exactly "glued" to the head, you can see it slightly moving apart from the head bone.

Here's a modified version of ExampleLayer.cpp from the cocos2dx example from the spine-runtime repository:

#include "ExampleLayer.h"
#include <iostream>
#include <fstream>
#include <string.h>

using namespace cocos2d;
using namespace spine;
using namespace std;

CCScene* ExampleLayer::scene () {
	CCScene *scene = CCScene::create();
	scene->addChild(ExampleLayer::create());
	return scene;
}

static CCSprite* sprite;

bool ExampleLayer::init () {
	if (!CCLayer::init()) return false;

skeletonNode = CCSkeletonAnimation::createWithFile("spineboy.json", "spineboy.atlas");
skeletonNode->setMix("walk", "jump", 0.2f);
skeletonNode->setMix("jump", "walk", 0.4f);

skeletonNode->setAnimation("walk", true);

skeletonNode->timeScale = 0.3f;
skeletonNode->debugBones = true;

skeletonNode->runAction(CCRepeatForever::create(CCSequence::create(
	CCMoveBy::create(2.0f,ccp(200.0f,0.0f)),
	CCMoveBy::create(2.0f,ccp(-200.0f,0.0f)),
	CCRotateBy::create(2.0f,90),
	CCRotateBy::create(2.0f,-90),
	NULL)));

CCSize windowSize = CCDirector::sharedDirector()->getWinSize();
skeletonNode->setPosition(ccp(windowSize.width / 2, 20));
addChild(skeletonNode,1);

sprite = CCSprite::create("sprite.png");
CC_SAFE_RETAIN(sprite);
addChild(sprite,2);

scheduleUpdate();

return true;
}

void ExampleLayer::update (float deltaTime) {
	//NO EFFECT: skeletonNode->updateWorldTransform();
	Bone* head = skeletonNode->findBone("head");
	CCPoint pos = ccpAdd(ccp(head->worldX,head->worldY),ccp(30,0));
	pos = skeletonNode->convertToWorldSpace(pos);
	sprite->setPosition(pos);
	sprite->setRotation(skeletonNode->getRotation()+head->worldRotation);
}

Just use any "sprite.png" file, it doesn't matter (well, not something too big or too small, something like 100x100 is fine). You can change my "30,0" value to offset the sprite to have a better reference (like having the sprite aligned with the head debug bone, and in this case you'll see the debug bone line being overlapped sometimes by the sprite)

I've tried to updateWorldTransform just before getting the bone with no effect.

Any idea?

Thanks for any help anoyone can provide!

I'm guessing ExampleLayer updates first and sets the CCSprite position, then the CCSkeleton updates which increments the animation time, changing the bone position, then drawing happens and the CCSprite is not at the bone position. I'm not familiar with how cocos2d handles this sort of update order problem.

Cocos2d schedules updates in the order they are registered if no priority is specified (which is the case in the Example program).

So in the sample I provided which exposes the bug, the order is as it should be:

  • First, the skeleton is updated (it registered the update schedule during it's creation)

  • Then, the ExampleLayer is updated, using the correctly computed bones position to force the position of the sprite

And if you run the sample, you'll see something wrong with the sprite position. It's slight but visible, and it may be much visible depending on the case (in my game, it's quite visible).

Hi,

did you get a chance to run the example and see the issue I'm talking about?

Sorry, I'm a bit behind catching up to forum posts.

You need to set the anchor point and adjust how you apply rotation to the sprite:

    void ExampleLayer::update (float deltaTime) {
       Bone* head = skeletonNode->findBone("head");
       CCPoint pos = ccp(head->worldX,head->worldY);
       pos = skeletonNode->convertToWorldSpace(pos);
       sprite->setPosition(pos);
       sprite->setAnchorPoint(ccp(0,0));
       sprite->setRotation(skeletonNode->getRotation() - head->worldRotation + 90);
    }

Thanks Nate, I found the culprit. It was not the anchor point (since it's local for the sprite), but both the rotation and the ccpAdd I used to offset the sprite. I added the offset directly in the bone nodespace but it was expressed in the world nodespace... couldn't work correctly.
The offset should be added after the convertToWorldSpace, but it must take the rotation into account then.

Edit:
Well, the offset can be added before converting to WorldSpace, but it must take head bone rotation into account too. I'm not sure which is the simpliest:

  • Using informations in the bone/skeleton to get new worldX/Y values that take my offset (from the bone) into account, directly in the skeleton nodespace, then use convertToWorldSpace

  • Use skeleton+bone rotation to compute the projected offset in world nodespace and apply it right before setting sprite position

  • Create a new "dummy" bone in the skeleton at the exact position I need (but it might not be feasable for my current project). This solution is a last resort.

Any suggestion? How easy would it be to, for example, compute a ccp(30,0) offset from the bone using bone/skeleton informations? (I guess I have to apply the bone rotation matrix, but it implies that I use a matrix too).

Thanks anyway, keep up the good work

Why doesn't my code work? The anchor must be the bone location so the origin used to rotate the sprite matches the origin used to rotate the bone. The anchor point is expressed local to the sprite, but it affects the sprite's position. Position the sprite at the bone position, then adjust the anchor to offset the sprite from the bone position. Eg, 0,0 puts the sprite's bottom left corner at the bone position. 1,1 puts its top right corner at the bone position. 0.5,0.5 centers the sprite at the bone position.

You code works perfectly (no more slight movements of the sprite), but I want to specify a greater offset than the sprite's width (I'm not sure cocos2d likes an anchor point greater than 1.0).

My sprite is approx 30px wide, and I want to offset it almost 100px from the bone's origin (but along the bone's x direction).
Plus I'd rather keep the anchor to the middle of the sprite (0.5,0.5) so I don't have to compute another offset to place it correctly once I get the center point (world coords) where I want to place it.

I know I could create another bone at the exact position instead of computing the offset at runtime, but this might not be feasable (multiple reasons, including runtime modification of the offset).

That's why I'm looking for a way to add another offset (I know the offset in the bone's nodespace) at runtime.

I'm not a math expert but I think of 2 solutions like I said in previous post, either using bone's matrix (don't know how yet), or computing the offset directly in world nodespace.

setAnchorPoint docs say outside of 0-1 is fine, but try this:

   void ExampleLayer::update (float deltaTime) {
      Bone* head = skeletonNode->findBone("right shoulder");
		float x = 40, y = 40;
      CCPoint pos = ccp(x * head->m00 + y * head->m01 + head->worldX, x * head->m10 + y * head->m11 + head->worldY);
      sprite->setPosition(skeletonNode->convertToWorldSpace(pos));
		sprite->setRotation(skeletonNode->getRotation() - head->worldRotation + 90);
   }

Awesome! You're the best!

This should be put somewhere so that people trying to do the same thing can find it easily: putting a cocos2d sprite on top of a skeleton bone (following position and rotation of the bone), with an optional offset from the bone.

Thanks a lot, once again.