27 декабря 2010

Unicode в GlScene: сложности и результаты.

Последние недели занимался локализацией "Тайн притяжения" на корейский язык. Я уже описывал процесс локализации игры на европейские языки, но корейский язык - это нечто иное, в чем же разница? Отличие заключается в том, что в корейском языке порядка 500 иероглифов, которые, очевидно, не влезают в стандартный байтовый интервал, который используется в GlScene (256 символов). Остается только одно - переделывать хранение символов (так как символов много, приходится их раскидывать по нескольким текстурам), их рендер, шрифто-генератор и много чего еще. В общем, понеслось переделывание кода "изнутри", чего я обычно опасаюсь на готовых проектах :)
Немного об этом (вдаваться в подробности не всегда интересно), а также демка для GlScene - в этом сообщении...
Решил сделать просто - отнаследоваться от стандартного класса-рендерер, перекрыть основные методы, добавить информации о символах и в путь! Но наши желания и возможности зачастую расходятся...
В общем, все началось с того, что внутри рендерера текста GlScene везде и сплошь используется AnsiChar, что очень расстроило. Также напрягло то, что жизненно важные методы этого рендерера (а именно CalcStringWidth, RenderString) не помечены директивой virual, и без изменения исходников не обойтись. Двигаемся дальше: внутри Ranges, который хранит в себе интервалы отображаемых символов, все вдоль и поперек напичкано AnsiChar. Но это не проблема - добавили в свой класс целый Word-массив для каждого символа, куда заталкиваем те, которые используем. И, наконец, столкнулся с тем, что не очень знаю как переключать текстуры при рендере. Снова отсутствия знаний OpenGL заставляют пасовать перед элементарными вещами. И здесь мне помог YarUnderoaker, за что ему огромное спасибо - спас меня от гугления этой проблемы.
В итоге, вышло вот так:
За эти недели подучил корейский :) То, что в caption'е квадратики указывает на то, что на моем компьютере мне так и не удалось подключить корейскую раскладку, и система не может выводить незнакомые символы в заголовке окна.
Простую демку с рендером unicode'овых символов, зашитых в двух текстурах + дополнительных файлах с сохраненными текстурными координатами, можно скачать отсюда.
Патч к GlScene (у меня установлена старая, привязанная еще к CVS, версия) можно взять здесь.
Запустив демку можно увидеть следующее:
Ура! Иероглифы в самом расцвете!
Немного пробегусь по коду. Внутри модуля uBitMapUnicodeFont имеется класс TBitMapUnicodeFont, описанный следующим образом:
TBitMapUnicodeFont = class (TGLCustomBitmapFont), таким образом наследуюется основной функционал. А самое главное, объект такого класса может быть назначен TGLHudText'ам (текстовым лэйблам). Внутри нашего нового класса хранится массив всех доступных символов:
fChars: array[0..65535] of TUnicodeChar;
где TUnicodeChar имеет следующий вид:
TUnicodeChar = class
  public
    fUnicodeIndex: Integer;
    fCharRect: TVector;
    fCharWidth, fCharHeight: Integer;
    fMaterialIndex: Integer;
  end;

Вот почти и все. Стоит отметить, что класс шрифта также хранит список материалов с текстурами шрифта, значение символа указывает на номер материала, в котором содержится сам символ. Добавить материал на использование можно "снаружи", вызвав TBitMapUnicodeFont.AddMaterial(aMaterial: TGLMaterial); Стоит делать это аккуратно, "закидывая" материалы в нужном порядке, чтобы у символов не было путаницы. Вот такой простенький класс получился. Весь смысл ее таится в передаваемых везде WideChar'ов вместо AnsiChar'ов. Кстати, а вот и простецкий способ переключения текстур, используемый внутри RenderString():

      if(BindedIndex <> fChars[Word(wChar)].fMaterialIndex) then
      begin
        glEnd;
        if (BindedIndex >= 0) and (BindedIndex < fMaterials.Count) then
          GetMaterialByIndex(BindedIndex).UnApply(rci);
        BindedIndex := fChars[Word(wChar)].fMaterialIndex;
        with GetMaterialByIndex(BindedIndex) do
        begin
          FrontProperties.Diffuse.Alpha := aColor[3];
          Apply(rci);
        end;
        glBegin(GL_QUADS);
      end;

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

В общем, с выводом разобрались. Теперь о вводе. То есть определение нажатых клавиш на клавиатуре. С этим - беда.
Дело в том, что
ToUnicode(key, MapVirtualKey(key, 0), @keyboardState, @Result, SizeOf(Result), 0);
не справился с корейской клавиатурой. Русская, немецкая, польская раскладки - без проблем. Корейская ни в какую. Постоянно выдает коды английских букв, нажатых на клавиатуре (хотя переключены на корейскую раскладку). В итоге не стал с этим возиться - оставил как есть, то есть вводить имена профилей можно только на английском языке. Вот такая вот недоработка...

Сообщения, схожие по тематике:

2 коммент.:

  1. Спасибо, очень интересно и познавательно, но хотелось бы чтобы в статье был код для обсуждения, а не только ссылкой. :)

    ОтветитьУдалить
  2. Ага, спасибо, добавил чутка...
    Просто кода там совсем мало - не очень знаю, что именно описать, на чем остановить внимание :)

    ОтветитьУдалить