Dragging and flicking/throwing skspritenode [duplicate] - ios

This question already has answers here:
how to throw SKSpriteNode?
(3 answers)
Closed 7 years ago.
I have ball sprite that i want to be able to flick towards other sprites. Right now, I have the function TouchesMoved below, but it moves my sprite towards any touch movement on the screen, when I want my user to first touch the sprite and drag their finger towards the target, causing the sprite to move. This is the dysfunctional function.. Any help would be appreciated!
EDIT: I wanted the ball velocity to remain constant no matter the flick length.. not addressed by other answers.
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
if firstTimerStarted == false {
let touch = touches.first as! UITouch
let touchLocation = touch.locationInNode(self)
sceneTouched(touchLocation)
}

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<NSObject>, 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<NSObject>, withEvent event: UIEvent) {
let touch = touches.first as! UITouch
let location = touch.locationInNode(self)
touchPoint = location
}
override func touchesEnded(touches: Set<NSObject>, 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.

Related

Stretch SKSpriteNode between two points/touches

I have an SKSpriteNode in which I've set the centerRect property so that the node can be stretched to appear like a styled line. My intention is for the user to touch the screen, and draw/drag a straight line with the node. The line would pivot around an anchor point to remain straight.
In touchesBegan:, the node is added:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let positionInScene = touch.location(in: self)
if let _ = fgNode.childNode(withName: "laser") {
print("already there")
} else {
laser.centerRect = CGRect(x: 0.42857143, y: 0.57142857, width: 0.14285714, height: 0.14285714)
laser.anchorPoint = CGPoint(x: 0, y: 0.5)
laser.position = positionInScene
fgNode.addChild(laser)
}
}
And adjusted in touchesMoved::
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let positionInScene = touch.location(in: self)
stretchLaserTo(positionInScene)
}
The node is stretched and rotated with two functions:
func stretchLaserTo(_ point: CGPoint) {
let offset = point - laser.anchorPoint
let length = offset.length()
let direction = offset / CGFloat(length)
laser.xScale = length
rotate(sprite: laser, direction: direction)
}
func rotate(sprite: SKSpriteNode, direction: CGPoint) {
sprite.zRotation = atan2(direction.y, direction.x)
}
I think I'm somewhat on the right track. The line rotates with my touch and expands, however, it's extremely sensitive and doesn't stay with my touch. Maybe I'm going about it wrong. Is there a standard technique for doing something like this?
An example of this working can be seen here: https://imgur.com/A83L45i
I suggest you set anchor point of the sprite to (0, 0), set the sprite's scale to the distance between the sprite's position and the current touch location, and then rotate the sprite.
First, create a sprite and set its anchor point.
let laser = SKSpriteNode(color: .white, size: CGSize(width: 1, height: 1))
override func didMove(to view: SKView) {
laser.anchorPoint = CGPoint(x: 0, y: 0)
addChild(laser)
}
In touchesBegan, set the position of the sprite to the location of the touch. In this case, it's also the start of the line.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let positionInScene = touch.location(in: self)
laser.position = positionInScene
laser.setScale(1)
}
Update the sprite so that it forms a line that starts at the position of the sprite and ends at the current touch location.
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let positionInScene = touch.location(in: self)
stretchLaserTo(positionInScene)
}
Stretch the sprite by setting its xScale to the distance from the start of the line to the location of the current touch and then rotate the sprite.
func stretchLaserTo(_ point: CGPoint) {
let dx = point.x - laser.position.x
let dy = point.y - laser.position.y
let length = sqrt(dx*dx + dy*dy)
let angle = atan2(dy, dx)
laser.xScale = length
laser.zRotation = angle
}

Move the object in a specific area

I have a circle called circle and a line called ground in the View.
I can drag & move the circle in the whole view, but when I try to limit it (in the code) to move only under the ground area - it works (when I try to drag it above the ground) and the circle moves only by my x location dragging, BUT when I try to drag it back under the ground - the circle only moves by the x location dragging and the y location dragging doesn't change (the y is the ground y).
How can I make it move back again, in a free way, under the ground and not only by the x location of the dragging?
Here is what I tried so far:
let ground = SKSpriteNode()
var circle = SKShapeNode(circleOfRadius: 40)
override func didMoveToView(view: SKView) {
/* Setup your scene here */
// ground.physicsBody?.dynamic = false
ground.color = SKColor.redColor()
ground.size = CGSizeMake(self.frame.size.width, 5)
ground.position = CGPoint(x: CGRectGetMidX(self.frame), y: self.frame.size.height / 5)
self.addChild(ground)
circle.position = CGPointMake(CGRectGetMidX(self.frame) ,CGRectGetMinY(ground.frame) - (circle.frame.size.height / 2))
circle.strokeColor = SKColor.blackColor()
circle.glowWidth = 1.0
circle.fillColor = SKColor.orangeColor()
self.addChild(circle)
}
var lastTouch: CGPoint? = nil
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
let touch = touches.first
let touchLocation = touch!.locationInNode(self)
if circle.position.y < ground.position.y - (circle.frame.size.height / 2){
circle.position = touchLocation
} else {
circle.position.x = CGFloat(touchLocation.x)
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.locationInNode(self)
if circle.position.y < ground.position.y - (circle.frame.size.height / 2) {
circle.position = touchLocation
} else {
circle.position.x = CGFloat(touchLocation.x)
}
}
I have solved it:
In touchesMoved func I wrote this code:
for touch in touches {
if touchLocation.y < ground.position.y {
circle.position = touchLocation
} else {
circle.position.x = touchLocation.x
}
}

Node rotation doesn't follow a finger

I'm trying to rotate an arrow to follow a finger movement but it performs weirdly. It is definitely not following it. I'm trying to do it in touchesMoved. I tried to do this:
var fingerLocation = CGPoint()
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
fingerLocation = touch.locationInNode(self)
let currentOrient = arrow.position
let angle = atan2(currentOrient.y - fingerLocation.y, currentOrient.x - fingerLocation.y)
let rotateAction = SKAction.rotateToAngle(angle + CGFloat(M_PI*0.5), duration: 0.0)
arrow.runAction(rotateAction)
}
}
And also tried this:
var fingerLocation = CGPoint()
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
fingerLocation = touch.locationInNode(self)
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
var radians = atan2(fingerLocation.x, fingerLocation.y)
arrow.zRotation = -radians
}
I also tried SKConstraint.orientToPoint but had no luck in it either. What am I doing wrong? Every answer to similar question is suggestion atan2, but it doesn't seem to work for me.
If you want to rotate the sprite towards to touch location, it should be simple as :
let touchLocation = touch.locationInNode(self)
var dx = hero.position.x - positionInScene.x;
var dy = hero.position.y - positionInScene.y ;
var angle = atan2(dy,dx) + CGFloat(M_PI_2)
hero.zRotation = angle
It worked when I tried, so it can give you an basic idea where to start. Or I misunderstood what you are trying to achieve...
EDIT:
Currently what you will get if you try to convert angle to degrees is angle in range from -90 to 270 degrees. Its described here why. If you want to work with angle in range of 0 to 360, you can change to code above to:
var dx = missile.position.x - positionInScene.x ;
var dy = missile.position.y - positionInScene.y;
var angleInRadians = atan2(dy,dx) + CGFloat(M_PI_2)
if(angleInRadians < 0){
angleInRadians = angleInRadians + 2 * CGFloat(M_PI)
}
missile.zRotation = angleInRadians
var degrees = angleInRadians < 0 ? angleInRadians * 57.29577951 + 360 : angleInRadians * 57.29577951
Here is the result with debugging data:

SpriteKit - Mouse Snaps to Center On Sprite Drag

I'm trying to create an iOS game and I have it so that when the item is touched and moved it is dragged. When it is dragged the mouse is snapped to the center of the sprite. How could I make it so that this wouldn't happen?
Here is an example of what is happening.
Here are the functions dealing with touch input
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
var papers = 0
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
let touchedNode = nodeAtPoint(location)
if ((touchedNode.name?.contains("paper")) != nil) {
touchedNode.position = location
touchedNode.position.x = CGRectGetMidX(self.frame)
}
}
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let touchedNode = nodeAtPoint(location)
if ((touchedNode.name?.contains("paper")) != nil) {
touchedNode.position = location
touchedNode.position.x = CGRectGetMidX(self.frame)
}
}
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let touchedNode = nodeAtPoint(location)
if ((touchedNode.name?.contains("paper")) != nil) {
touchedNode.zPosition = 0
touchedNode.position.x = CGRectGetMidX(self.frame)
}
}
}
P.S. contains is an extension off of the String class to check if a substring is in a string
Thanks in advance!
It's fairly straightforward to drag a sprite from the touch location instead of from the center of the sprite. To do this, calculate and store the difference (i.e., offset) between the touch location and the center of the sprite. Then, in touchesMoved, set the sprite's new position to the touch location plus the offset.
You can optionally overload the + and - operators to simplify adding and subtracting CGPoints. Define this outside of the GameScene class:
func - (left:CGPoint,right:CGPoint) -> CGPoint {
return CGPoint(x: right.x-left.x, y: right.y-left.y)
}
func + (left:CGPoint,right:CGPoint) -> CGPoint {
return CGPoint(x: right.x+left.x, y: right.y+left.y)
}
In GameScene, define the following instance variable
var offset:CGPoint?
and then in touchesBegan, replace
touchedNode.position = location
with
offset = location - touchedNode.position
and in touchesMoved, replace
touchedNode.position = location
with
if let offset = self.offset {
touchedNode.position = location + offset
}
I generalized the solution to offset the sprite's position in both the x and y dimensions. In your app, you can simply offset the sprite's y position since x is ignored.
You don't need touchesBegan and touchesEnded for this. You can use just touchesMoved:
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let touchedNode = nodeAtPoint(location)
let previousPosition = touch.previousLocationInNode(self)
if (touchedNode.name == "paper") {
var translation:CGPoint = CGPoint(x: location.x - previousPosition.x , y: location.y - previousPosition.y )
touchedNode.position = CGPoint(x: touchedNode.position.x , y: touchedNode.position.y + translation.y)
}
}
The idea is to calculate translation. You can read more about this solution here. For future readers Obj-C solution on StackOverflow can be found on this link.

nodeAtPoint wrong node

I'm making a game with swift and spritekit and I have a function where I have multiple sprites falling from the top of the screen. I also made it so I can be able to detect a touch on the sprite using nodeAtPoint, and made it possible to flick the sprites. My problem is that due to nodeAtPoint dragging the deepest node in the tree, when i click and drag on a sprite, the newest sprite in the scene gets pulled toward my touch instead of the one i originally touched. If anyone has some suggestions on how to only affect the node I touched I'd really appreciate it.
class GameScene: SKScene {
var ball = SKSpriteNode(imagedNamed: "blueBlue")
var touchedNode: SKNode? = SKNode()
override func didMoveToView(view: SKView) {
var create = SKAction.runBlock({() in self.createTargets()})
var wait = SKAction.waitForDuration(2)
var waitAndCreateForever = SKAction.repeatActionForever(SKAction.sequence([create, wait]))
self.runAction(waitAndCreateForever)
}
func createTargets() {
ball = SKSpriteNode(imageNamed: "blueBlue")
let randomx = Int(arc4random_uniform(170) + 290)
ball.zPosition = 0
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.width / 11)
ball.physicsBody?.dynamic = true
ball.size = CGSize(width: ball.size.width / 1.5, height: ball.size.height / 1.5)
let random = Int(arc4random_uniform(35))
let textures = [texture1, texture2, texture3, texture4, texture5, texture6, texture7, texture8, texture9, texture10, texture11, texture12, texture13, texture14, texture15, texture16, texture17, texture18, texture19, texture20, texture21, texture22, texture23, texture24, texture25, texture1, texture7, texture18, texture24, texture25, texture1, texture7, texture18, texture24, texture25]
ball.texture = textures[random]
ball.position = CGPoint(x: randomx, y: 1400)
addChild(ball)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
let touch = touches.first as! UITouch
let touchLocation = touch.locationInNode(self)
touchedNode = self.nodeAtPoint(touchLocation)
if touchedNode.frame.contains(touchLocation) {
touching = true
touchPoint = touchLocation
}
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
let touch = touches.first as! UITouch
let touchLocation = touch.locationInNode(self)
touching = true
touchPoint = touchLocation
}
override func update(currentTime: NSTimeInterval) {
if touching {
if touchPoint != touchedNode.position {
let dt: CGFloat = 0.1
let distance = CGVector(dx: touchPoint.x - touchedNode.position.x, dy: touchPoint.y - touchedNode.position.y)
let vel = CGVector(dx: distance.dx / dt, dy: distance.dy / dt)
if touchedNode.parent != nil {
touchedNode.physicsBody?.velocity = vel
}
}
}
}
}
I suggest you make the following changes to the code:
Add a check to see if a sprite is already being moved (if yes, do nothing)
Turn off the affectedByGravity property for the sprite being moved
Reset touchedNode and touching variables when the touch ends
Here's an implementation with the above changes...
Replace touchesBegan and touchesMoved with the following
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
let touch = touches.first as! UITouch
let touchLocation = touch.locationInNode(self)
if (!touching) {
let node = self.nodeAtPoint(touchLocation)
if node is SKSpriteNode {
touchedNode = node
touching = true
touchPoint = touchLocation
touchedNode!.physicsBody?.affectedByGravity = false
}
}
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
if (touching) {
let touch = touches.first as! UITouch
let touchLocation = touch.locationInNode(self)
touchPoint = touchLocation
}
}
And add this method
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
if (touchedNode != nil) {
touchedNode!.physicsBody!.affectedByGravity = true
}
touchedNode = nil
touching = false
}

Resources