Sprite Kit set Min. and Max. for Jump
Asked Answered
B

1

15

I want to move a SKSpriteNode on the Y-Axis. The SKSpriteNode called Player has no Velocity.The Player can only jump if a Platform is in contact.

Everytime the Screen is touched, I want to give the Player an impulse with a minimum impulse or a maximum impulse

If the Screen is tapped shortly, the Minimum impulse should be e.g. y = 50. If the Screen is hold, that means the finger is on the Screen long, the Maximum should be e.g. y = 100.

But the Player should be also able to jump between the Minimum and Maximum height, if for e.g. the Screen is not long but also not shortly pressed, the Player should only get an impulse of y = 70.

If the Screen is hold, the Player should jump to his max height, fall down, and if it is in contact with the Platform again, it should jump, because you still hold the Screen.

I have already tried this with the suggested answer in this Thread:StackOverFlow But this does not give the Minimum jump, also no Press jump.

For clarity: The impulse should not be after the tap is done, but while it is tapped. The longer you hold, the longer the jump is.

import SpriteKit
import GameKit

struct Constants {

static let minimumJumpForce:CGFloat = 40.0
static let maximumJumpForce:CGFloat = 60.0
static let characterSideSpeed:CGFloat = 18.0
}

class GameScene: SKScene, SKPhysicsContactDelegate {

var Player: SKSpriteNode!

var Platform0: SKSpriteNode!

var World: SKNode!
var Camera: SKNode!

var force: CGFloat = 40.0

var pressed = false

var isCharacterOnGround = false

.....

func SpawnPlatforms() {

Platform0 = SKSpriteNode (color: SKColor.greenColor(), size: CGSize(width: self.frame.size.width , height: 25))
Platform0.position = CGPoint(x: self.frame.size.width / 2, y: -36)
Platform0.zPosition = 1

Platform0.physicsBody = SKPhysicsBody(rectangleOfSize:Platform0.size)
Platform0.physicsBody?.dynamic = false
Platform0.physicsBody?.allowsRotation = false
Platform0.physicsBody?.restitution = 0
Platform0.physicsBody?.usesPreciseCollisionDetection = true

Platform0.physicsBody?.categoryBitMask = Platform0Category
Platform0.physicsBody?.collisionBitMask = PlayerCategory
Platform0.physicsBody?.contactTestBitMask = PlayerCategory

World.addChild(Platform0)

}

func SpawnPlayer(){

Player = SKSpriteNode (imageNamed: "Image.png")
Player.size = CGSize(width: 64, height: 64)
Player.position = CGPoint(x: self.frame.size.width / 2, y: 0)
Player.zPosition = 2

Player.physicsBody = SKPhysicsBody(rectangleOfSize:CGSize(width: 35, height: 50))
Player.physicsBody?.dynamic = true
Player.physicsBody?.allowsRotation = false
Player.physicsBody?.restitution = 0.1
Player.physicsBody?.usesPreciseCollisionDetection = true

Player.physicsBody?.categoryBitMask = PlayerCategory
Player.physicsBody?.collisionBitMask = Platform0Category
Player.physicsBody?.contactTestBitMask = Platform0Category | Platform1Category | Platform2Category | Platform3Category | Platform4Category | Platform5Category

World.addChild(Player)

}

func jump(force : CGFloat){


    if(self.isCharacterOnGround){

        self.Player.physicsBody?.applyImpulse(CGVectorMake(0, force))
        self.isCharacterOnGround = false
    }

}

override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
    /* Called when a touch begins */

    for touch in (touches as! Set<UITouch>) {
        let location = touch.locationInNode(self)

        self.pressed = true

        let timerAction = SKAction.waitForDuration(0.0)

        let update = SKAction.runBlock({
            if(self.force < Constants.maximumJumpForce){
                self.force += 2.0
            }else{
                self.jump(Constants.maximumJumpForce)
                self.force = Constants.maximumJumpForce
            }
        })
        let sequence = SKAction.sequence([timerAction, update])
        let repeat = SKAction.repeatActionForever(sequence)
        self.runAction(repeat, withKey:"repeatAction")
    }
}

override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
    for touch in (touches as! Set<UITouch>) {
        let location = touch.locationInNode(self)

        self.removeActionForKey("repeatAction")

        self.jump(self.force)

        self.force = Constants.minimumJumpForce

        self.pressed = false

}
}

func didBeginContact(contact: SKPhysicsContact) {

    //this gets called automatically when two objects begin contact with each other

    let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask

    switch(contactMask) {

    case PlayerCategory | Platform0Category:
        //either the contactMask was the bro type or the ground type
        println("Contact Made0")
        Green = true
        self.isCharacterOnGround = true

    default:
        return

    }

}
Barquentine answered 16/7, 2015 at 9:45 Comment(3)
I like the second answer (with SKAction) from the link you've posted. Have you tried that one? About continuously jumping while holding a finger... I guess you should add some variable which indicates that user still holding a finger on screen, and if that's a case, when contact between platform and player is detected you will apply impulse for the next jump...Popsicle
I tried the second one also, its still not what I meant. Yes, I should apply an impulse for the next jump after a contact is made, but still need to set the minimum and maximum.Barquentine
Check out my answer. Just copy&paste code to see how it works...Popsicle
P
12

Here is an working example on how to make something like:

  • long pressed jump based on duration of press
  • short (one tap jump)
  • restrict character to jump while in the air
  • keep character jumping while finger is on screen

Code (Swift 4.x)

import SpriteKit

struct Constants {
    static let minimumJumpForce:CGFloat = 15.0
    static let maximumJumpForce:CGFloat = 30.0
    static let characterSideSpeed:CGFloat = 18.0
}

class GameScene: SKScene,SKPhysicsContactDelegate
{
    let CharacterCategory   : UInt32 = 0x1 << 1
    let PlatformCategory    : UInt32 = 0x1 << 2
    let WallCategory        : UInt32 = 0x1 << 3

    var force: CGFloat = 16.0 //Initial force
    var pressed = false
    var isCharacterOnGround = false // Use this to prevent jumping while in the air
    let character = SKSpriteNode(color: .green, size: CGSize(width: 30, height:30))
    let debugLabel = SKLabelNode(fontNamed: "Geneva")

    override func didMove(to view: SKView)
    {
        //Setup contact delegate so we can use didBeginContact and didEndContact methods
        physicsWorld.contactDelegate = self
        physicsWorld.speed = 0.5
        //Setup borders so character can't escape from us :-)
        self.physicsBody = SKPhysicsBody(edgeLoopFrom: self.frame)
        self.physicsBody?.categoryBitMask = WallCategory
        self.physicsBody?.collisionBitMask = CharacterCategory

        //Setup character
        character.position = CGPoint(x: 150, y: 150)
        character.physicsBody = SKPhysicsBody(rectangleOf: character.size)
        character.physicsBody?.categoryBitMask = CharacterCategory
        character.physicsBody?.contactTestBitMask = PlatformCategory
        character.physicsBody?.collisionBitMask = PlatformCategory | WallCategory
        character.physicsBody?.allowsRotation = false
        character.physicsBody?.isDynamic = true
        character.physicsBody?.restitution = 0.1

        self.addChild(character)

        generatePlatforms()

        debugLabel.text = " DEBUG: "
        debugLabel.fontColor = .white
        debugLabel.fontSize = 12.0
        debugLabel.position = CGPoint(x: frame.midX, y: frame.midY+100)
        self.addChild(debugLabel)
    }

    func generatePlatforms(){
        for i in 1...4
        {
            let position = CGPoint(x: frame.midX, y: CGFloat(i)*140.0 - 100)
            let platform = createPlatformAtPosition(position: position)
            self.addChild(platform)
        }
    }


    func createPlatformAtPosition(position : CGPoint)->SKSpriteNode{

        let platform = SKSpriteNode(color: .green, size: CGSize(width: frame.size.width, height:20))

        platform.position = position

        platform.physicsBody = SKPhysicsBody(
            edgeFrom: CGPoint(x: -platform.size.width/2.0, y:platform.size.height/2.0),
            to:CGPoint(x: platform.size.width/2.0, y: platform.size.height/2.0))

        platform.physicsBody?.categoryBitMask       = PlatformCategory
        platform.physicsBody?.contactTestBitMask    = CharacterCategory
        platform.physicsBody?.collisionBitMask      = CharacterCategory
        platform.physicsBody?.allowsRotation        = false
        platform.name                               = "platform"
        platform.physicsBody?.isDynamic             = false
        platform.physicsBody?.restitution           = 0.0

        return platform
    }

    func jump(force : CGFloat){
        if(self.isCharacterOnGround){
            self.character.physicsBody?.applyImpulse(CGVector(dx: 0, dy: force))
            self.character.physicsBody?.collisionBitMask = WallCategory
            self.isCharacterOnGround = false
        }
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.pressed = true

        let timerAction = SKAction.wait(forDuration: 0.05)

        let update = SKAction.run({
            if(self.force < Constants.maximumJumpForce){
                self.force += 2.0
            }else{
                self.jump(force: Constants.maximumJumpForce)
                self.force = Constants.maximumJumpForce
            }
        })

        let sequence = SKAction.sequence([timerAction, update])
        let repeat_seq = SKAction.repeatForever(sequence)
        self.run(repeat_seq, withKey:"repeatAction")
    }


    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {

        self.removeAction(forKey: "repeatAction")
        self.jump(force: self.force)
        self.force = Constants.minimumJumpForce
        self.pressed = false

    }

    override func update(_ currentTime: TimeInterval) {

        debugLabel.text = "DEBUG: onTheGround : \(isCharacterOnGround), force \(force)"

        if(character.position.x <= character.size.width/2.0 + 5.0 && character.physicsBody!.velocity.dx < 0.0 ){
            character.physicsBody?.applyForce(CGVector(dx: Constants.characterSideSpeed, dy: 0.0))
        }else if((character.position.x >= self.frame.size.width - character.size.width/2.0 - 5.0) && character.physicsBody!.velocity.dx >= 0.0){
            character.physicsBody?.applyForce(CGVector(dx: -Constants.characterSideSpeed, dy: 0.0))
        }else if(character.physicsBody!.velocity.dx > 0.0){
            character.physicsBody!.applyForce(CGVector(dx: Constants.characterSideSpeed, dy: 0.0))
        }else{
            character.physicsBody!.applyForce(CGVector(dx: -Constants.characterSideSpeed, dy: 0.0))
        }
    }

    func didBegin(_ contact: SKPhysicsContact) {

        var firstBody, secondBody: SKPhysicsBody

        if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
            firstBody = contact.bodyA
            secondBody = contact.bodyB
        } else {
            firstBody = contact.bodyB
            secondBody = contact.bodyA
        }

        if ((firstBody.categoryBitMask & CharacterCategory) != 0 &&
            (secondBody.categoryBitMask & PlatformCategory != 0)) {


            let platform = secondBody.node! as! SKSpriteNode
            //  platform.color = UIColor.redColor()
            let platformSurfaceYPos = platform.position.y + platform.size.height/2.0

            let player = contact.bodyB.node! as! SKSpriteNode
            let playerLegsYPos = player.position.y - player.size.height/2.0

            if((platformSurfaceYPos <= playerLegsYPos)){
                character.physicsBody?.collisionBitMask = PlatformCategory | WallCategory
                self.isCharacterOnGround = true

                if(self.pressed){
                    let characterDx = character.physicsBody?.velocity.dx
                    character.physicsBody?.velocity = CGVector(dx: characterDx!, dy: 0.0)
                    self.jump(force: Constants.maximumJumpForce)
                }
            }
        }
    }

    func didEnd(_ contact: SKPhysicsContact) {

        var firstBody, secondBody: SKPhysicsBody

        if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
            firstBody = contact.bodyA
            secondBody = contact.bodyB
        } else {
            firstBody = contact.bodyB
            secondBody = contact.bodyA
        }

        if ((firstBody.categoryBitMask & CharacterCategory) != 0 &&
            (secondBody.categoryBitMask & PlatformCategory != 0)) {

            let platform = secondBody.node as! SKSpriteNode
            let platformSurfaceYPos = platform.position.y + platform.size.height/2.0

            let player = contact.bodyB.node as! SKSpriteNode
            let playerLegsYPos = player.position.y - player.size.height/2.0

            if((platformSurfaceYPos <= playerLegsYPos) && ((character.physicsBody?.velocity.dy)! > CGFloat(0.0))){
                character.physicsBody?.collisionBitMask = WallCategory
                self.isCharacterOnGround = false
            }
        }
    }
}

Note that this is simple example, and in real application you will probably have to handle states like isOnTheGround in a different way. Right now, to determine if character is on the ground you just set isOnTheGround = true when character make a contact with platform, and set it to false in didEndContact...But there are situations when character can be in contact with platform while in the air (eg. side contact)...

EDIT:

I changed the code to let the player jump while pressed. Here is the result:

enter image description here

Important:

Actual platform implementation and contact handling is up to you and this is not tested. The only purpose of this example is to show you how to jump while pressed. Currently, physicsWorld.speed is set to 0.5 to make animation slower because its easier to debug like that, but you can change this to default value (1.0).

So, as you can see from the image, while player is on the first platform some small jumps are presented (by simple tapping, or short pressing). Then (player is still on first platform) long press has been made, and player has jumped on second platform. After that, another long press is done, but this time without releasing, and player starts jumping from one platform to another using maximum force.

This needs a lot of tweaking and proper platform & contact detection, but it can give you an idea about how to implement jumping you asked about.

Popsicle answered 16/7, 2015 at 12:23 Comment(3)
Thanks for your suggestion! Its not quite what I wanted, but the Code for the "Jump when contact with Platform is made while Pressed" is working for me. I updated my post with a video of a game that uses the same logic.Barquentine
@Alber. Hi... Which part doesn't work for you ? You said : "I have already tried this with the suggested answer in this thread: #30224497! But this does not give the Minimum jump, also no Press jump." My example was related to that statement of yours, and its meant to show you how to change the jump force based on press duration and to have "minimum jump" and "press jump" ( short tap = small jump, long press = big jump etc. )Popsicle
With this code, we have a minimum and if the force is under 100, the longer you tap it gives +2 until 100, if I understood it correctly. However, the jump will only appear after your tap finished, not while it is going on. I want to have the jump when the screen is touched.Barquentine

© 2022 - 2024 — McMap. All rights reserved.