Пользователь
0,0
рейтинг
29 августа 2010 в 03:53

Разработка → Пишем игровой движок. Часть первая перевод

Введение


Что такое игровой движок?


Проще говоря, движком является набор систем, которые упрощают наиболее часто используемые функции игры. Движок состоит из подсистем, контролирующих определенные части игры. Большинство игр имеют следующие подсистемы:
  • Графическая подсистема
  • Подсистема ввода
  • Звуковая подсистема
  • Системное ядро

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

Зачем использовать игровой движок?


  • Движок может упрощать (и обычно таки упрощает) процесс разработки игр. Вместо вызова множества библиотечных функций для такой простой задачи, как вывод изображения на экран, можно использовать движок, который сделает это с помощью одной единственной функции.
  • Движок может сделать вашу игру более переносимой. Хорошо спроектированный игровой движок упрощает перенос игры на другую библиотеку или даже на другую платформу. Если бы вы использовали только вызов библиотечных функций напрямую, вам бы пришлось изменить все части игры и, возможно, переделать всю ее структуру. В противном случае, вы могли бы просто портировать определенные подсистемы движка.
  • Игровой движок делает код более организованным и более управляемым. Очень часто я работал над проектами, в которых при добавлении новой функции, игровой код начинал казаться неуправляемым. Движок поможет вам управлять кодом.
  • Движок позволяет работать абстрактно, а не иметь дело с низкоуровневыми представлениями о том, как работает та или иная вещь. При разработке игры вы не хотите беспокоиться о том, как бы сделать все элегантно и не наплодить ошибок в коде. При использовании игрового движка все, что вам нужно знать, это то, как использовать этот движок, что, как правило, проще и имеет более высокий уровень мышления.

Что я планирую охватить?


Принимая во внимание все вышесказанное, вы можете увидеть, почему использование игрового движка полезно. Я планирую охватить разработку 2D движка от начала и до конца. Я буду использовать SDL для ввода, звука и инициализации окна, OpenGL для графики, а также C++ в качестве используемого языка. Если это не удовлетворяет вашему вкусу насчет API и языка, все, о чем я рассказываю, может быть реализовано на любом языке, и я постараюсь сделать код независимым от языка разработки настолько насколько это возможно.

Системное ядро


Что именно делает ядро системы?


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

cStateManager


Что такое менеджер состояний и зачем он нужен?


Менеджер состояний, что следует из названия, позволяет следить за состоянием игры, в котором она сейчас находится. Чтобы прояснить ситуацию, давайте разберем игру. Сначала вы входите в игру и вам показывается логотип создателя или ролик или что-то еще. Давайте назовем это начальным состоянием. Затем вы попадаете в главное меню, где выбираете начало новой игры, сохранение/загрузку игры, изменение настроек или выход. Мы назовем это состоянием главного меню. Затем, после начала новой игры, мы входим в основное состояние игры. Как вы можете заметить, мы должны что-то иметь, что скажет игре, в каком состоянии она находится. Это то, чем занимается менеджер состояний.

Класс CStateManager


Вот класс структуры statemanager, которую я использую
  1. class cStateManager
  2. {
  3.   public:
  4.     cStateManager();
  5.     ~cStateManager();
  6.     bool Push( void (*Function)(void* CallerPtr, Purpose Purp), void* CallerPtr = NULL);
  7.     bool Pop( void* CallerPtr = NULL);
  8.     bool PopAll( void* CallerPtr = NULL);
  9.     bool Process( void* CallerPtr = NULL);
  10.   private:
  11.     sState* m_CurrentState;
  12. };
* This source code was highlighted with Source Code Highlighter.

Менеджер состояний спроектирован в виде стека. Вы можете протолкнуть (Push) состояние в стек и оно станет текущим состоянием игры при переходе к вызванному процессу. Вы можете извлечь (Pop) текущее состояние из стека и вернуться к предыдущему, или можете удалить все состояния с помощью функции PopAll. Последней функцией является функция Process, которая вызывает функцию текущего состояния. После просмотра этого класса вы, наверное, подумали, какая же структура у этого состояния? Что это за CallerPtr? И если вы не знакомы с указателями на функции, функция Push должна выглядеть для вас немного странно.

Структура SState


Отвечая на первый вопрос, определяем sState следующим образом
  1. struct sState
  2. {
  3.   sState* Prev;
  4.   void (*Function)(void* CallerPtr, Purpose Purp);
  5.   sState()
  6.   {
  7.     Prev = NULL;
  8.     Function = NULL;
  9.   }
  10.   ~sState()
  11.   {
  12.     delete Prev;
  13.   }
  14. };
* This source code was highlighted with Source Code Highlighter.

В основном это указатель на предыдущее состояние и указатель на функцию. Указатель на функцию похож на обычный указатель, но вместо значения вызывает функцию, когда она разыменовывается. Эта функция принимает два аргумента: CallerPtr и назначение. Прежде всего позвольте мне объяснить, что такое CallerPtr. Если вы еще не догадались, это просто указатель на вызываемую функцию.

Перечисление EPurpose


Назначение говорит функции для чего она вызывается, чтобы она могла действовать соответственно. Вот класс
  1. enum Purpose
  2. {
  3.   STOP_PURPOSE = 0,
  4.   INIT_PURPOSE,
  5.   FRAME_PURPOSE,
  6.   NO_PURPOSE
  7. };
* This source code was highlighted with Source Code Highlighter.

Здесь необходимы пояснения. Перечисление содержит четыре состояния, так что функция знает, что ей сейчас делать. STOP_PURPOSE говорит функции, что в настоящее время она остановлена, INIT_PURPOSE говорит функции, что идет инициализация, FRAME_PURPOSE говорит, что функция вызывается внутри основного цикла игры (фрейм) и NO_PURPOSE отвечает за все, что не вписывается в эти категории.

Пример использования


Теперь, когда вы увидели код менеджера состояний, вы можете удивиться, а как же его использовать. Вот простенький пример
  1. void fun1(void* CallerPtr, Purpose Purp)
  2. {
  3.   switch(Purp)
  4.   {
  5.     case STOP_PURPOSE:
  6.       cout << "fun1 Stopping" << endl;
  7.       break;
  8.     case INIT_PURPOSE:
  9.       cout << "fun1 Starting" << endl;
  10.       break;
  11.     case FRAME_PURPOSE:
  12.       cout << "fun1 processing" << endl;
  13.       break;
  14.     default:
  15.       cout << "fun1 no purpose" << endl;
  16.       break;
  17.   }
  18. }
  19. void fun2(void* CallerPtr, Purpose Purp)
  20. {
  21.   switch(Purp)
  22.   {
  23.     case STOP_PURPOSE:
  24.       cout << "fun2 Stopping" << endl;
  25.       break;
  26.     case INIT_PURPOSE:
  27.       cout << "fun2 Starting" << endl;
  28.       break;
  29.     case FRAME_PURPOSE:
  30.       cout << "fun2 processing" << endl;
  31.       break;
  32.     default:
  33.       cout << "fun2 no purpose" << endl;
  34.       break;
  35.   }
  36. }
  37. void fun3(void* CallerPtr, Purpose Purp)
  38. {
  39.   switch(Purp)
  40.   {
  41.     case STOP_PURPOSE:
  42.       cout << "fun3 Stopping" << endl;
  43.       break;
  44.     case INIT_PURPOSE:
  45.       cout << "fun3 Starting" << endl;
  46.       break;
  47.     case FRAME_PURPOSE:
  48.       cout << "fun3 processing" << endl;
  49.       break;
  50.     default:
  51.       cout << "fun3 no purpose" << endl;
  52.       break;
  53.   }
  54. }
  55. cStateManager StateMan;
  56. cout << " <process>" << endl;
  57. StateMan.Process(NULL);
  58. cout << " <change to fun1>" << endl;
  59. StateMan.Push(fun1, NULL);
  60. cout << " <change to fun2>" << endl;
  61. StateMan.Push(fun2, NULL);
  62. cout << " <process>" << endl;
  63. StateMan.Process(NULL);
  64. cout << " <kill fun2>" << endl;
  65. StateMan.Pop();
  66. cout << " <change to fun3>" << endl;
  67. StateMan.Push(fun3);
  68. cout << " <process>" << endl;
  69. StateMan.Process(NULL);
  70. cout << " <kill all>" << endl;
  71. StateMan.PopAll(NULL);
  72. cout << " <process>" << endl;
  73. StateMan.Process(NULL);
* This source code was highlighted with Source Code Highlighter.

В принципе, все, что эти три функции делают, это говорят, как называется текущее состояние. Также вы можете заметить, что я положил CallerPtr в NULL для всех функций. Сейчас я ставлю NULL, но потом я покажу вам, как все это используется (cApp). Итак, что же этот код делает?
  1.  <process>
  2.  <change to fun1>
  3. fun1 Starting
  4.  <change to fun2>
  5. fun2 Starting
  6.  <process>
  7. fun2 processing
  8.  <kill fun2>
  9. fun2 Stopping
  10.  <change to fun3>
  11. fun3 Starting
  12.  <process>
  13. fun3 processing
  14.  <kill all>
  15. fun3 Stopping
  16. fun1 Stopping
  17.  <process>
* This source code was highlighted with Source Code Highlighter.

Для ясности я объясню, что здесь происходит. Сначала мы вызываем Process, но пока еще стек пуст, поэтому ничего не происходит. Затем мы проталкиваем fun1, которая начинает инициализироваться. То же происходит и с fun2, затем мы вызываем Process снова. Далее мы останавливаем fun2 и делаем вызов, сообщающий это функции. Теперь мы добавляем fun3 в стек, запуская ее инициализацию, а затем вызываем PopAll, который останавливает fun3 и fun1. Обратите внимание, что порядок завершения обратен порядку вызова. Наконец, Process вызывается еще раз и, конечно, ничего в стеке нет, поэтому ничего и не происходит.

cProcessManager


Что же представляет собой менеджер процессов?


В этом учебнике менеджер процессов представляет собой набор процессов. Когда менеджер запускается, он запускает все процессы в наборе. Итак, почему это полезно? Иногда вместе с состояниями вам может понадобиться динамический список процессов в каждом фрейме. Например, когда вы находитесь в "основном состоянии игры", вам может понадобиться сделать что-то дополнительно, например, вывести количество fps, если игра находится в режиме отладки, или какую-либо другую информации, которую вы найдете полезной. Process Manager позволяет добавить функции к вашей игре без необходимости добавления уродливых 'switch' или 'for' по всему коду. Он также помогает сделать менеджер состояний проще в использовании, если вам понадобится запустить более одной функции во фрейме, менеджер процессов поможет сделать это. Чтобы прояснить ситуацию позвольте сказать, что в определенном состоянии, вам понадобится обрабатывать ввод, производить рендеринг, обновление ваших игроков и проигрывание звуков. Process Manager может объединить все эти отдельные функции в одну, чтобы все они вызывались, когда вы говорите менеджеру процессов выполнять свою работу.

Класс CProcessManager


Вот моя реализация класса cProcessManager
  1. class cProcessManager
  2. {
  3.   public:
  4.     cProcessManager();
  5.     ~cProcessManager();
  6.     int Push( void (*Function)(void* CallerPtr, Purpose Purp), void* CallerPtr = NULL);
  7.     bool Pop( unsigned int id, void* CallerPtr = NULL);
  8.     bool PopAll( void* CallerPtr = NULL);
  9.     bool Process( void* CallerPtr = NULL);
  10.   private:
  11.     sProcess* m_FirstProcess;
  12.     sProcess* m_LastProcess;
  13. };
* This source code was highlighted with Source Code Highlighter.

Как вы можете заметить, это очень похоже на класс менеджера состояний. Вы можете протолкнуть и извлечь процесс, как и в менеджер состояний, но погодите-ка, что это за переменная 'ID'? Проще говоря, ID представляет собой уникальный идентификатор для каждого процесса в менеджере. При проталкивании процесса он либо возвращает 0 связи с ошибкой или положительное число, свидетельствующее, что идентификатор был назначен этому процессу. Точно так же Pop принимает идентификатор для удаления процесса. Последняя функция, Process, выполняет все процессы в том порядке, в котором они были добавлены. Достаточно просто, не так ли?

Структура SProcess


  1. struct sProcess
  2. {
  3.   sProcess* Next;
  4.   sProcess* Prev;
  5.   void (*Function)(void* CallerPtr, Purpose Purp);
  6.   unsigned int Id;
  7.   sProcess()
  8.   {
  9.     Prev = NULL;
  10.     Next = NULL;
  11.     Function = NULL;
  12.     Id = 0;
  13.   }
  14.   ~sProcess()
  15.   {
  16.     delete Prev;
  17.     delete Next;
  18.   }
  19. };
* This source code was highlighted with Source Code Highlighter.

Как и ранее эта структура похожа на структуру sState. Здесь действительно очень мало отличий, я добавил 'Id' идентификации процессов, и указатель на предыдущий процесс, чтобы сделать свою жизнь проще при программировании менеджера процессов (смотрите код и вы поймете почему).

Пример


  1. cProcessManager ProcMan;
  2.   cout << " <process>" << endl;
  3.   ProcMan.Process(NULL);
  4.   cout << " <create fun1>" << endl;
  5.   int id1 = ProcMan.Push(fun1, NULL);
  6.   cout << " <create fun2>" << endl;
  7.   ProcMan.Push(fun2, NULL);
  8.   cout << " <process>" << endl;
  9.   ProcMan.Process(NULL);
  10.   cout << " <kill fun1>" << endl;
  11.   ProcMan.Pop(id1);
  12.   cout << " <create fun 3>" << endl;
  13.   ProcMan.Push(fun3);
  14.   cout << " <process>" << endl;
  15.   ProcMan.Process(NULL);
  16.   cout << " <kill all>" << endl;
  17.   ProcMan.PopAll(NULL);
  18.   cout << " <process>" << endl;
  19.   ProcMan.Process(NULL);
* This source code was highlighted with Source Code Highlighter.

Заметьте, что я пропустил код функций fun1, fun2 и fun3. Они такие же, как и у statemanager. Кроме того, этот код поразительно похож на код менеджера процессов. На самом деле все, что я сделал, написал cProcessManager вместо cStateManager и изменил StateMan на ProcMan. Разница только в том, как останавливается процесс. При остановке процесса вы должны дать менеджеру идентификатор, показывающий какой процесс нужно остановить. Я добавил его, чтобы показать вам разницу между двумя менеджерами. Вперед за результатами.
  1.  <process>
  2.  <create fun1>
  3. fun1 Starting
  4.  <create fun2>
  5. fun2 Starting
  6.  <process>
  7. fun1 processing
  8. fun2 processing
  9.  <kill fun1>
  10. fun1 Stopping
  11.  <create fun 3>
  12. fun3 Starting
  13.  <process>
  14. fun2 processing
  15. fun3 processing
  16.  <kill all>
  17. fun2 Stopping
  18. fun3 Stopping
  19.  <process>
* This source code was highlighted with Source Code Highlighter.

Есть несколько тонких различий в результатах работы каждого менеджера. Первое в том, когда вы вызываете процесс, все функции вызываются в том порядке, в котором они были добавлены. Второе отличие в том, когда вы вызываете PopAll, процессы, вместо того чтобы останавливаться в обратном порядке, как в стеке, останавливаются в том, в котором они были добавлены.

cApp


cApp? Что за cApp?


Наконец, мы получаем последний класс в ядре системы. Класс cApp производит основную инициализацию окна и вызывает функцию Run, с которой начинается наша игра. Без лишних церемоний вот оно
  1. class cApp
  2. {
  3.   public:
  4.     cApp();
  5.     virtual ~cApp();
  6.     virtual void Init() = 0;
  7.     virtual void Run() = 0;
  8.     virtual void Shutdown() = 0;
  9.     virtual void ResizeWindow(int width, int height) = 0;
  10.     virtual void ToggleFullscreen() = 0;
  11.   protected:
  12.     int m_Width;
  13.     int m_Height;
  14.     int m_Bpp;
  15.     std::string m_WindowName;
  16.     bool m_FullScreen;
  17.     SDL_Surface* m_Surface;
  18.     Uint32 m_SdlFlags;
  19. };
* This source code was highlighted with Source Code Highlighter.

Первое, что я замечаю, когда смотрю на это, это то, что класс на самом деле ничего не делает. Это значит, что вам придется реализовать свой класс cApp в зависимости от того, что вы хотите от своей игры. Вы можете поинтересоваться, если класс cApp был реализован статически, то что будет в его функциях? Ну в основном, функция Init, инициализирующая оконную систему и все библиотеки, которые будут использоваться. В моем исходном коде вы можете увидеть, что я инициализирую окно через SDL, а затем настроиваю OpenGl, но я, вероятно, позже перенесу эти OpenGL функции в графическое ядро. Следующая функция называется Run, в ней не так много кода на данный момент. Здесь должна быть установка состояния по умолчанию при запуске, и основной игровой цикл в противном случае. Также у нас есть функция Shutdown, которая, как вы наверное догадались, завершает игру. Сейчас Shutdown почти пустая. Следующая функция ResizeWindow, которая принимает на вход ширину и высоту, вновь эта функция на самом деле ничего не делает. Последняя, но не менее важная, у нас ToggleFullscreen, которая делает то, что следует из ее названия. Переключает игру между полноэкранным и оконным режимами. Также у нас есть довольно много переменных. Если вы знакомы с SDL, то вам не должно составить труда выяснить, для чего они используются.

cApp ничего не делает. Что я делаю не так?


cApp это просто интерфейс, обеспечивающий инициализацию, запуск, завершение и другие функции, которые работают с окном. Цель этой серии уроков не научить вас работе с SDL или какой-либо другой библиотекой, которую вы будете использовать. Речь идет о разработке игрового движка, поэтому я не буду вдаваться в подробности о том, как я реализовал эти функции (вы можете прочитать мой исходный код, если вы действительно хотите знать). Есть много учебников, которые помогут вам реализовать эти функции, если вы потерялись.

Заключение


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

О моем исходном коде


Видя, что это первый урок в серии, мне нужно сказать кое-что об исходном коде. Мой код создан в XCode 2.2. Это значит, что вы можете скомпилировать его на любом Mac с установленным XCode. XCode использует GCC версии 3.3, так что мой код будет переносимым на любую другую платформу. Единственное, что вам нужно будет изменить заголовки. Цель этого кода показать вам, как я реализовал движок и послужить реальным примером того, как он работает. В большинстве случаев, вам не нужно будет компилировать код, чтобы понять, что он делает, так как я стараюсь комментировать настолько, насколько это возможно. Еще стоит сказать, что это мой код, предназначенный для данной статьи, и если вы решили использовать его в своей игре, я бы хотел об этом знать.

Скачать исходник


Исходные статьи


Введение
Ядро системы



UPD: Вторая часть
Перевод: Sean Chapel
Николай @Robotex
карма
4,2
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (34)

  • +4
    Если кто-то может перенести в Game Development, буду рад :)
    • +3
      Только вы можете пренести статью в блог. Для этого вам нужно 5 кармы, предварительно вступив в блог в котором будет размещена статья.
      • +25
        Думаю автор это знает, просто как бэ намекает…
      • +2
        Перенес. Спасибо за помощь :)
  • +3
    Единственное, что из мира программирования для меня практически не познано — разработка игр. Спасибо, буду ждать продолжения.
    • НЛО прилетело и опубликовало эту надпись здесь
  • 0
    Спасибо очень за статью :) сам пишу сейчас игрушку SDL/C++
    Еще советую почитать:
    gpwiki.org/
    lazyfoo.net/SDL_tutorials/
    Хоть и на английском, но без этого никуда :)
    Побольше статьей автору, спасибо :)
    • 0
      это и есть с gpwiki.org :)
      будет продолжение?
      • +1
        Конечно :)
  • +8
    Сам по себе движок практически не имеет никакой полезной нагрузки. Снабжённый инструментами — редактором карт, скриптов, импортёром моделей, текстур и анимаций, паковщиком (т.н. cook), редактором шейдеров и систем частиц — он приобретает нужный смысл. В качестве грамотного SDK можно привести в пример Unreal Development Kit. Он бесплатен, можно спокойно повозиться в нём, и набраться нужных знаний для разработки своего. Ещё хороший пример — Unity3D. А плохой пример — Ogre3D, несмотря на общий высокий уровень движка (на нём сделаны Torchlight и Sacraboar, к примеру), ему не достаёт нужных инструментов. Правда есть очень дешёвая (100 долларов для инди и 400 для компаний) платная версия под названием NeoAxis Engine — там всё включено + оптимизации. Именно на этой версии сделан Sacraboar.
    • 0
      Сдесь не идет о 3D движках, и главное для чего эта статья — показать все изнутри, основы…
      Я например вообще не имел понятия как это делается, поэтому и начал писать игрушку — для себя, для опыта.
      Unreal Development Kit бесплатен только для некомерческого использования.
      • 0
        Я понимаю, статья хорошая, в лёгкой форме и про конечные автоматы рассказано, и про менеджер процессов. Просто дополнил. Обычно начинающие программисты считают, что написав движок сразу смогут создать гениальную игру. Увы, мечта быстро разбивается о трудности как написания движка, так и понимание того, что сам по себе движок бесполезен и нужны ещё инструменты, а помимо них — ещё и контентщики. А для просто поковырять и посмотреть как реализовано некоммерческого использования вполне достаточно — рекомендую, очень быстро промывает мозги.
  • –9
    Всё конечно понятно, но зачем изобретать велосипед? Если только для личного опыта.
    • +3
      Именно для личного опыта…
    • +1
      Для того, чтобы использовать уже готовое решение, нужно хотя бы немного понимать, как оно устроено…
  • +9
    Вспомнилось читал в далеком 95г

  • +1
    Сам давно лелеял мечту написать свой игровой движок, и сейчас, после трёх-четырёх уродливых эмбрионов делаю новый, чтобы на нём делать курсач, мало связанный с играми)
    Тоже использую связку SDL/OpenGL/C++ — пока только под виндами, но стараюсь проектировать движок так, чтобы потом попробовать перенести на иксы.
    Вообще делать игровой движок — действительно полезное занятие, даже если нет планов писать игры/прочие графические приложения — очень хорошо помогает помять мозги и изучить что-то новое и интересное
  • +2
    Google for «initialization list».
  • 0
    Торт!
  • 0
    прикольно. ) только как мне кажется игровой движок — малая, если не самая малая, проблема для свежеиспеченого разработчика компьютерной игры. а так, да, позновательно.
  • +2
    Зачем нужны эти надуманные сущности??? ProcessManager? Статья ни о чем!
    • –1
      Более того, скажу, что начинать разработку игры с написания движка — это путь в никуда. Проще взять готовый движок и начинать делать игру, а не заниматься тем, что к игре, по сути, отношения не имеет и потом еще отлавливать тонны багов в самописном движке.
      Основное преимущество готовых движков не в том, что они удобные (хотя не без этого), а в том, что в них уже исправлены многие баги, которых невозможно избежать при самостоятельной разработке движка.
  • +1
    Во всех примерах разная нотация именования переменных, примеры думаю видите сами, одного то присутствия то отсутствия m_ уже достаточно.
    Поинтеры не проверяем, действительно а зачем? Они же всегда валидны! Пишем {sProcess p;} и получаем удаление нулл-поинтера, супер.
    а за c и s в именах классов лично я даю по рукам, при грамотном проектировании должно быть плевать класс это, енум или структура. Если возникают вопросы значит это проблема.

    Про смысловую нагрузку всех этих классов я вообще молчу, тут итак все ясно.

    Короче доверия к таким учебникам, как к книжкам от «Жарков Пресс», извините конечно.

    Лучше бы взяли SexyAppFramework от PopCap и разобрали по полочкам, было бы больше пользы, имхо конечно.
    • +1
      void* pPointer = NULL;
      delete pPointer; // это вполне допустимо стандартом C++
      


      Хотя сам по привычке всеравно проверяю :)
      Другое дело, что важно после
      delete pPointer;

      делать
      pPointer = NULL;

      Чтобы если не дай бох деструктор вызовется второй раз, то не произойдет осовобождения по невалидному адресу, и не начнутся странные трудновоспроизводимые баги.
      • НЛО прилетело и опубликовало эту надпись здесь
        • 0
          А я-то тут причем? Автору поста пишите. Я в работе вообще reference pointer'ы использую (http://yasper.sourceforge.net/).
          • НЛО прилетело и опубликовало эту надпись здесь
            • 0
              Угу. Не знаю почему данный пост набрал столько плюсов, имхо такой код писать нельзя вообще :)
              • 0
                Смысл статьи не в коде, а в том, чтобы показать примерную архитектуру. Многие даже не знают что такое game loop.
                • 0
                  Для начинающих код должен быть простым и понятным, приведенный в статье ужасен и сложен сверх необходимого.

                  Пример на PopCap был бы куда лучше. И не было бы даже необходимости знать про game loop.
                  • 0
                    Ссылка на сайт автора в конце статьи.
  • 0
    убивал бы за такое void (*Function)(void* CallerPtr, Purpose Purp);!!!
    это же С++!!! может стоит сначала книжек почитать??

    за такое void fun2(void* CallerPtr, Purpose Purp)
    {
    switch(Purp)
    {
    case STOP_PURPOSE:
    cout << «fun2 Stopping» << endl;
    break;
    case INIT_PURPOSE:
    cout << «fun2 Starting» << endl;
    break;
    case FRAME_PURPOSE:
    cout << «fun2 processing» << endl;
    break;
    default:
    cout << «fun2 no purpose» << endl;
    break;
    }
    }

    остаток жизни называл бы говнокодером… это сейчас в этой функции 10 строк, как в ней разбираться когда строк станет 100 или 1000… еще раз учите С++!!! в нем можно обойтись без таких извращений…
    • 0
      Почему вы все умничаете в комментариях, а указать, что именно не так не можете? У вас пост состоит из кучи восклицательных знаков и упрёков в сторону автора, при этом как надо делать вы так и не сказали. Пользы — ноль от вашего поста.
  • 0
    класс cApp не нужен…
    вот сами подумайте зачем нам инициализация в 2 местах

    сначала в main нужно создать нужный объект класса унаследованного от cApp, вызвать его функции Init, Run (предварительно их реализовав) и тд. если мы прям из этого же майна можем вызвать нужные функции инициализации… гляньте как это сделано в Irrlicht и вам сразу станет понятно что так удобнее и в дальнейшем не возникнет желания плодить синглтоны из наследников класса сАрр…

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.