ゲームプログラムがテキストから文字を取り出すとき、1byte系の言語なら1byteが1文字と簡単である。
しかしCJK対応するには、1byteの文字なのか、後続のデータを含めて2-3byteで1文字なのかを判定して取り出す必要がある。
また、フォント処理でも、1byte系言語なら256の表を用意して
'A' => 文字コード41h => 表の41h番目のフォント
という簡単な処理だが、CJK対応には、たくさんの文字から適切にフォントを探すようなプログラムが必要になる。
1byte対応ゲームである
DOOM 3 のソースコードが公開されているので、具体例を見てみる。
DOOM3
フォントデータや、テキストから1文字を切り出す部分が1byte(文字コード0-255)を前提としたつくりになっている。
フォント情報の内部形式
const int GLYPH_START = 0;
const int GLYPH_END = 255;
const int GLYPH_CHARSTART = 32;
const int GLYPH_CHAREND = 127;
const int GLYPHS_PER_FONT = GLYPH_END - GLYPH_START + 1;
typedef struct {
int height; // number of scan lines
int top; // top of glyph in buffer
int bottom; // bottom of glyph in buffer
int pitch; // width for copying
int xSkip; // x adjustment
int imageWidth; // width of actual image
int imageHeight; // height of actual image
float s; // x offset in image where glyph starts // テクスチャ座標
float t; // y offset in image where glyph starts
float s2;
float t2;
const idMaterial * glyph; // shader with the glyph
char shaderName[32];
} glyphInfo_t;
typedef struct {
glyphInfo_t glyphs [GLYPHS_PER_FONT]; // 256文字固定
float glyphScale;
char name[64];
} fontInfo_t;
フォントのロード
フォントデータには個数や文字コード情報がなく、コード順に256文字並んでいることを前提にしている
bool idRenderSystemLocal::RegisterFont( const char *fontName, fontInfoEx_t &font ) {
...
fileSystem->ReadFile( name, &faceData, &ftime );
fdOffset = 0;
fdFile = reinterpret_cast<unsigned char*>(faceData);
for( i = 0; i < GLYPHS_PER_FONT; i++ ) { // 256文字固定
outFont->glyphs[i].height = readInt();
outFont->glyphs[i].top = readInt();
outFont->glyphs[i].bottom = readInt();
outFont->glyphs[i].pitch = readInt();
outFont->glyphs[i].xSkip = readInt();
outFont->glyphs[i].imageWidth = readInt();
outFont->glyphs[i].imageHeight = readInt();
outFont->glyphs[i].s = readFloat();
outFont->glyphs[i].t = readFloat();
outFont->glyphs[i].s2 = readFloat();
outFont->glyphs[i].t2 = readFloat();
int junk /* font.glyphs[i].glyph */ = readInt();
//FIXME: the +6, -6 skips the embedded fonts/
memcpy( outFont->glyphs[i].shaderName, &fdFile[fdOffset + 6], 32 - 6 );
fdOffset += 32;
}
...
}
テキストの表示
int idDeviceContext::DrawText(float x, float y, float scale, idVec4 color, const char *text, float adjust, int limit, int style, int cursor) {
...
while (s && *s && count < len) {
if ( *s < GLYPH_START || *s > GLYPH_END ) {
s++;
continue;
}
glyph = &useFont->glyphs[*s]; // 1byte取り出し
// 文字コード -> 文字形(glyph) は単純な表参照
...
float yadj = useScale * glyph->top;
PaintChar(x,y - yadj,glyph->imageWidth,glyph->imageHeight,useScale,glyph->s,glyph->t,glyph->s2,glyph->t2,glyph->glyph);
if (cursor == count) {
DrawEditCursor(x, y, scale);
}
x += (glyph->xSkip * useScale) + adjust;
s++;
count++; // 1byte進める
}
}
...
}
日本語化MOD
このように1byteコードのみにしか対応していないゲームは、基本的にかな化はできるが漢字日本語化はできない。
しかしソースコード(プログラムの元)が公開されている場合、プログラムを変更することによって日本語表示が可能になる。
フォント情報の内部形式
ここでは元のフォント情報内部形式はそのままに、
glyphs[0-254] は元のまま、
glyphs[255] に 独自のmy_glyphs_tをぶち込むという
少々トリッキーなことをしている。
neo/renderer/myfont.h
typedef struct {
int height; // 文字コードとして代用
int top; // top of glyph in buffer
int bottom; // bottom of glyph in buffer
int pitch; // width for copying
int xSkip; // x adjustment
int imageWidth; // width of actual image
int imageHeight; // height of actual image
float s; // x offset in image where glyph starts
float t; // y offset in image where glyph starts
float s2;
float t2;
const idMaterial * glyph; // shader with the glyph
int id;
//char shaderName[32];
} my_glyphInfo_t;
typedef struct {
int num; // グリフ数を管理
my_glyphInfo_t* glyphs;
} my_glyphs_t;
フォントのロード
neo/renderer/tr_font.cpp
fileSystem->ReadFile( name, &faceData, &ftime );
fdOffset = 0;
fdFile = reinterpret_cast<unsigned char*>(faceData);
int mw = 0;
int mh = 0;
if (memcmp(fdFile,"BMF\x03",4)==0) { // Bitmap Font Generator形式に対応
...
my_glyphInfo_t* myglyphs = (my_glyphInfo_t*)Mem_ClearedAlloc(sizeof(my_glyphInfo_t)*num);
int n=0;
float inv_w = 1.0f/scaleW,inv_h = 1.0f/scaleH;
for(;p < next;p+=20) {
int id = get32(p);
int x = get16(p+4);
int y = get16(p+6);
int w = get16(p+8);
int h = get16(p+10);
int xoffset = get16(p+12);
int yoffset = get16(p+14);
int xadvance = get16(p+16);
int page = p[18];
int xSkip = xadvance;
my_glyphInfo_t* glyph = (id < GLYPH_END)?(my_glyphInfo_t*)&outFont->glyphs[id]:&myglyphs[n++]; // 0-254はそのまま、それ以上はmyglyphsに入れる
glyph->id = id;
glyph->height = h+yoffset;
glyph->top = base-yoffset;
glyph->imageWidth = w;
glyph->imageHeight = h;
glyph->xSkip = xSkip;
glyph->s = x*inv_w;
glyph->t = y*inv_h;
glyph->s2 = (x+w)*inv_w;
glyph->t2 = (y+h)*inv_h;
glyph->glyph = materials[page];
// shaderName not used
if (mw<xSkip) mw=xSkip;
}
my_glyphs_t* t = (my_glyphs_t*)&outFont->glyphs[GLYPH_END];
t->glyphs = myglyphs; // &outFont->glyphs[255] にmyglyphsをぶち込む
t->num = n;
テキストの表示
neo/ui/DeviceContext.cpp
while (s && *s && count < len) {
if ( *s < GLYPH_START ) {
s++;
continue;
}
....
int ch = *s;
int l=1;
if (utf8) {
if (ch<0x80) {
} else if (ch<0xc0) {
} else if (ch<0xe0) {
if (!is_leading(s[1])) break;
ch = ((ch&0x1f)<< 6) | (s[1] & 0x3f); l=2;
} else {
if (!is_leading(s[1]) || !is_leading(s[2])) break;
ch = ((ch&0x0f)<<12) | ((s[1] & 0x3f)<<6) | (s[2] & 0x3f); l=3;
} // 1文字取り出してbyte数をlに代入
}
glyph = get_glyph(useFont, ch); // 対応する文字画像を取り出す
float yadj = useScale * glyph->top;
PaintChar(x,y - yadj,glyph->imageWidth,glyph->imageHeight,useScale,glyph->s,glyph->t,glyph->s2,glyph->t2,glyph->glyph); // 文字画像表示部分は元のまま
if (cursor == count) {
DrawEditCursor(x, y, scale);
}
x += (glyph->xSkip * useScale) + adjust;
s+=l;
count+=l; // 1文字分進める
DOOM3 BFG
最近DOOM3 BFG Editionのソースコードが公開されてたので、どう変更されたか見てみる。
(ところでid softwareは最初にDOOMのソースコードを公開してから毎回クリスマスに公開してたのだが、Bethesda Softworksに買収されてから時期が関係なくなった)
(ところでid softwareは最初にDOOMのソースコードを公開してから毎回クリスマスに公開してたのだが、Bethesda Softworksに買収されてから時期が関係なくなった)
フォント情報の内部形式
struct fontInfo_t {
struct oldInfo_t {
float maxWidth;
float maxHeight;
} oldInfo[3];
short ascender;
short descender;
short numGlyphs; // 個数を管理している
glyphInfo_t * glyphData;
// This is a sorted array of all characters in the font
// This maps directly to glyphData, so if charIndex[0] is 42 then glyphData[0] is character 42
uint32 * charIndex;
// As an optimization, provide a direct mapping for the ascii character set
char ascii[128];
const idMaterial * material;
};
フォントのロード
bool idFont::LoadFont() {
....
fd->ReadBig( fontInfo->numGlyphs ); // フォントデータから個数を読んでいる
fontInfo->glyphData = (glyphInfo_t *)Mem_Alloc( sizeof( glyphInfo_t ) * fontInfo->numGlyphs, TAG_FONT );
fontInfo->charIndex = (uint32 *)Mem_Alloc( sizeof( uint32 ) * fontInfo->numGlyphs, TAG_FONT );
fd->Read( fontInfo->glyphData, fontInfo->numGlyphs * sizeof( glyphInfo_t ) );
for( int i = 0; i < fontInfo->numGlyphs; i++ ) {
idSwap::Little( fontInfo->glyphData[i].width );
idSwap::Little( fontInfo->glyphData[i].height );
idSwap::Little( fontInfo->glyphData[i].top );
idSwap::Little( fontInfo->glyphData[i].left );
idSwap::Little( fontInfo->glyphData[i].xSkip );
idSwap::Little( fontInfo->glyphData[i].s );
idSwap::Little( fontInfo->glyphData[i].t );
}
...
}
テキストの表示
int idDeviceContext::DrawText(float x, float y, float scale, idVec4 color, const char *text, float adjust, int limit, int style, int cursor) {
...
while ( charIndex < len ) {
uint32 textChar = drawText.UTF8Char( charIndex ); // マルチバイトを考慮してcharIndexから1文字取りだし、charIndexを1文字分(1-3byte)進める。
...
scaledGlyphInfo_t glyphInfo;
activeFont->GetScaledGlyph( scale, textChar, glyphInfo ); // textCharのUnicodeの文字情報をscale倍してglyphInfoに読み込む。
prevGlyphSkip = glyphInfo.xSkip;
PaintChar( x, y, glyphInfo ); // 1文字表示
if (cursor == charIndex-1) {
DrawEditCursor(x, y, scale);
}
x += glyphInfo.xSkip + adjust;
drawText.UTF8Char( charIndex )neo/idlib/Str.hneo/idlib/Str.cpp
activeFont->GetScaledGlyph( scale, textChar, glyphInfo )
neo/renderer/Font.cpp
というわけで変更点はだいたい似通っている。
ゲームのソースコードは商売のタネなので基本的には公開されることはないのだが、
デベロッパによっては古いゲームのソースコードをファンサービスで公開することがある。
(それ以上非公開にしていても利益を生むことは無いという判断?)
元々マルチバイト対応してないゲームでも、ソースコードが公開されていれば(そしてライセンス上の問題が無ければ - 普通は問題無い形で公開されている)、表示できるように改造することで日本語化可能になる。
FreeSpace 2/日本語化プロジェクト
Aquaria 日本語化
元々1byteにしか対応していないものを日本語化するにはこのようなプログラムの変更が必要となるので、海外ゲームの日本語版が割高になるのは翻訳も含めてやむを得ない面もある。
しかし元々日本語化可能なゲームであるにもかかわらず、英語版のまま割高で国内販売してるのはフサケンナって思う。
0 件のコメント:
コメントを投稿