Шаг 5

Итак, сегодня мы займемся серьезным делом. Сделаем две Lua библиотеки - sdl и opengl. Первая - это кроссплатформенная библиотека для работы с окнами, вторая - трехмерная графическая библиотека. Позже, на основе этого примера, мы сможем соорудить что-нибудь еще более интересное. На этом шаге будет показано несколько важных вещей. Во-первых, мы создадим "большой" проект, то есть проект, состоящий из нескольких подпроектов. Для этого нам нужно будет создать структуру папок, в которых будут лежать сами проекты и некоторый вспомогательный код. Во-вторых, будет показано, как готовые библиотеки превращать в Lua-модули. Вспомогательный код, облегчающий взаимодействие с Lua, подробно описан в Шаге 4. Вот, вкратце, круг ближайших задач. Приступим.

Для написания и компиляции кода С++ будем использовать бесплатную IDE Codeblocks (http://www.codeblocks.org). Для написания кода Lua подойдет любой текстовый редактор. Я предпочитаю бесплатный Notepad++ (http://notepad-plus.sourceforge.net), который умеет подсвечивать синтаксис Lua.
Итак, сначала нам нужно организовать наше рабочее место. Создадим папку для нашего проекта. Я ее назову lualibs.
В ней создадим следующие папки:
 В папке modules создадим следующие папки:
Рабочее место подготовили. Теперь нам нужно взять в интернете библиотеки, которые мы будем использовать. Как минимум, нам понадобится библиотека Lua. Идем на http://www.lua.org. Далее идем в download -> binaries-> download. Скачиваем lua5_1_2_Win32_bin.zip. Распаковываем его в нашу папку bin. Также скачиваем lua5_1_2_Win32_dll_lib.zip и распаковываем lua5.1.lib в папку lib, а в нашей папке include создаем папку lua и в нее распаковываем файлы из папки архива include.
Проверим, все ли правильно мы сделали. Для этого создадим тестовый консольный проект. 
Запускаем Code::blocks. Создаем новое консольное приложение. Файл проекта console.cbp сохраним в папке modules. Настроим проект. В меню Project->Properties->вкладка Targets. Настроим Output filename так, чтобы получившийся файл .exe помещался в папку bin. У меня это выглядит так:



Далее нажимаем кнопку Target's build options. Переходим на вкладку Linker.Добавляем библиотеку lua5.1.lib.



Переходим на вкладку Directories. В ней на вкладке Compiler добавляем нашу папку include.



Нажимаем Ok. В файл main.cpp добавляем строки:

#include "lua/lua.hpp"

и первой строкой в функции main():

lua_State* L = lua_open();

Запускаем программу на выполнение: меню Build->Run. Должно напечататься "Hello, world!". Отлично, треть дела сделана!

Теперь сделаем Lua-модуль для библиотеки sdl. Идем на http://www.libsdl.org. Далее Download->SDL 1.2.В секции Source Code скачиваем SDL-1.2.12.zip и распаковываем содержимое папки include в нашу папку include/sdl. В секции Runtime Libraries->Win32 скачиваем SDL-1.2.12-win32.zip. Распаковываем sdl.dll в папку bin. В секции Development Libraries->Win32 скачиваем SDL-devel-1.2.12-VC6.zip. Файлы .lib распаковываем в папку lib.
Идем в Code::blocks. Создаем новый dll проект. Назывем его sdl и сохраняем в папке modules/sdl. Настраиваем для проекта пути и библиотеки. По аналогии с тестовым проектом изменяем расположение результирующего dll файла на ../../bin/lua-sdl.dll.



Далее снова кнопка Target's build options. Добавляем в проект библиотеки lua и sdl.



Затем открываем вкладку Directories. Настраиваем пути к заголовочным файлам lua, sdl и утилит lua.



Удалим из проекта созданный по умолчанию файл main.c и создадим  новый файл sdl.cpp. Это будет модуль Lua. Как изготавливать модули Lua подробно рассказано в Шаге 3. Наш модуль будет называться sdl и в Lua он будет экспортировать две функции - Create() и Run(). Для функционирования самого модуля скорее всего понадобятся еще какие-нибудь функции, но из Lua будут доступны (пока) только эти две. Позже добавим к ним другие функции.
Создадим "рыбу" модуля, то есть сначала напишем весь необходимый код для регистрации функций Create() и Run() в Lua, а затем заполним тела функций реальным содержанием. На этом этапе файл sdl.cpp может выглядеть так:

#include "lua/lua.hpp"

static int create(lua_State* L)
{
  return 0;
}

static int run(lua_State* L)
{
  return 0;
}

static const luaL_Reg sdl[] = {
  {"Create", create},
  {"Run", run},
  {0, 0},
};

extern "C" __declspec(dllexport) int luaopen_sdl(lua_State* L)
{
    luaL_register(L, "sdl", sdl);
  return 1;
}

Компилируем проект. Файл lua-sdl.dll создан успешно. Замечательно. Едем дальше.
Итак, функция Create() инициализирует библиотеку sdl, выполняет инициализацию OpenGL и создает окно заданного размера. Она принимает на вход три параметра - ширина окна, высота окна и во весь экран или нет. Внутри этой функции также выполняется настройка OpenGL. Заведем несколько статических переменных (они нам могут пригодиться потом):

static SDL_Surface* screen = 0;
static int screenWidth, screenHeight;
static bool fullScreen = false;

static int create(lua_State* L)
{
  // stack: width height fullscreen
  if(!fromLua(L, 1, screenWidth) ||
     !fromLua(L, 2, screenHeight))
  {
    luaL_error(L, "Invalid arguments!\n");
  }

  SDL_Init(SDL_INIT_VIDEO);
  SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
  SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
  SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
  SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
  SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
  // включаем поддержку Unicode в sdl
  SDL_EnableUNICODE(1);

  fromLua(L, 3, fullScreen);
  if(fullScreen)
  {
    screen = SDL_SetVideoMode(screenWidth, screenHeight, 32, SDL_OPENGL | SDL_FULLSCREEN);
  }
  else
  {
    screen = SDL_SetVideoMode(screenWidth, screenHeight, 32, SDL_OPENGL | SDL_RESIZABLE);
  }
  SDL_WM_SetCaption("LUA GUI TEST", "Lua gui test");

  return 0;
}

Теперь функция Run(). Она запускает бесконечный главный цикл программы. Внутри цикла на каждой итерации происходит следующее:
-вызывается глобальная Lua функция idle(), внутри которой производится вся работа по синхронизации состояния программы
-затем вызываются обработчики для внешних воздействий пользователя (клавиатура, мышь и т.д.)
-вызывается глобальная Lua функция draw(), внутри которой должно производиться все рисование.
-вызывается функция SDL_Delay(), которая позволяет операционной системе обрабатывать другие потоки (иначе даже при ничтожной загрузке в нашем потоке вся система будет работать очень медленно).
-цикл повторяется.

static int delay = 1;

static int run(lua_State* L)
{
  SDL_Event event;
  bool done = false;
  while(!done)
  {
    idle(L);
    while(SDL_PollEvent(&event))
    {
      switch(event.type)
          {
          case SDL_QUIT:
              done = true;
              break;
          case SDL_VIDEORESIZE:
              break;
          case SDL_MOUSEMOTION:
              break;
          case SDL_MOUSEBUTTONUP:
          break;
          case SDL_MOUSEBUTTONDOWN:
              break;
          case SDL_KEYUP:
          break;
          case SDL_KEYDOWN:
              break;
          default:
          break;
        }
    }
    draw(L);
    SDL_GL_SwapBuffers();
    SDL_Delay(delay);
  }
  SDL_Quit();

  return 0;
}

Компилируем. На выходе получаем готовый Lua-модуль. Протестируем его.
В папке sdl создадим Lua-скрипт sdltest.lua:

package.path = './?.lua'
package.cpath = '../../bin/lua-?.dll'

local sdl = require('sdl')

sdl.Create(300, 200, false)
sdl.Run()

Создадим там же файл sdltest.bat для запуска скрипта:

..\..\bin\lua5.1.exe sdltest.lua

Запускаем. У меня получилось такое окно:



Мда... Поскольку мы никакого реального рисования пока не выполняем, то в окне отобразился какой-то мусор от предыдущих запусков OpenGL приложений.

Переходим к третьей части нашего шага. Изготовим Lua-библиотеку для OpenGL. Небольшое замечание. Писать полный биндинг OpenGL к Lua у меня нет ни малейшего желания. Поэтому в Lua будут экспортироваться только те функции и константы OpenGL, которые нам нужны для решения конкретной задачи. Поехали. Cоздаем новый .dll проект, который сохраним в папке modules/opengl. Ставим галочку "Do not create any file" (все равно файл, сгенерированный по умалчанию, нам не нужен).



Создаем новый файл opengl.cpp и добавляем его в проект. Также добавляем в проект файлы из папки LuaUtil.
Изменим имя результирующей dll:



Настраиваем пути:



Теперь настраиваем библиотеки. Для работы с OpenGL будем использовать библиотеку glew (http://glew.sourceforge.net). Эта библиотека позволяет прозрачно работать с расширениями OpenGL (если нам таковые вдруг понадобятся). Скачиваем бинарники для Windows и распаковываем glew32.dll в нашу папку bin, glew32.lib в папку lib, а glew.h в папку include.
Подключаем библиотеки:



В файле opengl.cpp пишем "рыбу" с единственной функцией Create():

#include "LuaUtil.h"
#include "glew.h"
#include <iostream>

static int create(lua_State* L)
{
  // stack:
  glewInit(); // инициализация glew

  return 0;
}

static const luaL_Reg opengl[] = {
  {"Create", create},
  {0, 0},
};

extern "C" __declspec(dllexport) int luaopen_opengl(lua_State* L)
{
  luaL_register(L, "opengl", opengl);
  return 1;
}

Компилируем. Вроде все компилируется. Добавим функцию Clear():

static int clear(lua_State* L)
{
  // stack:
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  return 0;
}

static const luaL_Reg opengl[] = {
  {"Create", create},
  {"Clear", clear},
  {0, 0},
};

Пишем тест для проверки. В папке modules создаем файл opengltest.lua:

package.path = './?.lua'
package.cpath = '../bin/lua-?.dll'

local sdl = require('sdl')
local gl = require('opengl')

gl.Create()
sdl.Create(300, 200, false)

function idle()
  print('idle')
end

function draw()
  gl.Clear()
end

sdl.Run()

Создадим там же файл opengltest.bat для запуска скрипта:

..\bin\lua5.1.exe opengltest.lua

Запускаем.



Отлично! В функции draw(), которая вызывается на каждом кадре, происходит очистка буферов глубины и цвета, и в результате мы получаем глубокий черный цвет :)
На сегодня это все. В следущем уроке мы нарисуем что-нибудь более приятное глазу с анимацией (а на что нам функция idle() по вашему?).
До встречи!

Файлы урока можно взять здесь.

Назад

Hosted by uCoz