Reputation: 15
I am making a simon game in objective-c using sprite kit in Xcode. What I am trying to accomplish is to pick random number, add it to the sequence, fadeout, pick next random number... what happens when I run this code is that all the tiles in the sequence fadeout then back in at exactly the same time.
Here is my code:
-(void) sequence
{
NSMutableArray *sequenceArray = [[NSMutableArray alloc] initWithCapacity:50];
int randomNumber;
SKAction *fadeOut = [SKAction fadeOutWithDuration: 1]; //color of tile fades out
SKAction *fadeIn = [SKAction fadeInWithDuration: 1]; //color of tile fades in
SKAction *pulse = [SKAction sequence:@[fadeOut,fadeIn]]; //holds a sequence so tile fades out then fades in
CFTimeInterval startTime;
float elapsedTime;
for(int i = 0; i<level+1; i++)
{
startTime = CFAbsoluteTimeGetCurrent();
randomNumber=[self getRandomNumber]; //random number is picked
if(randomNumber==0)
{
sequenceArray[i]=blueBox;
}
else if(randomNumber==1)
{
sequenceArray[i]=redBox;
}
else if(randomNumber==2)
{
sequenceArray[i]=yellowBox;
}
else if(randomNumber==3)
{
sequenceArray[i]=greenBox;
}
[sequenceArray[i] runAction:[SKAction repeatAction:pulse count:1]]; //for each tile in the sequence the color fades
//out then back in
elapsedTime = CFAbsoluteTimeGetCurrent()-startTime; //trying to pause the loop so the sequence can finish before the
//next random number is picked
while(elapsedTime<.5)
{
elapsedTime = CFAbsoluteTimeGetCurrent()-startTime;
NSLog(@"elapsed time %f", elapsedTime);
}
}
}
Ok I have tried to implement some of the suggestions below. I still am running into errors, for example now the sequenceArray[index] at index=0 is out of bounds.
Here is my new code:
-(void)doLoop:(int)index
limit:(int)limit
sprite:(SKSpriteNode*) sprite
body:(void(^)(int))body
{
body(index);
index++;
if(index<level+1)
{
SKAction *loopAction;
[self runAction:loopAction completion:^{
[self doLoop:index limit:limit sprite:sprite body:body];
}];
}
}
-(void) sequence
{
NSMutableArray *sequenceArray = [[NSMutableArray alloc] initWithCapacity:50];
int index = 0;
SKAction *fadeOut = [SKAction fadeOutWithDuration: 1]; //color of tile fades out
SKAction *fadeIn = [SKAction fadeInWithDuration: 1]; //color of tile fades in
SKAction *pulse = [SKAction sequence:@[fadeOut,fadeIn]]; //holds a sequence so tile fades out then fades in
[self doLoop:0 limit:level+1 sprite:sequenceArray[index] body:^(int index){
int randomNumber=[self getRandomNumber]; //random number is picked
if(randomNumber==0)
{
sequenceArray[index]=blueBox;
}
else if(randomNumber==1)
{
sequenceArray[index]=redBox;
}
else if(randomNumber==2)
{
sequenceArray[index]=yellowBox;
}
else if(randomNumber==3)
{
sequenceArray[index]=greenBox;
}
[sequenceArray[index] runAction:pulse]; //for each tile in the sequence the color fades
//out then back in
NSLog(@"fadeout");
}];
}
Upvotes: 0
Views: 1475
Reputation: 53000
Your attempt to delay your loop with a while
statement is busy waiting and if this code is executing on your main thread provides no opportunity for your application to process events during the wait. This is the root cause of your problem.
What you need to do is replace your loop with a sequence of actions, each of which performs the work of one iteration, followed by completion blocks, where each completion block performs the "rest" of the loop. One way to do this is shown in this answer - in that case the need was for a simple delay and so the continuation of the "loop" is scheduled using dispatch_after()
.
In you case you can replace your calls to runAction:
with calls to runAction:completion:
to schedule the continuation of your "loop".
HTH
Addendum
Your attempt is a bit mixed up. Here is some untested code type directly into the answer - expect errors!
- (void) doLoop:(int)index
limit:(int)limit
sequenceArray:(NSMutableArray *)sequenceArray
action:(SKAction *)pulse
{
switch ([self getRandomNumber])
{
case 0:
sequenceArray[index] = blueBox; break;
case 1:
sequenceArray[index] = redBox; break;
case 2:
sequenceArray[index] = yellowBox; break;
case 3:
sequenceArray[index] = greenBox; break;
}
[sequenceArray[index] runAction:[SKAction repeatAction:pulse count:1]
completion:^{
// do next "iteration" if there is one
int next = index + 1;
if (next < limit)
[self doLoop:next
limit:limit
sequenceArray:sequenceArray
action:pulse];
}];
}
- (void) chainedSequence
{
NSMutableArray *sequenceArray = [[NSMutableArray alloc] initWithCapacity:50];
SKAction *fadeOut = [SKAction fadeOutWithDuration: 1]; //color of tile fades out
SKAction *fadeIn = [SKAction fadeInWithDuration: 1]; //color of tile fades in
SKAction *pulse = [SKAction sequence:@[fadeOut,fadeIn]]; //holds a sequence so tile fades out then fades in
[self doLoop:0 limit:(level+1) sequenceArray:sequenceArray action:pulse];
}
Make sure you understand what it is doing before using it! The previously referenced answer provides more detail.
The above code passes around and fills up your sequenceArray
, however you never use this in the code you provide other than for temporary storage within the method. Do you really need this array? If so remember that it contents will not be completely valid until the after the last "iteration" even though the call to doLoop:
in chainedSequence
will return before even the first action is complete.
Upvotes: 1
Reputation: 17958
It looks like you're calling this method on the main thread (the same thread which manages all of your application's view drawing). That means that you are queuing up all of your actions and then running them all at one. Your while
delay slows down the entire loop but never yields control for these animations to run. None of your view changes will appear until your sequence
method returns and the current event loop is able to finish.
Instead of waiting for animations to finish that while
is delaying them from starting.
Look at creating an SKAction sequence which exists for exactly this sort of scenario; when you want a number of SKAction
s to fire sequentially. By adding all of your actions to a sequence and then starting the sequence you save yourself from needing to either run a bunch of callback blocks on the main thread or synchronize some background thread selecting blocks with the main thread animating those selections.
Upvotes: 1