3
votes

I've been having some issues with a SpriteKit game I'm working on. I'm trying to do a platformer-style game, where the player taps on the screen to make the character jump. I'm achieving this with:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    // reset the velocity each jump, so that a constant impulse is always applied
    self.player.physicsBody.velocity = CGVectorMake(0, 0);       

    // jump the player up
    [self.player.physicsBody applyImpulse:CGVectorMake(0, PLAYER_JUMP_HEIGHT)];
}

PLAYER_JUMP_HEIGHT is just a constant. I also setup the player with a constant mass.

This works just great on the iPad simulator, which is where I did the bulk of my initial testing. However, after adjusting my code to scale with device resolution (for phones and such), it appears my physics are now very off, particularly on the iPhone 4S simulator. That same impulse now jumps the player up very high, and they also seem to fall off objects such as platforms quicker, in more of a linear curve than before.

I scaled my scene to accommodate multiple devices by first setting the scaling mode of the scene:

scene.scaleMode = SKSceneScaleModeResizeFill;

I also came up with a way to scale everything else in the game by comparing the current device's resolution to the resolution of the iPad, and dividing to get a scale factor to multiply everything by. So, where the iPad screen width is 2048, if I was running on the iPhone 4S, the scale factor would be 960 / 2048 = 0.46. In this way I can design the game using one set of constants that would scale across all device resolutions, without the cropping or other scaling issues of other scale modes.

I'm pretty stumped as to what this could be. I've tried multiplying the player mass and jump height by the screen scale factor, in all possible combinations, to no avail. I also tried messing with the physicsWorld gravity and speed, which didn't work either. If anyone has experience in dealing with this and would be up for sharing, any help would be greatly appreciated. Thank you!

1
Have you adjusted the CGVector for applyImpulse according to the scale factor?WangYudong
@WangYudong thanks for the quick comment! I did try multiplying PLAYER_JUMP_HEIGHT by the screen scale factor already.kingsapo

1 Answers

5
votes

Several factors need to be considered in your question, including the different resolution of different devices, the gravity and the momentum. Why momentum? Because in applyImpulse:(CGVector)impulse, impulse is a vector describes how much momentum is added. Therefore, simply multiplies a scale factor on PLAYER_JUMP_HEIGHT is no use.

Thanks to this answer, we can use a ratio named 'pixel to unit' to calculate how much dy needed to make the impulse vector.

The following is to explain how to make a player jump for constant PLAYER_JUMP_HEIGHT. You may want to adjust it according to different resolution yourself and this time your scale factor should work.

#define kScale ([[UIScreen mainScreen] bounds].size.height) / 1024.0
const CGFloat PLAYER_JUMP_HEIGHT = 200.0;

- (void)didMoveToView:(SKView *)view
{
    self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];

    // Print useful info
    NSLog(@"scale: %.3f", kScale);
    NSLog(@"total height: %.1f", self.frame.size.height);
    NSLog(@"jump height: %.1f%%", kScale * PLAYER_JUMP_HEIGHT / self.frame.size.height * 100);

    // Let player jump over the bar
    SKSpriteNode *bar = [SKSpriteNode spriteNodeWithColor:[UIColor redColor] size:CGSizeMake(CGRectGetMaxY(self.frame), 1.0)];
    bar.position = CGPointMake(CGRectGetMidX(self.frame), PLAYER_JUMP_HEIGHT * kScale);
    [self addChild:bar];

    self.player = [SKSpriteNode spriteNodeWithColor:[UIColor greenColor] size:CGSizeMake(kScale * 50.0, kScale * 50.0)];
    self.player.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(kScale * 50.0, kScale * 50.0)];
    self.player.physicsBody.mass = 0.25;
    self.player.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame)/5.0);
    [self addChild:self.player];

    // Pixel to unit
    ptu = 1.0 / sqrt([SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(1.0, 1.0)].mass);
}

In the code above, we firstly set the const PLAYER_JUMP_HEIGHT, a bar indicated how high is 200 points on screen, a player and the ratio ptu.

Then calculate dy and apply it to the impulse:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    // reset the velocity each jump, so that a constant impulse is always applied
    self.player.physicsBody.velocity = CGVectorMake(0, 0);
    self.physicsWorld.gravity = CGVectorMake(0, kScale * (-9.8));

    // jump the player up
    CGFloat dy = self.player.physicsBody.mass * sqrt(2 * (-self.physicsWorld.gravity.dy) * (kScale * PLAYER_JUMP_HEIGHT * ptu));

    [self.player.physicsBody applyImpulse:CGVectorMake(0, dy)];
}

Here's the sample project (outdated) for demo, run and play it. You will find the box sprite doesn't actually jump over the red bar but a little lower. I think the reason is the gravity.

Code updated in several places:

  1. Define kScale macro for different devices for quick test. Works only in portrait orientation.

  2. Add kScale where needed (jump height, player size, and gravity!). It now should jump adaptive height at the same velocity.