鍵盤を押すと音が出るようにコードを書く
下ごしらえはだいたいできました。いよいよ動くコードを書いていきましょう。
- 2フレーム(ラベル “set”)*注意↓
ここに主なコードを書きます。
- 7フレーム(ラベル “standby”)
待機用のフレームです。(コードはありません)
- 15フレーム〜(ラベル “2c” 〜 “5c”)
音を鳴らすためのフレームです。
こういうのってハマりポイントですよね。どうせ何かのタイプミスだろうと思ってチェックするんですが、違いました。Animate CC では HTM5 Canvasでオーサリングしてるときってコードエラーとか出してくれないんですよねぇ
鍵盤にどうやってアクションを割り当てるか考える
[鍵盤が押されたら音がなる]というアクションをさせるには…
- それぞれの鍵盤のムービークリップ・インスタンスにイベントリスナーを付けて “mousedown” イベントに反応させる
- イベントを受けたら、その鍵盤に合ったメインタイムラインのフレームに gotoAndPlay(“音のラベル名”) で移動して音を鳴らす
というステップが必要ですね。
前回「音を鳴らすサンプル」の時と同じように、白鍵黒鍵のインスタンスに addEventListener(“mousedown”, hoge) を付けますが、問題は13個もボタンがあって、それぞれ目指すフレームが違うという事なんです。
残念なアクション・コード
例えば、単純にそれぞれの鍵に対してイベントリスナー を書いた場合こんな感じになります。
//スマホ対応 if (createjs.Touch.isSupported()) { createjs.Touch.enable(stage, true); } this.gotoAndStop("standby"); //"standby"のフレームに移動 var _this = this; //------------------ ここから鍵盤のコード ---------------------- //k_c _this.keyboard.k_c.addEventListener("mousedown", onKey_c); function onKey_c(evt) { _this.keyboard.k_c.gotoAndPlay("on"); _this.gotoAndPlay("3c"); } //k_cs _this.keyboard.k_cs.addEventListener("mousedown", onKey_cs); function onKey_cs(evt) { _this.keyboard.k_cs.gotoAndPlay("on"); _this.gotoAndPlay("3cs"); } //k_d _this.keyboard.k_d.addEventListener("mousedown", onKey_d); function onKey_d(evt) { _this.keyboard.k_d.gotoAndPlay("on"); _this.gotoAndPlay("3d"); } //k_ds _this.keyboard.k_ds.addEventListener("mousedown", onKey_ds); function onKey_ds(evt) { _this.keyboard.k_ds.gotoAndPlay("on"); _this.gotoAndPlay("3ds"); } //k_e _this.keyboard.k_e.addEventListener("mousedown", onKey_e); function onKey_e(evt) { _this.keyboard.k_e.gotoAndPlay("on"); _this.gotoAndPlay("3e"); } //k_f _this.keyboard.k_f.addEventListener("mousedown", onKey_f); function onKey_f(evt) { _this.keyboard.k_f.gotoAndPlay("on"); _this.gotoAndPlay("3f"); } //k_fs _this.keyboard.k_fs.addEventListener("mousedown", onKey_fs); function onKey_fs(evt) { _this.keyboard.k_fs.gotoAndPlay("on"); _this.gotoAndPlay("3fs"); } //k_g _this.keyboard.k_g.addEventListener("mousedown", onKey_g); function onKey_g(evt) { _this.keyboard.k_g.gotoAndPlay("on"); _this.gotoAndPlay("3g"); } //k_gs _this.keyboard.k_gs.addEventListener("mousedown", onKey_gs); function onKey_gs(evt) { _this.keyboard.k_gs.gotoAndPlay("on"); _this.gotoAndPlay("3gs"); } //k_a _this.keyboard.k_a.addEventListener("mousedown", onKey_a); function onKey_a(evt) { _this.keyboard.k_a.gotoAndPlay("on"); _this.gotoAndPlay("3a"); } //k_as _this.keyboard.k_as.addEventListener("mousedown", onKey_as); function onKey_as(evt) { _this.keyboard.k_as.gotoAndPlay("on"); _this.gotoAndPlay("3as"); } //k_b _this.keyboard.k_b.addEventListener("mousedown", onKey_b); function onKey_b(evt) { _this.keyboard.k_b.gotoAndPlay("on"); _this.gotoAndPlay("3b"); } //k_hc _this.keyboard.k_hc.addEventListener("mousedown", onKey_hc); function onKey_hc(evt) { _this.keyboard.k_hc.gotoAndPlay("on"); _this.gotoAndPlay("4c"); }
基本的にはこの方法で鍵盤は動きます。
この方法でもとりあえず動きますがよく見ると、黄色くなっている11〜16行のコードと同じようなコードがちょっとずつ変わって続いているのがわかります。
11〜16行は下の“ド”の白鍵用のコードです。続いて“ド#”のコード、“レ”のコード…と13鍵分のコードが書かれています。
はっきり言ってこれは駄目コードです。私は以前、よくこういうコードを書いてゲームを作っていましたが、何と言っても修正が非常に面倒です。タイプミスも増えます。その結果、バグが大量に発生します。
他のやり方も知らなかったし、とりあえず動くものができればいいやと思ってたので…結構大変でした
スマートなアクション・コード
では次のコードを見てください。見た目の上では、さっきのコードと同じ動きをします。
//スマホ対応 if (createjs.Touch.isSupported()) { createjs.Touch.enable(stage, true); } this.gotoAndStop("standby"); //"standby"のフレームに移動 var _this = this; //------------------ ここから鍵盤のコード ---------------------- //キーのインスタンス名と鳴らす音名のリスト var keyObj = { "k_c": "3c", "k_cs": "3cs", "k_d": "3d", "k_ds": "3ds", "k_e": "3e", "k_f": "3f", "k_fs": "3fs", "k_g": "3g", "k_gs": "3gs", "k_a": "3a", "k_as": "3as", "k_b": "3b", "k_hc": "4c" }; //イベントリスナー割り当て繰り返し処理 for (var keyName in keyObj) { //keyNameにインスタンス名が代入される var keyIns = _this.keyboard[keyName]; //keyInsにインスタンスを代入 keyIns.note = keyObj[keyName]; //インスタンスに勝手プロパティ(.note)を追加 keyIns.addEventListener("mousedown", onKey); //イベントリスナー追加 } //キーが押された時のアクション function onKey(evt) { var cTrg = evt.currentTarget; //タップされた鍵盤インスタンス cTrg.gotoAndPlay("on"); //鍵盤インスタンスに色を付ける _this.gotoAndPlay(cTrg.note); //メイン・タイムラインを音名ラベルに移動 }
かなりスッキリしました。このコードにはいくつかポイントがあるので、順を追って解説します。
変数(var)
変数とはプログラムの中で使う、“値”を持った入れ物のようなものです。
例えば var playerName = “太郎” ; と宣言します。するとプログラムの中で playerName と書くと “太郎” が返ってきます。playerName という入れ物に “太郎” が入ったという事ですね。
さてさて、プログラムの途中で playerName = “花子” と書きます。そうするとさっきまで “太郎” だった playerName が、今度は “花子” を返します。入れ物の中身だけが変わったのです。このように中身が変化するので「変数」と呼ぶのでしょうね。
Javascript の変数、結構なんでも入ります。実は私 よくわかってないんですが、数字、テキスト、配列、オブジェクト、その他…とりあえずプログラムの中で何かを使う時は var 変数名 = 何か; と宣言して使い始めることのなっていますね。
連想配列(オブジェクト Object)
配列とはプログラムの中で使う、複数の“値”が入っている下駄箱のようなものです。通常、配列と言うとArray型 [ 値0 , 値1 , 値2 ] を指しますが、配列にもいくつか種類があるようで、今回は次に説明する for in で必要な 連想配列(Object) を使います。
連想配列=オブジェクトには 値とキー がセットで収められています。これがとても便利で、例えば上のTipsのように変数を宣言した場合、 _obj[ キー0 ] と書くと “値0” が返ってきます。
コードの12〜26行目で、keyObj(変数)には、鍵盤のインスタンス名(キー):音のラベル名(値)を13セット、鍵盤の数分入れました。こうすると、例えば keyObj[“k_c”] と書くと “3c” を返すようになります。
※キーの事を「プロパティ」と言う場合もあります。
実はシンボルもインスタンスもオブジェクト(連想配列)なんだそうです。インスタンスのプロパティと言えば x,y座標値 や インスタンス名(.name)などありますが、見方を変えるとこれらもキーと値のセットなんですね
繰り返し処理(for in)
繰り返し処理(ループ処理とも言うようです)のコードの書き方にはいくつか種類がありますが、今回のケースではオブジェクト(連想配列)と組み合わせて使う for in が便利です。このコードでは29〜33行目でやってます。
Tipsのようなコードを書くと、オブジェクトの項目の数だけ繰り返し、実行アクションの中では変数名でオブジェクトのキーが返ってきます。
コードを解説(繰り返し処理)
for (var keyName in keyObj) { var keyIns = _this.keyboard[keyName]; keyIns.note = keyObj[keyName]; keyIns.addEventListener("mousedown", onKey); }
29: for (var keyName in keyObj) {
keyObj は12行目で宣言した鍵盤インスタンス名と音ラベル名がセットになっているオブジェクトです。for inを使うことで keyName という変数に繰り返し1回目には “k_c” が入ります。2回目は “k_cs” 、3回目は “k_d” …という値が入ります。
参考までに for of を使うと変数にキーではなく値が入る繰り返し文になります。
30: var keyIns = _this.keyboard[keyName];
この行は変数 keyIns (key-instance)に、白鍵・黒鍵のムービークリップ・インスタンスを入れる宣言です。
例えば「下のド」の鍵盤の本来の住所は _this.keyboard.k_c です。繰り返し1回目に keyName には “k_c“ が入るので、keyIns を1回目は「下のド」インスタンス、2回目は「ド#」、3回目は「レ」…として扱うことができます。
※_this.keyboard.keyName と書いてもインスタンスを指定できません。ご注意ください。
31: keyIns.note = keyObj[keyName];
式の右側、keyObj[keyName] で音ラベル名が返ってきますので、これをどこかに記録しておかないといけません。新しく変数を宣言してもいいのですが、ちょうどいい裏技があるので使ってみましょう。
keyInsには鍵盤のムービークリップ・インスタンスが代入されていますので、このインスタンスに勝手プロパティ.note を作って音ラベル名を覚えさせます。(ちなみに “note” は音名という意味です)
作り方は簡単で、インスタンス.プロパティ名と書くだけです。ただし通常使われているプロパティと同じプロパティ名で書いてしまうとインスタンスがおかしくなるので命名には注意してください。
32: keyIns.addEventListener(“mousedown”, onKey);
鍵盤インスタンスにイベントリスナーを設置します。インスタンスがmousedownされたら関数onKeyを実行します。
コードを解説(ファンクション)
function onKey(evt) { var cTrg = evt.currentTarget; cTrg.gotoAndPlay("on"); _this.gotoAndPlay(cTrg.note); }
「残念なアクション・コード」では、イベントリスナーに紐づけるファンクション(関数というそうです)を鍵盤毎にちょっとずつ違うものを用意して作っていましたが、こっちではスマートに一つのファンクションでまかないます。
この関数でやりたいことは2つ。
- 押された鍵盤インスタンスに合った、メインタイムラインの音ラベルに移動して音を再生すること
- 押された鍵盤インスタンスに色が変わるようにインスタンスのタイムラインで”on”ラベルに移動ですこと
ということで、押されたインスタンスがどれなのかわからないと話は進まないのですが、これがぜんぜんわからなくて結構ハマります(私は昔、大ハマりました)。
正解はコードにある通りで、 evt.currentTarget というイベント・プロパティの値として「押されたインスタンス」を知ることができます。って、こんなの知らないと分からないですよねぇ。”evt”というのは function hogehoge(evt) { のevtなんですが、これも変数名で任意です。私はいつもは function hogehoge(e) { と書いて e.currentTarget でタップされたインスタンスを特定しています。
というわけで無事に変数cTrgにインスタンスが代入されて.gotoAndPlayできるコードになりました。
for文でリスナーを設置するときに、紐づけたfunction関数コードの中で繰り返しの変数を書いても思うように機能しません。
今回のコードでも、keyInsやkeyNameをfunctionの中に書いて実行しても関数はひとつなので変数は繰り返し処理の中で次々と書き換わっていき、結局最後に実行した変数(このコードの場合 “高いド”の処理をしたときの値)になっています。ちゃんと動かせる方法は他にもありますが、インスタンスに紐付けるかたちで値を持たないと動かないので工夫が必要ですね。ややこしい話ですが判っていないとハマりますのでご注意を。
ちなみに、evt.targetという関数もありますが、こっちだとまた別の値が返ってくるので間違えないように注意してください。
音のフレームのコード
15フレーム以降の音名ラベルのキーフレームのコード。
this.gotoAndStop("standby");
要するに「音が鳴ったら待機フレームに移動して止まる」という事ですね。
実は今回のピアノでは、2フレーム目(6行目)にも同じコードが書いてあります。このサンプルを作るにあたって何度かテストをしてみて、addEventListener を書いているフレーム(2フレーム)に戻ってしまうとリスナーが重複して設置されてしまうようで、戻る毎にねずみ算でリスナーが増えてしまいました。
前回の「音を鳴らすサンプル」ではタップする度にリスナーを外してスタンバイ状態に戻ったときにまたリスナーを設置するような作りにしましたが、同じような処理にするといろいろややこしかったので、今回は始めに一度だけリスナーを設置して、プレイ中はaddEventListenerしないようにしています。
次は「実際のサンプルのコード」
次はピアノ鍵盤のサンプルの中身をお見せしますよ