1

In an application I'm creating with GLKit, I have a slider puzzle where the user taps on tiles adjacent to an empty tile and the tile slides to the new position. To reduce the number of methods I had to perform a single task, I decided to try handling the entire sliding animation in one single loop.

- (void)slideSelectedTile
{
    while (!self.selectedTile.hasFinishedMoving)
    {
        [self.selectedTile moveByVector:self.tileDisplacement];
        [self render];
        [NSThread sleepForTimeInterval:.1];
    }
}

The problem is that the render calls do not work inside of this loop. The tile is only rendered once the entire loop is over and the game reaches the next glkViewDrawInRect call, where I also call render, which of course contains code for rendering each tile.

I have checked that the current thread is the main thread when I'm in the loop and that I can manually call render without having it in any sort of loop and it works. What am I doing wrong here?

michaelsnowden
  • 6,031
  • 2
  • 38
  • 83

1 Answers1

1

What you are doing wrong here is you missed the whole concept. This while loop of yours will block the thread you use for drawing and displaying on your screen. Though the buffer is redrawn N times your view is not.

Some quick fixes might be calling perform selector on main thread (and do wait until its done) on your render method. This way you could probably even lose the sleep factor. Another way would be to create another animator thread on which slideSelectedTile would be called but again the render will have to be called on your main thread.

In any case without making a nice code design you will get to some issues later. What you should do is have your object store a source location, target location and current location. It should implement some move method that would compute the current location with interpolation: Linear interpolation would be source + (target-source)*(currentTime/animationTime). In most cases the currentTime and animationTime might actually be numbers of frames as in I want the animation to be done in 6 frames the animationTime is 6 while currentTime iterates in [0,6] and the animation is done when currentTime==animationTime. So in the end you would call something like animateTo:(CGPoint)location in:(NSInteger)frameCount which would set currentTime to zero, animationTime to frameCount, source to current location and target to location.

At this point the question is what calls the move method and what takes the current location: The current location must be used on the main thread in your render method. The move method might be called on some new thread which you can control on how often to call it but it can also be called on every frame before (or inside) the render method.

So my suggestion is when animating some openGL driven code you need to have some repeating calls on your render method for instance use a display link. Use this call to do all the rendering while separating all the movement to some external method which has nothing to do with the rendering or openGL at all. This way you can control the movement on a separate thread or in the same one.

About complicating that movement animation method and interpolation: If you implement it the way I described you will be able to stop in the middle of animation and animate it to some other location with no issues at all.

Matic Oblak
  • 16,318
  • 3
  • 24
  • 43
  • Wow dude, that worked incredibly well. Thanks man! I notice you said specifically linear interpolation. Is there anything other than linear interpolation? – michaelsnowden Apr 09 '14 at 06:03
  • Yes, quite many actually. You might want to do something like ease in or ease out. Those two are most easily presented with some power on (currentTime/animationTime) as in pow((currentTime/animationTime), 1.5) would begin slowly and end quickly while using pow((currentTime/animationTime), .75) would begin quickly but end slowly. To do both ease in and ease out you might need a bit more work. In general you can use any function F(X) where F(0) = 0 and F(1) = 1, X in your case is (currentTime/animationTime) and is on interval [0,1]. – Matic Oblak Apr 09 '14 at 06:41
  • I like the idea of creating a separate thread inside the animation method so that I don't even have to call update on the tile from the main loop. Can you point me in the right direction on how to do this? Last question I promise. – michaelsnowden Apr 09 '14 at 15:22
  • Try something like this: [[[NSThread alloc] initWithTarget:item selector:@selector(animate:) object:@(animationFrameCount)] start]; - (void)animate:(NSNumber *)framesLeft { NSInteger frame = [framesLeft integerValue]; if(frame > 0) { //move object static const NSInteger FPS = 60; [NSThread sleepForTimeInterval:1.0/FPS]; [self performSelector:@selector(animate:) onThread:[NSThread currentThread] withObject:@(frame-1) waitUntilDone:YES]; } else { //animation done, exit [NSThread exit]; } } – Matic Oblak Apr 10 '14 at 06:34