I am making a game with Sprite Kit where the user has tap balls that pass through the screen. The balls are spawned every 1 second. However, if two balls have spawned and the user taps the first ball only the second (and any that have spawned after that) will be removed/recorded and not the one the user actually tapped.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let node = self.nodes(at: location).first
if node?.name == "BALL" {
currentScore += ballValue
player?.removeFromParent()
}
else {
gameOver()
}
}
}
override func didMove(to view: SKView) {
setupTracks()
createHUD()
self.run(SKAction.repeatForever(SKAction.sequence([SKAction.run {
self.createBall(forTrack: self.track)
}, SKAction.wait(forDuration: 2)])))
}
func createBall(forTrack track: Int) {
setLevel()
player?.name = "BALL"
player?.size = CGSize(width: 100, height: 100)
ballValue = 1
let ballPosition = trackArray?[track].position
player?.position = CGPoint(x: (ballPosition?.x)!, y: (ballPosition?.y)!)
player?.position.y = (ballPosition?.y)!
player?.zPosition = 1
if ballDirection == "right" {
player?.position.x = 0
moveRight()
}
else {
player?.position.x = (self.view?.frame.size.height)!
moveLeft()
}
}
I’m pretty sure it’s because you do:
player?.removeFromParent()
no matter which sprite is touched, but player is always the last sprite spawned. You’ve already assigned the node that was touched to node, so I think you need to do:
node.removeFromParent()
instead.
Related
I have multiple SpriteNodes loaded in my GameScene at random locations, but it is actually the same SpriteNode added multiple times. I have a function in touchesEnded, that removes a SpriteNode once the touch is released on the same location as the SpriteNode. This only works for the initial SpriteNode (the first SpriteNode that was added) but does not work for all the other SpriteNodes.
I tried to turn the code "if object.contains(location)" into a while loop, so that it would repeat for ever touch. That didn't work either.
var object = SKSpriteNode()
var objectCount = 0
func spawnObject() {
object = SKSpriteNode(imageNamed: "image")
object.position = CGPoint(x: randomX, y: randomY)
objectCount = objectCount + 1
self.addChild(object)
}
while objectCount < 10 {
spawnObject()
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
let location = t.location(in: self)
if object.contains(location) {
object.removeFromParent()
}
}
}
I expected that whenever I touch an object it would disappear. But that only happens with one object, and it works perfectly fine and as expected with the first object, but the other nine objects show no reaction.
Ok this is the basics of using an array to track the spawned objects so that you can check them all:
var objectList: [SKSpriteNode] = [] // Create an empty array
func spawnObject() {
let object = SKSpriteNode(imageNamed: "image")
object.position = CGPoint(x: randomX, y: randomY)
self.addChild(object)
objectList.append(object) // Add this object to our object array
}
while objectList.count < 10 {
spawnObject()
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
let location = t.location(in: self)
// Check all objects in the array
for object in objectList {
if object.contains(location) {
object.removeFromParent()
}
}
// Now remove those items from our array
objectList.removeAll { (object) -> Bool in
object.contains(location)
}
}
}
Note: that's not the best way to do this for especially from a performance point of view but it's enough to get the idea across.
I am working on a simple runner game, I have just 3 shapenodes a player node which is just a shapenode (redbox, 30 x 30) with left/right buttons basically two shapenodes.
I have managed to set flags and increment player node's position X and move it left and right with buttons in touchesBegan method and stop the movement in touchesEnded method everything works fine.
The problem is that if I touch lets say right button, the player moves to the right as expected. But if I touch and move my finger out of the button boundaries in any direction, the player keeps moving constantly as long as I touch that button again then it stops. Otherwise it keeps moving and the other button does not stop it as well.
I used touchesMoved method to stop the movement of the player when I move my finger but this does not fix the issue since touchesMoved method triggers with the slightest movement of the touch even when my finger slightly shakes.
I want the touchesMoved method called when my finger is moved off the button, not on the button.
How can I stop the movement of a sprite when touched and moved out of the node ( button ) boundaries?
You have 3 touch events:
touchesBegan; check if user tap is on one the move buttons (if yes - move player, if not - return)
touchesMoved; check if the user tap is still inside the boundaries of the move button (if yes - continue moving player, if not - stop movement)
touchesEnded; stop player movement
You can solve your problem by checking if the user finger is over the button area.
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
{
// nothing here
let touch = touches.first
let position_in_scene = touch!.location(in: self)
check_button(position: position_in_scene)
}
func check_button(position : CGPoint)
{
if right_button.contains(position)
{
// the tap was inside button boundaries
player.move_left()
}
else
{
// the tap was outside button boundaries
player.stop()
}
}
I have this code running :
class GameScene: SKScene {
var player = SKSpriteNode()
var left = SKSpriteNode()
var right = SKSpriteNode()
var jumpbtn = SKSpriteNode()
var leftbtnPressed = false
var rightbtnPressed = false
var jump = false
var hSpeed: CGFloat = 2.0
override func didMove(to view: SKView) {
let border = SKPhysicsBody(edgeLoopFrom: self.frame)
border.friction = 0
self.physicsBody = border
jumpbtn = self.childNode(withName: "jumpbtn") as! SKSpriteNode
player = self.childNode(withName: "r1") as! SKSpriteNode
left = self.childNode(withName: "leftbtn") as! SKSpriteNode
right = childNode(withName: "rightbtn") as! SKSpriteNode
}//didmove to view
func moveRight(){
player.position = CGPoint(x: player.position.x + hSpeed, y: player.position.y)
}
func moveLeft (){
player.position = CGPoint(x: player.position.x - hSpeed, y: player.position.y)
}
func movement (){
if rightbtnPressed == true {
moveRight()
}
if leftbtnPressed == true {
moveLeft()
}
}//movement
func CheckButton(position : CGPoint) {
if right.contains(position){
rightbtnPressed = true
}
if left.contains(position) {
leftbtnPressed = true
}
else {
leftbtnPressed = false
rightbtnPressed = false
}
}// checkbutton
func jumping (){
if jump == true {
player.physicsBody?.applyImpulse(CGVector(dx:0, dy: 5.0))
}
}// jumping
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let positionINScene = touch.location(in: self)
let touchednode = self.atPoint(positionINScene).name
if touchednode == "rightbtn"{
rightbtnPressed = true
}
if touchednode == "leftbtn"{
leftbtnPressed = true
}
if touchednode == "jumpbtn"{
jump = true
jumping()
}
}
}//touchesbegan
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let positionInScene = touch?.location(in: self)
CheckButton(position: positionInScene!)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let positionINScene = touch.location(in: self)
let touchednode = self.atPoint(positionINScene)
if touchednode.name == "rightbtn"{
rightbtnPressed = false
}
if touchednode.name == "leftbtn"{
leftbtnPressed = false
}
if touchednode.name == "jumpbtn" {
jump = false
}
}
}//touchesEnded
override func update(_ currentTime: TimeInterval) {
movement()
}
When i touch either left or right button the player starts moving as expected but the problem is while the player is moving let say to the right if i touch the jump button the player pauses moving where it should be moving and jumping
in simple words i can not move and jump at the same time
I have been developing a simple game in Swift in order to increase my exposure towards the language syntax and concepts. I am currently facing a problem in which the touches are not detected in the game application. The application uses action for a key method to start the game but unfortunately, when I tap on the screen the SKSpriteNodes are not spawning from the top of the screen (at-least visually). I have entered a few print states to see if the code has reached certain methods but it looks like the cause of the problem is within the touch method as the print line is not executed at the touch method. Can someone help me identify what is my mistake and how to prevent this?
For reference purposes I have included the class:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
private var counter: Int = 0
private var level: Int = 0
private var debug: SKLabelNode?
// Here we set initial values of counter and level. Debug label is created here as well.
override func didMove(to view: SKView) {
counter = 0
level = 1
backgroundColor = SKColor.gray
debug = SKLabelNode(fontNamed: "ArialMT")
debug?.fontColor = SKColor.purple
debug?.fontSize = 30.0
debug?.position = CGPoint(x: frame.midX, y: frame.midY)
debug?.text = "Counter : [ \(counter) ], Level [ \(level) ]"
if let aDebug = debug {
addChild(aDebug)
}
print(action(forKey: "counting") == nil)
}
//Method to start a timer. SKAction is used here to track a time passed and to maintain the current level
func startTimer() {
print("TIMER STARTED...")
weak var weakSelf: GameScene? = self
//make a weak reference to scene to avoid retain cycle
let block = SKAction.run({
weakSelf?.counter = (weakSelf?.counter ?? 0) + 1
//Maintaining level
if (weakSelf?.counter ?? 0) < 5 {
//level 1
weakSelf?.level = 1
} else if (weakSelf?.counter ?? 0) >= 5 && (weakSelf?.counter ?? 0) < 10 {
//level 2
weakSelf?.level = 2
} else {
//level 3
weakSelf?.level = 3
}
weakSelf?.debug?.text = "Counter : [ \(Int(weakSelf?.counter ?? 0)) ], Level [ \(Int(weakSelf?.level ?? 0)) ]"
})
run(SKAction.repeatForever(SKAction.sequence([SKAction.wait(forDuration: 1), block])), withKey: "counting")
}
//Method for stopping the timer and reset everything to the default state.
func stopTimer() {
print("TIMER STOPPED.")
if action(forKey: "counting") != nil {
removeAction(forKey: "counting")
}
counter = Int(0.0)
level = 1
debug?.text = "Counter : [ \(counter) ], Level [ \(level) ]"
}
//Get current speed based on time passed (based on counter variable)
func getCurrentSpeed() -> CGFloat {
if counter < 5 {
//level 1
return 1.0
} else if counter >= 5 && counter < 10 {
//level 2
return 2.0
} else {
//level 3
return 3.0
}
}
//Method which stops generating stones, called in touchesBegan
func stopGeneratingStones() {
print("STOPPED GENERATING STONES...")
if action(forKey: "spawning") != nil {
removeAction(forKey: "spawning")
}
}
func randomFloatBetween(_ smallNumber: CGFloat, and bigNumber: CGFloat) -> CGFloat {
let diff: CGFloat = bigNumber - smallNumber
//return (CGFloat(arc4random_uniform(UInt32(CGFloat(RAND_MAX) + 1 / CGFloat(RAND_MAX) * diff )))) + smallNumber
return CGFloat(arc4random() % (UInt32(RAND_MAX) + 1)) / CGFloat(RAND_MAX) * diff + smallNumber
}
//Method for generating stones, you run this method when you want to start spawning nodes (eg. didMoveToView or when some button is clicked)
func generateStones() {
print("GENERATING STONES...")
let delay = SKAction.wait(forDuration: 2, withRange: 0.5)
//randomizing delay time
weak var weakSelf: GameScene? = self
//make a weak reference to scene to avoid retain cycle
let block = SKAction.run({
let stone: SKSpriteNode? = weakSelf?.spawnStone(withSpeed: weakSelf?.getCurrentSpeed() ?? 0.0)
stone?.zPosition = 20
if let aStone = stone {
weakSelf?.addChild(aStone)
}
})
run(SKAction.repeatForever(SKAction.sequence([delay, block])), withKey: "spawning")
}
//Returns stone with moving action added. Inside, you set standard things, like size, texture, physics body, name and position of a stone
func spawnStone(withSpeed stoneSpeed: CGFloat) -> SKSpriteNode? {
print("SPAWNNING STONES...")
let stoneSize = CGSize(width: 30, height: 30) //size of shape.
//you can randomize size here
let stonePosition = CGPoint(x: randomFloatBetween(0.0, and: frame.size.width), y: frame.maxY) //initial position
//you can randomize position here
let stone = SKSpriteNode(color: SKColor.green, size: stoneSize) //setting size and color.
stone.name = "stone" //named shape so we can check collision.
//this helps if you want to enumerate all stones by name later on in your game
stone.position = stonePosition //set position.
let move = SKAction.moveBy(x: 0, y: -200, duration: 3.25)
//one way to change speed
move.speed = stoneSpeed
let moveAndRemove = SKAction.sequence([move, SKAction.removeFromParent()])
stone.run(moveAndRemove, withKey: "moving")
//access this key if you want to stop movement
return stone
}
func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
/* Called when a touch begins */
//just a simple way to start and stop a game
/**
TOUCH METHOD NOT WORKING.
*/
if action(forKey: "counting") == nil {
print("HERE")
startTimer()
generateStones()
} else {
print("OR HERE")
stopTimer()
stopGeneratingStones()
}
}
}
I have fixed the problem to my own question after some debugging. I have realised that the touchesBegan method does not use override the super, hence the application is mistaking it for a local method. To overcome this problem simply adding the override will fix problem.
change this:
func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
/* Called when a touch begins */
//just a simple way to start and stop a game
/**
TOUCH METHOD NOT WORKING.
*/
if action(forKey: "counting") == nil {
print("HERE")
startTimer()
generateStones()
} else {
print("OR HERE")
stopTimer()
stopGeneratingStones()
}
}
to this:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
/* Called when a touch begins */
//just a simple way to start and stop a game
/**
TOUCH METHOD NOT WORKING.
*/
if action(forKey: "counting") == nil {
print("HERE")
startTimer()
generateStones()
} else {
print("OR HERE")
stopTimer()
stopGeneratingStones()
}
}
I'm looking to have a scrollable background in Sprite Kit. I've had a go with some of the other solutions available online, but they were implementing infinite scrolling backgrounds, and I haven't been able to adapt the code to my needs.
Here is some sample code which I've got to try and get the background moving (without the detection of reaching the end of the background) - but it's very choppy and not smooth at all.
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touchLocation = touches.first?.location(in: self), let node = nodes(at: touchLocation).first {
if node.name != nil {
if node.name == "background" {
background.position = touchLocation
}
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touchLocation = touches.first?.location(in: self), let node = nodes(at: touchLocation).first {
if node.name != nil {
if node.name == "background" {
background.position = touchLocation
}
}
}
}
The image below demonstrates what I'm trying to achieve - I want the code to detect when you've reached the end of the background, and to prevent you from moving it any further.
So, taking #KnightOfDragon's comment into account about needing to set maximum and minimum X coordinate values for the background, I was able to solve my own question. I already had swipe left/right recognisers in my code (for another purpose in my game), and I was able to reuse these to fulfil my needs. Code is as follows:
In didMove():
swipeRightRec.addTarget(self, action: #selector(self.swipedRight) )
swipeRightRec.direction = .right
self.view!.addGestureRecognizer(swipeRightRec)
swipeLeftRec.addTarget(self, action: #selector(self.swipedLeft) )
swipeLeftRec.direction = .left
self.view!.addGestureRecognizer(swipeLeftRec)
And then these functions:
#objc func swipedRight() {
if background.position.x + 250 > maxBackgroundX {
let moveAction = SKAction.moveTo(x: maxBackgroundX, duration: 0.3)
background.run(moveAction)
} else {
let moveAction = SKAction.moveTo(x: background.position.x + 250, duration: 0.3)
background.run(moveAction)
}
}
#objc func swipedLeft() {
if background.position.x - 250 < minBackgroundX {
let moveAction = SKAction.moveTo(x: minBackgroundX, duration: 0.3)
background.run(moveAction)
} else {
let moveAction = SKAction.moveTo(x: background.position.x - 250, duration: 0.3)
background.run(moveAction)
}
}
Yes this means that the background moves a set amount each time you swipe, no matter how big the swipe is, but it is exactly what I required for my game. I hope this helps someone else who needs the same thing!
I am making a game where as the main sprite/player moves constantly, he/she needs to jump through barriers.
I need help with how to set a constant velocity for my moving sprite. When I try and do this in the SpriteKit update function, I can’t apply an impulse to jump whenever the user taps the screen.
Here is my code. I commented the places where I am having trouble:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
if (gameStarted == false) {
gameStarted = true
mainSprite.physicsBody?.affectedByGravity = true
mainSprite.physicsBody?.allowsRotation = true
let spawn = SKAction.runBlock({
() in
self.createWalls()
})
let delay = SKAction.waitForDuration(1.5)
let spawnDelay = SKAction.sequence([spawn, delay])
let spawnDelayForever = SKAction.repeatActionForever(spawnDelay)
self.runAction(spawnDelayForever)
let distance = CGFloat(self.frame.height + wallPair.frame.height)
let movePipes = SKAction.moveByX(0, y: -distance - 50, duration: NSTimeInterval(0.009 * distance)) // Speed up pipes
let removePipes = SKAction.removeFromParent()
moveAndRemove = SKAction.sequence([movePipes, removePipes])
} else {
if died == true {
}
else {
mainSprite.physicsBody?.applyImpulse(CGVectorMake(0, 20)) // TRYING TO APPLY AN IMPULSE TO MY SPRITE SO IT CAN JUMP AS IT MOVES
}
}
for touch in touches {
let location = touch.locationInNode(self)
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
updateSpritePosition()
mainSprite.physicsBody?.velocity = CGVectorMake(400, 0) // SETS A CONSTANT VELOCITY, HOWEVER I CAN NOT APPLY AN IMPULSE.
}
The problem is that you're overwriting the velocity in the update method. So even though you added an impulse, it gets immediately overwritten by code in the update. Try overwriting just the dx part of the velocity.
override func update(currentTime: CFTimeInterval) {
updateSpritePosition()
mainSprite.physicsBody?.velocity = CGVectorMake(400, mainSprite.physicsBody?.velocity.dy)
}