I'm trying to make a ball, SKSpriteNode, move in ALL directions including diagonals and all other directions. UIGesture doesn't work with it so I need to use UIPanGesture but I have no idea how to implement it into a SpriteKit file.
This is what I have so far. Any help?? The ball sprite node is called "ball".
func handlePan(recognizer:UIPanGestureRecognizer) {
let translation = recognizer.translation(in: self.view)
if let view = recognizer.view {
view.center = CGPoint(x:view.center.x + translation.x,
y:view.center.y + translation.y)
}
recognizer.setTranslation(CGPoint.zero, in: self.view)
}
Note that SpriteKit and UIKit have totally different coordinate systems (Spritekit is Cartesian, UIKit is reflected about the X axis) so you cannot use UIKit coordinates in Spritekit without conversion. Fortunately SKScene and SKNode have methods to convert points from a SKView and from other SKNodes into a nodes local space.
#objc private func pan(_ recognizer: UIPanGestureRecognizer) {
let pointInView = recognizer.location(in: view)
let pointInScene = convertPoint(fromView: pointInView)
switch recognizer.state {
case .began:
//Start dragging on the ball, or ignore this gesture
isPanning = atPoint(pointInScene) == ball
case .changed:
//Move the ball
guard isPanning else {
return
}
let translation = recognizer.translation(in: view)
ball.position.x -= translation.x
ball.position.y += translation.y
recognizer.setTranslation(.zero, in: view)
case .cancelled, .ended:
//Stop dragging
isPanning == false
default:
break
}
I have a full example on my GitHub here: https://github.com/joshuajhomann/AngryBirdsClone
Related
I need your help guys. I have game scene and func which allow to move camera using panGesture. Also i need pinchGesture to zoom in and out my SKScene. I found some code here, but it lags. Can plz someone help me to improve this code?
`
#objc private func didPinch(_ sender: UIPinchGestureRecognizer) {
guard let camera = self.camera else {return}
if sender.state == .changed {
previousCameraScale = camera.xScale
}
camera.setScale(previousCameraScale * 1 / sender.scale)
sender.scale = 1.0
}
`
try this pinch code.
//pinch -- simple version
#objc func pinch(_ recognizer:UIPinchGestureRecognizer) {
guard let camera = self.camera else { return } // The camera has a weak reference, so test it
if recognizer.state == .changed {
let deltaScale = (recognizer.scale - 1.0)*2
let convertedScale = recognizer.scale - deltaScale
let newScale = camera.xScale*convertedScale
camera.setScale(newScale)
//reset value for next time
recognizer.scale = 1.0
}
}
although i would recommend this slightly more complicated version which centers the pinch around the touch point. makes for a much nicer pinch in my experience.
//pinch around touch point
#objc func pinch(_ recognizer:UIPinchGestureRecognizer) {
guard let camera = self.camera else { return } // The camera has a weak reference, so test it
//cache location prior to scaling
let locationInView = recognizer.location(in: self.view)
let location = self.convertPoint(fromView: locationInView)
if recognizer.state == .changed {
let deltaScale = (recognizer.scale - 1.0)*2
let convertedScale = recognizer.scale - deltaScale
let newScale = camera.xScale*convertedScale
camera.setScale(newScale)
//zoom around touch point rather than center screen
let locationAfterScale = self.convertPoint(fromView: locationInView)
let locationDelta = location - locationAfterScale
let newPoint = camera.position + locationDelta
camera.position = newPoint
//reset value for next time
recognizer.scale = 1.0
}
}
//also need these extensions to add and subtract CGPoints
extension CGPoint {
static func + (a:CGPoint, b:CGPoint) -> CGPoint {
return CGPoint(x: a.x + b.x, y: a.y + b.y)
}
static func - (a:CGPoint, b:CGPoint) -> CGPoint {
return CGPoint(x: a.x - b.x, y: a.y - b.y)
}
}
I made a simple app with Xcode in swift in which a circular Imageview in the middle of the screen can be moved with swipe-gesture and changes color when touching the outside of the bigger ring (image for reference) and at the same time changes location to the middle of the screen. Change of color and respawn is achieved by an if statement that changes the file of the picture when the euclidian distance between the middle-points of the smaller Imageview and the middle coordinates of the screen exceeds a certain length. However, when the Imageview spawns in what seems to me to be fixed middle coordinates, the spawning-position is slighty moved in the direction of the swipe. And even more so if the swipe-gesture is faster. Thanks for any hints.
Edit: I removed every part that keeps this from being a minimal example for the problem I have and also uploaded the files I for the colored balls. Can anyone tell me how else I could improve my question to get an answer?
Edit: Now I added an animated gif which firstly shows the functionality and later, when I increase dragging speed, also shows the problem that I'm facing.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBOutlet weak var colorImg: UIImageView!
#IBAction func panMade(_ sender: UIPanGestureRecognizer) {
if sender.state == .began || sender.state == .changed {
let translation = sender.translation(in: sender.view)
let changeX = (sender.view?.center.x)! + translation.x
let changeY = (sender.view?.center.y)! + translation.y
sender.view?.center = CGPoint(x: changeX, y: changeY)
sender.setTranslation(CGPoint.zero, in: sender.view)
}
let coordX = colorImg.frame.origin.x+60
let coordY = colorImg.frame.origin.y+60
let centerX = UIScreen.main.bounds.size.width*0.5
let centerY = UIScreen.main.bounds.size.height*0.5
let a = (coordX-centerX) ; let b = (coordY-centerY) ; let c = (a*a) ; let d = (b*b) ; let j = Double(c+d)
if (j.squareRoot() > 112) {
let n = Int.random(in: 1..<4)
colorImg.image = UIImage(named: String(n) + ".png")
sleep(1)
colorImg.center = CGPoint(x: centerX, y: centerY)
}
}
}
I am trying to create a SpriteKit game where a ball moves across the screen. When the ball leaves the screen I would like to remove it from the parent and switch to a different scene (GameOverScene).
I am using enumerateChildNodes however it doesn't as if that is working. I am not really sure what the problem is however I think it may have something to do with the parent/child relationship...
func createBall(forTrack track: Int) {
setupTracks()
player?.physicsBody?.linearDamping = 0
player = SKSpriteNode(imageNamed: "small")
player?.name = "BALL"
player?.size = CGSize(width: 100, height: 100)
ballValue = 1
randFloat = Float(arc4random()) / Float(UINT32_MAX)
if randFloat > 0.001 {
ballSpeed = randFloat / 50
}
else {
ballSpeed = randFloat / 50
}
let ballPosition = trackArray?[track].position
player?.position = CGPoint(x: (ballPosition?.x)!, y: (ballPosition?.y)!)
player?.position.y = (ballPosition?.y)!
if ballDirection == "right" {
player?.position.x = 0
moveRight()
}
else {
player?.position.x = (self.view?.frame.size.height)!
moveLeft(speed: ballSpeed)
}
self.addChild(player!)
self.enumerateChildNodes(withName: "BALL") { (node: SKNode, nil) in
if node.position.x < -100 || node.position.x > (self.size.width) + 100 {
print("balls Out")
node.removeFromParent()
let transition = SKTransition.fade(withDuration: 1)
self.gameScene = SKScene(fileNamed: "GameOverScene")
self.gameScene.scaleMode = .aspectFit
self.view?.presentScene(self.gameScene, transition: transition)
}
}
}
I call this function twice, first in override func didMove():
override func didMove(to view: SKView) {
createHUD()
createBall(forTrack: track)
}
And second in override func touchesBegan:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = touch.previousLocation(in: self)
let node = self.nodes(at: location).first
if node?.name == "BALL" {
currentScore += ballValue
player?.removeFromParent()
createBall(forTrack: track)
}
else {
let transition = SKTransition.fade(withDuration: 1)
gameScene = SKScene(fileNamed: "GameOverScene")
gameScene.scaleMode = .aspectFit
self.view?.presentScene(gameScene, transition: transition)
}
}
}
update:
The line self.enumerateChildNodes(withName: "BALL") { (node: SKNode, nil) in works so it is not a child parent relationship issue. The if statement is not working.
reading your code I think it's not about a solution, but a different approach. These suggestions will make your life easier:
Start with a smaller code, avoid or comment out all unneeded (like the if randFloat)
Force unwrap player = SKSpriteNode(imageNamed: "small")! with the ! because you actually want to crash if the initialization fails, then you can get rid of all ?
in touchesBegan, better use for touch in touches { as it is more simple to manage
let location = touch.previousLocation(in: self) you probably mean let location = touch.location(in: self)
When the ball leaves the screen I would like to remove it from the parent so this is something happening at some time in the game. You would like to call your self.enumerateChildNodes(withName: "BALL") { into the update call, not every time you touch the screen
If this is not enough, feel free to post the least amount of code to make a playground and let me test it for you :]
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?
I am moving a label around the screen and I want it to go back to it's initial position after I remove my finger off it, for now it's stays there! Any help would be appreciated.
#IBAction func handlePan(recognizer:UIPanGestureRecognizer) {
let translation = recognizer.translationInView(self.view)
if let view = recognizer.view {
view.center = CGPoint(x:view.center.x + translation.x, y:view.center.y + translation.y)
}
recognizer.setTranslation(CGPointZero, inView: self.view)
if recognizer.state == UIGestureRecognizerState.Ended {
//What code to add to return my Label to it's initial position??
}
}
Save in a variable your initial center position view.center at start dragging, and when you end up moving your UIView around set this value again.
Try this out:
import UIKit
class ViewController: UIViewController {
var initial : CGPoint?
#IBAction func handlePan(recognizer:UIPanGestureRecognizer) {
if let view = recognizer.view {
if recognizer.state == UIGestureRecognizerState.Began {
initial = view.center
}
else if recognizer.state == UIGestureRecognizerState.Ended {
view.center = initial!
return
}
let translation = recognizer.translationInView(self.view)
view.center = CGPoint(x:view.center.x + translation.x, y:view.center.y + translation.y)
recognizer.setTranslation(CGPointZero, inView: self.view)
}
}
}
I believe inside the function touchesEnded you can reset the label's position.
Hope that helps :)