SpriteKit joint: follow the body

有些话、适合烂在心里 提交于 2019-12-23 01:06:14

问题


I've been asked to simplify this question, so that's what I'm doing.

I'm struggling in SpriteKit's physic joints (and possibly physic body properties). I tried every single subclass and many configurations but seams like nothing works or I'm doing something wrong.

I'm developing Snake game. User controls head of snake which should move at constant speed ahead and user can turn it clockwise or anticlockwise. All the remaining snake's pieces should follow the head - they should travel exactly the same path that head was some time ago.

I think for this game the Pin joint should be the answer, which anchor point is exactly in the centre between elements.

Unfortunately the result is not perfect. The structure should make the perfect circle, but it doesn't. I'm attaching the code, and gif showing the current effect. Is anyone experience enough to give me any suggestion what properties of physic body and or joints should are apply here for desired effect?

My code:

class GameScene: SKScene {
    private var elements = [SKNode]()

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

        let dummyTurnNode = SKNode()
        dummyTurnNode.position = CGPoint(x: size.width / 2 - 50, y: size.height / 2)
        let dummyTurnBody = SKPhysicsBody(circleOfRadius: 1)
        dummyTurnBody.isDynamic = false
        dummyTurnNode.physicsBody = dummyTurnBody
        addChild(dummyTurnNode)

        for index in 0..<5 {
            let element = SKShapeNode(circleOfRadius: 10)
            let body = SKPhysicsBody(circleOfRadius: 10)
            body.linearDamping = 0
            // body.mass = 0
            element.physicsBody = body
            element.position = CGPoint(x: size.width / 2, y: size.height / 2 - 30 * CGFloat(index))
            elements.append(element)
            addChild(element)

            let label = SKLabelNode(text: "A")
            label.fontSize = 10
            label.fontName = "Helvetica-Bold"
            element.addChild(label)

            if index == 0 {
                element.fillColor = UIColor.blue()

                body.velocity = CGVector(dx: 0, dy: 30)

                let dummyTurnJoint = SKPhysicsJointPin.joint(withBodyA: dummyTurnBody, bodyB: body, anchor: dummyTurnNode.position)
                physicsWorld.add(dummyTurnJoint)
            } else {
                body.linearDamping = 1

                element.fillColor = UIColor.red()
                let previousElement = elements[index - 1]
                let connectingJoint = SKPhysicsJointPin.joint(withBodyA: previousElement.physicsBody!, bodyB: body, anchor: CGPoint(x: size.width / 2, y: size.height / 2 - 30 * CGFloat(index) + CGFloat(15)))
                physicsWorld.add(connectingJoint)
            }
        }
    }

    override func update(_ currentTime: TimeInterval) {
        let head = elements.first!.physicsBody!
        var velocity = head.velocity
        velocity.normalize()
        velocity.multiply(30)
        head.velocity = velocity
    }
}

extension CGVector {
    var rwLength: CGFloat {
        let xSq = pow(dx, 2)
        let ySq = pow(dy, 2)
        return sqrt(xSq + ySq)
    }

    mutating func normalize() {
        dx /= rwLength
        dy /= rwLength
    }

    mutating func multiply(_ factor: CGFloat) {
        dx *= factor
        dy *= factor
    }
}

回答1:


"All the remaining snake's pieces should follow the head - they should travel exactly the same path that head was some time ago."

You should note that with Physics joints you are likely going to have variance no matter what you do. Even if you have it close to perfect you'll have rounding errors under the hood making the path not exact.

If all the tail parts are equal you can also use a different approach, this is something I've done for a comet tail. Basically the idea is that you have an array of tail objects and per-frame move move the last tail-object always to the same position as the head-object. If the head-object has a higher z-position the tail is drawn below it.

If you need to keep your tail in order you could vary the approach by storing an array of head-positions (per-frame path) and then place the tail objects along that path in your per-frame update call to the snake.

See my code below for example:

These are you head-object variables:

var tails = [SKEmitterNode]()
var tailIndex = 0

In your head init function instantiate the tail objects:

for _ in 0...MAX_TAIL_INDEX
        {
            if let remnant = SKEmitterNode(fileNamed: "FireTail.sks")
            {
                p.tails.append(remnant)
            }
        }

Call the below per-frame:

func drawTail()
{
    if tails.count > tailIndex
    {
        tails[tailIndex].resetSimulation()
        tails[tailIndex].particleSpeed = velocity() / 4
        tails[tailIndex].emissionAngle = zRotation - CGFloat(M_PI_2) // opposite direction
        tails[tailIndex].position = position
        tailIndex = tailIndex < MAX_TAIL_INDEX ? tailIndex + 1 : 0
    }
}

The resulting effect is actually really smooth when you call it from the scene update() function.



来源:https://stackoverflow.com/questions/38388005/spritekit-joint-follow-the-body

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!