#数字が好きか
皆さんは数字は好きですか?
僕は幼い時、自由帳へひたすら数字やら記号やらを書きなぐる変態少年だったらしく、
「ああ、この子絶対理系だわ・・・」
と親が察するぐらいには数字・数学が好きですウッヒョォォオア
ところが、先日東京ビックサイトで催されたMaker Fair Tokyoのとある作品にインスパイアを受け、
「いや、好きとかは口で言うのは簡単なんだよな。好きはちゃんと体で表現しないと。」
と思ったんです。
何を言ってるか分からない??
僕も分かりません。
ということで今回は、__全身で数字を表現するダンスゲームNumDance__を作ってみました。
アプリは公開しているので皆さんも全身全霊全身全力でやってみてくださいね。
Maker Fair Tokyoのとある作品については最後に触れようと思います。
#完成デモ
お題に書かれている数字に7秒間なりきる__のが基本ルールです。
1から始まるので、まずは__1を体で全力で表現します。
WebCameraからその姿が__1かどうかが厳密に判定される__ので__1判定の状態を7秒間維持します__。
7秒間維持をクリアしたら次の数字に進みます。
スタート時点で60秒の制限時間があり、各数字をクリアごとに60秒プラスされます。
1から9まで続き、__9をクリアして全ステージクリア__です。
ちなみに「2」がすでにむちゃくちゃむずいです。
全クリしたら教えてください。
どれだけ数字が好きか体で示してもらおうか#protoout pic.twitter.com/p186Ui683h
— canonno (@canonno_blog) October 8, 2020
#実装こまごま
##狂気の教師データ
今回は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のほどよろしくお願いいたします!!!!!!!!!!!!!!!!!!!!!