Physical speed makes the parent separate from the child

I am trying to make an infinite scrollable relief by programmatically adding / removing textured tiles as the player pans a view. New tiles should only be added to existing tiles that have an open edge. To determine if a tile has an open edge, I plan to attach a small physical body that sticks out from all four sides of the tiles to act as a sensor. If the sensor is in contact with any other sensors, we know that the edge of the tile is not exposed.

The problem I'm running into is that the gauges don't always stay aligned with the plates. To show this problem, I created a SpriteKit project with the code below.

Behavior touching enables a gesture recognizer in the class GameScene

that causes an invisible object to move Handle

. When the gesture ends, I use the physical body of the handle to give it a little speed on this line:

handle.physicsBody?.applyImpulse(CGVector(dx: velocity.x * multiplier, dy: -velocity.y * multiplier))

      

I also create an object Tile

(big green square at the bottom) and add it as a child of an invisible handle. This is great, now all the children I add will float with their parent.

Whenever a slice is created, an object is created Sensor

(the little red square at the bottom) and added as a child of the tile. It's also great, now all sensors will move with their parent tile, which in turn moves with its parent, invisible handle. There's only one problem ...

When I pan the screen, both the green tile and its red gauge (shown below) move together in unison, as expected. When I let go of my gesture, the extra hit of speed I give the handle also carries over to its child tile, as expected. But this speed does not affect the baby tile sensor. As soon as I release the gesture, the sensor stops on the screen, while the tile continues to move with the handle, until they both stop. It is desirable that the sensor continues to move with its parent tile.

Here's a video link that might show what's going on better than I can describe it: https://youtu.be/ccJKdZv-NsM

I can't figure out why the tile is in sync with its parents' movement, but the sensor doesn't do the same. Thanks for understanding this issue.

Screenshot of the result

GameScene.swift:

import SpriteKit

class GameScene: SKScene {
    let handle = Handle()
    let startTile = Tile()

    override func didMove(to view: SKView) {
        self.backgroundColor = .white
        self.physicsWorld.gravity = CGVector(dx: 0, dy: 0)

        self.addChild(handle)
        startTile.position.x = handle.anchorPoint.x
        startTile.position.y = handle.anchorPoint.y
        handle.addChild(startTile)

        let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePanFrom))
        panGestureRecognizer.cancelsTouchesInView = false
        panGestureRecognizer.delaysTouchesEnded = false
        self.view!.addGestureRecognizer(panGestureRecognizer)
    }

    func handlePanFrom(_ recognizer: UIPanGestureRecognizer) {
        if recognizer.state == .changed {
            var translation = recognizer.translation(in: recognizer.view)
            translation = CGPoint(x: translation.x, y: -translation.y)
            self.panForTranslation(translation)
            recognizer.setTranslation(.zero, in: recognizer.view)
        } else if recognizer.state == .ended {
            let velocity = recognizer.velocity(in: self.view)
            let multiplier = CGFloat(0.5)
            handle.physicsBody?.applyImpulse(CGVector(dx: velocity.x * multiplier, dy: -velocity.y * multiplier))
        }
    }

    func panForTranslation(_ translation: CGPoint) {
        let position = handle.position
        let newPosition = CGPoint(x: position.x + translation.x, y: position.y + translation.y)
        handle.position = newPosition
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        handle.physicsBody?.isResting = true
    }
}

      

Control class:

import SpriteKit

class Handle : SKSpriteNode {
    init() {
        super.init(texture: nil, color: .clear, size: CGSize(width: 1, height: 1))
        self.physicsBody = SKPhysicsBody(rectangleOf: self.size)
        self.physicsBody?.mass = 1
        self.physicsBody?.linearDamping = 2
        self.physicsBody?.categoryBitMask = 0
        self.physicsBody?.contactTestBitMask = 0
        self.physicsBody?.collisionBitMask = 0
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

      

Tile class:

import SpriteKit

class Tile : SKSpriteNode {

    init() {
        super.init(texture: nil, color: .green, size: CGSize(width: 300, height: 300))
        let sensorA = Sensor()
        self.addChild(sensorA)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

      

Sensor class:

import SpriteKit

class Sensor : SKSpriteNode {

    init() {
        super.init(texture: nil, color: .red, size: CGSize(width: 50, height: 50))
        self.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 50, height: 50))
        self.physicsBody?.categoryBitMask = 0b1
        self.physicsBody?.contactTestBitMask = 0b1
        self.physicsBody?.collisionBitMask = 0
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

      


UPDATE: The accepted answer provided by Whirlwind solved the issue that the child was split from the parent with. I believe that the cause of the problem became clear in the comments to this answer.

My understanding of this is that the red square did not move because it has its own physical body, which does not gain any speed after the handle stops moving. As long as the handle object (and its child tile) keeps moving because it has speed. So it looks like the physical body in the red box was holding him back.

+3


source to share


1 answer


I don't really have time to figure out why your code is doing something, but if you want to move another physical body along with the physical body of the handle, then you can bind it to it.

I'll just change my code to make it work, but you have to worry about encapsulation yourself. First, make the gauge variable inside the Tile class visible to the outside world, so you can use it later in your scene:

class Tile : SKSpriteNode {
    let sensorA = Sensor()
    init() {
        super.init(texture: nil, color: .green, size: CGSize(width: 300, height: 300))

        self.addChild(sensorA)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

      

Then in your scene sensor to handle:



   let pin = SKPhysicsJointPin.joint(withBodyA: self.startTile.sensorA.physicsBody!, bodyB: self.handle.physicsBody!, anchor: CGPoint.zero)
   startTile.sensorA.physicsBody?.allowsRotation = false
   self.physicsWorld.add(pin)

      

I think this is what you wanted:

sensor

+2


source







All Articles