DXライブラリは標準で付いてくるViewパーツのようなものがありません。
よってAndroidやMFCやiOSのように「元からあるボタンパーツをポンポンおいてイベントの中身だけ書いてあげれば出来上がり」
なんて楽ができません。
そこでDXライブラリでもAndroidらいくなボタンを作ってみましょう。
出来上がりはこんな感じです。
ボタンが複数あり、クリックイベントに応じてボタンを区別して処理することを可能にします。
全体としてはかなり長いコードになってしまいましたが、全体のソースコードを紹介します。
main.cpp
#include <DxLib.h>
#include "GameMgr.h"
int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int){
GameMgr mgr;
mgr.Main();
return 0;
}
プログラムの中核をクラスにしたいのでクラス化しています。
GameMgr.h
#pragma once
#include "OnClickListener.h"
#include "Button.h"
class GameMgr : public OnClickListener {
Button *mPrevButton;
Button *mNextButton;
public:
void Main();
void OnClick(View* view)override;
};
ボタン(Button)を2つ持っており、OnClickListenerを実装します。
ボタン押下時はこのOnClickが発火することになります。
GameMgr.cpp
#include <DxLib.h>
#include "GameMgr.h"
#include "Button.h"
#include "Mouse.h"
void GameMgr::Main(){
ChangeWindowMode(TRUE),DxLib_Init(),SetDrawScreen(DX_SCREEN_BACK);
mPrevButton = new Button(320,200,100,50,"前へ");
mPrevButton->SetOnClickListener(this);
mNextButton = new Button(440,200,100,50,"次へ");
mNextButton->SetOnClickListener(this);
while(!ScreenFlip()&&!ProcessMessage()&&!ClearDrawScreen()){
Mouse::Instance()->Update();
mPrevButton->Update();
mNextButton->Update();
mPrevButton->Draw();
mNextButton->Draw();
}
delete mPrevButton;
delete mNextButton;
DxLib_End();
}
void GameMgr::OnClick(View* view){
if(view==mPrevButton){
printfDx("「前へ」が押されました\n");
}
if(view==mNextButton){
printfDx("「次へ」が押されました\n");
}
}
Buttonクラスは生成時にx,y,w,hとボタンの中に表示する文字列を渡します。
そしてSetOnClickLietenerでボタンクリック時にこのインスタンスにコールバックできるようにリスナーを登録します。
OnClickを今回自分のクラスに実装しているのでthisを渡します。
OnClickにはviewがわたってきます。viewとはButtonなどパーツの大元の基底クラスです。
これと比較することでどのボタンが押されたかを識別します。
View.h
#pragma once
class View {
public:
virtual ~View(){}
virtual void Update(){}
virtual void Draw(){}
};
Viewの実体はありません。
継承して使う物で、インターフェイスクラス(もどき)です。
Singleton.h
/***** Singleton.h ******/
#pragma once
template <typename _T>
class Singleton {
protected:
Singleton() {}
virtual ~Singleton() {}
Singleton(const Singleton& r){}
Singleton& operator=(const Singleton& r){}
public:
static _T* Instance() {
static _T inst;
return &inst;
};
};
これはシングルトンクラスを利用する時のお決まりの書き方。
別章こちらとこちらで紹介していますので、そちらを参考にどうぞ。
Mouse.h
#pragma once
#include "Singleton.h"
class Mouse : public Singleton<Mouse> {
Mouse();
friend Singleton<Mouse>;
public:
bool Update(); //更新
int GetPressingCount(int keyCode);//keyCodeのキーが押されているフレーム数を取得
int GetReleasingCount(int keyCode);//keyCodeのキーが離されているフレーム数を取得
int GetX();
int GetY();
const static int LEFT = 0;
const static int RIGHT = 1;
const static int MIDDLE = 2;
private:
const static int BUTTON_NUM = 8;
int mKeyPressingCount [BUTTON_NUM];//押されカウンタ
int mKeyReleasingCount[BUTTON_NUM];//離されカウンタ
int mX, mY;
bool IsAvailableCode(int keyCode);//keyCodeが有効なキー番号か問う
};
Singleton.hを作ったのはMouseを作るためです。
ボタンのクリックを検出するにはマウスの状態を見て返してくれるクラスが必要なので作ります。
Mouse.cpp
#include "Mouse.h"
#include <DxLib.h>
Mouse::Mouse():
mX(0)
,mY(0)
{
memset(mKeyPressingCount, 0, sizeof(mKeyPressingCount) );
memset(mKeyReleasingCount, 0, sizeof(mKeyReleasingCount));
}
//更新
bool Mouse::Update(){
int nowInput = GetMouseInput() ; //今のキーの入力状態を取得
for(int i=0; i<BUTTON_NUM; i++){
if((nowInput>>i)&0x01){ //i番のキーが押されていたら
if(mKeyReleasingCount[i] > 0){//離されカウンタが0より大きければ
mKeyReleasingCount[i] = 0; //0に戻す
}
mKeyPressingCount[i]++; //押されカウンタを増やす
} else { //i番のキーが離されていたら
if(mKeyPressingCount[i] > 0){ //押されカウンタが0より大きければ
mKeyPressingCount[i] = 0; //0に戻す
}
mKeyReleasingCount[i]++; //離されカウンタを増やす
}
}
GetMousePoint(&mX, &mY);
return true;
}
//keyCodeのキーが押されているフレーム数を返す
int Mouse::GetPressingCount(int keyCode){
if(!Mouse::IsAvailableCode(keyCode)){
return -1;
}
return mKeyPressingCount[keyCode];
}
//keyCodeのキーが離されているフレーム数を返す
int Mouse::GetReleasingCount(int keyCode){
if(!Mouse::IsAvailableCode(keyCode)){
return -1;
}
return mKeyReleasingCount[keyCode];
}
//keyCodeが有効な値かチェックする
bool Mouse::IsAvailableCode(int keyCode){
if(0<=keyCode && keyCode<BUTTON_NUM){
return true;
}
return false;
}
int Mouse::GetX(){
return mX;
}
int Mouse::GetY(){
return mY;
}
コードは長いですが、やっていることはなんてことはありません。
キーボードで紹介したコードのマウス版です。
OnClickListener.h
#pragma once
#include "View.h"
class OnClickListener {
public:
virtual ~OnClickListener(){}
virtual void OnClick(View* view){}
};
OnClickListenerはonClickを呼ぶためのインターフェイスを用意しているだけです。
これを実装しているクラスのインスタンスをButtonが持つことでButtonからクリックを検出してGameMgrにイベントを渡すことが出来るのです。
Button.h
#pragma once
#include "View.h"
#include "OnClickListener.h"
class Button : public View {
public:
Button(int x, int y, int w, int h, const char* str);
void Update() override;
void Draw() override;
void SetOnClickListener(OnClickListener* listener);
private:
int mX, mY, mW, mH;
const char *mStr;
bool IsPressed;
OnClickListener* mListener;
};
今回のキモとなるButtonクラスです。
基本的にゲーム制作前にここをしっかり作っておき、ゲーム制作中は変更しない前提です。
SDKなどにあらかじめ入っているパーツだというイメージで作っています。
Button.cpp
#include <DxLib.h>
#include "Button.h"
#include "Mouse.h"
Button::Button(int x, int y, int w, int h, const char* str) :
mListener(NULL)
,IsPressed(false)
{
mX = x;
mY = y;
mW = w;
mH = h;
mStr = str;
}
void Button::Update(){
int x = Mouse::Instance()->GetX();
int y = Mouse::Instance()->GetY();
if(Mouse::Instance()->GetPressingCount(Mouse::LEFT)==0){
IsPressed = false;
}
if(Mouse::Instance()->GetPressingCount(Mouse::LEFT)==1){
if(mX<=x && x<=mX+mW && mY<=y && y<=mY+mH){
mListener->OnClick(this);
IsPressed =true;
}
}
if(!(mX<=x && x<=mX+mW && mY<=y && y<=mY+mH)){
IsPressed = false;
}
}
void Button::Draw(){
int sub=0;
if(IsPressed){
sub = 2;
}
int strW = GetDrawStringWidth(mStr, strlen(mStr));
DrawBox(mX+sub, mY+sub, mX+mW-sub, mY+mH-sub, GetColor(100,255,255),TRUE);
DrawBox(mX+sub, mY+sub, mX+mW-sub, mY+mH-sub, GetColor(255,100,100),FALSE);
int strX = mX+mW/2-strW/2;
int strY = mY+mH/2-DEFAULT_FONT_SIZE/2;
DrawString(strX,strY,mStr,GetColor(0,0,0));
}
void Button::SetOnClickListener(OnClickListener* listener){
mListener = listener;
}
Update()であたり判定を計算しています。
GetPressingCountが1の時がクリックなので、その時ボタンの矩形の中にマウスがあればクリックされたイベントを発火させます。
mListener->OnClick(this)を呼ぶことでGameMgrにイベントをコールバックします。
その時引数にthisを渡してやることでどのインスタンスのボタンからイベントが発火したのか区別できるようにします。
プロジェクトを一式ダウンロード
- Remical Soft -