A03.GUIの表示
Irrlicht専用のメッセージボックスやボタンなどを表示します。
A02で紹介した日本語表示関数をFontクラス化することで、GUIに日本語も表示可能です。

ヘッダファイルの追加
日本語を入力する際に必要なヘッダを追加しておきます。
また、レシーバーでdeviceを使用するので、グローバル化しておきます。
#include <stdio.h>
#include <stdarg.h>
#include <wchar.h>
#include <locale.h>
IrrlichtDevice *DEV;
|
UTF→SJIS変換
Irrichtでは文字列は内部でUTFで扱われています。
Windowsユーザーの場合は、SJISの方が馴染みがあるのと、
台詞などのスクリプトファイルを外部から読み込んだ時のために
文字コードを変換する関数を用意しておきます。
Windowsに限定するのであればWideCharToMultiByteを使用することもできます。
変換ルーチンについては、KamoLandさんのを使用させて頂きました。
UTFをSJISに変換する関数です。
//UTF-16BE → SJIS へ文字列のコード変換
char* utf16be_to_sjis(const wchar_t *pUcsStr, int *nBytesOut)
{
char *pAnsiStr = NULL;
int nLen;
if(!pUcsStr)
return NULL;
setlocale(LC_ALL, "Japanese");//これがないとUnicodeに変換されない
nLen = wcslen(pUcsStr);
if(pUcsStr[0] == 0xfeff || pUcsStr[0] == 0xfffe)
{
pUcsStr++; //先頭にBOM(Byte Order Mark)があれば,スキップする
nLen--;
}
pAnsiStr = (char *)calloc((nLen+1), sizeof(wchar_t));
if(!pAnsiStr)
return NULL;
//1文字ずつ変換する
//まとめて変換すると、変換不能文字への対応が困難なので
int nRet, i, nMbpos = 0;
char *pcMbchar = new char[MB_CUR_MAX];
for(i=0;i<nLen;i++)
{
//irrlicht用調整
//変換時のゴミが入っているので無視する
//例)終了をWCHARにすると8F4997B90000だが
//内部では8FFF490097FFB9FF0000として格納されている
if((pUcsStr[i] & 0xFF00) == 0xFF00)
{
pAnsiStr[nMbpos++] = pUcsStr[i] & 0x00FF;
}
//通常処理
else
{
//変換処理
nRet = wctomb(pcMbchar, pUcsStr[i]);
switch(nRet)
{
case 1: //1バイト文字
pAnsiStr[nMbpos++] = pcMbchar[0];
break;
case 2: //2バイト文字
pAnsiStr[nMbpos++] = pcMbchar[0];
pAnsiStr[nMbpos++] = pcMbchar[1];
break;
default: //変換不能
pAnsiStr[nMbpos++] = ' ';
break;
}
}
}
pAnsiStr[nMbpos] = '\0';
delete [] pcMbchar;
*nBytesOut = nMbpos;
return pAnsiStr;
}
|
SJIS→UTF
SJISをUTFへ変換する関数です。
//SJIS → UTF-16BEへ文字列のコード変換
wchar_t* sjis_to_utf16be(const char *pAnsiStr, int *nBytesOut)
{
int len;
wchar_t *pUcsStr = NULL;
if(!pAnsiStr)
return NULL;
setlocale(LC_ALL, "Japanese"); //これがないとUnicodeに変換されない
len = strlen(pAnsiStr);
*nBytesOut = sizeof(wchar_t)*(len);
pUcsStr = (wchar_t *)calloc(*nBytesOut + 2, 1);
if(!pUcsStr)
return NULL;
//変換処理
mbstowcs(pUcsStr, pAnsiStr, len+1);
return pUcsStr;
}
|
日本語表示のFontクラス化
日本語表示をFontクラス化することで、GUIでも使用可能になります。
実際の表示はPrintfを呼び出して、DrawImageで画像を描画しています。
//日本語表示関数をFontクラス化:これによりGUIなどでも使用可能に
class CJPFont : public IGUIFont
{
public:
CJPFont(IVideoDriver* Driver);
virtual ~CJPFont();
virtual void draw(const wchar_t* text, const rect<s32>& position,
SColor color, bool hcenter=false, bool vcenter=false,
const rect<s32>* clip=0);
virtual s32 getCharacterFromPos(const wchar_t* text, s32 pixel_x)const ;
virtual dimension2d<s32> getDimension(const wchar_t *text)const ;
virtual s32 getKerningHeight()const {return 0;}
virtual s32 getKerningWidth(const wchar_t *thisLetter=0,
const wchar_t *previousLetter=0)const {return 0;}
//virtual EGUI_FONT_TYPE getType();
virtual void setKerningHeight(s32 kerning){}
virtual void setKerningWidth(s32 kerning){}
private:
s32 getLen(const wchar_t* text);
IVideoDriver* Driver;
};
|
Fontクラス詳細
Fontクラスの詳細ですが、ここは読み飛ばしても大丈夫です。
こういう感じに定義するものと思ってください。
//コンストラクタ
CJPFont::CJPFont(IVideoDriver* driver)
{
Driver = driver;
}
//デストラクタ
CJPFont::~CJPFont()
{
if(Driver)
Driver->drop();
}
//文字の描画
void CJPFont::draw(const wchar_t* text, const rect<s32>& position,
SColor color, bool hcenter, bool vcenter, const rect<s32>* clip)
{
int len;
char* buf = NULL;
//マルチバイト文字列をchar型に変換(Windows専用 UTF->SJIS)
// len = WideCharToMultiByte(CP_ACP, 0, text, -1, NULL, 0, NULL, NULL);
// buf = (char*)malloc(len);
// WideCharToMultiByte(CP_ACP, 0, text, -1, buf, len, NULL, NULL);
buf = utf16be_to_sjis(text, &len);
//中央寄せの処理 描画位置の微調整を行います
dimension2d<s32> textDimension;
position2d<s32> offset = position.UpperLeftCorner;
if(hcenter || vcenter)
{
textDimension = getDimension(text);
if(hcenter)
offset.X = ((position.getWidth()
- textDimension.Width)>>1) + offset.X;
if(vcenter)
offset.Y = ((position.getHeight()
- textDimension.Height)>>1) + offset.Y;
}
//Printfで描画(クリッピングを行っていないので注意)
Printf(Driver, offset.X, offset.Y, color.color, buf);
free(buf);
}
//現在の位置からどの文字か取得します
s32 CJPFont::getCharacterFromPos(const wchar_t* text, s32 pixel_x)
{
s32 x = 0;
s32 i = 0;
while(text[i])
{
if(text[i] < 0x0080)
x += 8;
else x += 16;
if(x >= pixel_x)
return i;
++i;
}
return -1;
}
//描画領域サイズの取得
dimension2d<s32> CJPFont::getDimension(const wchar_t *text)
{
dimension2d<s32> dim;
int len;
len = getLen(text);
//改行コードは視野にいれてないので注意
dim.Width = len*8;
dim.Height = 16;
return dim;
}
//文字列長を取得します
s32 CJPFont::getLen(const wchar_t* text)
{
s32 len = 0;
s32 i = 0;
while(text[i])
{
//1バイト文字か2バイト文字かの判定
if(text[i] < 0x0080)
len += 1;
else len += 2;
i++;
}
return len;
}
|
レシーバー設定
キーボードやマウスのイベントのように、GUIのイベントもレシーバーで処理します。
特定のボタンに対応した処理をここに記述、またはここから呼び出します。
ここで使用しているIDは、各GUIを作成した際に引数でつけたIDになっています。
class MyEventReceiver : public IEventReceiver
{
public:
IGUIWindow* window;
virtual bool OnEvent(const SEvent& event)
{
//GUIのイベント処理
if(event.EventType == EET_GUI_EVENT)
{
s32 id = event.GUIEvent.Caller->getID();//イベントが起こったID
IGUIEnvironment* env = DEV->getGUIEnvironment();
switch(event.GUIEvent.EventType)
{
//ボタンが押された時の処理
case EGET_BUTTON_CLICKED:
//終了ボタン
if(id == 101)
{
//デバイスを終了させます
DEV->closeDevice();
return true;
}
//拡張版ボタン
if(id == 102)
{
IVideoDriver *driver = DEV->getVideoDriver();
dimension2d<s32> screen = driver->getScreenSize();
//メッセージボックスの大きさと位置指定(画面中央に表示)
s16 w = 240;
s16 h = 120;
s16 x = (screen.Width - w)/2;
s16 y = (screen.Height - h)/2;
window = env->addWindow(
rect<s32>(x,y,x+w,y+h), true, L"メッセージボックス");
//クローズボタンを非表示にします
window->getCloseButton()->setVisible(false);
//最大化ボタンを表示します
//IGUIButton *MaxBtn = window->getMaximizeButton();
//MaxBtn->setVisible(true);
//最小化ボタンを表示します
//IGUIButton *MinBtn = window->getMinimizeButton();
//MinBtn->setVisible(true);
//メッセージ
env->addStaticText(L"アイコンつき\nサンプルテキスト",
rect<s32>(60,40,150,50), false, false, window);
//画像の追加
ITexture *tex;
tex = driver->getTexture("question.png");
env->addImage(tex, position2d<s32>(20,30), true, window, 1000);
//ボタンの配置
env->addButton(rect<s32>( 15,80, 15+100,80+30),
window, 1001, L"はい");
env->addButton(rect<s32>(125,80,125+100,80+30),
window, 1002, L"キャンセル");
return true;
}
//通常版ボタン
if(id == 103)
{
window = env->addMessageBox(L"IrrLicht MessageBox",
L"これは通常版のメッセージボックスです。",true, EMBF_YES);
return true;
}
//拡張版メッセージボックスで作成したボタン
if(id > 1000)
{
//ウインドウが1つしか表示されないのを仮定してこの書き方
window->remove();
}
break;
default:
break;
}
}
return false;
}
private:
};
|
GUIスキン設定
ここからGUIの設定になります。
envを作成し、skinを取得します。そのskinに自作したFontクラスを当てはめます。
これにより、GUIでも日本語が表示できるわけです。
//GUIスキン設定
IGUIEnvironment* env = DEV->getGUIEnvironment();
IGUISkin* skin;
skin = env->getSkin(); //スキン取得
CJPFont *font;
font = new CJPFont(driver); //日本語Fontクラス用意
skin->setFont(font); //日本語Fontクラス割り当て
skin->setColor(EGDC_ACTIVE_CAPTION, 0xFFFFFF00); //アクティブは黄色に
skin->setDefaultText(EGDT_WINDOW_CLOSE , L""); //ヘルプメッセージなしに
|
透明度設定
デフォルトのGUIは標準で半透明になっています。
後ろ側も見れてなかなか良いのですが、重要なメッセージを表示している時は、
少し問題があるので、不透明に設定しなおしています。
//スキンの透明度変更
s32 i;
SColor col;
for(i=0;i<EGDC_COUNT;++i)
{
col = env->getSkin()->getColor((EGUI_DEFAULT_COLOR)i); //カラー取得
col.setAlpha(0xFF); //不透明に
env->getSkin()->setColor((EGUI_DEFAULT_COLOR)i, col); //反映
}
|

スキンカラー
設定できるスキンカラーには以下があります。
この値を使用して、特定のスキンだけ変更することも可能です。
EGDC_3D_DARK_SHADOW 最も部分
EGDC_3D_SHADOW 暗い部分
EGDC_3D_FACE 表面
EGDC_3D_HIGH_LIGHT 最も明るい部分
EGDC_3D_LIGHT 明るい部分
EGDC_ACTIVE_BORDER アクティブウインドウのボーダー
EGDC_ACTIVE_CAPTION アクティブウインドウのキャプション
EGDC_APP_WORKSPACE MDIアプリケーションの背景
EGDC_BUTTON_TEXT ボタンのテキスト
EGDC_GRAY_TEXT disable状態のテキスト
EGDC_HIGH_LIGHT コントロールで選択状態のアイテム
EGDC_HIGH_LIGHT_TEXT コントロールで選択状態のアイテムのテキスト
EGDC_INACTIVE_BORDER 非アクティブウインドウのボーダー
EGDC_INACTIVE_CAPTION 非アクティブウインドウのキャプション
EGDC_TOOLTIP ヘルプメッセージのテキストカラー
EGDC_TOOLTIP_BACKGROUND ヘルプメッセージの背景
EGDC_SCROLLBAR スクロールバーの矢印
EGDC_WINDOW ウインドウの背景
EGDC_WINDOW_SYMBOL クローズボタンなどのウインドウシンボル
EGDC_ICON ツリー/リストのアイコン
EGDC_ICON_HIGH_LIGHT ツリー/リストの選択されたアイコン
EGDC_COUNT この値は使用されません トータルのカウント数を意味します
|
GUIの配置
add〜でGUIを作成します。
作成する際に、IDを引数で指定していますが、これがレシーバーで使用されます。
終了ボタンをSJISからUTFに変換して設定しています。
ファイルから読み込んだ内容を表示したい際も、このように行うとよいでしょう。
//変換のテスト
wchar_t* buf;
int len;
char* mess = "終了";
buf = sjis_to_utf16be(mess, &len);
//GUI配置
//env->addEditBox(L"", rect<s32>(10,10,10+200,10+30), true);
env->addStaticText(L"スタティックテキスト", rect<s32>(10,50,10+200,50+30), true);
env->addButton(rect<s32>(10,100,10+200,100+30), 0, 101, buf);
env->addButton(rect<s32>(10,150,10+200,150+30), 0, 102, L"拡張版");
env->addButton(rect<s32>(10,200,10+200,200+30), 0, 103, L"通常版");
|

addStaticText
スタティックテキストを追加します。
IGUIStaticText* addStaticText(
const wchar_t* text,
const rect<s32>& rectangle,
bool border = false,
bool wordWrap = true,
IGUIElement* parent = 0,
s32 id = -1,
bool fillBackground = false
)
|
text 表示するテキストを設定します。
rectangle スタティックテキストを表示する位置を指定します。
border 枠を表示するかどうかを指定します。
wordWrap 複数行表示する際に、ラップさせるかどうかを指定します。
parent 親(例えばウインドウ)を指定します。親が動けば一緒に動きます。
id IDを指定します。
fillBackground 背景をぬりつぶすかどうか指定します。
|

addButton
ボタンを追加します。
IGUIButton* addButton(
const rect<s32>& rectangle,
IGUIElement* parent = 0,
s32 id = -1,
const wchar_t* text = 0,
const wchar_t* tooltiptext = 0
)
|
rectangle ボタンを表示する位置を指定します。
parent 親(例えばウインドウ)を指定します。親が動けば一緒に動きます。
id IDを指定します。レシーバーで処理するときに使用します。
text ボタンに表示するテキストを指定します。
tooltiptext ボタンのヘルプメッセージを指定します。
|

他のGUI
上で紹介したもの以外にも、たくさんのGUIが用意されています。
以下に代表的なものを紹介します。
addCheckBox チェックボックスを追加します。
addEditBox エディットボックスを追加します。
addListBox リストボックスを追加します。
|
描画
GUIもシーンと同じように、メインループの中でdarwAllを呼び出して描画します。
while(DEV->run())
{
driver->beginScene(true,true,0xFF6060FF);
//シーンの描画
smgr->drawAll();
//GUI描画
env->drawAll();
driver->endScene();
}
|

GUIの話
Irrlichtにはエディットボックスもあるのですが、Fontクラス化を行っただけでは、
エディットボックス内で日本語の入力はできません。
また、ライブラリを修正しないとIME変換ウインドウも左上に表示されてしまいます。
あると便利なのですが、作成したプログラムにGUIの雰囲気が合わない場合も多々あるので、
同等の機能を持ったGUIを自作した方が早いかもしれません。
使い辛いためか、次回バージョンからはGUIを外すオプションも実装されるみたいです。
|
ダウンロード
今回作成したファイル一式です。
宿題
1.ファイルから読み込んだ情報をStaticTextに表示させてみましょう。
2.ボタンを押すと、画像が出てくるようにしましょう。
|
|