Как стать автором
Обновить

Основы создания игрового движка: таймер

Время на прочтение 6 мин
Количество просмотров 8.5K
Публикуется по просьбе моего знакомого, если вам понравится статья, можете выслать ему инвайт.
Мыло скажу в ПМ или могу написать здесь же. :)


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

Цель: написать точный таймер для использования в игровом движке (а это значит, что помимо отмерения отрезков времени нам необходимо обеспечить работу событий, возникающих через заданное время).

Для начала необходимо решить, что использовать для измерения временных отрезков вообще. Самым логичным решением относительно точности измерения времени является использование функций QueryPerformanceCounter/QueryPerformanceFrequency. Смысл измерения времени заключается в запоминании начала временного отрезка, затем проверок текущего времени и возвращении результата — разницы между двумя замерами. Но нам необходимо сделать «продолжительное» время, т. е. надо будет наперед знать, когда пройдет, например, ровно 3 секунды от текущего момента времени.

Для обеспечения такого способа работы таймера можно поступить следующим образом: хранить «события» вместе с количеством времени до их начала в классе менеджера событий и, постоянно отмеряя временные отрезки (начало следующего = конец текущего), добавлять отрезки времени к переменной, изначально равной 0, и как только в каком-либо из событий значение переменной станет больше или равно количеству времени до начала события, выполнять событие.

Количество времени до начала события назовем его длительностью, т. к. событие логически не всегда означает выполнение действия после прошествия времени до его начала, а порой и выполнение действий или сохранение какого-либо статуса во время «ожидания его начала». Для примера — в моем движке события используются для обеспечения точного количества выстрелов в секунду (после каждого выстрела запрещается стрельба и разрешается через заданное время, т.о. получается, что событие служит для сохранения статуса «запрещена стрельба»).

Остается подумать лишь над тем, как собственно проверять-запоминать эти временные отрезки так, чтобы это было последовательно и непрерывно. Логичным решением здесь будет использование частоты кадров и замера отрезка времени на каждом кадре. Т. к. функции QueryPerformanceCounter/QueryPerformanceFrequency сильно загружают процессор, имеет смысл ограничить чатоту кадров, выполняя Sleep после каждого из них. Грубость функции Sleep здесь не имеет значения, т. к. время будет замеряться непрерывно, т. е. между замерянными отрезками не будет промежутков.

Теперь немного кода для демонстрации вышеописанного:

Полный класс таймера:
de_timer.h:
Copy Source | Copy HTML<br/>#ifndef __DE_TIMER_H<br/>#define __DE_TIMER_H<br/> <br/>#include <windows.h><br/> <br/>//класс таймера<br/>class deTimer<br/>{<br/>  LARGE_INTEGER start; //начальное время<br/>  double frequency; //частота таймера<br/>  double tickLength; //длина одного тика таймера<br/>  double currentCheckTime; //текущий замер времени<br/>  double lastCheckTime; //предыдущий замер времени<br/>  //инициализировать таймер<br/>  void init();<br/>  //вернуть частоту таймера<br/>  double getFrequency();<br/>  //вернуть время (от начального времени)<br/>  double readTimer();<br/>public:<br/>  //запустить таймер<br/>  void startTimer();<br/>  //обновить таймер<br/>  void updateTimer();<br/>  //получить временной отрезок между текущим замером и предыдущим<br/>  //обратите внимание: время здесь не замеряется,<br/>  // только лишь возвращается уже замерянный результат<br/>  double getTimeInterval() {return currentCheckTime - lastCheckTime;}<br/>};<br/> <br/>#endif<br/> <br/>


de_timer.cpp:
Copy Source | Copy HTML<br/>#include "de_timer.h"<br/> <br/>//инициализировать таймер<br/>void deTimer::init()<br/>{<br/>  //получить частоту таймера и длину тика<br/>  frequency = getFrequency();<br/>  tickLength = 1. 0 / frequency;<br/>}<br/> <br/>//вернуть частоту таймера<br/>double deTimer::getFrequency()<br/>{<br/>  LARGE_INTEGER freq;<br/> <br/>  if (!QueryPerformanceFrequency(&freq))<br/>    return  0;<br/>  return (double)freq.QuadPart;<br/>}<br/> <br/>//вернуть время (от начального времени)<br/>double deTimer::readTimer()<br/>{<br/>  //использовать только первое ядро процессора<br/>  DWORD_PTR oldMask = SetThreadAffinityMask(GetCurrentThread(),  0);<br/>  //засечь текущее время<br/>  LARGE_INTEGER currentTime;<br/>  QueryPerformanceCounter(&currentTime);<br/>  //восстановить маску использования ядер процессора<br/>  SetThreadAffinityMask(GetCurrentThread(), oldMask);<br/>  //вернуть текущее время<br/>  return (currentTime.QuadPart - start.QuadPart) * tickLength;<br/>}<br/> <br/>//запустить таймер<br/>void deTimer::startTimer()<br/>{<br/>  //инициализировать таймер<br/>  init();<br/>  //использовать только первое ядро процессора<br/>  DWORD_PTR oldMask = SetThreadAffinityMask(GetCurrentThread(),  0);<br/>  //засечь текущее время<br/>  QueryPerformanceCounter(&start);<br/>  //восстановить маску использования ядер процессора<br/>  SetThreadAffinityMask(GetCurrentThread(), oldMask);<br/>  //инициализировать текущий и прошлый замер времени<br/>  lastCheckTime = currentCheckTime = readTimer();<br/>  //инициализировать рандомайзер (можно убрать из класса в принципе)<br/>  srand((int)start.QuadPart);<br/>}<br/> <br/>//обновить таймер<br/>void deTimer::updateTimer()<br/>{<br/>  //текущий замер становится прошлым<br/>  lastCheckTime = currentCheckTime;<br/>  //текущий замеряется<br/>  currentCheckTime = readTimer();<br/>}<br/> <br/>


Остальной код движка в текущем его состоянии вы можете посмотреть на sourceforge.net/projects/dustengine
Код таймера и менеджера событий находится в файлах:
de_timer.h, de_timer.cpp, de_event.h, de_event_manager.h, de_event_manager.cpp
Версия движка на момента написания статьи: 0.1

Сразу оговорюсь, что код написан мною, несмотря на комментарии на английском языке (вырабатываю привычку использовать английский в проектах).

UPD. Экзешник на sf.net только для win7 (!) (возможно также будет работать на висте), на winXP работает при установке совместимости с win98 (странно, но факт). Все это компилилось на win7 и под win7.
Теги:
Хабы:
+1
Комментарии 39
Комментарии Комментарии 39

Публикации

Истории

Работа

Программист C++
122 вакансии
QT разработчик
13 вакансий

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн