SpriteKit Animations - swift

I have this code that does the animation that I want to be executed over the entire time I'm holding down a button that I've created. However I would like this to repeat while the button is being held. Once I've let go it would return the sprite back to a standing position.
func runForward()
{
let run = SKAction.animateWithTextures([
SKTexture(imageNamed: "walk1"),
SKTexture(imageNamed: "walk2"),
SKTexture(imageNamed: "walk3"),
SKTexture(imageNamed: "walk4")
], timePerFrame: 0.09)
_hero!.runAction(run)
}
If I put this code inside of update it updates every frame causing the animation to only finish once I lift my finger off the button. If I start this animation once the button is clicked it only performs it at the very beginning. I was wondering how I get this to run consecutively until I lift my finger off the button.
Heres the code of the button which is just a Sprite Node placed on screen.
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
// Loop over all the touches in this event
for touch: AnyObject in touches {
// Get the location of the touch in this scene
let location = touch.locationInNode(self)
// Check if the location of the touch is within the button's
if (right.containsPoint(location)) {
_right = true
runForward()
}
}
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
_right = false
}

What you want to do is start the animation when your touch begins (touchesBegan) and end the animation when your touch ends (touchesEnded).
Therefore, you should perform an action that repeats forever once your touch begins. This action will have a key (or a name). Once your touch ends, you can use the key (or name) to cancel the action that is running forever (thus it will stop the animation)
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let run = SKAction.animateWithTextures([
SKTexture(imageNamed: "walk1"),
SKTexture(imageNamed: "walk2"),
SKTexture(imageNamed: "walk3"),
SKTexture(imageNamed: "walk4")
], timePerFrame: 0.09)
hero.runAction(SKAction.repeatActionForever(SKAction.sequence([
run,
SKAction.waitForDuration(0.001)
])
), withKey: "heroRunning"
)
}
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
hero.removeActionForKey("heroRunning")
}
}

Related

removeFromParent() strange behavior

I have really strange behavior with function removeFromParent
lazy var buttonAds: SKSpriteNode = {
let n = SKSpriteNode(imageNamed: "ButtonAds")
n.position = CGPoint(x: size.width / 2, y: 600)
n.zPosition = 100
n.setScale(1.4)
return n
}()
in didMove(...) add this button with addChild(buttonAds), and latter in touchesBegan:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first!
if buttonAds.contains(touch.location(in: self)) {
// ...
doAds()
buttonAds.removeFromParent()
}
}
If you tap on button for ads, will be removed, but if tap on that place again, this will call function doAds() again... it's strange, buttonAd don't exist on scene.
Initial:
and after tap:
Thanks
What you want to do is check if the node you touch is of the type it should be. Change your code to this:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first!
if nodeAtPoint(touch.locationInNode(self)) == buttonAds {
doAds()
buttonAds.removeFromParent()
}
}
This should do the trick!
edit: as to why this works, you're removing the node from the scene but it is still an object in memory (otherwise you wouldn't be able to use buttonAds.contains(...) on it) so it also still has its position stored.

Using Swipe Recognizer in SpriteKit

I'm trying to implement swipe gestures left and right into a game in SpriteKit where the player collects falling objects spawning from the top of the screen. The problem I'm facing is trying to keep a continuous movement of the player while the finger is on the screen until the touch is ended and the player stays where the last touch ended. There might be a better way to implement this other than swipe gestures hence why I'm asking you guys! Any help would be great, thank you all!
This is not something you want to use a swipe (I think you are using pan) gesture for. What you want to do is override the touchesBegan, touchesMoved, and touchesEnded calls on the scene, and plan your movement according to these 3 methods.
You are probably going to want to use SKAction.move(to:duration:) with these methods, and figure out the math to keep a constant speed.
E.G.
func movePlayer(to position:CGPoint)
{
let distance = player.position.distance(to:position)
let move = SKAction.move(to:position, duration: distance / 100) // I want my player to move 100 points per second
//using a key will cancel the last move action
player.runAction(move,withKey:"playerMoving")
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)
{
let touch = touches.first
let position = touch.location(in node:self)
movePlayer(to:position)
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?)
{
let touch = touches.first
let position = touch.location(in node:self)
movePlayer(to:position)
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let position = touch.location(in node:self)
movePlayer(to:position)
}

SpriteKit Scale on tap

im trying to create a "button touch" effect for one of my sprites, it works well but then I tap with 2 or more fingers at the same time, i get really weird results, here is my code:
let buttonPressAction = SKAction.scaleBy(0.8, duration: 0)
var button = SKNode()
override func didMoveToView(view: SKView) {
//assign sprite to node
button = self.childNodeWithName("button") as! SKSpriteNode!
}
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if button.containsPoint(location) {
button.runAction(buttonPressAction)
}
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
button.runAction(buttonPressAction.reversedAction())
}
Try changing the SK scale action from
...scaleBy
to
...scaleTo
to ensure it will always scale to the same size. With scaleBy it will scale it by 0.8, not to 0.8. That most likely causes the weird results on multiple touches because you are scaling by 0.8 for each finger/tap.
I never used reverseAction before so I am not sure if that might cause issues. If it does just reset the button by scaling it back to 1
...scaleTo(1, duration: 0)
As as side note you can just say
for touch in touches
instead of
for touch: AnyObject in touches

How to recognize continuous touch in Swift?

How can I recognize continuous user touch in Swift code? By continuous I mean that the user has her finger on the screen. I would like to move a sprite kit node to the direction of user's touch for as long as the user is touching screen.
The basic steps
Store the location of the touch events (touchesBegan/touchesMoved)
Move sprite node toward that location (update)
Stop moving the node when touch is no longer detected (touchesEnded)
Here's an example of how to do that
Xcode 8
let sprite = SKSpriteNode(color: SKColor.white, size: CGSize(width:32, height:32))
var touched:Bool = false
var location = CGPoint.zero
override func didMove(to view: SKView) {
/* Add a sprite to the scene */
sprite.position = CGPoint(x:0, y:0)
self.addChild(sprite)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
touched = true
for touch in touches {
location = touch.location(in:self)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
location = touch.location(in: self)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
// Stop node from moving to touch
touched = false
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if (touched) {
moveNodeToLocation()
}
}
// Move the node to the location of the touch
func moveNodeToLocation() {
// Compute vector components in direction of the touch
var dx = location.x - sprite.position.x
var dy = location.y - sprite.position.y
// How fast to move the node. Adjust this as needed
let speed:CGFloat = 0.25
// Scale vector
dx = dx * speed
dy = dy * speed
sprite.position = CGPoint(x:sprite.position.x+dx, y:sprite.position.y+dy)
}
Xcode 7
let sprite = SKSpriteNode(color: SKColor.whiteColor(), size: CGSizeMake(32, 32))
var touched:Bool = false
var location = CGPointMake(0, 0)
override func didMoveToView(view: SKView) {
self.scaleMode = .ResizeFill
/* Add a sprite to the scene */
sprite.position = CGPointMake(100, 100)
self.addChild(sprite)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Start moving node to touch location */
touched = true
for touch in touches {
location = touch.locationInNode(self)
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Update to new touch location */
for touch in touches {
location = touch.locationInNode(self)
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
// Stop node from moving to touch
touched = false
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
if (touched) {
moveNodeToLocation()
}
}
// Move the node to the location of the touch
func moveNodeToLocation() {
// How fast to move the node
let speed:CGFloat = 0.25
// Compute vector components in direction of the touch
var dx = location.x - sprite.position.x
var dy = location.y - sprite.position.y
// Scale vector
dx = dx * speed
dy = dy * speed
sprite.position = CGPointMake(sprite.position.x+dx, sprite.position.y+dy)
}
The most difficult thing about this process is tracking single touches within a multitouch environment. The issue with the "simple" solution to this (i.e., turn "istouched" on in touchesBegan and turn it off in touchesEnded) is that if the user touches another finger on the screen and then lifts it, it will cancel the first touch's actions.
To make this bulletproof, you need to track individual touches over their lifetime. When the first touch occurs, you save the location of that touch and move the object towards that location. Any further touches should be compared to the first touch, and should be ignored if they aren't the first touch. This approach also allows you to handle multitouch, where the object could be made to move towards any finger currently on the screen, and then move to the next finger if the first one is lifted, and so on.
It's important to note that UITouch objects are constant across touchesBegan, touchesMoved, and touchesEnded. You can think of a UITouch object as being created in touchesBegan, altered in touchesMoved, and destroyed in touchesEnded. You can track the phase of a touch over the course of its life by saving a reference to the touch object to a dictionary or an array as it is created in touchesBegan, then in touchesMoved you can check the new location of any existing touches and alter the object's course if the user moves their finger (you can apply tolerances to prevent jitter, e.g., if the x/y distance is less than some tolerance, don't alter the course). In touchesEnded you can check if the touch in focus is the one that ended, and cancel the object's movement, or set it to move towards any other touch that is still occurring. This is important, as if you just check for any old touch object ending, this will cancel other touches as well, which can produce unexpected results.
This article is in Obj-C, but the code is easily ported to Swift and shows you what you need to do, just check out the stuff under "Handling a Complex Multitouch Sequence": https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/multitouch_background/multitouch_background.html
Below is the code to drag the node around on X position (left and right), it is very easy to add Y position and do the same thing.
let item = SKSpriteNode(imageNamed: "xx")
var itemXposition = 50
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
// updates itemXposition variable on every touch
for touch in touches {
let location = touch.location(in: self)
itemXposition = Int(location.x)
}
}
// this function is called for each frame render, updates the position on view
override func update(_ currentTime: TimeInterval) {
spaceShip.position = CGPoint(x: self.itemXposition , y: 50 )
}

Sprite Kit how to update an image on a button?

I am new to Swift and Sprite Kit.
I want in MyScene.swift to create a button that has a specific image. Every time I click on the button I want to change the button image with a new image.
How can I do that?
GameScene.swift
class GameScene: SKScene {
var rollButton:SKSpriteNode = SKSpriteNode(imageNamed: "dice1.png")
override func didMoveToView(view: SKView) {
rollButton.position = CGPoint(x: 79, y: 290) //this position is based to what? Is there an easier way to put a button using storyborard
rollButton.setScale(0.5) //what does setScale mean?
self.addChild(rollButton)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
if CGRectContainsPoint(rollButton.frame, touch.locationInNode(self)) {
changeButtonImage()
}
}
}
func changeButtonImage() {
//radom get value beteween 1 - 6
change the rollButton image with a new value??? //how to do that
}
Is there a way i an add a UIButton on Storyboard and than connect
GameScene,swift with that storyboard? (adding child nodes
programatically can be really hard)
The CGPoint of a node is based to what? the current UIView?
How to programatically change the image of a button (in swift using
SpriteKit Node) with other images if for example I have the images:
dice1, dice2, dice3...
This is the objective C code:
NSString *fileName = [NSString stringWithFormat:#"dice%d.png", num];
// Setting the uiimageview to the appropriate image
self.rollButton.image = [UIImage imageNamed:fileName];
I didn't find any good example with how to set buttons from storyborard and than using uiviewcontrollers to each new GameScene created,
First, create an array of the images you want the button to have:
let arrayOfImages = [
"image1.png",
"image2.png",
"image3.png",
"image4.png",
"image5.png",
"image6.png"
]
Then initialize the button sprite and give it a texture to start:
var rollButton = SKSpriteNode()
rollButton.texture = SKTexture(imageNamed: arrayOfImages[0])
Then, each time changeButtonImage() is called, generate a random number and change the button's texture:
var randomNumberBetween0And5 = Int(arc4random_uniform(6))
rollButton.texture = SKTexture(imageNamed: arrayOfImages[randomNumberBetween0And5])
If you want to build a button or a switch for generic use, you should try this control I made, the use its pretty straight forward:
You just initialize the type of Button/Switch you want (ColoredSprite, Textured or TextOnly)
let control = TWButton(normalColor: SKColor.blueColor(), highlightedColor: SKColor.redColor(), size: CGSize(width: 160, height: 80))
And after initialize you add a closure to it (like addTargetForSelector on UIButton)
control.addClosureFor(.TouchUpInside, target: self, closure: { (scene, sender) -> () in
scene.testProperty = "Changed Property"
})
}
That’s it! There’s more info on the readme section on the GitHub page: https://github.com/txaidw/TWControls
But if you want to implement in a specific node here’s how I did:
internal override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
let touch = touches.first as! UITouch
let touchPoint = touch.locationInNode(self.parent)
if self.containsPoint(touchPoint) {
self.touchLocationLast = touchPoint
touchDown()
}
}
internal override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
let touch = touches.first as! UITouch
let touchPoint = touch.locationInNode(self.parent)
self.touchLocationLast = touchPoint
}
internal override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
let touch = touches.first as! UITouch
let touchPoint = touch.locationInNode(self.parent)
if let lastPoint = self.touchLocationLast where self.containsPoint(lastPoint) {
// Ended inside
touchUpInside()
}
else {
// Ended outside
touchUpOutside()
}
}