Игровой цикл с отдельным таймером для рендеринга и игровой логики

#iphone #ios #timer

#iPhone #iOS #таймер

Вопрос:

Я хочу разделить игровую логику и рендеринг на два разных цикла, потому что я не хочу, чтобы fps контролировал скорость игры. Я попытался добиться этого, создав CADisplayLink для рендеринга и NSTimer для игровой логики. Но затем произошла странная вещь:

Иногда (1 из 15 запусков приложения) игра работает с очень низким fps (около 5-10), но в остальное время все идет абсолютно гладко. Если я удалю NSTimer игровой логики и объединю два цикла, fps будет стабильно высоким, но это, очевидно, неприемлемое решение. Похоже, что иногда два таймера «задерживают друг друга» или что-то в этом роде, но я не совсем понимаю внутреннюю работу runloops.

Вот как я создаю таймер и отображаемую ссылку:

 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;
  

Можете ли вы сказать мне, что вызывает проблему с fps и как это исправить?

Или вы можете порекомендовать какие-либо другие решения для разделения рендеринга и игрового логического цикла?

Комментарии:

1. Вы не должны создавать два цикла для разделения скорости игры и кадров в секунду…

2. Тогда как я могу сделать это с помощью одного цикла?

Ответ №1:

Вы можете сделать это с помощью одного цикла, используя либо ваш GameTimer, либо CADisplayLink, измеряя время, прошедшее с момента последнего цикла, и используя его для улучшения вашей игровой логики.

Итак..

 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];
}
  

Обычно я так справляюсь с подобными вещами. Обычно мне нравится встраивать функции обновления прямо в мои игровые объекты. Таким образом, обновление корабля на самом деле будет выглядеть так:

 [ship update:timePassed_mc];
  

Комментарии:

1. Спасибо за предложение. Это вдохновило меня на окончательное решение (см. Ниже).

Ответ №2:

В итоге я использовал то, что предложил Эндрю Циммер, с некоторыми небольшими изменениями, поскольку я обновляю свои игровые объекты вместе через равные промежутки времени.

Итак, я использую только один цикл, тот, который запускается с помощью CADisplayLink. Вот окончательный код:

 - (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
    (...)
    //
}
  

Комментарии:

1. Рад, что это работает для вас! Имейте в виду, что вы можете увидеть некоторое снижение производительности, если вы выполняете много обработки в функции updateGame (например, обнаружение столкновений между большим количеством объектов). Приветствую!

Ответ №3:

Имейте в виду, что NSTimer не даст вам никакого надежного времени. Это близко, но не гарантировано, и если вы запустите игровой цикл с него, вы получите случайный пропущенный кадр.

Вы могли бы выполнять свою игровую логику в потоке и спать до следующего кадра (это не будет постоянным промежутком времени).

Комментарии:

1. NSTimer не очень надежен, но с точки зрения пользовательского опыта 56 обновлений в секунду или 64 обновления в секунду на самом деле не имеют значения. Но если я обновляю игровые объекты только при рисовании, он может упасть до 20 или 30 обновлений в секунду на более медленных устройствах, когда он выполняется в два раза быстрее на более быстрых устройствах.