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))!
}
Move SKSpriteNode by button touch
You create some kind of repeating step that starts at touchesBegan
and ends at touchesEnded
, at touchesCancelled
or when the touch is moved off the node
by checking touchesMoved
override func touchesBegan(...)
{
...
let moveAction = SKAction.repeatForever(SKAction.move(by:CGPoint(x:x,y:y)))
node.run(moveAction,forKey:"moving")
...
}
override func touchesMoved(...)
{
...
//add a check to see if if touch.position is not inside the button
if (...)
{
node.removeAction(withKey:"moving")
}
...
}
override func touchesEnded(...)
{
...
node.removeAction(withKey:"moving")
...
}
override func touchesCancelled(...)
{
...
node.removeAction(withKey:"moving")
...
}
Sprite-Kit: moving an SKSpriteNode
In order to move a node in a direction you want, you first have to know three things
- the
anchorPoint
of the node you want to move - the
anchorPoint
of the parent node of the node you want to move - the
position
of node you want to move
The anchorPoint
property of the nodes store its values in normalised way from (0, 0) to (1, 1). Defaults to (0.5, 0.5) for SPSpriteNode
The position
property of a node store its position as (x, y) coordinates in a ortogonal coordinate system in which the point (0, 0) is positioned where the anchor point of the PARENT is.
So your scene's anchorPoint
is (0.5, 0.5) meaning if you place a direct child in it at coordinates (0, 0), the child will be positioned so that its anchorPoint
is located at (0, 0) which will always match its parent's anchorPoint
.
When you are using those overloads touchesBegan, touchesMoved, etc... inside SKScene
or SKNodes
as a general, if you want to get the position of touch represented in the coordinate system in which all direct children of scene(sknode) are situated, you should do something like
class GameScene: SKScene {
override func didMove(to view: SKView) {
super.didMove(to: view)
let rect = SKSpriteNode(color: .green, size: CGSize(width: 100, height: 100))
addChild(rect)
rect.name = "rect"
// don't pay attention to these two i need them because i dont have .sks file
size = CGSize(width: 1334, height: 750)
anchorPoint = CGPoint(x: 0.5, y: 0.5)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let moveAction = SKAction.move(to: touch.location(in: self), duration: 1)
childNode(withName: "rect")?.run(moveAction)
}
}
hope it was helpful, other problems that you may have is that your ball is not direct child of scene, and you get its coordinates wrong
Touching moving Sprites in SpriteKit
Are your entities too small? With the given info, I recreated a small scene in a playground, and all is working as expected. If I have a presented child node, defined as
let circle = SKShapeNode(circleOfRadius: 20)
And I have defined both the physicsWorld of the SKScene, and physicsBody of the SKNode, with the given touchesBegan, there is no problem in detecting a collision.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let location = touches.first?.location(in: self)
if self.atPoint(location) === circle {
print("TOUCH")
}
}
}
Drag SKSpriteNode based on touch location
You need to offset the newPosition based on the current touch position on the node.
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches)
{
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:self.selectedNode] )
{
CGPoint previousLocation = [touch previousLocationInNode:self];
float diff = location.y - previousLocation.y;
CGPoint newPosition = CGPointMake(node.position.x, node.position.y + diff);
if (newPosition.y > 230) {
newPosition.y = 230;
}
node.position = newPosition;
}
}
}
Related Topics
iOS Swift - Uitableviewcell Custom Subclass Not Displaying Content
Swift Throws Python Errors from Terminal
How to Remove Space Above and Below Text View in Swiftui
Swift 2, Protocol Extensions & Respondstoselector
Exc_Bad_Instruction Happens When Using Dispatch_Get_Global_Queue on iOS 7(Swift)
Lottieanimationview Size Won't Change/Is Too Small (Ios/Swift)
How to Pass/Get Core Data Context in Swiftui Mvvm Viewmodel
Change a Dictionary's Key in Swift
Stack Overflow When Defining Subscript on Ckrecord in Swift
How to Draw Dashed Line in Arkit (Scenekit) Like in the Measure App
Explicitly Unwrapping Optional Nil Does Not Cause Crash
Navigationview Swiftui Shows Split View in iPad
Resizing Uimage When Using Sf Symbols - Uiimage(Systemname:)
Does the Initializer of an 'Open' Class Need to Be Open as Well
Take a Full Screenshot for All Webview in Swift
Swift 1.2 Not Working with Same Function Name and Different Parameter