Animate CC で “箱入り娘” のサンプルを作ってみるよ!
いよいよ本格的(っぽい)webゲームを作ってみましょう。ということで日本の伝統パズル「箱入り娘」に挑戦です。
↓完成したサンプルです↓
タップすると画面に表示されます
- はじめに挑戦するステージを選んでください(初回は“練習”のみ)
- 「娘」の駒を出口(中央の下)まで動かせばゲームクリア
- 駒をやりくりして「娘」を出口(中央の下)まで運びます
- プレイ途中で画面を閉じても、続きがプレイできます
基本的な構成を考える
今回は“本格的webゲーム”ということで、押さえておきたい要素があります。それと内容的にはシンプルなスライドパズルなので、操作性も重要視したいと思います。
- ステージをクリアしないと、次のステージがプレイできない
- プレイ中にページを閉じても、次に続きからプレイ可能
- ステージ毎にスコア(タイム)を記録する
- 可能であれば複数のブロックを同時に動かせる
メインタイムライン
今回のタイムラインは、これまでのサンプルよりかなりごちゃごちゃしてしまいました。
ごちゃごちゃなのはタイムラインだけじゃないよ。今回はコードもぐちゃぐちゃで〜す(汗;)
シーンは4つ
今回のシーンは4つ
- [continue]プレイ中に中断したゲームを[続ける/やめる]画面
- [menu]ゲームのステージを選ぶメニュー
- [play]ゲームをプレイするシーン
- [clear]パズルが解けた時の終了画面
labelというレイヤーにシーンを分けるラベルが付けてあります。
さらに、それぞれのシーンの手前に[cSet]、[mSet]、[pSet]というラベルがあって、これらSet系ラベルの2フレーム後のactionレイヤーにキーフレームが付けられています。
HTML5 Canvas でのフレームアクションの書き方
Animate CC では通常、Flashでおなじみの ActionScript 3.0 というプログラム言語でオーサリングします。私もスマホアプリはActionScriptで作りました。
一方、今回のサンプルは HTML5 Canvas というタイプを選択していてコードは JavaScript (CreateJS) で書きます。
ActionScript 3.0
webアプリ(HTML5 Canvas)は
JavaScript (CreateJS)
goToAndPlayの飛び先にコードが書けない
“箱入り娘”のタイムラインの一部です。25フレーム目『mSet』のラベルの2フレーム後ろに、アクション(コード)を書いたキーフレーム(a-mSet)があります。
このa-mSetには、ステージ選択メニューを制御するコードが書いてあります。プログラムを起動すると初期設定を済ませた後に gotoAndPlay で mSet に移動、メニュー画面のセッティングを行います。
さて、コードは全くいじらずにキーフレームの場所だけ変えてみました。移動先の mSet と同じフレームです。
この状態でHTML5にしてブラウザでプレビューしてみると…
右側の赤い文字がズラズラと表示されます。エラーです。
「○○が見つからない」とか「△△は未設定です」とか出まくります
当然ですがメニューの画面要素は mSet に全て準備されています。しかし準備した要素が「みつからない」といったエラーが出るのです。
アクションのコードは移動先の2フレーム後ろに書きましょう
エラーになる状況としては、例えば移動したキーフレームにボタン用のムービークリップ・インスタンス(btn_level1)が配置されていて、このインスタンスにbtn_level1.addEventListner(“click”,hogehoge);でリスナーを設置するコードを書くとか、btn_level1.gotoAndStop(“on”);のようにインスタンス・タイムラインを動かそうとした時に問題が発生します。
従来(ActionScriptの時)は移動先のラベルしたキーフレームにコードを書いて行なっていたアクションができないのは辛いですね。
なぜこのような現象が起こるのか? 理由は私にはわかりませんが、とにかく問題を避けるために、2フレーム後にキーフレームを作ってコードを書くという方法をつかっています。
移動先に到着した時点では素材が読み込まれていないっぽい動きです。だいたいは1フレーム後でエラーなしになるのですが、なぜか今回は2フレームうしろにしないとエラーになるケースがあったので、念のため全て2フレーム後ろにしてあります。
ちゃんと作っているハズなのにエラーになるのでドロ沼にハマりました。ご注意ください
見た目に注意しよう
コードを書くキーフレームを2フレーム後に書くと、移動した瞬間まだコントロールされていないムービークリップ・インスタンスが画面上に現れてしまいます。
この2つの画像は同じシーンのイメージですが、左側は素材のままの状態、右側がプログラムでコントロールした後の状態です。
2フレーム進んでコードが書いてあるキーフレームに達すると、右の状態になるのですが、ほんの一瞬、左の画面が見えます。
ほんの一瞬、とはいえ注意深く見るとガチャガチャっとしたものが見えてしまいます。
そこで、プログラムで見た目が動くパーツは図のように menu のキーフレームの位置にキーフレームを打ち、前半のセッティングするタイミングではカラー効果のアルファ値を『1%』にしてほぼ透明にしています。
連続して同じインスタンス名で配置された同じシンボルのインスタンスはキーフレームで分割しても同じものと見なされるようです。プロパティはキーフレーム毎に設定可能(トゥイーンもOK)なので、うまく使えばガチャガチャ感を軽減できます。
アルファ0%にすると動きませんのでご注意を!
本当はこんな気遣いしないで動かしたいけど、そうもいかないですよね〜
- 飛び先のラベルの2フレーム後にアクションを書けば、とりあえずちゃんと動く
- フレーム移動からアクション実行までの間の見た目に注意
プレイデータを保存する方法 localStorage
Animate CC で作ったwebアプリでも、データの保存ができます。
保存データの読み込み
localStorage.setItem()
保存データの書き込み
//----保存データのよみこみ var saveDataValue = localStorage.getItem("tanegame004slidepuzzle_playData"); var playData = JSON.parse(saveDataValue); //JSON文字列からオブジェクトに変換 //----データを保存する function onSavePlayData() { saveDataValue = JSON.stringify(playData); //オブジェクトをJSON文字列に変換 window.localStorage.setItem("tanegame004slidepuzzle_playData", saveDataValue); }
この方法を使うことでハイスコアの保存や、クリア状況の保存が可能になります。
ちなみに“箱入り娘”で保存しているデータはこんな感じです。
//初期設定 playData = { currentLv: 0 /*最後にプレイしたレベル*/, nowPlaying: false /*プレイ中のデータがあるか*/, time:0 /*プレイ中の経過時間*/, move: 0 /*プレイ中の手数*/, layout: [] /*プレイ中のブロックの配置*/ }; //レベルごとのクリアデータ playData.clear = new Array(); playData.clear[0] = {/*入門*/ clear: false, move: 9999, time: (24 * 60 * 60 * 1000) }; playData.clear[1] = {/*初級*/ clear: false, move: 9999, time: (24 * 60 * 60 * 1000) }; playData.clear[2] = {/*中級*/ clear: false, move: 9999, time: (24 * 60 * 60 * 1000) }; playData.clear[3] = {/*上級*/ clear: false, move: 9999, time: (24 * 60 * 60 * 1000) }; playData.clear[4] = {/*伝統的*/ clear: false, move: 9999, time: (24 * 60 * 60 * 1000) }; //ここまで
レベル毎のクリアデータは新記録が出ると更新するように作ったので初期設定では時間は24時間、手数は9,999手を初期設定で持たせています。
Webアプリの場合、プレイの途中でうっかりページを閉じるとか、別のページを開いてしまう事がよくありそうです。なので今回のようにプレイ再開できる機能があるとプレイヤーはうれしいですよね。
これは使えますよ!しかしながらプレイ中のデータを保持しようと思うとコードはかなり増えるし、はじめにしっかり設計しないと途中で混乱します…
今回はなんとか実装できましたが、この辺りはまだまだバグが潜んでいそう
- ハイスコアやクリア情報の保存には localStorage を使う
プレイ時間を計る
日時を扱う
.getTime()
1970年1月1日0時0分0秒からのミリ秒(整数)を返す
//ゲーム開始の時間をメモ var sDate = new Date(); playData.startTime = sDate.getTime(); //現在のプレイタイム var _date = new Date(); playData.time = _date.getTime() - playData.startTime;
ゲームを開始した時に playData.startTime に getTime() した値を入れておき、ゲームプレイ中は getTime() した現在時刻からplayData.startTime を引くとプレイ時間がわかります。
単位はミリ秒。1000 =1秒。1000* 60 = 1分。1000* 60* 60 =1時間です。
中断から再開する時は、ゲームスタート時に getTime() した値から中断するまでの playData.time を引いた時間を playData.startTime にすれば引き続きプレイ時間が測定できます。
- 時間系には Date() を使い、getTime() で整数として扱うと便利
操作性(ブロックの動かし方)
ちょっとこだわって作ってみました。
例えば図のように3つのブロックを右に動かしたい時、空いているマスの横にあるブロックひとつだけが動かせるスライドパズルのアプリが多いんですね。
そうすると3つのブロックを移動させるのに3手かかってしまいます。
でも実物のパズルだったら一番左のブロックを動かして、左側の2つを押して1手で完了ですよね。
この動きを作りたかったんです
させたい動きのイメージを固める
図のようなケースは、オレンジのブロックを中央に移動させるのに、まず左のブロックを押し退け、さらに中央のブロックも下に押しやって、1手で3つのブロックを動かせたら良さそうです。
この図の場合は3つのブロックが同時に動かしたいですね。
同じ配置から、こちらのブロックを起点にすると4つのブロックが同時に動いてほしいです。
プログラムのイメージ
ということで、このような動きを実現するためには、どうやってプログラムすればいいかを考えます。
例えばこんな状況だったとしましょう。ブロックを配置する [座標値] とは別に、マップの配列を作ります。
var _map = [ [m, m, 0, c, c], [m, m, b, 0, k3], [0, k1, b, k2, 0], [k0, 0, h, h, 0] ];
そしてこのブロック配置のマップを基にして、左右、上下と方向別にどのブロックが動けるかを割り出し、それぞれ配列にします。
左に動けるブロックを割り出す方法はこんなイメージです。
左に進むので、左側からチェックします。
[x=0] 左の列のマップを参照してブロックがあれば -1 を、無ければ 0 を代入します。
[x=1] 2列目ではブロックがある場合、ひとつ左のマスが -1 なら -1を、そうでなければ(else if) 配列 [ ] を宣言して中に自分を代入します。
[x=2] 3列目からは、左が -1 なら -1、そうでなければ(else if)配列 [ ] を宣言して中に自分を代入し、もし左側が配列であったらその中の要素を自分の配列に追加します。
このような準備をしておけば、ブロックが選択されたときに…
- どの方向に動かせるか
- どのブロックを一緒に動くか
がすぐにわかります。
ブロックの配置が変わる毎に、マップを含めた5つの配列を作るんですよ
というわけで、こんな実装できればイメージ通りに動かせるでしょう。
- プログラミングのイメージを事前に組んでおかないと後で苦労する
まとめ
実は中身はぐちゃぐちゃだけど、いい感じに動くwebゲームになったと思う
「箱入り娘」を作ってみて、Animate CC の HTML5 Canvas で動くwebアプリを作りこむのはやっぱり結構大変だと思いました。
とはいえ、ここまでいくつか作ってきたサンプルの経験が活きてます。これからもっと色々なものを作っていけば効率的なやり方がわかってきそうですね。