【M5Stack FACES】テトリスのサンプルゲームをGameBoy カバーに対応させる

スポンサーリンク

みなさんこんにちは〜。
前回のM5Stack投稿では、GameBoyカバーのボタン入力の取得方法を見つけました。
今回はサンプルゲームのテトリスをGameBoy風に遊べる様にしちゃおうと思います。

スポンサーリンク

キー入力はどこで処理してる?

ボタン入力の対応を行うためには、まず、現状のキー入力判定箇所を見つけ出す必要がありますね。
では、実際にどうやってその場所を特定すれば良いのでしょうか?

……… はい、地道にそれっぽいところを探しました。

テトリスのサンプルコードは、200行も無かったので、上から下までくるくるくる〜っとスクロールしてみたところ。
107-112行目でそれっぽいコードがありました。

bool KeyPadLoop(){
  if(M5.BtnA.wasPressed()){ClearKeys();but_LEFT =true;return true;}
  if(M5.BtnB.wasPressed()){ClearKeys();but_RIGHT=true;return true;}
  if(M5.BtnC.wasPressed()){ClearKeys();but_A    =true;return true;}
  return false;
}

まぁ、十中八九ここでA,B,Cの3ボタンを処理していると思われます。
念のために".wasPressed"でコード内を検索してもこの部分しかヒットしなかったので、ボタン処理はここだけで行っているのでしょう。

該当コードを読んでみる

処理コードの場所に当たりをつけたので、次はそのコードで何をしているのかを考えてみます。
まずは1つ目のif文ですが…どう考えてもAボタンが押された時の処理ですね。
1行に連続して記述されているので、それを改行して分かりやすくすると次のようになります。

if (M5.BtnA.wasPressed()) { // Aボタンが押されていたら、
   ClearKeys();        // キー状態をクリアして
   but_LEFT =true;  // but_LEFT 変数に true を入れて
   return true;          // trueを返して関数を終了
}

コメントに書いた通りの処理です。
同様に、2つ目のif文がBボタンの処理で、but_RIGHT を true に
3つ目のif文がCボタンの処理で、but_A を true にしています。
そして、AもBもCも押されてなければ、falseを返して関数が終了となります。

GameBoyカバー入力に対応させる修正

コードの処理内容もわかった所で、実際に処理を追加してみましょう。

処理の追加内容を考える

では、実際にどのような処理を追加すれば良いかというと…
・ ←ボタンが押されていたら、本体のAボタンが押された時と同じ処理をする。(1つ目のif文と同様の処理)
・ →ボタンが押されていたら、本体のBボタンが押された時と同じ処理をする。(2つ目のif文と同様の処理)
・ Aボタンが押されていたら、本体のCボタンが押された時と同じ処理をする。(3つ目のif文と同様の処理)
の3点となります。
今回は、元のボタン操作を有効にしつつ、GameBoyカバー側の操作も有効にするような実装をして行きたいと思います。

実際に処理を追加する

GameBoyカバー側のボタンを有効にするのに必要な実装は前回の記事で探し出していますので、
それを参考に修正後のコードを考えると…

void setup(void) {
  M5.begin();                   // M5STACK INITIALIZE
  Wire.begin(); // I2Cを利用できるようにする
  ......
}
......
bool KeyPadLoop(){
  if(M5.BtnA.wasPressed()){ClearKeys();but_LEFT =true;return true;}
  if(M5.BtnB.wasPressed()){ClearKeys();but_RIGHT=true;return true;}
  if(M5.BtnC.wasPressed()){ClearKeys();but_A    =true;return true;}

  // GameBoyカバーボタンの対応
  Wire.requestFrom(0X08, 1); // 0x08アドレスから1バイト読み出す要求
  if(0 != Wire.available()) { // 読み出すデータがあれば処理をする
    uint8_t key_val = Wire.read(); // 1バイトデータを取得
    // ↑:FE ↓:FD ←:FB →:F7 A:EF B:DF SELECT:BF START:7F
    if(0xFB == key_val){ClearKeys();but_LEFT =true;return true;} // ← 押下処理
    if(0xF7 == key_val){ClearKeys();but_RIGHT=true;return true;} // → 押下処理
    if(0xEF == key_val){ClearKeys();but_A    =true;return true;} // A 押下処理
  }
  
  return false;
}

となりました。赤字の部分が追加したコードです。
コード内でコメントも書いているので、そんなに難しい所は無いと思いますが、どうでしょう?

Let’s プレー

それでは、修正したコードを実際に動かしてみましょう。

  レッツプレー!!!

…… 如何でしょう? 動かしてみて気付きました?
実は、このコードだと、ボタンを押し続けていると反応し続けてしまいます。
かろうじて←→ボタンは良いとしても、Aボタンは高速回転になってしまい、操作性最悪です。
あと、ついでに、2ボタン以上が押された場合は反応しなくなります。
これは流石に放置できませんね。
2つ目はまぁ、今は目を瞑るとして、高速回転は何とかしないと。

Aボタンを押し続けると高速回転してしまう…

高速回転を修正する

という訳で、高速回転を修正しちゃいましょう。
高速回転の理由は、『ループでボタンの状態を調べる度に、回転処理をしてしまっている』のが原因です。
なので、『前回と同じボタンが押されている場合は、処理をしない』ように修正します。

...
boolean but_A = false, but_LEFT = false, but_RIGHT = false;
uint8_t old_key_val = 0xff; // 1つ前のキー入力状態を保持
int game_speed = 25; // 25msec

...

bool KeyPadLoop(){
	...

	if(0 != Wire.available()) {
		uint8_t key_val = Wire.read();
		if (old_key_val != key_val ) {    // 1つ前で取得したキー情報と同じでなければ処理をする
			old_key_val = key_val;  // 取得したキー情報を保持
			// ↑:FE ↓:FD ←:FB →:F7 A:EF B:DF SELECT:BF START:7F
			if(0xFB == key_val){ClearKeys();but_LEFT =true;return true;}
			if(0xF7 == key_val){ClearKeys();but_RIGHT=true;return true;}
			if(0xEF == key_val){ClearKeys();but_A    =true;return true;}
		}
	}
	return false;
}
...

赤字部分が今回の修正箇所です。

やっている事は単純です。
始めに1つ前のキー入力情報を保持する変数 old_key_val を宣言しておき
(同時に何も押されていない状態の0xffで初期化もしています)、
キー入力の判定部分で、前回と異なる入力状態なら処理をするようにしています。
前回と異なる場合は、old_key_val の値も更新するのを忘れないようにしましょう。
忘れてしまったら、このif文は何も意味を為さなくなってしまいますからね。

修正が終わったら、実行をしてみて下さい。
今度はボタンを押した時に1回だけ反応(移動や回転)をするようになっていると思います。(めでたしめでたし)

さらなる改善点

一応、GameBoyカバーのキー入力に対応できましたので、今回はこれで終わりとなりますが、
操作性を高めるには、もう少し工夫をしたいところです。
私が遊んでみた感じでは…
・ ボタンを押し続けた時は、一定間隔(?)で移動や回転を続けて欲しい
・ 移動ボタンと回転ボタンの同時押しに対応して欲しい
・ ↓ボタンで高速落下させたい
・ (おまけ)Bボタンで逆回転させてみたい
と言ったところを改善させたいと思いました。
みなさんはどうでしょう?
せっかく色々といじれる環境が手元にあるのですから、思った事を試してどんどん改造してしまいましょう!

おわりに。次回は…

今回はGameBoyのキー入力でテトリスを遊べるようにするところまで出来ました。
これで完結させてしまっても良いのですが…
せっかくなのでもう少し続けて、前項であげた改善点についても実際にやっていこうかと思います。

という訳で、次回は改善点にあげた項目のどれかを対応させますよ。
どの項目をやるかは…乞うご期待☆ です。(まだ何も考えてないので)

あと、今回のコードはGitHubに公開していますので、自由に使って下さいね。
GitHub:https://github.com/sweetsblast/m5stackfaces_tetris
Commit:GameBoyカバーのボタンに対応
Commit:ボタン押下時に一度だけ移動や回転の処理を行うように修正

スポンサーリンク