【MoveNet/第6回】

MoveNet

第6回です。最終回となります。

今までの機能を合体しよう

第3回の後半から第5回にかけて、旗揚げゲームに必要なパーツを制作してきました。

MoveNetで旗を持たせたり、ランダムに旗の出題をするシステムや旗の状態を管理するシステムは作りましたが、上の図にある3つの条件分岐をまだ実装できていません。

また、出題回数や持ち点の管理に関してもまだ実装できていません。

旗の状態が正しいかを判定する

まずは旗と出題を比較します。下記のコードは実行しなくてもいいです。

let miss = 0; // ミスの数
let right, left; // 手の判定結果
let poseData; //姿勢データ

// 手の判定を行う
function judgeHands(flagState) {
    // 右手の判定
    const rightHandY = poseData[0].keypoints[10].y <= 540 ? true : false;
    const rightHandCondition = flagState[2];
    right = (rightHandY && rightHandCondition) || (!rightHandY && !rightHandCondition)
        ? 'success'
        : 'fail';
    console.log(`右手: ${right}`);

    // 左手の判定
    const leftHandY = poseData[0].keypoints[9].y <= 540 ? true : false;
    const leftHandCondition = flagState[3];
    left = (leftHandY && leftHandCondition) || (!leftHandY && !leftHandCondition)
        ? 'success'
        : 'fail';
    console.log(`左手: ${left}`);

    // ミスの集計
    if (right === 'fail' || left === 'fail') {
        console.log('減点!');
        miss++;
    }
}

右手の判定や左手の判定の下にある変数はBoolean型が格納されています。それぞれの変数を比較して真なら’success’、偽なら’fail’をright、leftの変数に格納します。right、leftの変数のどちらかに’fail’が格納されていた場合にミスが加点されます。

コードを見ていると、不思議な記述に気づいたかもしれません。文字を?と:でつないでいる箇所があります。

const rightHandY = poseData[0].keypoints[10].y <= 540 ? true : false;

これはif文の省略形です。本来であれば下記のコードが必要になるところを1行で済ませることができます。便利!

const rightHandY;
if (poseData[0].keypoints[10].y <= 540) {
    rightHandY = true;
} else {
    rightHandY = false;
}

手の判定は下記のような記述ですが、どういった処理をしているかイメージできるでしょうか。

(rightHandY && rightHandCondition) || (!rightHandY && !rightHandCondition)

どちらの変数もtrue、あるいはfalseの場合にはtureを返す、というものです。論理演算子をフルに活用したコードになっています。基本論理回路で言うとEX-NORです。さらに短く記述すると下記です。

!(rightHandY ^ rightHandCondition)
rightHandYrightHandCondition結果
falsefalsetrue
falsetruefalse
truefalsefalse
truetruetrue
//例)どちらもfalseの場合
(rightHandY && rightHandCondition) || (!rightHandY && !rightHandCondition)
(false && false) || (!false && !false)
(false && false) || (true && true) //NOT演算子でfalseがtrueになる
false || true //AND演算子でfalse同士はfalseに、true同士はtrueになる
true //OR演算子でtrueになる
//結果はtrueになる。

ゲームの継続判定をする

あとは持ち点と出題回数を管理し、ゲーム全体のコードを集約するのみになりました。

先ほどのコードに点数管理とゲームの開始トリガーを追記しました。

// ゲームの状態管理
let count = 0; // ゲームの回数
let miss = 0; // ミスの数
let right, left; // 手の判定結果
let poseData; //姿勢データ

// 手の判定を行う
function judgeHands(flagState) {
    // 右手の判定
    const rightHandY = poseData[0].keypoints[10].y <= 540 ? true : false;
    const rightHandCondition = flagState[2];
    right = (rightHandY && rightHandCondition) || (!rightHandY && !rightHandCondition)
        ? 'success'
        : 'fail';
    console.log(`右手: ${right}`);

    // 左手の判定
    const leftHandY = poseData[0].keypoints[9].y <= 540 ? true : false;
    const leftHandCondition = flagState[3];
    left = (leftHandY && leftHandCondition) || (!leftHandY && !leftHandCondition)
        ? 'success'
        : 'fail';
    console.log(`左手: ${left}`);

    // ミスの集計
    if (right === 'fail' || left === 'fail') {
        console.log('減点!');
        miss++;
    }

    const missDisplay = document.getElementById('miss');
    missDisplay.textContent = `ミスの数: ${miss}`;

    // ゲームの継続判定
    const order = document.getElementById('order');
    if (count < 10 && miss < 3) {
        startGame(); // 次の回へ
    } else if (miss >= 3) {
        order.textContent = 'ゲームオーバー!';
    } else {
        order.textContent = 'ゲームクリア!';
    }
}

// ゲームの開始
function startGame() {
    console.log(`${count + 1}回目`);
    count++;

    const flagState = isFlagRised();
    const order = document.getElementById('order');
    order.textContent = flagState[0] + flagState[1];

    // 3秒待機後に手の判定を行う
    setTimeout(judgeHands, 3000, flagState);
}

ここで特筆すべきところはsetTimeout()です。◯秒後に処理を実行することができるメソッドです。注意点として、関数名()ではなく、関数名のみ記述します

setTimeout(関数名, 秒数(ミリ秒), 関数の引数);
//正:setTimeout(judgeHands, 3000, flagState);
//誤:setTimeout(judgeHands(), 3000, flagState);

あとは見慣れたものが多くなっているのではないのでしょうか。もちろん、まだまだ知らないものも多いと思うので適宜調べるようにしましょう。

仕上げ

これまでのコードをすべてまとめてzipファイルにしたので、まずはダウンロードしましょう。

任意の場所で展開してLocal Serverで実行してみましょう。

こんな感じのテーブルが表示されると共に、いきなり問題が始まると思います。必要最低限の実装しかないためです。

今回の課題はこのゲームに味付けをするものにします。

課題

今回の課題はただひとつです。

  • このゲームを自由に改変せよ

さすがに手放しが過ぎるので、いくつかアイデアを提供します。

  1. ゲームのスタート画面を作る
  2. ゲームの回数やミス数を変える
  3. 回数が増えると出題までの時間が短くなる
  4. canvasに回数やミス数を表示する
  5. 出題を音声で読み上げる
  6. 足挙げも追加する

などなど、たくさんできることはあると思うので、スクラップ&ビルドしまくってください。

まとめ

お疲れ様でした。初めてJavaScripとMoveNetに触れた人にとってはなかなか難しい課題も多かったと思いますが、ここまで読んでくれた方はかなり力がついていると思います。

このあとは自分でゲームを作ってみてください。手を挙げたらキャラクターがジャンプする横スクロールゲームからスクワットの回数をカウントするソフトなど、なんでもできると思います。

困ったときは基本的にここを見れば解決できるので、ぜひ参考にしてください。それでは。

JavaScrip

開発者向けのウェブ技術 | MDN
オープンなウェブは、開発者に素晴らしい機会をもたらします。これらの技術を最大限に活用するには、それらの使い方を知る必要があります。以下に、ウェブ技術のドキュメントへのリンクがあります。

MoveNet

tfjs-models/pose-detection at master · tensorflow/tfjs-models
Pretrained models for TensorFlow.js. Contribute to tensorflow/tfjs-models development by creating an account on GitHub.
MoveNet: 超高速で高性能な姿勢検出モデル  |  TensorFlow Hub