How to make a character jump accurate in SceneKit using updateAtTime

Viewed 91

I try to make a character jump in SceneKit. I currently have a solution, but I am not happy at all with it.

The character movement is based on the updateAtTime renderer function. There are no SCNActions involved. I want it to be moved only by the updateAtTime function.

My scene is entirely based on Apple's Fox demo Game. But In this Demo Game the Fox Character can only fall from heights - not jump. So I made an adaptation to reverse the fall Function into a jump function. The result is kind of woking, but not accurate. Have a look:

Current Jumping and Expected Jumping

As this image shows, the character currently jumps up in a manner that is against natural laws. It accelerates slowly, then gets faster and faster. (Falling down is okay). I'd like to have my character jump in a nice sinus wave, like the right side of my image illustrates. and as it would be in real nature.

Here is some Code, that makes my character currently jump up and down.

// For Jumping (Falling)
if groundAltitude < position.y - threshold || self.isJumping {
    if self.isJumpingUp {
        accelerationY -= SCNFloat(deltaTime) * gravityAcceleration // approximation of acceleration for a delta time. UP MOVEMENT
    } else {
        accelerationY += SCNFloat(deltaTime) * gravityAcceleration // approximation of acceleration for a delta time. DOWN MOVEMENT (Orig Apple)
    }
} else {
    accelerationY = 0
}

// Set Position
position.y -= accelerationY // ORIG

// reset acceleration if we touch the ground
if groundAltitude > position.y {
    accelerationY = 0
    position.y = groundAltitude
}

As you can see, I am using the variables isJumping and isJumpingUp to control/define the Y-direction in which the character should be moved. So what I do is setting the isJumping Variable to true and also the isJumpingUp, which makes the Character move up. Then at half the time I set the variable isJumpingUp to false, which will reverse the direction, and brings the character down to the ground level.

This all results in a more or less in an inaccurate jump movement as the image visualises.

Then I found this article on SO: How to make my character jump with gravity? (It has even the ability to run this code snippet in the Browser.)

And this is very close, if not exactly what I am looking for. (But I am only looking for the Y-direction movement stuff)

But each and every attempt to make a Swift/SceneKit adaptation of this results in a total mess. I don't get it managed to implement into my update function the same way as it behaves on the website. And I don't know what I am doing wrong.

Some attempts made the character to jump up very very fast and then never come down. Or the character did not jump at all. I just have no clue... I would like my character to jump up about 1m to 2m in height and then fall down.

Any help in making my character jump accurately is so much appreciated.

For visualisation purposes - here is my entire Movement Function as it currently is:

func moveCharacter(time: TimeInterval) {
    
    // Delta time since last update
    if previousUpdateTime   == 0.0 { previousUpdateTime = time }
    let deltaTime           = Float(min(time - previousUpdateTime, 1.0 / 60))
    previousUpdateTime      = time // GOOD
    
    groundType              = GroundType.inTheAir // always make it in the Air, later change, seems to crash the app ??? oder zufall? - RE-ENABLED for testing
    
    // Speed Control
    var characterSpeed      = deltaTime * self.speedFactor
    let characterRunSpeed   = deltaTime * self.speedFactor * 3.4
    
    // Remember initial position
    let initialPosition = self.node.position
    
    // Move Character Left or Right
    if self.isWalkingLeft  { self.node.position = self.node.position - SCNVector3(1.0,0.0,0.0) * characterSpeed}
    if self.isWalkingRight { self.node.position = self.node.position + SCNVector3(1.0,0.0,0.0) * characterSpeed}
    
    if self.isRunningLeft  { self.node.position = self.node.position - SCNVector3(1.0,0.0,0.0) * characterRunSpeed}
    if self.isRunningRight { self.node.position = self.node.position + SCNVector3(1.0,0.0,0.0) * characterRunSpeed}
    
    
    // Character height positioning
    var position = self.node.position
    var p0 = position
    var p1 = position
    
    let maxRise = SCNFloat(1.0) // orig 0.08
    let maxJump = SCNFloat(50.0) // orig 20.0
    p0.y -= maxJump
    p1.y += maxRise
    
    // Do a vertical ray intersection
    let results = gameScene.physicsWorld.rayTestWithSegment(from: p1, to: p0, options:[.collisionBitMask: BitMasks.BitmaskCollision, .searchMode: SCNPhysicsWorld.TestSearchMode.closest])
    
    if let result = results.first {
        
        guard (result.node.geometry != nil) else {return}
        
        let groundAltitude = result.worldCoordinates.y
        
        // can the following if statement be made in other way, because of the new guard?
        if (result.node.geometry!.firstMaterial) != nil { groundType = groundTypeFromMaterial(material: result.node.geometry!.firstMaterial!) } else { groundType = .rock }
        
        // MARK: Handle Y Position
        let threshold = SCNFloat(1e-5)
        let gravityAcceleration = SCNFloat(0.18) // 0.18
        
        if groundAltitude < position.y - threshold || self.isJumping {
            if self.isJumpingUp {
                accelerationY -= SCNFloat(deltaTime) * gravityAcceleration // approximation of acceleration for a delta time. UP
            } else {
                accelerationY += SCNFloat(deltaTime) * gravityAcceleration // approximation of acceleration for a delta time. DOWN (Orig)
            }
        }
        else {
            accelerationY = 0
        }
        
        // Set Position
        position.y -= accelerationY // orig.
        
        // reset acceleration if we touch the ground
        if groundAltitude > position.y {
            accelerationY = 0
            position.y = groundAltitude
        }
        
        // Finally, update the position of the character.
        self.node.position = position
        
        // if not touching the ground, we are in the air.
        if groundAltitude < position.y - 0.2 {
            groundType = .inTheAir
        }
        
    } else {
        // no result, we are probably out the bounds of the level -> revert the position of the character.
        self.node.position = initialPosition
    }
    
}
1 Answers

Well, after some sleep, and reflecting myself, I came on that solution:

(and it works quite nice)

Added this variables to the class:

let gravityDrag : SCNFloat  = 0.9999  // kind of slowing down the jump
let jumpPower   : SCNFloat  = -0.09   // jumps between 1,5m to 2.0m

Then changed the moveCharacter function like so:

// Calc Y Position with acceleration and gravity
if groundAltitude < position.y - threshold || self.isJumping {
    accelerationY += SCNFloat(deltaTime) * gravityAcceleration // approximation of acceleration for a delta time. DOWN (Orig)
    accelerationY *= gravityDrag
}
else {
    accelerationY = 0
}

            // Set Position
position.y -= accelerationY // orig.

// reset acceleration if we touch the ground
if groundAltitude > position.y {
    isJumping = false
    accelerationY = 0
    position.y = groundAltitude
}

Trigger a jump like so:

isJumping = true
accelerationY = jumpPower

Hopefully this will help someone, somewhen.

Related