0
votes

A sprite has a physics body. When the sprite moves in any direction, the physics body is displaced in that direction (see the attached image). This is bad for the contact and collision.

The physics body is displaced during the movement only. When the sprite stands still, the physics body is not displaced.

My question: why is the physics body displaced in the movement direction in sprite kit? Is there any way to prevent it?

Thanks a lot

EDIT:

OK.. I wrote a small test to demonstrate this issue. I have only one node with physics body which is not affected by gravity. The physics world also has no gravity. By touching the screen, the hero moves upwards. If you please test the code, set the SKView showsPhysics property = YES. By touching, watch how the physics body moves from its correct place. It is then displaced in the movement direction all the time of the movement.

Thank you for any suggestions.

// header file

#import <SpriteKit/SpriteKit.h>

@interface TestScene : SKScene <SKPhysicsContactDelegate>

@end


// implementation file


#import "TestScene.h"

@implementation TestScene
{
    SKNode *_world;
    SKSpriteNode *_hero;

    BOOL _play;
    BOOL _touch;
    CGFloat _startY;
}

- (instancetype)initWithSize:(CGSize)size
{
    if(self = [super initWithSize:size])
    {
        self.backgroundColor = [SKColor whiteColor];
        self.physicsWorld.contactDelegate = self;
        // no gravity
        //self.physicsWorld.gravity = CGVectorMake(0.0f, -9.8f);

        _play = NO;
        _touch = NO;
        _startY = 50;

        [self addWorld];
        [self addHero];
    }

    return self;
}

- (void)addWorld
{
    _world = [SKNode node];
    _world.position = CGPointMake(0, 0);
    [self addChild:_world];
}

- (void)addHero
{
    _hero = [SKSpriteNode spriteNodeWithImageNamed:@"hero"];
    _hero.size = CGSizeMake(50, 50);
    _hero.position = CGPointMake(CGRectGetMidX(self.frame), _startY);
    [_world addChild:_hero];

    _hero.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:_hero.size.width/2.0f];
    _hero.physicsBody.affectedByGravity = NO;
    _hero.physicsBody.dynamic = YES;
    _hero.physicsBody.allowsRotation = NO;
}

- (void)applyHeroUpwardForce
{
    [_hero.physicsBody applyForce:(CGVectorMake(0, 100))];

    // limit velocity
    CGVector vel = _hero.physicsBody.velocity;
    vel.dy = vel.dy > 1000 ? 1000 : vel.dy;
    _hero.physicsBody.velocity = vel;

    // control the hero movement. it really moves upwards even we don’t see that, since the _world moves downwards
    NSLog(@"_hero.position.y: %f", _hero.position.y);
}

- (void)updateWorld
{
    _world.position = CGPointMake(_world.position.x, -(_hero.position.y - _startY));
}

- (void)didSimulatePhysics
{
    if(!_play)
        return;

    if(_touch)
    {
        [self applyHeroUpwardForce];
        [self updateWorld];
    }

}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    if(!_play)
        _play = YES;

    _touch = YES;
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    _touch = NO;
}

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    _touch = NO;
}

@end

enter image description here


EDIT 2:

The same issue in the game 'Pierre Penguin Escapes The Antarctic'

enter image description here

3
I really don't understand what it is you're asking. Of course the physics body moves with the sprite, why wouldn't you want that? - Pierce
You have to upload relevant code... - Whirlwind
Yes, the physics body moves with the sprite. BUT the physics body is displaced. see the attached image please. If the missile hits the space ship in the back side, then the contact / collision doesn't occur, because the physics body of the space ship is displaced to the front, as shown in the attached image - suyama
How do you actually move nodes? By actions or by applying impulses and forces to physics bodies? Manually moving nodes (eg using actions) doesnt play well in combination with physics engine (eg when nodes are affected by gravity) This is because you are affecting on node's position in two ways at the same time. - Whirlwind
@suyama Why do you apply forces after phyics simulation is done? You should apply force in update: method instead of didSimulatePhysics. - Whirlwind

3 Answers

1
votes

With SpriteKit, I have seen this happen in cases where you would update the sprite's position in the physics contact handler delegate methods.

Never update the underlying node's position / rotation / scale within the callback delegate methods. Instead have a mechanism that identifies and tracks the required updates to be made inside the physics callback, and have a separate method called from the scene's update method that applies the changes to be made to the nodes.

0
votes

If you init physics body properly it should displace out of parent node. Yes, physics body gonna move, but it gonna move exact the same path moves node. Check your physics body shape.

0
votes

After several tests, the best solution for this issue (described in apple docu):

set a flag inside didBeginContact: or didEndContact: and make changes in response to that flag in the update:forScene: method in a SKSceneDelegate.

@implementation TestScene
{
    // . . . 

    BOOL _didBeginContact;
    SKPhysicsContact *_contact;
}


- (instancetype)initWithSize:(CGSize)size
{
    if(self = [super initWithSize:size])
    {
        // . . . 

        _didBeginContact = NO;
        _contact = nil;
    }

    return self;
}

- (void)didBeginContact:(SKPhysicsContact *)contact
{
    // to be on the safe side: avoid multiple contacts
    if(_didBeginContact || _contact)
        return;

    // set your variables which you maybe need in your contact method 

    _contact = contact;
    _didBeginContact = YES;
}

- (void)didContact
{
    // to be on the safe side
    if(_contact == nil)
        return;

    // what ever you need during/after contact. 
    // use _contact set in didBeginContact: 

    if(_contact.bodyA.categoryBitMask // what ever
    if(_contact.bodyB.categoryBitMask // what ever
    // etc.

    // . . . 

    // important
    _contact = nil;
}

- (void)update:(NSTimeInterval)currentTime
{
    // if contact happens, so call your contact: method
    if(_didBeginContact)
    {
        _didBeginContact = NO; // important
        [self didContact];
    }
}

That's it. Thanks to all.