Swift-Making an SKNode simply move forward on an angle
To do this, you just need to use some trigonometry!
When zRotation
is between 0 and 90, you don't need to do anything to the angle except converting it to radians. Just call tan(radianAngle)
. Now tan
will return how much should the node move in the y axis when it moves by 1 in the x axis. If you learned trigonometry before, you should understand what I'm saying. If you have not learned trigonometry, learn it. :)
Let's say the node's zRotation
is 60 degrees, which is π/3 radians. Pass that into tan
and you get √3. This means your dx
and dy
parameters in SKAction
must be in the ratio of 1 : √3 in order to make the node move in the direction of 60 degrees.
When zRotation
is between 90 and 180, you need to first subtract the angle from 180, then convert it to radians. Again, pass that to tan
and the return value is how much your node should move in the y direction when it moves by -1 in the x axis. The dx : dy
ratio is now -1 : tan(angleInRadians).
When zRotation
is between 180 and 270, subtract 180 from that angle and convert it to radians. The dx : dy
ratio is -1 : -tan(angleInRadians).
Lastly, a zRotation
bewteen 270 and 360, subtract the angle from 360 and convert it to radians. The dx : dy
ratio is 1 : -tan(angleInRadians).
Just before you convert the angle to radians, check if the angle is 90 degrees. If it is, please hard code the dx
and dy
because tan(M_PI / 2)
is undefined.
(Xcode Swift SpriteKit) How do I move a sprite in the direction it is facing
You will need to use sin and cos. An SKAction may not be the best thing for you, so I would just do this in the update method for now till you find a better spot:
sprite.position = CGPoint(x:sprite.position.x + cos(sprite.zRotation) * 10,y:sprite.position.y + sin(sprite.zRotation) * 10)
Where 10 is the magnitude that you want the sprite to move (aka move 10 pixels)
This assumes that angle 0 means the sprite is looking right, angle 90 (PI/2) is looking up, angle 180 (PI) is looking left, and angle 270 (3PI/2) is looking down.
Move view in direction of specific angle
If you have angle you can calculate new coordinates by getting sine and cosine values. You can try out following code
let pathLength = 50 as Double // total distance view should move
let piFactor = M_PI / 180
let angle = 90 as Double // direction in which you need to move it
let xCoord = outView.frame.origin.x + CGFloat(pathLength * sin(piFactor*angle)) //outView is name of view you want to animate
let yCoord = outView.frame.origin.y + CGFloat(pathLength * cos(piFactor*angle))
UIView.animateWithDuration(1, delay: 0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in
self.outView.frame = CGRectMake(xCoord, yCoord, self.outView.frame.size.width, self.outView.frame.size.height)
}, completion: { (Bool) -> Void in
})
Moving SKSpriteNode to location of the touch
Here you go - a simple app with a ship you can drag around the screen and missiles that shoot towards the location of a touch.
If you touch the ship you can drag it around; touch outside the ship and a missile will shoot from the ship to the touch location.
import SpriteKit
class GameScene: SKScene {
var ship = SKSpriteNode()
var shipIsTouched = false
var missileDestination = CGPoint()
let missileSpeed: CGFloat = 800 // Points per second)
let missileFireRate : TimeInterval = 0.2 // Seconds between each missile
override func didMove(to view: SKView) {
missileDestination = CGPoint(x: 0, y: (self.size.height / 2))
createPlayerShip()
let fire = SKAction.sequence([SKAction.run(fireMissile), SKAction.wait(forDuration: missileFireRate)])
run(SKAction.repeatForever(fire))
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
if ship.contains(touch.location(in: self)) {
shipIsTouched = true
} else {
missileDestination = touch.location(in: self)
ship.zRotation = direction(to: missileDestination, from: ship.position) - CGFloat(Double.pi/2)
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if (shipIsTouched == true) {
ship.position = (touches.first?.location(in: self))!
ship.zRotation = direction(to: missileDestination, from: ship.position) - CGFloat(Double.pi/2)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if shipIsTouched {
shipIsTouched = false
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
func createPlayerShip() {
ship = SKSpriteNode(imageNamed: "Spaceship")
ship.zRotation = CGFloat(-Double.pi/2.0)
ship.scale(to: CGSize(width: 150, height: 150))
ship.position = CGPoint(x: -size.width/2 + 200, y: 0)
addChild(ship)
}
func fireMissile() {
let missile = SKSpriteNode(color: .white, size: CGSize(width: 50, height: 10))
missile.position = ship.position
let missileFlightTime = travelTime(to: missileDestination, from: ship.position, atSpeed: missileSpeed)
missile.zRotation = direction(to: missileDestination, from: missile.position)
addChild(missile)
let missileMove = SKAction.move(to: missileDestination,
duration: TimeInterval(missileFlightTime))
let missileRemove = SKAction.removeFromParent()
missile.run(SKAction.sequence([missileMove, missileRemove]))
}
func travelTime(to target : CGPoint, from : CGPoint, atSpeed speed : CGFloat) -> TimeInterval {
let distance = sqrt(pow(abs(target.x - from.x),2) +
pow(abs(target.y - from.y),2))
return TimeInterval(distance/speed)
}
func direction(to target : CGPoint, from: CGPoint) -> CGFloat {
let x = target.x - from.x
let y = target.y - from.y
var angle = atan(y / x)
if x < 0 {
angle = angle + CGFloat.pi
}
return angle
}
}
There's a bit of extra trickery to make the missiles speed consistent (since moveTo takes a time, not a speed, so if the destination was close the missiles would move slowly, and if further away they'd move faster) and to make the missiles rotate to face the destination.
You could create a curved path for the missiles to follow to the destination, which would look cool but may not be appropriate for your app.
EDIT:
If you want the ship stationary, and the missiles to follow your finger, replace all the code down to createPlayerShip
with this (yes, we've lost touchesEnded()
and update()
:
import SpriteKit
class GameScene: SKScene {
var ship = SKSpriteNode()
var missileDestination = CGPoint()
let missileSpeed: CGFloat = 800 // Points per second)
let missileFireRate : TimeInterval = 0.2 // Seconds between each missile
override func didMove(to view: SKView) {
missileDestination = CGPoint(x: size.height/2, y: 0)
createPlayerShip()
let fire = SKAction.sequence([SKAction.run(fireMissile), SKAction.wait(forDuration: missileFireRate)])
run(SKAction.repeatForever(fire))
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
missileDestination = touch.location(in: self)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
missileDestination = (touches.first?.location(in: self))!
}
Rotate an object in its direction of motion
It's fairly straightforward to rotate a sprite in the direction of its motion. You can do this by converting the x
and y
components of the sprite's velocity into an angle with the atan2
function. You should rotate the sprite only when its speed is greater than some nominal value to prevent the sprite from resetting to zero degrees when the sprite's speed is (nearly) zero.
If we extend CGVector
to compute speed and angle from the velocity's components by
extension CGVector {
func speed() -> CGFloat {
return sqrt(dx*dx+dy*dy)
}
func angle() -> CGFloat {
return atan2(dy, dx)
}
}
we can rotate a sprite to face in the direction of its motion with
override func didSimulatePhysics() {
if let body = sprite.physicsBody {
if (body.velocity.speed() > 0.01) {
sprite.zRotation = body.velocity.angle() - offset
}
}
}
where offset = CGFloat(M_PI_2)
if your sprite faces up when zRotation
is zero, and offset = 0
if your sprite faces to the right when zRotation
is zero.
Related Topics
Uisearchcontroller Hiding Status Bar on iOS 11 Swift 4
How to Remove The Fading Animation on .Ondelete Swiftui
Nssavepannel - How to Restrict User to Only Save One One Set Directory
Nsnumber/Nsdecimalnumber Bizarre Behavior
How to Resolve Rctpromiseresolveblock After Bftask
Customize Mglpolyline Using Mapbox
Touch Sprite, Make It Jump Up Then Fall Down Again(Repeat as Many Times as Spritenode Is Tapped.)
Mkpolyline Broken When Using Type Satelliteflyover
How to Constrain Second Nsviewcontroller Minimum Size in Os X App
Can't Update Label from Other Class in Swift
How to Download Datas from Multiple Url in Concurrency Mode
Swift: How to Continuously Send an Action from a Nstextfield
Pfuser That Created Object Cannot Delete Object Despite Acl Writing Access
How to Observe Torchlevel in Swift
What's a Placeholder? Why Do We Need to Define Placeholder Data Type for Generics
Turn Off Splash Screen When Using Flutterviewcontroller Within Existing Native App
How to Stop Playback of Avplayerviewcontroller as UIviewrepresentable After Dismissal