Trouble converting between coordinate spaces in SpriteKit - ios

I am trying to get a SKShapeNode to follow a UITouch
My problem is that the location of the UITouch is relative to the bottom-left corner of the screen and the position of the SKShapeNode is relative to the location I gave it when I created it, the center of the screen.
For example: ball is centered at (189.0, 335.0), the center of the view. I click the ball, the location of the touch tells me the touch event occurred at (189.0, 335.0), but the ball.position will tell me (0.0, 0.0). How can I get the ball's position in the view's coordinate system?
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var touched = false
var location = CGPoint.zero
var ball: SKShapeNode = SKShapeNode(circleOfRadius: 15)
var background: SKSpriteNode = SKSpriteNode(imageNamed: "background")
override func didMove(to view: SKView) {
background.size = frame.size
background.position = CGPoint(x: view.frame.size.width / 2, y: view.frame.size.height / 2)
addChild(background)
let path = CGMutablePath()
path.addArc(center: CGPoint(x: view.frame.size.width / 2, y: view.frame.size.height / 2),
radius: 15,
startAngle: 0,
endAngle: CGFloat.pi * 2,
clockwise: false)
ball = SKShapeNode(path: path) // Added to center of screen
ball.lineWidth = 1
ball.fillColor = .cyan
ball.strokeColor = .cyan
ball.glowWidth = 0.5
addChild(ball)
print(ball.position)
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
moveNode()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
touched = true
for touch in touches {
location = touch.location(in: view)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
location = touch.location(in: view)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
touched = false
}
func moveNode() {
if touched {
let speed: CGFloat = 1
if location.x < ball.position.x {
let newLocation = ball.position.x - speed
ball.position = CGPoint(x: newLocation, y: ball.position.y)
} else {
let newLocation = ball.position.x + speed
ball.position = CGPoint(x: newLocation, y: ball.position.y)
}
}
}
}

You never set the ball position so it is (0,0). The problem is that the ball is at (0,0) but its path represents something at (189,335) in ball coordinates.
Change to:
override func didMove(to view: SKView) {
background.size = frame.size
background.position = CGPoint(x: view.frame.size.width / 2, y: view.frame.size.height / 2)
addChild(background)
let path = CGMutablePath()
// construct a path relative to center of the ball
path.addArc(center: CGPoint(x: 0, y: 0),
radius: 15,
startAngle: 0,
endAngle: CGFloat.pi * 2,
clockwise: false)
ball = SKShapeNode(path: path)
// locate the ball at the center of the screen
ball.position = CGPoint(x: view.frame.size.width / 2, y: view.frame.size.height / 2)
ball.lineWidth = 1
ball.fillColor = .cyan
ball.strokeColor = .cyan
ball.glowWidth = 0.5
addChild(ball)
print(ball.position)
}

Related

How can I alter my code to make my sprite, which rotates in a circular orbit, jump up then back down when a user taps the screen?

I'm trying to make my orange circle sprite, which orbits in a circular motion, jump at ant point in its rotation and fall back down again when the user taps the screen but can't seem to figure out what I'm doing wrong. Please help.
I've tried taking the advice to somewhat similar post o here but have had no luck.
class GameScene: SKScene {
let sprite = SKSpriteNode(imageNamed: "circle")
let player = SKSpriteNode(imageNamed: "player")
var node2AngularDistance: CGFloat = 0
override func didMove(to view: SKView) {
backgroundColor = SKColor(red: 94.0/255, green: 63.0/255, blue: 107.0/255, alpha: 1)
physicsWorld.gravity = CGVector(dx: 0, dy: 0)
sprite.size = CGSize(width: 150, height: 150)
sprite.position = CGPoint(x: 0,y: 0)
sprite.physicsBody = SKPhysicsBody(circleOfRadius: 10)
player.size = CGSize(width: 100, height: 100)
player.position = CGPoint(x: 0+50, y: 0)
player.physicsBody = SKPhysicsBody(circleOfRadius: 10)
self.addChild(sprite)
self.addChild(player)
}
//dont touch blue lines that discard code and don't allow you to undo
override func update(_ currentTime: TimeInterval) {
let dt: CGFloat = 1.0/60.0 //Delta Time
let period: CGFloat = 3 //Number of seconds it takes to complete 1 orbit.
let orbitPosition = sprite.position //Point to orbit.
let orbitRadius = CGPoint(x: 150, y: 150) //Radius of orbit.
let normal = CGVector(dx:orbitPosition.x + CGFloat(cos(self.node2AngularDistance))*orbitRadius.x ,dy:orbitPosition.y + CGFloat(sin(self.node2AngularDistance))*orbitRadius.y);
self.node2AngularDistance += (CGFloat(Double.pi)*2.0)/period*dt;
if (abs(self.node2AngularDistance)>CGFloat(Double.pi)*2) {
self.node2AngularDistance = 0
}
player.physicsBody!.velocity = CGVector(dx:(normal.dx-player.position.x)/dt ,dy:(normal.dy-player.position.y)/dt);
//func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
//sprite.position = (touches.first! as! UITouch).location(in: self)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchDown(atPoint: t.location(in: self)) }
}
func touchDown(atPoint pos: CGPoint) {
jump()
}
func jump() {
player.texture = SKTexture(imageNamed: "player")
player.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 500))
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchUp(atPoint: t.location(in: self)) }
}
func touchUp(atPoint pos: CGPoint) {
player.texture = SKTexture(imageNamed: "player")
}
}
The expected results would be a small circle sprite called player that rotates in circular orbit around a larger circle called sprite and when the user taps the screen, the player(small circle) would jump up then back down at any point in its orbit.The actual results are the the circle rotates around the larger circle but does not jump up when I click on the screen in the iPhone simulator.
A jumpping state needs to record as : isJumpping. If isJumpping, the update will not perform, if this is what you need.
The second issue is after touching up, the player falls back to the orbital. A simple math is given here as the player directly falling to the circle.
But if the situation is complicated, you may consider to use PhysicsField to simulate the situation.
This is running in a Xcode 10 and playground
import UIKit
import PlaygroundSupport
import SpriteKit
import simd
class GameScene: SKScene {
let sprite = SKSpriteNode(imageNamed: "circle")
let player = SKSpriteNode(imageNamed: "player")
var node2AngularDistance: CGFloat = 0
override func didMove(to view: SKView) {
print(100)
// backgroundColor = SKColor(red: 94.0/255, green: 63.0/255, blue: 107.0/255, alpha: 1)
backgroundColor = SKColor.black
physicsWorld.gravity = CGVector(dx: 0, dy: 0)
sprite.size = CGSize(width: 150, height: 150)
sprite.position = CGPoint(x: 0,y: 0)
sprite.physicsBody = SKPhysicsBody(circleOfRadius: 10)
player.size = CGSize(width: 100, height: 100)
player.position = CGPoint(x: 0+50, y: 0)
player.physicsBody = SKPhysicsBody(circleOfRadius: 10)
self.anchorPoint = CGPoint.init(x: 0.5, y: 0.5)
self.addChild(sprite)
self.addChild(player)
}
//dont touch blue lines that discard code and don't allow you to undo
override func update(_ currentTime: TimeInterval) {
guard !isJumpping else {
return
}
let dt: CGFloat = 1.0/60.0 //Delta Time
let period: CGFloat = 3 //Number of seconds it takes to complete 1 orbit.
let orbitPosition = sprite.position //Point to orbit.
let orbitRadius = CGPoint(x: 150, y: 150) //Radius of orbit.
let currentDistance = distance(double2(x: Double(orbitPosition.x), y: Double(orbitPosition.y)), double2(x: Double(player.position.x), y: Double(player.position.y)))
if ( currentDistance > 150){
player.physicsBody!.velocity = CGVector(dx:(orbitPosition.x - player.position.x) ,dy:(orbitPosition.y - player.position.y));
let x = Double(player.position.x) - Double(orbitPosition.x)
let y = Double(player.position.y) - Double(orbitPosition.y)
self.node2AngularDistance = CGFloat(acos(x/currentDistance))
if y < 0 {
self.node2AngularDistance = 2 * CGFloat.pi - self.node2AngularDistance }
return;
}
let normal = CGVector(dx:orbitPosition.x + CGFloat(cos(self.node2AngularDistance))*orbitRadius.x ,dy:orbitPosition.y + CGFloat(sin(self.node2AngularDistance))*orbitRadius.y);
self.node2AngularDistance += (CGFloat(Double.pi)*2.0)/period*dt;
if (abs(self.node2AngularDistance)>CGFloat(Double.pi)*2) {
self.node2AngularDistance = 0
}
player.physicsBody!.velocity = CGVector(dx:(normal.dx-player.position.x)/dt ,dy:(normal.dy-player.position.y)/dt);
//func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
//sprite.position = (touches.first! as! UITouch).location(in: self)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchDown(atPoint: t.location(in: self)) }
}
func touchDown(atPoint pos: CGPoint) {
jump()
}
private var isJumpping : Bool = false
func jump() {
isJumpping.toggle()
player.texture = SKTexture(imageNamed: "player")
player.physicsBody?.applyImpulse(CGVector(dx: (self.player.position.x - self.sprite.position.x) / 100.0, dy: (self.player.position.y - self.sprite.position.y) / 100.0))
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchUp(atPoint: t.location(in: self)) }
}
func touchUp(atPoint pos: CGPoint) {
isJumpping.toggle()
player.texture = SKTexture(imageNamed: "player")
}
}
class GameViewCointroller: UIViewController{
var skview: SKView!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
skview = SKView.init(frame: view.bounds)
let scene = GameScene.init(size: view.frame.size)
skview.presentScene(scene)
view.addSubview(skview)
}
}
PlaygroundPage.current.liveView = GameViewCointroller()

How to move line with finger?

I am defining the following variables in my ViewController for a line.
var scanlineRect = CGRect.zero
var scanlineStartY: CGFloat = 0
var scanlineStopY: CGFloat = 0
var topBottomMargin: CGFloat = 30
var scanLine: UIView = UIView()
I can draw a line using UIView and can move it in vertical direction using UIView.animate method. How to move a line dragging it with finger?
func drawLine()
{
self.view.addSubview(scanLine)
scanLine.backgroundColor = UIColor.black
scanlineRect = CGRect(x: 15, y: 0, width: self.view.frame.width - 30, height: 5)
scanlineStartY = topBottomMargin
scanlineStopY = self.view.frame.height - topBottomMargin
scanLine.frame = scanlineRect
scanLine.center = CGPoint(x: scanLine.center.x, y: scanlineStartY)
scanLine.isHidden = false
}
func Move(Location : CGPoint)
{
scanLine.isHidden = false
weak var weakSelf = scanLine
UIView.animate(withDuration: 3.0, animations: {
weakSelf!.center = CGPoint(x: weakSelf!.center.x, y: Location.y)
}, completion: nil)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
var lastPoint = CGPoint.zero
if let touch = touches.first
{
lastPoint = touch.location(in: self.view)
}
Move(Location: lastPoint)
}
override func viewDidLoad()
{
super.viewDidLoad()
drawLine()
}
Try using touchesMoved instead of touchesBegan
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
guard let location = touch?.location(in: self.view) else{
return
}
scanLine.frame.origin = CGPoint(x: location.x-scanLine.frame.size.width/2, y: location.y-scanLine.frame.size.height/2)
}
updated
If you want only move in y axis only you need to change this line
scanLine.frame.origin = CGPoint(x: location.x-scanLine.frame.size.width/2, y: location.y-scanLine.frame.size.height/2)
use this instead note that scanline.frame.origin.x is unchanged
scanLine.frame.origin = CGPoint(x: scanLine.frame.origin.x, y: location.y-scanLine.frame.size.height/2)

Swift PhysicsBody not affected by Impulse

this is my first post here and i am new to iOS programming. I am using Xcode 8 and Swift 3 to build my practice game. All i want is i want to jump the ball when the screen is tapped but the impulse is not working. Here is the code i have written.
class GameScene: SKScene {
var footBall = SKSpriteNode(imageNamed: "sfb")
var Score = 0
var GameStarted = false
override func didMove(to view: SKView) {
self.removeAllChildren()
self.anchorPoint = CGPoint(x: 0.0, y: 0.0)
footBall.size = CGSize(width: 100, height: 100)
footBall.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2)
footBall.physicsBody = SKPhysicsBody(circleOfRadius: footBall.frame.height / 2)
footBall.physicsBody?.affectedByGravity = false
footBall.physicsBody?.allowsRotation = true
footBall.physicsBody?.isDynamic = true
self.addChild(footBall)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if(GameStarted == false){
GameStarted = true
footBall.physicsBody?.affectedByGravity = true
footBall.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
footBall.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 90))
}
else{
footBall.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
footBall.physicsBody?.applyForce(CGVector(dx: 0, dy: 90))
}
}
override func update(_ currentTime: TimeInterval) {
}
When clicked the velocity of the ball becomes zero for a moment but the impulse should drive it up which is not happening. Please help me through this and ignore any ignorance because this is my first question and i am new to this.
Please note that in the true branch you are applying a force, not an impulse.
Change:
footBall.physicsBody?.applyForce(CGVector(dx: 0, dy: 90))
to
footBall.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 90))
Don't understand why you always zero the velocity either.

An SKSpriteNode not following another node properly

In my game scene I have a ball, a magnet and a platform called leveUnit as seen below
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var ball = SKShapeNode()
var magnet = SKShapeNode()
let worldNode:SKNode = SKNode()
var levelUnit = SKSpriteNode()
override func didMove(to view: SKView) {
ball = SKShapeNode(circleOfRadius: 30)
ball.fillColor = .yellow
ball.position = CGPoint(x: 0, y: 200)
magnet = SKShapeNode(circleOfRadius: 30)
magnet.fillColor = .blue
magnet.position = CGPoint(x: 0, y: 0)
levelUnit = SKSpriteNode(imageNamed: "Wall")
levelUnit.position = CGPoint(x: 0, y: 250)
worldNode.addChild(levelUnit)
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
addChild(worldNode)
worldNode.addChild(magnet)
//addChild(ball)
levelUnit.addChild(ball)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch: UITouch = touches.first!
let location: CGPoint = touch.location(in: self)
let node: SKNode = self.atPoint(location)
if node == self {
magnet.position = location
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
let playerLocation:CGPoint = self.convert(magnet.position, from: worldNode)
let location = playerLocation
let nodeSpeed:CGFloat = 20.5
let node = ball
//Aim
let dx = location.x - (node.position.x)
let dy = location.y - (node.position.y)
let angle = atan2(dy, dx)
node.zRotation = angle
//Seek
let vx = cos(angle) * nodeSpeed
let vy = sin(angle) * nodeSpeed
node.position.x += vx
node.position.y += vy
}
}
My problem here is when I add the ball to the levelUnit instead of just to the view itself it doesn't seem to follow the magnet to its exact position it just seems to hover slightly above its position while when I have it added to the view itself it goes to the magnets exact location. I am using the solution from this question here to make the ball go to the magnet's location.
what am I doing wrong? and what is causing this to happen?

As sprite moves up, more jump pads appear on screen

I am trying to make a Doodle Jump-like game. I want to make it so that as a sprite, my main character, moves above a certain Y point, more pads to jump on appear, giving it the illusion that it is jumping higher and higher.
How would I go about coding this? Any examples would be nice. I am using SpriteKit in Xcode 7.
Here is my code so far:
import SpriteKit
import Foundation
class GameScene: SKScene {
var mainSprite = SKSpriteNode()
var wallPair = SKNode()
var ground = SKSpriteNode()
var moveAndRemove = SKAction()
override func didMoveToView(view: SKView) {
/* Setup your scene here */
mainSprite.size = CGSize(width: 30, height: 30)
mainSprite.color = SKColor.redColor();
mainSprite.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2 - 100)
self.addChild(mainSprite)
ground.size = CGSize(width: self.frame.width, height: 300)
ground.color = SKColor.brownColor()
ground.position = CGPoint(x: self.frame.width / 2, y: 0)
self.addChild(ground)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
createWallMap()
for touch in touches {
let location = touch.locationInNode(self)
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
func createWalls(height: CGFloat) {
wallPair = SKNode()
wallPair.name = "wallPair"
let rightWall = SKSpriteNode(imageNamed: "LeftWall.png") // I know it is opposite. Named the image wrong
let leftWall = SKSpriteNode(imageNamed: "RightWall.png")
rightWall.setScale(0.9)
leftWall.setScale(0.9)
rightWall.color = SKColor.redColor()
leftWall.color = SKColor.blueColor()
rightWall.position = CGPoint(x: self.frame.width / 2 + 340, y: height)
leftWall.position = CGPoint(x: self.frame.width / 2 - 340, y: height)
wallPair.addChild(rightWall)
wallPair.addChild(leftWall)
let randomPosition = CGFloat.random(min: -140, max: 140)
wallPair.position.x = wallPair.position.x + randomPosition
//wallPair.runAction(moveAndRemove)
self.addChild(wallPair)
}

Resources