How to Make a Button Continually Call a Function When Held Down (Spritekit)

How to make a Button continually call a function when held down (SpriteKit)

You know when the button is touched because touchesBegan is called. You then have to set a flag to indicate that the button is pressed.

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first!
if leftButton.containsPoint(touch.locationInNode(self)) {
leftButtonIsPressed = true
}
if rightButton.containsPoint(touch.locationInNode(self)) {
rightButtonIsPressed = true
}
}

In update(), call your function that flag is true:

update() {
if leftButtonIsPressed == true {
moveLeft()
}

if rightButtonIsPressed == true {
moveRight()
}

}

You set the flag to false when touchesEnded is called for that button:

override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first!
if leftButton.containsPoint(touch.locationInNode(self)) {
leftButtonIsPressed = false
}
if rightButton.containsPoint(touch.locationInNode(self)) {
rightButtonIsPressed = flase
}
}

Edit:

As pointed out by KoD, a cleaner way to do this (for movement buttons) is with SKAction which removes the need for the flag:

  1. Define SKActions for moveTo x:0 and moveTo x:frame.width in
    didMoveTo(View:)
  2. In touchesBegan, run the correct SKAction on the correct object
    specifying a key for the SKAction.
  3. In touchesEnded, remove the relevant SKAction.

You'll have to do some maths to calculate how many points your object will have to move and then set a duration for the SKAction based upon this distance and a movement speed (in points per second).

Alternatively, (thanks to KnightOfDragons for this) create a SKAction.MoveBy x: which moves a small distance (based upon your desired movement speed) and with a duration of 1/60s. Repeat this action forever (SKAction.repeatForever) when the button is touched and remove the repeating SKAction when the button is released.

How to move spriteKit node up and down with a button

There may not be any provision for buttons in SpriteKit, but one can easily subclass SKSpriteNode to act as a button. I have created such a class which is available on GitHub here.

Using SKAction for movement based on direction buttons is not advisable. Instead, you need to use flags for direction buttons upon which you will move the node in the -update: method.

Maintain the flags as instance variables.

@implementation MyScene
{
BOOL upDirection;
BOOL downDirection;
}

Initialise them to FALSE in the -initWithSize: method.

This is how you should handle the flags in the -update method:

-(void)update:(CFTimeInterval)currentTime
{
/* Called before each frame is rendered */

if (upDirection)
{
myNode.position = CGPointMake(myNode.position.x, myNode.position.y + 5); //Increment value can be adjusted
}

if (downDirection)
{
myNode.position = CGPointMake(myNode.position.x, myNode.position.y - 5); //Decrement value can be adjusted
}
}

shoot projectiles while button is held

You can use the touchesBegan and touchesEnded function to keep track of how long the button was pressed. Then use the update function of the SKScene to shoot projectiles with a delay.

The logic is to set a boolean shooting to true when the button is pressed. shooting is set to false inside touchesEnded. This way we can keep track of the touch. Then in the update function the projectiles are shot if the shooting variable is true.

In Objective C

//GameScene.h

@property (nonatomic,strong) SKSpriteNode *shootButton;

//GameScene.m

BOOL shooting = false;
CFTimeInterval lastShootingTime = 0;
CFTimeInterval delayBetweenShots = 0.5;

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];

if ([self nodeAtPoint:location] == self.shootButton)
{
shooting = true;
NSLog(@"start shooting");
}

}

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];

if ([self nodeAtPoint:location] == self.shootButton)
{
shooting = false;
NSLog(@"stop shooting");
}
}


-(void)shoot
{
// Projectile code
NSLog(@"shooting");
}

-(void)update:(NSTimeInterval)currentTime {

if (shooting)
{
NSTimeInterval delay = currentTime - lastShootingTime;
if (delay >= delayBetweenShots) {
[self shoot];
lastShootingTime = currentTime;
}
}
}

In Swift

var shootButton : SKSpriteNode!

var shooting = false
var lastShootingTime : CFTimeInterval = 0
var delayBetweenShots : CFTimeInterval = 0.5

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let touch: AnyObject? = touches.anyObject()
if let location = touch?.locationInNode(self)
{
if self.nodeAtPoint(location) == shootButton
{
self.shooting = true
println("start shooting")
}
}
}

override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
let touch: AnyObject? = touches.anyObject()
if let location = touch?.locationInNode(self)
{
if self.nodeAtPoint(location) == shootButton
{
self.shooting = false
println("stop shooting")
}
}
}

func shoot()
{
// Projectile code
println("shooting")
}

override func update(currentTime: NSTimeInterval) {

if shooting
{
let delay = currentTime - lastShootingTime
if delay >= delayBetweenShots {
shoot()
lastShootingTime = currentTime
}
}
}

You can adjust the delayBetweenShots variable to change how fast the firing needs to happen.

Repeat function while touchesBegan in Swift 4

what you need is someway of turning on the fire when you press down on the screen and turning it off when you lift your finger. You can use the touches began to set a variable to true the says you should be firing isFiring = true and then catch that variable in the update. When you lift your finger the touches ended will turn the variable off.

Another nice touch is that you can adjust the firingInterval variable in real time to adjust the rate of fire.

private var updateTime: Double = 0
private var isFiring = false
//rate of fire, 1 means fire once a second
private var firingInterval: Double = 1

override func update(_ currentTime: TimeInterval) {

//check if we should be firing if not get outta here
guard isFiring else { return }

if updateTime == 0 {
updateTime = currentTime
}

if currentTime - updateTime > firingInterval {
self.fireBullet()
updateTime = currentTime
}
}

override touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
isFiring = true
}

override touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
isFiring = false
}

func fireBullet() {

let bullet = SKSpriteNode(imageNamed: "bullet")
bullet.name = "Bullet"
bullet.setScale(1.5) // Bullet Size
bullet.position = player.position
bullet.zPosition = 1
bullet.physicsBody = SKPhysicsBody(rectangleOf: bullet.size)
bullet.physicsBody!.affectedByGravity = false
bullet.physicsBody!.categoryBitMask = PhysicsCategories.Bullet
bullet.physicsBody!.collisionBitMask = PhysicsCategories.None
bullet.physicsBody!.contactTestBitMask = PhysicsCategories.Enemy
self.addChild(bullet)

let moveBullet = SKAction.moveTo(y: self.size.height + bullet.size.height, duration: 1)
let deleteBullet = SKAction.removeFromParent()

let bulletSequence = SKAction.sequence([bulletSound, moveBullet, deleteBullet])
//let bulletRepeat = SKAction.repeatForever(bulletSequence)
bullet.run(bulletSequence )
}

swift skscene touch and hold other action than just touching

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 :)

Move an animated character only while a button is held down

Attach an action to the touch down inside event for your button. In that code, start the animation.

Then attach a second action to touch up inside and touch up outside, and in that method, stop the animation.

SKAction.animate not animating all frames

Hey guys I found the solution! So you guys were right, it was because update() was calling the animation function more than once. I set up some Boolean checks and it works now. I also came across this that really helped me out.

Run two SKActions repeatedly in sequence?

Can you try this (Hints are in the code comments)?

let sequence = SKAction.sequence([movePlayerForward, movePlayerBack])
let player = SKSpriteNode(imageNamed: "player")

// start your action with repeating it forever
player.runAction(SKAction.repeatActionForever(sequence), withKey: "moveForwardBackward")

// stop your action after tapping
player.removeActionForKey("moveForwardBackward")


Related Topics



Leave a reply



Submit