Joystick, clamping the control stick inside the joystick base - ios

I have a circular joystick i created in SpriteKit, how do i clamp the smaller control stick inside of the larger joystick base and prevent it from going outside the joystick base programmatically.
The code setting up my joystick inside of my didMove(toView:) method.
var DPad = SKSpriteNode(imageNamed: "dpad")
DPad.size = CGSize(width: 150, height: 150)
DPad.position = CGPoint(x: -270, y: -100)
var thumbNode = SKSpriteNode(imageNamed: "joystick")
thumbNode.size = CGSize(width: 50, height: 50)
thumbNode.position = CGPoint(x: DPad.position.x, y: DPad.position.y)
touchesBegan methods
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if thumbNode.contains(location) && isTracking == false {
isTracking = true
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if isTracking == true {
thumbNode.run(SKAction.move(to: location, duration: 0.01))
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
thumbNode.run(SKAction.move(to: DPad.position, duration: 0.01))
if thumbNode.position.x > DPad.size.width || thumbNode.position.y > DPad.size.height {
thumbNode.position = DPad.position
}
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if thumbNode.physicsBody?.velocity.dx > Float(0) || thumbNode.physicsBody?.velocity.dx < Float(0) {
print("greater")
}
}

For this, you can use SKConstraint.distance(SKRange(upperLimit: radius), to: baseNode)
To add it, just do joystickNode.constraints = [SKConstraint.distance(SKRange(upperLimit: radius), to: baseNode)]

Related

applyImpulse not working when touching node

I have a circle on the screen that will slowly get bigger (incidentally, Balloon class is a type of SKShapeNode). In touchesBegan I click on the circle and it prints that I touched it, however no impulse is applied. How can I find the issue? I'm fairly new to SpriteKit.
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var radius = 40.0
var balloon : Balloon? = nil
override func didMove(to view: SKView) {
balloon = Balloon(radius: radius, position: CGPoint(x: frame.midX, y: frame.minY + 250))
balloon?.name = "balloon"
let physicsBody = SKPhysicsBody(circleOfRadius: CGFloat(radius))
physicsBody.affectedByGravity = false
balloon?.physicsBody = physicsBody
balloon?.physicsBody?.isDynamic = true
let borderBody = SKPhysicsBody(edgeLoopFrom: self.frame)
borderBody.friction = 0
self.physicsBody = borderBody
physicsWorld.gravity = CGVector(dx: 0.0, dy: 0.0)
self.addChild(balloon!)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let node : SKNode = self.atPoint(location)
if node.name == "balloon" {
print("touching balloon.")
balloon?.physicsBody?.applyImpulse(CGVector(dx: 10.0, dy: 10.0), at: location)
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
radius += 0.05
self.balloon?.radius = radius
let physicsBody = SKPhysicsBody(circleOfRadius: CGFloat(radius))
physicsBody.affectedByGravity = false
balloon?.physicsBody = physicsBody
balloon?.physicsBody?.isDynamic = true
}
}
You are creating a new body every update. If you want to inflate your balloon, use scale:
var scale = 1.0
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
scale += 0.05
self.balloon?.setScale(scale)
}
Then your physics body will be able to have the impulse applied to it because you won't be creating a new body each update.
The following line:
let location = touch.location(in: self)
finds the location of the touch in the scene. However, in the function call applyImpulse, it is relative to the node. Instead of
balloon?.physicsBody?.applyImpulse(CGVector(dx: 10.0, dy: 10.0), at: location)
use:
balloon?.physicsBody?.applyImpulse(CGVector(dx: 10.0, dy: 10.0), at: touch.location(in: balloon))

UIButton Frame changed After Move from one Position To Another

I have 1 UIButton in StoryBoard like below screen and I move UIButton from one position to another position by following this Answer.
Edit
Another thing that I want to rotate UIButton and for that I have tried below code and it works fine but after rotating UIButton when I try to move UIButton position from 1 place to another then UIButton frame is changed.
Entire UIButton code.
class DraggableButton: UIButton {
var localTouchPosition : CGPoint?
var lastRotation: CGFloat = 0
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// ROTATION CODE
let rotate = UIRotationGestureRecognizer(target: self, action:#selector(rotatedView(_:)))
self.addGestureRecognizer(rotate)
}
// SCROLLING CONTENT FROM ONE POSITION TO ANOTHER
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
let touch = touches.first
self.localTouchPosition = touch?.preciseLocation(in: self)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
let touch = touches.first
guard let location = touch?.location(in: self.superview), let localTouchPosition = self.localTouchPosition else{
return
}
self.frame.origin = CGPoint(x: location.x - localTouchPosition.x, y: location.y - localTouchPosition.y)
print(self.frame)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
self.localTouchPosition = nil
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesCancelled(touches, with: event)
self.localTouchPosition = nil
}
// ROTATION CODE
#objc func rotatedView(_ sender: UIRotationGestureRecognizer) {
var originalRotation = CGFloat()
if sender.state == .began {
sender.rotation = lastRotation
originalRotation = sender.rotation
} else if sender.state == .changed {
let newRotation = sender.rotation + originalRotation
sender.view?.transform = CGAffineTransform(rotationAngle: newRotation)
} else if sender.state == .ended {
lastRotation = sender.rotation
}
}
}
Edit 1
I uploaded issue video.
Edit 2
If I used UIButton Rotation and Movement Code separately then it works but when I write both code it generate this issue.
I believe the issue is that the rotation is about the center of the view, so when you apply the rotation the frame size increases which throws off the distance relative to the origin of the frame.
I was able to fix this by moving the view relative to its center:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
let touch = touches.first
guard let location = touch?.location(in: self.superview) else { return }
// Store localTouchPosition relative to center
self.localTouchPosition = CGPoint(x: location.x - self.center.x, y: location.y - self.center.y)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
let touch = touches.first
guard let location = touch?.location(in: self.superview), let localTouchPosition = self.localTouchPosition else{
return
}
self.center = CGPoint(x: location.x - localTouchPosition.x, y: location.y - localTouchPosition.y)
print(self.frame)
}

SpriteKit interplay between line and node: line should give a direction and speed to the node

I would like to have the following interplay between line and ball in my game: a line gives direction and speed to the ball. The longer the line, the faster the ball.
What I have now: a ball is following the line and stops at the end of it. But it shouldn't stop here. Of course, I understand that the ball is only following the path I made. But how could I change it?
Here is my code:
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
// Basic for dynamic sizes step01
var width = CGFloat()
var height = CGFloat()
var ball:SKSpriteNode!
var line:SKShapeNode!
var startPoint: CGPoint!
var location = CGPoint()
override func didMove(to view: SKView) {
self.backgroundColor = .purple
//declare dynamic size of the screen
width = self.frame.size.width
height = self.frame.size.height
self.physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
self.physicsWorld.gravity = CGVector.zero
createBall()
}
func createBall(){
ball = SKSpriteNode(imageNamed: "yellowBtn")
ball.position = CGPoint(x:0, y: -(height/2.5))
ball.size = CGSize(width: width/6, height: width/6)
self.addChild(ball)
}
func drawLine(endPoint:CGPoint){
if(line != nil ){ line.removeFromParent() }
startPoint = ball.position
let path = CGMutablePath()
path.move(to: startPoint)
path.addLine(to: endPoint)
line = SKShapeNode()
line.path = path
line.lineWidth = 5
line.strokeColor = UIColor.white
self.addChild(line)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if(line != nil ){ line.removeFromParent()
for touch in (touches ) {
let location = touch.location(in: self)
drawLine(endPoint: location)
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches ) {
let location = touch.location(in: self)
drawLine(endPoint: location)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches ) {
location = touch.location(in: self)
drawLine(endPoint: location)
}
let moveAction = SKAction.move(to: location, duration: 10)
ball.run(moveAction)
}
override func update(_ currentTime: TimeInterval) {
}
}
I have found at least a part of answer to my question: I need to calculate CGVector manually (lastPoint - firstPoint) and then apply impulse to the ball:
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches ) {
location = touch.location(in: self)
drawLine(endPoint: location)
}
let dx = location.x - startPoint.x
let dy = location.y - startPoint.y
let movement = CGVector(dx: dx, dy: dy)
ball.physicsBody = SKPhysicsBody(circleOfRadius: 50)
ball.physicsBody?.applyImpulse(movement)
}
Now I should find the second part: how to set the speed of the ball accordingly to the length of the line.

How do I limit the dragging area for image views

My first post and I am currently making an app in Xcode 8.1 using Swift 3
I have 9 images that I have made draggable with touchesBegan and touchesMoved functions.
However they are able to be dragged ANYWHERE on the screen and this can cause them to cover up other images I have. I would like to limit their movement by setting a boundary for them so that even when the user tries to drag the images out of that boundary they wont be able to.
I have created this code in draggedimageview.swift this allows the Image views to be dragged.
I have been spending a long time trying to figure out how to do this and if anyone can help I would appreciate it.
Thanks...
import UIKit
class DraggedImageView: UIImageView {
var startLocation: CGPoint?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
startLocation = touches.first?.location(in: self)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let currentLocation = touches.first?.location(in: self)
let dx = currentLocation!.x - startLocation!.x
let dy = currentLocation!.y - startLocation!.y
self.center = CGPoint(x: self.center.x+dx, y: self.center.y+dy)
}
}
You can do this:
let cx = self.center.x+dx
if (cx > 100) {
cx = 100
}
self.center = CGPoint(x: cx, y: self.center.y+dy)
But alter the if based on what you are trying to do. This clamps it so that it cannot be moved to a position where center.x > 100
Try to define your "allowed area" in a rect, such as:
import UIKit
class DraggedImageView: UIImageView {
var startLocation: CGPoint?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
startLocation = touches.first?.location(in: self)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let currentLocation = touches.first?.location(in: self)
let dx = currentLocation!.x - startLocation!.x
let dy = currentLocation!.y - startLocation!.y
// This is the area in which the dragging is allowed
let coolArea = CGRect(x: 0, y: 0, width: 100, height: 100)
let newCenter = CGPoint(x: self.center.x+dx, y: self.center.y+dy)
// If the allowed area contains the new point, we can assign it
if coolArea.contains(newCenter) {
self.center = newCenter
}
// else {
// print("Out of boundaries!")
// }
self.center = CGPoint(x: self.center.x+dx, y: self.center.y+dy)
}
}
You may want to change the code if you want something different to happen when user is dragging out of the bounds.
Taking the "allowed area" from the view that contains the UIImageView
import UIKit
class DraggedImageView: UIImageView {
var startLocation: CGPoint?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
startLocation = touches.first?.location(in: self)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let currentLocation = touches.first?.location(in: self)
let dx = currentLocation!.x - startLocation!.x
let dy = currentLocation!.y - startLocation!.y
let coolArea = (self.superview?.bounds)!
let newCenter = CGPoint(x: self.center.x+dx, y: self.center.y+dy)
// If the allowed area contains the new point, we can assign it
if coolArea.contains(newCenter) {
self.center = newCenter
print("touchesMoved")
}
else {
print("Out of boundaries!")
}
}
}

Throwing an object with SpriteKit

I have the following code at the moment. Even though the code build is successful, i cannot seem to get it to work. I am trying to make it so when you flick the object, it moves at the velocity of your begin and end touch.
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)
}
//for touch: AnyObject in touches {
//let location = touch.locationInNode(self)
//let touchedNode = self.nodeAtPoint(location)
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if sprite.frame.contains(location) {
touchPoint = location
touching = true
}
}
func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
touchPoint = location
}
func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
touching = false
}
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 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<NSObject>, 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<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
touchPoint = location
}
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
touching = false
}
override func touchesCancelled(touches: Set<NSObject>!, 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
}
}
}
ABakerSmith's solutions updated for Swift 4:
import SpriteKit
class GameScene: SKScene {
var sprite: SKSpriteNode!
var touchPoint: CGPoint = CGPoint()
var touching: Bool = false
override func didMove(to view: SKView) {
self.physicsBody = SKPhysicsBody.init(edgeLoopFrom: self.frame)
sprite = SKSpriteNode(color: .red, size: CGSize(width: 50, height: 50))
sprite.physicsBody = SKPhysicsBody.init(rectangleOf: 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<UITouch>, with event: UIEvent?) {
if let touch = touches.first?.location(in: self) {
if sprite.frame.contains(touch) {
touchPoint = touch
touching = true
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
let location = t.location(in: self)
touchPoint = location
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
touching = false
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
touching = false
}
override func update(_ currentTime: TimeInterval) {
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
}
}
}
Solution updated to not have the touch/drag snap to the middle of the sprite. If you're dragging/throwing an object you're going to want that throw to come from where the user touched the object and not snap to the middle of the object. This will make it look a lot smoother
import SpriteKit
class GameScene: SKScene {
var sprite: SKSpriteNode!
var touchPoint: CGPoint = CGPoint()
var touching: Bool = false
var xDif = CGFloat()
var yDif = CGFloat()
override func didMove(to view: SKView) {
self.physicsBody = SKPhysicsBody.init(edgeLoopFrom: self.frame)
sprite = SKSpriteNode(color: .red, size: CGSize(width: 50, height: 50))
sprite.physicsBody = SKPhysicsBody.init(rectangleOf: 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<UITouch>, with event: UIEvent?) {
if let touch = touches.first?.location(in: self) {
xDif = sprite.position.x - touch.x
yDif = sprite.position.y - touch.y
if sprite.frame.contains(touch) {
touchPoint = touch
touching = true
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
let location = t.location(in: self)
touchPoint = location
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
touching = false
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
touching = false
}
override func update(_ currentTime: TimeInterval) {
if touching {
let dt:CGFloat = 1.0/60.0
let distance = CGVector(dx: touchPoint.x-sprite.position.x + xDif, dy: touchPoint.y-sprite.position.y + yDif)
let velocity = CGVector(dx: distance.dx/dt, dy: distance.dy/dt)
sprite.physicsBody!.velocity = velocity
}
}
}

Resources