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

フニゲの開発日記

Electronとか...

主な登場人物

原稿


2014/02/15修正。 v2.2.2対応。


 眠いときにプログラムをしていると、意識が朦朧として、何もコードを変えてないのに突然まったく動かなくなったり、逆に直したつもりのないバグがいつの間にか直ってたり、不思議な経験をよくします。そんなときには、コンピュータの中の世界の住人達のことをちょっと考えてみるのもいいでしょう。

cc.Directorとcc.Scheduler

 Cocos2dの世界には数多くの魅力的なキャラクター(クラス)が登場しますが、シングルトンとそうでないものの二つに分けて整理すると理解しやすいでしょう。
 後者の代表がcc.Spriteなら、前者の代表はcc.Directorです。スプライトのインスタンスが一度に数十個〜数百個生成されるのに対して、cc.Directorのインスタンスは、常にひとつのゲームに一個だけです。


 Cocos2d-html5のシングルトンは、getInstanceメソッドを使ってプログラムのどこからでも簡単にアクセスすることができます。

var director = cc.Director.getInstance();

// ウィンドウのサイズを取得
var size = director.getWinSize();

 シングルトンはそれぞれ重要な役割を担っていますが、cc.Directorの主な仕事はシーンを切り替えることです。cc.Transitionを一緒に使うと、シーンの切り替えに様々なトランジション効果を加えることができます。

// フェードアウトして次の画面へ
var transition = cc.TransitionFade.create(1.0, new ResultScene());
cc.Director.getInstance().replaceScene(transition);

 ちょっとcc.Actionと似ていますね。cc.TransitionFadeの他にも、多くのトランジションが用意されています。
 トランジションには時間がかかりますから、トランジションが終わるのを待って何か実行したい時もあるでしょう。例えばトランジションの間タッチ操作を禁止して、トランジションが終わったらタッチに反応するようにしたい時は、レイヤーのonEnter­Transition­Did­Finishメソッドを使うのが簡単です。

var Sample = cc.Layer.extend({
  ...
  onEnter:function () {
    this._super();
    this.pause = true;
  },

  onEnterTransitionDidFinish:function () {
    this._super();
    this.pause = false;
  },
  ...

 こうしておけば、pauseフラグでトランジション中かどうか判別できます。


 シーンがはじまると、cc.Directorは一見何もしていないように見えますが、実はcc.Directorの影でcc.Schedulerというクラスが忙しく動き回っています。cc.Schedulerは厳密にはシングルトンではありませんが、cc.Schedulerのインスタンスを所有しているのはcc.Directorだけですから、まあシングルトンのようなものですね。
 cc.Schedulerはゲームを支える裏方の中でも特に多忙で、影の主人公といってもいい存在です。cc.Schedulerがどこにあるのか知らなくても、ここまでコードを書きながら読み進めていれば、何度もお世話になっているはずです。

 ノードの中でscheduleUpdateを実行すると、ノードのupdateメソッドが毎割り込み呼び出されるようになりますが、この呼び出しを実際に行っているのがスケジューラです。
 ノードの中でscheduleOnceを呼べば、「何秒後にこれを実行して」とスケジューラに一回限りの処理をお願いすることもできます。

// 3分後に実行するようにスケジューラに依頼する
this.scheduleOnce(function () {
  cc.log("♨(・◎・)ラーメンできたよ");
}, 180);

cc.Scene

 cc.Sceneも何度も出てきましたが、何のためにいるのかよく分からないクラスです。
 もしかしていらないんじゃないかと思って、筆者も調べてみたことがありました。フレームワークソースコード (~/­cocos2d-html5/­layers_­scenes_­transitions_­nodes/­CCScene.js) を読むとその正体が明らかになります。

cc.Scene = cc.Node.extend({
  ctor:function () {
    this._super();
    this._ignoreAnchorPointForPosition = true;
    this.setAnchorPoint(cc.p(0.5, 0.5));
    this.setContentSize(cc.Director.getInstance().getWinSize());
  }
});

cc.Scene.create = function () {
  return new cc.Scene();
};

 これだけです。今までの知識でだいたい読めるでしょう。
 隠れて大事な仕事をしているとか、そういう秘密は何もないのでした。
 新しいシーンを書くときは、いつもテンプレート通りにcc.Sceneとcc.Layerをセットで作ると思うのですが、仕事を全部レイヤーに押しつけて、レイヤーだけが太ったコードになりがちです。なんだか不公平な感じがします。レイヤーがやらなくてもいい仕事は、cc.Sceneにやってもらうことを考えましょう。

cc.Layer

 cc.Layerはこの世界の主人公です。とても有能ですが、便利だからといって雑用を押しつけていると、すぐに大量の仕事を抱え込んで太ってしまうタイプです。
 何か新しい変数が必要になったとき、ついレイヤーの中で宣言したくなります。グローバルは何か使っちゃいけないみたいな気がするし、シーンのプロパティに宣言すると、レイヤーの中からアクセスできないですからね。
 しかし、表示に直接関係がない変数はグローバルで持つのが正解だと思います。レイヤーの中で宣言すると、結局状態の管理もレイヤーが面倒を見ることになって、ずるずるとレイヤーの仕事が増えていきます。

 グローバル変数は、使いすぎなければ便利なものです。

var g = {
  score:0,
  life:3,
  reset:function () {  // 関数も入れておくことができますね
    this.score = 0; 
    this.life = 3; 
  },
  ...
};

 こんな感じで短い名前の「箱」を一つ用意して何でも入れるようにしておけば、グローバル変数を作りすぎて訳がわからなくなることもありません。まあ本当はゲームの状態を保持する自分用のシングルトンを作ればいいんですけど、小規模なゲームならこれで十分です。

 同時に表示するパーツが多いときは、複数のレイヤーで手分けして処理することも検討しましょう。スコアや残機数を画面の上端に常に表示しておく場合、別レイヤーに分離するのはいい考えです。メインになるレイヤーのダイエットに気を使うことで、ゲーム全体が見通しのいい、保守しやすいものになります。


 cc.Layerには妹分のcc.LayerColorとcc.Layer­Gradientもあります。
 スプライトやラベルと同じ感覚でcreateして、手軽に画面に矩形を表示する方法として使えますし、シーンを作るときcc.Layerの代わりにcc.LayerColorやcc.Layer­Gradientを継承すればゲームの背景に簡単に色やグラデーションを入れることもできます。

// 幅300x高さ200の赤い矩形を表示
var square = cc.LayerColor.create(cc.c3b(255, 0, 0), 300, 200);

// ノードの座標が矩形の中心になるように
square._ignoreAnchorPointForPosition = false; 
this.addChild(square);


// 半透明の矩形
var square2 = cc.LayerColor.create(
  cc.c4b(255, 0, 0, 128), 300, 200);

// グラデーション
var square3 = cc.LayerGradient.create(
  cc.c4b(255, 0, 0, 128), 
  cc.c4b(0, 0, 255, 128), 
  cc.p(0, 1));

cc.Sprite

 【スプライト】西洋の妖精。亡霊や精霊を指すこともある。
 Wikipediaによれば、コンピュータの世界に最初にこの言葉を持ち込んだのは1970年代のテキサス・インスツルメンツの技術者のようですが、おかげで現代の私達は、毎日大量の「妖精」を目にして暮らすようになりました。


 スプライトの効率的な使い方さえわかればゲームはできたも同然です。スプライトの表示を最適化する技術についてはちょっと詳しく勉強していきましょう。

cc.TextureCache

 スプライトに使われる画像はだいたい画像ファイルから読み込まないといけないわけですが、重い処理なので何度も同じ画像を読み込んではいけません。Cocos2dでは読み込んだ画像はすべてcc.­Texture­Cacheというシングルトンに自動的にキャッシュされて、2回目に同じ名前のファイルにアクセスする時はメモリ上のキャッシュが使われます。


 普段は表に出てこないcc.TextureCacheですが、以下のようなコードで、メモリに現在読み込まれている画像のリストをコンソールに表示してくれます。

> var textureCache = cc.TextureCache.getInstance();
> textureCache.dumpCachedTextureInfo();

// 出力はこんなかんじ
cocos2d: 'res/ship.png' id=http://localhost:8000/Sample/res/ship.png 150 x 150 Cocos2d-html5-v2.2.1.min.js:24
cocos2d: 'res/enemy.png' id=http://localhost:8000/Sample/res/enemy.png 150 x 150 Cocos2d-html5-v2.2.1.min.js:24
cocos2d: 'res/bg.jpg' id=http://localhost:8000/Sample/res/bg.jpg 800 x 450 Cocos2d-html5-v2.2.1.min.js:24
cocos2d: 'res/bg2.jpg' id=http://localhost:8000/Sample/res/bg2.jpg 800 x 450 Cocos2d-html5-v2.2.1.min.js:24
cocos2d: TextureCache dumpDebugInfo: 4 textures, HTMLCanvasElement for 2988.28125 KB (2.92 MB) Cocos2d-html5-v2.2.1.min.js:24

 Cocos2dでは、キャッシュした画像が勝手に捨てられることはありません。つまり、キャッシュはどんどん大きくなります。使わなくなった画像をメモリから追い出す方法についてはcc.Loaderの項で説明しますが、上のコードでキャッシュの状況を常に確認するようにしておけば安心ですね。

cc.SpriteFrameCache

 現代のハードウェアでは、画像はメモリに展開されたあと、さらにそこからもの凄い速度でGPUに転送されて、そこでもの凄い勢いでスプライトやポリゴンに貼り付けられて表示されます。なんだかよくわからない魔法の世界の話ですが、このへんの魔法を効率よく行うための技術がテクスチャアトラスです。
 【アトラス】天の蒼穹を支えるタイタン族の巨人。妖精の次は巨人ですよ。


 テクスチャアトラスというのは、平たく言うと、たくさんの小さな画像を並べて一枚の大きな画像(テクスチャ)にまとめたものです。普通1024x1024とか、2048x2048とか、2のべき乗のサイズの画像が使われます。
 元になった個々の画像のファイル名と位置をxmlファイル(拡張子は.plist)にまとめて、画像ファイルと対にして使用します。
 今はいいツールができたので、このへんの作業はすっかり自動化されて簡単になりました。

f:id:funige:20131215102753p:plain

 Cocos2dでテクスチャアトラスの面倒を見るのは、cc.­Sprite­Frame­Cacheというシングルトンです。スプライトを使いはじめる前に、テクスチャアトラス(画像ファイルとplistの二つ)をcc.­Sprite­Frame­Cacheに登録しておきます。

// resource.js
var s_atlus = "atlus.png";
var s_atlusPlist = "atlus.plist";

var g_resources = [
  {src:s_atlus},
  {src:s_atlusPlist},
  ...

// sample.js
  init:function () {
    ...
    var cache = cc.SpriteFrameCache.getInstance();
    cache.addSpriteFrames(s_atlusPlist, s_atlus);
    ...

 面倒な処理は全部cc.­Sprite­Frame­Cacheがやってくれるので、スプライトを使うときに考えることは何もありません。テクスチャアトラスを使わないときは以下のようにスプライトを作成したのはおぼえているでしょうか。

var sprite = cc.Sprite.create("ship.png");

 テクスチャアトラスからスプライトを作る時は、createの代わりにcreate­With­Sprite­Frame­Nameを使います。

var sprite = cc.Sprite.createWithSpriteFrameName("ship.png");


 テクスチャアトラスを使ったスプライトアニメーションを作るときは、キャッシュからget­Sprite­Frameメソッドでフレームを取り出してアニメーションに登録します。前章で紹介したme0.pngとme1.pngを交互に表示するコードは、以下のようになります。

var cache = cc.SpriteFrameCache.getInstance();

var animation = cc.Animation.create();
animation.addSpriteFrame(cache.getSpriteFrame("me0.png"));
animation.addSpriteFrame(cache.getSpriteFrame("me1.png"));
animation.setDelayPerUnit(0.1); // 0.1秒ずつ表示
var action = cc.Animate.create(animation);

var me = cc.Sprite.create();
me.runAction(cc.RepeatForever.create(action));
TexturePackerの使い方

 テクスチャアトラスを作るときに使うのがTexture­Packer (http://­www.­codeandweb.­com/­texture­packer) というツールです。有料(4000円ぐらい)ですがとても便利なので、ここではTexture­Packerを使ってテクスチャアトラスを作る手順を紹介しておきます。

f:id:funige:20131215102936p:plain

 起動したらまず左上の方にあるOutputの「Data Format」を「cocos2d」にしておきます。起動するたびに直すのは面倒なので「File → Save Defaults」でデフォルトの設定として保存しておいてください。
 画像ファイルを一つのフォルダにまとめておき、Texture­Packerの右側の「Sprites」と書いてあるところにそのフォルダをドラッグしてください。あとは上段のPublishボタンを押して、名前をつけて保存するだけです。拡張子が.pngと.plistの二つのファイルができるので、二つともプロジェクトのresフォルダにコピーします。


 TexturePackerにはコマンドライン版もあります。TexturePackerのメニューから「Install Command Line Tool」を選択して、指示に従ってインストールしてください。ターミナルやスクリプトから簡単にテクスチャアトラスの作成を行うことができます。

#hogeフォルダの画像を使ってテクスチャアトラスを作り
#hoge.plistとhoge.pngに保存する

$ TexturePacker --format cocos2d --data hoge.plist --sheet hoge.png hoge/*.png

 大量の画像を使うゲームでは画像の修正が頻繁に発生します。そのたびにTexture­Packerを起動してGUIを使っていたら時間もかかるし、ミスも増えます。スクリプト一発で更新できるようにしておくべきでしょう。

cc.SpriteBatchNode

 ゲームに登場するスプライトをすべて1枚のテクスチャアトラスにまとめたら、表示するときは必ずcc.­Sprite­Batch­Nodeを使いましょう。各スプライトの描画前に行われているテクスチャの読み込みが省略されるので、大量のスプライトを描画するときスピードに大きな差がつきます。
 使い方はとても簡単で、直接レイヤーにaddChildしていたスプライトを、cc.­Sprite­Batch­Nodeを間に挟んでaddChildするように書き換えるだけです。

this.addChild(sprite1);
this.addChild(sprite2);
...

 これを以下のように書き換えます。

var batch = cc.SpriteBatchNode.create(s_atlus);
this.addChild(batch);

batch.addChild(sprite1);
batch.addChild(sprite2);
...

 まあ正直に言うと、あまり速度が変わらないと思うのですが、とにかく挟んでおきましょう。プログラマたるもの、どんな環境にも最高のパフォーマンスが出るように作っておかなければいけません。

cc.LabelTTF

 「はじめてのゲーム」の章では、cc.­LabelTTFに大活躍してもらいました。説明は不要だと思いますが、TTFというのはTrueType Fontの略です。どんなサイズのフォントでも、アンチエイリアスを使ってきれいに描画してくれます。スコア表示に使えるのはもちろん、簡単な説明文を画面に表示するときにもcc.­LabelTTFは欠かせません。
 cc.­LabelTTFの欠点はちょっと動作が重いことと、端末によって使えるフォントが違うことです。もちろん、すべての環境でチェックすることはできません。多少フォントが違っても大事な表示が画面からはみ出して消えてしまったりしないように、余裕を持ってレイアウトを考える必要があります。

cc.LabelBMFont

 TrueType Fontの代わりにビットマップフォントを使って文字を描画するのが、cc.­Label­BMFontです。BMFontというのは元々はブラジルのAndreas Jönssonさんが無料公開しているWindows用のビットマップフォント作成ツールの名前ですが、今ではビットマップフォントの標準フォーマットとしてゲーム業界で広く使われています。

bmGlyph

 Mac OS Xには残念ながら無料のいいツールがないので、ここではMac App Storeで入手できるbmGlyph (http://­www.­bmglyph.­com/) というツールを使ってフォントを作成してみます。

f:id:funige:20140215064838p:plain

 左のペインでフォントやサイズなどを適当に選択してフォントをデザインしたら、画面上段のPublishボタンを押して適当なファイル名をつけてPublishしてください。
 画面左下に見えるテキストボックスに含まれる文字だけがフォント化されます。日本語で使う文字すべてをビットマップで持つことはできませんから、画面に出力する予定のメッセージをこのテキストボックスにペーストして、実際に使われている文字だけがフォント化されるようにします。

 ビットマップフォントは、テクスチャアトラスととてもよく似ています。テクスチャアトラスは.plistと.pngの二つのファイルを組にして使いましたが、ビットマップフォントが使うのは.fntと.pngの二つのファイルです。テクスチャアトラスの時と同じようにプロジェクトのresフォルダにコピーして、resource.jsで読み込まれるようにします。

// resource.js
var s_font = "font.png";
var s_fontFnt = "font.fnt";

var g_resources = [
  {src:s_font},
  {src:s_fontFnt},
  ...

// sample.js
  init:function () {
    ...
    var helloLabel = cc.LabelBMFont.create("Hello BMFont!", s_fontFnt);
    this.addChild(helloLabel);

 

cc.ParticleSystem

 Cocos2dには多くのクラスがありますが、中でもいちばん個性的なのがcc.ParticleSystem(とその仲間)です。とにかく見た目が派手で、あっという間に数百枚のスプライトを作ってゲームを死ぬほど重くしても知らん顔です。
 ParticleDesigner (http://­www.­71squared.­com/­ja/­particle­designer) というツールを使うと、他のユーザーが作ったエフェクトを下敷きにして、炎でも爆発でも簡単に作ることができます。個人的には、ちょっと値段が高いと思うんですけどね。

f:id:funige:20131215103220p:plain

 Cocos2dには最初から無料で使える組み込みのエフェクトもたくさん用意されているので、ここではこちらを紹介します。


 組み込みのエフェクトを使うときは、パーティクルの表示に何か画像が必要です。何でもいいのですが、煙みたいな形の32x32とか64x64ぐらいの小さな画像を用意してください。ここでは、s_smokeという名前で、resource.jsで先読みしたことにします。

  init:function () {
    ...
    var textureCache = cc.TextureCache.getInstance().
    var particles = cc.ParticleExplosion.create();
    particles.setTexture(textureCache.textureForKey(s_smoke);
    this.addChild(particles);
  }

 cc.ParticleExplosionの他にも、多くのエフェクトが用意されています。名前からどんなエフェクトか想像できるでしょうか。

  • cc.ParticleFire
  • cc.ParticleFireworks
  • cc.ParticleFlower
  • cc.ParticleGalaxy
  • cc.ParticleMeteor
  • cc.ParticleRain
  • cc.ParticleSmoke
  • cc.ParticleSnow
  • cc.ParticleSpiral
  • cc.ParticleSun

cc.AudioEngine

 個性的と言う意味では、cc.AudioEngineもかなり特異な存在です。BGMや効果音を一手に引き受ける重要なシングルトンですから、これが無ければゲームになりません。

  var audioEngine = cc.AudioEngine.getInstance();

  // 効果音の再生
  audioEngine.playEffect(s_explosion06);

  // BGMの再生(2番目の引数はループするかどうかのフラグ)
  audioEngine.playMusic(s_cyber07, true);

  // BGMの停止
  audioEngine.stopMusic(s_cyber07);

 ブラウザのキャッシュがオフになっていると、最初の一回だけ音が出て二回目以降何も出ないことがあります。デバッグのためにキャッシュを無効にしている場合は、有効にしてもう一度リロードしてみてください。


 ついでにミュート(消音)機能の実装を考えておきましょう。スマートホンのアプリでは、音が突然鳴り出すとユーザーに怒られてしまいますので、消音機能は必須と言えます。

  // ゲームの最初に前回の設定を復元する
  initMute:function () {
    var mute = parseInt(sys.localStorage.getItem("mute") || 1);
    setMute(mute);
  },

  // ミュートの状態が変わったときに呼ぶ
  setMute:function (mute) {
    var volume = (mute) ? 0 : 1;
    cc.AudioEngine.getInstance().setEffectsVolume(volume);
    cc.AudioEngine.getInstance().setMusicVolume(volume);
    sys.localStorage.setItem('mute', (mute) ? 1 : 0);
  },

cc.Menuとcc.MenuItem

 ミュートボタンなんかがいい例なのですが、ボタンをタイトル画面に複数配置したいときに手軽に使えるのがcc.Menuです。
 個々のボタンはcc.MenuItemのサブクラスになります。スプライトを使ったボタンはcc.­Menu­Item­Image、文字を使ったボタンならcc.­Menu­Item­Fontです。Cocos2dのメニューは、個々のボタンを画面のどこに置いても構いません。各ボタンは普通のノードなので、子ノードでもアクションでも何でも追加できます。「ボタン」と言うよりは、タッチに反応する特別なスプライトやラベルと考えていいと思います。

  init:function () {
    ...
    var muteItem = cc.MenuItemImage.create(
      s_MuteNormal, 
      s_MuteSelected, 
      this.menuTapped, this);
    muteItem.setTag(0);
    muteItem.setPosition(cc.p(100, 100));

    var startItem = cc.MenuItemImage.create(
      s_StartNormal, 
      s_StartSelected, 
      this.menuTapped, this);
    startItem.setTag(1);
    startItem.setPosition(cc.p(100, 150));
        
    this.menu = cc.Menu.create(muteItem, startItem);
    this.addChild(this.menu);
  },

 MenuItemImageに使う画像も、必ずresource.jsで先読みして下さい。

 ボタンがタップされたとき通知を受け取るハンドラは、以下のようになります。ここでは、タグを使ってどのボタンが押されたか判別しています。

  menuTapped:function (sender) {
    switch (sender.getTag()) {
    case 0:
      cc.log("mute button pressed");
      break;
    case 1:
      cc.log("start button pressed");
      break;
    }
  },

 ミュートボタンには現在オンなのかオフなのかわかるように、状態に応じて違う画像を表示するべきです。初期化や状態が変わるタイミングで以下のようにボタン画像を書き換えてしまいましょう。

  // muteの状態が変わった時に呼ぶ
  updateMuteButton:function (mute) {
    var menuItem = this.menu.getChildByTag(0);
    var normalImage = (mute) ? s_MuteNormal : s_MuteSelected;
    muteItem.setNormalImage(cc.Sprite.create(normalImage));
  },

cc.Loader

 cc.Loaderは小さなゲームを作っている間は使う機会がないかも知れませんが、ステージが増えてすべての画像をメモリに持つことができなくなったとき、とても頼りになるシングルトンです。

 今までのサンプルでは、リソースをresource.jsのg_resoucesというひとつの配列に追加していましたが、量が多いときはステージ毎に例えばg_stage0、g_stage1、…と用意して、ステージを開始する直前にcc.Loader.­preload()またはcc.Loader.­preload­Async()を使って読み込むようにします。

// resource.js
// 各ステージのリソース
var g_stage0 = [{src:"res/stage0.png"}, {src:"res/stage0.plist"}];
var g_stage1 = [{src:"res/stage1.png"}, {src:"res/stage1.plist"}];
...

// 全ステージ共通のリソース
var g_resources = [
...

 例えば、Titleシーンでステージを選択して、Gameシーンでゲーム表示する場合、シーンを切り替える際のコードはこんな感じに書けるでしょう。もし前のステージのテクスチャが残っていても、以下のような手順で解放できます。

// title.js
Title = cc.Layer.extend({
  stages:[ g_stage0, g_stage1, ... ],
  ...
  // ステージnoが選択されたとき
  stageSelected:function (no) {
    var spriteFrameCache = cc.SpriteFrameCache.getInstance();
    var textureCache = cc.TextureCache.getInstance();

    if (Title.currentStage) {
      // 前のステージのデータが残っていたら解放する
      spriteFrameCache.removeSpriteFramesFromFile(
        Title.currentStage[1].src);
      textureCache.removeUnusedTextures();
    }

    // 新しいステージのデータを読み込む
    Title.currentStage = this.stages[no];
    cc.Loader.preload(Title.currentStage, function () {
      spriteFrameCache.addSpriteFrames(
        Title.currentStage[1].src, 
        Title.currentStage[0].src);

      // ちゃんと読み込み・解放できたか確認しておこう
      textureCache.dumpCachedTextureInfo();

      // ステージ開始
      cc.Director.getInstance().replaceScene(new GameScene());
    }, this);
  },
  ...
});

Title.currentStage = null;
...

cc.Application

 Cocos2d-html5は様々な環境で動くのが売りですが、動作環境で場合分けして違うコードを実行させたいときもあります。そういうときはcc.Applicationで実行環境を知ることができます。
 残念ですが、このへんはCoco2d-xとCocos2d-html5で微妙に仕様が異なっているので、両方で動くようにうまく書かなければいけません。

if (cc.TARGET_PLATFORM == undefined) {
  // Cocos2d-xで「定義されてないよ」とか言われるので
  cc.TARGET_PLATFORM = {
    ANDROID:3,
    IPHONE:4,
    IPAD:5,
    MOBILE_BROWSER:100,
    PC_BROWSER:101
  };
}

// 動作環境を取得
var platform = cc.Application.getInstance().getTargetPlatrom();

switch (platform) {
case cc.TARGET_PLATFORM.PC_BROWSER:
  // ブラウザ
  break;

case cc.TARGET_PLATFORM.ANDROID:
  // Android
  break;

case cc.TARGET_PLATFORM.IPHONE:
case cc.TARGET_PLATFORM.IPAD:
  // iOS
  break;
}

 言語の場合分けも同じです。うーん。ここもちょっと違う。このへんの違いは今後のバージョンで修正されるんじゃないかと思います。

// 言語を取得
var language;
if (cc.Application.getCurrentLanguage) {
  // Cocos2d-html5
  language  = cc.Application.getCurrentLanguage();
} else {
  // Cocos2d-x
  language  = cc.Application.getInstance().getCurrentLanguage();
}

switch (language) {
case cc.LANGUAGE_JAPANESE:
  // 日本語
  break;

default:
  // それ以外
  break;
}

 ゲーム中のメッセージなどを多国語対応するには、ゲームの最初でユーザーの使っている言語を取得して、使う文字列を切り替えるようにします。

var strings = {
  en: {
    HelloWorld:"Hello World!",
    Start:"Start",
  },
  ja: {
    HelloWorld:"やあ(・ω・)",
    Start:"はじめる",
  },
}

// ここは短い名前の関数がいいですね
var _ = (language == cc.LANGUAGE_JAPANESE) ? strings.ja : strings.en;

cc.log(_.HelloWorld); // 現在の言語でhelloWorldを表示

cc.Scale9Sprite

 Scale9Spriteはボタンの画像などによく使うストレッチ可能なスプライトです。
 これもMenuItemと同じで、resource.jsで画像をpreloadしておかないと何も表示されませんので注意してください。

// resource.js
var s_me = "res/me.png";
...
var g_ressources = [
  {src:s_me},
  ...
];

// game.js
var sprite = cc.Scale9Sprite.create(s_me);
this.addChild(sprite);
sprite.setContentSize(cc.size(400, 300)); // 好きなサイズに引きのばす

 何も指定しなければ、画像は高さと幅をそれぞれ3等分されて、真ん中のブロックが引き延ばされます。

f:id:funige:20131021111954p:plain

 画像を9等分したくない場合、例えば中央の1x1ピクセルだけを引き延ばしたいなら、createの第3引数で引き延ばす領域を指定します。この画像は96x128なので、中央のピクセルの座標は (48, 64) になります。

f:id:funige:20131021112009p:plain

cc.EditBox

 Scale9SpriteやこのEditBoxのソースコードは、~/­cocos2d-html5/­extensions/というフォルダの下にあります。Cocos2dの「拡張機能」という扱いですね。実際にゲームを開発したときに「ああこの機能がCocos2dには足りない」と思って誰かが付け足したものなのでしょう。extensionsの下にあるクラスはちょっと癖がありますが、とても実践的で役に立つものばかりです。


 cc.EditBoxはキーボードで文字列を入力する機能を実装するときとても便利なクラスです。cc.EditBoxのインスタンスを作るときは、縦横のサイズの他に、背景に貼るScale9Spriteを指定します。

  // EditBox
  var bg = cc.Scale9Sprite.create("res/bg.png");
  var editBox = cc.EditBox.create(cc.size(200, 50), bg);
  editBox.setPosition(cc.p(240, 160));
  editBox.setDelegate(this);
  this.addChild(editBox);

 サンプルとして、入力した式を評価して出力する簡単な電卓を作ってみましょう。まず結果を出力するラベルを用意します。

  // answer
  var answer = cc.LabelTTF.create("", "Arial", 32);
  answer.setPosition(cc.p(240, 60));
  this.addChild(answer);
  this.answer = answer;

 EditBoxの内容が変化したときは、setDelegateで指定したオブジェクト(this)にメッセージが送られます。

  // cc.EditBoxDelegate
  editBoxReturn:function (sender) {
      this.answer.setString(eval(sender.getText()));
  },

 10行あまりのコードですが、ちゃんとEditBoxの位置がタップされたかどうか調べて、タップされたら(モバイルデバイスなら)キーボードを表示してくれます。これは便利ですね。

f:id:funige:20131213181712p:plain

広告を非表示にする