How to rotate an SKSpriteNode by 360° before go to angle - swift

I was faced with a problem today when I wanted to make a 360° rotation of a spritenode before go to a specified angle, I found a way but I don't know if this is the best.
I tried many ways and this code doesn't work as expected:
let angle = CGFloat(self.angle * number) // angle in degrees
let flip = CGFloat(360+angle).degreesToRadians // 360°+angle in radians
SKAction.rotateToAngle(-flip, duration: 0.4, shortestUnitArc:false)

After trying differents methods this code works as expected, it makes a complete loop before rotate to the angle. Is this the best way?
let angle = CGFloat(self.angle * number)
let flip = CGFloat(360+angle).degreesToRadians
let needleTurn = SKAction.sequence([
SKAction.rotateToAngle(-flip/2, duration: 0.2, shortestUnitArc:true),
SKAction.rotateToAngle(-flip, duration: 0.2, shortestUnitArc:false)
])

Related

Placing "SCNNode" at a given position and facing "SCNCamera" with offset around Y-axis

So,
I have the exact position I want to place the node at. If I test things with a sphere geometry I can place spheres in the world by telling the node:
node.simdPosition = position
(I provide the "position" as an input to the function).
That successfully places the object in the world exactly where I want it to go.
What I really want to do is placing a plane:
let plane = SCNPlane(width: 0.2, height: 0.3)
plane.cornerRadius = plane.width / 10
plane.firstMaterial?.diffuse.contents = UIColor.red
plane.firstMaterial?.specular.contents = UIColor.white
let node = SCNNode(geometry: plane)
Then telling it to be placed at the "position":
node.simdPosition = position
All this works with the plane as well. What I have problems with is the angle:
I want to tell the plane's node to be placed with a given "angle" (around Y) offset to the camera. I tried this but it's not working:
node.rotation = SCNVector4Make(0, 1, 0, currentFrame.camera.eulerAngles.z - angle)
So then, the question is, how can a node be placed at a certain position and at the moment it gets placed in the world, also have a certain Y angle offset from the perpendicular to the camera?
I was using the wrong Euler angle... (z)
This made it work:
node.eulerAngles = SCNVector3Make(0, cameraEulerAngles.y - Float(0.7), 0)

How to get a boss character to fire at all directions at once

I'm new to SpriteKit game development. I'm trying give a boss character the ability to cast fireballs in multiple directions (16 fireballs all at once, 360 degree/16 = 22.5 degree apart).
I know how to get him to fire at a certain position by providing the player's current position, but how to get him to fire at 16 different angles regardless of player's position?
Thanks for any help in advance.
First, set up a loop over the angles
let numAngles = 16
var angle:CGFloat = 0
var angleIncr = CGFloat(2 * M_PI) / CGFloat(numAngles)
let strength:CGFloat = 50
for _ in 0..<numAngles {
...
angle += angleIncr
}
In the loop, convert the angle to the corresponding vector components and then create a vector
let dx = strength * cos (angle)
let dy = strength * sin (angle)
let vector = CGVectorMake (dx, dy)
and create a new fireball and apply an impulse to its physics body
let fireball = ...
fireball.position = player.position
fireball.zRotation = angle
// Add a physics body here
fireball.physicsBody?.appyImpulse (vector)
I'm not sure what code you have in place. for shooting. but ill give this a shot. angles in spritekit are in radians and a there are 2*pi radians in a circle. so you just need to do something like this
let fireballs = 16
let threeSixty = CGFloat(M_PI*2)
for i in 1...fireballs {
let angle = (CGFloat(i) / CGFloat(fireballs)) * threeSixty
// do something useful with your angle
}

How to make a physics impulse move the same percentage of the screen on all devices

So what I am trying to do is make it so that a physics impulsee seems to have the same effect on all devices. So basically if I can figure out A way to do the following I will be able to accomplish my goal.
First lets simplify things by taking out all gravity.
Basically I need to calculate the impulse it will take to get a physics object on the far left of the screen to get to the far right of the screen in the same amount of time no matter how big the screen size is.
The reason I ask is I am making a movement system based on the magnitude and angle of a swipe. However I want it to play the same way on every device. I am calculating magnitude by
(distance (in virtual points)) / (Time spent making gesture)
Then i am applying it as a physics impulse.
This is the code I am working with:
func Jump(angle: CGFloat, strength: CGFloat)
{
if (Ready == true)
{
var rangle:CGFloat = angle * CGFloat(M_PI / 180)
var translate:CGPoint = CGPoint(x: 1, y: 0)
var vx:CGFloat = ((translate.x * cos(rangle)) - (translate.y * sin(angle)))
var vy:CGFloat = ((translate.y * cos(rangle)) + (translate.x * sin(rangle)))
vx *= width
vy *= height
vx *= (strength)
vy *= (strength)
vx /= 4000
vy /= 4000
print("Applying Impulse VX: ")
print(vx)
print(" , VY: ")
print(vy)
println(" )")
var velx = Cavity.physicsBody?.velocity.dx
var vely = Cavity.physicsBody?.velocity.dy
Cavity.physicsBody?.velocity = CGVector(dx: CGFloat(velx!) / 2, dy: CGFloat(vely!) / 2)
Cavity.physicsBody?.applyImpulse(CGVectorMake(vx, vy))
//Cavity.physicsBody?.applyImpulse(CGVectorMake(1000 / width, 1000 / height))
}
}
So basically I want it to be so that if a strength of 1 or 2 is passed it will make the same looking result on all devices.
What you can do is make the strength relative to the screen size.
strengthAdjustment = (1/375*UIScreen.mainScreen().bounds.width)
This uses the iPhone 6 screen (4.7") width (375 pts) to make the strength = 1.
With an iPhone 5s the screen will be only 320 pts which and will only require 0.8533 of the impulse strength to move the width of the screen in the same amount of time.
Hopefully this helps you out.

SceneKit physics add velocity in local space

I am trying to manipulate the player node using physicsBody.velocity by adding or subtracting velocity to the axis for different directions. The problem is that I'm having trouble finding the method for applying that to local space or at least applying the velocity in relation to the direction the object is facing. In other words, it works fine if I have not rotated the object's node. If I do it will still add the velocity to the unrotated space.
I know there is a way to add velocity to the current SCNVector3, but I cannot figure it out.
if isZThrustPositive {
if let velocity = self.physicsBody?.velocity {
if velocity.z * 100 <= 8000 {
thrustDirection = SCNVector3(
x: velocity.x,
y: velocity.y,
z: velocity.z + kPlayerShipMainThrust)
self.physicsBody?.velocity = thrustDirection
}
}
}
I am also trying to rotate the node using angularVelocity in a similar fashion. That works fine as long as the node has not moved or rotated. If it has, it seems to be using world space as well.
if isYRotatingPositive {
if let angularVel = self.physicsBody?.angularVelocity {
self.physicsBody?.angularVelocity = SCNVector4(
x: angularVel.x,
y: angularVel.y + kPlayerShipRotationSpeed,
z: angularVel.z,
w: angularVel.w + kPlayerShipRotationMagnitude)
}
}
After the physics are simulated, I am updating the node's position and rotation.
I have also tried convertPosition, but could not figure out a way to make this work. (I got some really crazy results with this).
Any help is much appreciated.
UPDATE I got the playerShip object to add velocity in the proper directions when it is rotated, but I am still unable to perform multiple rotations without it not turning in the correct direction.
For the ship velocity I did this:
if isXThrustPositive {
thrustDirection = self.convertPosition(SCNVector3(x: kPlayerShipMainThrust, y: 0.0, z: 0.0), toNode: self.parentNode!)
thrustDirection.x = thrustDirection.x - self.position.x
thrustDirection.y = thrustDirection.y - self.position.y
thrustDirection.z = thrustDirection.z - self.position.z
self.physicsBody?.applyForce(thrustDirection, impulse: true)
}
When trying to use a similar method for rotation (I know it would be doomed), the resulting SCNVector3 is just a position, not the direction the node is currently facing. Is there any information on convertTransform and how I might use that?
As it turns out, I had to get the proper position from the rootNode of the scene to perform a proper rotation based on the current orientation of the SCNNode.
xAxis = self.convertPosition(SCNVector3Make(1.0, 0.0, 0.0), toNode: self.parentNode!)
yAxis = self.convertPosition(SCNVector3Make(0.0, 1.0, 0.0), toNode: self.parentNode!)
zAxis = self.convertPosition(SCNVector3Make(0.0, 0.0, 1.0), toNode: self.parentNode!)
if isXRotatingPositive {
self.physicsBody?.applyTorque(
SCNVector4(
x: sin(kPlayerShipRotationSpeed/2.0) * (xAxis.x - self.position.x),
y: sin(kPlayerShipRotationSpeed/2.0) * (xAxis.y - self.position.y),
z: sin(kPlayerShipRotationSpeed/2.0) * (xAxis.z - self.position.z),
w: cos(kPlayerShipRotationSpeed/2.0) * kPlayerShipRotationMagnitude),
impulse: true)
}
Then I just used the standard quaternion rotation formula to get the rotation based on the new axes from the current position.
I hope this helps someone else (and that more information on SceneKit is forthcoming)
If any SceneKit experts want to comment on this or offer suggestions, they are much appreciated. :)

CMDeviceMotion yaw values unstable when iPhone is vertical

In a iOS prototype I use a combination of CMDeviceMotion.deviceMotion.yaw and CLHeading.trueHeading to make stable compass heading that is responsive and accurate. This works well when the iPhone is held flat, where I have a graphical arrow that point to a stable compass heading.
The problem appear when the iPhone is held vertical in portait mode. The UIDeviceOrientation constantly changes from UIDeviceOrientationFaceDown to UIDeviceOrientationFaceUp and back. This makes the yaw value to skip back and forth +/-180 degrees based on small changes of the pitch. Is it possible to lock the device to one orientation that gives a stable yaw value, predict the change without glitches or compute the gyro yaw (or roll in this orientation) in other ways?
This poor guy have the same problem, with no answers. Double points possible people! :)
https://stackoverflow.com/questions/10470938/euler-angle-yaw-not-working-when-iphone-orientation-changes
I was just searching for an answer to this problem. It broke my heart a bit to see that you posted this over a year ago, but I figured maybe you or someone else could benefit from the solution.
The issue is gimbal lock. When pitch is about 90 degrees, yaw and roll match up and the gyro loses a degree of freedom. Quaternions are one way of avoiding gimbal lock, but I honestly didn't feel like wrapping my mind around that. Instead, I noticed that yaw and roll actually match up and can simply be summed to to solve the problem (assuming you only care about yaw).
SOLUTION:
float yawDegrees = currentAttitude.yaw * (180.0 / M_PI);
float pitchDegrees = currentAttitude.pitch * (180.0 / M_PI);
float rollDegrees = currentAttitude.roll * (180.0 / M_PI);
double rotationDegrees;
if(rollDegrees < 0 && yawDegrees < 0) // This is the condition where simply
// summing yawDegrees with rollDegrees
// wouldn't work.
// Suppose yaw = -177 and pitch = -165.
// rotationDegrees would then be -342,
// making your rotation angle jump all
// the way around the circle.
{
rotationDegrees = 360 - (-1 * (yawDegrees + rollDegrees));
}
else
{
rotationDegrees = yawDegrees + rollDegrees;
}
// Use rotationDegrees with range 0 - 360 to do whatever you want.
I hope this helps someone else!
If somebody is interested in the implementation in iOS Swift the code is given below:
let queue = NSOperationQueue()
motionManager.startDeviceMotionUpdatesToQueue(queue) {
[weak self] (data: CMDeviceMotion!, error: NSError!) in
var yawDegrees: Double = self!.motionManager.deviceMotion.attitude.yaw * (180.0 / M_PI)
var pitchDegrees: Double = self!.motionManager.deviceMotion.attitude.pitch * (180.0 / M_PI)
var rollDegrees: Double = self!.motionManager.deviceMotion.attitude.roll * (180.0 / M_PI)
if(rollDegrees < 0 && yawDegrees < 0){
self!.rotationDegrees = 360 - (-1 * (yawDegrees + rollDegrees))
}
else {
self!.rotationDegrees = yawDegrees + rollDegrees
}
}
However I am having some problems and I hope #blkhp19 can help me with this because at certain points the angles go into negative values which then messes up the entire calculation and I can't figure out what the problem is.
The problem is a bit confusing because there are at least two different ways to think about Yaw. One is from the phone's perspective, and one from the world perspective.
I'll use this image from Apple to explain further:
If the phone is flat on a table:
Rotations along the phone's yaw (or Z axis): change the compass heading.
Rotations along the phone's roll (or Y axis): do not change compass heading.
Rotations along the phone's pitch (or X axis): do not change compass heading.
If the phone is flat against a wall:
Rotations along the phone's yaw (or Z axis): change the compass heading.
Rotations along the phone's roll (or Y axis): change the compass heading.
Rotations along the phone's pitch (or X axis): do not change compass heading.
For the remainder of this answer, I'll assume the phone is upright and yaw, pitch, and roll refer to exactly what's in the photo above.
Yaw
You'll need to use atan2 and inspect gravity as in this example.
let yaw = -Angle(radians: .pi - atan2(motion.gravity.x, motion.gravity.y))
Pitch
Similar to the above, I primarily just swapped x and z and it seems to be returning the correct values:
let pitch = Angle(radians: .pi - atan2(motion.gravity.z, motion.gravity.y))
Roll (aka Compass Heading)
Use blkhp19's code above which sums up the attitude yaw and roll. If you import SwiftUI, you can leverage the Angle struct to make radian + degrees conversion easier:
func roll(motion: CMDeviceMotion) -> Angle {
let attitudeYaw = Angle(radians: motion.attitude.yaw)
let attitudeRoll = Angle(radians: motion.attitude.roll)
var compassHeading: Angle = attitudeYaw + attitudeRoll
if attitudeRoll.degrees < 0 && attitudeYaw.degrees < 0 {
compassHeading = Angle(degrees: 360 - (-1 * compassHeading.degrees))
}
return compassHeading
}
Also note that if you don't need the actual angle, and all you need is the relationship (e.g. isPhoneUpright), you can simply read gravity values for those.
extension CMDeviceMotion {
var yaw: Angle {
-Angle(radians: .pi - atan2(gravity.x, gravity.y))
}
var pitch: Angle {
Angle(radians: .pi - atan2(gravity.z, gravity.y))
}
var roll: Angle {
let attitudeYaw = Angle(radians: attitude.yaw)
let attitudeRoll = Angle(radians: attitude.roll)
var compassHeading: Angle = attitudeYaw + attitudeRoll
if attitudeRoll.degrees < 0 && attitudeYaw.degrees < 0 {
compassHeading = Angle(degrees: 360 - (-1 * compassHeading.degrees))
}
return compassHeading
}
}