So I recently made a swift game with sprite kit and now I am stuck.
I have a character selection screen and I want to show a description for the character if you hold on the character, but if you just touch it you choose it and play with it. I already have the code to play/show the description. I just need to know when to call the corresponding functions and how to differentiate if the node is held or just touched.
Thanks in advance
So, here is how you can do it using SKActions... Also, there is another way using SKAction but I can't really show you all the possibilities :) Anyways, here is the code which does what you want:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
let kLongPressDelayActionKey = "longPressDelay"
let kLongPressStartedActionKey = "longPressStarted"
let kStoppingLongPressActionKey = "stoppingLongPressActionKey"
let desc = SKSpriteNode(color: .white, size: CGSize(width: 150, height: 150))
let button = SKSpriteNode(color: .purple, size: CGSize(width: 150, height: 150))
let other = SKSpriteNode(color: .yellow, size: CGSize(width: 150, height: 150))
override func didMove(to view: SKView) {
addChild(desc)
addChild(button)
addChild(other)
button.position.y = -160
button.name = "button"
desc.alpha = 0.0
desc.name = "description"
other.name = "other"
other.alpha = 0.0
other.position.y = -320
}
private func singleTap(onNode:SKNode){
other.alpha = other.alpha == 0.0 ? 1.0 : 0.0
}
private func startLongPress(withDuration duration:TimeInterval){
//How long does it take before long press is fired
//If user moves his finger of the screen within this delay, the single tap will be fired
let delay = SKAction.wait(forDuration: duration)
let completion = SKAction.run({
[unowned self] in
self.desc.removeAction(forKey: self.kLongPressDelayActionKey)
self.desc.run(SKAction.fadeIn(withDuration: 0.5), withKey: self.kLongPressStartedActionKey)
})
self.desc.run(SKAction.sequence([delay,completion]), withKey: kLongPressDelayActionKey)
}
private func stopLongPress(){
//Fire single tap and stop long press
if desc.action(forKey: kLongPressDelayActionKey) != nil{
desc.removeAction(forKey: kLongPressDelayActionKey)
self.singleTap(onNode: self.other)
//or just stop the long press
}else{
desc.removeAction(forKey: kLongPressStartedActionKey)
//Start fade out action if it isn't already started
if desc.action(forKey: kStoppingLongPressActionKey) == nil {
desc.run(SKAction.fadeOut(withDuration: 0.2), withKey: kStoppingLongPressActionKey)
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
//To stop the long press if we slide a finger of the button
if let touch = touches.first {
let location = touch.location(in: self)
if !button.contains(location) {
stopLongPress()
}
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = touch.location(in: self)
if button.contains(location) {
print("Button tapped")
startLongPress( withDuration: 1)
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
stopLongPress()
}
}
You can run the code and you will see that if you tap on the purple button, a yellow box will pop up. If you tap it again, it will hide. Also, if you hold your finger on purple box for 1 second, a white box will fade in. When you release the finger, it will fade out. As an addition, I have implemented touchesMoved, so if you slide your finger of the purple button while blue box is out, it will fade out as well.
So in this example, I have fused long press with single tap. Single tap is considered to be everything that is not long press. Eg, if user holds his finger 0.01 sec, or 0.5 sec, or 0.99 sec, it will be considered as single tap and yellow box will pop out. If you hold your finger for >= 1 second, the long press action will be triggered.
Another way would be to use gesture recognizers...And I will probably make an example for that later :)
And here is how you can do it using gesture recognizers:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var longPressGesture:UILongPressGestureRecognizer!
var singleTapGesture:UITapGestureRecognizer!
let kLongPressStartedActionKey = "longPressStarted"
let kStoppingLongPressActionKey = "stoppingLongPressActionKey"
let desc = SKSpriteNode(color: .white, size: CGSize(width: 150, height: 150))
let button = SKSpriteNode(color: .purple, size: CGSize(width: 150, height: 150))
let other = SKSpriteNode(color: .yellow, size: CGSize(width: 150, height: 150))
override func didMove(to view: SKView) {
longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(GameScene.longPress(_:)))
singleTapGesture = UITapGestureRecognizer(target: self, action: #selector(GameScene.singleTap(_:)))
self.view?.addGestureRecognizer(longPressGesture)
self.view?.addGestureRecognizer(singleTapGesture)
addChild(desc)
addChild(button)
addChild(other)
button.position.y = -160
button.name = "button"
desc.alpha = 0.0
desc.name = "description"
other.name = "other"
other.alpha = 0.0
other.position.y = -320
}
private func stopLongPress(){
desc.removeAction(forKey: self.kLongPressStartedActionKey)
//Start fade out action if it isn't already started
if desc.action(forKey: kStoppingLongPressActionKey) == nil {
desc.run(SKAction.fadeOut(withDuration: 0.2), withKey: kStoppingLongPressActionKey)
}
}
func singleTap(_ sender:UITapGestureRecognizer){
if sender.state == .ended {
let location = convertPoint(fromView: sender.location(in: self.view))
if self.button.contains(location) {
self.other.alpha = self.other.alpha == 0.0 ? 1.0 : 0.0
}
}
}
func longPress(_ sender: UILongPressGestureRecognizer) {
let longPressLocation = convertPoint(fromView: sender.location(in: self.view))
if sender.state == .began {
if button.contains(longPressLocation) {
desc.run(SKAction.fadeIn(withDuration: 0.5), withKey: self.kLongPressStartedActionKey)
}
}else if sender.state == .ended {
self.stopLongPress()
}else if sender.state == .changed {
let location = convertPoint(fromView: sender.location(in: self.view))
if !button.contains(location) {
stopLongPress()
}
}
}
}
Related
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let positionInScene = touch!.location(in: self)
let touchedNode = self.atPoint(positionInScene)
if let name = touchedNode.name {
if name == "jumpbutton" {
let wait = SKAction.wait(forDuration: 3)
let boost = SKAction.applyImpulse(CGVector(dx: 0.0, dy: 160.0), duration: 0.1)
let action = SKAction.group([boost, wait])
player!.run(action)
print("jumpbutton")
}
Here is my code where I tried to set a cool down of 3 seconds before the jump button could be pressed again. Thanks for any help.
What works is creating a flag to determine whether pressing the jump button allows us to jump or it does not allow us to jump (i.e it is in cool down).
For this you create a variable called isReady near the top of the class and set it to true.
When it comes to the touches, we want to check whether the node we are touching is the jump button (touchedNode.name == "jump button"), and whether the button is ready (isReady == true). If both equate to true we can continue on. We first set the flag isReady = false, this is so we can not immediately run this section of code again (think of someone rapidly pressing the jump button).
Next part is creating the boost and running it.
Then finally, we create a wait action with a duration of 3 seconds. We run this wait action, then on completion we set the flag back to true (isReady = true).
class GameScene: SKScene {
var isReady = true
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let positionInScene = touch!.location(in: self)
let touchedNode = self.atPoint(positionInScene)
if touchedNode.name == "jumpbutton" && isReady == true {
isReady = false
let boost = SKAction.applyImpulse(CGVector(dx: 0.0, dy: 160.0), duration: 0.1)
player!.run(boost)
print("jumpbutton")
let wait = SKAction.wait(forDuration: 3)
run(wait) { [self] in
isReady = true
}
}
}
}
You could simply implement with actions like you were inclined to do:
// Disable button
let waitAction = SKAction.wait(forDuration: coolDownTime)
run(waitAction) { [weak self] in
// Enable button
}
To expand on this answer I would like to propose the creation of a component, so you don't need to deal with every button press on your GameScene.
First create your button on the file SKButton.swift:
import SpriteKit
class SKButton: SKSpriteNode {
var coolDownTime: TimeInterval = 2.0
var isReady: Bool = true
private var originalColor: UIColor = .red
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if isReady {
print("This button was touched")
originalColor = color
color = .gray
isReady = false
let waitAction = SKAction.wait(forDuration: coolDownTime)
run(waitAction) { [weak self] in
self?.isReady = true
self?.color = self?.originalColor ?? .blue
}
}
}
}
Here is an example of it working in my GameScene:
import SpriteKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
super.didMove(to: view)
let myButton = SKButton()
myButton.position = CGPoint(x: frame.midX, y: frame.midY)
myButton.color = .red
myButton.size = CGSize(width: 200, height: 200)
myButton.isUserInteractionEnabled = true
addChild(myButton)
}
}
Everything I need are commented down below, I have a function that when I tap it, the game should restart. The restart function works by its self, but I only want it to restart after the user taps the screen. If you need some clearing up on what certain parts of the code do, just comment down below please Could you please let me know what I've done wrong, thanks!
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var ball = SKSpriteNode()
var enemy = SKSpriteNode()
var main = SKSpriteNode()
var topLbl = SKLabelNode()
var btmLbl = SKLabelNode()
var score = [Int]()
func pauseGame() { // PAUSE GAME FUCTION
self.isPaused = true
self.physicsWorld.speed = 0
self.speed = 0.0
self.scene?.view?.isPaused = true
}
func test(gestureRecognizer: UITapGestureRecognizer) { // MY FUNC TO RESTART THE GAME
let skView = self.view!
skView.presentScene(scene)
}
override func didMove(to view: SKView) {
topLbl = self.childNode(withName: "topLabel") as! SKLabelNode
btmLbl = self.childNode(withName: "btmLabel") as! SKLabelNode
ball = self.childNode(withName: "ball") as! SKSpriteNode
print(self.view?.bounds.height)
enemy = self.childNode(withName: "enemy") as! SKSpriteNode
enemy.position.y = (self.frame.height / 2) - 50
main = self.childNode(withName: "main") as! SKSpriteNode
main.position.y = (-self.frame.height / 2) + 50
let border = SKPhysicsBody(edgeLoopFrom: self.frame)
border.friction = 0
border.restitution = 1
self.physicsBody = border
startGame()
}
func startGame() {
score = [0,0]
topLbl.text = "\(score[1])"
btmLbl.text = "\(score[0])"
ball.physicsBody?.applyImpulse(CGVector(dx: 10 , dy: 10))
}
func addScore(playerWhoWon : SKSpriteNode){
ball.position = CGPoint(x: 0, y: 0)
ball.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
if playerWhoWon == main {
score[0] += 1
ball.physicsBody?.applyImpulse(CGVector(dx: 10, dy: 10))
}
else if playerWhoWon == enemy {
score[1] += 1
ball.physicsBody?.applyImpulse(CGVector(dx: -10, dy: -10))
}
if score[0] >= 10 {
pauseGame()
test() // HERE'S WHERE I TRY AND CALL THE FUNCTION, BUT IT's NOT WORKING
}
else if
score [1] >= 10 {
pauseGame()
test() // HERE'S WHERE I TRY AND CALL THE FUNCTION, BUT IT's NOT WORKING AGAIN
}
topLbl.text = "\(score[1])"
btmLbl.text = "\(score[0])"
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if currentGameType == .player2 {
if location.y > 0 {
enemy.run(SKAction.moveTo(x: location.x, duration: 0.2))
}
if location.y < 0 {
main.run(SKAction.moveTo(x: location.x, duration: 0.2))
}
}
else {
main.run(SKAction.moveTo(x: location.x, duration: 0.2))
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if currentGameType == .player2 {
if location.y > 0 {
enemy.run(SKAction.moveTo(x: location.x, duration: 0.2))
}
if location.y < 0 {
main.run(SKAction.moveTo(x: location.x, duration: 0.2))
}
}
else{
main.run(SKAction.moveTo(x: location.x, duration: 0.2))
}
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
switch currentGameType {
case .easy:
enemy.run(SKAction.moveTo(x: ball.position.x, duration: 1.3))
break
case .medium:
enemy.run(SKAction.moveTo(x: ball.position.x, duration: 1.0))
break
case .hard:
enemy.run(SKAction.moveTo(x: ball.position.x, duration: 0.7))
break
case .player2:
break
}
if ball.position.y <= main.position.y - 30 {
addScore(playerWhoWon: enemy)
}
else if ball.position.y >= enemy.position.y + 30 {
addScore(playerWhoWon: main)
}
}
}
As far as I understand, SKScene conforms to UIResponder protocol since you're using touchesBegan and touchesMoved methods. The protocol also has touchesEnded(Set<UITouch>, with: UIEvent?) method.
Basucally you need to add something like:
override func touchesEnded(Set<UITouch>, with: UIEvent?)
{
let skView = self.view!
skView.presentScene(scene)
}
This method is invoked when user removes his finger from the screen after tapping the GameScene object.
You can use UITapGestureRecognizer as well. To do so however, you need to create an instance of it, declare your class as its delegate, add the view you want to tap into its collection and attach a method to it. I've never done this programmatically, only in the IB. The setup looks like so:
To add a tap gesture via code:
In your GameScene class:
weak var tapGesture : UITapGestureRecognizer!
override func didMoveToView(view: SKView) {
tapGesture = UITapGestureRecognizer(target:self,action:#selector(self.handleTap:)
view.addGestureRecognizer(tapGesture )
}
func handleTap(sender:UITapGestureRecognizer){
//restartLogic here
}
deinit{
view.removeGestureRecognizer(tapGesture)
}
Skip the gesture recognizer.
Simply call the restart code from touchesEnded if the game is not in progress.
I calculated the speed of UIGestureRecognizer and I want to use it as velocity of Physicsbody in SpriteKit.
How can I do that since Speed is calculated in touches began and touches ended method, while my Physicsbody is wrapped in swipeUp & swipeDown etc functions, which does not have access to the Speed variable?
Here is my code:
class GameScene: SKScene, SKPhysicsContactDelegate, SKViewDelegate, UIGestureRecognizerDelegate, UIViewControllerTransitioningDelegate {
var Kite = SKSpriteNode(imageNamed: "ASC_8025_large.jpg")
#objc let minusButton = SKSpriteNode(imageNamed: "minus.jpg")
#objc let plusButton = SKSpriteNode(imageNamed: "plus.png")
//override init(Kite: SKSpriteNode) {
//For long Long press gesture to work
let longPressGestureRecPlus = UILongPressGestureRecognizer()
let longPressGestureRecMinus = UILongPressGestureRecognizer(target: self, action: #selector(longPressed(press:)))
//First we declare all of our Gestures...
//swipes
let swipeRightRec = UISwipeGestureRecognizer()
let swipeLeftRec = UISwipeGestureRecognizer()
let swipeUpRec = UISwipeGestureRecognizer()
let swipeDownRec = UISwipeGestureRecognizer()
//rotate
let rotateRec = UIRotationGestureRecognizer()
//taps
let tapRec = UITapGestureRecognizer()
let tapRec2 = UITapGestureRecognizer()
override func didMove(to view: SKView) {
// Get label node from scene and store it for use later
Kite.size = CGSize(width: 40.0, height: 40.0)
Kite.position = CGPoint(x: frame.midX, y: frame.midY)
plusButton.size = CGSize(width: 30.0, height: 30.0)
plusButton.position = CGPoint(x: frame.midX, y: frame.minY + 20.0)
plusButton.name = "plusButton"
//plusButton.isUserInteractionEnabled = true
addChild(Kite)
addChild(plusButton)
swipeRightRec.addTarget(self, action: #selector(GameScene.swipedRight) )
swipeRightRec.direction = .right
self.view!.addGestureRecognizer(swipeRightRec)
swipeLeftRec.addTarget(self, action: #selector(GameScene.swipedLeft) )
swipeLeftRec.direction = .left
self.view!.addGestureRecognizer(swipeLeftRec)
swipeUpRec.addTarget(self, action: #selector(GameScene.swipedUp) )
swipeUpRec.direction = .up
self.view!.addGestureRecognizer(swipeUpRec)
swipeDownRec.addTarget(self, action: #selector(GameScene.swipedDown) )
swipeDownRec.direction = .down
self.view!.addGestureRecognizer(swipeDownRec)
//notice the function this calls has (_:) after it because we are passing in info about the gesture itself (the sender)
rotateRec.addTarget(self, action: #selector (GameScene.rotatedView (_:) ))
self.view!.addGestureRecognizer(rotateRec)
// again notice (_:), we'll need this to find out where the tap occurred.
tapRec.addTarget(self, action:#selector(GameScene.tappedView(_:) ))
tapRec.numberOfTouchesRequired = 1
tapRec.numberOfTapsRequired = 1
self.view!.addGestureRecognizer(tapRec)
tapRec2.addTarget(self, action:#selector(GameScene.tappedView2(_:) ))
tapRec2.numberOfTouchesRequired = 1
tapRec2.numberOfTapsRequired = 2 //2 taps instead of 1 this time
self.view!.addGestureRecognizer(tapRec2)
longPressGestureRecPlus.addTarget(self, action: #selector(longPressed(press:)))
longPressGestureRecPlus.minimumPressDuration = 2.0
self.view?.addGestureRecognizer(longPressGestureRecPlus)
}
//the functions that get called when swiping...
#objc func swipedRight() {
print("Right")
Kite.physicsBody?.velocity = CGVector(dx: 60, dy: 60)
//Tilts the Kite towards Right
let tiltRight = SKAction.rotate(toAngle: -1.00, duration: 0.1)
Kite.run(tiltRight)
}
#objc func swipedLeft() {
Kite.physicsBody?.velocity = CGVector(dx: -60, dy: 60)
//Tilts the Kite towards Right
let tiltLeft = SKAction.rotate(toAngle: 1.00, duration: 0.1)
Kite.run(tiltLeft)
print("Left")
}
#objc func swipedUp() {
Kite.physicsBody?.velocity = CGVector(dx: 60, dy: 60)
//Straightens the Kite
let straightens = SKAction.rotate(toAngle: 0.00, duration: 0.1)
Kite.run(straightens)
print("Up")
}
#objc func swipedDown() {
Kite.physicsBody?.velocity = CGVector(dx: 0, dy: -60)
print("Down")
}
// what gets called when there's a single tap...
//notice the sender is a parameter. This is why we added (_:) that part to the selector earlier
#objc func tappedView(_ sender:UITapGestureRecognizer) {
let point:CGPoint = sender.location(in: self.view)
print("Single tap")
print(point)
}
// what gets called when there's a double tap...
//notice the sender is a parameter. This is why we added (_:) that part to the selector earlier
#objc func tappedView2(_ sender:UITapGestureRecognizer) {
let point:CGPoint = sender.location(in: self.view)
print("Double tap")
print(point)
}
//what gets called when there's a rotation gesture
//notice the sender is a parameter. This is why we added (_:) that part to the selector earlier
#objc func rotatedView(_ sender:UIRotationGestureRecognizer) {
if (sender.state == .began) {
print("rotation began")
}
if (sender.state == .changed) {
print("rotation changed")
//you could easily make any sprite's rotation equal this amount like so...
//thePlayer.zRotation = -sender.rotation
//convert rotation to degrees...
let rotateAmount = Measurement(value: Double(sender.rotation), unit: UnitAngle.radians).converted(to: .degrees).value
print("\(rotateAmount) degreess" )
}
if (sender.state == .ended) {
print("rotation ended")
}
}
func removeAllGestures(){
//if you need to remove all gesture recognizers with Swift you can do this....
for gesture in (self.view?.gestureRecognizers)! {
self.view?.removeGestureRecognizer(gesture)
}
//this is good to do before a SKScene transitions to another SKScene.
}
func removeAGesture()
{
//To remove a single gesture you can use...
self.view?.removeGestureRecognizer(swipeUpRec)
}
//Fix the gesture recognizing the plusButton
#objc func longPressed(press: UILongPressGestureRecognizer) {
if press.state == .began {
isUserInteractionEnabled = true
let positionInScene = press.location(in: self.view)
let touchedNode = self.atPoint(positionInScene)
plusButton.name = "plusButton"
Kite.physicsBody?.velocity = CGVector(dx: 0, dy: 60.0*3)
print("Pressed on the screen")
if let name = touchedNode.name {
if name == "plusButton" {
print("LONG TAPPED")
}
}
}
}
var start: CGPoint?
var startTime: TimeInterval?
var taps = 0
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Began")
Kite.physicsBody = SKPhysicsBody(rectangleOf: Kite.size)
Kite.physicsBody?.affectedByGravity = false
//Kite.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
Kite.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 5))
plusButton.name = "plusButton"
plusButton.isUserInteractionEnabled = false
//We figure how to interact with BUTTON in Spritekit
let touch = touches.first
let positionInScene = touch!.location(in: self)
let touchedNode = self.atPoint(positionInScene)
if let name = touchedNode.name {
if name == "plusButton" {
taps += 1
Kite.size = CGSize(width: 200, height: 200)
print("tapped")
print("Taps", taps)
}
}
for touch in touches {
let location:CGPoint = touch.location(in: self.view!)
start = location
startTime = touch.timestamp
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Ended")
//Calculating Speed of the Gestures
for touch in touches {
let location:CGPoint = touch.location(in: self.view!)
var dx:CGFloat = location.x - start!.x;
var dy:CGFloat = location.y - start!.y;
var magnitude:CGFloat = sqrt(dx*dx+dy*dy)
//Calculate Time
var dt:CGFloat = CGFloat(touch.timestamp - startTime!)
//Speed = Distance / Time
var speed:CGFloat = magnitude / dt
var speedX:CGFloat = dx/dt
var speedY:CGFloat = dy/dt
print("SpeedY", speedX)
print("SpeedY", speedY)
}
}
so calculating the speed from a vector like dx and dy is
let speed = sqrtf(Float(pow(dx!, 2) + pow(dy!, 2)))
So if you want apply the speed as velocity to an object you need at least one of the directions.
let newDx = 0 // means in no x direction
// next you can change the formula to your new dy to match the given speed
let newDy = sqrtf(pow(speed, 2) - Float(pow(newDx, 2)))
yourNode.physicsBody?.velocity = CGVector(dx: newDx, dy: CGFloat(newDy))
Below I have part of the code for a simple game I am making. in the game, your finger around the screen a ball is underneath your finger. Then every 10 seconds a ball gets added in which follows your ball. I have an SKAaction which calls my add enemy function every 10 seconds which spawns the enemy. The issue is that I can't make the add enemy function update every frame because the SKAction won't let me call it if its updating every frame, so I'm not sure what to do in order for the the ball to be added in every 10 seconds and to have that ball track your location. Because currently it only tracks the initial location of the ball when it was added in. any help is appreciated, thank you.
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var me = SKSpriteNode()
override func didMove(to view: SKView) {
me = self.childNode(withName: "me") as! SKSpriteNode
let border = SKPhysicsBody (edgeLoopFrom: self.frame)
border.friction = 0
self.physicsBody = border
run(SKAction.repeatForever(SKAction.sequence([SKAction.run(createEnemy), SKAction.wait(forDuration: 10.0)])))
}
func createEnemy () {
let enemy = SKSpriteNode(imageNamed: "ball 1")
enemy.name = "enemy"
enemy.position = CGPoint(x:667, y: -200)
enemy.run(SKAction.moveTo(x: me.position.x, duration: 2))
enemy.run(SKAction.moveTo(y: me.position.y, duration: 2))
enemy.zPosition = +1
addChild(enemy)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let location = touch.location(in: self)
me.run(SKAction.moveTo(x: location.x, duration: 0))
me.run(SKAction.moveTo(y: location.y, duration: 0))
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let location = touch.location(in: self)
me.run(SKAction.moveTo(x: location.x, duration: 0))
me.run(SKAction.moveTo(y: location.y, duration: 0))
}
}
override func update(_ currentTime: TimeInterval) {
}
}
Keep a list of all of your currently alive enemies in a dictionary via their .name property. The values in this dictionary are a tuple. This tuple keeps track of the enemy's instance, and its target's instance.
I do this in your createEnemy via makeEnemyName and addEnemyToDict
during update give each enemy a new action to follow its target. This is done by iterating through the dictionary, then using each key's value to moveFollowerToTarget. This information is stored in the tuple type we created.
import SpriteKit
class GameScene22: SKScene {
// MARK: - My code:
// Tuple to keep track of enemy objects:
typealias FollowerAndTarget = (follower: SKSpriteNode, target: SKSpriteNode)
// [followerName: (followerSprite, targetSprite):
var spriteDictionary: [String: FollowerAndTarget] = [:]
// Give each enemy a unique name for the dictionary:
var enemyCounter = 0
// Assign to each enemy a unique name for the dictionary:
private func makeEnemyName() -> String {
enemyCounter += 1
return "enemy\(enemyCounter)"
}
private func addEnemyToDict(enemy: SKSpriteNode, target: SKSpriteNode) {
if let name = enemy.name { spriteDictionary[name] = (enemy, target) }
else { print("enemy not found") } // error!
}
private func removeEnemyFromDict(enemy: SKSpriteNode) {
if let name = enemy.name { spriteDictionary[name] = nil }
else { print("enemy not removed from dictionary!") } // error!
}
// Note, you did not set the enemy to have a constant speed, so the enemy will move at random speeds
// based on how far they are away from the target.
private func moveFollowerToTarget(_ sprites: FollowerAndTarget) {
let action = SKAction.move(to: sprites.target.position, duration: 2)
sprites.follower.run(action)
}
private func allEnemiesMoveToTarget() {
for sprites in spriteDictionary.values {
moveFollowerToTarget(sprites)
}
}
// Use this carefully if you are using phsyics later on:
func killEnemy(_ enemy: SKSpriteNode) {
// Remove this enemy from the update loop:
removeEnemyFromDict(enemy: enemy)
enemy.removeAllActions()
enemy.removeFromParent()
enemy.physicsBody = nil
}
// MARK: - Your code (mostly):
var me = SKSpriteNode()
override func didMove(to view: SKView) {
me = SKSpriteNode(color: .blue, size: CGSize(width: 50, height: 50))
me.name = "me"
addChild(me)
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let border = SKPhysicsBody (edgeLoopFrom: self.frame)
border.friction = 0
self.physicsBody = border
run(SKAction.repeatForever(SKAction.sequence([SKAction.run(createEnemy), SKAction.wait(forDuration: 10.0)])))
}
func createEnemy () {
let enemy = SKSpriteNode(color: .purple, size: CGSize(width: 30, height: 30))
enemy.name = makeEnemyName()
addEnemyToDict(enemy: enemy, target: me)
moveFollowerToTarget((follower: enemy, target: me))
enemy.zPosition = +1
addChild(enemy)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let location = touch.location(in: self)
me.run(SKAction.moveTo(x: location.x, duration: 0))
me.run(SKAction.moveTo(y: location.y, duration: 0))
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let location = touch.location(in: self)
me.run(SKAction.moveTo(x: location.x, duration: 0))
me.run(SKAction.moveTo(y: location.y, duration: 0))
allEnemiesMoveToTarget()
}
}
override func update(_ currentTime: TimeInterval) {
// Will iterate through dictonary and then call moveFollowerToTarget()
// thus giving each enemy a new movement action to follow.
allEnemiesMoveToTarget()
}
}
So I am currently working on a 2D endless runner written in Swift3 and using Spritekit for my nodes and scenes. I recently implemented some code to detect swipes in general, they will be below. So my question is: how can I detect a swipe action and check the direction of that swipe on a Spritekit Node? I realize that this has been asked before, but I cannot find a working solution since everything I come across seems to be for Swift and Swift2, not Swift3.
Here's my swipe detection code:
override func viewDidLoad() {
super.viewDidLoad()
let swipeLeft = UISwipeGestureRecognizer(target: self, action: #selector(self.respondToSwipeGesture))
swipeLeft.direction = UISwipeGestureRecognizerDirection.left
self.view.addGestureRecognizer(swipeLeft)
let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(self.respondToSwipeGesture))
swipeRight.direction = UISwipeGestureRecognizerDirection.right
self.view.addGestureRecognizer(swipeRight)
let swipeUp = UISwipeGestureRecognizer(target: self, action: #selector(self.respondToSwipeGesture))
swipeUp.direction = UISwipeGestureRecognizerDirection.up
self.view.addGestureRecognizer(swipeUp)
let swipeDown = UISwipeGestureRecognizer(target: self, action: #selector(self.respondToSwipeGesture))
swipeDown.direction = UISwipeGestureRecognizerDirection.down
self.view.addGestureRecognizer(swipeDown)
}
I have tested these with a function:
func respondToSwipeGesture(gesture: UIGestureRecognizer) {
if let swipeGesture = gesture as? UISwipeGestureRecognizer {
switch swipeGesture.direction {
case UISwipeGestureRecognizerDirection.right:
print("Swiped right")
case UISwipeGestureRecognizerDirection.down:
print("Swiped down")
case UISwipeGestureRecognizerDirection.left:
print("Swiped left")
case UISwipeGestureRecognizerDirection.up:
print("Swiped up")
default:
break
}
}
}
The swipes seem to work all right, as the print statements have been displaying the correct outputs in response to my swipes.
I have also added a delegate to avoid possible SIGABRT errors:
class GameViewController: UIViewController, UIGestureRecognizerDelegate
Please let me know if you'd like more information!
Edit:
Added code where I initialized my puzzle pieces
//The class
class ChoosePiecesClass: SKSpriteNode{
func moveWithCamera() {
self.position.x += 5;
}
//initialize a piece
private var RandomPiece1: ChoosePiecesClass?;
override func didMove(to view: SKView) {
RandomPiece1 = childNode(withName: "RandomPiece1") as? ChoosePiecesClass;
RandomPiece1?.position.x = 195;
RandomPiece1?.position.y = -251;
}
override func update(_ currentTime: TimeInterval) {
RandomPiece1?.moveWithCamera();
}
Edited to provide the error statement from console, upon clicking Start Game and triggering the fatalError code, causing the game to crash.
fatal error: init(coder:) has not been implemented: file /Users/ardemis/Documents/PuzzleRunner/PuzzleRunner 3 V3/PuzzleRunner/ChoosePieces.swift, line 33
It also displays the following
EXEC_BAT_INSTRUCTION(code = EXEC_1386_INVOP, subcode = 0x0)
on the same line as the fatalError declaration.
I wouldn't use the UIGestures if I was trying to track specific SpriteNodes. You can easily track your nodes in TouchesBegan and figure out which way a swipe direction occurs. This example has three sprites on the screen on will print/log whatever direction one of them gets swiped and will ignore all other swipes.
EDIT > I just created a subclass for my Box object (sorry I didn't use your naming, but it has the same functions). There are probably many ways of doing this, I chose to use create a protocol on the subclass and make the scene conform to the protocol. I moved all of the touch/swipe functionality into the sub class, and when it is done detecting the swipe it just calls the delegate and says which object has been swiped.
protocol BoxDelegate: NSObjectProtocol {
func boxSwiped(box: Box)
}
class Box: SKSpriteNode {
weak var boxDelegate: BoxDelegate!
private var moveAmtX: CGFloat = 0
private var moveAmtY: CGFloat = 0
private let minimum_detect_distance: CGFloat = 100
private var initialPosition: CGPoint = CGPoint.zero
private var initialTouch: CGPoint = CGPoint.zero
private var resettingSlider = false
override init(texture: SKTexture?, color: UIColor, size: CGSize) {
super.init(texture: texture, color: color, size: size)
self.isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func moveWithCamera() {
self.position.x += 5
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch! {
initialTouch = touch.location(in: self.scene!.view)
moveAmtY = 0
moveAmtX = 0
initialPosition = self.position
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch! {
let movingPoint: CGPoint = touch.location(in: self.scene!.view)
moveAmtX = movingPoint.x - initialTouch.x
moveAmtY = movingPoint.y - initialTouch.y
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
var direction = ""
if fabs(moveAmtX) > minimum_detect_distance {
//must be moving side to side
if moveAmtX < 0 {
direction = "left"
}
else {
direction = "right"
}
}
else if fabs(moveAmtY) > minimum_detect_distance {
//must be moving up and down
if moveAmtY < 0 {
direction = "up"
}
else {
direction = "down"
}
}
print("object \(self.name!) swiped " + direction)
self.boxDelegate.boxSwiped(box: self)
}
}
In GameScene.sks
Make sure to make GameScene conform to BoxDelegate by adding the protocol after the declaration
class GameScene: SKScene, BoxDelegate {
var box = Box()
var box2 = Box()
var box3 = Box()
override func didMove(to view: SKView) {
box = Box(color: .white, size: CGSize(width: 200, height: 200))
box.zPosition = 1
box.position = CGPoint(x: 0 - 900, y: 0)
box.name = "white box"
box.boxDelegate = self
addChild(box)
box2 = Box(color: .blue, size: CGSize(width: 200, height: 200))
box2.zPosition = 1
box2.name = "blue box"
box2.position = CGPoint(x: 0 - 600, y: 0)
box2.boxDelegate = self
addChild(box2)
box3 = Box(color: .red, size: CGSize(width: 200, height: 200))
box3.zPosition = 1
box3.name = "red box"
box3.position = CGPoint(x: -300, y: 0)
box3.boxDelegate = self
addChild(box3)
}
func boxSwiped(box: Box) {
currentObject = box
print("currentObject \(currentObject)")
}
override func update(_ currentTime: TimeInterval) {
box.moveWithCamera()
box2.moveWithCamera()
box3.moveWithCamera()
}
}