axmolengine / axmol

Axmol Engine – A Multi-platform Engine for Desktop, XBOX (UWP) and Mobile games. (A fork of Cocos2d-x-4.0)
https://axmol.dev
MIT License
922 stars 205 forks source link

Problems with Physics when use animation #1573

Closed halx99 closed 10 months ago

halx99 commented 10 months ago

Discussed in https://github.com/axmolengine/axmol/discussions/1545

Originally posted by **paulocoutinhox** December 26, 2023 Hi, I have a small problem when i use Physics with animation. I made a small project to show: https://github.com/paulocoutinhox/axmol-bomb-test When i use this code: ``` // create animation auto animation = Animation::createWithSpriteFrames(frames, 0.1f); auto animate = Animate::create(animation); auto bomb = Sprite::create(); bomb->runAction(RepeatForever::create(animate)); bomb->setName("shoot"); bomb->setContentSize(Vec2(78, 64)); // setup the physical body auto physicsBody = PhysicsBody::createCircle(bomb->getContentSize().width / 2, PhysicsMaterial(1.0f, 0.1f, 0.8f)); physicsBody->setMass(1.0f); bomb->setPhysicsBody(physicsBody); ``` The bomb goes to: ss3 But when i use this code: ``` auto bomb = Sprite::create( "bomb.png"); bomb->setName("shoot"); bomb->setContentSize(Vec2(78, 64)); // setup the physical body auto physicsBody = PhysicsBody::createCircle(bomb->getContentSize().width / 2, PhysicsMaterial(1.0f, 0.1f, 0.8f)); physicsBody->setMass(1.0f); bomb->setPhysicsBody(physicsBody); ``` The bomb goes to down, correctly. Thanks.
paulocoutinhox commented 10 months ago

Hi,

I have updated the bomb-test project to create two sprites, one with animation and other without animation and left then fall with physics/gravity: https://github.com/paulocoutinhox/axmol-bomb-test

I also make a video to ppl understand:

https://github.com/axmolengine/axmol/assets/395096/fe3bf581-8a59-4551-abac-02b511de56fd

aismann commented 10 months ago

As @rh101 has written: The only thing I've noticed is that a person can get a lot more help if they have shown that a decent amount of effort has been put into tracking down the source of the issue (simply debugging may not be enough).

@paulocoutinhox I have made a small look on it, it needs more time.

It's only a known bug at this moment...may not easy to fix it in short time. So please give some more help.

rh101 commented 10 months ago

@paulocoutinhox If you printed out the PhysicsBody variables for each of the two sprites, such as position, rotation etc. etc., you would have noticed that nothing is different between the two objects, except the position.

You could have narrowed the issue down further by checking the Animate code to see what it actually does, and eventually you would have realised that the only lines that modify the sprite are these 2 lines:

static_cast<Sprite*>(_target)->setSpriteFrame(_origFrame);
static_cast<Sprite*>(_target)->setBlendFunc(blend);

setBlendFunc doesn't affect anything, so the only thing left to check is setSpriteFrame. Placing a breakpoint in that method to trace through it and see what code is executed would lead you to the only line that would make sense to affect anything, being: setTextureRect(spriteFrame->getRect(), _rectRotated, spriteFrame->getOriginalSize());

The contents of the setTextureRect is:

void Sprite::setTextureRect(const Rect& rect, bool rotated, const Vec2& untrimmedSize)
{
    _rectRotated = rotated;

    Node::setContentSize(untrimmedSize);
    _originalContentSize = untrimmedSize;

    setVertexRect(rect);
    updateStretchFactor();
    updatePoly();
}

For a quick test, one could simply comment out the contents to see what happens, and funny enough, things start working. Uncommenting lines until the issue re-appears would reveal this one line as the culprit: Node::setContentSize(untrimmedSize);

Putting a breakpoint on that call shows the untrimmedSize as 97.5, 80.0.

Now, a few things to note:

1 - The frame sizes are all 78,64, as shown in the PLIST file. 2 - Your own code has this call: bomb->setContentSize(Vec2(78, 64)); 3 - They don't match the untrimmedSize being set, implying that some kind of content scaling is happening, most likely due to Director::setContentScaleFactor

If you comment out the hard-coded line, bomb->setContentSize(Vec2(78, 64));, then everything starts working, although your sprite without animation is huge and incorrect: image

That is because of this line: bomb->setTexture("bomb.png");. You're using the entire sprite sheet for the image texture, which is strange. It would have made more sense to call this: bomb->setSpriteFrame("frame1.png");, but that would require fixing other issues in your code, such as ensuring that the sprite sheet is loaded only once, and not just for the animated sprite.

So, move this code to MainScene::init(), which is where it belongs:

auto spriteFrameCache = SpriteFrameCache::getInstance();
spriteFrameCache->addSpriteFramesWithFile("animation.plist", "bomb.png");

To unload it, you can do so in the destructor, MainScene::~MainScene, or when you're done with it (meaning no sprites references those textures).

Now, knowing that removing the call bomb->setContentSize(Vec2(78, 64)); fixes the issue means something else is happening.

Remember the untrimmedSize as 97.5, 80.0, well that means things are being scaled. So, based on that, testing with a content scale factor to 1.0, via director->setContentScaleFactor(1.0f);, then everything also works correctly, regardless of whether the call to bomb->setContentSize(Vec2(78, 64)); is made or not.

The content scale factor of 1 would mean the untrimmedSize is always 78,64, so the call to Node::setContentSize(untrimmedSize); doesn't actually do anything, given the original size is the same as the new size:

void Node::setContentSize(const Vec2& size)
{
    if (!size.equals(_contentSize))
    {
        _contentSize = size;

        _anchorPointInPoints.set(_contentSize.width * _anchorPoint.x, _contentSize.height * _anchorPoint.y);
        _transformUpdated = _transformDirty = _inverseDirty = _contentSizeDirty = true;
    }
}

The only thing I can think of is that changing the size of the sprite after the physics body has been created is somehow affecting the transforms, which are used in calculations within the PhysicsBody.

So, in summary, what works is any of the following:

Delete the line bomb->setContentSize(Vec2(78, 64));

or

Set it to the correct scaled size (being 97.5,80) via: bomb->setContentSize(Vec2(78, 64) / AX_CONTENT_SCALE_FACTOR());

or

Only set the content size of the sprite if and only if the sprite is not animating, so calling bomb->setContentSize(Vec2(78, 64)); on the non-animating sprite is fine, since the size never changes.

or

Use a content scale factor of 1.0 via director->setContentScaleFactor(1.0f);

The most important thing to remember is: The content size of a sprite cannot be changed after the physics object is set on a node. This means you should not call setContentSize() with a different size (directly or indirectly) after you have set the physics body on the sprite. You can scale the sprite, rotate it, or anything else, just do not call setContentSize once a physics body is attached. This also means sprite frames in an animation must all be the same size.

In the end, this entire issue can be reproduced with a few lines that have nothing to do with Animate:

auto sprite = Sprite::create("SomeImage.png");
auto physicsBody = PhysicsBody::createCircle(sprite->getContentSize().width / 2, PhysicsMaterial(1.0f, 0.1f, 0.8f));
physicsBody->setMass(1.0f);
sprite->setPhysicsBody(physicsBody);
sprite->runAction(Sequence::createWithTwoActions(DelayTime::create(1.5f), CallFunc::create([sprite]() {
    sprite->setContentSize(sprite->getContentSize() / 2); // Problem will show itself after 1.5 seconds, when setContentSize is called with a different size
})));
aismann commented 10 months ago

Added a wiki hint

paulocoutinhox commented 10 months ago

Hi,

In my code don't have anything changing content size after set physics body. I don't know if you see my code, but i will paste it here:

Sprite *MainScene::createBomb(bool animated)
{
    auto bomb = Sprite::create();

    if (animated)
    {
        // create animation
        auto spriteFrameCache = SpriteFrameCache::getInstance();
        spriteFrameCache->addSpriteFramesWithFile("animation.plist", "bomb.png");

        // create sprites
        Vector<SpriteFrame *> frames;
        char frameName[100];

        for (int i = 1; i <= 4; i++)
        {
            sprintf(frameName, "frame%d.png", i);

            SpriteFrame *frame = spriteFrameCache->getSpriteFrameByName(frameName);

            if (frame)
            {
                frames.pushBack(frame);
            }
        }

        // create animation
        auto animation = Animation::createWithSpriteFrames(frames, 0.1f);
        auto animate = Animate::create(animation);

        bomb->runAction(RepeatForever::create(animate));
    }
    else
    {
        bomb->setTexture("bomb.png");
    }

    bomb->setName("shoot");
    bomb->setContentSize(Vec2(78, 64));

    // setup the physical body
    auto physicsBody = PhysicsBody::createCircle(bomb->getContentSize().width / 2, PhysicsMaterial(1.0f, 0.1f, 0.8f));
    physicsBody->setMass(1.0f);
    bomb->setPhysicsBody(physicsBody);

    return bomb;
}

It create a animated or not sprite, and at last, it add physics body.

The line that change something on it and make it work is related to scale as you write above, not content size position in my code, since it is before physics body definition:

bomb->setContentSize(Vec2(78, 64) / AX_CONTENT_SCALE_FACTOR());
or
bomb->setContentSize(Vec2(78, 64) / _director->getContentScaleFactor());

Thanks.

rh101 commented 10 months ago

In my code don't have anything changing content size after set physics body. I don't know if you see my code, but i will paste it here:

As already mentioned in my previous post, setContentSize cannot be called after the physics body is set, whether it is from your own code or indirectly through other code paths. In your case, it is done so via setSpriteFrame, which is called by via the Animate action, as I explained in detail in my previous post.

The line that change something on it and make it work is related to scale as you write above, not content size position in my code, since it is before physics body definition:

That is not correct. It is definitely setContentSize that is causing the issue, and nothing to do with scaling the object. Just because the content scale factor is used doesn't mean the object is being scaled. You're just setting the correct size by using it, which means when the animation is playing, the size is exactly the same, so all calls to setContentSize made during the animation will not change anything, because of this if (!size.equals(_contentSize)) check in Node::setContentSize.

If that is still not completely clear, just re-read the post I made above, and follow the code path yourself to see what actually happens.

paulocoutinhox commented 10 months ago

Hi, relax man, i read it, all lines.

  1. I only set the same sprite to not animated object ONLY to use the same image in my test, i already know that it is not beautiful, but it is only a test.
  2. I already understand that cant change content size after add physic body.

What i do to solve is set content size with original frame size and change only the scale:

bomb->setName("shoot");
bomb->setContentSize(Vec2(78, 64) / AX_CONTENT_SCALE_FACTOR());
bomb->setScale(0.5);

This is wrong?

rh101 commented 10 months ago

This is wrong?

Sorry, I'll clarify. This code is correct: bomb->setContentSize(Vec2(78, 64) / AX_CONTENT_SCALE_FACTOR());

What is not correct is this remark regarding the content size and your usage of it:

The line that change something on it and make it work is related to scale as you write above, not content size position in my code, since it is before physics body definition:

The reason is that the line bomb->setContentSize(Vec2(78, 64) was setting a content size of 78,64, but when it is animating, setSpriteFrame was calling setContentSize with 97.5, 80.0, because of the scale factor. As a result of this, whatever parameters are changed within the node end up affecting the physics object. That is why the fix is to divide by the content scale factor when setting the content size, since future calls to the setContentSize by the Animate code will no longer trigger changes in that node.

paulocoutinhox commented 10 months ago

Nice. Thanks.

paulocoutinhox commented 10 months ago

rsrsrsrs

Animation

aismann commented 10 months ago

@paulocoutinhox

rsrsrsrs

[Animation]

What is the reason to put it here?

Please close this issue if is "fixed".

paulocoutinhox commented 10 months ago

I can't, it was not open by me