忍者ブログ
  • 2024.10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 2024.12
[PR]
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

【2024/11/24 01:43 】 |
cocos2d-x(v3.7)の新しいプロジェクトを作成する
やることは大して変わらない。
まず、ダウンロードページへ行く

最新のバージョンをダウンロードしたら解凍。
cocos2d-x-*.*
という名前のディレクトリができる

<cocos2d-x-*.*>/README.md

に基本的に書いてあるが

$ ./setup.py
$ source ~/.bash_profile
$ cocos new MyGame -p com.your_company.mygame -l cpp -d NEW_PROJECTS_DIR

で作れる。


PR
【2015/07/24 12:39 】 | cocos2d-x | 有り難いご意見(0)
iOSで通知タイマーを管理する
アプリ内でNSTimerを発行したいときがある。

◯記述場所
NSTimerは、アプリが起動している間だけ生きていれば良かったので、Model層では扱う必要が無い。
だからViewControllerで完結させるように記述した。


◯破棄
さて、NSTimerはキャンセルすることもできるようにしたい。
ということは、作成したNSTimerのインスタンスを保持しておく必要がある。
NSTimerインスタンスの生成・破棄及び参照保持と削除のタイミングは、

・作成ボタン押下
インスタンス化、参照保持

・キャンセルボタン押下
無効化、参照削除

・タイマー発動
参照削除(←忘れがちだがここでもちゃんと削除しておく。そうでないとvalidでないNSTimerインスタンスの参照がずっと残ることになる)

ということになる。

さてインスタンスの保持の方法だが、
[NSTimer]
Array<NStimer>
Dictionary
NSMutableSet

といろいろあるが、試してみて一番便利なのは

NSMutableSet

だった。
なぜなら、削除したいインスタンスを直接
set.removeObject(timer)
という形で指定できるからである。
それ以外の型だと、削除していいインスタンスかどうかの判定のロジックを書かなければならなくなる。


◯別のViewControllerから生成・削除させる

キャンセルは実は別の画面から行われることもある、という場合。
あるViewControllerから別のViewControllerにどうやって情報を伝達するか。

NotificationCenterを使う。

・通知を発信する側

var notification = NSNotification(name: "RemoveInAppNotification", object: self, userInfo: ["data": declareData!])
NSNotificationCenter.defaultCenter().postNotification(notification)

なおuserInfoは必ず Dictionary? 型でないといけない

・通知を受け取る側

NSNotificationCenter.defaultCenter().addObserver(self, selector: "removeInAppNotifications:", name: "RemoveInAppNotification", object: nil)

func removeInAppNotifications(notification: NSNotification) {
//notification.userInfo などを使って処理
}

となる。
nameはもちろん揃っている必要がある。
通知を受け取る側は、addObserver()やremoveObserver()を正しく書いておく必要がある。
画面に出入りしたときにadd/removeObserverしたいところだが、この場合はいけない。
「別の画面から呼び出されたとき」に通知を受け取りたいからである。
もちろん、該当のViewControllerが常にメモリ上に存在している保証が必要だ(そんなことできるのか?)


以上まとめるとコードは以下のようになる


class MyViewController{

//宣言タイマー
var reminderTimers: NSMutableSet = NSMutableSet()

override func viewDidLoad() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "removeInAppNotifications:", name: "RemoveInAppNotification", object: nil)
}

func makeInAppNotification(data: DeclareData, delayedSec: NSTimeInterval, message: String){
var userInfo = ["data": data, "message": message]
var t = NSTimer.scheduledTimerWithTimeInterval(delayedSec, target: self, selector: "showInAppNotification:", userInfo: userInfo, repeats: false)
reminderTimers.addObject(t)
}

//指定した宣言のタイマーを削除
func removeInAppNotifications(notification: NSNotification) {
var targetDeclareId = (notification.userInfo?["data"] as DeclareData).declareId

for obj in reminderTimers {
var timer = obj as NSTimer
if timer.valid {
var dic = timer.userInfo as NSDictionary
var timerData = dic["data"] as DeclareData
if timerData.declareId == targetDeclareId {
timer.invalidate()
reminderTimers.removeObject(timer)
}
}
else{
reminderTimers.removeObject(timer)
}
}
}

//NSTimerから呼び出されるメソッド
func showInAppNotification(timer: NSTimer){
var dic = timer.userInfo as NSDictionary
//var data = dic["data"] as DeclareData
var message = dic["message"] as String

UtilAlertView("", message, "OK")

reminderTimers.removeObject(timer)
}

}

class OtherViewController{
@IBAction func cancelButtonPressed(sender: UIButton) {
var notification = NSNotification(name: "RemoveInAppNotification", object: self, userInfo: ["data": declareData!])
NSNotificationCenter.defaultCenter().postNotification(notification)
}
}







【2015/07/12 18:54 】 | iPhone | 有り難いご意見(0)
cocos2d-xのラベルはグローバルZが実質使えない
cocos2d-xには、グローバルZ値を設定できる。
これによって、親子関係に依らず表示順序を設定できる。

グローバルZは、設定したそのノードだけに効く。
例えばノードAの子ノードBにグローバルZを設定し、一番手前に表示したとする。
ノードBの子ノードCがあったとき、ノードCは、ノードBより手前には表示されない。
ノードAの子として通常のローカルZによって描画順序が決定される。
ノードCをノードBより手前に表示したいときは、ノードCにもグローバルZを指定する必要がある。

なお、グローバルZは、値が小さい方が手前に表示される。
この点、ローカルZと逆になっている。
なぜこんなことになっているのか…。
ちなみにdouble型。


さて、グローバルZが子ノードに伝播しないと、
Labelが困ったことになる。
Labelは内部的にSpriteを子ノードに持っているため、
label->setGlobalZ()
を設定しても、実際の文字にはグローバルZの値が効かないのだ!

参考

大変困る話だ。

表示文字列が決まっているなら、文字全体を一枚のテクスチャで作ってしまっておけばSpriteで表示するといった手で逃げられるが。




【2015/07/07 02:14 】 | cocos2d-x | 有り難いご意見(0)
インクリメントは前置のほうが速い
++i;
i++;

を比べると
++i;
のほうが速い。

違いはもちろん
前置インクリメントは、インクリメントを先に行う。
後置インクリメントは、インクリメントを後に行う。
ということだ。


前置の場合、自身のインスタンスをインクリメントし、自身を戻り値として返せば良い。
なぜなら、インクリメントが先だから。インクリメント後の値をその次の計算で使うので、インクリメントしたものをそのまま戻り値にすればよい。

後置の場合、そうはいかない。
インクリメントするが、戻り値として返すのはインクリメントする前の元の値である。
ということで、「インクリメントするまえに自身のコピーインスタンスを作成している」のだ。
そして、戻り値として返すのはこのコピーインスタンスなのである。

このコピーの手間分だけ、後置インクリメントは遅くなる。

クラスインスタンスなどはもとより、intなどのプリミティブ型でもその差は出てくる。


試しに以下の処理を実行してみる




その結果は以下のようになった。



何度もやると時間は変化するし、まれに逆転することもあるが、
後置は前置より遅くなる。
(この処理の前置と後置の順番を逆にしてもやはり後置が遅い)


というわけで、インクリメント処理は理由が無い限り前置を使うこと。
【2015/06/22 14:22 】 | C/C++ | 有り難いご意見(0)
Eclipseの起動時に"Android SDK: resolving error markers"で動かないとき
以下のコマンドを実行する

$ cd <eclipseのworkspace>
$ cd .metadata
$ find . -name .markers -exec rm {} \;
$ path/to/adt-bundle-mac-x86_64-YYYYMMDD/eclipse/Eclipse.app/Contents/MacOS/eclipse -clean -refresh


参考
【2015/06/19 13:47 】 | cocos2d-x | 有り難いご意見(0)
リジェクト理由:ゲーム内容と異なるアイコン
以下のリジェクト理由が来た


3.3 - Apps with names, descriptions, screenshots, or previews not relevant to the content and functionality of the App will be rejected


さらに後ろにその詳細に付いても書いてある。


3.3 Details

Also, we noticed your icon includes the following image, which is not relevant to the application content and functionality:

We've attached the screenshot for your reference.



というわけで添付されていたスクショを見ると、それはcocos2d-xのアイコンであった。
なるほど。



さっそくアイコンを削除。

バージョン番号、ビルド番号を上げ、アーカイブを作成してサブミット!
…と思ったら、サブミットでエラー発生。



とのこと。
で、見てみる。

プロジェクトビューからプロジェクトを選択。
Infoタブを見てみると、確かに
IconFile
という項目が。その値が
icon57.png
になっている。

これはxcassetsが導入されてから不要になったはず。
というわけでそれを項目ごと削除。

これでOK、通った
【2015/06/17 21:43 】 | iPhone | 有り難いご意見(0)
Androidで、コード上でビューを座標指定して表示させる方法
Androidで、ビューをpx指定で表示したいことがある。

当たり前のやり方のような気がするが、意外と方法が用意されていない。
iOSなら、view.frameやview.boundsで矩形を指定することができるのだが、Androidはそれがない。


Androidは、ビューに加えて、レイアウト(FrameLayout,LinearLayout,RelativeLayoutなど)という概念がある。
(正確にはレイアウトもビューなのだが。)
ビューはレイアウトの中に配置される。

そのビューを、レイアウトから上下左右のマージンいくつ離すか、という指定をすることになる。

本来これでいいはずなのだが、
実際にビューを配置してみると、狙った位置と大きさも位置もずれていることがある。


なぜか。

マージンを指定することによって、矩形の大きさが決まる。
例えば、幅640,高さ240のLayoutの中に、左右マージン各20,上下マージン各10を取ると、
ビューのRect(x,y,w,h)=(20,10,600,220)になる。

しかし、ビューが実際に正確にその大きさで配置されるとは限らない。

たとえば、ビューの幅、高さの指定がWRAP_CONTENTだと、矩形のサイズはあまり関係なくビューの「見た目の大きさ」は決まってしまう。

また、MATCH_PARENTだとしても、
たとえばImageViewなら、元画像の縦横比を崩さない範囲で最大サイズ表示、のようなことになるからだ。
また、広告SDKで表示するアイコンなど、実際の表示サイズが不定になるものの場合もある。

そのようなとき、ビューは大抵左上寄せの位置に表示される。

これは嫌だ。指定座標を中心とする位置に収まってほしい。


じゃあ、gravityをCENTERに指定すれば…?
としたいが、gravity指定はビューにはできず、Layoutに指定する。
gravityは、そのレイアウト全体の重力場を指定するためのものだからだ。

ビューを中央寄せにする指定をビュー自体に対してできないか…となる。
もちろんその指定はあって、xmlでいうと
centerHorizontal
などの指定がそれにあたる。これは、
「自分の親のレイアウトの左右方向中心に配置」
ということだ。

……ということは、自分の親のレイアウトが必要になる。
何も考えずそのままcenterHorizontal,centerVerticalを指定したら、大本のレイアウト(おそらく画面全体を覆うRelativeLayoutになっているだろう)の中心に行ってしまう。それはスクリーンの中心だ。
そうじゃない。

さっき指定した矩形の中心にいてほしいのだ!


…………ということは、さっきの指定の矩形のレイアウトを作成すればいいんだ。


大本のRelativeLayout
+-- 矩形Layout
+-- ビュー(中央寄せで配置)




という二重構造にすればよい。




private void addChildView(View parent, View view, Rect rect) {
int x = rect.left;
int y = rect.top;
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;

// 中間レイアウトを作成。正確にrectの範囲を覆うRelativeLayout。
RelativeLayout positioner = new RelativeLayout(getApplicationContext());

// 中間レイアウトのレイアウトパラメータ
RelativeLayout.LayoutParams lp1 = new RelativeLayout.LayoutParams(width, height);
lp1.leftMargin = x;
lp1.topMargin = y;

// 中間レイアウトを親ビューの子にする
parent.addView(positioner, lp1);

// 目的のビューのレイアウトパラメータを作成。今作った中間ビューの中心に配置
RelativeLayout.LayoutParams lp2 = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
lp2.addRule(RelativeLayout.CENTER_VERTICAL);
lp2.addRule(RelativeLayout.CENTER_HORIZONTAL);

// 目的のビューを中間レイアウトの子にする
positioner.addView(view, lp2);
}






こういう感じになる。

中間レイアウトはRelativeLayoutでなければダメで、他のレイアウトだとマージン指定が効かない。
FrameLayoutやLinearLayoutにすると表示位置が左上固定になってしまう。

RelativeLayoutなので、レイアウトパラメータもRelativeLayout.LayoutParamsにする。



ビューのレイアウトパラメータもRelativeLayout.LayoutParamsにする。
でないとaddRule()メソッドが使えない。(だからCENTER_HORIZONTAL/VERTICAL指定ができない)

FrameLayoutには
lp2.gravity = Gravity.CENTER;
のような指定もできるが、これは自分の子孫に対して効かせるパラメータなので、ビュー自身を中央寄せしたい場合は意味が無い。




というわけで、
=================================
中間レイアウトはRelativeLayout
中間レイアウトのパラメータはRelativeLayout.LayoutParams
ビューのパラメータもRelativeLayout.LayoutParams
=================================
となる。



ちなみにレイアウトパラメータ類の子孫関係は以下

ViewGroup.LayoutParams
+-- ViewGroup.MarginLayoutParams
+-- FrameLayout.LayoutParams
+-- RelativeLayout.LayoutParams
+-- LinearLayout.LayoutParams



どのレイアウトパラメータも、コンストラクタの基本はwidthとheightの指定(int)である。
lp1のように、幅と高さを直に設定することもできるが、
多くの場合はlp2のように MATCH_PARENT(-1) か WRAP_CONTENT(-2) という特殊な値を入れて使うことになる。

【2015/05/25 20:25 】 | Android | 有り難いご意見(0)
Androidでローカル通知
こんな感じ。


private int localNotificationTag = 0;
private int cancelledLocalNotificationTag = 0;
private final String LOCAL_NOTIFICATION_TAG_PREFERENCE_KEY = "local_notification_tag";
private final String CANCELLED_LOCAL_NOTIFICATION_TAG_PREFERENCE_KEY = "cancelled_local_notification_tag";

// ローカル通知レシーブ用のクラス
// マニフェストにも記述のこと。また、必ずstaticをつけて宣言すること
public static class LocalNotificationReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {

int notificationId = intent.getIntExtra("notification_id", 0);
String message = intent.getStringExtra("message");

Intent intent2 = new Intent(context, Cocos2dxActivity.class);
intent2.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent2,
PendingIntent.FLAG_UPDATE_CURRENT);

Bitmap largeIcon = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setContentTitle(context.getString(R.string.app_name));
builder.setContentText(message);
builder.setSmallIcon(R.drawable.ic_launcher);
builder.setLargeIcon(largeIcon);
builder.setTicker(message);
builder.setAutoCancel(true);
builder.setDefaults(Notification.DEFAULT_ALL);
builder.setContentIntent(pendingIntent);

NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(notificationId, builder.build());
}
}

// 指定秒後の時刻をミリ秒で返す
private long getTimeInMillisSinceNow(int secSinceNow) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.add(Calendar.SECOND, secSinceNow);
return calendar.getTimeInMillis();
}

// ローカル通知用のPendingIntentを作成する
private PendingIntent makeLocalNotificationPendingIntent(String message, int tag) {
Intent intent = new Intent(getApplicationContext(), LocalNotificationReceiver.class);
intent.putExtra("notification_id", tag);
intent.putExtra("message", message);
PendingIntent sender = PendingIntent.getBroadcast(this, tag, intent, PendingIntent.FLAG_UPDATE_CURRENT);
return sender;
}

// ローカル通知用のPendingIntentを返す(データは入れない)
private PendingIntent getLocalNotificationPendingIntent(int tag) {
Intent intent = new Intent(getApplicationContext(), LocalNotificationReceiver.class);
PendingIntent sender = PendingIntent.getBroadcast(this, tag, intent, PendingIntent.FLAG_UPDATE_CURRENT);
return sender;
}

private static void InitLocalNotification() {
instance.localNotificationTag = LoadInt(instance.LOCAL_NOTIFICATION_TAG_PREFERENCE_KEY, 0);
instance.cancelledLocalNotificationTag = LoadInt(instance.CANCELLED_LOCAL_NOTIFICATION_TAG_PREFERENCE_KEY, 0);
}

// 指定秒後に指定の文字列でローカル通知を行う
public static void Native_scheduleLocalNotification(int secSinceNow, String text) {
PendingIntent sender = instance.makeLocalNotificationPendingIntent(text, ++instance.localNotificationTag);

long time = instance.getTimeInMillisSinceNow(secSinceNow);
AlarmManager am = (AlarmManager) instance.getSystemService(ALARM_SERVICE);
am.set(AlarmManager.RTC_WAKEUP, time, sender);

SaveInt(instance.LOCAL_NOTIFICATION_TAG_PREFERENCE_KEY, instance.localNotificationTag);
}

// 登録されているローカル通知をすべて削除する
public static void Native_unscheduleAllLocalNotifications() {
AlarmManager am = (AlarmManager) instance.getSystemService(ALARM_SERVICE);

for (int tag = instance.cancelledLocalNotificationTag + 1; tag <= instance.localNotificationTag; ++tag) {
PendingIntent sender = instance.getLocalNotificationPendingIntent(tag);
am.cancel(sender);
}
instance.cancelledLocalNotificationTag = instance.localNotificationTag;
SaveInt(instance.CANCELLED_LOCAL_NOTIFICATION_TAG_PREFERENCE_KEY, instance.cancelledLocalNotificationTag);
}





AndroidManifest.xmlには

<receiver
android:name="***.***.MainActivity$LocalNotificationReceiver"
android:process=":remote" />


と記述のこと。


◯内部クラスを使う場合の注意

・内部クラスはstaticをつけて定義する
Javaでは、内部クラスは基本的にstaticをつけて宣言することが推奨。
staticにしていないと、「no empty constructor」という実行時エラーになる。
なぜなら、staticでない内部クラスは、それを包含するクラスからしかインスタンス化できないからだ。

ちなみに、staticでない内部クラスは、自身を包含するクラスへの参照を暗黙的に持つ。アクセスできて便利だが、当然gcを阻害することになる


・AndroidManifest.xmlでは、内部クラスは $ で繋ぐ。
これもJava的な決まり事。

【2015/05/25 19:54 】 | Android | 有り難いご意見(0)
cocos2d-xでAndroidでBGMが2度目以降再生されない
cocos2d-xでandroidで、同じBGMが2度目以降再生されないバグがある。

(cocos2d-xトップディレクトリ)/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxMusic.java

の中。

stopBackgroundMusic()
のあと、同じファイルで
playBackgroundMusic()
すると音が鳴らない。

結論言うと、
prepare()をしていないから。


cocosの音声周りの実装は、SimpleAudioEngineを介して、Cocos2dxMusic.java を呼び出している。

Cocos2dxMusic は、メンバにAndroidSDKのMediaPlayerを保持し、これを操作している。

初回のplayBackgroundMusicは、createMediaPlayerメソッドを実行し、内部で
mediaPlayerのインスタンス化、
setDataSource
prepare
setVolume
を行っている。

その後playを実行し、音が鳴り始める。

さて、2回目にplayBackgroundMusicが呼ばれたとき。
直前に鳴らした音声の情報はmediaPlayerの中に残っており、(stopBackgroundMusicを実行しても、stopするだけでreleaseはしていない)、同ファイルだった場合は初期化処理を丸ごと飛ばす。
ここが問題。
このとき、本来実行すべきprepare()が抜けているのだ。

そこで、同ファイルのplayBackgroundMusicのとき、
prepare
seekTo(0)…曲の先頭に移動
を行うようにした。




public void playBackgroundMusic(final String pPath, final boolean isLoop) {
if (this.mCurrentPath == null) {
:
:
} else {
if (!this.mCurrentPath.equals(pPath)) {
:
:
}
else{



try{
this.mBackgroundMediaPlayer.prepare();
this.mBackgroundMediaPlayer.seekTo(0);
} catch (final Exception e) {
Log.e(Cocos2dxMusic.TAG, "error: " + e.getMessage(), e);
}
}
:


これでOK。

ちなみにlibcocos2dxプロジェクトのjavaファイルを書き換えてビルドすれば
ゲームプロジェクトにもちゃんと反映される。
【2015/05/25 18:10 】 | cocos2d-x | 有り難いご意見(0)
JNIでattempt to use stale local reference 0x1
JNIで

attempt to use stale local reference 0x1


というエラーが出た。

ググってみると、無効になったクラスにアクセスしてるとか、解放された変数に参照しているみたいなことが書いてある。
参考
参考

でも、そんな単純ミスはしていない。
おかしい。

その少し前のワーニングで、
trying to work around app JNI bugs, but didn't find 0xXXXXXXXX in table!
DeleteLocalRef(0xXXXXXXXX) failed to find entry
というようなのが出ている。

で、このポインタが示す変数はなんぞや、というのを出力して見てみたところ、
C++変数から変換したJNIオブジェクトだった。
特に何の問題もなさそう。


さらに発見したのが、

jobject objResult = methodInfo.env->CallStaticObjectMethod(methodInfo.classID , methodInfo.methodID, jarg);


の戻り値のobjResultが0x1になっている。
エラーはこの変数にアクセスした瞬間起きたようだ。


しかし、メソッドシグネチャーも合っているし、なぜ戻り値がこんなことになるのか。



このコードのどこに問題があるのか。


こういう不可解なことが起きるときはスレッドが絡んでいるもの。
そうすると真の原因はここでないまったく別の場所にある可能性がある。

ということで、ともかく例外を出力させてみる。

if (methodInfo.env->ExceptionCheck()) {
methodInfo.env->ExceptionDescribe();
}



すると…

05-25 13:04:16.466: W/System.err(15337): java.lang.IllegalStateException: isLoaded must be called on the main UI thread.



案の定、スレッドのエラーだった。
発生箇所とはこことは無関係の場所。


ここを直したら、無事に動いた。
【2015/05/25 13:28 】 | Android | 有り難いご意見(0)
<<前ページ | ホーム | 次ページ>>