Home | Lesson | Game | Tool | Link

A08.ユーザーイベント

ユーザーが独自に設定したイベントを発行/受信します。
まずは、独自のボタンコントロールの処理を行います。
操作方法
X軸回転 :X軸方向への回転をON/OFFします。
Y軸回転 :Y軸方向への回転をON/OFFします。
Z軸回転 :Z軸方向への回転をON/OFFします。
速度変更:回転速度を0.01〜0.1で変化させます。1回押すと+0.01です。
拡張項目:無効/有効、非表示/表示を切り替えます。
情報表示:現在は使用できません。
終了確認:現在は使用できません。
終了    :プログラムを終了します。

設定項目に合わせて、画面右の立方体が動きます。


タイプとIDを定義
ユーザーイベントを使用するにあたって、わかりやすいタイプ名とIDを定義しておきます。
ユーザーIDにはここで定義したID以外も使用することが可能です。
ここで定義したものは予約語みたいなものです。
//ユーザーイベントタイプ
enum EUSER_MESSAGE_TYPE
{
	EUMT_BUTTON_CLICKED = 0
};

//ユーザーイベントID
enum USER_ID
{
	UID_NONE = 0,
	UID_OK,
	UID_CANCEL,
	UID_ABORT,
	UID_RETRY,
	UID_IGNORE,
	UID_YES,
	UID_NO,
	UID_CLOSE,
	UID_HELP,
	UID_EXIT
};

ユーザーイベント送信
postEventFromUserを使用してユーザーイベントを送信します。
ユーザーデータには明確な使用方法が存在していないので、以下のようにします。
UserData1:イベントタイプ(クリックされた、ドラッグされたなど)
UserData2:イベントID
2つの値を設定して送信します。
//ユーザーイベント送信
void postEvent(s32 eventType, s32 eventId)
{
	SEvent event;
	event.EventType = EET_USER_EVENT;
	event.UserEvent.UserData1 = eventType;
	event.UserEvent.UserData2 = eventId;

	device->postEventFromUser(event);
}


postEventFromUser
ユーザーが作成したイベントをエンジンに送信します。
void postEventFromUser
(
	SEvent event
)
event イベント内容を設定します。

ユーザー用に用意されているデータは、以下のようになっています。
event.UserEvent.UserData1 int型のデータを設定します。
event.UserEvent.UserData2 int型のデータを設定します。
event.UserEvent.UserData3 float型のデータを設定します。

イベント処理
イベントレシーバーにユーザーイベントが送信されてくるので、
ここでイベントをそれぞれ処理します。
UID_EXITが設定されていた場合は、デバイスを終了させます。
他のIDが指定されていた場合は、対応する処理を行います。
ユーザーイベント自体は以上で終了ですが、呼び出し元がないので、
次から呼び出しもとの設定も行います。
//レシーバー設定
class MyEventReceiver : public IEventReceiver
{
public:
    virtual bool OnEvent(const SEvent& event)
    {
        //ユーザーイベント処理
        if(event.EventType == EET_USER_EVENT)
        {
            //イベントが起こったID
            s32 id = event.UserEvent.UserData2;

            switch(event.UserEvent.UserData1)
            {
                //ボタンが押された時の処理
                case EUMT_BUTTON_CLICKED:
                    switch(id)
                    {
                        case 0x10:
                            b_state.rx = 1-b_state.rx;
                            break;
                        case 0x11:
                            b_state.ry = 1-b_state.ry;
                            break;
                        case 0x12:
                            b_state.rz = 1-b_state.rz;
                            break;
                        case 0x13:
                            b_state.speed += 0.01f;
                            if(b_state.speed > 0.1f)b_state.speed = 0.01f;
                            break;
                        case 0x14:
                            if(b_state.bShow)b_state.bShow = false;
                            else b_state.bShow = true;
                            break;
                        case UID_EXIT:
                            //デバイスを終了させます
                            device->closeDevice();
                            break;
                        default:
                            return true;
                    }
                    return true;
                    break;
                default:
                    return true;
            }
        }
        return false;
    }
};

データ用意
マウスで処理していくので、マウス/ボタン用のデータを作成していきます。
move:マウスの現在の座標が入ります。
press:マウスの左ボタンを押した座標が入ります。
release:マウスの左ボタンを離した座標が入ります。
ボタン用のデータとして、XYZに対しての回転許可、回転速度、表示のON/OFFを保存できるようにします。
//マウスデータ
typedef struct
{
	position2d move;

	s32 state;
	position2d press;		//state = 1
	position2d release;	//state = 0
}mouseData;
mouseData mouse;

//ボタン設定
typedef struct
{
	s32  rx,ry,rz;
	f32  speed;
	bool bShow;
}buttonState;
buttonState b_state = {0,0,0,0.01f,false};


マウス用関数
マウスの情報を消去、マウスの座標チェック、状態取得を行っています。
状態取得関数では、マウスがクリックされた情報をもとに、イベントを送信しています。
もっと綺麗なやい方がありそうですが…。
//マウス情報消去
void mouseClear()
{
	mouse.state     =  0;
	mouse.press.X   = -1;
	mouse.press.Y   = -1;
	mouse.release.X = -1;
	mouse.release.Y = -1;
}

//指定範囲内にマウスがあるか
bool checkRange(s32 mouseX, s32 mouseY, s32 x, s32 y, s32 w, s32 h)
{
	if( x <= mouseX && mouseX <= x+w &&
	    y <= mouseY && mouseY <= y+h)
		return true;

	return false;
}

//マウスの状態取得
int getState(mouseData m_data, s32 ctrlID, s32 x, s32 y, s32 w, s32 h)
{
	s32 state = 1;

	//マウスがコントロール内か
	if(checkRange(m_data.move.X, m_data.move.Y, x,y,w,h))
	{
		//左ボタンが押されているか
		if(m_data.state == 1)
		{
			//押した場所がコントロール内か
			if(checkRange(m_data.press.X, m_data.press.Y, x,y,w,h))
				state = 3;
		}
		//押されていない
		else 
		{
			//押して離した場所が同一のコントロールの場合
			if( checkRange(m_data.press.X, m_data.press.Y, x,y,w,h) && 
			    checkRange(m_data.release.X, m_data.release.Y, x,y,w,h))
			{
				mouseClear();
				postEvent(EUMT_BUTTON_CLICKED, ctrlID);
			}
			state = 2;
		}
	}
	return state;
}

コントロール
コントロールを作成します。
・コントロールの基本となるベースコントロール。
・今回ユーザーイベントで使用するボタンコントロール。
・コントロールを処理するGUI。

ベースコントロールは、今後色々なコントロールを追加するかもしれないので、
基本的な処理をするように設定しています。
ボタンコントロールは、追加、キャプション変更、表示の機能を持っています。
マウスの状態によって、ボタンの色が変わるようになっています。
GUIは、各機能の呼び出しと、全ての描画を行います。
メッセージウインドウなどの追加により、アクティブウインドウが変更された場合などに
各コントロールを調整したりするようにします。
//ベースコントロールクラス
class BaseCtrl
{
public:
	//コントロール初期化
	void initialize(c8* name)
	{
		ctrlName = name;
		bVisible = true;
		bEnable  = true;
	}

	//コントロール名取得
	const c8* GetCtrlName() const {return ctrlName;}
	//コントロールの可視状態設定
	void setVisible(bool bState){bVisible = bState;}
	//コントロールの可視状態取得
	bool getVisible(){return bVisible;}
	//コントロールの有効無効設定
	void setEnable(bool bState){bEnable = bState;}
	//コントロールの有効無効取得
	bool getEnable(){return bEnable;}

private:
	c8*  ctrlName;
	bool bVisible;
	bool bEnable;
};

//ボタンコントロールクラス
class ButtonCtrl : public BaseCtrl
{
public:
	ButtonCtrl(){init();}
	~ButtonCtrl(){}

	//コントロール初期化
	void init()
	{
		name = NULL;
		cx = cy = cw = ch = 0;
		state   = 0;
		ctrlID  = 0;
		bCenter = false;

		initialize("ButtonCtrl");
	}

	//コントロール追加
	bool add(c8* caption, s32 id, s32 x, s32 y, s32 w, s32 h, bool center)
	{
		name    = caption;
		cx      = x;
		cy      = y;
		cw      = w;
		ch      = h;
		ctrlID  = id;
		bCenter = center;

		return true;
	}

	//キャプション変更
	void setText(c8*caption)
	{
		name = caption;
	}

	//コントロール表示
	void draw(mouseData m_data, bool bActive)
	{
		s32 len;
		s32 state = 0;
		s32 sx    = 4;		//左寄せ
		s32 sy    = (ch - 16) / 2;	//中央寄せ
		
		//カラー          無効        通常        マウスオン  クリック  
		SColor back[4] = {0xFF999999, 0xFF00FF00, 0xFF33FF33, 0xFF66FF66};
		SColor top[4]  = {0xFF333333, 0xFF006600, 0xFF339933, 0xFF66CC66};
		SColor font[4] = {0xFFCCCCCC, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF};

		//名前チェック
		if(name == NULL || strlen(name) == 0)
			return;
		len = strlen(name);

		//中央寄せ
		if(bCenter)
			sx = (cw - len*8)/2;	//中央寄せ

		//表示可の場合は表示する
		if(getVisible())
		{
			//有効かどうか
			if(getEnable())
			{
				//アクティブかどうか
				if(bActive)
				{
				    //詳細を取得する
				    state = getState(m_data, ctrlID, cx,cy,cw,ch);
				}
				else state = 1;
			}

			//ボタン作成
			FillRect(driver, cx,cy,cw,ch, back[state]);
			FillRect(driver, cx+1,cy+1,cw-2,ch-2, top[state]);
			Printf(driver,cx+sx,cy+sy,font[state],name);
		}
	}

private:
	c8* name;
	s32 cx,cy,cw,ch;
	s32 state;
	s32 ctrlID;
	bool bCenter;
};

class UserGUI
{
public:
	UserGUI(){init();}
	~UserGUI(){}

	void init()
	{
		btnCnt = 0;
	}

	void setEnable(s32 num, bool state)
	{
		btn[num].setEnable(state);
	}

	void setVisible(s32 num, bool state)
	{
		btn[num].setVisible(state);
	}

	void setText(s32 num, c8* caption)
	{
		btn[num].setText(caption);
	}

	bool button(c8* caption, s32 id, s32 x, s32 y, s32 w, s32 h, bool center)
	{
		if(btnCnt > 9)
			return false;

		btn[btnCnt].add(caption, id,x,y,w,h,center);
		btnCnt++;

		return true;
	}

	void drawAll()
	{
		u32 i;
		for(i=0;i<btnCnt;i++)
			btn[i].draw(mouse, true);
	}

private:
	s32 btnCnt;
	ButtonCtrl btn[10];
};

イベントレシーバー調整
マウスでの処理を追加します。以下を追加しています。
・左ボタンを押した座標を取得。
・左ボタンを離した座標を取得。
・マウスの座標を取得。
	virtual bool OnEvent(const SEvent& event)
	{
		//マウスイベント
		if(event.EventType == EET_MOUSE_INPUT_EVENT)
		{
			switch(event.MouseInput.Event)
			{
				case EMIE_LMOUSE_PRESSED_DOWN:
					mouse.state = 1;
					mouse.press.X = event.MouseInput.X;
					mouse.press.Y = event.MouseInput.Y;
					return true;
				case EMIE_LMOUSE_LEFT_UP:
					mouse.state = 0;
					mouse.release.X = event.MouseInput.X;
					mouse.release.Y = event.MouseInput.Y;
					return true;
				case EMIE_MOUSE_MOVED:
					mouse.move.X = event.MouseInput.X;
					mouse.move.Y = event.MouseInput.Y;
					return true;
				default:
					return true;
			}
		}
		//ユーザーイベント処理
		if(event.EventType == EET_USER_EVENT)

ボタン追加
画面左側にボタンを8個追加します。
newで作成しているので、最後に忘れずにdeleteしてください。
	//ボタン作成
	c8 buf[32];
	UserGUI *userGui = new UserGUI();
	userGui->button("X軸回転",  0x10, 16, 16,144,20, true);
	userGui->button("Y軸回転",  0x11, 16, 40,144,20, true);
	userGui->button("Z軸回転",  0x12, 16, 64,144,20, true);
	userGui->button("速度変更", 0x13, 16, 88,144,20, true);
	userGui->button("拡張項目", 0x14, 16,112,144,20, true);
	userGui->button("情報表示", 0x15, 16,136,144,20, true);
	userGui->button("終了確認", 0x16, 16,160,144,20, true);
	userGui->button("終了", UID_EXIT, 16,184,144,20, true);

表示
ボタンには、以下の特別な処理を行っています。
・回転速度によってボタンのキャプションを変更しています。
・表示状態によって、ボタンを無効化/有効化、表示/非表示を切り替えています。
無効のボタンは表示されていても、押すことができません。
非表示のボタンは、画面に表示もされません。
smgr->drawAll()の後に、GUIの描画を行っています。
これは、一番前面に表示するためです。もしsmgr->drawAll()の前に描画したら、
ポリゴンなどの後ろに描画されるようになります。
	while(device->run())
	{
		driver->beginScene(true,true,0xFF6060FF);

		//背景作成
		FillRect(driver, 164,16,144,160, 0xFFFFFFFF);
		FillRect2(driver,165,17,142,158, 0xFF666666,
			0xFF000000,0xFF000000,0xFF666666,PS_SPECIAL);

		//ボタン設定
		sprintf(buf, "速度変更:%0.2f", b_state.speed);
		userGui->setText(3, buf);			//キャプション変更
		userGui->setEnable(5, b_state.bShow);	//有効/無効設定
		userGui->setVisible(6, b_state.bShow);	//表示/非表示設定

		//図形作成
		node->setRotation(vector3df(rx,ry,rz));
		if(b_state.rx)rx += b_state.speed;
		if(b_state.ry)ry += b_state.speed;
		if(b_state.rz)rz += b_state.speed;

		//シーンの描画
		smgr->drawAll();

		//GUI描画
		userGui->drawAll();

		driver->endScene();
	}

	driver->drop();

	delete userGui;


図形/文字の描画
図形/文字の描画には、今までに作成してきたDraw系の関数を使用しています。
darw.cppとdraw.hにまとめてあるのをインクルードしています。
今回作成したボタンはFillRectで作成してありますが、DrawImageを使用すれば
画像に差し替えることも可能です。
また、「ボタンが押された」というのをわかりやすくするために
表示座標を変更してみるのもよいかもしれません。

ダウンロード
今回作成したファイル一式です。 cube.xに法線を加えたものも同梱してあります。

宿題
1.ボタンの背景に画像を使用してみましょう。
2.クリック状態により、画像が変更するようにしてみましょう。
3.クリック感を出すために、クリックしたボタンの座標を1ずらしてみましょう。