Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Game loop with separate timer for rendering and game logic

Tags:

ios

iphone

timer

I want to separate the game logic and the rendering into two different loops, because I don't want the fps to control the game speed. I tried to achieve this by creating a CADisplayLink for the rendering, and an NSTimer for the game logic. But then a strange thing happened:

Sometimes (1 out of 15 application runs) the game runs on a very low fps (about 5-10), but the rest of the times it's completely smooth. If I remove the game logic's NSTimer and combine the two loops the fps is consistently high, but it's obviously not an acceptable solution. So it looks like that sometimes the two timers 'delaying each other' or something like that, but I don't completely understand the inner working of runloops.

Here's how I create the timer and the displaylink:

NSTimer *gameTimer = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:1.0 / 60.0 target:self selector:@selector(gameTimerFired:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:gameTimer forMode:NSDefaultRunLoopMode];
[gameTimer release];

CADisplayLink *aDisplayLink = [[UIScreen mainScreen] displayLinkWithTarget:self selector:@selector(drawFrame)];
[aDisplayLink setFrameInterval:animationFrameInterval];
[aDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
self.displayLink = aDisplayLink;

Can you tell me what causes the fps issue and how to fix it?

Or can you recommend any other solutions to separate the rendering and the game logic loop?

like image 888
mikabast Avatar asked Oct 27 '11 18:10

mikabast


2 Answers

You can do this with one loop using either your gameTimer or the CADisplayLink by measuring the time passed since the last loop and using it to augment your game logic.

So..

NSDate *oldTime = [[NSDate date] retain];

-(void)updateGame {
    NSDate *curTime = [NSDate date];
    NSTimeInterval timePassed_ms = [curTime timeIntervalSinceDate:oldTime] * 1000.0;

    [oldTime release];
    oldTime = curTime;
    [oldTime retain];

    //use the timePassed_ms to augment your game logic.  IE: Moving a ship
    [ship moveLength:ship.velocity * timePassed_ms/1000.0];
}

That's usually the way I handle this sort of stuff. I usually like to build update functions right into my game objects. So updating the ship would actually look like this:

[ship update:timePassed_mc];
like image 82
Andrew Zimmer Avatar answered Oct 20 '22 09:10

Andrew Zimmer


So I ended up using what Andrew Zimmer suggested, with some tiny modifications, since I update my game objects together between equal intervals.

So I'm using only one loop, the one that launched by the CADisplayLink. Here's the final code:

- (void)drawFrame
{
    if (!isGameTimerPaused)
    {
        NSDate *newDate = [NSDate date];
        timeSinceLastUpdate += [newDate timeIntervalSinceDate:lastUpdateDate];

        while (timeSinceLastUpdate > 1.0 / 60.0) 
        {
            [self updateGame]; // UPDATE GAME OBJECTS
            timeSinceLastUpdate -= 1.0 / 60.0;
        }

        [lastUpdateDate release];
        lastUpdateDate = [newDate retain];
    }


    // DRAWING CODE HERE
    (...)
    //
}
like image 40
mikabast Avatar answered Oct 20 '22 09:10

mikabast