カテゴリー別アーカイブ: cocos2d-x v3

cocos2d-x v3 > 画像を動的に生成

cocos2d-x で高速に動作する Conway’s Game Of Life (ライフゲーム)を作ろうと思い、画像を動的に生成する方法を調べてみた。

動的に画像を生成するには DrawNode::drawDot() を使うと簡単だ。
が、drawDot() は大きさを持ったドットを三角形の組み合わせに分解し、その頂点座標と塗りつぶし色を OpenGL に送るので、Game Of Life 用としては無駄が多く、パフォーマンス的に問題だ。

よって、矩形スプライトを作成し、そのテクスチャパターンを動的に生成する方がよい。

宣言ファイル:

 std::vector<Color3B> m_buffer;  //  テクスチャデータを格納する動的配列
 cocos2d::Texture2D *m_texture;
 cocos2d::Sprite *m_sprite;

実装ファイル:

const int WIDTH = 800;
const int HEIGHT = 1000;
m_buffer.resize(WIDTH*HEIGHT);
for(int i = 0; i != m_buffer.size(); ++i) {
    m_buffer[i].r = 赤色値;
    m_buffer[i].g = 緑色値;
    m_buffer[i].b = 青色値;
}
Size contentSize;
contentSize.width  = WIDTH;
contentSize.height = HEIGHT;
m_texture = new Texture2D();
m_texture->initWithData((const void *)&m_buffer[0], WIDTH*HEIGHT*sizeof(Color3B),
			Texture2D::PixelFormat::RGB888, WIDTH, HEIGHT, contentSize);
m_sprite = Sprite::createWithTexture(m_Texture);
m_sprite->setPosition(位置);
addChild(m_sprite);

Nexus 7 (2013) 800×1000 グリッドサイズの場合、DrawNode::drawDot() を使った場合は数FPSだったのが、上記の方法により約30FPSを実現するのに成功した。

cocos2d-x v3>カードをめくるアニメーション

裏と表の画像を用意し、裏の画像を RotateBy(秒数, 90) し、そこで画像を切りかえ、-90度回転した状態から90度回転するとよい。

    auto card = Sprite::create(裏画像);
    card->setPosition(位置);
    addChild(card);
    auto rot = RotateBy::create(5, Vec3(0, 90, 0));
    auto callFunc = CallFunc::create([this, card](){	//	card が90度回転した時点で呼ばれる
        card->setTexture(表画像);
        card->setRotation3D(Vec3(0, -90, 0));
        auto rot = RotateBy::create(5, Vec3(0, 90, 0));
        card->runAction(rot);
    });
    card->runAction(Sequence::create(rot, callFunc, NULL));

動画:

cocos2d-x ver 3.5 をVSC2013でビルド

某氏の生放送を見に行ったら、彼が cocos2d-x 3.5 のセットアップをすいすいと行っていた。
3.4 にはバグがあって、スクロールがうまくいかないとのことであった。
公式サイトをチェックするといつの間にか v3.5 がリリースされていたので、おいらもダウンロード&VSC2013 でビルドしてみた。

ダウンロードした cocos2d-x-3.5.zip を展開し、cocos2d-x-3.5/build/cocos2d-win32.vc2012.sln をVSC2013で開いて、F7 を押してビルドする。
ただし、例によってテストプログラムで定数が二行につづいているというエラーが出るので、cocos2d-x-3.5/tests/cpp-tests/Classes/LabelTest/LabelTestNew.cpp を さくさくエディタ 等で開き、BOM付きUTF-8 で上書き保存し、ビルドするように。

ビルドが終わったら、F5 を押して実行すると、以下のようなテストプログラムが起動する。

temp

 

テストプロジェクトを実行すると、上記の画面があらわれる。
メニューを選択すると、各機能のデモが実行される。
なお、画面の下の方のメニューを表示するには、画面をタップしてスクロールさせるといいぞ。

3.5 の目玉は3次元のパーティクルの様だ。
参照: COCOS2D-X V3.5 RELEASED

以下は、パーティクル3Dの例。

temp

静止画だと3Dであることがわかりづらいが、イナズマがきらめいているデモだ。
また、たくさんのバグがフィックスされているとのこと。

cocos2d-x > AdMob インターステンシャル広告

本稿では、cocos2d-x アプリに AdMob のインターステンシャル広告(以下、「全面広告」と記述する)を実装するための方法を記述します。
最初に書いておきますが、この方法は数々の素晴らしいアプリをリリースされている愛ふぉん太郎氏にご教授していただいた方法です。
ほんとうにありがとうございました。

手順は以下のとおりです。

1. AdMob 管理画面からインターステンシャル広告のIDを取得しておく
2. 全面広告を表示する Java の関数を実装する
3. 上記 Java 関数をコールする JNI インタフェース関数を実装する
4. 上記のインタフェース関数をコールする。

以下、詳細を説明する。ただし、1. の詳細は省略する。

2. 全面広告を表示する Java の関数を実装する
“AppActivity.java” を以下のように修正。

public class AppActivity extends Cocos2dxActivity {
    private static AppActivity thisptr = null;
    static InterstitialAd interstitialAd;
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState); 
        thisptr = this;
        .....
    }
    public static void launchInterstitial(){
      thisptr.runOnUiThread(new Runnable() {
      @Override
      public void run() {
        //ロード
        interstitialAd = new InterstitialAd(thisptr);
        interstitialAd.setAdUnitId("ca-app-pub-XXXXXXXXXXXXXXXX/XXXXXXXXXX");
        AdRequest adRequestI = new AdRequest.Builder().build();
        interstitialAd.loadAd(adRequestI);
        interstitialAd.setAdListener(new AdListener() {
          @Override
          public void onAdLoaded() {
            interstitialAd.show();
    	  }
    	});
      }
      });
    }
}

3. 上記 Java 関数をコールする JNI インタフェース関数を実装する

class InterfaceJNI
{
public:
  static void launchInterstitial();
};
void InterfaceJNI::launchInterstitial()
{
    JniMethodInfo jmInfo;
    if(JniHelper::getStaticMethodInfo(jmInfo,
        JNICLASSNAME,
        "launchInterstitial",
        "()V"))		//	void ()
    {
        jmInfo.env->CallStaticVoidMethod(jmInfo.classID, jmInfo.methodID);
        jmInfo.env->DeleteLocalRef(jmInfo.classID);
    }
}

4. 上記のインタフェース関数をコールする。

InterfaceJNI::launchInterstitial(); を記述するだけ。

 

cocos2d-x > 矩形スプライトを作る

矩形スプライトを作って配置する場合、その画像を予め作っておき、

auto sprite = Sprite::create("rect.png");

と書いてもいいのだが、以下のように create() でスプライトを
生成し、setTextureRect(const Rect&) で矩形を生成することも可能だぞ。

auto sprite = Sprite::create();
sprite->setTextureRect(Rect(0, 0, 100, 200));

矩形の色を指定する場合は setColor(const Color3B&) を、
透明度を指定する場合は setOpacity(GLubyte) をコールするといいぞ。

sprite->setColor(Color3B(0, 255, 128));
sprite->setOpacity(128);

cocos2d-x v3 > adMob バナー広告を表示

・ AdMob ID を取得する
・ google-play-service SDK を組み込む
・ class AppActivity を以下のように修正する。

import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.net.Uri;
import android.view.Gravity;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.Toast;

import com.google.android.gms.ads.*;

public class AppActivity extends Cocos2dxActivity {
	private static AdView adView;
    private final int lp = LinearLayout.LayoutParams.WRAP_CONTENT; 
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState); 
        
        adView = new AdView(this);
        adView.setAdSize(AdSize.BANNER);	//	320x50	標準のバナー
        adView.setAdUnitId("ca-app-pub-XXXXXXXXXXXXXXXX/XXXXXXXXXX");  //  AdMob ID
         
        FrameLayout.LayoutParams adParams = new FrameLayout.LayoutParams(lp,lp);
        adParams.gravity = (Gravity.BOTTOM|Gravity.CENTER); 
        addContentView(adView, adParams);
         
        AdRequest adRequest = new AdRequest.Builder().build();
         
        adView.loadAd(adRequest);
    }
}

・マニュフェストに以下を追加する

 <meta-data android:name="com.google.android.gms.version"
 android:value="@integer/google_play_services_version"/>

 <activity android:name="com.google.android.gms.ads.AdActivity"
 android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize" />

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

※ v3.4 にアップデータとしたら、cocos compile でビルドすると、configChanges に screenSize を指定すると、ビルドエラーが出るようになった。
これを解決するには project.properties の target を 13 以上にするとよい。
この解決方法は 愛ふぉん太郎 氏にご教授していただいた。あらためて御礼申し上げます。

cocos2d-x v3 Android > パッケージを追加する

cocos2d-x Android 版に AdMob を追加するときなどは、プロジェクトにパッケージを追加しなくてはいけない。
そうでないと、ビルド時に「パッケージcom.google.android.gms.adsは存在しません」みたいなエラーが発生する。

以下は Eclipse を使って、プロジェクトに google-play-services パッケージを追加する方法
[1] Eclipse でプロジェクトをインポートする
プロジェクト/proj.java を指定するといいぞ。
[2] プロジェクトのプロパティを開き、左のリストから Android を選び、右下の【Add…】を押す

temp

[3] google-play-services SDK が既にインストールされていれば、一覧に表示されるそれを選択して【OK】を押す

temp

以上でビルドエラーは出なくなるはずだ。

で、この情報はいったいどこに格納されているのか。。。謎だ

 

cocos2d-x v3 > BGM

BGMを再生するには、以下のようにするといいぞ

  1. resource 下に mp3 ファイルを入れる
  2. #include “SimpleAudioEngine.h” を書く
  3. CocosDenshion::SimpleAudioEngine::getInstance()->preloadBackgroundMusic( “BGMファイル名.mp3” ); で、BGMをプリロードしておく
  4. CocosDenshion::SimpleAudioEngine::sharedEngine()->setBackgroundMusicVolume(ボリューム); でボリュームを指定。ボリュームは、範囲 [0, 1] の float 型
  5. CocosDenshion::SimpleAudioEngine::sharedEngine()->playBackgroundMusic("BGMファイル名.mp3", true); で再生開始。第2引数はループ再生するかどうかのフラグ
  6. CocosDenshion::SimpleAudioEngine::sharedEngine()->stopBackgroundMusic(); で再生停止

 

cocos2d-x v3 > 効果音

効果音を再生するには、以下のようにすればよろし

[1] resource 下に mp3 ファイルを入れる
[2] #include “SimpleAudioEngine.h” を書く
[3] CocosDenshion::SimpleAudioEngine::getInstance()->playEffect(“ファイル名.mp3”); を書く

いじょ

cocos2d-x v3 > JNI

JNI を使って、C++ から Java のスタティック関数を呼び出すには、以下の2つの作業を行う。

  1. Java でクラスとスタティック関数を追加する
  2. java 関数をコールするためのC++関数を追加

以下、上記の内容を詳しく説明する。

1. Java でクラスとスタティック関数を追加する

スタティック関数をお手軽に追加するのでよければ、proj.android/src/org/cocos2dx/cpp/AppActivity.java に追加する

.....
import org.cocos2dx.lib.Cocos2dxActivity;
public class AppActivity extends Cocos2dxActivity {
    // ← ここにスタティック関数を追加
}

例えば、ビープ音を鳴らす関数 beep() を追加したいのであれば、以下のように記述する

.....
import org.cocos2dx.lib.Cocos2dxActivity;
import android.media.AudioManager;
import android.media.ToneGenerator;
public class AppActivity extends Cocos2dxActivity {
  static void beep()
  {
      ToneGenerator toneGenerator = new ToneGenerator(AudioManager.STREAM_SYSTEM,
                                                      ToneGenerator.MAX_VOLUME);
      toneGenerator.startTone(ToneGenerator.TONE_PROP_BEEP);
  }
}

2. java 関数をコールするためのC++関数を追加

JNI をコールするラッパーをC++で定義するといいだろう。
ここでは、interfaceJNI.h, interfaceJNI.cpp を作成し、プロジェクトに追加するものとする。
別ファイルにするのではなく、既存のファイルに以下のコードを追加してもよい。

“interfaceJNI.h” では、ラッパー関数の宣言を行う。
先の例の beep() を呼び出す関数 beep() を宣言する。
必須ではないが、この例では名前空間を JNI としておく。

namespace JNI {
  void beep();
};

“interfaceJNI.cpp” では、JNI::beep() の実装を行う。
その前に以下のおまじないを書いておく。
最後の JNICLASSNAME は、先に java 側で書いた beep 関数のパッケージ名を示す。
先の beep() は AppActivity 内のスタティック関数として定義したが、別のパッケージを作りその中で定義した場合は、当然クラス名を変更する必要がある。

#include "interfaceJNI.h"
#include "platform/android/jni/JniHelper.h"
#include 
// 呼び出すメソッドが書かれているパッケージ名とjavaクラス名
#define JNICLASSNAME "org/cocos2dx/cpp/AppActivity"

ついで、実装は以下のようになる。

namespace JNI {
  void beep()
  {
    JniMethodInfo methodInfo;
    if(JniHelper::getStaticMethodInfo(methodInfo, JNICLASSNAME, "beep", "()V")){
      methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID);
      methodInfo.env->DeleteLocalRef(methodInfo.classID);
    }
  }
}

JniMethodInfo は JNI メソッド情報構造体である。
そのオブジェクトを生成し、JniHelper::getStaticMethodInfo() でメソッドを検索する。
第1引数に JNI メソッド情報構造体を、第2引数にJNIクラス名を、第3引数に Java スタティック関数名を、第4引数に関数引数と返り値型のシグネチャを指定する。
今回の beep() は引数無し、戻り型は void なので “()V” と指定している。
※ シグネチャに詳細については後述する。
そして、methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID); で JNI 経由で java 関数を呼び出し、methodInfo.env->DeleteLocalRef(methodInfo.classID); でメモリのクリーンナップを行う。

以上が定義できていれば、プロジェクトの任意の位置から、以下のコードで java 関数を呼び出すことが出来る。

#include "interfaceJNI.h"
.....
  JNI::beep();

・int 型のメソッドを呼ぶ場合は CallStaticIntMethod() を使用する
・関数引数は、第3引数以降に指定する

例として int add(int a, int b) を呼ぶ場合は、以下のように記述する。

  int add(int arg1, int arg2) {
    JniMethodInfo methodInfo;
    if(JniHelper::getStaticMethodInfo(methodInfo, JNICLASSNAME, "add", "(II)I")){
      int rv = methodInfo.env->CallStaticIntMethod(methodInfo.classID, methodInfo.methodID, arg1, arg2);
      methodInfo.env->DeleteLocalRef(methodInfo.classID);
      return rv;
    } else
      return -1;
  }