I have the following code in a pong paddle game in the GameViewController:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
main.run(SKAction.moveTo(x: location.x,duration: 4), completion: {
print( "COMPLETED TOUCH BEGAN")
})
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
main.run(SKAction.moveTo(x: location.x,duration: 1), completion: {
print( "COMPLETED TOUCH MOVED")
})
}
}
For some reason, when the duration of touchesBegan is greater than the duration of touchesMoved, my player will complete the touchesMoved actions first (even though touchesBegan had to come first), and then jump to a different x-position and then move with duration 4 to complete the touchesBegan. It's almost like the player reaches the position designated in touchesMoved (which should be almost exactly identical to the position in touchesBegan), goes back a somewhat arbitrary amount, and then moves to touchesBegan position. Is there a reason why it's jumping back and then moving like so?
My hypothesis is that for some reason, the touchesMoved actions are ran concurrently, and when they finish, the position jumps back to where it is slowly moving in touchesBegan and completes that movement to completion - almost like there are two objects moving at different rates with the slower one coming up after the faster one finishes. Is this at all correct? If so, why is it like this?
Edit:
I see that when I make the paddle move to one side of the screen, then tap on the opposite end of the screen and slide over, it will move to the final position that I released at, and then jump and move to where I first pressed. It's almost like the first action is playing in the background. The strange thing is if I hold my finger down (keeping the touchesMoved method called) until the movement in the touchesBegan finishes (so here it's 4 seconds), it doesn't jump to the other side of the screen!
In summary, I have multiple SKActions running with different durations, and from my observation, the priority is over the most recent action added to the action array. But instead of playing multiple actions over each other, the object jumps to different places on the screen to complete the final action.
If you set up a second SKAction moving the same node, the second one doesn't cancel out the first. Rather, the position of the node is calculated after every frame based on all SKActions running. If the SKActions have the same duration and run at the same time, the last SKAction to run will always win out. For example:
// Same duration, node finishes at 100
main.run(SKAction.moveTo(x: -100, duration: 2)) //first to run
main.run(SKAction.moveTo(x: 100, duration: 2)) //second to run
If the first SKAction has a greater duration however, after the second SKAction has finished the position will only be calculated based on the first SKAction, for example:
//First SKAction's duration is longer, node finishes at -100
main.run(SKAction.moveTo(x: -100, duration: 3))
main.run(SKAction.moveTo(x: 100, duration: 2))
That's the reason why you're seeing the 'jump'.
SKAction documentation:
A node can run multiple actions simultaneously, even if those actions
were executed at different times. The scene keeps track of how far
each action is from completing and computes the effect that the action
has on the node. For example, if you run two actions that move the
same node, both actions apply changes to every frame. If the move
actions were in equal and opposite directions, the node would remain
stationary.
In your code touchesMoved can be called many times, and each time it is adding another SKAction to the node. I recommend you remove all actions on the main node before running another SKAction:
main.removeAllActions()
So in the end it will be:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
main.removeAllActions()
main.run(SKAction.moveTo(x: location.x,duration: 4), completion: {
print( "COMPLETED TOUCH BEGAN")
})
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
main.removeAllActions()
main.run(SKAction.moveTo(x: location.x,duration: 1), completion: {
print( "COMPLETED TOUCH MOVED")
})
}
}
Related
I am currently working with SpriteKit and I want to run a code block in the update-loop as long as the user is touching a certain SpriteNode. I tried to achieve this by using a Boolean, that gets set to true, when the touchesBegan() method recognises a touch on this node and gets set to false, when the touchesEnded() method recognises a touch ending on this node. However, when the user touches the node and then moves his finger outside of the boundaries, the touchesEnded() method does not recognise this.
Is there a simple way to check if a touch, that began in this node, but then moved outside of it, still exists? Or can I check in general if a UITouch instance still exists?
It's not clear what behavior you want, but in general you probably want to use touch identity to track what's happening.
For example, if you're handling touches in the scene containing the node, and if the desire is simply to have the action start when the node is touched and stop when that touch ends, then something like:
// Whatever touch initiated the action
var activeTouch: UITouch?
// Checked by update loop
var doingSomething = false
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
// Ignore new touches if one is already active
guard activeTouch == nil else { return }
let location = touch.location(in: self)
let touchedNodes = self.nodes(at: location)
for node in touchedNodes {
if <some test for the interesting node> {
activeTouch = touch
doingSomething = true
}
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
if touch == activeTouch {
// Finished
activeTouch = nil
doingSomething = false
}
}
}
If you also want the action to stop if the user moves their finger off the node and restart if they move back on, then also override touchesMoved, e.g.:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
if touch == activeTouch {
let location = touch.location(in: self)
let touchedNodes = self.nodes(at: location)
// Assume they moved off the node
doingSomething = false
for node in touchedNodes {
if <some test for the interesting node> {
// Nope, still touching it
doingSomething = true
}
}
}
}
}
(You should also handle touchesCancelled in some appropriate way, maybe stopping the action and clearing activeTouch as in touchesEnded)
Perhaps you have some other behavior in mind for the case when there are multiple touches active. You might need to keep track of all the active touches and their status of being on or off the node, and then set doingSomething = true if any active touch is on the node. Or maybe you want touchesMoved to discard an active touch as soon as it moves off the node, so moving that touch back on again won't reactivate.
The main point is that keeping track of touch identity gives you a lot of flexibility, but you have to decide on how you want the game to react.
I have a sprite node on the screen, that the player is able to move left and right. I know how to make it move left and right using touchesMoved, however, if the player touches a location the node snaps to that location.
For example, if the node was on the left side of the screen, and the player touched the right side of the screen, the node would immediately move to the touch location. I don't want this behavior. I only want to the node to move when the player is moving their finger left or right, not when they touch a location. Below is the code I currently use. There isn't any code that would affect the playerNode x position in the touchesBegan or touchesEnded function.
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches
{
if game.state == .game
{
let position = touch.location(in: playableArea)
game.playerNode.position.x = position.x
}
}
}
How can I stop this behavior from happening, and only have the node move when the players finger is moving left or right?
I ended up resolving my issue. I knew that I had to essentially move the node relative to the touch location, but I wasn't sure how to do that. This is how I got it working.
First I created a variable that would be equal to the players touch location. This variable gets set in touchesBegan.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
if game.state == .game
{
let position = touch.location(in: playableArea)
playerTouchPosition = position
}
}
I use this variable as the starting point of the touch. When the player moves their finger, it calculates how much the player moved their finger relative to the starting point. For example, if the player touched at the X value of 100, and moved their finger to the X value of 120, it would calculate that the player moved their finger by 20. After finding that value, I set the playerTouchPosition to the position of touchesMoved. This is to make the next move be relative to the previous touch. Then I move the players node by the amount the player moved their finger.
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches
{
if game.state == .game
{
let position = touch.location(in: playableArea)
let newX : CGFloat!
if position.x > playerTouchPosition.x
{
newX = position.x - playerTouchPosition.x
}else
{
newX = -(playerTouchPosition.x - position.x)
}
playerTouchPosition = position
game.playerNode.position.x = game.playerNode.position.x + newX
}
}
}
This stopped the players node from immediately moving to where ever the touch locations X value was.
I have a few button with amorphous look. (the Rect of the buttons are intersecting) First I evaluated the BeganTouch in the GameScene. Then I get several touches.
Since I have in my buttons still child nodes, they have swallowed the touchs. Ok, I have made with the help of here a subclass of the SpriteNodes and processed the touch inside the subclass. Now I have the problem that the first button does not pass the touch to the underlying sprites.
But now I would like top ignore the transparent areas of the buttons, how can I do that?
I have read that one can work with physicsbody, I have tried, gravitiy = 0 but since I move and scale the buttons via actions there were violent effects.
Why can I check for the alpha in the touch location and pass the touch to the next sprite.
by the way: how can I get a reference to the view? to get the global loc.
let loc = touch.location(in: view)
with the global touch location I could check all sprites under this point for the alpha!
you can try passing the touch to it's parent (presumably the scene) from your subclass.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch! {
//handle whatever code you want your subclass to do
...
//pass the touch event to the parent
super.touchesBegan(touches, with: event)
}
}
and then in your scene, cycle through the your buttons (in this example I have the buttons in a button array)
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch! {
let touchLocation = touch.location(in: self)
for button in buttons {
if button.contains(touchLocation) {
//handle all other buttons
...
}
}
}
}
although this seems a little redundant to do the touches in two different locations. #Knight0fDragon is correct, it seems odd to have a button have transparent areas.
ok, it's simple, all buttons are from the same subclass. this subclass delegates to an method in the GameScene. here I can check with
allNodes = nodes(at: globalLocation)
now I can check for the name of the node, calculate the point of the pixel inside each node and get the alpha value.
thanxs all
I have looked around at many articles, but none of them seem to make sense or are relevant to my issue. I want to give the user a set time to press a node. If they succeed in pressing the node within the set time the timer should reset, if they fail to press the node within the set time it will be game over.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first!
if square.contains(touch.location(in: self)) {
moveSquare()
GameScene.score+=1
scoreLabel.text = "\(GameScene.score)"
}
} else {
gameOverScene()
}
}
so my question is how do I set the timer up to achieve those requirements, and where do I put the code?
Use SKActions instead of timers:
let countdown = SKAction.sequence([SKAction.wait(forDuration: 5),
SKAction.perform(#selector(gameOver), onTarget: Self)])
run(countdown, withKey: "gameOverTimer")
(this assumes that the function gameOver is the one you want to call if the correct button is not touched in time)
and in touchesBegan, if the correct node is touched:
removeAction(forKey: "gameOverTimer")
In a game I'm building a user is able to move the camera around by dragging around the screen similar to a scrollview. This is all handled in the main scene file by the touchesBegan and touchesMoved method. However I also want the ability for a user to drag from an SKSpriteNode (not actually moving it) to another SKSpriteNode - I then perform an action based on the 2 nodes. I can do both of these fine separately. But when a user is dragging from one node to another, I don't want the camera to pan.
Again I've managed to resolve this using a boolean flag that's set on touchesBegan if they have touched a node. However the screen then doesn't pan if a user starts touching on one of the sprites but finishes off of one e.g they actually quickly performed a pan that happened to start on a node. Does anyone have any good solutions for this?
One thought I had was to store all the touch events in touchesMoved, and then loop through them in touchesEnded performing the same movement logic, provided they didn't start and end on a sprite. e.g
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
movingTouches.append(touch)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if startedOnSprite && !endedOnSprite || !(startedOnSprite && endedOnSprite) {
let location = movingTouches.last().location(in: self)
let previousLocation = movingTouches.first().location(in: self)
let deltaY = location.y - previousLocation.y
let deltax = location.x - previousLocation.x
gameCamera?.position.y -= deltaY
view.frame.size.height
gameCamera?.position.y = (gameCamera?.position.y)! > viewHeight/2 ? viewHeight/2 : (gameCamera?.position.y)!
gameCamera?.position.y = (gameCamera?.position.y)! < -(viewHeight/2) ? -(viewHeight/2) : (gameCamera?.position.y)!
gameCamera?.position.x -= deltax
gameCamera?.position.x = (gameCamera?.position.x)! < -(viewWidth/2) ? -(viewWidth/2) : (gameCamera?.position.x)!
gameCamera?.position.x = (gameCamera?.position.x)! > (viewWidth/2) ? (viewWidth/2) : (gameCamera?.position.x)!
}
movingTouches = [UITouch]()
endedOnSprite = false
startedOnSprite = false
}
To complicate matters I also want the Sprites to have a tap event on them as well as dragging. I'm struggling to find a good way to do all this in SpriteKit
Unfortunately the above isn't very smooth at all, and in think it appears to not even scroll the distance I would expect either (the exact same code does scroll correctly if I'm not bothered about whether or not it starts on a Sprite)
So to be clear, I want scrolling behaviour, provided a user isn't actually using a finger to 'draw' from one sprite to another
Gesture recognisors on your scene would do what you need, keeping taps and swipes separated.