プログラミング素人のはてなブログ

プログラミングも電気回路も専門外の技術屋の末端が勉強したことや作品をアウトプットするブログ。コードに間違いなど見つけられたら、気軽にコメントください。 C#、Python3、ラズパイなど。

M5Stackで作った迷路ゲームのアニメーション制御

M5Stackのアニメーション制御を解説します

このプロジェクトでもっとも重要な要素の一つである、M5StackのLCD上でアニメーションを制御(表示)する方法を解説します。
先の記事の続き的な位置づけです。
s51517765.hatenadiary.jp
ソースコードは以下です。
github.com

画素オブジェクトを点滅させる

最も基本的にはカーソル位置を指定M5.Lcd.setCursor(60, 105);し、文字列を表示M5.Lcd.print(">START<");します。
これをフラグprintを見て、表示と消去m5.Lcd.fillScreen(BLACK);を交互に行います。
これで点滅が表現できます。(動画はYoutubeで見てください。Linkはこの記事の末にあります。)

  while (true)
  {
    M5.Lcd.setCursor(60, 105);
    if (print)
      M5.Lcd.print(">START<");
    else
      m5.Lcd.fillScreen(BLACK);
    print = !print;
  }

画素オブジェクトを追加する

次はCircleを円周状に追加していくことで、時計のような効果になります。
円周なので三角関数を使うと完結に表現できます。

    for (int i = 0; i < step; i++)
    {
      x = posi_x + offset + 80 * sin(float(i * PI * 2 / step));
      y = posi_y + offset - 80 * cos(float(i * PI * 2 / step));
      M5.Lcd.fillCircle(x, y, 5, GREEN);
      delay(1000 / step);
    }

中央の数字を更新するときはM5.Lcd.fillScreen(BLACK);で全体を塗りつぶします。

迷路を描画する

迷路は一定のサイズの四角のタイルを迷路配列maze[j][i]に応じて描画します。
ここではif ((i + j) % 2 == 0)のようにすることで色を交互にして市松模様にしています。
M5.Lcd.fillRect(x-pos,y-pos,x-size,y-size,color)で四角を描画できますが、引数は順番にポジションx、ポジションy、サイズx、サイズy、色となっています。

  M5.Lcd.setTextSize(5);
  for (int j = 0; j < MEIRO_HEIGHT; j++)
  {
    for (int i = 0; i < MEIRO_WIDTH; i++)
    {
      if (maze[j][i] == 1)
      {
        if ((i + j) % 2 == 0)
        {
          M5.Lcd.fillRect(i * BLOCKSIZE, j * BLOCKSIZE, BLOCKSIZE, BLOCKSIZE, ORANGE); // x-pos,y-pos,x-size,y-size,color //orange 0xFD20
        }
        else
        {
          M5.Lcd.fillRect(i * BLOCKSIZE, j * BLOCKSIZE, BLOCKSIZE, BLOCKSIZE, YELLOW);
        }
      }
    }
  }

ボールの移動


ボールの移動も画像の描画で表現します。
このため、新しいボールの位置にボールを描画することと、その前(現在)の位置のボールの描画を消去する、ということが必要になります。

  //ボールの軌跡を消す
  printBackground(x, y);
  //ボールの位置
  M5.Lcd.fillCircle((int)x, (int)y, BALLSIZE, GREEN); // x,y,r,color

ここでprintBackground(x, y);は現在の位置の通路の再描画と、パラメータによりますが、周囲の壁の再描画を行います。
最初は、画面すべての迷路の再描画を行っていましたが、このようにすると時間がかかりすぎてしまうので、ボールの周囲のみにすることでゲームに違和感のない速度を実現しています。
また、すべてを一度「Black」に塗りつぶすと画面全体が点滅したような状態になってしまいます。
ボール自体は若干点滅が見えると思いますが、この辺はゲームのレスポンスと見た目の違和感(点滅)のバランスで再描画の速度(FPSといわれるやつですね)を調整しました。

void printBackground(int x, int y)
{
  //迷路を描画
  M5.Lcd.setTextSize(5);
  int h_start = max((int)(y / BLOCKSIZE - 1), 0);
  int w_start = max((int)(x / BLOCKSIZE - 1), 0);

  for (int j = h_start; j < h_start + 3; j++)
  {
    for (int i = w_start; i < w_start + 3; i++)
    {
      if (maze[j][i] == 1)
      {
        if ((i + j) % 2 == 0)
        {
          M5.Lcd.fillRect(i * BLOCKSIZE, j * BLOCKSIZE, BLOCKSIZE, BLOCKSIZE, ORANGE); // x-pos,y-pos,x-size,y-size,color //orange 0xFD20
        }
        else
        {
          M5.Lcd.fillRect(i * BLOCKSIZE, j * BLOCKSIZE, BLOCKSIZE, BLOCKSIZE, YELLOW);
        }
      }
      else
      {
        M5.Lcd.fillRect(i * BLOCKSIZE, j * BLOCKSIZE, BLOCKSIZE, BLOCKSIZE, BLACK);
      }
    }
  }
}

経過時間の表示

ボールの描画の応用ですが、経過時間の表示位置のBackgroundを迷路で塗りつぶしてから新しい経過時間を表示します。

void dispTime()
{
  M5.Lcd.setTextSize(5);
  int j = 0;
  for (int i = MEIRO_WIDTH - 4; i < MEIRO_WIDTH; i++)
  {
    if (maze[j][i] == 1)
    {
      if ((i + j) % 2 == 0)
      {
        M5.Lcd.fillRect(i * BLOCKSIZE, j * BLOCKSIZE, BLOCKSIZE, BLOCKSIZE, ORANGE); // x-pos,y-pos,x-size,y-size,color //orange 0xFD20
      }
      else
      {
        M5.Lcd.fillRect(i * BLOCKSIZE, j * BLOCKSIZE, BLOCKSIZE, BLOCKSIZE, YELLOW);
      }
    }
  }
}

  m5.Lcd.setCursor(170, 2);
  M5.Lcd.print("Time: ");
  m5.Lcd.setCursor(230, 2);
  String st = String(t / 1000) + ":" + String(t % 1000);
  M5.Lcd.print(st);

まとめ

M5Stackで作った迷路ゲームのアニメーション制御について解説しました。
アニメーションはBackgroundをオブジェクトの上に描画することで消えたように見せたり、現在の状態と次の状態が少し変わることで移動するように見せたりして実現します。
簡単には、画面全体を上書きしても実現できますが、再描画するオブジェクトが多いと画面更新の速度が低下するので、必要最小限にする必要があります。
また、M5stackではBackgroundを描画してから新たなオブジェクトを描画しないと、文字等は前のものが残ってしまいます。
youtu.be