マウス操作の選択画面

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
パル

マウス操作の選択画面

#1

投稿記事 by パル » 10年前

コード:

 
#include "DxLib.h"

// メニュー項目のクラス
class MenuElement_t{
public:
	char name[128]; // 項目名格納用変数
	int x, y;       // 座標格納用変数
	int state;		// 0:初期状態 1:オンマウス 2:クリックされた
	void OnClick(int i){  //あとで処理を変える
		switch (i){
			case 0:
				//ゲームスタート
				DrawString(0, 0, "ゲームスタート", GetColor(100, 100, 100));
				break;
			case 1:
				//おまけ
				DrawString(0, 0, "おまけ", GetColor(100, 100, 100));
				break;
			case 2:
				//ヘルプ
				DrawString(0, 0, "ヘルプ", GetColor(100, 100, 100));
				break;
			case 3:
				//コンフィグ
				DrawString(0, 0, "コンフィグ", GetColor(100, 100, 100));
				break;
			case 4:
				//ゲーム終了
				DrawString(0, 0, "ゲーム終了", GetColor(100, 100, 100));
				break;
			default:
				//エラー
				DrawString(0, 0, "エラー", GetColor(100, 100, 100));
				break;
		}
	}
};

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int){
	ChangeWindowMode(TRUE), DxLib_Init(), SetDrawScreen(DX_SCREEN_BACK); //ウィンドウモード変更と初期化と裏画面設定

	// メニュー項目要素を5つ作る
	MenuElement_t MenuElement[5] = {
		{  "ゲームスタート",100, 100, 0 }, // タグの中身の順番で格納される。
		{  "おまけ",        100, 150, 0 },
		{  "ヘルプ",        100, 200, 0 },
		{  "コンフィグ",    100, 250, 0 },
		{  "ゲーム終了",    100, 300, 0 }
	};
	
	int MouseX, MouseY;

	// while(裏画面を表画面に反映, メッセージ処理, 画面クリア, キー更新)
	while (ScreenFlip() == 0 && ProcessMessage() == 0 && ClearDrawScreen() == 0){

		// 計算フェーズ 
		GetMousePoint(&MouseX, &MouseY);                //マウスの座標取得
		for (int i = 0; i < 5; i++){
			if (MenuElement[i].x < MouseX && MouseX < (MenuElement[i].x + GetDrawStringWidth(MenuElement[i].name, strlen(MenuElement[i].name)))	
			 && MenuElement[i].y < MouseY && MouseY < (MenuElement[i].y + 16)){		//←16:文字の高さ(初期値?)	↑文字列の幅
				MenuElement[i].state = 1;
				if ((GetMouseInput() & MOUSE_INPUT_LEFT) != 0)
				{
					// 押されている
					MenuElement[i].state = 2;
				}
			}
			else{
				MenuElement[i].state = 0;
			}
		}

		// 描画フェーズ
		for (int i = 0; i<5; i++){			// メニュー項目数である5個ループ処理
			switch (MenuElement[i].state){	// 0:初期状態 1:オンマウス 2:クリックされた
			case 0: 
				DrawFormatString(MenuElement[i].x, MenuElement[i].y, GetColor(255, 255, 255), MenuElement[i].name); 
				break;
			case 1: 
				DrawFormatString(MenuElement[i].x, MenuElement[i].y, GetColor(  0, 255,   0), MenuElement[i].name); 
				break;
			case 2: 
				DrawFormatString(MenuElement[i].x, MenuElement[i].y, GetColor(  0,   0, 255), MenuElement[i].name); 
				MenuElement[i].OnClick(i);	//内容が描画なのでここに
				break;
			default:	//エラー
				DrawFormatString(MenuElement[i].x, MenuElement[i].y, GetColor(255,   0,   0), MenuElement[i].name); 
				break;
			}
		}
	}

	DxLib_End(); // DXライブラリ終了処理
	return 0;
}
 
[1] 質問文
 [1.1] 自分が今行いたい事は何か
 ・マウスで操作できる選択画面を作りたい
 ・基本的なコードの書き方を知りたい
 [1.2] どのように取り組んだか(プログラムコードがある場合記載)
  上記
 [1.4] 今何がわからないのか、知りたいのか
 ・メニュー項目が増えるとOnClick()のswitch分が長くなるのが気になるのですが
  他に方法があれば教えてほしいです
  それぞれの処理用の関数を作って、そちらに飛ばす予定ではあります
 ・クラスの変数はそれぞれSet関数Get関数を作ってprivateに移動するつもりですが
  そうするとGetDrawStringWidth(MenuElement.name, strlen(MenuElement.name))のような部分が
  さらに長くなりますがそれでいいのでしょうか?
 ・コメントの付け方はこんな感じでいいのでしょうか?(コピペ元のも残ってますが)
 ・コードを他の人に見せるのは初めてなので、他にも気になる点があれば指摘してほしいです

[2] 環境  
 [2.1] OS : Windows8.1
 [2.2] コンパイラ名 :Visual Studio Express 2013 for Windows Desktop

[3] その他
 ・どの程度C言語を理解しているか
  いろいろなサイトの入門編を読んで実践を始めたばかり
  新ゲームプログラミングの館は4.4章までと4.7章を読みました
 ・ライブラリを使っている場合は何を使っているか
  DXライブラリ

パル

Re: マウス操作の選択画面

#2

投稿記事 by パル » 10年前

すいませんもう一つ質問です
このようにコピペして作ったコードを他の場所で見せたり
プログラムを公開したりしてもいいものなのでしょうか?

FUNK
記事: 25
登録日時: 11年前

Re: マウス操作の選択画面

#3

投稿記事 by FUNK » 10年前

・メニュー項目が増えるとOnClick()のswitch分が長くなるのが気になるのですが
  他に方法があれば教えてほしいです
  それぞれの処理用の関数を作って、そちらに飛ばす予定ではあります
ゲームの設計思想としては描画処理は他の処理から完全に独立するのが理想です。
WinMainの方は「計算フェーズ」と「描画フェーズ」の様に分けていますが、「MenuElement_t」クラスは分けれていません。
この様に独立が完全ではないと、他の箇所では描画処理を切り分けていてもあまり意味をなさいません。

あとswitchを使いたくなければ例えば以下の様なやり方もあります。

コード:

const char name[6][128] = { "ゲームスタート", "おまけ", "ヘルプ", "コンフィグ", "ゲーム終了", "エラー" };
...
DrawString(0, 0, name[i], GetColor(100, 100, 100));
もちろん他にも色々やり方はあります。
またアドバイスですが、メニューのクラスひとつだけではなく、さらに細分化してメニューの項目というクラスを作り、
メニューの項目のインスタンス生成時に「ゲームスタート」などの要素を持たせる作りの方が柔軟性やメンテナンス性が高まります。
・クラスの変数はそれぞれSet関数Get関数を作ってprivateに移動するつもりですが
  そうするとGetDrawStringWidth(MenuElement.name, strlen(MenuElement.name))のような部分が
  さらに長くなりますがそれでいいのでしょうか?


ここら辺は好みですが、クラス名、変数名を説明的(で長い)にすることで、特にコメントを設けなくても名前でわかるというメリットはあります。

・コメントの付け方はこんな感じでいいのでしょうか?(コピペ元のも残ってますが)


ソースを他人に見せる機会が多い、複数人でソースを共有して開発する、ということではなく、
個人的なものであればコメントは自分自身に書くことになりますので、自分がよければいいと思います。

のようにコピペして作ったコードを他の場所で見せたり
プログラムを公開したりしてもいいものなのでしょうか?


マナーの視点から言えば、製作者元に一言あった方がいいかもしれません。
Boogaloo is funk feelin'.

パル

Re: マウス操作の選択画面

#4

投稿記事 by パル » 10年前

回答ありがとうございます
FUNK さんが書きました: ゲームの設計思想としては描画処理は他の処理から完全に独立するのが理想です。
WinMainの方は「計算フェーズ」と「描画フェーズ」の様に分けていますが、「MenuElement_t」クラスは分けれていません。
この様に独立が完全ではないと、他の箇所では描画処理を切り分けていてもあまり意味をなさいません。
完全独立を目指して頑張りたいと思います
FUNK さんが書きました: またアドバイスですが、メニューのクラスひとつだけではなく、さらに細分化してメニューの項目というクラスを作り、
メニューの項目のインスタンス生成時に「ゲームスタート」などの要素を持たせる作りの方が柔軟性やメンテナンス性が高まります。
それだとMenuElementを使ったfor分が使えなくなるように思うのですが
メニュー画面の作り方sp.3~sp.6のようなことができるのでしょうか?
少し読んでみて使えそうだと思ったのですが、理解できずに諦めてしまいました
もしそうなら頑張って勉強してみます 全然違ったらすいません

FUNK
記事: 25
登録日時: 11年前

Re: マウス操作の選択画面

#5

投稿記事 by FUNK » 10年前

ちょっと説明が長くなりそうな気がしたので実際にコードを書いて見ました。
また書くに当たり最初のソースを基にしていますが、
書き方(コーディングルール)を普段私が書いている方法に近くなってしまいましたがご了承下さい。

コード:

#include "DxLib.h"
 
#define MENU_STRING_COLOR_NORMAL	GetColor( 255, 255, 255 )	// メニュー項目文字列カラー(通常)
#define MENU_STRING_COLOR_OVER		GetColor(   0, 255,   0 )	// メニュー項目文字列カラー(マウスオーバー)
#define MENU_STRING_COLOR_CLICK		GetColor(   0,   0, 255 )	// メニュー項目文字列カラー(クリック)
#define MENU_STRING_COLOR_ERROR		GetColor( 255,   0,   0 )	// メニュー項目文字列カラー(エラー)

#define MENU_NUM					5							// メニュー項目数
#define MENU_STRING_SIZE			16							// メニュー項目文字列の文字サイズ

// メニュー項目のクラス
class C_MenuElement
{
public:

	// コンストラクタ
	C_MenuElement( const char* name, int x, int y )
	{
		strcpy( m_name, name );
		m_x = x;
		m_y = y;
	}

	// 更新処理
	void Update( void )
	{
		int mouse_x, mouse_y;

		GetMousePoint( &mouse_x, &mouse_y );	//マウスの座標取得
		if( m_x < mouse_x && mouse_x < m_x + GetDrawStringWidth( m_name, strlen( m_name ) ) &&
			m_y < mouse_y && mouse_y < m_y + MENU_STRING_SIZE )	// 文字列領域内
		{
			if( ( GetMouseInput() & MOUSE_INPUT_LEFT) != 0 )		// 押されている
				m_state = 2;
			else													// 押されてない
				m_state = 1;
		}
		else													// 領域外
			m_state = 0;
	}

	// 描画処理
	void Draw( void )
	{
		int string_color;

	    switch( m_state )
		{
			case 0:		string_color = MENU_STRING_COLOR_NORMAL;	break;
			case 1:		string_color = MENU_STRING_COLOR_OVER;		break;
			case 2:		string_color = MENU_STRING_COLOR_CLICK;		break;
			default:	string_color = MENU_STRING_COLOR_ERROR;		break;
		}

		DrawString( m_x, m_y, m_name, string_color );
	}

private:

	char	m_name[128];	// 項目名格納用変数
	int		m_x, m_y;		// 座標格納用変数
	int		m_state;		// 0:初期状態 1:オンマウス 2:クリックされた
};

// メニューのクラス
class C_Menu
{
public:

	// コンストラクタ
	C_Menu( void )
	{
		const char* menu_name[MENU_NUM] = { "ゲームスタート", "おまけ", "ヘルプ", "コンフィグ", "ゲーム終了" };
		const int menu_y[MENU_NUM] = { 100, 150, 200, 250, 300 };

		// メニュー項目を5つ作成(クラス配列動的確保)
		for( int i = 0; i < MENU_NUM; i++ )
			menu_element[i] = new C_MenuElement( menu_name[i], 100, menu_y[i] );
	}

	// デストラクタ
	~C_Menu( void )
	{
		// クラス配列解放
		for( int i = 0; i < MENU_NUM; i++ )
			delete menu_element[i];
	}

	// 更新処理
	void Update( void )
	{
		for( int i = 0; i < MENU_NUM; i++ )
			menu_element[i]->Update();
	}

	// 描画処理
	void Draw( void )
	{
		for( int i = 0; i < MENU_NUM; i++ )
			menu_element[i]->Draw();
	}

private:

	C_MenuElement* menu_element[MENU_NUM];		// メニュー項目のクラスポインタ
};
 
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int){
    ChangeWindowMode(TRUE), DxLib_Init(), SetDrawScreen(DX_SCREEN_BACK); //ウィンドウモード変更と初期化と裏画面設定

    C_Menu menu;		// メニュー作成
 
    // while(裏画面を表画面に反映, メッセージ処理, 画面クリア, キー更新)
    while (ScreenFlip() == 0 && ProcessMessage() == 0 && ClearDrawScreen() == 0){
 
        // 更新フェーズ 
		menu.Update();
 
        // 描画フェーズ
        menu.Draw();
    }
 
    DxLib_End(); // DXライブラリ終了処理
    return 0;
}
メニューとメニュー項目を分けました。
こうする事で、例えば新しい項目を追加したい時はとても簡単に出来ますし、
他の場面で同じことをしたい場合は使いまわし出来ます。

また、各クラスのメンバ変数はprivateに書いて、外部から直接触れないようにしてカプセル化を図っています。
それだとMenuElementを使ったfor分が使えなくなるように思うのですが


たぶんこれは「クラスのインスタンス生成において、要素を引数で渡せないのでは?」と言う事だと思うのですが、
確かに基本的にC++はインスタンス生成を配列にすると、引数付きのコンストラクタが呼べなくなります。
実は実現可能なやり方が幾つかありまして、
一番シンプルなのは、
クラスのポインタの配列を作って、そこへ「new」で動的生成することでひとつずつ生成出来ますので、
これで実現できます。

またこのソースでは、描画と計算(すみません、表現を「更新」に変えさせてもらいました)は独立させました。
どちらかを呼ばなくても、もう片方に影響はありません。
こうする事で例えばポーズ(一時停止)させたい場合は更新処理のメソッドを呼ばないだけで可能です。


このソースにてわからないところがありましたら質問して下さい。
Boogaloo is funk feelin'.

パル

Re: マウス操作の選択画面

#6

投稿記事 by パル » 10年前

それだとMenuElementを使ったfor分が使えなくなるように思うのですが

すいません
class gamestart、class omake、、、と作ってgamestart menu1、omake menu2、、、ということかと思ってました;

ソースコードについては読めはするけど自分では書けなそうです・・
ポインタの使いどころがよく分かってないからだと思いますが

コード:

 
//変更箇所だけ わかりにくかったらすいません
C_MenuElement menu_element[MENU_NUM];
menu_element[i].Update();
menu_element[i].Draw();

// コンストラクタ 引数なしに
C_MenuElement(){}

//関数追加 二度手間ですが・・
void SetMenuElement(const char* name, int x, int y)
{
	strcpy_s(m_name,128, name);
	m_x = x;
	m_y = y;
}

for (int i = 0; i < MENU_NUM; i++){
	menu_element[i].SetMenuElement(menu_name[i], 100, menu_y[i]);
}
 
と書いてしまいそうです
これで一応うまくいきましたが、これだと問題ありますか?
一番の問題は初期化する関数がコンストラクタではないことでしょうか・・
自分でもポインタとnewを使えるようになりたいのですが、よく理解せずに使うのは危険らしいというのもあり・・
今からでも使って慣れるべきでしょうか?

アバター
usao
記事: 1887
登録日時: 11年前

Re: マウス操作の選択画面

#7

投稿記事 by usao » 10年前

No.5に書かれたコードのように,
多態性を用いたりせずに単に同じC_MenuElement型の要素を固定数持つだけで良いのであれば,
ポインタ配列とnewをわざわざ持ち出すことの利点は特にないと思います.
(C_MenuElement型のサイズが巨大だとかいうわけでもないでしょうし,
 C_Menu型のコピーが問題になったりして,面倒になるだけでしょう.)

コンストラクタで SetMenuElement() を全要素に対してコール等しとけばそれで済むのではないでしょうか.


>自分でもポインタとnewを使えるようになりたいのですが、よく理解せずに使うのは危険らしいというのもあり・・
>今からでも使って慣れるべきでしょうか?

ポインタやnew等が無い状態でやっていくのは非常に困難だと思います.

FUNK
記事: 25
登録日時: 11年前

Re: マウス操作の選択画面

#8

投稿記事 by FUNK » 10年前

元のソースや質問から、インスタンスを生成するタイミングでメニューの要素を格納したいと思いまして、
それを実現する為の方法としてnewを使いました。
そうでなくてもいいのであれば、そのソースにおける「SetMenuElement」という関数を設けるのももちろん有りです。
その場合はインスタンスを生成した後、必ず実行しないといけない事を忘れてはいけませんが・・・。
(おせっかいかもしれませんが、その関数の命名は初期処理を表す感じで「Initialize、 InitMenuElement、etc...」と言う方がわかりやすいかもしれません)
自分でもポインタとnewを使えるようになりたいのですが、よく理解せずに使うのは危険らしいというのもあり・・
今からでも使って慣れるべきでしょうか?
まず「危険」といのは、例えばPCが爆発したり、犯罪行為だったり、命が脅かされることなのでしょうか?
当然そんなことはありません。
私の考えでは、勉強を兼ねているのであればどんどん失敗しても・・・いや、むしろ失敗した方が良い経験値が貯まります。
webや参考書からただ知識だけを頭に入れていくより、自分で上手く行かない所を試行錯誤した方が実感の伴う本当の理解に繋がります。
それと最初は理解できてなくても使っているうちにいつの間にか理解していた、というのもあるかもしれません。
それとよく「諦める」という言葉が出てきますが、諦めていたら永遠に理解できませんよ。
(もちろん解らないものに固執してしばらく手が止まるのも問題ですが)

ポインタはC言語ではまだしも、C++だと最も基本的なものと言うレベルです。
今後学んでいくであろう様々な手法にかなりの頻度でポインタは出てきます。
私が思うに「ポインタが難しいと感じる人は難しく考えすぎているのでは」というのがあると思います。
実際にはポインタはかなりシンプルなものです。
Boogaloo is funk feelin'.

パル

Re: マウス操作の選択画面

#9

投稿記事 by パル » 10年前

お二人ともありがとうございます
とりあえず「クラスの配列を作るときはポインタとnewを使うと便利」ということを覚えておいて
これから少しずつ慣れていこうと思います
ということで解決です

アバター
usao
記事: 1887
登録日時: 11年前

Re: マウス操作の選択画面

#10

投稿記事 by usao » 10年前

オフトピック
C → C++ という普通な(?)順で学ばれているのではなくて
いきなり C++ からスタートされているのでしょうか.
とりあえず
「C/C++でポインタとかnewとか抜きでゲーム作ってね」とか言われたら超絶的苦行でしかないと思うので
この機にしっかり把握しておくことをお勧めします.きっととてつもなく楽になるので.

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: マウス操作の選択画面

#11

投稿記事 by ISLe » 10年前

わたしなら、MenuクラスはMenuElementのコントロールを行うクラスにすると思います。

MenuElementを回す処理はイテレータを用いれば、(テンプレートで)配列でもvectorでも同様に扱えるし、newだのポインタだの気にしないで済ませることも可能かと。

パル

Re: マウス操作の選択画面

#12

投稿記事 by パル » 10年前

オフトピック
usao さんが書きました:C → C++ という普通な(?)順で学ばれているのではなくて
いきなり C++ からスタートされているのでしょうか.
C++入門の前にCを勉強してきてね、というサイトが多かったのでCを先にやりました
CをやるならどうせC++もやることになるしC++から始めてもいい、
というところもあったのでコードを書くときはC++も使ってみてます
usao さんが書きました:とりあえず
「C/C++でポインタとかnewとか抜きでゲーム作ってね」とか言われたら超絶的苦行でしかないと思うので
この機にしっかり把握しておくことをお勧めします.きっととてつもなく楽になるので.
はい、ありがとうございます
もちろん絶対使いたくないとは思ってませんでしたが、後回しにして必要になったらでいいかなと思ってました
もう必要になってるので頑張りたいと思います

パル

Re: マウス操作の選択画面

#13

投稿記事 by パル » 10年前

ありがとうございます
ISLe さんが書きました:MenuElementを回す処理はイテレータを用いれば、(テンプレートで)配列でもvectorでも同様に扱えるし、newだのポインタだの気にしないで済ませることも可能かと。
イテレータを知らなかったので少し調べてきましたが、難しいですね
これも使ってみないとわからないでしょうし、
ISLe さんが書きました:わたしなら、MenuクラスはMenuElementのコントロールを行うクラスにすると思います。
を目標にしてやってみます

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: マウス操作の選択画面

#14

投稿記事 by ISLe » 10年前

わたしならこういう実装をするというサンプルを書いてみました。
元のプログラムから一部の動作・仕様を変更しています。

コード:

#include "DxLib.h"
#include <string>
#include <vector>

class Menu;

class MenuItem
{
	friend Menu;
	std::string caption;
	int x, y;
	int state;
public:
	enum {
		STATE_NORMAL,
		STATE_ACTIVE,
		STATE_PRESSED,
	};
	MenuItem(int x, int y, const char *caption)
	 : caption(caption), x(x), y(y) {
	}
	void OnClick() {
		printfDx("[%s]をクリックした\n", caption.c_str());
	}
};

class Menu
{
	std::vector<MenuItem> items;
public:
	void addItem(const MenuItem &item) {
		items.push_back(item);
	}
	void Update() {
		int MouseX, MouseY;
		bool isPress = ((GetMouseInput() & MOUSE_INPUT_LEFT) != 0);
		GetMousePoint(&MouseX, &MouseY);
		for (auto &item : items) {
			auto x = item.x;
			auto y = item.y;
			auto w = GetDrawStringWidth(item.caption.c_str(), strlen(item.caption.c_str()));
			auto h = GetFontSize();
			auto state_old = item.state;
			if (MouseX >= x && MouseX < x + w && MouseY >= y && MouseY < y + h) {
				item.state = isPress ? MenuItem::STATE_PRESSED : MenuItem::STATE_ACTIVE;
			} else {
				item.state = MenuItem::STATE_NORMAL;
			}
			if (state_old == MenuItem::STATE_PRESSED && item.state == MenuItem::STATE_ACTIVE) {
				// 同アイテム上で押して離したとき
				item.OnClick();
			}
		}
	}
	void Render() {
		for (auto &item : items) {
			auto color = GetColor(255, 0, 0);
			switch (item.state) {
			case MenuItem::STATE_NORMAL:  color = GetColor(255, 255, 255); break;
			case MenuItem::STATE_ACTIVE:  color = GetColor(  0, 255,   0); break;
			case MenuItem::STATE_PRESSED: color = GetColor(  0,   0, 255); break;
			}
			DrawFormatString(item.x, item.y, color, item.caption.c_str());
		}
	}
};

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	ChangeWindowMode(TRUE);
	if (DxLib_Init() != 0) return 0;
	SetDrawScreen(DX_SCREEN_BACK);

	Menu menu;
	menu.addItem(MenuItem(100, 100, "ゲームスタート"));
	menu.addItem(MenuItem(100, 150, "おまけ"));
	menu.addItem(MenuItem(100, 200, "ヘルプ"));
	menu.addItem(MenuItem(100, 250, "コンフィグ"));
	menu.addItem(MenuItem(100, 300, "ゲーム終了"));

	while (ProcessMessage() == 0 && ScreenFlip() == 0 && ClearDrawScreen() == 0) {
		if ((GetMouseInput() & MOUSE_INPUT_RIGHT) != 0) clsDx();
 		menu.Update();
		menu.Render();
	}
	
	DxLib_End();
	return 0;
}
ポインタを意識した実装に移行するにしても、まずは設計を見直し、いったんこのくらいまで持っていったほうが良いと思います。

パル

Re: マウス操作の選択画面

#15

投稿記事 by パル » 10年前

ありがとうございます 勉強になります
検索で調べながらなんとか読めたという感じでしたが、
改造して使ってみながら覚えていきたいと思います

閉鎖

“C言語何でも質問掲示板” へ戻る