2014-12-27 4 views
14

Ich habe mein eigenes sehr einfaches Testspiel basierend auf Breakout erstellt, während ich SpriteKit (mit iOS Games by Tutorials von Ray Wenderlich et al.) Erlerne, um zu sehen, ob ich mich bewerben kann Konzepte, die ich gelernt habe. Ich habe mich entschieden, meinen Code zu vereinfachen, indem ich eine .sks-Datei verwende, um die Sprite-Knoten zu erstellen und meine manuellen Grenzen, die Überprüfung und Kollision mit physikalischen Körpern zu ersetzen.SpriteKit Physik in Swift - Ball gleitet gegen die Wand anstatt zu reflektieren

Allerdings läuft mein Ball immer parallel zu Wänden/anderen Rechtecken (wie in, einfach auf und ab gleiten), immer wenn er in steilem Winkel mit ihnen kollidiert. Hier wird der entsprechende Code - Ich habe die Physik Körpereigenschaften in Code bewegt, um sie besser sichtbar:

import SpriteKit 

struct PhysicsCategory { 
    static let None:  UInt32 = 0  // 0 
    static let Edge:  UInt32 = 0b1 // 1 
    static let Paddle:  UInt32 = 0b10 // 2 
    static let Ball:  UInt32 = 0b100 // 4 
} 

var paddle: SKSpriteNode! 
var ball: SKSpriteNode! 

class GameScene: SKScene, SKPhysicsContactDelegate { 

    override func didMoveToView(view: SKView) { 

    physicsWorld.gravity = CGVector.zeroVector 

    let edge = SKNode() 
    edge.physicsBody = SKPhysicsBody(edgeLoopFromRect: frame) 
    edge.physicsBody!.usesPreciseCollisionDetection = true 
    edge.physicsBody!.categoryBitMask = PhysicsCategory.Edge 
    edge.physicsBody!.friction = 0 
    edge.physicsBody!.restitution = 1 
    edge.physicsBody!.angularDamping = 0 
    edge.physicsBody!.linearDamping = 0 
    edge.physicsBody!.dynamic = false 
    addChild(edge) 

    ball = childNodeWithName("ball") as SKSpriteNode 
    ball.physicsBody = SKPhysicsBody(rectangleOfSize: ball.size)) 
    ball.physicsBody!.usesPreciseCollisionDetection = true 
    ball.physicsBody!.categoryBitMask = PhysicsCategory.Ball 
    ball.physicsBody!.collisionBitMask = PhysicsCategory.Edge | PhysicsCategory.Paddle 
    ball.physicsBody!.allowsRotation = false 
    ball.physicsBody!.friction = 0 
    ball.physicsBody!.restitution = 1 
    ball.physicsBody!.angularDamping = 0 
    ball.physicsBody!.linearDamping = 0 

    physicsWorld.contactDelegate = self 
} 

vergessen dies vor zu erwähnen, aber ich hinzugefügt, um eine einfache touchesBegan Funktion die Bounces zu debuggen - es passt einfach die Geschwindigkeit der Kugel an dem Berührungspunkt Punkt:

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) { 
    let touch = touches.anyObject() as UITouch 
    let moveToward = touch.locationInNode(self) 
    let targetVector = (moveToward - ball.position).normalized() * 300.0 
    ball.physicsBody!.velocity = CGVector(point: targetVector) 
} 

die normalisierte() Funktion reduziert es den Ball/Berührungsposition Delta zu einem Einheitsvektor, und es gibt eine Überschreibung des minus-Operator, der für CGPoint Subtraktion ermöglicht .

Die Kugel/Kanten Kollisionen sollten immer den Ball in einem genau entgegengesetzten Winkel reflektieren aber aus irgendeinem Grund scheint der Ball wirklich etwas für rechte Winkel zu haben. Ich kann natürlich eine Workaround implementieren, um den Winkel des Balls manuell zu reflektieren, aber der Punkt ist, dass ich das alles mit der eingebauten Physik-Funktionalität in SpriteKit machen möchte. Gibt es etwas Offensichtliches, dass ich vermisse?

+2

Sie müssen das Sprit bewegen, indem eine Kraft oder einen Impuls auf seinen Physikkörper anwenden oder seine Geschwindigkeitseigenschaft einstellen. Das Sprite nimmt nicht an der Physiksimulation teil, wenn Sie es mit einer Aktion bewegen oder seine Position direkt setzen. – 0x141E

+0

Entschuldigung, ich hatte meine Geschwindigkeitsanpassung weggelassen. Ich habe es gerade bearbeitet, aber im Grunde ist es ein Berührungsereignis, das die Ballgeschwindigkeit in Richtung des Berührungspunkts in der SKScene einstellt. –

+0

Tippen und lösen Sie Ihren Finger oder halten Sie ihn gedrückt, wenn der Kontakt auftritt? – 0x141E

Antwort

7

Ich habe eine temporäre Lösung gefunden, die erstaunlich gut funktioniert. Wenden Sie einfach einen sehr kleinen Impuls gegenüber der Grenze an. Möglicherweise müssen Sie die strength basierend auf den Massen in Ihrem System ändern.

func didBeginContact(contact: SKPhysicsContact) { 

    let otherNode = contact.bodyA.node == ball.sprite ? contact.bodyB.node : contact.bodyA.node 

    if let obstacle = otherNode as? Obstacle { 
     ball.onCollision(obstacle) 
    } 
    else if let border = otherNode as? SKSpriteNode { 

     assert(border.name == "border", "Bad assumption") 

     let strength = 1.0 * (ball.sprite.position.x < frame.width/2 ? 1 : -1) 
     let body = ball.sprite.physicsBody! 
     body.applyImpulse(CGVector(dx: strength, dy: 0)) 
    } 
} 

In Wirklichkeit sollte dies nicht notwendig sein, da wie in der Frage beschrieben, haft- und vollelastische Kollision schreibt vor, dass die Kugel durch das Umkehren der x-Geschwindigkeit (unter der Annahme, Seitenränder), egal wie klein die Kollision Rebound sollte Winkel ist.

Im Spiel verhält es sich so, als würde das Sprite-Kit die X-Geschwindigkeit ignorieren, wenn sie kleiner als ein bestimmter Wert ist und den Ball ohne Rückstoß an die Wand gleiten lässt.

Abschließender Hinweis

Nach der Lektüre this und this, dann ist es mir klar, dass die wirkliche Antwort ist für jedes ernsthaftes Physik Spiel, das Sie haben, sollten Sie Box2D stattdessen werden. Sie erhalten viel zu viele Vorteile von der Migration.

+0

Box2D funktioniert gut und hat keine Probleme wie diese Klebrigkeit an der Kante? – Jurik

+0

@Jurik Das ist, was die Artikel, auf die ich verwiesen habe, beweisen – Mazyod

+0

Ah okay - danke - weil ich durch beide Artikel geschaut habe und keine Informationen gefunden habe, die zu diesem Bug gehören oder dass Box2D keine Float-Rundungsprobleme hat. – Jurik

8

Dies scheint ein Problem bei der Kollisionserkennung zu sein. Die meisten haben Lösungen gefunden, indem sie didBeginContact verwenden und die Kraft in eine entgegengesetzte Richtung anlegen. Hinweis: Er sagt didMoveToView, korrigiert sich aber in einem späteren Kommentar zu didBeginContact.

Siehe Anmerkungen am Ende des Ray Wenderlich Tutorial here

ich eine Lösung für das Problem mit dem Ball haben „Reiten der Schiene“, wenn es Streiks in einem flachen Winkel (@ aziz76 und @colinf). Ich fügte eine weitere Kategorie, "BorderCategory" hinzu und ordnete sie der Grenze PhysicsBody , die wir in didMoveToView erstellen.

und eine ähnliche SO Frage here zu erklären, warum es passiert.

Auch wenn Sie das tun, aber haben viel Physik-Engines (einschließlich SpriteKit die) Probleme mit Situationen wie dieser wegen Gleitkomma-Rundungsfehler. Ich habe festgestellt, dass, wenn ich einen Körper zu halten eine konstante Geschwindigkeit nach einer Kollision wollen, ist es am besten, es zu erzwingen - Verwenden Sie einen DidEndContact: oder didSimulatePhysics Handler, um die Bewegung Körper Geschwindigkeit zurückgesetzt, so dass es die gleiche Geschwindigkeit es geht war vor der Kollision (aber in die entgegengesetzte Richtung).

Auch eine andere Sache, die ich bemerkt, ist, dass Sie für Ihren Ball ein Quadrat statt eines Kreises verwenden und können mit in Betracht ziehen ...

ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.width/2) 

So stellt sich heraus, Sie sind nicht verrückt die ist immer gut, von jemand anderem zu hören und hoffentlich wird dies Ihnen helfen, eine Lösung zu finden, die am besten für Ihre Anwendung funktioniert.

+0

Ausgezeichnete Erklärung, was das Problem ist. +1 – sangony

+0

Ich persönlich habe die Impulslösung schon gefunden, war der intuitivste Weg zu gehen, aber auch hier sollte es nicht nötig sein. Ich verstehe, dass SpriteKit keine deterministische Physik-Engine hat, sondern an einer ausgefeilteren Lösung für alle Fälle interessiert war. schließlich hat der Ball einen runden Körper. – Mazyod

+0

@Mazyod Ich stimme zu. Es sollte kein Problem sein. Wenn du der Physik-Engine nicht trauen kannst, hast du Angst davor, dass deine App nicht wie beabsichtigt funktioniert und es nicht einmal deine Schuld ist. Für so etwas wie ein Breakout-Spiel ist es vielleicht besser, in der Update-Schleife mit der eigenen Positionierung/Geschwindigkeit umzugehen, aber SK benachrichtigt Sie, wenn der Kontakt beginnt. Auf diese Weise haben Sie immer eine konsistente Geschwindigkeit, weil Sie die vollständige Kontrolle darüber haben. Ähnliche Dinge wurden mit dem Bewegen eines Charakters mit einem Bildschirm-Joystick gemacht. Hoffentlich verbessert Apple dies mit dem nächsten Update. –

0

Ich sah genau das gleiche Problem, aber die Lösung für mich war nicht auf die Kollisionserkennung Probleme in den anderen Antworten erwähnt. Es stellte sich heraus, dass ich den Ball in Bewegung setzte, indem ich eine SKAction verwendete, die für immer wiederholt. Ich habe schließlich herausgefunden, dass dies mit der Physiksimulation von SpriteKit kollidiert, die dazu führt, dass der Knoten/Ball entlang der Wand reist, anstatt von ihm abzuprallen.

Ich gehe davon aus, dass die Wiederholung SKAction weiterhin angewendet wird und überschreibt/Konflikte mit der automatischen Anpassung der Physik-Simulation der Ball physicsBody.velocity Eigenschaft.

Die Lösung dafür war, den Ball in Bewegung zu setzen, indem man velocity auf seine physicsBody Eigenschaft setzte. Sobald ich das getan hatte, begann der Ball richtig zu springen. Ich vermute, dass die Manipulation seiner Position über physicsBody durch die Verwendung von Kräften und Impulsen auch funktionieren wird, da sie Teil der Physiksimulation sind.

Es hat mich eine peinliche Zeit gekostet, dieses Problem zu lösen, also poste ich das hier für den Fall, dass ich irgendjemand anderen Zeit sparen kann. Vielen Dank an 0x141e! Deine comment setze mich (und meinen Ball) auf den richtigen Weg.

1

Dieses Problem tritt nur dann auf, wenn die Geschwindigkeit in beiden Richtungen gering ist. Doch die Wirkung zu reduzieren, ist es möglich, die Geschwindigkeit des Physics World zu verringern, zum Beispiel

physicsWorld.speed = 0.1 

und dann die Geschwindigkeit des physicsBody zu erhöhen, zum Beispiel

let targetVector = (moveToward - ball.position).normalized() * 300.0 * 10 
ball.physicsBody!.velocity = CGVector(point: targetVector) 
+0

Das hat funktioniert, aber ... warum ?! – Norbert

+0

Gute Frage. Ich weiß es nicht wirklich, aber ich schätze, das liegt an der Physik: F = ma. Nehmen Sie an, dass m = 1 und numerisch a = dv/dt, eine Schätzung ist, dass die Einstellung der physikalischen Welt die gleiche ist wie die Einstellung von dt, dh wenn wir die pW-Geschwindigkeit auf 0,1 setzen, haben wir F = dv/(0.1 * dt). Wenn also das Gleiten für F CrazyChris