twitter連携
ios
http://youngforever.hatenablog.com/entry/2014/05/31/150452
上記を参考に実装
android
twitter4jによるツイート実装もあるが、認証が面倒でツイートしてくれなそうなので
シンプルに公式アプリに対してIntentを送る事にした。
(公式アプリを探す処理が入ってるので若干長い)
public class TwitterTool { public static int REQUEST_CODE = 777; public static void share(String paramsString) { // パラメータをjson文字列形式で貰ってる Log.d("TwitterTool", "paramsString:" + paramsString); Map<String, String> params = JSON.decode(paramsString); AppActivity activity = (AppActivity) AppActivity.getContext(); Intent tweetIntent = new Intent(Intent.ACTION_SEND); tweetIntent.putExtra(Intent.EXTRA_TEXT, params.get("text")); //tweetIntent.setType("text/plain"); // resフォルダ内の画像ファイルも追加する tweetIntent.setType("image/*"); String fileName = "android.resource://" + activity.getPackageName() + "/" + R.raw.twitter_share; Uri uri = Uri.parse(fileName); tweetIntent.putExtra(Intent.EXTRA_STREAM, uri); // twitter公式アプリのみを探す処理 PackageManager packManager = activity.getPackageManager(); List<ResolveInfo> resolvedInfoList = packManager.queryIntentActivities(tweetIntent, PackageManager.MATCH_DEFAULT_ONLY); boolean resolved = false; for(ResolveInfo resolveInfo: resolvedInfoList){ if(resolveInfo.activityInfo.packageName.startsWith("com.twitter.android")){ tweetIntent.setClassName( resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name ); resolved = true; break; } } if(resolved){ activity.startActivityForResult(tweetIntent, REQUEST_CODE); }else{ Toast.makeText(activity, "最新のtwitter公式アプリが必要です", Toast.LENGTH_LONG).show(); } } public static void successCallback() { // ここでツイート成功処理 } }
MainActivity
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if(requestCode == TwitterTool.REQUEST_CODE){ Log.d("AppActivity", "onActivityResult get twitter requestCode resultCode:" + resultCode); // ツイート成功は-1っぽい if(resultCode == -1){ TwitterTool.successCallback(); } } }
Cocos2d-JS Facebook連携 作業メモ
作業メモです
ios
基本的な流れ
http://cocos2d-x.org/docs/manual/framework/html5/facebook-sdk/facebook-sdk-on-ios/en
facebookアプリ登録
https://developers.facebook.com/docs/ios/getting-started/#appid
ハマった点
http://forums.xamarin.com/discussion/24656/error-running-app-after-upgrading-xamarin
上記のエラーが出るようになってしまったけど、よくわからないので放置。
アプリ毎に変わるところ
facebookアプリ登録
FacebookAppID, FacebookDisplayName, URL types in Info.plist file
を変更
android
基本的な流れ
http://www.cocos2d-x.org/docs/manual/framework/html5/facebook-sdk/facebook-sdk-on-android/en
facebookアプリ登録
https://developers.facebook.com/docs/android/getting-started?locale=ja_JP
ハマった点
3.1から指定方法が変更になり、下記のようにしないとコンパイルできなかった
(誤)LOCAL_WHOLE_STATIC_LIBRARIES += jsb_pluginx_static
(正)LOCAL_STATIC_LIBRARIES += jsb_pluginx_static
アプリ毎に変わるところ
facebookアプリ登録
app_id
を変更
Cocos2d-JSでflappy bird
某有名フラッピーバードのロジック部分をCocos2d-JSで再現してみました。
コードはあんま綺麗じゃないかもしれないですが、 C++で書くよりだいぶシンプルに書けるというのが伝わればいいと思います。
- 画面サイズは720×1280
- underscorejsを使用しています。
実際の動き
シーンのコード
var createPlayer = (function(){ var player = { // 重力 GRAVITY_Y_FORCE: -3, // ジャンプ力 JUMP_Y_FORCE: 25, // y方向に向かう力 y_force: 0, update: function(dt) { // y方向の移動 this.y += this.y_force; // 重力を加算 this.y_force += this.GRAVITY_Y_FORCE; }, jump: function() { // ジャンプの分 Y方向を加算 this.y_force = this.JUMP_Y_FORCE; } }; return function () { // playerにあたるスプライトを生成 var sprite = new cc.Sprite(); sprite.setTextureRect(cc.rect(0, 0, 100, 100)); sprite.color = cc.color(255, 0, 0, 128); // 初期位置 sprite.x = 300; sprite.y = 640; // spriteに属性を追加 _.extend(sprite, player); return sprite; } })(); var createEnemy = (function(){ var enemy = {}; return function (y_under, y_top) { // 敵(土管)にあたるスプライトを生成 var sprite = new cc.Sprite(); sprite.color = cc.color(0, 255, 0, 128); sprite.x = 900; // 画面外にセット sprite.y = (y_top + y_under) / 2; sprite.setTextureRect(cc.rect(0, 0, 100, (y_top - y_under))); // spriteに属性を追加 _.extend(sprite, enemy); return sprite; } })(); var createScorePoint = (function(){ var scorePoint = {}; return function (y_under, y_top) { // スコア獲得地点にあたるスプライトを生成 var sprite = new cc.Sprite(); sprite.color = cc.color(0, 0, 255, 30); sprite.x = 900; // 画面外にセット sprite.y = (y_top + y_under) / 2; sprite.setTextureRect(cc.rect(0, 0, 20, (y_top - y_under))); // spriteに属性を追加 _.extend(sprite, scorePoint); return sprite; } })(); READY = 1; START = 2; OVER = 3; var ActionLayer = cc.Layer.extend({ // ゲームの状態 gameMode: READY, // ゲームの状態を表すテキスト labelGameMode: null, // スコア score: 0, // スコアを表すテキスト labelScore: null, // 敵(土管)スピード ENEMY_X_SPEED: -10, // 敵(土管)の隙間 ENEMY_Y_SPACE: 400, // プレイヤー player: null, // 敵(土管) enemies: [], // スコア獲得ポイント scorePoints: [], // 敵(土管)が生まれてからの時間 enemyBornDt: 0, ctor:function () { this._super(); // プレイヤー作成 this.player = createPlayer(); this.addChild(this.player); // タッチイベント登録 this.initTouchEvent(); // ゲームスタート this.changeGameMode(READY); this.enemies = []; this.scorePoints = []; return true; }, update:function (dt) { // 敵(土管)の作成 this.manageEnemy(dt); // プレイヤーの死亡判定 this.chackDeath(); // スコア獲得判定 this.chackEarnScore(); }, initTouchEvent: function() { var listener = cc.EventListener.create({ event: cc.EventListener.TOUCH_ONE_BY_ONE, swallowTouches: false, onTouchBegan: function (touch, event) { var target = event.getCurrentTarget(); target.touched(); return true; } }); cc.eventManager.addListener(listener, this); }, touched: function() { if (this.gameMode == READY) { // ゲームスタート this.changeGameMode(START); } if (this.gameMode == START) { // ジャンプ this.player.jump(); } if (this.gameMode == OVER) { // シーンを読み込み直す cc.director.runScene(new ActionScene()); } }, changeGameMode: function(gameMode) { this.gameMode = gameMode; if(!this.labelGameMode){ this.labelGameMode = new cc.LabelTTF(); this.labelGameMode.setFontSize(30); this.labelGameMode.setPosition(100, 1180); this.addChild(this.labelGameMode); } this.labelGameMode.setString("gameMode:" + gameMode); if (this.gameMode == START) { // それぞれのupdateメソッドを毎フレーム呼び出すようにする this.scheduleUpdate(); this.player.scheduleUpdate(); } if (this.gameMode == OVER) { // それぞれのupdateメソッド呼び出しを停止 this.unscheduleUpdate(); this.player.unscheduleUpdate(); } }, manageEnemy:function (dt) { // 敵(土管)が生まれてからの時間 this.enemyBornDt += dt; // 存在する敵(土管)を移動 _.each(this.enemies, function(enemy) { // 移動 enemy.x += this.ENEMY_X_SPEED; }, this); // スコアポイントを移動 _.each(this.scorePoints, function(scorePoint) { // 移動 scorePoint.x += this.ENEMY_X_SPEED; }, this); // 存在する敵(土管)が画面外に出たら消す this.enemies = _.filter(this.enemies, function(enemy) { if(enemy.x < -100){ enemy.removeFromParent(); return false; } return true; }, this); // 一定時間たったら敵(土管)を生成 if(this.enemyBornDt > 5){ this.enemyBornDt = 0; // 上下の土管の中心Y座標をランダムで取得 var centerOfEnemy = _.random(200, 1080); // 上の土管の下部の座標 var _top = centerOfEnemy + this.ENEMY_Y_SPACE / 2; // 下の土管の上部の座標 var _bottom = centerOfEnemy - this.ENEMY_Y_SPACE / 2; // 上側 var enemyUpper = createEnemy(_top, 1280); this.addChild(enemyUpper); this.enemies.push(enemyUpper); // 下側 var enemyUnder = createEnemy(0, _bottom); this.addChild(enemyUnder); this.enemies.push(enemyUnder); // スコア獲得ポイント var scorePoint = createScorePoint(_top, _bottom); this.addChild(scorePoint); this.scorePoints.push(scorePoint); } }, chackDeath:function () { // 敵(土管)と接触したら死ぬ _.each(this.enemies, function(enemy, index, array) { if(cc.rectIntersectsRect( this.player.getBoundingBox(), enemy.getBoundingBox() )){ this.die(); } }, this); // プレイヤーが画面外に出たら死ぬ if(this.player.y < 0 || 1280 < this.player.y){ this.die(); } }, chackEarnScore:function () { // スコア獲得ポイントと接触したらスコア加算 _.each(this.scorePoints, function(scorePoint, index, array) { if(cc.rectIntersectsRect( this.player.getBoundingBox(), scorePoint.getBoundingBox() )){ this.addScore(); scorePoint.removeFromParent(); array.splice(index, 1); } }, this); }, addScore:function () { this.score += 1; if(!this.labelScore){ this.labelScore = new cc.LabelTTF(); this.labelScore.setFontSize(30); this.labelScore.setPosition(100, 1080); this.addChild(this.labelScore); } this.labelScore.setString("score:" + this.score); }, die:function () { // 死んだ this.changeGameMode(OVER); } }); var ActionScene = cc.Scene.extend({ onEnter:function () { this._super(); var layer = new ActionLayer(); this.addChild(layer); } });
Cocos2d-JSのガベージコレクションについて
Cocos2d-JSでSQLite - galapagosit’s blog
上記の実装をしていた際に気になったこと。
var accesser = new jsb.SQLiteBridge("My.db"); accesser.execSql(sql); // この後accesserは使用しないものとする
jsバインディングで生成したSQLiteBridgeインスタンス、ちゃんとdeleteされてメモリが開放されるのだろうか?
であればどこかのタイミングで下記の処理が呼ばれるはずだが、呼ばれていない。
SQLiteBridge::~SQLiteBridge()
{
CCLOG("SQLiteBridge::~SQLiteBridge");
sqlite3_close(useDataBase);
}
調査してみた
コンストラクタ
そもそもコンストラクタはgenerator.pyによって生成された以下のコードで実行されていた。
bool js_jsb_sqlite_bridge_auto_SQLiteBridge_constructor(JSContext *cx, uint32_t argc, jsval *vp) { jsval *argv = JS_ARGV(cx, vp); bool ok = true; std::string arg0; ok &= jsval_to_std_string(cx, argv[0], &arg0); JSB_PRECONDITION2(ok, cx, false, "js_jsb_sqlite_bridge_auto_SQLiteBridge_constructor : Error processing arguments"); SQLiteBridge* cobj = new (std::nothrow) SQLiteBridge(arg0); TypeTest<SQLiteBridge> t; js_type_class_t *typeClass = nullptr; std::string typeName = t.s_name(); auto typeMapIter = _js_global_type_map.find(typeName); CCASSERT(typeMapIter != _js_global_type_map.end(), "Can't find the class type!"); typeClass = typeMapIter->second; CCASSERT(typeClass, "The value is null."); JSObject *obj = JS_NewObject(cx, typeClass->jsclass, typeClass->proto, typeClass->parentProto); JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(obj)); // link the native object with the javascript object js_proxy_t* p = jsb_new_proxy(cobj, obj); if (JS_HasProperty(cx, obj, "_ctor", &ok)) ScriptingCore::getInstance()->executeFunctionWithOwner(OBJECT_TO_JSVAL(obj), "_ctor", argc, argv); return true; }
デストラクタ
ではデストラクタは何処だろうか、おそらく上記と同一コード内の以下の処理
void js_SQLiteBridge_finalize(JSFreeOp *fop, JSObject *obj) { CCLOGINFO("jsbindings: finalizing JS object %p (SQLiteBridge)", obj); js_proxy_t* nproxy; js_proxy_t* jsproxy; jsproxy = jsb_get_js_proxy(obj); if (jsproxy) { nproxy = jsb_get_native_proxy(jsproxy->ptr); SQLiteBridge *nobj = static_cast<SQLiteBridge *>(nproxy->ptr); if (nobj) delete nobj; jsb_remove_proxy(nproxy, jsproxy); } }
デストラクタはいつ呼ばれるのか
ソースを追ってみるとおそらく該当のjs変数(accesser)がガベージコレクションされるタイミングの様だ。
で、それっていつなの?デストラクタが呼ばれないと不安で夜も眠れないので意地でも呼ばれるところを見届けたいところです。
どうやら下記のコードで強制的にガベージコレクション出来るようです。
__jsc__.garbageCollect();
これを呼んだ所、ちゃんとデストラクタが呼ばれました。良かったよかった。
そもそもの疑問の答えとしては、ガベージコレクションのタイミングはSpiderMonkeyが制御しているので、すぐには呼ばれなかったということだと思います。
Cocos2d-JSでSQLite
Cocos2d-JSネイティブアプリ(not ブラウザ)でsqliteを使う。
方針
- javascriptからsqlを実行出来て、json形式で結果を受け取れるようにする。
- cocos2d-jsのapiにはsqliteを扱う仕組みが無いので、下記の仕組みを使ってc++とバインディングする
公式
How to bind a custom class to js runtime | Documents | Cocos2d-x
参考サイト
Cocos2d-JS v3.0 で他の言語とやり取り (JavaScript → C++ 編) | ましろいろのねこ
準備
C++では
- sqlite3
- picojson
を使用させてもらっていますので、それぞれ設定をします
実装
bindするソース
SQLiteBridge.h
#ifndef __BRIDGE_H__ #define __BRIDGE_H__ #include "sqlite3.h" #include <iostream> class SQLiteBridge { private: sqlite3 *useDataBase = NULL; public: SQLiteBridge(std::string dbFileName); ~SQLiteBridge(); std::string execSql(std::string sql); }; #endif
SQLiteBridge.cpp
#include "SQLiteBridge.h" #include "cocos2d.h" #include "sqlite3.h" #include "picojson.h" using namespace cocos2d; using namespace std; SQLiteBridge::SQLiteBridge(std::string dbFileName) { char *errorMessage; //DBファイルの保存先のパス auto filePath = FileUtils::getInstance()->getWritablePath(); filePath.append(dbFileName); auto status = sqlite3_open(filePath.c_str(), &this->useDataBase); if (status != SQLITE_OK){ CCLOG("open failed : %s", errorMessage); return; } } SQLiteBridge::~SQLiteBridge() { CCLOG("SQLiteBridge::~SQLiteBridge"); sqlite3_close(useDataBase); } static int execSqlCallback(void *data, int argc, char **argv, char **azColName){ picojson::array *resultArray = (picojson::array *)data; picojson::object obj; int i; for(i=0; i<argc; i++){ obj.insert(std::make_pair(azColName[i], picojson::value(argv[i]))); } resultArray->push_back(picojson::value(obj)); return 0; } std::string SQLiteBridge::execSql(std::string sql) { CCLOG("SQLiteBridge::execSql start : %s", sql.c_str()); char *errorMessage; picojson::object result; picojson::array resultArray; auto status = sqlite3_exec(useDataBase, sql.c_str(), execSqlCallback, &resultArray, &errorMessage); result.insert(std::make_pair("status", picojson::value(double(status)))); if( status != SQLITE_OK ){ CCLOG("execSql failed : %s", errorMessage); result.insert(std::make_pair("errorMessage", picojson::value(errorMessage))); }else{ result.insert(std::make_pair("result", picojson::value(resultArray))); } picojson::value json = picojson::value(result); return json.serialize(); }
使い方
var accesser = new jsb.SQLiteBridge("My.db"); var sql1 = "CREATE TABLE hoge (id integer primary key autoincrement);"; var ret1 = accesser.execSql(sql1); var json1 = JSON.parse(ret1); if(json1.status !== 0){ cc.log("error!!!"); return; } var sql2 = "SELECT count(*) as cnt FROM hoge"; var ret2 = accesser.execSql(sql2); var json2 = JSON.parse(ret2); if(json2.status !== 0){ cc.log("error!!!"); return; } cc.log("cnt >>> " + json2.result[0].cnt);
Cocos2d-JSでBGM・効果音をpreload
Cocos2d-JSネイティブアプリ(not ブラウザ)で音声ファイルを再生する時のメモ。
音声ファイルは下記のコードで再生できるが、初回実行時に若干遅れて聞こえる。。。
cc.audioEngine.playEffect(mp3file);
調べてみるとC++で言うところの
SimpleAudioEngine::preloadEffect
なる処理を予めやっておかないと初回読み込みに時間がかかってしまうらしい。
同様の処理をCocos2d-JSで出来ないか調べてみた。
結論から言うと、事前に下記の処理を実行しておけば良いっぽい
cc.audioEngine.preloadEffect(mp3file);
おまけ(調べた過程)
公式ドキュメント
http://www.cocos2d-x.org/reference/html5-js/V3.0/index.html
のcc.audioEngineにはそれらしきメソッドは記載されていなかった。。。
(おそらくブラウザ環境と互換性が無いため?)
cocos2d-js-v3.0/samples/js-tests/src/
配下のサンプルにも無い。。。
こういう時は黙ってソースを読むしか無いw
tools/tojs/cocos2dx.ini
に下記の記載を発見
rename_functions = ... SimpleAudioEngine::[preloadBackgroundMusic=preloadMusic setBackgroundMusicVolume=setMusicVolume getBackgroundMusicVolume=getMusicVolume playBackgroundMusic=playMusic stopBackgroundMusic=stopMusic pauseBackgroundMusic=pauseMusic resumeBackgroundMusic=resumeMusic rewindBackgroundMusic=rewindMusic isBackgroundMusicPlaying=isMusicPlaying willPlayBackgroundMusic=willPlayMusic], ... rename_classes = ... SimpleAudioEngine::AudioEngine,
クラス名、メソッド名共に別名が付けられているっぽい!
そして
frameworks/js-bindings/bindings/script/jsb_boot.js
内で、
cc.audioEngine = cc.AudioEngine.getInstance();
されてる、って感じで結論になりました。
Cocos2d-JSでカスタムttfフォントを使用する
準備
iosの場合は下記の記事にあるように
http://kyokomi.hatenablog.com/entries/2014/01/07
Fonts provided by application を追加する必要がある。
設置方法
下記のような感じ。
// テキスト配置 var label; if(cc.sys.OS_ANDROID === cc.sys.os){ label = cc.LabelTTF.create("Alignment 0\nnew line\n!!", "res/fonts/Abduction.ttf", 24); } else { label = cc.LabelTTF.create("Alignment 0\nnew line\n!!", "Abduction", 24); } some_layer.addChild(label, 127, "label"); label.color = cc.color(0, 0, 0); label.x = 640; label.y = 500; label.opacity = 255;