WangYudong
WangYudong

Reputation: 4423

Can't prevent the sprite from moving out of the screen

I want to keep my sprite just moving in the range of the screen. So I create a edge loop by bodyWithEdgeLoopFromRect, also the collision bit mask has been set to make them collide with each other.

static const uint32_t kRocketCategory = 0x1 << 0;
static const uint32_t kEdgeCategory = 0x1 << 6;

I use the pan recognizer to move the sprite and here is the current code that sets the properties of all the things.

- (void)didMoveToView:(SKView *)view
{
    // Pan gesture
    self.panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanFrom:)];
    [self.view addGestureRecognizer:self.panRecognizer];

    // Edge
    self.backgroundColor = [SKColor blackColor];
    self.physicsWorld.gravity = CGVectorMake(0.0f, 0.0f);
    self.physicsWorld.contactDelegate = self;
    self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
    self.physicsBody.categoryBitMask = kEdgeCategory;
    self.physicsBody.contactTestBitMask = kRocketCategory;
    self.physicsBody.collisionBitMask = kRocketCategory;
    self.physicsBody.usesPreciseCollisionDetection = YES;
    self.physicsBody.restitution = 0;

    // Rocket
    SKTexture *texture = [SKTexture textureWithImageNamed:@"Rocketship-v2-1"];
    texture.filteringMode = SKTextureFilteringNearest;
    self.rocketSprite = [SKSpriteNode spriteNodeWithTexture:texture];

    self.rocketSprite.position = CGPointMake(CGRectGetMidX(self.scene.frame), CGRectGetMaxY(self.scene.frame)*0.3);    // 30% Y-axis
    self.rocketSprite.name = @"rocketNode";
    self.rocketSprite.xScale = 2;
    self.rocketSprite.yScale = 2;

    self.rocketSprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.rocketSprite.size];
    self.rocketSprite.physicsBody.categoryBitMask = kRocketCategory;
    self.rocketSprite.physicsBody.contactTestBitMask = kEdgeCategory;
    self.rocketSprite.physicsBody.collisionBitMask = kEdgeCategory;
    self.rocketSprite.physicsBody.usesPreciseCollisionDetection = YES;
    self.rocketSprite.physicsBody.allowsRotation = NO;
    self.rocketSprite.physicsBody.restitution = 0;

    [self addChild:self.rocketSprite];
}

Pan recognizer code:

- (void)handlePanFrom:(UIPanGestureRecognizer *)recognizer
{
    if (recognizer.state == UIGestureRecognizerStateBegan) {
        self.selectedRocket = self.rocketSprite;
    } else if (recognizer.state == UIGestureRecognizerStateChanged) {
        CGPoint translation = [recognizer translationInView:recognizer.view];
        translation = CGPointMake(translation.x, -translation.y);

        [self panForTranslation:translation];
        [recognizer setTranslation:CGPointZero inView:recognizer.view];
    } else if (recognizer.state == UIGestureRecognizerStateEnded) {
    }
}

- (void)panForTranslation:(CGPoint)translation
{
    CGPoint position = self.selectedRocket.position;
    if ([self.selectedRocket.name isEqualToString:@"rocketNode"]) {
        self.selectedRocket.position = CGPointMake(position.x + translation.x, position.y);
    }
}

Now the problem is when I move the sprite (rocketship) slowly, the edge will stop the sprite going out of the screen. However, when I move the sprite very quickly, the sprite will rush out the range. See the animation below. I have read some solutions to the similar problem but I still don't know what's wrong with my code. Do the edge loop and collision bit mask not enough for the situation here?

enter image description here

Upvotes: 1

Views: 589

Answers (3)

Micrified
Micrified

Reputation: 3650

Your attempt is similar to what I've found when searching online, and having read what you've tried it looks like you've pretty much covered most of the more common problems (Not using SKAction, for instance).

I don't currently use a contact Bitmask to handle collisions on the side of my scene, but I do use a loop that actively checks positions.

Here's the method I use to reposition objects: (Repositions everything, you can specify it to move only the rocket.

-(void)repositionObjects
{
    for (CXSpriteNode *i in self.sceneObjects) // CXSpriteNode is a certain subclass
    {
        CGPoint position = i.position;
        if (position.x > self.background.size.width || position.x < 0)
        {
            CGPoint newPosition;
            if (position.x > self.background.size.width)
            {
                newPosition = CGPointMake(self.background.size.width-1, position.y);
            } else {
                newPosition = CGPointMake(1, position.y);
            }
            [i runAction:[SKAction moveTo:newPosition duration:0.0f]];
        }

        if (position.y > self.background.size.height || position.y < 0)
        {
            CGPoint newPosition;
            if (position.y > self.background.size.height)
            {
                newPosition = CGPointMake(self.background.size.height-1, 1);
            } else {
                newPosition = CGPointMake(position.x, 1);
            }
            [i runAction:[SKAction moveTo:newPosition duration:0.0f]];
        }
    }
}

This gets called from the SKScene loop as such:

-(void)update:(NSTimeInterval)currentTime
{ 
    [self repositionObjects];
}

Now, it's evidently not as elegant as your desired outcome, and I have a feeling it might still induce flickering, but I'd give it a try anyways.

Also, it might be worth trying to disable/cancel the gesture once it goes out of range momentarily to stop repeated swipes that may cause flickering.

Hope this helps.

Upvotes: 1

duci9y
duci9y

Reputation: 4168

Use SpriteKit actions instead of directly modifying the position of the sprite.

What is happening is that the recognizer isn't sending updates fast enough. The sprite's bounds never intersect with the edge in any one frame.

In one update, the recognizer says that the touch is within the bounds of the edge loop. But in the next update, the touch has moved way beyond the edge loop. This leaves no frame in which the sprite's bounds intersect the edge, so no collision gets detected.

Upvotes: 1

s1ddok
s1ddok

Reputation: 4654

I do not use sprite kit in daily life, but you could for example solve it the hard way by adding something like this in your sprite's update method

(Pseudo code)

CGFloat x = clamp(self.position.x, minXValue, maxXValue);
CGFloat y = clamp(self.position.y, minYValue, maxYValue);
self.position = CGPointMake(x, y);

Upvotes: 0

Related Questions