SpriteKit - Touch moved, how to prevent node jump to touch location - sprite-kit

I wrote code for touch moved like this:
"override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {return}
let location = touch.location(in: self)
player.position = location"
The problem is the node/player will jump to wherever I touch and moved in the scene.
How may I fix this? Thank you.

I am going to answer this in pseudo code for two reasons, I am here to help you not do it for you and as a beginner figuring out how to do this yourself could be greatly beneficial.
var ball: SKSpriteNode!
var minWidth: CGFloat = 0
var maxWidth: CGFloat = 0
var startPosX: CGFloat = 0
var startBallPosX: CGFloat = 0
func didMove() {
//did move func of the scene
//setup ball and min and max widths
ball = SKSpriteNode()
addChild(ball)
minWidth = scene.frame.minX
maxWidth = scene.frame.maxY
}
func touchesBegan() {
let location = touch.location(in: self)
startPosX = location.x
startBallPosX = ball.position.x
}
func touchesMoved() {
let location = touch.location(in: self)
var moveXValue = location.x - startPosX
if startBallPosX + moveXValue > maxWidth {
//if ball wants to go past right edge of screen keep it at the edge
ball.position.x = maxWidth
}
else if startBallPosX + moveXValue < minWidth {
//if ball wants to go past left edge of screen keep it at the edge
ball.position.x = minWidth
}
else {
ball.position.x = startBallPosX + moveXValue
}
}

Related

how to define the area of a node to make it a button

AS always, I am not familiar with swift and SpriteKit, so apologies.
I am creating a button that changes scene. I am using a simple code that detects if the touch is in the SKNode area, if yes it changes the scene.
My problem is that the node.position is defined by a CGPoint, not an area, so when you touch the screen you never actually touch the node.
Any suggestion?
I don't know how to solve the problem.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let touchedNode = atPoint(location)
if touchedNode.name == "B" {
let menuScene = MenuScene(size: view!.bounds.size)
view!.presentScene(menuScene)
}
}
}
SOLVED! I created the node "B" with text initialiser and not with name initialiser, that is why I could not find it.
let b = SKLabelNode(name: "B")
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let nodeB = self.childNode(withName: "B")
let frameB = nodeB!.calculateAccumulatedFrame()
let minX = frameB.minX
let maxX = frameB.maxX
let minY = frameB.minY
let maxY = frameB.maxY
if location.x > minX && location.x < maxX {
if location.y > minY && location.y < maxY {
let gameScene = GameScene(size: view!.bounds.size)
view!.presentScene(gameScene)
}
}
}
}

How do I rotate a colour sprite by moving finger along y-axis in the BOTTOM HALF of the screen?

Code:
for touch in touches{
let location = touch.location(in: self)
let maxTouchHeight = self.frame.midY
if currentGameType == .wobble{
main.zRotation = (location.y)/512
if location.y > maxTouchHeight { return }
}
}
}
So I am trying to rotate a paddle depending on where your finger is on the y-axis in a type of pong game. However, I have been unable to make it so that it only works in the bottom half of the screen i.e. the two extremities of rotation are at the very top and very bottom of the screen but I want the top extremity to be at the top of the bottom half of the screen. (main is a pong paddle).
Any help is appreciated :)
After followed discussion from comments and repeated answer updates, I have a solution for you. Please let me know if questions:
class GameScene: SKScene {
let paddle = SKSpriteNode()
let maximumRotation = CGFloat(45)
override func didMove(to view: SKView) {
paddle.color = .blue
paddle.size = CGSize(width: 200, height: 15)
let deg45 = CGFloat(0.785398)
let degNeg45 = -deg45
let constraint = [SKConstraint.zRotation(SKRange(lowerLimit: degNeg45, upperLimit: deg45))]
paddle.constraints = constraint
addChild(paddle)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let location = touches.first?.location(in: self) else { return }
guard let lastLocation = touches.first?.previousLocation(in: self) else { return }
guard location.y < self.frame.midY else { return }
// Our vertical plots:
let yVal = location.y
let lastY = lastLocation.y
// How much we moved in a certain direction this frame:
let deltaY = yVal - lastY
// This value represents 100% of a 45deg angle:
let oneHundredPercent = self.frame.height/2
assert(oneHundredPercent != 0)
// The % of 100%Val (45degrees) that we moved (in radians):
let absY = abs(deltaY)
let radToDegFactor = CGFloat(0.01745329252)
let multiplier = (absY / oneHundredPercent) * radToDegFactor
// I suggest a sensitivity of 2-4:
let sensitivity = CGFloat(3)
let amountToRotate = maximumRotation * (multiplier * sensitivity)
// Rotate the correct amount in the correct direction:
if deltaY > 0 {
// Rotate counter-clockwise:
paddle.run(.rotate(byAngle: amountToRotate, duration: 0))
} else {
// Rotate clockwise:
paddle.run(.rotate(byAngle: -amountToRotate, duration: 0))
}
}
}
Just open up a new project and try it out!
There may be an easier way to do this, but this is what popped in my head. It's a little verbose too so hopefully you can see each process step-by-step.
As far as doing something using the bottom half only, you can do something like this
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let fingerPosition = touch.location(in: self)
//this is the bottom
if fingerPosition.y < self.size.height/2 {
//just replace print with your own code
print("bottom half")
}
//this is the top half
if fingerPosition.y > self.size.width/2 {
//you can just leave this as it is or if ever you want to do something with the top part, just add your own code!
print("not bottom half")
}
}
}
Hopefully this helps.
put this inside of your touches functions:
for touch in touches{
let location = touch.location(in: self)
let maxTouchHeight = self.frame.midY
if location.y > maxTouchHeight { return }
if currentGameType == .wobble{
main.zRotation = (location.y)/512
}
}

Continous minimum velocity tracking of a finger on a sprite in spritekit/ios

What is a good way to continuously track the velocity of a user moving their finger up and down on a sprite without lifting the finger? And if they don't go fast enough(set a minimum) to perform an action.
This is some code you can use to drag a sprite across the screen at different speeds depending on your finger acceleration. You can add your own code to detect the magnitude of change and perform whichever action you want..
class GameScene: SKScene {
// time values
var delta:NSTimeInterval = NSTimeInterval(0)
var last_update_time:NSTimeInterval = NSTimeInterval(0)
// our sprite
let sprite = SKSpriteNode(color: SKColor.redColor(), size: CGSize(width: 20, height: 30))
// tracking difference between touches
var currentTouchY: CGFloat = CGFloat(0)
var lastTouchY: CGFloat = CGFloat(0)
// the magnitude of change between current and last touch
var forceMag: CGFloat = CGFloat(0)
// max mag so our sprite doesnt travel at crazy speed
let forceMagMax: CGFloat = CGFloat(7)
// direction of our sprites travel
var forceDir: CGFloat = CGFloat(0)
// arbitrary speed we want sprite to move
let velocity: CGFloat = CGFloat(70)
// we use this to stop moving our sprite when we let go
var intervalsWithoutChange = 0
override func didMoveToView(view: SKView) {
sprite.position.x = 100
sprite.position.y = size.width/2
self.addChild(sprite)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let yCoord = location.y
lastTouchY = yCoord
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let yCoord = location.y
intervalsWithoutChange = 0
currentTouchY = yCoord
let diff = currentTouchY - lastTouchY
forceMag = abs(diff)
forceMag = forceMag > forceMagMax ? forceMagMax : forceMag
forceDir = diff < 0 ? -1 : 1
lastTouchY = yCoord
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
intervalsWithoutChange = 1
currentTouchY = 0
lastTouchY = 0
}
override func update(currentTime: NSTimeInterval) {
if last_update_time == 0.0 {
delta = 0
} else {
delta = currentTime - last_update_time
}
last_update_time = currentTime
if intervalsWithoutChange > 0 {
forceMag -= CGFloat(15) * CGFloat(delta)
}
if forceMag < 0.01 {
forceMag = 0
}
let change = forceDir * pow(forceMag, 1.29)
sprite.position.y += change * CGFloat(delta) * velocity
}
}

Rotate SkSpriteNode around internal point that is not .anchorPoint

So I'm using this code to rotate an object either clockwise or anti-clockwise.
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first as UITouch!
let touchPosition = touch.locationInNode(self)
let newRotationDirection : rotationDirection = touchPosition.x < CGRectGetMidX(self.frame) ? .clockwise : .counterClockwise
if currentRotationDirection != newRotationDirection && currentRotationDirection != .none {
reverseRotation()
currentRotationDirection = newRotationDirection
}
else if (currentRotationDirection == .none) {
setupRotationWith(direction: newRotationDirection)
currentRotationDirection = newRotationDirection
}
}
func reverseRotation(){
let oldRotateAction = ship.actionForKey("rotate")
let newRotateAction = SKAction.reversedAction(oldRotateAction!)
ship.runAction(newRotateAction(), withKey: "rotate")
}
func stopRotation(){
ship.removeActionForKey("rotate")
}
func setupRotationWith(direction direction: rotationDirection){
let angle : CGFloat = (direction == .clockwise) ? CGFloat(M_PI) : -CGFloat(M_PI)
let rotate = SKAction.rotateByAngle(angle, duration: 2)
let repeatAction = SKAction.repeatActionForever(rotate)
ship.runAction(repeatAction, withKey: "rotate")
}
My question is, this creates clockwise or anticlockwise rotation around the SKSpriteNode's anchor point.
However I would like the object to rotate about another point inside of the SKSprite (something like 4/5's of the way up from the base of the sprite image). I assume I cannot use a set point defined through CGPointMake as I would like the sprite to independently move around the screen and this would not work?
Any suggestions anyone?
Have you tried setting the anchorPoint property of your sprite to the point around which you want the sprite to rotate?
ship.anchorPoint = CGPoint(x: 0, y: 4/5)
Is this what you were looking for?

Drag Rotate a Node around a fixed point

I'm trying to create a spinable node similar to the "prize wheel" in this question. So far I have the flinging capability, adding angular impulses on a physics body using a UIPanGestureRecognizer that works really well. I can also stop the spinning using a touch.
Now I'm trying to allow fine adjustment of the wheel using a drag or swipe gesture so if the player isn't happy with what they end up with they can manually spin/drag/rotate it to their favoured rotation.
Currently I save the location of the touch in the touchesBegan and try to increment the zRotation of my node in the update loop.
The rotation doesn't follow my finger and is jerky. I'm not sure if I'm getting an accurate enough read on the finger movement or if the change position of the finger isn't being accurately translated into radians. I suspect detecting the touch and then dealing with it in the update isn't a great solution.
Here's my code.
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent?) {
if let touch = touches.first as? UITouch {
var location = touch.locationInView(self.view)
location = self.convertPointFromView(location)
mostRecentTouchLocation = location
let node = nodeAtPoint(location)
if node.name == Optional("left") && node.physicsBody?.angularVelocity != 0
{
node.physicsBody = SKPhysicsBody(circleOfRadius:150)
node.physicsBody?.applyAngularImpulse(0)
node.physicsBody?.pinned = true
}
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
if mostRecentTouchLocation != CGPointZero{
let node = nodeAtPoint(mostRecentTouchLocation)
if node.name == Optional("left")
{
var positionInScene:CGPoint = mostRecentTouchLocation
let deltaX:Float = Float(positionInScene.x) - Float(node.position.x)
let deltaY:Float = Float(positionInScene.y) - Float(node.position.y)
let angle:CGFloat = CGFloat(atan2f(deltaY, deltaX))
let maths:CGFloat = angle - (CGFloat(90) * (CGFloat(M_PI) / 180.0))
node.zRotation += maths
mostRecentTouchLocation = CGPointZero
}
}
}
I've spread some of the maths across multiple lines in the update to make debugging a bit easier.
I can add the PanGestureRecognizer code if needed but I'll try to keep it short for now.
EDIT
Here is my latest code based on GilderMan's recommendation. I think it's working better but the rotation is far from smooth. It's jumping in large increments and not following the finger well. Does this mean there's something wrong with my angle calculation?
override func didSimulatePhysics() {
if mostRecentTouchLocation != CGPointZero {
let node = nodeAtPoint(mostRecentTouchLocation)
if node.name == Optional("left")
{
var positionInScene:CGPoint = mostRecentTouchLocation
let deltaX:Float = Float(positionInScene.x) - Float(node.position.x)
let deltaY:Float = Float(positionInScene.y) - Float(node.position.y)
let angle:CGFloat = CGFloat(atan2f(deltaY, deltaX))
node.zRotation += angle
println(angle)
mostRecentTouchLocation = CGPointZero
}
}
}
The following code simulates a prize wheel that spins based on touch. As the user's finger moves, the wheel rotates proportionately to the speed of the finger. When the user swipes on the wheel, the wheel will spin proportionately to the velocity of the swipe. You can change the angularDamping property of the physics body to slow or increase the rate at which the wheel comes to a stop.
class GameScene: SKScene {
var startingAngle:CGFloat?
var startingTime:TimeInterval?
override func didMove(to view: SKView) {
let wheel = SKSpriteNode(imageNamed: "Spaceship")
wheel.name = "wheel"
wheel.setScale(0.5)
wheel.physicsBody = SKPhysicsBody(circleOfRadius: wheel.size.width/2)
// Change this property as needed (increase it to slow faster)
wheel.physicsBody!.angularDamping = 0.25
wheel.physicsBody?.pinned = true
wheel.physicsBody?.affectedByGravity = false
addChild(wheel)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in:self)
let node = atPoint(location)
if node.name == "wheel" {
let dx = location.x - node.position.x
let dy = location.y - node.position.y
// Store angle and current time
startingAngle = atan2(dy, dx)
startingTime = touch.timestamp
node.physicsBody?.angularVelocity = 0
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let location = touch.location(in:self)
let node = atPoint(location)
if node.name == "wheel" {
let dx = location.x - node.position.x
let dy = location.y - node.position.y
let angle = atan2(dy, dx)
// Calculate angular velocity; handle wrap at pi/-pi
var deltaAngle = angle - startingAngle!
if abs(deltaAngle) > CGFloat.pi {
if (deltaAngle > 0) {
deltaAngle = deltaAngle - CGFloat.pi * 2
}
else {
deltaAngle = deltaAngle + CGFloat.pi * 2
}
}
let dt = CGFloat(touch.timestamp - startingTime!)
let velocity = deltaAngle / dt
node.physicsBody?.angularVelocity = velocity
// Update angle and time
startingAngle = angle
startingTime = touch.timestamp
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
startingAngle = nil
startingTime = nil
}
}
The zRotation of a SKNode is in radians. You can remove your conversion to degrees.
You may wish to do the angle adjustment in the didSimulatePhysics in order to compute the zRotation after physics have been applied. (This may not apply directly to this situation, but is good practice further down the line.)
I tried two set of coodes below.
Code Sample 1
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let node = atPoint(location)
guard let safeNode = node.parent else { break }
if safeNode.name == "wheel" {
let wheel = node.parent!
let dx = location.x
let dy = location.y
startingAngle = atan2(dx, dy)
startingTime = touch.timestamp
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let node = atPoint(location)
guard let safeNode = node.parent else { break }
if safeNode.name == "wheel" {
let wheel = node.parent!
if !isRotating {
let dx = location.x
let dy = location.y
let angle = atan2(dx, dy)
wheel.zRotation = -angle
//update angle and time
startingAngle = angle
startingTime = touch.timestamp
}
}
}
}
Code Sample 2
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let node = atPoint(location)
guard let safeNode = node.parent else { break }
if safeNode.name == "wheel" {
let wheel = node.parent!
let dx = location.x
let dy = location.y
startingAngle = atan2(dx, dy)
startingTime = touch.timestamp
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let node = atPoint(location)
guard let safeNode = node.parent else { break }
if safeNode.name == "wheel" {
let wheel = node.parent!
if !isRotating {
let dx = location.x
let dy = location.y
let angle = atan2(dx, dy)
let deltaAngle = angle - startingAngle!
wheel.zRotation -= deltaAngle
//update angle and time
startingAngle = angle
startingTime = touch.timestamp
}
}
}
}
The only difference between Code Sample 1 & 2 is the method of finding zRotation of the wheel.
Code Sample 1 uses wheel.zRotation = -angle. It ensures wheel follows the finger position exactly. But there is a bug that the wheel will jump position when you touch two distant locations in wheel then move your fingure. I am still unable to fix this bug.
Code Sample 2 uses wheel.zRotation -= deltaAngle to stack the change in angle of each finger movement. It supposes to generate the same result of code sample 1 but it does not exactly follow the finger movement.
I found out the reason by comparing the zPosition of Code Sample 1 and 2 with same finger movement. The deltaAngle in Code Sample 2 is slightly less (up to 8 decimal places)so the wheel does not exactly follow the fingure movement.