raywenderlich - swift 4 spritekit tutorial
Cómo limitar el movimiento de dos líneas ancladas para que se balanceen continuamente como un péndulo (1)
He creado dos líneas ancladas a un sprite, que están separadas por 30˚. Quiero que ambas líneas se balanceen hacia la izquierda y hacia la derecha como un péndulo, siempre oscilando de un extremo a otro (de tal manera que estén girando 45˚ hacia la izquierda y la derecha de su posición inicial). Por favor vea la siguiente imagen de lo que estoy tratando de lograr:
A continuación se muestra el código de lo que he podido lograr:
extension Int {
var degreesToRadians: Double { return Double(self) * .pi / 180 }
}
extension FloatingPoint {
var degreesToRadians: Self { return self * .pi / 180 }
var radiansToDegrees: Self { return self * 180 / .pi }
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var anchorSprite = SKSpriteNode(imageNamed: "swingPin")
var armLeft = SKSpriteNode(imageNamed: "swingArm")
var armRight = SKSpriteNode(imageNamed: "swingArm")
override func didMove(to view: SKView) {
self.physicsWorld.gravity = CGVector(dx: 0, dy: -1.8)
self.physicsWorld.contactDelegate = self
var tealBg = SKSpriteNode(imageNamed: "tealBg")
tealBg.position = CGPoint(x: frame.midX, y: frame.midY)
tealBg.zPosition = 10
addChild(tealBg)
anchorSprite.position = CGPoint(x: frame.midX, y: frame.midY + frame.midY/2)
anchorSprite.zPosition = 20
anchorSprite.physicsBody = SKPhysicsBody(rectangleOf: anchorSprite.frame.size)
anchorSprite.physicsBody?.categoryBitMask = pinCategory
anchorSprite.physicsBody?.isDynamic = false
addChild(anchorSprite)
armRight.anchorPoint = CGPoint(x: 0.5, y: 1)
armRight.position = anchorSprite.position
armRight.zPosition = 20
armRight.physicsBody = SKPhysicsBody(rectangleOf: armRight.frame.size)
armRight.zRotation = CGFloat(Double(15).degreesToRadians)//CGFloat(Double.pi/6)
armRight.physicsBody!.isDynamic = true
addChild(armRight)
armLeft.anchorPoint = CGPoint(x: 0.5, y: 1)
armLeft.position = anchorSprite.position
armLeft.zPosition = 20
armLeft.physicsBody = SKPhysicsBody(rectangleOf: armRight.frame.size)
armLeft.zRotation = CGFloat(Double(-15).degreesToRadians)//CGFloat(-Double.pi/6)
armLeft.physicsBody!.isDynamic = true
addChild(armLeft)
// Create joint between two objects
//Pin joint
var pinAndRightArmJoint = SKPhysicsJointPin.joint(withBodyA: anchorSprite.physicsBody!, bodyB: armRight.physicsBody!, anchor: CGPoint(x: anchorSprite.position.x, y: self.armRight.frame.maxY))
self.physicsWorld.add(pinAndRightArmJoint)
var pinAndLeftArmJoint = SKPhysicsJointPin.joint(withBodyA: anchorSprite.physicsBody!, bodyB: armLeft.physicsBody!, anchor: CGPoint(x: anchorSprite.position.x, y: self.armLeft.frame.maxY))
self.physicsWorld.add(pinAndLeftArmJoint)
var fixArms = SKPhysicsJointFixed.joint(withBodyA: armLeft.physicsBody!, bodyB: armRight.physicsBody!, anchor: CGPoint.zero)
self.physicsWorld.add(fixArms)
pinAndRightArmJoint.shouldEnableLimits = true
pinAndRightArmJoint.lowerAngleLimit = CGFloat(Double(-60).degreesToRadians)
pinAndRightArmJoint.upperAngleLimit = CGFloat(Double(60).degreesToRadians)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//armRight.physicsBody?.angularVelocity = -100.0
let seq = SKAction.sequence([
SKAction.rotate(byAngle: CGFloat(Double(45).degreesToRadians), duration: 0.5),
SKAction.rotate(byAngle: CGFloat(Double(-45).degreesToRadians), duration: 1.0),
SKAction.rotate(byAngle: CGFloat(Double(45).degreesToRadians), duration: 1.0),
SKAction.rotate(byAngle: CGFloat(Double(-45).degreesToRadians), duration: 1.0),
SKAction.rotate(byAngle: CGFloat(Double(45).degreesToRadians), duration: 1.0),
SKAction.rotate(byAngle: CGFloat(Double(-45).degreesToRadians), duration: 1.0)
])
armRight.run(seq)
}
Desde el código anterior establecí los límites de los ángulos inferior y superior y traté de ejecutar una acción, pero esto solo hace que las líneas se desplacen un poco hacia los lados de una manera muy poco realista. También intenté aplicar la velocidad angular en el cuerpo de física, pero esto solo hizo que girara brevemente a una velocidad inconsistente (necesito que oscile constantemente de un extremo al otro).
nótese bien
Ya que necesito que oscile de un extremo a otro cada vez, necesito que el ciclo de swing sea consistente cada vez, no necesariamente constante. Un ciclo generalmente se movería más rápido a medida que las líneas se mueven hacia el centro y se desaceleran un poco al cambiar de una dirección a otra. Ese es el tipo de sentimiento que quiero para el movimiento.
Aquí está la respuesta práctica:
Reemplace addChild(armRight)
con anchorSprite.addChild(armRight)
. Reemplace addChild(armLeft)
con anchorSprite.addChild(armLeft)
. Elimine armRight.position = anchorSprite.position
y elimine armLeft.position = anchorSprite.position
. Además, a menos que use las uniones físicas para otros movimientos en su código, elimínelos todos, ya que mi solución no requiere uniones.
Ahora sus brazos son hijos de anchorSprite
y están sujetos a su sistema de coordenadas. Si desea rotar ambos brazos en la misma dirección al mismo tiempo, puede ejecutar una acción de rotación en el anclaSprite. Si desea que los brazos giren en diferentes direcciones, tendrá que ejecutar la acción de rotación en cada brazo por separado. Para cualquiera de las dos situaciones, puede usar esta función práctica que hice para la recompensa en esta pregunta :-P
func runPendulumRotationOnNode(_ node:SKNode, withAngle angle:CGFloat, period:TimeInterval, key:String) {
let initialRotate = SKAction.rotate(byAngle: angle/2, duration: period/2)
initialRotate.timingMode = .easeOut
let rotate = SKAction.rotate(byAngle: angle, duration: period)
rotate.timingMode = .easeInEaseOut
let rotateForever = SKAction.repeatForever(SKAction.sequence([rotate.reversed(), rotate]))
let rotateSequence = SKAction.sequence([initialRotate, rotateForever])
node.run(rotateSequence, withKey:key)
}
Lo he probado, y funciona muy bien! Puedes llamarlo así para girar ambos brazos juntos:
runPendulumRotationOnNode(anchorSprite, withAngle:CGFloat.pi/2, period:0.5, key:"")
O para rotar los brazos en direcciones opuestas, puedes usarlo así:
runPendulumRotationOnNode(armRight, withAngle:CGFloat.pi/2, period:0.5, key:"")
runPendulumRotationOnNode(armLeft, withAngle:-CGFloat.pi/2, period:0.5, key:"")
Algunas notas menores, observe cómo uso CGFloat.pi
, una constante fácil para π. Además, esta función supone que el péndulo comienza en su punto medio en la rotación, por lo que π / 2 (90 grados) girará los brazos π / 4 (45 grados) en cualquier dirección.