Moving SKSpriteNode in a semi circle with a virtual joystick - ios

I am making a game that contains a virtual joystick. My joystick is composed by two SKSpriteNode, one for the stick the other for the base panel. ( see the picture below )
Obviously my joystick can only be moved following the green semi-circle on the panel.
My problem here is that i'm trying to make it happen, here is my code for now:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.location(in: self)
guard isTracking else {
return
}
let maxDistantion = panel.radius
let realDistantion = sqrt(pow(location.x, 2) + pow(location.y, 2))
let needPosition = realDistantion <= maxDistantion ? CGPoint(x: location.x, y: location.y) : CGPoint(x: location.x / realDistantion * maxDistantion, y: location.y / realDistantion * maxDistantion)
if (needPosition.y > 0) {
stick.position = needPosition
} else {
stick.position = CGPoint(x: needPosition.x, y: 0)
}
data = AnalogJoystickData(velocity: needPosition, angular: -atan2(needPosition.x, needPosition.y))
}
}
With this code, I can move into the semi-circle, but I would to only move my stick onto the green line, if someone can help me with my poor mathematics
Thank you

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
}

SpriteKit: calculate angle of joystick and change sprite based on that

I am making a RPG Birds-eye style game with SpriteKit. I made a joystick because a D-Pad does not give the player enough control over his character.
I cannot wrap my brain around how I would calculate the neccessary data needed to change the Sprite of my character based on the angle of the joystick thumb Node.
Here is my code I am using
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if isTracking == false && base.contains(location) {
isTracking = true
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location: CGPoint = touch.location(in: self)
if isTracking == true {
let v = CGVector(dx: location.x - base.position.x, dy: location.y - DPad.position.y)
let angle = atan2(v.dy, v.dx)
let deg = angle * CGFloat(180 / Double.pi)
let Length:CGFloat = base.frame.size.height / 2
let xDist: CGFloat = sin(angle - 1.57079633) * Length
let yDist: CGFloat = cos(angle - 1.57079633) * Length
print(xDist,yDist)
xJoystickDelta = location.x * base.position.x / CGFloat(Double.pi)
yJoystickDelta = location.y * base.position.y / CGFloat(Double.pi)
if base.contains(location) {
thumbNode.position = location
} else {
thumbNode.position = CGPoint(x: base.position.x - xDist, y: base.position.y + yDist)
}
}
}
}
Update method
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if xJoystickDelta > 0 && yJoystickDelta < 0 {
print("forward")
}
}
The way I have set up right now tests the positive or negative state of the Joystick position in a cross method based on where the thumb Node is inside of the four marked sections below
I dont want it to do that
How can I set it up so that it changes the sprite based on where the thumb node is actually pointing inside my joysticks base like so.
I have been struggling with this for 3 days now so any help would be appreciated.
That looks far too complicated. Just compare the x and y components
of the difference vector v. Something like this:
if v.dx > abs(v.dy) {
// right
} else if v.dx < -abs(v.dy) {
// left
} else if v.dy < 0 {
// up
} else if v.dy > 0 {
// down
}

Swift 3 Gesture Recognizers path.boundingBox is infinite

I wanted to learn more about custom gesture recognizers so I was reading the Ray Wenderlich tutorial, which I planned to modify in order to learn the details and what I can change easily to learn how each piece works, but it was written in a previous version of Swift. Swift updated most of the code and I was able to fix the rest manually except that I am having trouble getting the touch gestures to be drawn on the screen, and no shapes are recognized as circles, which I'm hoping both tie back to the same problem. The website and code snippet are as follows:
https://www.raywenderlich.com/104744/uigesturerecognizer-tutorial-creating-custom-recognizers
import UIKit
import UIKit.UIGestureRecognizerSubclass
class CircleGestureRecognizer: UIGestureRecognizer {
fileprivate var touchedPoints = [CGPoint]() // point history
var fitResult = CircleResult() // information about how circle-like is the path
var tolerance: CGFloat = 0.2 // circle wiggle room, lower is more circle like higher is oval or other
var isCircle = false
var path = CGMutablePath() // running CGPath - helps with drawing
override func touchesBegan(_ touches: (Set<UITouch>!), with event: UIEvent) {
if touches.count != 1 {
state = .failed
}
state = .began
let window = view?.window
if let touches = touches, let loc = touches.first?.location(in: window) {
//print("path 1 \(path.currentPoint)")
path.move(to: CGPoint(x: loc.x, y: loc.y)) // start the path
print("path 2 \(path.currentPoint)")
}
//super.touchesBegan(touches, with: event)
}
override func touchesMoved(_ touches: (Set<UITouch>!), with event: UIEvent) {
// 1
if state == .failed {
return
}
// 2
let window = view?.window
if let touches = touches, let loc = touches.first?.location(in: window) {
// 3
touchedPoints.append(loc)
print("path 3 \(path.currentPoint)")
path.move(to: CGPoint(x: loc.x, y: loc.y))
print("path 4 \(path.currentPoint)")
// 4
state = .changed
}
}
override func touchesEnded(_ touches: (Set<UITouch>!), with event: UIEvent) {
print("path 5 \(path.currentPoint)")
// now that the user has stopped touching, figure out if the path was a circle
fitResult = fitCircle(touchedPoints)
// make sure there are no points in the middle of the circle
let hasInside = anyPointsInTheMiddle()
let percentOverlap = calculateBoundingOverlap()
isCircle = fitResult.error <= tolerance && !hasInside && percentOverlap > (1-tolerance)
state = isCircle ? .ended : .failed
}
override func reset() {
//super.reset()
touchedPoints.removeAll(keepingCapacity: true)
path = CGMutablePath()
isCircle = false
state = .possible
}
fileprivate func anyPointsInTheMiddle() -> Bool {
// 1
let fitInnerRadius = fitResult.radius / sqrt(2) * tolerance
// 2
let innerBox = CGRect(
x: fitResult.center.x - fitInnerRadius,
y: fitResult.center.y - fitInnerRadius,
width: 2 * fitInnerRadius,
height: 2 * fitInnerRadius)
// 3
var hasInside = false
for point in touchedPoints {
if innerBox.contains(point) {
hasInside = true
break
}
}
//print(hasInside)
return hasInside
}
fileprivate func calculateBoundingOverlap() -> CGFloat {
// 1
let fitBoundingBox = CGRect(
x: fitResult.center.x - fitResult.radius,
y: fitResult.center.y - fitResult.radius,
width: 2 * fitResult.radius,
height: 2 * fitResult.radius)
let pathBoundingBox = path.boundingBox
// 2
let overlapRect = fitBoundingBox.intersection(pathBoundingBox)
// 3
let overlapRectArea = overlapRect.width * overlapRect.height
let circleBoxArea = fitBoundingBox.height * fitBoundingBox.width
let percentOverlap = overlapRectArea / circleBoxArea
print("Percent Overlap \(percentOverlap)")
print("pathBoundingBox \(pathBoundingBox)")
print("path 6 \(path.currentPoint)")
return percentOverlap
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
state = .cancelled // forward the cancel state
}
}
As shown in the tutorial this bit of code is supposed to compare a bounding box for the path to a box that would fit a circle and compare the overlap, but when I print the pathBoundingBox is states: "pathBoundingBox (inf, inf, 0.0, 0.0)" which is probably why the percentOverlap is 0. I was thinking it was the path.move(to: loc) where loc is the first touch location but the documentation for move(to:) says "This method implicitly ends the current subpath (if any) and sets the current point to the value in the point parameter." so I'm struggling to figure out why the path.boundingBox is infinite...
That's not an infinite bounding box, it's just the opposite — a zero bounding box. The problem is that your path is empty.

Swift SpriteKit: Creating a realistic driving experience 2D

How would I go by creating a realistic driving experience?
I'm using iOS Swift 3 with SpriteKit using the functions applyForce to accelerate when I click the gas button and when braking I add friction to the physicsBody, also I can't seem to turn right, no idea on how to do it.
Right now for turning I'm splitting the screen using the left and right to turn but I'm using applyForce but its very bad because it turns when the car is stopped and in a very unrealistic way.
When I apply force its only to go up so if I do create a turning mechanism and I do a uturn the car will still go up.
Also side note: Multitouch doesn't work?
Any help? Thanks
override func didMove(to view: SKView) {
// Multi Touch
self.view?.isMultipleTouchEnabled = true
car.carNode.position = CGPoint(x: 0, y: 0)
car.carNode.physicsBody = SKPhysicsBody(rectangleOf: car.carNode.size)
car.carNode.physicsBody?.affectedByGravity = false
car.carNode.physicsBody?.angularDamping = 0.1
car.carNode.physicsBody?.linearDamping = 0.1
car.carNode.physicsBody?.friction = 0.1
car.carNode.physicsBody?.mass = 1
self.addChild(car.carNode)
// HUD Display
gas.gasButton.position = CGPoint(x: 300, y: -500)
self.addChild(gas.gasButton)
brake.brakeButton.position = CGPoint(x: 150, y: -500)
self.addChild(brake.brakeButton)
}
override func update(_ currentTime: TimeInterval) {
if touching {
car.carNode.physicsBody?.applyForce(CGVector(dx: 0, dy: 180))
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if location.x < self.frame.size.width / 2 {
// Left side of the screen
car.carNode.physicsBody?.applyForce(CGVector(dx: -100, dy: 0))
} else {
// Right side of the screen
car.carNode.physicsBody?.applyForce(CGVector(dx: 100, dy: 0))
}
// Gas Button
if (gas.gasButton.contains(location)) {
touching = true
}
// Brake Button
else if (brake.brakeButton.contains(location)) {
car.carNode.physicsBody?.friction = 1
}
}
}
Integration of the comment
for touch in touches {
let location = touch.location(in: self)
let velY = car.carNode.physicsBody?.velocity.dy
let factor:CGFloat = 100
let maxFactor:CGFloat = 300
//limit
let dxCalc = factor * velY > maxFactor ? maxFactor : factor * velY
if location.x < self.frame.size.width / 2 {
// Left side of the screen
car.carNode.physicsBody?.applyForce(CGVector(dx: -dxCalc, dy: 0))
} else {
// Right side of the screen
car.carNode.physicsBody?.applyForce(CGVector(dx: dxCalc, dy: 0))
}
// Gas Button
//etc
}
}

Touch contact with circle sprite oversize

I've got a simple spritekit project in which I'm noticing that the point at which contact is registered with my sprite is occurring well outside the circle physics body.
Demonstrated here:
https://gfycat.com/DentalBriskAfricangroundhornbill
With showPhysics on: http://i.imgur.com/F4BcXhb.png
Code for the object spawning the circles:
func spawnObject() {
object = SKSpriteNode(imageNamed: "Oval")
//object.fillColor = SKColor.white
object.name = "ball"
object.position = CGPoint(x: 280, y: 520)
object.physicsBody = SKPhysicsBody(circleOfRadius: object.size.width / 2)
object.physicsBody!.isDynamic = true
object.physicsBody!.contactTestBitMask = object.physicsBody!.collisionBitMask
object.physicsBody?.friction = 0.2
object.physicsBody?.restitution = 0.2
object.physicsBody?.mass = 5
addChild(object)
}
The circles themselves interact perfectly with one another - it appears touch is the outlier.
Would appreciate any assistance :)
Edit:
Example touch code:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = touch.location(in: self)
let tappedNodes = nodes(at: location)
for node in tappedNodes {
if node.name == "ball" {
node.alpha = 0.5
}
}
}
}

Resources