Above is an image of my game. A top-down game. Wherever on the screen the player touches, I want the bullets to go to that location with a duration. I also want the
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))!
}