I'm basically trying to code a "find the ball under the cups" game for practice.
So there is 3 cups, and one target to find. All using SKSpriteNode. The target is randomly a child of one cup, and follows rotations as the parent rotate around an SKNode.
Between each game, the program is supposed to show where is the target, by simply animate it up, then down. Here the sequence code :
//THE ANIMATIONS
SKAction *moveUp = [SKAction moveByX:0.0 y:100 duration:1];
SKAction *moveDown = [SKAction moveByX:0.0 y:-100 duration:1];
SKAction *wait = [SKAction waitForDuration:0.5];
_presentTargetSequence = [SKAction sequence:#[moveUp,wait,moveDown]];
And the method using it :
- (void) presentTarget
{
NSLog(#"presentTarget()");
[_target runAction:_presentTargetSequence completion:^{
_canMove = YES;
}];
}
The code works fine, but only the first time, after that, the method is called but never go through [_target runAction ...].
BUT it's working if the target does get in the rotation/swap.
So my question is : is there anything that can make a node ignore it run action method ? NSLog(#"presentTarget()") is called as I said, but not reaction.
I find that every time I'm confused about an action not running, it's because the object running the action is not currently in the node hierarchy for the scene. There is no error for this, it just doesn't run the action. So double check to make sure that the object is added to the current scene at the time you ask it to run the action.
Related
I am building a game with sprite kit and have a sprite moving from left to right with an endless action.
SKAction *moveRight = [SKAction moveByX:3.0 y:0 duration:3.5];
SKAction *moveLeft = [SKAction moveByX:-3.0 y:0 duration:3.5];
SKAction *reversedMoveRight = [moveRight reversedAction];
SKAction *reversedMoveLeft = [moveLeft reversedAction];
SKAction *completion = [SKAction runBlock:^{
SKAction *sequence = [SKAction sequence:#[moveRight, moveLeft, reversedMoveRight,reversedMoveLeft]];
SKAction *endlessAction = [SKAction repeatActionForever:sequence];
[snake runAction:endlessAction];
}];
[snake runAction:completion withKey:#"KeySnake"];
This works, but after a short period of time my game slows down. The CPU and memory usage continues to grow in the debug navigator in Xcode. I think the endless action is causing the problem, but I don't know any other way to move it constantly like I want to.
From your comment I understand you are calling
[snake runAction:completion withKey:#"KeySnake"];
inside the update method. This is the problem, infact you are creating and running a new action every frame.
Move the whole block of code (you showed in your question) inside a method that is called only once.
Example: here I also refactored the construction of your action and changed the x value (in the action) from 3.0 to 100.0
#import "GameScene.h"
#implementation GameScene
{
SKSpriteNode * _snake;
}
- (void)didMoveToView:(SKView *)view {
[self addSnake];
[self startSnakeMoving];
}
- (void)addSnake{
_snake = [SKSpriteNode spriteNodeWithImageNamed:#"Snake"];
_snake.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:_snake];
}
- (void)startSnakeMoving {
SKAction * moveRight = [SKAction moveByX:100.0 y:0 duration:3.5];
SKAction * sequence = [SKAction sequence:#[moveRight, moveRight.reversedAction, moveRight.reversedAction, moveRight]];
SKAction * endlessAction = [SKAction repeatActionForever:sequence];
[_snake runAction:endlessAction withKey:#"KeySnake"];
}
#end
Rather than making snake (whatever is snake) calling your endless action (that is in fact 'sequence' repeated forever), you should call 'sequence' via a CADisplayLink (which is a screen refresh), that is make to drive the rendering of anything (so game) at screen refresh frequency.
I have a node on which I run tow actions - one on the mouseDown event and second on the mouseUp event.
The action that is being run may take longer than left mouse button is down and I would like to continue executing this action and somehow run the second action from the mouseUp method after the action from the mouseDown method has finished. Is it possible?
You don't have code for me to work off of and I am not that great with Swift anyway so hopefully this makes sense with Objective-C
First Option
OnMouseDown:
SKAction *firstAction = [SKAction moveByX:100 y:100 duration:5];
SKAction *customAction = [SKAction customActionWithDuration:0 actionBlock:^(SKNode *node, CGFloat elapsedTime) {
if (self.runSecondAction)
{
SKAction *secondAction = [SKAction moveByX:-100 y:-100 duration:5];
[node runAction:secondAction];
self.runSecondAction = NO;
}
}];
SKAction *squence = [SKAction sequence:#[firstAction, customAction]];
[sprite runAction:squence withKey:#"moveAction"];
OnMouseUp:
if ([sprite actionForKey:#"moveAction"])
{
self.runSecondAction = YES;
}
else
{
SKAction *secondAction = [SKAction moveByX:-100 y:-100 duration:5];
[sprite runAction:secondAction];
}
The basic concept is you run a sequence with a custom action that runs a block. In that block you check to see if a bool has been set to run the second object. This will make it run after the first one is complete.
In mouseUp you want to run the action right away if the first action is done but if not you set a bool that will be check during the sequence.
Second Option
Don't run a sequence in OnMouseDown and check if there is a #"moveAction" going OnMouseUp. If there is check the state of the sprite and create the sequence at that point with what is remaining of that action. If there isn't that #"moveAction" just run the second action.
Hopefully that makes sense and is helpful.
Again, probably a newbie question. I'm following the instructions I found in these two links, since they solve exactly the problem I have (modify the speed of a SKAction from outside its own method):
How to change duration of executed SpriteKit action
How to run or execute an SKAction from outside of the object?
In my case, have this SKAction:
SKAction * moveBall = [SKAction moveToY:0 duration:1];
[ball runAction:moveBall withKey:#"ball falling"];
I create the property, like this:
#property SKAction * moveBall;
And then I want to call it from the touchesBegan after touching a button, like this:
if ([node.name isEqualToString:#"Slow Down Button"]) {
self.moveBall.speed = 0.5;
}
moveBall.speed inside its own method is 1.0, but self.moveBall.speed indicates a speed of 0.0 (same for _moveBall.speed), so the property declaration is not working correctly. I tried several things, but so far I couldn't find what is missing.
Thanks in advance!
This:
SKAction * moveBall = [SKAction moveToY:0 duration:1];
creates a local variable named moveBall. But your property is accessed through self.moveBall respetively directly through its auto-synthesized ivar named _moveBall.
The solution is one of these two:
self.moveBall = [SKAction moveToY:0 duration:1];
or
_moveBall = [SKAction moveToY:0 duration:1];
and then continue to use that variable for addChild: and other uses.
You most likely need to spend some time reading about properties and pointers. But it's possible that you just aren't showing all the code you are using. The phrase "inside its own method" is confused.
SKAction *moveBall = is not the same as _moveBall, unless you have assigned that pointer to the property. But you are using the same name for a pointer and a property in your code, confusing things further.
I suspect you need to change:
SKAction * moveBall = [SKAction moveToY:0 duration:1];
to
_moveBall = [SKAction moveToY:0 duration:1];
and then only use _moveBall throughout your code. But make sure you read up on pointers and properties and understand the difference.
I want to move two (or more) SKSpriteNodes in sync. Any difference will show. I tried to trigger the SKAction for each sprite in order and when the last one is finished it triggers a new move. But it turns out that the actions doesn't end in the same order they are started, which causes a slightly time difference that is noticeable.
Is there a way to run parallel SKActions on two or more sprites with a duration so that they end at exactly the same time or at least in the order they are started?
Here is an principle example of what is not working:
- (void)testMethod1{
SKSpriteNode *child_1=[arrayWithSprites objectAtIndex:1];
SKSpriteNode *child_2=[arrayWithSprites objectAtIndex:2];
//This doesn't work.
[child_1 runAction:[SKAction moveToX:20.0 duration:0.5]];
[child_2 runAction:[SKAction moveToX:20.0 duration:0.5]
completion:^{[self testMethod1];}];
//Actions might not be finished in the order they are started.
}
And here is a way I haven't tried yet but wonder if it might solve my problem:
- (void)testMethod2{
SKSpriteNode *child_1=[arrayWithSprites objectAtIndex:1];
SKSpriteNode *child_2=[arrayWithSprites objectAtIndex:2];
//Will this guarantee total syncronisation?
[self runAction:[SKAction group:[NSArray arrayWithObjects:
[SKAction runBlock:^{[child_1 runAction:[SKAction moveToX:20.0 duration:0.5]];}],
[SKAction runBlock:^{[child_2 runAction:[SKAction moveToX:20.0 duration:0.5]];}],
nil]]
completion:^{[self testMethod2];}];
}
I hope my English and thoughts are understandable.
//Micke....
Just add a container SKNode, and then they will both move at once:
SKNode *containerNode = [[SKNode alloc] init];
[containerNode addChild:node1];
[containerNode addChild:node2]; //add as many as necessary
[containerNode runAction:someAction]; //declare the action you want them both to perform
The solution by Tyler works perfectly.
But if you can't or don't want to do that, you should know that very likely actions are multithreaded and thus they can finish in any order, but they should still finish in the same frame.
To ensure they both ran to completion before running testMethod, you should perform the code in didEvaluateActions. Then you need to find a way to figure out whether both actions have finished. One way is to use a key.
[child_1 runAction:[SKAction moveToX:20.0 duration:0.5] withKey:#"action1"];
[child_2 runAction:[SKAction moveToX:20.0 duration:0.5] withKey:#"action2"];
Then check if both of these actions have run to completion by checking if they still exist:
-(void) didEvaluateActions
{
if ([child_1 actionForKey:#"action1"] == nil &&
[child_2 actionForKey:#"action2"] == nil)
{
// both actions have ended, start new ones here ...
}
}
Alternatively you can use a completion block that both actions run. Increase a NSUInteger counter variable and in the block increase the counter by 1, then test if the counter's value is equal to (or greater) than the number of concurrent actions. If it is, you know that both actions have run to completion:
__block NSUInteger counter = 0;
void (^synchBlock)(void) = ^{
counter++;
if (counter == 2)
{
[self testMethod1];
}
};
[child_1 runAction:[SKAction moveToX:20.0 duration:0.5] completion:synchBlock];
[child_2 runAction:[SKAction moveToX:20.0 duration:0.5] completion:synchBlock];
Is your framerate constant ? Your first example technically should work, however the completion order might be based on something else, like their draw order.
One concept you could employ is to temporarily encapsulate them in a container node, and then move just the container node via an action. When the action is complete remove them from the container and back to their original parent.
Are you finding that they move out of sync visually ? Or does your testMethod1 in some way require that both actions are complete ?
Although the container concept I have suggested will work for your specific example, it's not a valid option unless it's desired that the objects are moving as if they are connected. An example of where the container would not work, is if each object had to move to different locations in 1 second.
i want to make a parallax effect on my background title screen with mountains moving. The "trick" i want to make is when a mountain goes to the end of the screen, instead of destroying the node and create it again at the begining position, i just want to change it's position to the begining position and loop the rest of actions. For this i create my moutain sprite at the begining of the createSceneContents() function and i pass the sprite to a method animate() wich do allways the same combo of actions forever: animate to the right, then when it's at x position, change mountain.position.x to the begining...
-(void)createSceneContents{
//crear
SKSpriteNode *mountain = [SKSpriteNode spriteNodeWithImageNamed:#"mountain.png"];
mountain.name = #"mountain";
//initial position
mountain.position = CGPointMake(-161.5,15);
//animate
SKAction *animRight = [SKAction moveToX:801.5 duration:4];
SKAction *comboActions = [SKAction repeatActionForever:[SKAction performSelector:#selector(animate:mountain) onTarget:#"mountain"]];
And here i have my method declaration:
- (void) animate:(SKSpriteNode*)mountain
{
// code....
}
My problem is i'm allways having errors passing the SKSpriteNode *mountain in my method and
I'm going crazy trying everithing.
You can avoid performSelector action, using a custom action like this.
SKAction* parallaxMoveAction = [SKAction sequence:#[[SKAction moveToX:801.5 duration:4.0f],[SKAction customActionWithDuration:0 actionBlock:^(SKNode* node, CGFloat elapsedTime){
[node setPosition:CGPointMake(-161.5,15)];
}]]];
If you set custom action duration to 0 it will be performed only once.
On the other hand your perform selector action should look like this:
[SKAction performSelector:#selector(animate:) onTarget:self]
As far as i know you can't pass arguments to it.