Triggering the end of a while loop using the completion of SKAction - ios

I'm trying to use a "while loop" while my SKSpritenode is animating and I want to trigger the end of the SKAction to trigger the end of the loop. This is my attempt
while (orgNode.hasActions) {
//render the background
}
what I want to do is create a bottom up sweep effect that changes the sprite nodes texture underneath the originNode's position, so that the user sees the sweeping motion of the node changing the background color.

You can override the update or didEvaluateActions function of the SKScene if you want to update the node every frame. didEvaluateActions function is called in each frame after all the SKActions are evaluated. The update function is called before all actions are evaluated.
override func didEvaluateActions() {
if node.hasActions()
{
println("Running Action")
}
}
The following image shows which all functions are called in each frame and in what order.
For more information : https://developer.apple.com/library/ios/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG/Introduction/Introduction.html

Related

Apply a sequence of different SKActions to different SKNodes

I want to create a sequence of SKActions, each of which is for a different SKSpriteNode. I want to fade my layer node, remove it from the parent, wait for 3 seconds, then start a move SKAction for my snake node. Here's some code:
func startGame() {
layer.run(SKAction.sequence([
SKAction.fadeAlpha(to: 0, duration: 1),
SKAction.removeFromParent(),
SKAction.wait(forDuration: 1),
]))
//Here move the snake node
}
The problem is, if I add snake.run(SKAction.move(...)) at the place of the comment, it gets executed at the same time with layer.run(...).
You can run a block of code or function as an action (because in Swift closures are also function types), so you can add SKAction.runBlock(moveSnake) as the last SKAction on the array of actions run in layer.
Then in func moveSnake() {} you can run your actions to move the snake.

Xamarin SpriteKit running action with key

I am using SpriteKit with Xamarin. I'm currently making a game similar to flappy bird, except the pipes can also 'chomp' down and kill you like that. So at any given time my pipe sprites can be running two actions, one for movement along the X axis (when the player is moving) and one for movement along the Y axis (closing the pipes). When the player stops moving along the X axis I want to stop the running action of the pipes that moves them horizontally, but still keep the chomping action going. I'm having trouble setting up a key for my action though. Currently, this is what I have.
public override void TouchesEnded (NSSet touches, UIEvent evt)
{
base.TouchesEnded (touches, evt);
//TODO: Optimize by removing action by key instead of all of them. Figure out how to do this and impliment.
//This needs to be done before refining chomp as it will flow into the chomp logic
//by not stopping a chomp mid-way because the player lifted their finger.
poletop.RemoveAllActions();
polebottom.RemoveAllActions();
pole2top.RemoveAllActions ();
pole2bottom.RemoveAllActions ();
background.RemoveAllActions ();
//restarting the chomping action if it was stopped before.
chomped_return (false);
chomped_return (true);
}
So I'm basically stopping all actions, then restarting only the chomp if it was running (gets restarted in chomped_return).
This isn't very efficient and also causes some lag in-game as it stops and starts.
Code for starting the action along the X axis (player movement). This is the SKAction that I want to have the key so I can stop it and it alone. resetpoles is the completion function.
SKAction movebottompole;
SKAction movetoppole;
movebottompole = SKAction.MoveToX (edgebottom.X, flTime);
movetoppole = SKAction.MoveToX (edgetop.X, flTime);
polebottom.RunAction (movebottompole, resetpoles);
poletop.RunAction(movetoppole, resetpoles);
Chomping down on the player is an instant teleport of the pipes, but here's the code that runs to start the action of the pipes returning to their original position along the Y axis. This is setting up the action that I don't want to stop until it has completed.
public void chomped_return(bool blFirstPole)
{
//define our two actions
SKAction topreturn;
SKAction botreturn;
//define our floats for the time calculation
float flTime = 0;
float flMoveSpeed = 750;
float flDistance = 0.0f;
if (blFirstPole == true)
{
flDistance = (float)polebottom.Position.Y;
}
else if (blFirstPole == false)
{
flDistance = (float)pole2bottom.Position.Y;
}
//calculate time based on distance and vector units/second desired.
flTime = flDistance / flMoveSpeed;
//setup our moveto actions and use the time calculated above. Start the action.
topreturn = SKAction.MoveToY (750.0f, flTime);
botreturn = SKAction.MoveToY (0.0f, flTime);
if (blFirstPole == true)
{
poletop.RunAction (topreturn);
polebottom.RunAction (botreturn);
}
else if (blFirstPole == false)
{
pole2top.RunAction (topreturn);
pole2bottom.RunAction (botreturn);
}
return;
}
The variable blFirstPole is used to determine whether or not we are running the action on the first set of poles or second set (as there can be two on screen at once)
Would really appreciate any assistance, if you need any more information please do let me know!
Thanks
You can use RemoveActionForKey to remove an action with an certain key.
https://developer.xamarin.com/api/member/MonoTouch.SpriteKit.SKNode.RemoveActionForKey/p/System.String/
polebottom.RunAction(movebottompole, "movebottompole");
// remove it
polebottom.RemoveActionForKey("movebottompole");
But now you don't have your completion handler anymore. You can solve it by combining these two actions into one Sequence.
var poleactionWithCompletion = SKAction.Sequence(movebottompole, restpoles);
polebottom.RunAction(poleactionWithCompletion, "movebottompole");
If you need this more often, you can implement an extension method like:
public static class SKNodeExtension
{
public static void RunAction(this SKNode node, SKAction action, SKAction completion, string key)
{
var sequence = SKAction.Sequence(action, completion);
node.RunAction(sequence, key);
}
}
and then run your action like:
polebottom.RunAction(movebottompole, restpoles, "movebottompole");

Sprite Kit - Creating node from didBeginContact()

I am using a custom brick class:
import SpriteKit
class Brick: SKSpriteNode {
enum type { case Normal }
convenience init (type: Brick.type) {
self.init(color: .greenColor(), size: CGSizeMake(75, 25))
physicsBody.SKPhysicsBody(rectangleOfSize: size)
physicsBody!.mass = 9999
physicsBody!.affectedByGravity = false
position = CGPointMake(100, 50)
// Category and collision bitmasks ...
// Action to move the brick upwards ...
}
// Stuff
}
In my scene, I'm creating one initial brick, and everything works just fine. However, when I try to add a brick in the didBeginContact() function, I get unexpected results.
I have a SKNode at 1/5 height of the screen. So everytime a brick reaches that height, it will make contact with this invisible node and create a new brick:
// Inside SKScene
func didBeginContact(contact: SKPhysicsContact) {
// I set physics body A and B ...
if a.categoryBitMask == Category.brick && b.categoryBitMask == Category.brickSpawner {
addChild(Brick(type: .Normal))
}
}
So the problem is: when I create a new brick inside this function, the position is set to (0, 0) instead of (100, 50) as defined in the Brick class. Actually, if I go ahead and use println(brick.position) I get (100, 50), but in the screen it looks positioned at (0, 0).
If I create a brick anywhere else in the code, for example in the touchesBegan() or update() functions, the position is set correctly. This problem only happens when creating the node from didBeginContact().
This should help, it explains the steps in a single frame.
https://developer.apple.com/library/ios/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG/Introduction/Introduction.html
You are creating it at the wrong time, so one of the steps behind the scenes is resetting it due to a few steps being missing. Queue that process up for the didFinishUpdate command. ( I would just create the object in the did contact, then throw it into an array, then on didFinishUpdate, go through the array and add them to the scene, and clear the array)

How do SKAction durations work in relation to frame rates?

I am trying to understand how SKActions work. Specifically with SKAction.runAction(), one of the parameters is a duration for the animation. However it seems that whatever I put in as the duration, the animation will render every frame. I have tried to put the SKAction.runAction() line in other places, but it seems to only work in the update() override method in SKScene. I have tried the SKAction.repeatActionForever, but this does not seem to be able to run parallel to other processes.
My question is, if there is a required parameter for the duration of the animation, where would one place the SKAction.runAction() method to have the animation run based on the given duration rather then on the frame rate?
This is my code:
GameScene.swift (The Player class is a subclass of the Character class. Refer to the code below.)
let player = Player()
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
//Code here to recognize user inputs
scene?.addChild(player.sprite)
}
override func update(currentTime: CFTimeInterval) {
player.sprite.runAction(SKAction.runBlock(player.move))
}
}
CharachterClass.swift move() function
func move(){
switch(direction){
case "up":
sprite.position = CGPoint(x: sprite.position.x, y: sprite.position.y + speed)
case "down":
sprite.position = CGPoint(x: sprite.position.x, y: sprite.position.y - speed)
case "right":
sprite.position = CGPoint(x: sprite.position.x + speed, y: sprite.position.y)
case "left":
sprite.position = CGPoint(x: sprite.position.x - speed, y: sprite.position.y)
default:
break
}
SKAction.moveTo(sprite.position, duration: 1)
}
Note: the direction variable is the direction that the sprite is moving in and speed variable is the amount of pixels the sprite should move each time the SKAction is run.
I don't know what you intend to do with this:
override func update(currentTime: CFTimeInterval) {
player.sprite.runAction(SKAction.runBlock(player.move))
}
But in effect you can quite simply do the following instead:
override func update(currentTime: CFTimeInterval) {
player.move()
}
The difference between the two methods is that runAction will schedule the block execution and depending on when the scheduling occurs, it may not run in the current frame (update cycle) but in the next one. So quite possibly the run block solution introduces a 1-frame delay.
Next, this will run a move action for the duration of 1 second (not: 1 frame):
SKAction.moveTo(sprite.position, duration: 1)
Since you run this in every update method, you effectively null and void the SKAction's primary purpose: to run a task over a given time (duration).
Worse, you run a new move action every frame so over the duration of 1 second you may accumulate about 60 move actions that run simultaneously, with undefined behavior.
If you were to call runAction with the "key" parameter you could at least prevent the actions from stacking, but it would still be rather pointless because replacing an action every frame that is supposed to run over the time of 1 second means it will have little effect, if any.
Coincidentally, with the way the code is set up right now, you could as well just assign to position directly ... but ... wait, you already do that:
sprite.position = CGPoint(x: sprite.position.x - speed, y: sprite.position.y)
But immediately afterwards you run this move action:
SKAction.moveTo(sprite.position, duration: 1)
So all in all, you try to run an action over the course of 1 second that ends up being replaced every frame (60 times per second) - and that action does ... nothing. It will not do anything because you've already set the sprite to be at the position that you then pass into the move action as the desired target position. But the sprite is already at that position, leaving the action nothing to do. For one second - or perhaps it may quit early, that depends on SK implementation details we don't know about.
No matter how you look at it, this move action is over-engineered and has no effect. I suggest you (re-)read the actions article in the SK Programming Guide to better understand the timing and other action behaviors.

Set A bool to true for two seconds then change back to false

As the question states, in spritekit I am trying to find a way to change my value of a bool from false, too true for x amount of seconds then change it back to false.
the logic is i have this var, isStunned, when my player comes in contact with x sprite i call it and my joystick is disabled since the player is "stunned"
if (controlButtonDown) { // If i pressed joystick
if (!isStunned) { // IF i am not stunned
//move player
When my player touches the glue, i set isStunned to yes and the joystick is disabled, however I am wondering of a way to only disable my joystick or not move my sprite for x amount of seconds. I know of SKAction and the wait sequence, but those are actions and I dont think they will apply here. Any help is appreciated.
Thank you
Since you ask specifically with a spritekit tag.
Don't use NSTimer or actions to work with time in a game. Instead use the update function. If you're running at 60fps that means the update function will be called 120 times for two seconds. Using the frames instead of seconds to keep your game updated will avoid the gameplay being changed by lag. Imagine for instance that the players game lags a little, in other words runs slower. 2 seconds is still 2 seconds regardless of game lag, so now he is affected less by the glue than a person who has no lag. So...
First off you should have a variable keeping track of in game time: int _time;
In every update cycle you add one to time: _time++;
You should have a variable keeping track of when the user touched the glue: int _glueTouchedAtTime;
When the user clicks glue you set: _glueTouchedAtTime = time;
You should have a constant defining how long the glue is active: #define GLUE_TIME 120;
When you initiate the game set _glueTouchedAtTime to: _glueTouchedAtTime = -GLUE_TIME; To prevent the glue from being on when (_time == 0)
Testing if the user has touched glue now works like this:
if(_glueTouchedAtTime+GLUE_TIME>time) {
// Glue is active
} else {
// Glue is not active
}
Different glues for different sprites
To have different glues for different sprites I would suggest first doing a general game object (GameObject) as a subclass of either SKNode or SKSpriteNode depending on your needs. Then create a subclass of GameObject called GameObjectGluable. This should have a property called: #property int gluedAtTime;
You glue the glueable game object by: aGameObjectGluable.gluedAtTime = time;
Now you can test:
if(aGameObjectGluable.gluedAtTime +GLUE_TIME>time) {
// Game Object is glued
} else {
// Game object is not glued
}
Set your value-of-interest to true and then fire a NSTimer after two seconds to set it back to false.
For an explanation of how you might use NSTimer, see this SO thread: How do I use NSTimer?
You can use SKAction also. The runBlock method let's you execute blocks of code as actions.
SKAction *wait = [SKAction waitForDuration:2.0];
SKAction *reenableJoystick = [SKAction runBlock:^{
joystick.userInteractionEnabled = TRUE;
}];
SKAction *sequence = [SKAction sequence:#[wait, reenableJoystick]];
[self runAction:sequence];

Resources