4

Моя цель - создать эффективный игровой цикл, который использует requestAnimationFrame для обновления холста дисплея и setTimeout для обновления игровой логики. Мой вопрос в том, должен ли я делать все операции рисования внутри цикла requestAnimationFrame или только основной операции рисования, которая обновляет холст html?Как следует использовать requestAnimationFrame и setTimeout параллельно, чтобы создать лучший игровой цикл?

Что я имею в виду под «всеми операциями рисования» - это все буферизация. Например, я нарисую все спрайты в буфер, а затем нарисую буфер на основной холст. С одной стороны, если я поместил всю буферизацию в requestAnimationFrame, я не буду тратить впустую процессор на каждое обновление логики, с другой стороны, чертеж тяжелый процессор и может привести к тому, что requestAniomationFrame дождитесь завершения всех этих операций ... Точка отделения логических обновлений от чертежа такова, что requestAnimationFrame не увязнет в обработке без рисования.

Есть ли у кого-нибудь опыт работы с этим подходом к созданию игрового цикла? И не говорите «просто поместите все это в requestAnimationFrame», потому что это замедляет рендеринг. Я убежден, что отделить логику от рисования - путь. Вот пример того, что я говорю:

/* The drawing loop. */ 
function render(time_stamp_){//First parameter of RAF callback is timestamp. 
    window.requestAnimationFrame(render); 

    /* Draw all my sprites in the render function? */ 
    /* Or should I move this to the logic loop? */ 
    for (var i=sprites.length-1;i>-1;i--){ 
     sprites[i].drawTo(buffer); 
    } 

    /* Update the on screen canvas. */ 
    display.drawImage(buffer.canvas,0,0,100,100,0,0,100,100); 
} 

/* The logic loop. */ 
function update(){ 
    window.setTimeout(update,20); 

    /* Update all my sprites. */ 
    for (var i=sprites.length-1;i>-1;i--){ 
     sprites[i].update(); 
    } 
} 

Спасибо!

Edit:

Я решил пойти с веб-рабочих, чтобы полностью отделить логику игры от рисунка, который, насколько я понимаю, должен иметь место в главном сценарии загруженного DOM.

+1

Если 'update' работает медленно, он все равно блокирует RAF. Асинхронный код не означает, что он многопоточен. Попытайтесь использовать работников, если у вас проблемы с производительностью. – zzzzBov

+0

Я слышал о рабочих, знаете ли вы о хорошей документации? И являются ли они способом многопоточного JavaScript? – Frank

+0

Хороший учебник для веб-мастеров: http://www.html5rocks.com/en/tutorials/workers/basics/ убедитесь, что в системах, которые их не поддерживают (или не являются многоядерными), у вас есть резерв ! – Rias

ответ

2

Итак, я никогда не нашел отличный способ разделить логику и рисунок, потому что JavaScript использует один поток. Независимо от того, что я делаю, выполнение функции рисования может мешать логике или наоборот. То, что я сделал, это найти способ выполнить их наиболее своевременно, а также обеспечить постоянное обновление времени для логики и оптимизированного чертежа с использованием requestAnimation Frame. Эта система настроена на интерполяцию анимаций, чтобы компенсировать пропущенные кадры, если устройство слишком медленно рисовать с требуемой частотой кадров. Во всяком случае, вот мой код.

var engine = { 
     /* FUNCTIONS. */ 
     /* Starts the engine. */ 
     /* interval_ is the number of milliseconds to wait between updating the logic. */ 
     start : function(interval_) { 
      /* The accumulated_time is how much time has passed between the last logic update and the most recent call to render. */ 
      var accumulated_time = interval_; 
      /* The current time is the current time of the most recent call to render. */ 
      var current_time = undefined; 
      /* The amount of time between the second most recent call to render and the most recent call to render. */ 
      var elapsed_time = undefined; 
      /* You need a reference to this in order to keep track of timeout and requestAnimationFrame ids inside the loop. */ 
      var handle = this; 
      /* The last time render was called, as in the time that the second most recent call to render was made. */ 
      var last_time = Date.now(); 

      /* Here are the functions to be looped. */ 
      /* They loop by setting up callbacks to themselves inside their own execution, thus creating a string of endless callbacks unless intentionally stopped. */ 
      /* Each function is defined and called immediately using those fancy parenthesis. This keeps the functions totally private. Any scope above them won't know they exist! */ 
      /* You want to call the logic function first so the drawing function will have something to work with. */ 
      (function logic() { 
       /* Set up the next callback to logic to perpetuate the loop! */ 
       handle.timeout = window.setTimeout(logic, interval_); 

       /* This is all pretty much just used to add onto the accumulated time since the last update. */ 
       current_time = Date.now(); 
       /* Really, I don't even need an elapsed time variable. I could just add the computation right onto accumulated time and save some allocation. */ 
       elapsed_time = current_time - last_time; 
       last_time = current_time; 

       accumulated_time += elapsed_time; 

       /* Now you want to update once for every time interval_ can fit into accumulated_time. */ 
       while (accumulated_time >= interval_) { 
        /* Update the logic!!!!!!!!!!!!!!!! */ 
        red_square.update(); 

        accumulated_time -= interval_; 
       } 
      })(); 

      /* The reason for keeping the logic and drawing loops separate even though they're executing in the same thread asynchronously is because of the nature of timer based updates in an asynchronously updating environment. */ 
      /* You don't want to waste any time when it comes to updating; any "naps" taken by the processor should be at the very end of a cycle after everything has already been processed. */ 
      /* So, say your logic is wrapped in your RAF loop: it's only going to run whenever RAF says it's ready to draw. */ 
      /* If you want your logic to run as consistently as possible on a set interval, it's best to keep it separate, because even if it has to wait for the RAF or input events to be processed, it still might naturally happen before or after those events, and we don't want to force it to occur at an earlier or later time if we don't have to. */ 
      /* Ultimately, keeping these separate will allow them to execute in a more efficient manner rather than waiting when they don't have to. */ 
      /* And since logic is way faster to update than drawing, drawing won't have to wait that long for updates to finish, should they happen before RAF. */ 

      /* time_stamp_ is an argument accepted by the callback function of RAF. It records a high resolution time stamp of when the function was first executed. */ 
      (function render(time_stamp_) { 
       /* Set up the next callback to RAF to perpetuate the loop! */ 
       handle.animation_frame = window.requestAnimationFrame(render); 

       /* You don't want to render if your accumulated time is greater than interval_. */ 
       /* This is dropping a frame when your refresh rate is faster than your logic can update. */ 
       /* But it's dropped for a good reason. If interval > accumulated_time, then no new updates have occurred recently, so you'd just be redrawing the same old scene, anyway. */ 
       if (accumulated_time < interval_) { 
        buffer.clearRect(0, 0, buffer.canvas.width, buffer.canvas.height); 

        /* accumulated_time/interval_ is the time step. */ 
        /* It should always be less than 1. */ 
        red_square.draw(accumulated_time/interval_); 

        html.output.innerHTML = "Number of warps: " + red_square.number_of_warps; 

        /* Always do this last. */ 
        /* This updates the actual display canvas. */ 
        display.clearRect(0, 0, display.canvas.width, display.canvas.height); 
        display.drawImage(buffer.canvas, 0, 0, buffer.canvas.width, buffer.canvas.height, 0, 0, display.canvas.width, display.canvas.height); 
       } 
      })(); 
     }, 
     /* Stops the engine by killing the timeout and the RAF. */ 
     stop : function() { 
      window.cancelAnimationFrame(this.animation_frame); 
      window.clearTimeout(this.timeout); 
      this.animation_frame = this.timeout = undefined; 
     }, 
     /* VARIABLES. */ 
     animation_frame : undefined, 
     timeout : undefined 
    }; 

Это разорван прямо из одного из моих проектов, так что есть несколько переменных в там, которые определены в других местах в коде. red_square - одна из этих переменных.Если вы хотите проверить полный пример, взгляните на мою страницу github! userpoth.github.io Кроме того, сторонняя заметка, я попытался использовать веб-работников, чтобы отделить логику, и это был несчастный сбой. Работники веб-приложений замечательны, когда у вас много математики, и очень мало объектов для передачи между потоками, но они не могут рисовать, и они медленны с большими передачами данных, по крайней мере, в контексте игровой логики.

1

Как я понимаю ваш вопрос, ключевые моменты

  1. Я хочу, чтобы обновить экран как можно чаще
  2. Существует несколько дорогостоящих операций, которые я не хочу делать на каждом обновлении экрана , Конечно, это означает, что есть другие вещи для обновления, если нет, предыдущая точка бесполезна.
  3. У меня нет и не может быть флажка, указывающего, что необходимы предыдущие операции. Обратите внимание, что это разумный способ сделать это, а другие варианты являются альтернативным выбором, если это невозможно.

В вашем коде вы решили выполнить эту операцию 20 раз в секунду.

В этом случае я установил временную метку, указывающую, были ли выполнены эти операции.

В коде requestAnimationFrame проверьте, была ли эта временная метка в возрасте более 1/20 с, а затем выполните код.

+0

К сожалению, я не могу избежать проблемы с помощью одного потока. Независимо от того, где я помещаю интенсивную логику процессора, она будет выполняться до или после «requestAnimationFrame» асинхронно. В лучшем случае я буду чаще использовать 'requestAnimationFrame', но у меня не будет никаких новых логических обновлений для рендеринга, поэтому бессмысленно даже попробовать без второго потока. Я думаю, мне придется больше смотреть в веб-работников и переносимые объекты, если я собираюсь по-настоящему отделить логику от чертежа. – Frank

 Смежные вопросы

  • Нет связанных вопросов^_^