Filtering Fast User Touch Input - swift

I am coding an app in sprite-kit and swift where when you touch the screen a sprite(the player) throws a projectile at another sprite moving towards it. If the player hits the other sprite then the projectile and the sprite disappear. A problem with the game is that if the player rapidly touches the screen he can easily run up his score in the game. How can I make the code only recognize that the screen is being touched every let's say .3 seconds?

In SpriteKit/GameplayKit games, most of your code is running inside a game loop where you are constantly being passed the current time. That's what this function in an SKScene is:
override public func update(_ currentTime: TimeInterval) {
}
In here it's common to keep track of time and enable/disable things. To keep it simple:
Add the following vars
var firingEnabled = true
var enableFiringAtTime: TimeInterval = 0
var currentTime: TimeInterval = 0
When they fire, add this code
if firingEnabled {
firingEnabled = false
enableFiringAtTime = self.currentTime + 0.3
// your fire code here
}
And in the update override
self.currentTime = currentTime
if currentTime > enableFiringAtTime {
firingEnabled = true
}

Related

SpriteKit - change animation speed dynamically

I am new to the SpriteKit framework and cannot figure out how to do the following:
In the middle of the scene I have a sprite to which I apply an .animateWithTextures SKAction. Now I simply want to increase the speed of that animation, or decrease its duration for the same effect.
I made the SKAction a property of my GameScene class so I can access its properties from everywhere, but neither changing its speed nor its duration affects the animation.
I read several threads, the closest to my problem being this one:
How to change duration of executed SpriteKit action
which is from seven years ago, and none of the answers is working for me.
Here is basically what I do:
class GameScene: SKScene {
var thePlayer: SKSpriteNode = SKSpriteNode()
var playerAnimation: SKAction?
...
...
func setInitialAnimation() {
let walkAnimation = SKAction.repeatForever(SKAction(named: "PlayerWalk", duration: 1)!)
self.playerAnimation = walkAnimation
self.thePlayer.run(walkAnimation)
}
func changeAnimationDuration(to duration: CGFloat) {
self.playerAnimation?.duration = duration
}
}
The animation starts when the setInitialAnimation method is called, but changing the action's duration has no effect. The only thing that works so far is removing the old action from the player sprite and running a new one.
Should it not be possible to change the properties of a running SKAction or is that some kind of fire and forget mechanism?
Play around with the timePerFrame to get the correct animation speed.
var frames = [SKTexture]()
for i in 1...10 {
frames.append(SKTexture(imageNamed: "YourTextureAnimationFrame\(i)"))
}
let animate = SKAction.animate(with: frames, timePerFrame: 0.025)
//timePerFrame setting the speed of the animation.
node.run(animate)

Keep particles in scene paused after app becomes active

Let me preface this question by saying I know it has been asked a few times, but the most upvoted post here was from back in 2014 and the top answer doesn't seem to work anymore.
Issue
In my game, I have all my 'moving' nodes as children of pauseNode. When the pauses button is pressed or when the player leaves the app, pauseScene() is called:
func pauseScene() {
self.node.speed = 0
self.physicsWorld.speed = 0
self.node.isPaused = true
}
This pauses all sprite nodes and emitter nodes. This keeps the sprite nodes paused if the app transitions from background to foreground state, but the particle emitters seem to resume animating.
Current solution
I solved this issue by triggering pauseScene() after a small delay when the app became active.
override func didMove(to view: SKView) {
NotificationCenter.default.addObserver(self, selector: #selector(GameScene.applicationDidBecomeActive(notification:)), name: NSNotification.Name.UIApplicationDidBecomeActive, object: app)
}
#objc func applicationDidBecomeActive(notification: NSNotification) {
NSDelay(0.01) {
pauseScene()
}
}
func NSDelay(_ delay:Double, closure:#escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
Goal
While this solution pauses the particles, it is not perfect because they are visible moving for the split second it takes to pause them again (this is the shortest delay that actually re-pauses them).
So, after reading the post I linked above, I tried to override applicationDidBecomeActive() in an SKView subclass as was suggested, but I couldn't get the method to be called. I want to prevent these particles from being unpaused in the first place, if possible. Thanks
So, it's been a little over a day and I've found a better solution. A comment in this post suggested having a variable that recorded the status of the scene, and updating it along with the pause/play functions:
var sceneIsPaused = false
func pauseScene() {
self.node.speed = 0
self.physicsWorld.speed = 0
self.node.isPaused = true
sceneIsPaused = true
}
func playScene() {
self.node.speed = 1
self.physicsWorld.speed = 1
self.node.isPaused = false
sceneIsPaused = false
}
Then, override SKView's update method and check the state of that variable. If the scene should be paused, pause it. This will mean that if the scene is automatically unpaused it will re-pause the next frame. This is much faster and cleaner than setting a delay:
override func update(_ currentTime: TimeInterval) {
if (sceneIsPaused == true) {
self.node.speed = 0
self.physicsWorld.speed = 0
self.node.isPaused = true
}
}

Virtual Joystick moving sprite while changing animations

I'm working on a project where I'll have a virtual joystick on the screen which moves a character around the board. I found a few good examples and the one I'm using currently is located # https://github.com/Ishawn-Gullapalli/SpritekitJoystick/tree/master/SpritekitJoystick
While the player is moved around just like I want, I'm unable to figure out how by using this method I can change the player animation based on the direction he's going. I'm able to determine direction but my usual methods of invoking a SKAction doesn't animate the player, it just moves him to the new location while pausing the idle animation. I'm moving my player in the update statement like below and am looking for tips on how to change animations appropriately.
I'm including my update statement below. I've also tried to pass in a CGPoint value with the same effect. Thanks in advance for looking.
override func update(_ currentTime: TimeInterval) {
if joystick.joyStickTouched == true {
mainHero.position.x += joystick.moveRight(speed: movementSpeed)
// Example of me trying to animate my player
if joystick.moveRight(speed: movementSpeed) > 0 {
walkRight()
}
mainHero.position.x -= joystick.moveLeft(speed: movementSpeed)
mainHero.position.y += joystick.moveUp(speed: movementSpeed)
mainHero.position.y -= joystick.moveDown(speed: movementSpeed)
joystick.update(currentTime)
}
}
func walkRight() {
mainHero.isPaused = false
let idleAnimation = SKAction(named: "MainWalkRight")!
let rightGroup = SKAction.group([idleAnimation])
mainHero.run(rightGroup, withKey: "Right")
}

detect nodes last position

I create an game that include balls that has gravity and restitution properties (every ball has different properties by random function). now I want to detect the last position of each ball and then call a remove from node function. Im using the update function to detected the position of the ball while its jumping on the screen. im not sure if I can and how can I detect its last position. Im thinking about create an array that will store each position but I not sure if its right way and wonder if there is a simpler and better way.
here is my code for now:
override func update(_ currentTime: TimeInterval) {
self.enumerateChildNodes(withName: "BALL") { (node:SKNode, nil) in
print(node.position)
}
}
can you help me with that? the array option is only way or there is another one and better one?
You could subclass the ball and add your own property that stores the position.
class BallNode: SKShapeNode {
var lastPosition: CGPoint?
}
let ball = BallNode()
ball.name = “BALL”
ball.position = CGPoint.zero
self.addChild(ball)
In update....
override func update(_ currentTime: TimeInterval) {
self.enumerateChildNodes(withName: "BALL") { (node:BallNode, nil) in
print(node.position)
if let last= node.lastPosition {
print(last)
// do something with last
}
//Update last position before end of updatE
node.lastPosition = node.position
}
}

SpriteKit: How do I remove objects once they leave the screen?

I have a rock sprite that it is falling and every time it goes out of the screen I want to reset it back to the top and have it fall again. It should be a continuous cycle. Here's my code:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
func addRock(){
var rock = self.childNode(withName: "rock")
rock?.physicsBody?.affectedByGravity = true
//self.addChild(rock!)
}
override func sceneDidLoad() {
//bRock = self.childNode(withName: "rock")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//addRock()
var rock = self.childNode(withName: "rock")
rock?.physicsBody?.affectedByGravity = true
/*if (Int((rock?.position.y)!) < Int((self.view?.scene?.view?.bounds.minY)!)){
print("out of screen")
rock?.removeFromParent()
addRock()
}*/
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
var rock = self.childNode(withName: "rock")
//rock?.physicsBody?.affectedByGravity = true
if (!intersects(rock!)){
print("out of screen")
rock?.removeFromParent()
addRock()
}
}
}
I have the rock coming on the screen and then falling. Once it leaves the screen, it does not reset and I get an error. I tried placing the reset code in both the touchesBegan and update functions but neither work. If someone could guide me to the correct path, that would be greatly appreciated.
The problem you're having is that you're removing the rock node from the scene but the addRock method doesn't actually add a new rock, it just finds the existing node if there is one and sets a property on its physicsBody. Instead of removing the node, you should just change its position.
Assuming your scene has the default anchor point of (0,0), you can reset the position like this:
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if let rock = self.childNode(withName: "rock") {
if !intersects(rock!) {
print("out of screen")
rock.position.y = size.height/2 // divided by 2 as discussed in comments
}
}
That will reset the rock's position to the top of the scene and it should resume falling from there.
Three ways:
the same rock is newly located at the right place after leaving the screen, so you just need to set the position of it in (misnamed) addRock (resetRock should be better): rock.position = CGPoint(...). But beware that you also need to reset its velocity, etc, as it was previously moved by physical laws...
create a new rock each time one leaves the screen in addRock: let rock = SKSpriteNode(...)... (and all the initializing code for it. Much simpler as all its physical parameters will be initialized with right values by default.
create a clone of an initial rock. I suppose you have a model of it in your sks file, then don't name it rock but something like (rockModel), and just clone it in addRock with giving it the right name rock. Don't forget to remove the model from the scene at the beginning. This is the usual way to do it.
You could also think about using a series of move actions in a sequence to control the rock's behaviour. Given the move actions are already defined, in your defined sequence, pass in sequence[(falling, hide, backtotop, unhide)] - Repeat Forever.
As long as the rocks don't actually interact with anything and are just for the backdrop/background, hiding and unhiding them is a good way to give the affect they are falling.