Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
1
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

Organization

どれだけ数字が好きか体で示してもらおうか

数字が好きか

皆さんは数字は好きですか?
僕は幼い時、自由帳へひたすら数字やら記号やらを書きなぐる変態少年だったらしく、
ああ、この子絶対理系だわ・・・
と親が察するぐらいには数字・数学が好きですウッヒョォォオア

ところが、先日東京ビックサイトで催されたMaker Fair Tokyoのとある作品にインスパイアを受け、
いや、好きとかは口で言うのは簡単なんだよな。好きはちゃんと体で表現しないと。
と思ったんです。

何を言ってるか分からない??
僕も分かりません。

ということで今回は、全身で数字を表現するダンスゲームNumDanceを作ってみました。
アプリは公開しているので皆さんも全身全霊全身全力でやってみてくださいね。
Maker Fair Tokyoのとある作品については最後に触れようと思います。

Appはこちらから
コード(Gist)はこちらから

完成デモ

お題に書かれている数字に7秒間なりきるのが基本ルールです。
1から始まるので、まずは1を体で全力で表現します
WebCameraからその姿が1かどうかが厳密に判定されるので1判定の状態を7秒間維持します

7秒間維持をクリアしたら次の数字に進みます。
スタート時点で60秒の制限時間があり、各数字をクリアごとに60秒プラスされます
1から9まで続き、9をクリアして全ステージクリアです。

ちなみに「2」がすでにむちゃくちゃむずいです
全クリしたら教えてください。

実装こまごま

狂気の教師データ

今回はTeachable Machineを使って数字判定を実装しました。
最初は純粋に「1と判定される学習データだから、パワポかなんかで1って書いてスクショ取ればいいや」とか思っていました。
こんな感じです。

しかし実装が進むにつれ、「体で数字で表現するゲームなんだから、体で表現した学習データがないとおかしくないか」ということに気づき始めます。
ということで集めた学習データがこちら。

楽しそうですね。

これらをTeachable Machineに食わせて数字判定学習器を作りました。
学習データの枚数がむちゃくちゃ少ないので、判定ガバガバだと思います
でもそこをどうにかするのが数字愛なのではないか、と考えております(強引)

判定実行してブラウザ上に表示

こちらの記事でも書きましたが、Teachable Machineで作ったモデルをはクラウドへデプロイできます。
ブラウザ上で実行するコードもTeachable Machine側で用意してくれるので非常にベンリィィイイです。
判定結果はlabelという変数に入れられ、画面に表示されます。

// Classifier Variable
  let classifier;
  // Model URL
  let imageModelURL = 'URLをここに';

  // Video
  let video;
  let flippedVideo;
  // To store the classification
  let label = "";
  let label_before = "";
 
  // Load the model first
  function preload() {
    classifier = ml5.imageClassifier(imageModelURL + 'model.json');
  }

  //初期化処理
  function setup() {
    createCanvas(640, 640);

    // Create the video
    video = createCapture(VIDEO);
    video.size(640, 480);
    video.hide();

    flippedVideo = ml5.flipImage(video);
    // Start classifying
    classifyVideo();
  }
  
  // Get a prediction for the current video frame
  function classifyVideo() {
    flippedVideo = ml5.flipImage(video)
    classifier.classify(flippedVideo, gotResult);
    flippedVideo.remove();

  }

  // When we get a result
  function gotResult(error, results) {
    // If there is an error
    if (error) {
      console.error(error);
      return;
    }
    label_before = label;
    // The results are in an array ordered by confidence.
    // console.log(results[0]);
    label = results[0].label;

    // Classifiy again!
    classifyVideo();
  }

ゲームロジック

あとはここに細かいゲームロジックを追加していくだけです。
p5jsのお作法についてはこちらの記事こちらの記事で紹介していますので、こちらもご覧になってくださいね。
modeでゲーム中かゲームクリアかゲームオーバーかを定義し、それに応じて処理を変更しています。


//常にここが実行される
  function draw() {
    //ゲームの状態によって処理を変える
    switch (mode){
      case "game":
        play_game();
        break;
      case "game_over":
        game_over();
        break;
      case "game_clear":
        game_clear();
        break;
    }
  }

ゲーム中は基本的には画像判定とタイマーのカウントダウンぐらいしか処理していません。
タイマーの実装はこちらのサイトを参考にしています。


//ゲーム中の処理
  function play_game(){
    background(0);
    // Draw the video
    image(flippedVideo, 0, 0);

    // Draw the label
    fill(255);

    //画面右側の文字群
    textSize(50);
    textAlign(CENTER);
    text(label, 450, height - 4);
    textSize(25);
    textAlign(CENTER);
    text("あと"+sec+"秒耐えろ!", 450, 560);

    //画面左側の文字群
    textSize(50);
    textAlign(CENTER);
    text("お題:"+odai_num, 190, height - 4);
    textSize(25);
    textAlign(CENTER);
    text("残り時間あと"+time_limit+"", 190, 560);

    //経過時間を測定
    const now = millis();
    elapsedTime = now - startTime;

    //1000ms経過したらtime_limitを1下げる
    if(elapsedTime >= oneSec){
      time_limit--;
      startTime = millis();

      //time_limitが0になったらmodeを変える
      if (time_limit <= 0){
        mode = "game_over";
      }
    }  
  }

ゲームオーバーとゲームクリアはそれぞれ画面を上塗りするだけの処理です。

//ゲームクリアの処理
  function game_clear(){
    background(0);
    text("数字の気持ちが完璧に分かりました", 250, 300);
    textSize(25);
    text("Press r button to retry !", 220, 350);
    if (key == "r") {
        key = 0;
        setup();
    }
  }

  //ゲームオーバーの処理
  function game_over(){
    background(0)
    text(odai_num-1+"まで気持ちが分かりました", 250, 300);
    textSize(25);
    text("Press r button to retry !", 220, 350);
    if (key == "r") {
      key = 0;
      setup();
    }
  }

お題と同じ数字である時間だけカウントダウン

これが地味に難しかったので備忘録的にメモします。
「別の数字になったらリセットする」というところをどう実装しようか少し悩みました。
結局直前の判定結果がlabel_beforeに入れ、今の結果labelと一致するかどうかで条件分岐しました。

function gotResult(error, results) {
    // If there is an error
    if (error) {
      console.error(error);
      return;
    }
    label_before = label;
    // The results are in an array ordered by confidence.
    // console.log(results[0]);
    label = results[0].label;

    if (label_before==label && label == odai_num){
      const now = millis();
      elapsed_sec = now - start_sec;
      if (elapsed_sec >= 1000){
        sec--;
        start_sec = millis();
        if(sec <= 0){
          next_stage();
        }
      }
    }else{
      sec = sec_max;
    }
    // Classifiy again!
    classifyVideo();
  }

余談

この記事を作るきっかけになった作品が、Maker Fair Tokyoで発表された、AIプログラミング「ドレミダンス」です。
こちらのYoutubeから3:18:45あたりでご覧になれます。
全身の動きを使って音楽を奏でるという発想に感銘を受けました。

ゲームを作るときってやっぱりキーボードとかコントローラーとか、手先で操作できるものを想像してしまうんですよね。
画像認識技術を使えば体の形に意味を持たせられるという点がなるほどなあと思った部分です。
可能性が広がりますね。

さいごに

画像認識周りは技術が進歩して実装が簡単になりましたし、本当に可能性にあふれているなあという印象。
そしてp5js便利すぎる。
引き続き色んなゲーム作っていきたいなあと思います。

最後までご覧いただきありがとうございました!
LGTMいただけると励みになります!

LGTMのほどよろしくお願いいたします!!!!!!!!!!!!!!!!!!!!!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
1
Help us understand the problem. What are the problem?