how to throw SKSpriteNode?
Here is a quick example I wrote of moving a sprite with touch by simulating its velocity in the game-loop as opposed to setting the position directly. This makes the sprite more dynamic (i.e. you can "throw" it, and let it interact with other physics bodies as your drag the sprite). No angle calculations are needed, i'm simply calculating the necessary velocity to move the sprite to the touch position over some interval of time. In this case I set the time as 1/60 so that the motion is applied instantaneously so the object appears very responsive.
import SpriteKit
class GameScene: SKScene {
var sprite: SKSpriteNode!
var touchPoint: CGPoint = CGPoint()
var touching: Bool = false
override func didMoveToView(view: SKView) {
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
sprite = SKSpriteNode(color: UIColor.redColor(), size: CGSize(width: 50, height: 50))
sprite.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.size)
sprite.position = CGPoint(x: self.size.width/2.0, y: self.size.height/2.0)
self.addChild(sprite)
}
override func touchesBegan(touches: Set, withEvent event: UIEvent) {
let touch = touches.first as! UITouch
let location = touch.locationInNode(self)
if sprite.frame.contains(location) {
touchPoint = location
touching = true
}
}
override func touchesMoved(touches: Set, withEvent event: UIEvent) {
let touch = touches.first as! UITouch
let location = touch.locationInNode(self)
touchPoint = location
}
override func touchesEnded(touches: Set, withEvent event: UIEvent) {
touching = false
}
override func update(currentTime: CFTimeInterval) {
if touching {
let dt:CGFloat = 1.0/60.0
let distance = CGVector(dx: touchPoint.x-sprite.position.x, dy: touchPoint.y-sprite.position.y)
let velocity = CGVector(dx: distance.dx/dt, dy: distance.dy/dt)
sprite.physicsBody!.velocity=velocity
}
}
}
You can fine-tune the phyiscs calculations yourself to get the desired effect you are looking for. But this code should be enough to get you started. Some enhancements I could think of are possibly capping the velocity so the object can't move too fast when released. Adding a delay to the touch-drag so that moving the sprite but then accidentally stopping short at the end continues to throw the object.
Dragging and flicking/throwing skspritenode
This should work, I just dug it out from an old project.
CGFloat dt is for changing the speed/power of the movement.
var touching = false
override func touchesBegan(touches: Set, withEvent event: UIEvent) {
let touch = touches.first as! UITouch
let location = touch.locationInNode(self)
if sprite.frame.contains(location) {
touchPoint = location
touching = true
}
}
override func touchesMoved(touches: Set, withEvent event: UIEvent) {
let touch = touches.first as! UITouch
let location = touch.locationInNode(self)
touchPoint = location
}
override func touchesEnded(touches: Set, withEvent event: UIEvent) {
touching = false
}
override func update(currentTime: CFTimeInterval) {
if touching {
if touchPoint != sprite.position
{
let dt:CGFloat = 0.15
let distance = CGVector(dx: touchPoint.x-sprite.position.x, dy: touchPoint.y-sprite.position.y)
let vel = CGVector(dx: distance.dx/dt, dy: distance.dy/dt)
sprite.physicsBody!.velocity = vel
}
}
}
EDIT:
The reason it gets stronger the farther the distance, is because the vector IS the distance between the the sprite and the touch point.
Try popping this in as the update function. It should work...
override func update(currentTime: CFTimeInterval) {
if touching {
if touchPoint != sprite.position
{
let pointA = touchPoint
let pointB = sprite.position
let pointC = CGPointMake(sprite.position.x + 2, sprite.position.y)
let angle_ab = atan2(pointA.y - pointB.y, pointA.x - pointB.x)
let angle_cb = atan2(pointC.y - pointB.y, pointC.x - pointB.x)
let angle_abc = angle_ab - angle_cb
let vectorx = cos(angle_abc)
let vectory = sin(angle_abc)
let dt:CGFloat = 15
let vel = CGVector(dx: vectorx * dt, dy: vectory * dt)
sprite.physicsBody!.velocity = vel
}
}
}
With the touchPoint (pointA), and the sprite's position (pointB) we can create an angle.
atan2, is a very famous function from C, it creates the angle between two points.
BUT, it's 0 degrees is in a different location than usual.
So, we need our own 0 degrees marker, I use the mid-right of the point as my marker.
It's the common 0 degree placement:
Since it's to the right of the sprite's position, we create a point just to the right of the sprite (pointC).
We now use atan2 to find the angle.
To create a vector from an angle, we just use cos and sin for the x and y values.
Throwing an object with SpriteKit
You accidentally placed touchesMoved
, touchesEnded
and update
inside touchesBegan
. Besides that your code works. A hint that there were problems was the fact you didn't need to prefix touchesMoved
, touchesEnded
or update
with override
.
In the future, I would recommend using breakpoints and print statements to check the methods you expect to execute, are in fact running. Doing that you'd see that your versions of touchesMoved
, touchesEnded
and update
weren't being called.
Anyway, here's it corrected it and now it works perfectly:
import SpriteKit
class GameScene: SKScene {
var sprite: SKSpriteNode!
var touchPoint: CGPoint = CGPoint()
var touching: Bool = false
override func didMoveToView(view: SKView) {
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
sprite = SKSpriteNode(color: UIColor.redColor(), size: CGSize(width: 50, height: 50))
sprite.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.size)
sprite.position = CGPoint(x: self.size.width/2.0, y: self.size.height/2.0)
self.addChild(sprite)
}
override func touchesBegan(touches: Set, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if sprite.frame.contains(location) {
touchPoint = location
touching = true
}
}
}
override func touchesMoved(touches: Set, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
touchPoint = location
}
}
override func touchesEnded(touches: Set, withEvent event: UIEvent) {
touching = false
}
override func touchesCancelled(touches: Set!, withEvent event: UIEvent!) {
touching = false
}
override func update(currentTime: NSTimeInterval) {
if touching {
let dt:CGFloat = 1.0/60.0
let distance = CGVector(dx: touchPoint.x-sprite.position.x, dy: touchPoint.y-sprite.position.y)
let velocity = CGVector(dx: distance.dx/dt, dy: distance.dy/dt)
sprite.physicsBody!.velocity = velocity
}
}
}
SpriteKit SKSpriteNode velocity on touch and hold
thanks to Code Monkey I was able to come up with solution, it's easier than I thought:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
isTouching = true
}
override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!) {
/* Called when a touch begins */
isTouching = false
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
if isTouching {
flyingObject.physicsBody.applyImpulse(CGVectorMake(0, 5))
}
}
Shooting bullets from SKSpriteNode
Shooting multiple bullets in the same direction is fairly straightforward. The key is to determine the bullets' initial positions and direction vectors when the character is rotated.
You can calculate a bullet's initial position within the scene by
let point = node.convertPoint(weapon.position, toNode: self)
where node
is the character, weapon.position
is non-rotated position of a gun, and self
is the scene.
Typically, a bullet moves to the right, CGVector(dx:1, dy:0)
, or up, CGVector (dx:0, dy:1)
, when the character is not rotated. You can calculate the direction of the impulse to apply to the bullet's physics body by rotating the vector by the character's zRotation
with
func rotate(vector:CGVector, angle:CGFloat) -> CGVector {
let rotatedX = vector.dx * cos(angle) - vector.dy * sin(angle)
let rotatedY = vector.dx * sin(angle) + vector.dy * cos(angle)
return CGVector(dx: rotatedX, dy: rotatedY)
}
Here's an example of how to shoot two bullets from a rotated character:
struct Weapon {
var position:CGPoint
}
class GameScene: SKScene {
let sprite = SKSpriteNode(imageNamed:"Spaceship")
let dualGuns = [Weapon(position: CGPoint(x: -15, y: 15)), Weapon(position: CGPoint(x: 15, y: 15))]
let singleGun = [Weapon(position: CGPoint(x: 0, y: 15))]
let numGuns = 1
// If your character faces up where zRotation == 0, offset by pi
let rotationOffset = CGFloat(M_PI_2)
override func didMoveToView(view: SKView) {
scaleMode = .ResizeFill
sprite.position = view.center
sprite.size = CGSizeMake(25, 25)
self.addChild(sprite)
}
override func touchesBegan(touches: Set, withEvent event: UIEvent?) {
if let _ = touches.first {
let action = SKAction.rotateByAngle(CGFloat(M_PI_4/2), duration:1)
sprite.runAction(action) {
[weak self] in
if let scene = self {
switch (scene.numGuns) {
case 1:
for weapon in scene.singleGun {
scene.shoot(weapon: weapon, from: scene.sprite)
}
case 2:
for weapon in scene.dualGuns {
scene.shoot(weapon: weapon, from: scene.sprite)
}
default:
break
}
}
}
}
}
func shoot(weapon weapon:Weapon, from node:SKNode) {
// Convert the position from the character's coordinate system to scene coodinates
let converted = node.convertPoint(weapon.position, toNode: self)
// Determine the direction of the bullet based on the character's rotation
let vector = rotate(CGVector(dx: 0.25, dy: 0), angle:node.zRotation+rotationOffset)
// Create a bullet with a physics body
let bullet = SKSpriteNode(color: SKColor.blueColor(), size: CGSizeMake(4,4))
bullet.physicsBody = SKPhysicsBody(circleOfRadius: 2)
bullet.physicsBody?.affectedByGravity = false
bullet.position = CGPointMake(converted.x, converted.y)
addChild(bullet)
bullet.physicsBody?.applyImpulse(vector)
}
// Rotates a point (or vector) about the z-axis
func rotate(vector:CGVector, angle:CGFloat) -> CGVector {
let rotatedX = vector.dx * cos(angle) - vector.dy * sin(angle)
let rotatedY = vector.dx * sin(angle) + vector.dy * cos(angle)
return CGVector(dx: rotatedX, dy: rotatedY)
}
}
Dragging an SKSpriteNode only when touched
You can implement these three methods to achieve what you want: touchesBegan
, touchesMoved
and touchesEnded
. When you touched muncherMan
, set muncherManTouched
true which indicated muncherMan
would probably be moved in the next frame. When finishing the move or touch, set muncherManTouched
false and wait for the next touch.
Here's the example code. I add a name for muncherMan
so we can determine which node is being touched in touchesBegan
.
var muncherManTouched = false
override func didMoveToView(view: SKView) {
/* Setup your scene here */
...
muncherMan.name = "muncherMan"
self.addChild(muncherMan)
}
override func touchesBegan(touches: Set, withEvent event: UIEvent?) {
/* Called when a touch begins */
for touch in touches {
let location = touch.locationInNode(self)
let nodeTouched = self.nodeAtPoint(location)
if (nodeTouched.name == "muncherMan") {
muncherManTouched = true
}
}
}
override func touchesMoved(touches: Set, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
if (muncherManTouched == true) {
muncherMan.position = location
}
}
}
override func touchesEnded(touches: Set, withEvent event: UIEvent?) {
muncherManTouched = false
}
Related Topics
Xcode 4: Create Ipa File Instead of .Xcarchive
Manual Language Selection in an Ios-App (Iphone and Ipad)
Looping a Video With Avfoundation Avplayer
Using Ssl in an Iphone App - Export Compliance
How to Detect the End of Loading of Uitableview
Get a List of All Contacts on Ios
Making Text Bold Using Attributed String in Swift
Getting iOS System Uptime, That Doesn't Pause When Asleep
How to Test Apple Push Notification Service Without an Iphone
Can Afnetworking Return Data Synchronously (Inside a Block)
Detecting Sheet Was Dismissed on iOS 13
Swiftui - How to Pass Environmentobject into View Model
How to Ignore Touch Events and Pass Them to Another Subview'S Uicontrol Objects
What's the Best Way to Detect When the App Is Entering the Background For My View