How can I make an arrow swing like in this video?
So far, I can rotate my node back and forth like in this video using the following code in didMove(to:) in my SKScene:
// Ball
let ballNode = SKSpriteNode(imageNamed: "Ball")
let offsetFromCorner: CGFloat = 20
ballNode.position = CGPoint(x: frame.minX + ballNode.size.width / 2 + offsetFromCorner, y: frame.minY + ballNode.size.height / 2 + offsetFromCorner)
addChild(ballNode)
/* ... */
// Aim arrow
let aimArrowNode = SKSpriteNode(imageNamed: "AimArrow")
aimArrowNode.position.y += aimArrowNode.size.height / 2
ballNode.addChild(aimArrowNode)
ballNode.zRotation = -.pi / 18 * 8
let rotateUp = SKAction.rotate(toAngle: -.pi / 18, duration: 1)
let rotateDown = SKAction.rotate(toAngle: -.pi / 18 * 8, duration: 1)
let combinedActions = SKAction.sequence([rotateUp, rotateDown])
ballNode.run(SKAction.repeatForever(combinedActions))
However, I want the arrow to appear to "slow down" as it gets nearer to the edge. How can I achieve this?
If you have any questions, please ask!
Luckily, the answer is actually built into SKAction.
Just add these after you create the actions:
rotateUp.timingMode = .easeInEaseOut
rotateDown.timingMode = .easeInEaseOut
Now you get the effect I was looking for! :)
Related
I am creating a iOS Game where a player has a bow and arrow that spins in a 360 degree circle, and the player must shoot the bow at the right time to hit the target. Right now I am having trouble getting the arrow to shoot in the direction the bow is facing, as well as getting the arrow to shoot at the right angle towards that direction.
let bullet = SKSpriteNode(fileNamed: "Bullet")
bullet?.size = CGSize(width: 100, height: 100)
bullet.zPosition = -5
bullet.position = CGPointMake(player.position.x, player.position.y)
bullet.zRotation = player.zRotation
let action = SKAction.moveToY(self.size.height + 30, duration: 0.8)
let actionDone = SKAction.removeFromParent()
bullet.runAction(SKAction.sequence([action, actionDone]))
bullet.physicsBody = SKPhysicsBody(rectangleOfSize: bullet.size)
bullet.physicsBody?.affectedByGravity = false
bullet.physicsBody?.dynamic = false
self.addChild(bullet)
The player refers the the bow for reference.
Instead of moving the bullet to self.size.height + 30 in the Y direction and 0 pixels in the X direction, you can rotate that movement direction by the zRotation using trigonometry.
let amount = self.size.height + 30
let action = SKAction.moveTo(CGPointMake(bullet.position.x + amount * sin(bullet.zRotation), bullet.position.y + amount * cos(bullet.zRotation)), duration: 0.8)
You can get the behavior you are looking for by calculating a force vector from the bullet's zRotation and then use it to apply a force to the bullet's physicsBody.
To do this we will use trigonometry.
//adjust rotation by pi/2 radians to match spriteKits rotation system
let adjustedRotation = bullet.zRotation + .pi/2
//intensity scalar
let intensity:CGFloat = 4000 //adjust this value
//find x and y components using adjustedRotation and scale by intensity
let vx = intensity * cos(adjustedRotation)
let vy = intensity * sin(adjustedRotation)
//make vector using vx and vy components
let forceVector = CGVector(dx:vx, dy: vy)
//apply force to physicsBody
bullet.physicsBody?.applyForce(forceVector)
I am creating an AR app using ARKit in which I have to place a TV on a wall. As vertical plane detection is not supported in ARKit as of now, I have created a wall using the technique like https://github.com/bjarnel/arkit-occlusion did. Now I have to place a TV on this wall. But when I am placing the TV model on this wall, it's not oriented correctly. I am using the same Euler angles for TV model as of wall. But I am not able to place it correctly.
Code for creating the wall -
class func node(from:SCNVector3,
to:SCNVector3) -> SCNNode {
let distance = from.distance(vector: to)
let wall = SCNPlane(width: CGFloat(distance),
height: HEIGHT)
wall.firstMaterial = wallMaterial()
let node = SCNNode(geometry: wall)
// always render before the beachballs
node.renderingOrder = 10
// get center point
node.position = SCNVector3(from.x + (to.x - from.x) * 0.5,
from.y + Float(HEIGHT) * 0.5,
from.z + (to.z - from.z) * 0.5)
node.eulerAngles = SCNVector3(0, -atan2(to.x - node.position.x, from.z - node.position.z) - Float.pi * 0.5, 0)
return node
}
Code for loading the TV model on wall -
let tvScene = SCNScene(named: "art.scnassets/\(name)")
let tvNode = tvScene?.rootNode.childNode(withName: "TV",
recursively: true)
tvNode?.position = SCNVector3(hitResult.worldTransform.columns.3.x, hitResult.worldTransform.columns.3.y, wall.position.z)
tvNode?.eulerAngles = SCNVector3(-90, wall.eulerAngles.y, 180)
self.sceneView.scene.rootNode.addChildNode(tvNode!)
Please help me out.
I don't know about your angle are correctly calculated or not. But SCNNode requires angles in Radians not in degree
SCNVector3(-90, wall.eulerAngles.y, 180)
Should be
SCNVector3(-.pi / 2, wall.eulerAngles.y, pi)
Hope it is helpful
I have a player that when the user taps I want to be able to spawn 8 bullets around the player (with a 45˚ separation between them) and proceed to move them outwards to the edge of the screen.
The circle from where the bullets originate from is correct, but the bullets in the bottom left of the screen seem to be moving faster than the ones in the top right. Also the bullets are facing sideways, not pointing outwards.
func fireSpecialWeapon() {
stride(from: 0, to: 2 * CGFloat.pi, by: 2 * CGFloat.pi / 10 ).forEach { angle in
let bullet = SKSpriteNode(imageNamed: "bulletCircle")
bullet.setScale(3)
bullet.zRotation = angle
bullet.position = player.position
bullet.zPosition = 2
//move outwards to the edge of the screen
let distance: CGFloat = 2000
let endPoint = CGPoint(x: distance * cos(angle), y: distance * sin(angle))
let move = SKAction.move(to: endPoint, duration: 2)
self.addChild(bullet)
bullet.run(move)
}
}
You should use trig to figure out the end point based on the angle. distance * sin is the y component and distance * cos is the x component. The code looks somethign like this:
stride(from: 0, to: 2 * CGFloat.pi, by: 2 * CGFloat.pi / 8).forEach { angle in
let bullet = SKSpriteNode(imageNamed: "bulletCircle")
bullet.setScale(3)
bullet.zRotation = angle
bullet.position = player.position
bullet.zPosition = 2
//move outwards to the edge of the screen
let distance: CGFloat = 500
let endPoint = CGPoint(x: distance * cos(angle), y: distance * sin(angle))
let move = SKAction.move(to: endPoint, duration: 2)
self.addChild(bullet)
bullet.run(move)
}
The first thing I noticed is that your rotation calculation is not correct.
First the rotation of each bullet should be pi / 4 from its neighbours.
So you should not use pi / I but (pi / 4) * I.
That should fix up the rotation but I’m not sure if that’s everything that’s not working.
Hey I have a ball that gets moved by a force-applied. What im trying to get it to do is basically have the causal effect of gravity acting upon it while its moving through the air to its destination. basically when the "move to" action is playing gravity does not take affect so instead of slowly falling down to the ground it instead moves to its final position then it just falls straight down when the "move to" action stops. do to the gravity in the scene.
Im trying to get the ball to be thrown in an arc and land on the target?
Code:
func CreateBall() {
let BallScene = SCNScene(named: "art.scnassets/Footballs.dae")
Ball = BallScene!.rootNode.childNodeWithName("Armature", recursively: true)! //the Amature/Bones
Ballbody = BallScene!.rootNode.childNodeWithName("Ball", recursively: true)!
let collisionCapsuleRadius3 = CGFloat(0.01) // Width of physicsBody
let collisionCapsuleHeight3 = CGFloat(0.01) // Height of physicsBody
Ball.position = SCNVector3Make(Guy.position.x, Guy.position.y, Guy.position.z)
Ball.scale = SCNVector3Make(5, 5, 5)
Ball.rotation = SCNVector4Make(0.0,0.0,0.0,0.0) // x,y,z,w
Ball.physicsBody = SCNPhysicsBody(type: .Dynamic, shape:SCNPhysicsShape(geometry: SCNCapsule(capRadius: collisionCapsuleRadius3, height: collisionCapsuleHeight3), options:nil))
Ball.physicsBody?.affectedByGravity = true
Ball.physicsBody?.friction = 1 //
Ball.physicsBody?.restitution = 0 //bounceness of the object. 1.0 will boounce forever
Ball.physicsBody?.angularDamping = 1 // ability to rotate
Ball.physicsBody?.mass = 1
Ball.physicsBody?.rollingFriction = 1
Ball.physicsBody!.categoryBitMask = BitmaskCollision4
Ball.physicsBody?.contactTestBitMask = BitmaskCollision3 //| BitmaskCollision2
Ballbody.physicsBody?.collisionBitMask = BitmaskCollision2 | BitmaskCollision3 | BitmaskCollision//| BitmaskCollision2
scnView.scene!.rootNode.addChildNode(Ball)
scnView.scene!.rootNode.addChildNode(Ballbody)
}
CreateBall()
now this is where the magic happens:
scnView.scene!.physicsWorld.gravity = SCNVector3(x: 0, y: -9.8, z: 0)
let location = SCNVector3(Guy2.presentationNode.position.x, 0.0, Guy2.presentationNode.position.z + Float(50) )
let moveAction = SCNAction.moveTo(location, duration: 2.0)
Ball.runAction(SCNAction.sequence([moveAction]))
let forceApplyed = SCNVector3(x: 0.0, y: 100.0 , z: 0.0)
Ball.physicsBody?.applyForce(forceApplyed, atPosition: Ball.presentationNode.position, impulse: true)
Combining SCNActions and physics doesn't work, you need to use one or the other. Using physics you can calculate the exact force needed to propel your node to a target.
I have adapted a solution for Unity found here and utilised an SCNVector3 extension that makes some of the calculations much easier.
Basically you pass in an SCNNode that you want to throw, an SCNVector3 for the target and an angle (in radians) that you want the node to be thrown at. This function will then work out the force required to reach the target.
func shootProjectile() {
let velocity = ballisticVelocity(ball, target: target.position, angle: Float(0.4))
ball.physicsBody?.applyForce(velocity, impulse: true)
}
func ballisticVelocity(projectile:SCNNode, target: SCNVector3, angle: Float) -> SCNVector3 {
let origin = projectile.presentationNode.position
var dir = target - origin // get target direction
let h = dir.y // get height difference
dir.y = 0 // retain only the horizontal direction
var dist = dir.length() // get horizontal distance
dir.y = dist * tan(angle) // set dir to the elevation angle
dist += h / tan(angle) // correct for small height differences
// calculate the velocity magnitude
let vel = sqrt(dist * -scene.physicsWorld.gravity.y / sin(2 * angle))
return dir.normalized() * vel * Float(projectile.physicsBody!.mass)
}
It is also important to set the damping of the physicsBody to 0, otherwise it will be affected by air resistance.
I’m not going to pretend to know exactly how this works, but Wikipedia has articles that explain all the maths behind it.
UPDATE
Since using the code above I've noticed it doesn't always work, especially when the heights of the origin and target are different. From the same forum this function seems more reliable.
func calculateBestThrowSpeed(origin: SCNVector3, target: SCNVector3, timeToTarget:Float) -> SCNVector3 {
let gravity:SCNVector3 = sceneView.scene!.physicsWorld.gravity
let toTarget = target - origin
var toTargetXZ = toTarget
toTargetXZ.y = 0
let y = toTarget.y
let xz = toTargetXZ.length()
let t = timeToTarget
let v0y = y / t + 0.5 * gravity.length() * t
let v0xz = xz / t
var result = toTargetXZ.normalized()
result *= v0xz
result.y = v0y
return result
}
I'm building a 2D game and I'm looking to direct a node in a particular clockwise curve from the sky to the ground (like a bird would swoop down to the ground) in a clockwise motion with some friction to give the effect of a bird landing.
I've been playing around with 'physicsBody' but i cant seem to get the right curve, can anyone help... here is a picture of the curve I'm trying to achieve. http://www.theluxuryteam.com/uploads/shared/images/curved_arrow-black.png
Thank you
func addCrow() {
var crow = SKSpriteNode(imageNamed: "crow")
crow.physicsBody = SKPhysicsBody(rectangleOfSize: crow.size)
crow.physicsBody?.dynamic = true
crow.physicsBody?.collisionBitMask = 0
crow.physicsBody?.velocity = CGVector(dx: -200, dy: 0)
crow.physicsBody?.angularVelocity = 100
crow.physicsBody?.linearDamping = 0
crow.physicsBody?.angularDamping = 100
var random : CGFloat = CGFloat(arc4random_uniform(300))
crow.position = CGPoint(x: self.frame.size.width / 1.4, y: self.frame.size.height / 1.2 ) // 1.4 0.8
self.addChild(crow)
}