I'm trying to rotate sprites with SKAction.rotateToAngle. For example:
rotate = SKAction.rotateToAngle(desiredAngle, duration: 1.0)
sprite.runAction(rotate)
But it didn't work as expected. The sprites rotates in a different way. By trying and trying I've found out that this is the rotation system adopted by SpriteKit:
Now I don't know why they decided to adopt this coordinate system but it's pretty counter-intuitive. How can I convert between this system and the normal one?
And by the way how can I rotate the sprites clockwise?
From the docs:
An angle of 0 radians specifies the positive x axis. A positive angle
is in the counterclockwise direction.
To rotate an object by 90 degrees counterclockwise, do this:
let action = SKAction.rotateToAngle(CGFloat(M_PI/2.0), duration: 5.0)
To rotate an object by 90 degrees clockwise, do this:
let action = SKAction.rotateToAngle(CGFloat(-M_PI/2.0), duration: 5.0)
Or instead of using negatives (-90 degrees), you can specify the angle as a positive value(270 degrees), and use rotateToAngle(_:duration:shortestUnitArc:) method which rotates the object in direction which results in the smallest rotation.
let action = SKAction.rotateToAngle(CGFloat(2 * M_PI - M_PI/2.0), duration: 5, shortestUnitArc: true)
Swift 5.2
To rotate an object by 180 degree clockwise:
let spin = SKAction.rotate(toAngle: -.pi, duration: 10)
To rotate an object by 180 degree counterclockwise:
let spin = SKAction.rotate(toAngle: .pi, duration: 10)
To 90 degrees, divide by 2:
let spin = SKAction.rotate(toAngle: -.pi/2, duration: 10)
let spin = SKAction.rotate(toAngle: .pi/2, duration: 10)
Related
i am following this tutorial (i think it is written in JavaScript): 3D Terrain
Im trying it in Swift with SpriteKit but have a problem with the rotation at the x-axis.
So this is what it looks like in the tutorial:
And this is where i am now:
I have created this grid with following Code:
var shapePoints = [CGPoint]()
for i in 0...zeilen+1{
for j in 0...spalten{
shapePoints.append(CGPoint(x: zellenGroesse*j, y: zellenGroesse*i))
shapePoints.append(CGPoint(x: zellenGroesse*j, y: zellenGroesse*(i+1)))
}
}
let fertigeShape = SKShapeNode(points: &shapePoints, count: shapePoints.count)
self.addChild(fertigeShape)
Now i would like to rotate it by some degrees, but i can only rotate it at the z-axis, not the x-axis.
Is there a way in SpriteKit to rotate the node at the x-axis, too?
Thanks and best regards
You can since iOS11, with what's called an SKTransformNode. When you want to 3D perspective tilt a node, add it as a child to a SKTransformNode, and then set it's xRotation or yRotation, which is a value in radians.
let t = SKTransformNode()
t.addChild(someNode)
scene.addChild(t)
t.xRotation = CGFloat.pi // tilts someNode by 180 degrees
You can also animate the change, like this.
let degrees: CGFloat = 20
let radians = CGFloat.pi / 180 * degrees
let duration = 2.0
let tilt = SKAction.customAction(withDuration: duration) { (node, elapsed) in
let percent = elapsed / CGFloat(duration)
t.xRotation = percent * radians
}
tilt.timingMode = .easeOut
t.run(tilt)
No, it isn't possible, SpriteKit is 2d. You should look at SceneKit for 3d. https://developer.apple.com/reference/scenekit
Hopefully you found a solution, but now Apple has released SKTransformNode - you can put your node(s) inside an SKTransformNode and rotate them around X and Y in addition to the standard Z
I have an image named "Ghost" and it moves throughout the screen. I want to have the SpriteNode constantly turning without being pressed on.
Ghost = SKSpriteNode(imageNamed: "Ghost1")
Ghost.size = CGSize(width: 50, height: 50)
Ghost.position = CGPoint(x: self.frame.width / 2 - Ghost.frame.width, y: self.frame.height / 2)
Ghost.physicsBody = SKPhysicsBody(circleOfRadius: Ghost.frame.height / 1.4)
Ghost.physicsBody?.categoryBitMask = PhysicsCatagory.Ghost
Ghost.physicsBody?.collisionBitMask = PhysicsCatagory.Ground | PhysicsCatagory.Wall
Ghost.physicsBody?.contactTestBitMask = PhysicsCatagory.Ground | PhysicsCatagory.Wall | PhysicsCatagory.Score
Ghost.physicsBody?.affectedByGravity = false
Ghost.physicsBody?.isDynamic = true
Ghost.zPosition = 2
self.addChild(Ghost)
I have looked for some answers on Stack Overflow but I can't seem to find any one with this same question, please help.
To have the SKSpriteNode constantly rotating, add this to wherever you want to start the rotating:
let rotate = SKAction.rotate(byAngle: CGFloat.pi * 2.0, duration: 2)
Ghost.run(SKAction.repeatForever(rotate), withKey: "rotateGhost")
To rotate in the opposite direction, change CGFloat.pi to -CGFloat.pi
You can fidget with the duration to change the duration for a complete rotation.
If you wish to remove the action, call:
removeAction(forKey: "rotateGhost")
Or you can just pause the action instead.
You could apply a force of an angular type, so as to rotate the body, and turn off damping so it continues to rotate forever:
https://developer.apple.com/reference/spritekit/skaction/1417775-applyangularimpulse
Or, apply torque:
Firstly, make sure it’s able to rotate by setting this to true:
https://developer.apple.com/reference/spritekit/skphysicsbody/1519986-allowsrotation
Make sure there’s no angular damping so that it doesn’t slow in rotation rate:
https://developer.apple.com/reference/spritekit/skphysicsbody/1519913-angulardamping
Now make it spin by applying a torque:
https://developer.apple.com/reference/spritekit/skphysicsbody/1519588-applytorque
Or, set a spin rate:
https://developer.apple.com/reference/spritekit/skphysicsbody/1519766-angularvelocity
I have a SKAction that changes the y position of a sprite. The sprite is affected by gravity towards the right, and when i set the x position in the SKAction to the sprite's position the sprite movement is slowing down while the Action is running.
How can i change this so that the x movement is not effected by the action running?
let moveDown = SKAction.moveTo(CGPoint(x: player.position.x, y: 0), duration: 0.3)
player.runAction(moveDown)
You could try using linearDamping. I'm not sure this is exactly what you're looking for, but it's a property that reduces the body’s linear velocity.
player.physicsBody!.linearDamping = 0.0
Let me know if this works for you or if this wasn't what you were looking for!
Solved it using this code:
let moveDown = SKAction.moveToY(0, duration: 0.3)
player.runAction(moveDown)
I have a SceneKit SCNBox Cube that should follow all the movements of my iphone. So If my iphone rotate 90 or 360 degrees sideways should the cube also rotate 90 or 360 degrees and if I spin my phone forward the cube should spin like the phone.
I have tried with startDeviceMotionUpdatesUsingReferenceFrame, but it doesn't match the movements of my phone :(
motionManager.deviceMotionUpdateInterval = 1.0 / 60.0
motionManager.startDeviceMotionUpdatesUsingReferenceFrame(
CMAttitudeReferenceFrame.XArbitraryZVertical,
toQueue: NSOperationQueue.mainQueue(),
withHandler: { (motion: CMDeviceMotion!, error: NSError!) -> Void in
let currentAttitude = motion.attitude
let roll = Float(currentAttitude.roll)
let pitch = Float(currentAttitude.pitch)
let yaw = Float(currentAttitude.yaw)
self.arrRoll.append(roll)
self.arrPitch.append(pitch)
self.arrYaw.append(yaw)
cubeNode.eulerAngles = SCNVector3Make(roll, 0.0, 0.0)
cubeNode.eulerAngles = SCNVector3Make(0.0, 0.0, pitch)
cubeNode.eulerAngles = SCNVector3Make(0.0, yaw, 0.0)
})
Your code snippet is a little out of context, but one thing that strikes me off the bat is that you are (most likely unintentionally) overwriting the eulerAngles property of your node.
Why do you have 3 separate assignments (only the last of which will have an effect), instead of:
cubeNode.eulerAngles = SCNVector3Make(roll, yaw, pitch)
?
I am really bad at explaining these situations so bear with me.
What I want is when the user taps the white annotation, that point will scroll to the center (along with the globe)
I would also like to be able to do this programmatically, scrolling to a point when i provide x/y coords for the globe
I am using the following function to calculate the SCNVector3 based on x/y coordinates
func positionForCoordinates(coordinates: CGPoint, radius: CGFloat) -> SCNVector3 {
let s = coordinates.x
let t = coordinates.y
let r = radius
let x = r * cos(s) * sin(t)
let y = r * sin(s) * sin(t)
let z = r * cos(t)
return SCNVector3(x: Float(x), y: Float(y), z: Float(z))
}
its the math that really is eluding me.
Let's assume according to your problem, we knew the following
coordinate (latitude/longitude) of annotation that you placed it on the globe, and you want to simulate rotating a globe model to center on it
a camera is facing at the center of the screen
we will use a camera to rotate around globe model instead of rotating the globe itself
We can take advantage of animatable properties of SCNNode and SCNCamera so this will make it easier for us to do animation and not to manually lerp values in render loop.
First - Set up camera
func setupCamera(scene: SCNScene) {
cameraOrbit = SCNNode()
cameraNode = SCNNode()
camera = SCNCamera()
// camera stuff
camera.usesOrthographicProjection = true
camera.orthographicScale = 10
camera.zNear = 1
camera.zFar = 100
// initially position is far away as we will animate moving into the globe
cameraNode.position = SCNVector3(x: 0, y: 0, z: 70)
cameraNode.camera = camera
cameraOrbit = SCNNode()
cameraOrbit.addChildNode(cameraNode)
scene.rootNode.addChildNode(cameraOrbit)
}
Camera node is set with SCNCamera instance via its camera property, and is wrapped inside another node as we will use to manipulate its rotation.
I left code for defining those variables in the class for brevity.
Second - Implement conversion method
We need a method that convert map coordinate (latitude/longitude) to rotation angles for us to plug it into SCNNode.eulerAngles in order to rotate our camera around the globe model.
/**
Get rotation angle of sphere along x and y direction from input map coordinate to show such location at the center of view.
- Parameter from: Map coordinate to get rotation angle for sphere
- Returns: Tuple of rotation angle in form (x:, y:)
*/
func rotationXY(from coordinate: CLLocationCoordinate2D) -> (x: Double, y: Double) {
// convert map coordiante to texture coordinate
let v = 0.5 - (coordinate.latitude / 180.0)
let u = (coordinate.longitude / 360.0) + 0.5
// convert texture coordinate to rotation angles
let angleX = (u-0.5) * 2 * Double.pi
let angleY = (0.5-v) * -Double.pi
return (x: angleX, y: angleY)
}
From the code, we need to convert from map coordinate to texture coordinate in which when you place a texture ie. diffuse texture onto a sphere to render normally without any modification of rotation angle of globe node, and camera's position is placed along z-axis (ie. x=0, y=0, z=N), the center of such texture will be shown at the camera. So in the equation, we take into account 0.5 to accommodate on this.
After that we convert results into angles (radians). Along x direction, we could rotate sphere for 360 degrees to wrap around it fully. For y direction, it takes 180 degrees to wrap around it.
Please note I didn't get rid of 0.5 for both case as to make it as-is in each conversion, and for clearer to see along with explanation. You can simplify it by removing from the code.
Third - Animate
As a plus, I will include zoom level as well.
Let's assume that you allow zooming in level of 0.0 to 10.0. We use orthographicScale property of SCNNode to simulate zooming in for orthographic type of camera we've set up above. In contrast, orthographicScale is inverse of zoom level in normal understanding. So at zoom level of 10.0, orthographicScale will be 0.0 in order to achieve zoom-in effect.
/**
Rotate camera around globe to specified coordinate.
- Parameter to: Location in coordinate (latitude/longitude)
- Parameter zoomLevel: Zoom level in range 0.0 - 10.0.
- Parameter duration: Duration for rotation
- Parameter completion: Delegate when rotation completes to notify back to user
*/
func flyGlobeTo(to location: CLLocationCoordinate2D, zoomLevel: Double, duration: Double, completion: (()->Void)?=nil) {
// make a call to our conversion method
let rotation = self.rotationXY(from: location)
SCNTransaction.begin()
SCNTransaction.animationDuration = duration
SCNTransaction.completionBlock = {
completion?()
}
self.cameraOrbit.eulerAngles.x = Float(rotation.y)
self.cameraOrbit.eulerAngles.y = Float(rotation.x)
self.camera.orthographicScale = 10.0 - zoomLevel // calculate value from inverse from orthographicScale
SCNTransaction.commit()
}
And that's it. Whenever you need to fly to a target coordinate on the map, just use flyGlobeTo() method. Make sure you call it in main thread. Animation is done via SCNTransaction and not through UIView.animate, obviously it's different technology but I note it here too as first time I did use the latter myself.