あっぷりノート

Fix the Bits | あっぷり工房

旅、ギア、サプリ、マインド、トレーニング ── “走る”は創れる

【大公開】ランニング用メトロノームにアニメーションを完全同期させました

f:id:you_key69:20220209220030j:plain


メトロノームのテンポ(bpm)とロックマンの走行ピッチ(spm)を合わせて、クリック音と接地がリンクする最強のランニング用メトロノームを作りました。


以前のメトロノーム問題

先日、「ランニングメトロノーム」と銘打ってWebアプリを公開しました。

しかし、

ほざけ!どこがランニングメトロノームじゃ

と誰しもが思ったことでしょう。

いや、興味すらわかなかったかもしれません ^^;


なんせ、ランニング要素がほとんどなかったからです。


しかーし!

今回のは違います。

走行ピッチとメトロノーム音を完全同期!

今回のメトロノームでは、ロックマンがお手本を示してくれるかのように、一緒に走れるようなイメージで改良しました。




bpm  拍子

  • Edge、Chrome、Firefox(Android、PC)動作確認済み
  • IE、Safari(iOS)非対応です><

メトロノームに合わせてピッチを刻もう

よく「ピッチを180くらいにするといい」という話を耳にすることがあるかもしれません。

脚が疲れにくくなるとか、走りが安定するとか、効能は分かる。

でも、実際にどうやったらいいの?

という素朴なギモンを解決するために、モーション付きのメトロノームを開発してみました。


一緒にロックマンの接地に合わせてメトロノームのクリック音を感じましょう。

そして、テンポを少しずつ上げて走ってみて、これなら超時間キープできそう!というテンポ(ピッチ)をぜひ探ってみてください。

※別に180bpmにこだわる必要はありません

開発に至った5ステップ

さて、成果物は以上です。

あとは、開発に至ったステップと記述したコードを簡単に共有させていただきます。

  1. JavaScriptを扱ってみる
  2. メトロノームで音を出してみる
  3. ドット絵を書いてみる
  4. アニメーションを創ってみる
  5. 学びをすべて総動員する

ここ3週間、JavaScriptを色々といじっていましたが、実はすべては繋がっていたのです(怪しい…w)

12コマにしたのも4拍子と6拍子の両パターンに対応できるように、最小公倍数を採用しました(天才)

JavaScriptを扱ってみる

まず、ブログにJavaScript(JS)を埋め込む方法、そしてJSで何ができるのかを確かめました。

メトロノームで音を出してみる

JSで音声を鳴らす方法、そして音声を64バイトの文字列に変換する考え方について学びました。

また、JavaScriptには「待ち(wait)」の概念がないので、インターバルで代用する手法を学びました。

ドット絵を書いてみる

一旦JSから離れて、Excelでドット絵を描くというお家芸。

そして、VBAでシートを切り替えてアニメーションとして不自然な点はないか、動作確認を行いました。

また、Excelで描いたドット絵を画像化(jpg化)できることや、Windows10の標準機能でPC画面を録画できることを知りました。

アニメーションを創ってみる

ここでは「配列」の概念と、画像の呼び出し・切り替えを学びました。

ここまで来ればあとすこし。

学びをすべて総動員する

上記の学びをすべて総動員して実現したのが、当記事のメトロノーム×アニメーションです。

次項にJavaScriptの全ソースコードを公開しておくので、参考にしてみてください。

JavaScriptソースコード

ほとんど

  • 2. メトロノームで音を出してみる
  • 4. アニメーションを創ってみる

を組み合わせることで実現可能でした。

これぞ「ノウハウ」ってやつですね。

<img id="SlideImg12" src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/*********/20220208/20220208224628.jpg" width="540px" height="auto">
<form id="Form12" action="#">
	<select name="bpm" id="bpm12">
	</select> bpm 
	<select name="beat" id="beat12">
	<option value="4" selected>4</option>
	<option value="6">6</option>
	</select> 拍子
	<input type="button" onclick="Start()" value="START" id="btn_Play12">  <input type="button" onclick="Stop12()" value="STOP" id="btn_Stop">
</form>

<script>
	// インターバル宣言
	var Interval;
	var BeatInterval;

	// ループ処理でコンボボックスの値生成
	for (var i = 0; i <= 16; i++) {

		//select要素を取得する
		const selectBpm = document.getElementById('bpm12');
		
		//option要素を新しく作る
		const option1 = document.createElement('option');
		
		//option要素にvalueと表示名を設定
		option1.value = 140 + (i * 5);
		option1.textContent = 140 + (i * 5);
		
		//select要素にoption要素を追加する
		selectBpm.appendChild(option1)
	}

	// 画像URL
	let path = "https://cdn-ak.f.st-hatena.com/images/fotolife/y/you_key69/";

	// 配列に追加
	const ImgArray = [
	 path + "20220208/20220208224639.jpg"
	,path + "20220208/20220208224653.jpg"
	,path + "20220208/20220208224813.jpg"
	,path + "20220208/20220208224824.jpg"
	,path + "20220208/20220208224848.jpg"
	,path + "20220208/20220208224857.jpg"
	,path + "20220208/20220208224915.jpg"
	,path + "20220208/20220208224949.jpg"
	,path + "20220208/20220208231425.jpg"
	,path + "20220208/20220208231416.jpg"
	,path + "20220208/20220208231410.jpg"
	,path + "20220208/20220208231359.jpg"
	];

function Start(){
	// 再生ボタン無効化
	btn_Play12.disabled = true;

	// コンボボックスの値を取得
	let elementBpm = document.getElementById('bpm12');
	let elementBeat = document.getElementById('beat12');
	var bpm = elementBpm.value;	// bpm:テンポ(1分間に何拍するか
	var beat = elementBeat.value; // beat:拍子(指定した拍数ごとに高いビープ音を鳴らす)
	var MilSec = 60 / bpm * 1000;
	Metronome(MilSec, beat);
	SlideShow(MilSec);
}
function SlideShow(MilSec){
	// スライドショー開始
	var j = 0;
	Interval = setInterval(function(){ // 指定テンポで繰り返す
		SlideImg12.src = ImgArray[j];
		j = j + 1;
		if (j == 12){j = 0;} // 12枚目終了後に1枚目に戻る
	}, MilSec / 2);
}
function Metronome(MilSec, beat){
	// 低め音
	var low = "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";
	// 高めの音
	var high = "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";

	// それぞれ Audio オブジェクト生成
	var lowPlayer = new Audio("data:audio/wav;base64," + low);
	var highPlayer = new Audio("data:audio/wav;base64," + high);

	var j = 0;
	BeatInterval = setInterval(function(){ // 指定テンポで繰り返す
		j = j + 1;
		if (j != 1){lowPlayer.play();} // 1拍目以外Low
		if (j == 1){highPlayer.play();} // 1拍目のみHigh
		if (j == beat){j = 0;} // 指定拍でループリセット
	}, MilSec);
}
function Stop12(){
	// インターバルを停止
	clearInterval(Interval);
	clearInterval(BeatInterval);
	
	// 再生ボタン有効化
	btn_Play12.disabled = false;
	
	// 画像リセット
	SlideImg12.src = "https://cdn-ak.f.st-hatena.com/images/fotolife/y/you_key69/20220208/20220208224628.jpg";
}
</script>

本来なら1つのインターバル内でアニメーションも音声も完結できると美しいのですが、クオリティも遜色なかったのでスピード重視で仕上げました。

まとめ

JavaScriptを勉強し始めて、3週間の集大成です。

これにてやりたかったことはひとまずやり遂げることができました。


あとはゲーム性をプラスしていくとかでしょうか。

追伸

まさかカプコンさんが訴えてくるようなことはないと思いますが…むしろ製品化してくれるとうれしいです。

他のキャラのVersionも喜んで作りますよ笑