суббота, 19 апреля 2008 г.

Поддержка нескольких языков приложением

При разработке коммерческих приложений рано или поздно придется столкнуться с проблемой локализации. Как правило, если сразу этот вопрос не был рассмотрен, то появляется множество проблем при попытке перевести свой программный продукт на другие языкы. Большниство новичков даже не знают, что из себя представляет Unicode и как с ним работать. На dtf.ru недавно появилась небольшая статья, которая показывает в каком направлении надо двигаться.

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

На Земле насчитывается более 5000 языков. А финальный вариант стандарта ISO 639-3 (трехбуквенные языковые коды), учитывающий даже диалекты мертвых языков, содержит целых 7589 записей.
Очевидно, что языковое разнообразие вашей игры будет описываться значительно более скромными цифрами; не менее очевиден ваш интерес в том, чтобы какое-то разнообразие было. Однако никто не любит долго заниматься локализацией, поэтому ее неизбежность должна заставлять задуматься над правильной организацией процесса.

По моим подсчетам, на тему локализации на различных КРИ было прочитано 5 докладов, что указывает на определенный интерес коллег к этой теме. Особое внимание заслуживает доклад "Бумеранг, написание локализуемой игры", сделанный в 2003 году Юрием Блажевичем и Василием Подобедом. Доклад содержит в себе ряд правил, следование которым поможет быстро достичь счастья всем участникам процесса локализации. Полное содержание доклада вы можете получить с сайта КРИ, я же позволю себе уточнить и раскрыть некоторые аспекты.

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

Олег Смирнов

Продолжение...

Object Iterators

Методы end() и begin() возвращают luabind::iterator, который можно использовать для перебора всех элементов в таблице luabind::object. Так же есть другой тип итератора — luabind::functor, который позволяет перебрать все функции в таблице (более подробно можно прочитать в документации к luabind).

Метод is_valid и оператор bool

Метод is_valid и оператор bool позволяют проверить является ли полученный luabind::object рабочим:


//получаем глобальную табилцу lua объекту luabind::object
luabind::object MyObject = get_globals(pLua);

//проверяем действителен ли объект
if (MyObject.is_valid())
{
DoSomething(MyObject[key]);
}


Так же возможен такой вариант:


if (MyObject)
{
DoSomething(MyObject[key]);
}



is_valid
действителен, когда был использован конструктор по-умолчанию и не было присвоено значение.

четверг, 17 апреля 2008 г.

Команды at() и [] для luabind::object

Как только lua тип назначен для luabind::object вы можете использовать оператор [] или методм at() для доступа к данным. at() обеспечивает read-only доступ, а [] - read-write доступ. Параметр, передаваемый в [] или at(), должен быть lua типом доступным в глобальном пространстве имен (помните, что все переменные lua объявлены в глобальной области видимости, если явно не указано ключевое слово local перед первым объявлением переменной в скрипте lua). Для преобразования типа luabind::object в C++ тип необходимо использовать luabind::object_cast.

Например давайте объявим такие переменные в lua скрипте:


Mat = 37
Sharon = 15
Scooter = 1.5


Теперь мы должны связать luabind::object с глобальной таблицей lua. Для этого необходимо использовать функцию get_globals:


luabind::object global_table = get_globals(pLua);


После этого можно получить доступ до объявленных переменных:


float scooter = luabind::object_cast<float>(global_table.at("Scooter"));


Или поменять значение переменных:


global_table["Mat"] = 10;


Одной из важных особенностей luabind::object является возможность вызывать функции, определенные в lua. Вы можете даже включать luabind::object в поля класса C++, позволяя расширить функциональные возможности класса. В следующих статьях я покажу, как это сделать.

luabind::object

Чтобы упростить привязку lua типов к вашим C++ функция и объектам, luabind содержит класс object. Этот класс может представлять любой тип lua. Интерфейс класса object скопирован с документации luabind:


class object
{
public:
class iterator;
class raw_iterator;
class array_iterator;

template<class T>
object(lua_State*, const T& value);
object(const object&);
object(lua_State*);
object();

-object();

iterator begin() const;
iterator end() const;
raw_iterator raw_begin() const;
raw_iterator raw_end() const;
array_iterator abegin() const;
array_iterator aend() const;

void set();
lua_State* lua_state() const;
void pushvalue() const;
bool is_valid() const;
operator bool() const;

template<class Key>
<implementation-defined> operator[](const Key&);

template<class Key>
object at(const Key&) const;

template<class Key>
object raw_at(const Key&) const;

template<class T>
object& operator=(const T&);
object& operator=(const object&);

template<class T>
bool operator==(const T&) const;
bool operator==(const object&) const;
bool operator<(const object&) const;
bool operator<=(const object&) const;
bool operator>(const object&) const;
bool operator>=(const object&) const;
bool operator!=(const object&) const;

void swap(object&);
int type() const;

<implementation-defined> operator()();

template<class A0>
<implementation-defined> operator()(const A0& a0);

template<class A0, class A1>
<implementation-defined> operator()(const A0& a0, const A1& a1);

/* ... */
};

среда, 16 апреля 2008 г.

Создание классов в lua

Так же возможно определять классы в ваших lua скриптах с использованием luabind, а затем экспортировать их в C++ программы. Давайте создадим класс Animal на lua:


class 'Animal'

function Animal:__init(num_legs, noise_made)

self.NoiseMade = noise_made
self.NumLegs = num_legs

end

function Animal:Speak()

print(self.NoiseMade)

end

function Animal:GetNumLegs()

return self.NumLegs

end


Ключевое слово self работает точно так же, как и в this в C++, которое возвращает указатель класса на себя. Созданный класс Animal теперь можно использовать в нашем скрипте:


cat = Animal(4, "meow")

cat:Speak()

print ("У кота "..cat:GetNumLegs().." ноги")


После выполнения скрипт выведет на экран:


meow
У кота 4 ноги


Так же можно использовать наследование для классов:


class 'Pet' (Animal)

function Pet:__init(name, num_legs, noise_made) super(num_legs, noise_made)
self.Name = name

end

function Pet:GetName()

return self.Name

end


Ключевое слово super используется для вызова конструктора базового класса перед инициализацией класса-наследника. Пример использования класса Pet:


dog = Pet("Albert", 4, "woof")

dog:Speak()

print ("Мою собаку зовут "..dog:GetName())


После выполнения скрипт выведет на экран:


woof
Мою собаку зовут Albert

вторник, 15 апреля 2008 г.

Экспортирование C++ классов в luabind

Привязка классов C++ к lua не на много сложнее, чем функции. Она реализована с помощью шаблона class_ и метода def, который регистриует конструкторы, методы, переменные и деструкторы. class_::def возвращает this указатель на редактируемую цепочку.

Класс животного:

class Animal
{
private:

int m_iNumLegs;

std::string m_NoiseEmitted;

public:

Animal(std::string NoiseEmitted,
int NumLegs):m_iNumLegs(NumLegs),
m_NoiseEmitted(NoiseEmitted)
{}

virtual ~Animal(){}

virtual void Speak()const
{std::cout << "\n[C++]: " << m_NoiseEmitted << std::endl;}

int NumLegs()const{return m_iNumLegs;}
};


экспорт класса с помощью luabind:

module(pLua)
[
class_<Animal>("Animal")
.def(constructor<string, int>())
.def("Speak", &Animal::Speak)
.def("NumLegs", &Animal::NumLegs)
];


Теперь можно применять этот класс в lua скрипте:


--создание объекта животное и вызов его методов

cat = Animal("Мяу", 4);

print ("\n[Lua]: У кота "..cat:NumLegs().. " ног.");

cat:Speak();


Заметьте, что в скрипте используется оператор : для вызовов методов. Другой путь вызова метода: cat.Speak(cat). Методы можно вызывать любым из указанных способов. Классы в lua представлены в виде таблиц, каждый элемент таблицы представляет собой метод или переменную класса.

Классы-наследники тоже легко экспортировать с помощью luabind. Пример класса, полученного от класса Animal:


class Pet : public Animal
{
private:

std::string m_Name;

public:

Pet(std::string name,
std::string noise,
int NumLegs):Animal(noise, NumLegs),
m_Name(name)
{}

std::string GetName()const{return m_Name;}
};


Используя luabind, класс Pet легко привязывать к lua. Это делается с помощью параметра шаблона bases<base class>, который определяет родительский класс:


module(pLua)
[
class_<Pet, bases<Animal> >("Pet")
.def(constructor<string, string, int>())
.def("GetName", &Pet::GetName)
];


Если ваш класс был создан при помощи множественного наследования, то каждый родительский класс должен быть прописан в bases<> и разделен запятыми:


class_<Derived, bases<Base1, Base2, Base3> >("Derived")

Экспортирование C/C++ функций в luabind

Чтобы привязать C/C++ функцию к lua используйте luabind::def фунцию. Например, давайте создадим простые функции: сложения двух чисел и вывода сообщения Hello World и привяжем их к lua:


using namespace std;
using namespace luabind;

void HelloWorld()
{
cout << "\n[C++]: Hello World!" << endl;
}

int add(int a, int b)
{
return a+b;
}

module(pL)
[
def("HelloWorld", &HelloWorld),
def("add", &add)
];


Теперь в скриптах lua можно вызывать эти функции:


print("[lua]: Вызов функции C++ HelloWorld()")

HelloWorld()


print("\n[lua]: Вызов функции C++ add()")

a=10
b=5

print ("\n[lua]: "..a.." + "..b.." = "..add(a, b))


После выполнения скрипта получим:


[lua]: [lua]: Вызов функции C++ HelloWorld()
[C++]: HelloWorld!
[lua]: [lua]: Вызов функции C++ add()
[lua]: 10+5=15


Если у вас есть перегруженные функции, например:


int MyFunc(int a);
void MyFunc(double a);


то def будет иметь следующий вид:


module(pLua)
[
def("MyFunc", (int (*)(int)) &MyFunc),
def("MyFunc", (void (*)(double)) &MyFunc),
];

понедельник, 14 апреля 2008 г.

Интересный блог о разработке казульного проекта Bloom 2

Под вечер нашел еще очень неплохие блоги автора elmortem. В своем новом блоге он рассказывает о создании проекта Bloom 2 (я играл в bloom, игра сделана очень качественно для первой казулки, надеюсь новая часть будет еще лучше предыдущей). Так же на его другом блоге можно найти кучу полезных расширений для HGE, особенно полезна разработка HGE с с полной поддержкой Unicode (сам когда-то хотел сделать такое, но потом решил работать с дефолтными возможностями по локализации).

воскресенье, 13 апреля 2008 г.

Экспорт С++ классов и функций с помощью luabind (общая информация)

Экспорт функций в lua очень прост даже без библиотеки luabind, но классы - это совсем другое дело! Чтобы экспортировать простой класс и его методы из lua в c++, вам придется создать lua-таблицу, которая будет содержать как данные класса, так и методы, которые вам потребуются. Вам придется создать метатаблицу, которая будет определять, как ваш класс ведет себя с операторами == или *. Чтобы экспортировать lua-функцию в c-функцию требуется много времени и сил, а чтобы сделать тоже самое с классом придется потратить гораздо больше ресурсов. К счастью за нас уже сделали тяжелую работу и создали API, который позволяет экспортировать функции и классы без каких-либо трудностей. Этот API называется luabind, и точно так же как и lua является open-source решением, которое можно легко использовать для своей работы.

Luabind - это библиотека, которая позволяет соединять данные между lua и C ++. Это реализовано благодаря использованию мета-программирования. Вы можете можете создавать классы в lua и использовать их в своих c++ программах, а также наоборот.

Чтобы начать использовать luabind, нужно включить заголовочные файлы библиотеки и lua в ваш проект, и затем вызывать функцию luabind::open(lua_State*) для инициализации luabind:


extern "C"
{
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}

#include "luabind/luabind.hpp"

int main()
{
//создание lua state
lua_State* pLua = lua_open();

//инициализация luabind
luabind::open(pLua);

/* регистрация функций и классов в luabind */

/* загрузка и выполение скриптов */

// освобождение ресурсов
lua_close(pLua);

return 0;
}


Для добавление своей функции нужно зарегистрировать ее в окружении luabind, для этого используется luabind::module(pL):


luabind::module(pL)
[
// регистрация классов и функций
];


Этот вызов регистрирует вашу функцию или класс в глобальном пространстве имен. Чтобы указать какое-то другое пространство имен сделайте:


luabind::module(pL, "MyNamespace")
[
// регистрация классов и функций
];


Luabind представляет пространство имен, как таблицу. В данном примере, все функции и классы будут находится в таблице с именем MyNameSpace.

Изучение luabind.

Сегодня решил начать изучение различных библиотек для связывания lua и c++. Начну рассмотрение с luabind.

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

Для того, чтобы начать работать с luabind, её необходимо скачать с офцициального сайта. Так же для работы потребуется библиотека boost.