「メニュー」タグのついた投稿

今回はゲーム内の状態変化の実装の仕方についてメモります。

状態変化とは具体的な例を挙げれば:

 

オープニング → メニュー → ゲーム→ その他色々

 

と言う風にシーンを移動することです。

これに関しては実装方法が幾つかあり、1回で全部を書くのは時間がかかるので、今回はenumとswitch文を使用しての実装方法を書きます。なお、このメモには前に書いたメニュークラスと、同じ記事で説明した入力用のメソッドを使っています。今回乗せる全てのコードは、Game1クラス内に書かれています。

 

まず、状態(シーン)のタイプを表すenumを宣言します。

enum GameStates
{
Menu,
InGame,
Pause,
};

// 現在の状態を表す変数。最初はメニュー画面
GameStates state = GameStates.Menu;

次に、Update()メソッド内にGameStatesのenumを使用してのswitch文を書きます。これで各シーンごとに何を更新するかを決めています(例:stateの値がGameStates.Pauseの場合は、ポーズの部分だけを処理する)。Switch文が長くなると読みにくくなるので、各シーンの処理は個別のメソッドに分けています。

protected override void Update(GameTime gameTime)
{
float delta = (float)gameTime.ElapsedGameTime.TotalSeconds;

// 現在の状態によって呼ぶupdateメソッドを
// 変更する
switch (state)
{
case GameStates.Menu:
UpdateMenu(delta);
break;
case GameStates.InGame:
UpdateInGame(delta);
break;
case GameStates.Pause:
UpdatePause(delta);
break;
}

base.Update(gameTime);
}
 

各メソッドの処理は必要なものに合わせてください。メニュー画面は、前に作ったものをUpdateMenu()メソッドに移すことも出来ます。

private void UpdateMenu(float delta)
{
menu.Update(delta);
if (Input.IsPressed(Keys.Down))
{
// 下へ移動
menu.SelectNext();
}

if (Input.IsPressed(Keys.Up))
{
// 上へ移動
menu.SelectPrevious();
}

// メニューで選択された場合
if (Input.IsPressed(Keys.Enter))
{
int option = menu.SelectedNumber;

if (option == 0)
{
state = GameStates.InGame;
}
else if (option == 1)
{
// ...Optionが選択されたときの処理
}
else if (option == 2)
{
this.Exit();
}
}
}

private void UpdateInGame(float delta)
{
if (Input.IsPressed(Keys.Space))
{
// ポーズ画面へ移動
state = GameStates.Pause;
}
else if (Input.IsPressed(Keys.Enter))
{
// メニュー画面へ移動
state = GameStates.Menu;
}
}

private void UpdatePause(float delta)
{
if (Input.IsPressed(Keys.Space))
{
// ゲーム画面へ戻る
state = GameStates.InGame;
}
}
 

各メソッド内では、入力によってstate変数を変えることで違うシーンに移動しています。

後は、描画処理です。描画もUpdate()メソッドと同じ様に書くことで実装できます。

protected override void Draw(GameTime gameTime)

GraphicsDevice.Clear(Color.CornflowerBlue);

spriteBatch.Begin();

switch (state)
{
case GameStates.Menu:
DrawMenu();
break;
case GameStates.InGame:
DrawInGame();
break;
case GameStates.Pause:
DrawPause();
break;
}

spriteBatch.End();
base.Draw(gameTime);
}
 

Draw()メソッドもUpdate()メソッド同様シーンごとにメソッドを分けています。ちなみに、このサンプルには2種類のSpriteFontを使用しています。

private void DrawMenu()
{
menu.Draw(spriteBatch, font);
spriteBatch.DrawString(largeFont, "Menu Screen", new Vector2(300, 20), Color.Silver);
}

private void DrawInGame()
{
spriteBatch.DrawString(largeFont, "In Game", new Vector2(300, 20), Color.Silver);
spriteBatch.DrawString(font, "Press Space to Pause", new Vector2(300, 300), Color.Silver);
spriteBatch.DrawString(font, "Press Enter to return to Menu", new Vector2(300, 340), Color.Silver);
}

private void DrawPause()
{
spriteBatch.DrawString(font, "Pause Screen", new Vector2(300, 20), Color.Silver);
spriteBatch.DrawString(font, "Press Space to resume", new Vector2(300, 300), Color.Silver);
}
 

以上です。実装後はこんな感じになります:

 

この方法はゲームのシーン以降だけでなく、様々なところで使えます。ですが、ゲームのシーン用となると処理が多くなり、結果的にGame1が巨大化したり、作業分担が面倒になるので、個人的にはオブジェクト指向を使った状態変化を実装することをオススメします。それに関しては、多分次回メモります。

今回は、サイズが変わるメニューの作り方をメモります。

言葉ではわかりにくいかもしれませんので、実装後の動画を先に載せます:

 

まず、前に作ったメニュークラスに文字列の大きさを保存するためのリストを作ります。

class Menu
{
List<float> m_scales; // 大きさのリスト
float m_maxScale = 1.5f; // 大きさの最高値
float m_minScale = 1.0f; // 大きさのデフォルト値
 

そして、メニューの選択肢を足す時にデフォルトの大きさを足してください。

public virtual void AddMenuItem(string name, Vector2 position)
{
m_menuItem.Add(name);
m_positions.Add(position);
m_scales.Add(1.0f); // デフォルトのサイズを足す
}
 

次に、実際に文字列の大きさを変える処理を実装します。この処理は、少しずつサイズを変更するために毎フレーム呼び出される必要があります。

やっている事は、選択されているオプションを大きくし、それ以外のオプションはデフォルト値より大きい場合は小さくします。毎フレーム大きさを変更をするための数値は、ソレっぽく見える様に適当に作ったので、好みで変更してください(speed変数)。

public virtual void Update(float delta)
{
// サイズ変更の速度
float speed = 1.2f * delta;

for (int i = 0; i < m_menuItem.Count; ++i)
{
// もし現在選択されている選択肢なら
if (i == SelectedNumber)
{
 if (m_scales[i] < m_maxScale)
 {
  //選択肢のサイズを大きくする
  m_scales[i] += speed; }
 }
 else if (m_scales[i] > m_minScale && i != m_currentMenuItem)
 {
  //選択されてなく、なおデフォルト値より大きい場合小さくする
  m_scales[i] -= speed;
 }
}
}
 

後は、描画処理です。メニューのオプションを描画する前に、大きさを計算して位置を計算する必要があります。この位置の計算は好みで変更してください。SpriteBatch.DrawString()メソッドは、前と違うオーバーロードを使ってサイズ指定をしています。

public virtual void Draw(SpriteBatch sp, SpriteFont font)
{
// メニューのオプションを描画する
for (int i = 0; i < m_menuItem.Count; i++)
{
// 描画位置を計算する
Vector2 pos = m_positions[i];
pos.Y -= (float)(22 * m_scales[i] / 2);
pos.X -= (float)(22 * m_scales[i] / 2);

// もし現在カーソルがあっている場合
if (i == SelectedNumber)
{
sp.DrawString(font, m_menuItem[i], pos, m_selectedColour, 0, Vector2.Zero, m_scales[i], SpriteEffects.None, 0);
}
else
{
// 他と同じ色で描画する
sp.DrawString(font, m_menuItem[i], pos, m_unSelectedColour, 0, Vector2.Zero, m_scales[i], SpriteEffects.None, 0);
}
}
// ...テキストの描画
 

後は実際にメニュークラスのUpdateメソッドを呼ぶだけです。

menu.Update((float)gameTime.ElapsedGameTime.TotalSeconds);
 

今回はここまでです。何か質問があればコメントかメールをお送りください。

今回は、前回作ったメニューにアイコンを表示させます。このメモはかなり短めで、シンプルなことだけをします。

まず、適当なアイコンを作り、プロジェクトに追加します。その後に、テクスチャアを読み込みます。

Texture2D icon;

protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);

font = Content.Load<SpriteFont>("font");
icon = Content.Load<Texture2D>("cursor");
}
 

今度はメニュークラスの変更です。メニューのDrawメソッドの引数に、描画するテクスチャアを追加します。その後に、アイコンの描画処理を追加します。

public virtual void Draw(SpriteBatch sp, SpriteFont font, Texture2D icon)
{
// メニューが空じゃない場合
if (m_menuItem.Count != 0)
{
// アイコンの描画
Vector2 pos = new Vector2(m_positions[m_currentMenuItem].X - icon.Width - 15, m_positions[m_currentMenuItem].Y);
sp.Draw(icon, pos, Color.White);
}
// ...メニューの描画処理
}
 

アイコンを描画位置は、現在選択されているオプションの左側に描画しています。

実装後はこんな感じになります:

 

今回はココまでです。

今回はあまり中身がなかったなぁ・・・orz

今回はメニューの作り方をメモります。今回は時間の都合から、2,3回に分けて書きます。

最初に、プロジェクトにSpritefontを一つ追加してください。その後は、メニューで使う変数を宣言します。今回使うのは、選択する為の文字列、タイトルとかに使うただ単に描画するだけの文字列、それらの文字列を描画する位置、現在選ばれている文字を指すint値、現在選ばれているオプションの文字列の色、選ばれてないオプションの色です。

class Menu
{
List<string> m_menuItem; //メニューのオプション
List<string> m_menuText; //ただの文字列
List<Vector2> m_positions; // オプションの位置
List<Vector2> m_textPosition; // 文字列の位置
Color m_selectedColour; // 選択されているオプションの色
Color m_unSelectedColour; // 選択されていないオプションの色
int m_currentMenuItem; // 現在選択されているオプション
 

宣言後は、コンストラクタで初期化します:


public Menu(Color selectedColour, Color unSelectedColour)
{
m_menuItem = new List<string>();
m_menuText = new List<string>();
m_positions = new List<Vector2>();
m_textPosition = new List<Vector2>();
m_selectedColour = selectedColour;
m_unSelectedColour = unSelectedColour;
m_currentMenuItem = 0;
}
 

次に、メニュー用のオプションや、文字列を足すメソッドを作ります。これは単に表示する文字列と、描画する位置を渡すだけです。


public virtual void AddMenuItem(string name, Vector2 position)
{
m_menuItem.Add(name);
m_positions.Add(position);
}

public void AddMenuText(string text, Vector2 position)
{
m_menuText.Add(text);
m_textPosition.Add(position);
}
 

作り終わったら、実際にメニュー用のオプションを足して見ます。


Menu menu;

public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";

menu = new Menu(Color.Gold, Color.White);

// メニューにオプションを足す
menu.AddMenuItem("Start", new Vector2(400.0f, 300.0f));
menu.AddMenuItem("Option", new Vector2(400.0f, 340.0f));
menu.AddMenuItem("Exit", new Vector2(400.0f, 380.0f));
}
 

今度は、メニューを描画処理を作ります。基本的にすることは、リスト内に保存されている、メニューのオプションを全て描画するだけです。その時、現在選ばれている(例えると、カーソルが乗っている文字)オプションだけ違う色にします。そうする事で、メニューの操作中に何処を選んでいるかわかり易くなります。


public virtual void Draw(SpriteBatch sp, SpriteFont font)
{
// メニューのオプションを描画する
for (int i = 0; i < m_menuItem.Count; i++)
{
// もし現在カーソルがあっている場合
if (i == m_currentMenuItem)
{
// 違う色で描画する
sp.DrawString(font, m_menuItem[i], m_positions[i], m_selectedColour);
}
else
{
// 他と同じ色で描画する
sp.DrawString(font, m_menuItem[i], m_positions[i], m_unSelectedColour);
}
}
 

ただの文字列の描画も、色を変える以外は同じようにします。描画が出来れば後はメニュー内で移動するだけです。そのためには、m_currentMenuItemの数を上げるか、下げる必要があります。移動した際には、メニューの上限を過ぎた場合最初に戻るようにします。


/// <summary>
/// 次のオプションへ移動する
/// </summary>
public virtual void SelectNext()
{
if (m_currentMenuItem < m_menuItem.Count - 1)
{
m_currentMenuItem++;
}
else
{
m_currentMenuItem = 0;
}
}

/// <summary>
/// 前のオプションへ移動する
/// </summary>
public virtual void SelectPrevious()
{
if (m_currentMenuItem > 0)
{
m_currentMenuItem--;
}
else
{
m_currentMenuItem = m_menuItem.Count - 1;
}
}
 

実装が終われば、後は実際に動かしてみます。


if (Input.IsPressed(Keys.Down))
{
// 下へ移動
menu.SelectNext();
}

if (Input.IsPressed(Keys.Up))
{
// 上へ移動
menu.SelectPrevious();
}
 

上のコードでは、Input.IsPressed()という、メソッドを使っていますが、これはキーが現在のフレームに押された場合だけ、trueを返します。そうしないと、メニューが一押しでかなり移動してしまうので、これを使っています。これ自体はメニューと関係ないのと、実装するのが簡単なので中身は記載しません。

メニューとして扱うには、メニューで何かが選択された時にどのオプションが選択されたかを知る必要が有るので、m_currentMenuItemへのアクセサを作ります。


public int SelectedNumber
{
get { return m_currentMenuItem; }
}
 

後は実際に選択された時にどうするかを実装するだけです。


// メニューで選択された場合
if (Input.IsPressed(Keys.Enter))
{
int option = menu.SelectedNumber;

if (option == 0)
{
// ...スタートが選択されたときの処理
}
else if (option == 1)
{
// ...Optionが選択されたときの処理
}
else if (option == 2)
{
// ...exitが選択されたときの処理
}
}
 

実装後はこんな感じになります(メニューを描画するのを忘れずに):

 

今回はココまでです。今回は凄く基本的な機能しか実装してないので、次回のメモを読んだり、実装方法こみで色々と試してみてください。

例を挙げると:

–       現在選択されているオプションの隣にアイコンの表示(次回書く予定)

–       選んでいる選択肢が大きくなり、移動すると元に戻る(次回かその次に書く予定)

–       メニューのオプションを一つのクラスとして扱ってみる

–       メニュークラス内で、移動等の処理の実装してみる

–       メニューの選択後の処理のif文に、intの代わりにenumを使う

–       CurrentNumberのアクセサの代わりに、string型や、enum型を返すようにする

今回紹介した実装方法や、上で例に挙げた方法は好みによる所が多いので、自分が使いやすいようにいじってみてください。