読者です 読者をやめる 読者になる 読者になる

undefined

bokuweb.me

【12ステップで実際に作ってみる】スマホからも遊べるwebゲームレシピ【cocos2d-JS】

cocos2d-JS CoffeeScript

概要

以前作成した音ゲーのWEBアプリ版のレシピを公開したいと思います。 フレームワークはcocos2d-JS、言語はCoffeeScriptを使用します.

cocos2d-JSとは?

cocos2d-JSはオープンソースの2DゲームフレームワークのJavaScript版です。JavaScriptでiOS、Androidの両方に対応したアプリが作れます。また、WEB用にビルドすることでブラウザ上で動作するアプリを作ることも可能です。 以下は公式のデモですが、スマホのブラウザ上でもヌルヌル動くことが確認できると思います。

Cocos2d-html5 Show Case - MoonWarriors

CoffeeScriptとは?

JavaScript のコードに変換されるAltJSの一つです。 概要は以下の記事を参照してください。

blog.bokuweb.me

どんなゲーム?

f:id:bokuweb:20150518211738p:plain

『ノート』が曲に合わせて落ちてきます。『ターゲット』に重なるタイミングでタッチしてください。

完成品

こんなんです。

Cocos2d-html5 Hello World test

準備

node.jsのインストール

CoffeeScriptやGulpを使用するためnode.jsをインストールします。

公式ページからダウンロードしインストールしてください。

Node.js

Macの場合、Homebrewを使ってインストールすることも可能です。 以下のコマンドでバージョンが表示されればインストール完了です。

node -v

Gulpのインストール

GulpはCoffeeScriptのコンパイルや結合などに使用します。 以下のコマンドでインストールできます。

npm i gulp -g

cocos2d-JSのインストール

以下の記事を参考にインストールしてください。 現在のcocos2d-JSのバージョンは3.6です。 記事はwindows環境での作業になっていますが、macでも同様です。 基本的にはcocos2d-JSをダウンロードし、setup.pyを実行すればOKです。 記事後半の「coffeescriptへの変換」は不要です。

blog.bokuweb.me

プロジェクトの作成

今回作成するサンプルプロジェクトを作成します。 以下のコマンドを実行してください。 「flava」がプロジェクト名になります。

cocos new flava -l js -d .

完了後、「flava」ディレクトリが作成されていると思います。 ディレクトリ内のmain.jssrc/app.jssrc/resource.jsを削除しておいてください。 次に、以下のzip解凍し、中身を「flava」ディレクトリへコピー・上書きしてください。

https://github.com/bokuweb/cocos2d-JS_skeleton/archive/gh-pages.zip

「src」ディレクトリ内にapp.coffeemain.coffeeresource.coffeeが、ディレクトリのトップにgulpfile.coffeepackage.jsonが格納されていればOKです。

プロジェクトの動作確認

以下のコマンドを実行してください。

npm i 

その後、以下のコマンドを実行してください。

gulp build:coffee
gulp watchify

正常に完了すればディレクトリのトップにmain.jsが生成されます。 次回以降gulp watchを実行することにより、src/*.coffeeに変更があるたびに自動的にmain.jsが生成されます。 ctrl+cで終了できます。

試に動かしてみます。 以下のコマンドを実行してください。

cocos run -p web

cocs2d-JSはディレクトリトップのmain.jsを実行します。 ブラウザが起動し、以下のような画面が起動すれば動作OKです。

f:id:bokuweb:20150518183650j:plain

リソースの用意

今後使うことになる画像等のファイルをダウンロードして./resに放り込んでおいてください。

ファイル名 説明
Ouroboros.mp3 今回プレイする音楽ファイル
bg.jpg 背景画像
cover.jpg 音楽カバーイメージ
note.png ノートイメージ
dest.png ターゲットイメージ

ディレクトリについて

いろいろディレクトリがありますが基本的には触るのはressrcです。

ディレクトリ名 説明
res 画像等のリソースを格納する
src ゲームのソースファイルを格納する

srcの中には現在以下のようなファイルがあると思います。

ファイル名 説明
main.coffee main。まずはこいつが実行される
resource.coffee 画像等のリソースファイルを管理する
app.coffee アプリ部分

ブラウザのキャッシュをOFFにしておく

開発中はブラウザのキャッシュをOFFにしておいたほうがよいです。 chromeの場合デベロッパーツールの設定でDisable cache (while DevTools is open)にチェックを入れておくとよいです。 デベロッパーツールを開いている間のみキャッシュOFFになります。

f:id:bokuweb:20150518183717j:plain

手順1. 背景を表示してみる

※各ファイルの変更前にgulp watchを実行しておいてください。 変更後に以下のようにしてもOKです。

gulp build:coffee
gulp watchify

main.coffeeの変更

まずはmain.coffeeを編集します。 5行目でapp.coffeeをrequireしています。いつまでもHelloWorldSceneじゃおかしいのでGameSceneにリネームします。 同様に14行目もGameSceneにリネームします。

次に11行目を次のように変更します。 cc.view.setDesignResolutionSize 320, 480, cc.ResolutionPolicy.SHOW_ALL これはどんな解像度の端末であれ「横320px×480pxで描画しますよー」という仮想的な解像度です。 この設定により端末による差異をなくし、同じように表示することができます。 cc.ResolutionPolicy.SHOW_ALLは縦横比を保ったまま、はみださないように端末にフィットさせる設定です。 他にも縦横比を無視して画面にフィットさせる設定や、縦横比を保ったまま短い辺に合わせる(長い辺ははみだす)設定などがあります。

13~15行目でリソースのプリロードを行い、プリロード完了のコールバックでGameSceneに切り替えています。 cc.directorは名前の通りでゲームのシーンなどを管理します。 cc.director.runScene new GameScene()でシーンをGameSceneに切り替えています。

21~25行目ではGameSceneを定義し、GameSceneGameLayerを追加しています。 cocos2dでは『シーン』と『レイヤー』の概念があり、『スプライト』や『ラベル』をレイヤーに追加し、1つ以上の『レイヤー』を追加することで『シーン』を構築していくのが基本っぽいです。

・変更後 main.coffee

resource.coffeeの変更

resource.coffeeは画像などのリソースを管理します。 画像などを使用する場合は必ずこのファイルに追記する必要があります。 ついでに今後使用する予定のリソースをすべて追記しておきます。 何か独自に使用したい画像などがあれば現在のフォーマットに習って追加すればOKです。

・変更後 resource.coffee

これで背景画像bg.jpgを使用することができます。

app.coffeeの変更

main.coffeeと同様HelloWorldSceneGameSceneにリネームします。 1行目でresource.coffeerequireしています。 5行目のctorはコンストラクタです。new GameLayer()としたときに実行されます。(ここでは24行目ですね)。 ここでは"Hello World"を表示するラベルと背景画像の設置を行っています。 14行目~19行目が背景画像配置処理です。 15行目でアプリサイズの取得。 16行目で背景画像bg.jpgを使用したスプライトを生成しています。cc.Sprite.create "画像へのパス"でスプライトは生成できます。
17~18行目でスプライトをアプリの中央に配置。 19行目でGameLayerに追加しています。addChildの第二引数はzIndexすなわちZ方向の表示順序です。 この場合"0"なのでGameLayerの最奥に配置され、"1"や"2"に設定されたスプライトやラベルの方が手前に表示されます。

・変更後 app.coffee

実行してみる

cocos run -p web

ブラウザが起動して以下のようになればOKです。

f:id:bokuweb:20150518183411j:plain

手順2. タイマークラスの実装

timer.coffeeの追加

cocos2d-JSでは残念ながらBGMの再生時間を取得することができません。 なので、自身でタイマを作成し再生時間を計測する必要があります。 タイマークラスは以下のようにしました。srcに追加してください。

・timer.coffee

start()でタイマーを開始し、get()で開始からの経過時間を取得します。

手順3. タッチ(クリック)できるスプライトの実装

通常のスプライトはタッチ(クリック)しても何も反応しません。 今回のゲームはタイミングよく『ノート』にタッチするゲームなのでタッチできるスプライトを作る必要があります。 以下のようなTouchSpriteクラスを作成します。

touchSprite.coffeeの追加

3行目の@_superは継承元(この場合cc.Sprite)の同名のメソッド(この場合ctor)を呼び出しています。 すなわちcc.Spriteのコンストラクタctorにスプライトに張り付ける画像を渡しています。 4~8行目でタッチベントを登録しています。8行目でタッチ開始時に呼ばれるメソッドを登録しています。 10~17行目はタッチ時の処理です。 ただし、11行目~15行目はひとまずおまじないと思ってもらっていいと思います。 タッチされた領域が一致しているか比較してるっぽいです。 なのでタッチされたときの処理は16行目以降にifの中に書けばいいです。

・touchSprite.coffee

手順4. タッチスプライトを継承しノートクラスの実装

note.coffeeの追加

手順3で作成したタッチスプライトを継承し『ノート』クラスを作ってみます。 『ノート』の動作は以下です。

  • 設定した時間に設定したY座標(ターゲット)に到達するように移動する
  • タッチされず一定時間経過した場合はフェードアウトする
  • タッチされた場合、設定時間とタッチされた時間との差分を計算し、判定を行いリスナーに通知する

これらを実現していきます。

5~7行目はコンストラクタです。 第一引数でテクスチャ、第二引数で各種パラメータ、第三引数でタイマーを受け取っています。 パラメータの内容は目標Y座標、目標Y座標に到達する時間、落下速度、判定しきい値です。

6行目でTouchSpriteにテクスチャを渡しています。 7行目の@_listenersイベント(今回は判定イベント)が発生したときに実行するコールバックを保持する配列です。初期化しています。

9~10行目は動作開始メソッドです。 @scheduleUpdate()を呼ぶことで、60FPS(すなわち16.6ms毎)でupdateが実行されるようになります。

17~32行目は60FPS(すなわち16.6ms毎)で実行されるupdateメソッドです。 18~22行目で移動処理を行っています。 18行目で現在時間の取得を行っています。 19~20行目で現在時間が設定時間以上だった場合設定座標で停止するようにしています。 21~22行目で現在時間と設定時間の差分よりY座標を算出し設定・移動しています。

24~32行目では現在時間が設定時間+Good判定しきい値を超えた場合にフェードアウトする処理を行っています。 25行目は外部に「BAD判定があったよー」と通知しています。 26行目で@unscheduleUpdate()を呼び、updateを停止しています。 32行目でアニメーションを実行しています。 どんなアニメーションかを28行目で設定してます。 cc.sequenceは処理を順番に実行していきます。(ちなみに同時に実行するのは38行目のcc.spawnです。) cc.fadeOut 0.2で0.2秒かけてフェードアウトした後、cc.CallFunc.create cb, thisでコールバックcbを実行します。 コールバックは27行目でcb =-> @removeFromParent onとありますので、親要素から自身を削除します。 すなわち、「0.2秒でフェードアウトした後、親要素から自身を削除する」という処理を24~32行目で行っています。

34~47行目でタッチ時の処理を行っています。 36行目で判定処理をおこなっています。 以降は24~32行目と基本的に同じですが、cc.spawnを使用し、cc.fadeOut 0.2cc.scaleBy 0.2, 1.5, 1.5が同時に実行されるようにしています。これにより、0.2秒かけてフェードアウトしながら、1.5倍に拡大するアニメーションが実行されます。

49~59行目は判定処理です。 現在時間を取得し、設定時間との差分を計算。 パラメータで受け取った判定しきい値との比較を行い、判定結果を_triggerメソッドでリスナーに通知しています。

61~64行目はイベント通知処理です。 @_listenersに格納されたコールバックを実施します。

『ノート』クラスを使ってみます。 app.coffeeで『ノート』を作成し、動かしてみます。 1~3行目で各クラスをrequireします。 10~16行目で各種『ノート』のパラメータを設定します。 今回は再生時間が4秒の時に目標座標0pxに500px/secで到達するように設定しています。 今更ですがcocos2dの場合、アプリの左下が座標(0,0)になります。 今回の場合、右下が(320,0)、右上が(320, 480)となります。 また±0.2secでGREAT判定、±0.4secでGOOD判定、それ以外はBADとなるよう設定しています。

18行~23行目で『ノート』を作成しレイヤーに追加しています。

24行目で判定が行われたときに呼び出されるリスナーを登録します。 今回は判定が行われると、_onJudge()が呼ばれるよう登録しておきます。

25行目で『ノート』の動作を開始します。

26行目でタイマーを開始します。

28~29行目は上述した、判定時に呼ばれるメソッドで今回は判定結果をコンソールに表示します。

動かしてみます。

cocos run -p web

こんな感じになります。

f:id:bokuweb:20150518183452g:plain

判定結果がコンソールに表示されます。

ここまでのソース

github.com

手順5. 高解像度端末に対応する

main.coffeeの変更

現状のままだと高解像度端末で起動した場合、画像がぼやけてしまうらしいです。 対策として、画像は解像度が倍(以上)のものを用意する必要があります。 ただ、そのまま解像度が倍のものを使用すると倍のサイズで表示されてしまいます。 よって、表示サイズを1/2に縮小する必要がありますが、これを自動でやってくれるのがcc.director.setContentScaleFactor 2です。 cc.director.setContentScaleFactor 2により、画像などの表示サイズを1/2にしてくれます。

11行目に追加しています。

動かしてみると落ちてくる『ノート』が1/2になっていることが確認できると思います。

cocos run -p web

手順6. 『ノート』の『ターゲット』を描画する

app.coffeeの変更

『ノート』の『ターゲット』を描画します。

6~9行目で各種パラメータを設定しておきます。

  • _keyNum : 5 #『ターゲット』をx方向に5つ描画する
  • _margin : 60 #『ターゲット』のx方向マージン60px
  • _offset : 38 #『ターゲット』のx方向オフセット38px
  • _destY : 60 #『ターゲット』のy座標60px

43~50行目が描画メソッドです。 13行目で呼ばれています。

・app.coffee

動かしてみると『ターゲット』が描画されているのが確認できると思います。

cocos run -p web

こんな感じ。

f:id:bokuweb:20150518183633j:plain

手順7. 『ノート』を『ターゲット』に向けて落下させる

music.coffeeの追加

まずは再生する音楽に関する情報を記述したmusic.coffeeを用意します。

2~5行目にはファイルパスやカバーイメージ、再生終了時間などを記載しておきます。後で使用します。 6行目以降は何秒にどの『ターゲット』へ到達させるかを記載しています。 keyが0の場合は一番左に、keyが4の場合は一番右に落ちてきます。

app.coffeeの変更

music.coffeeで設定した内容に沿って動作するよう修正します。 11行~14行目で落下速度や判定しきい値を設定しています。 35行~46行目で落下してくる予定の『ノート』を作りおきしておきます。 ゲーム中にスプライトを作成するとメモリを確保のためにGC(ガベージコレクション)が実行される可能性があるためです。 GCが走っている間、プログラムが停止するため、アニメーションがカクカクになったりします。 このためメモリはゲーム中に確保したり破棄するのではなく、初期化時に極力確保しておきGCの回数を最小限にする必要があるそうです。

このあたりが参考になります。

www.html5rocks.com

ここでは一旦画面外に描画しておき、配列にpushしておきます。

手順4でも記述しましたが、updatescheduleUpdate()が呼ばれることでおよそ60FPSで実行されるメソッドです。 updateメソッド内では『ノート』の落下に要する時間や現在時間、到達時間などから動作を開始すべき時間を計算し、必要に応じて動作の開始を行います。

・app.coffee

動かしてみると『ターゲット』に向かって『ノート』が落下していくの確認できると思います。

cocos run -p web

こんな感じ。

f:id:bokuweb:20150518183841g:plain

手順8. ゲーム終了判定と終了画面を実装する

gameOver.coffeeの追加

終了時に表示するゲームオーバーシーンの追加を行います。 中央に”Game Over”と表示するのみのシーンです。

・gameOver.coffee

app.coffeeの変更

5行目でgameOver.coffeeをrequireします。 36行~39行目にゲーム終了処理を追記しています。 update内で現在時間がmusic.coffeeに設定したplayTimeを超えていないかモニタし超えていたら終了処理を行います。 37行目でゲームオーバーシーンを作成しています。 38行目でupdateを停止しています。 39行目でゲームオーバーシーンにシーンを切替えています。 cc.director.runScenenew cc.TransitionFade(1.2, gameOver)を渡すことで1.2秒でフェードしながらシーンを切替えることができます。

・app.coffee

動かすとシーン切替後"Game Over"と表示され、シーンが切り替わっていることを確認できると思います。

cocos run -p web

f:id:bokuweb:20150518183931j:plain

手順9. スタートメッセージの追加と楽曲の再生を実装する

app.coffeeの変更

スタートメッセージをタッチすることでゲームが開始するようにします。 主な追加箇所は79~110行目です。 _addStartButtonではスタートメッセージの追加を行っています。 基本的には画面中央にメッセージを表示するだけなんですが、cc.sequencecc.RepeatForeverを使用することで1秒間隔で永久に点滅するようにしています。 また90~94行目でタッチイベントを登録しています。メッセージがタッチされると、96行目の_onTouchStartが実行されます。

_onTouchStartはごちゃごちゃしていますが、ほぼタッチのための処理であり、基本的には102行目の”自身を親から削除する”と103行目の”楽曲を再生する”がメインの処理です。

107~110行目の_timerStartIfMusicPlayingではBGMが再生中か判断し、再生中であればタイマーを開始しています。 _onTouchStart内で再生していますが、@_music.playMusicから実際に再生が始まるまでタイムラグがあるようなので(環境によるものかもしれませんが)このようにしています。この方法でも多少のずれは発生しますが、無視できるレベルだと思います。 スケジュールは27行目でセットしており、タイマ開始とともにうスケジュールをクリアしています。

・app.coffee これで一応最低限遊ぶことができるようになりました。

手順10. スコアの計算と表示を行う

app.coffeeの変更

スコアの計算と表示を行います。 118~125行目でスコアラベルの追加を行っています。 _onJudge()で判定結果に合わせてスコアの計算を行っています。 すべてGreat判定の場合100000点、すべてgoodの場合70000点となるように計算しています。

・app.coffee

手順11. 判定結果の表示を行う

app.coffeeの変更

判定結果の表示を行います。 133~141行目で判定表示ラベルの追加を行っています。 _onJudge()で判定結果に合わせて判定表示ラベルの文字列を変更しています。 変更後、_showJudgeLabel()により一定期間判定表示ラベルを表示します。 具体的には

seq = cc.sequence cc.fadeIn(0.2), cc.fadeOut(1)
@_judgeLabel.runAction seq

で0.2秒かけてフェードイン、1秒かけてフェードアウトさせています。

・app.coffee

手順12. 楽曲カバーイメージとタイトルの表示を行う

app.coffeeの変更

もはや説明不要かと思いますが、カバーイメージとタイトルの表示を行います。 149行~163行目の_addCoverImage()_addTitle()で表示を行っています。

・app.coffee

以上です。一応音ゲーっぽくはなったと思います。 お疲れ様でした。

リポジトリは以下です。 色々いじって遊んでみてください。

github.com

さいごに

実はなんですが、もともとAndroid/iOS両対応のアプリを作る記事として書き始めたんですが現状の書き方だと、ネイティブ動作でうまくいかないこが分かり、WEBアプリの記事にピボットしました。ネイティブ用に修正する気力がもてなかったため、公開もやめようと思いましたが、途中まで書いていたこともあり、中途半端ながら公開することとします。

おそらくですが、『ノート』のタッチイベントの登録のところで上手くいっていないと思います。sampleのTouchesTestも何故かネイティブ動作NGになっていますので多分問題があるのだと思います。

気になる方はcocos2d-js-v3.x/samples/js-testscocos run -p webとかcocos run -p iosし、ソースと比較しながらいろいろ試してみてください。

またAndroid版とそのソースも公開しているので参考にしてみてください。

github.com

cocos2d-xではじめるスマートフォンゲーム開発 [cocos2d-x Ver.3対応] for iOS/Android

cocos2d-xではじめるスマートフォンゲーム開発 [cocos2d-x Ver.3対応] for iOS/Android