How to Throw Skspritenode

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
}
}
}

Sample Image

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:

http://www.cs.ucsb.edu/~pconrad/images/math/unitCircleWithDegrees.png

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



Leave a reply



Submit