× [PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。 |
adcropsを導入したときの話。
かなり変な癖があって行儀が悪いSDKだったので、今後のために書いておく。 公式 今回実装した広告は、全部ウォール型。正しい名前は確認していないが ・リストウォール ・バナーウォール ・スロットウォール ◯広告設定 ID的なものは2つ。 サイトIDとサイトキー。 1つのアプリに複数種類の広告を置いても、このIDは増えない。 どの種類の広告もこの2つのIDを用いて表示する。 ここが他の広告媒体と違うので注意。 1つで何でも表示できるので便利…といえないこともない。 でも表示箇所ごとに広告を管理(フィルタリングなど)することができないのでどうなんだ。 ◯SDKインストール まずはSDKをインストールする。 header,resourceをまるっとプロジェクトに追加 リンカオプションに -ObjC を追加する。 なぜこのオプションが必要か。 このSDKは、categoryファイルを含んでいる。 そもそもUNIXでC/C++をビルドしたとき、 オブジェクトファイル(ソースファイルから作られるバイナリファイル)内に見つからないシンボルがあるとき、リンカがそれを解決する。つまり、そのシンボル(メソッド)を含む別のオブジェクトファイルをインクルードする。 だが、ObjCのstatic library の categoryはそうならない。 未定義シンボルは未定義のままで、実行時に"selector not recognized"例外が発生してしまう。 そこで-ObjCオプションを付けると、すべてのcategoryのメソッドが実行可能ファイルに含まれるようになる。 ただし、不要なメソッドも含んでしまうので、実行可能ファイルが膨大になることがある。(だからデフォルトオプションになっていない) また、コード中にC/C++で書かれている部分がコンパイルエラーになることがある。 特にcocos2dのライブラリ内でよく起きるのだが、 その場合は、そのファイル全体をコメントアウトしてしまうのが早い。 (大抵ムービーとかゲームコントローラとかあんまり使わないところでしか起きないからだ) 公式のQ&A ◯サンプル さて、この状態ではまだ広告は出ない。 adcropsのSDKはかなり基本的なところしか対応していなくて、 ウォールを表示しようとしたら自分でテーブルビューとか作らないといけない。 これを自分でやるとなると不親切極まりないと憤慨するところだが、 サンプルコードがいろいろ付いている。 サンプルコードをみると、SDKの他に Chk***.h/m/mm というファイルがたくさん入っている。 これらがテーブルビューとかを用意している。いわばヘルパークラスで、アプリ側とSDKを繋ぐ位置にいる。 これらのヘルパーファイルをコピーして、自分のプロジェクトに追加する。 で、アプリ側からはこれらのヘルパーを呼び出す形にする。 複数の広告種類を出すなら、それぞれのサンプルからヘルパーをコピーしてくる。 これで動くようになる。 初めからこのヘルパーもSDKに入れておけ、というところだ。 PR |
cocos2d-xで、LayerとかSpriteを継承して新しいクラスを作る場合がある。
たとえば画面を作りたいなら、Layerを継承すればいい。cocosプロジェクトを作ったときもコード中にHelloWorldSceneがそうして作られているし、それを真似すればいい。 基本的にゲーム中のオブジェクトなら、Layerを継承して作るのが良さそうだ。 前の記事も参照。 その他にも、たとえばキャラクターなら、Spriteを継承して作るなど、内容に応じていろいろなクラスを継承して作るのが良い。 以下はLayerを継承して作った例
こうすると、コード中にnewもdeleteも現れなくなる。 cocos2d-xの作法にあった形になる。 static関数のcreate() は、CREATE_FUNCマクロによって生成されている。 CREATE_FUNCマクロを使うためには、 ・引数無しコンストラクタがあること ・引数無しで戻り値がboolのinit()という関数を持っていること ・そのクラスがRefを継承していること(より正確には、autorelease()という関数を持っていること) が必要。
純粋なデータ構造を扱うクラスだから、SceneもNodeも継承する必要が無い、と思っても 「メモリ管理の方法は統一しておくべき」という観点から、最低でもRefは継承するのがよい。 Refは自動でメモリ管理をしてくれるクラス。使用されなくなったらreleaseすれば、裏でPoolManagerが自動的に破棄する。 <<注意!>> init()関数はNodeクラスに仮想関数として定義がある。 オーバーライドして、さらに親クラスのinit()を呼ぶのを忘れないように。 Refクラスを直接継承する場合は、オーバーライドではなく新規の定義になる(Refクラスにinit()がないので) Spriteクラスを継承して、親クラスのinit()の呼び出しを忘れた場合は、大抵バクる。 (glProgramStateがセットされないというアサートが出る。) もうひとつ。 create()メソッドは継承はしないが、 元々create()メソッドを持っているクラスから継承した場合、自クラスで定義しなくても呼び出せるわけだ。 例えばSpriteクラスを継承してMySpriteクラスを作った場合 auto sprite = MySprite::create(); とすると、 Sprite::create()が呼び出されることになる。 ただしその場合、戻り値型はSprite*なので、 MySprite* sprite = MySprite::create(); はコンパイルエラーになる。 ちゃんとMySpriteでcreate()を定義する必要がある(CREATE_FUNC(MySprite); と書くだけ) Spriteの場合、create()だけでなく create(const std::string& filename) create(const std::string& filename, const Rect& rect) createWithTexture(Texture2D *texture) createWithTexture(Texture2D *texture, const Rect& rect, bool rotated=false) createWithSpriteFrame(SpriteFrame *spriteFrame) createWithSpriteFrameName(const std::string& spriteFrameName) と言う具合に多くのcreate()系のstaticメソッドがある。 本来であれば、MySpriteクラスにすべての同名関数を用意すべきである。 ただし、戻り値がMySprite*でなくSprite*なので、コンパイルエラーで気付けるといえば気付ける。 |
|
cocos2d-xにおいては、すべてのクラスは
cocos2d::Ref の子孫クラスである。 Refクラスは、参照カウンタを扱っている。 Refクラスのautorelease()メソッドを呼んでおくと、 Refとその子孫クラスのインスタンスを生成したとき、破棄処理を自分でしなくても ゲームのメインループが来たときに自動で破棄してくれる。 CCRef.cpp
CCDirector.cpp
CCAutoReleasePool.cpp
CCRef.cpp
CCAutoReleasePool.cppを見るとわかるが、_managedObjectArrayに格納されているすべてのobjectに対してrelease()を実行している。 ずいぶん潔いやり方だ。生成しても放っておいてよい、というか放っておかなくてはいけない。自分でrelease()とか呼ぶと却ってバグる。 その使い方についてはCCRef.cppのrelase()メソッド内に詳しく書いてある。 一言で言うと new/retain() と autorelease()/release() を対で呼び出さなければならない。 ということだ。 生成時にnewが呼ばれる。 cocos的には、Node::create()やSprite::create()などのように、static関数のcreate()メソッドが相当。create()の中でnewをしている。 例としてNode::create()を見てみると
となっている。 createの中で new と autorelease() を両方やっている。 それ以外にも、retain()を呼ぶならrelease()かautorelease()をどこかで呼んでおく。 対で呼び出されるようになっていれば、メモリリークは起こらない。 autorelease()は複数回呼び出しても問題ないようだ。autoreleasePoolに2回登録されるので、2回releaseが走る。 なお、Nodeインスタンスの場合、別のNodeの子になる(addChild)と参照カウンタが1増える。
これは、parentNodeのメンバ変数 Vector<Node*> _children; に追加されるから。 cocos2d-xのVectorクラスは、pushBack(またはinsert,replace)したときにretain()を呼ぶようになっている。 このため、addChildされた子ノードは破棄されずに残る。 |
cocosでアニメーション作成する
以下は、立っている絵から歩きアニメーションを再生するところ。
standSpriteは、アニメーションを再生しなければ単なるスプライト。 アニメーション(この場合スプライトアニメーション)を再生することで、立ち絵のスプライトが歩きスプライトのアニメーションに変わる。 setRestoreOriginalFrame(true) を指定していると、アニメーションが終了したときに元のスプライト(立ち絵の表示)に戻る。ただしここではRepeatForeverアニメーションなので、終了することがないので設定が無意味だが。 |
Xcodeでデバッグする便利機能いくつか
【メモリのアクセス違反を検知】 Guard Malloc という機能を使う。 これはXcodeにデフォルトで入っている。 使い方は以下のとおり。スキームの設定をする。 参考 その他の機能の説明 Enable Scribble. Fill allocated memory with 0xAA and deallocated memory with 0x55. Enable Guard Edges. Add guard pages before and after large allocations. Enable Guard Malloc. Use libgmalloc to catch common memory problems such as buffer overruns and use-after-free. 引用元 zombie objectとは、解放されたメモリをNSZombieObjectにリプレースする機能。 解放済みメモリにアクセス違反したとき、どこにアクセスしたのかなどの情報が分かりやすくなる。 NSZombieObjectの説明 さらにいろんな便利機能 参考 うう〜ん、べんり! なお、注意点として、Guard Mallocを有効にした状態で実機実行はできない。 以下のエラーが出る。
面倒だが、シミュレータのときと実機のときで、スキームから設定を変更しよう。 参考 |
公式ドキュメント
を参照。 26ページから(特に28ページ冒頭)を要チェック。 引用----------
リスト 2-8 アクションをカテゴリにまとめるコード例
リスト 2-9 通知カテゴリを登録するコード例
リスト 2-11 ローカル通知に用いるアクションのカテゴリを定義するコード例
引用ここまで------------- つまり まずアクションを作成する。 UIUserNotificationActionクラス identifier title activationMode authenticationRequired destructive などの設定をする。 通知において、ひとつのボタンの操作に対応。 次に、アクションをまとめてカテゴリーを作る。 UIMutableUserNotificationCategory カテゴリーはアクションの配列を格納し、識別子を持つ。 ひとつの通知におけるビューがこれで作られる。 カテゴリーは別に1アプリ1つではない。通知の種類によっていくらでもカテゴリーは作れる。 ただし、コンテキストによって2種類の配列を登録する必要がある。 UIUserNotificationActionContextDefault …これはモーダルビューで見た時 UIUserNotificationActionContextMinimal …これはロック画面で見た時。こちらは最大2つしかアクションを持てない さらに、カテゴリーをセッティングにまとめる。 UIUserNotificationSettings セッティングは、通知種別(アラート、バッジ、サウンドなど)を持つ そして、このセッティングをアプリケーションに登録する。 [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; これにて通知が仕えるようになる。 実際に使うためには、通知をスケジューリング登録する。 UILocalNotification *notification = [[UILocalNotification alloc] init]; ... notification.category = @"INVITE_CATEGORY"; [[UIApplication sharedApplication] scheduleLocalNotification:notification]; |
今やiOSでは当たり前となったAutoLayout。
制約(NSLayoutConstraint)を適切に指定することで、レイアウトを指定することができ、画面サイズの違う端末でも意図通りに表示できるようになる。 AutoLayoutによる位置・サイズの決定は、viewWillAppearとviewDidAppearの間で行われる。 【画面初期化の流れ】 viewDidLoad実行 ↓ viewWillAppear実行 ↓ AutoLayout実行(内部) ↓ ビューのlayoutSubviews実行 ↓ viewDidLayoutSubviews実行(もう一度呼ばれる) ↓ 画面が構成される(内部) ↓ 画面遷移開始(ここからユーザの眼に触れる) ↓ 画面遷移終了 ↓ viewDidAppear実行 間違って(あるいは面倒くさがって)ビューコントローラ上のコードで画面レイアウトを作ろうとして、画面表示前(viewDidLoadなど)でビューのframeを直接いじっても、 その後にAutoLayout制約が適用されて上書きされてしまう。 (じゃあviewDidAppearの後にframeをいじろう、と考えたくなるところだが、それだとちょっと間に合わない。画面遷移をしながら表示するビューの場合、frameをいじる前のビューが画面に映ってしまう(=手遅れ)なのだ。 だから、レイアウトを処理・変更する必要があるビューは、必ずカスタム定義をして layoutSubviews メソッドを実装しておく必要がある。 このメソッドは、viewWillAppearの後に呼ばれる。 また、画面を再構成する必要があると自動的に判断されたときにも呼ばれるし、明示的に呼びたい場合は、ビューコントローラで [view setNeedsLayout]; を実行すればよい。 じゃあどうするか。 制約適用直後に画面が映ってしまうのはさけられない。 ということはつまり、制約適用時に求める画面が構成されるようにしておけばいい。 だから、、、 【制約を動的に作成する】 ということになる。 つまり、AutoLayoutを使うようになった時点で、画面構成はすべて制約で作るようにするべきなのだ。 frameとかboundsとかを直接いじることはなくなってくるはずだ(少なくとも、定数値を代入するようなことはないはずだ。せっかくAutoLayoutを指定していても台無しになる)。 さて、そうすると、どうやってコード上で制約を記述するのか、ということを覚えなければならない。ストーリーボードではいつも作ってるけど、コーディングはどうやって…? 公式のAutoLayoutマニュアル 制約クラスは NSLayoutConstraint 制約のインスタンスを作るメソッドは2種類ある 1.constraintsWithVisualFormat: options: metrics: views: VisualFormat VisualFormatとは、制約を人間に見やすく記述できるDSL。 これを使用して、ビューのサイズや位置を指定する。storyboardとほぼ同じことができる 公式のVisual Format Language仕様 いくつか例示する。 なお、view1,view2などは、制約を作りたいビューのメンバ変数名(詳細は後述) view1の横幅を10にする H:[view1(==10)] view1の高さを20以上にする V:[view1(>=20)] view1の高さを20以上100以下にする V:[view1(>=20,<=100)] view1とview2の幅を同じにする H:[view1(==view2)] ※比較演算子は、==,>=,<= の3つしか使えない ※カッコで囲わないと、パーサーがビュー名の区切りを判断できない。 ※HもVも書かないときはH(水平方向)になる view1とview2を12px空けて横に並べる H:[view1]-12-[view2] view1とview2をデフォルトの間隔を空けて縦に並べる V:[view1]-[view2] view1とview2を間隔無しで並べる H:[view1][view2] superviewとの間隔を指定する。パイプ(|)がsuperviewを表す H:|-50-[view1]-50-| options 指定できる内容は以下 NSLayoutFormatAlignAllLeft NSLayoutFormatAlignAllRight NSLayoutFormatAlignAllTop NSLayoutFormatAlignAllBottom NSLayoutFormatAlignAllLeading NSLayoutFormatAlignAllTrailing NSLayoutFormatAlignAllCenterX NSLayoutFormatAlignAllCenterY NSLayoutFormatAlignAllBaseline NSLayoutFormatAlignAllLastBaseline NSLayoutFormatAlignAllFirstBaseline NSLayoutFormatDirectionLeadingToTrailing NSLayoutFormatDirectionLeftToRight NSLayoutFormatDirectionRightToLeft 何もいらなければ0を指定。 metrics 調べてない views VisualFormat内に登場したビュー名と、ビューへの参照を格納したNSDictionary。 上記の例だと、 NSDictionary viewsDictionary = [[NSDictionary alloc]initWithObjects:@[view1,view2] forKeys:@[@"view1",@"view2"]]; のような感じ。 ちなみにこれと同じことが NSDictionaryOfVariableBindings(view1, view2); と書くことができる。 戻り値 記述した制約をすべて満たせるNSLayoutConstraintの配列が返ってくる。 最終的なコード例 view1の横幅を40にし、view2を水平に12px空けて並べ、それぞれsuperviewと左右端を合わせる。(制約が4つ作られる)
例外を捕まえる VisualFormatは実行時にパースされるので、書き間違えていると例外が飛んでくる。 ちゃんとキャッチしてチェックしよう。
2.constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant: item 制約を付けるビュー。 attribute 以下のものがある。 実態は前のメソッドのoptionに指定するNSLayoutFormat***と同じ。 NSLayoutAttributeLeft = 1, NSLayoutAttributeRight, NSLayoutAttributeTop, NSLayoutAttributeBottom, NSLayoutAttributeLeading, NSLayoutAttributeTrailing, NSLayoutAttributeWidth, NSLayoutAttributeHeight, NSLayoutAttributeCenterX, NSLayoutAttributeCenterY, NSLayoutAttributeBaseline, NSLayoutAttributeLastBaseline = NSLayoutAttributeBaseline, NSLayoutAttributeFirstBaseline NS_ENUM_AVAILABLE_IOS(8_0), NSLayoutAttributeLeftMargin NS_ENUM_AVAILABLE_IOS(8_0), NSLayoutAttributeRightMargin NS_ENUM_AVAILABLE_IOS(8_0), NSLayoutAttributeTopMargin NS_ENUM_AVAILABLE_IOS(8_0), NSLayoutAttributeBottomMargin NS_ENUM_AVAILABLE_IOS(8_0), NSLayoutAttributeLeadingMargin NS_ENUM_AVAILABLE_IOS(8_0), NSLayoutAttributeTrailingMargin NS_ENUM_AVAILABLE_IOS(8_0), NSLayoutAttributeCenterXWithinMargins NS_ENUM_AVAILABLE_IOS(8_0), NSLayoutAttributeCenterYWithinMargins NS_ENUM_AVAILABLE_IOS(8_0), relatedBy 比較演算子。以下の3つ NSLayoutRelationLessThanOrEqual = -1, NSLayoutRelationEqual = 0, NSLayoutRelationGreaterThanOrEqual = 1, toItem 対象となるビューを指定する。 align系なら、どのビューからの距離か、を指定するときに使う。 size系なら、あのビューのサイズの○倍、というような指定の時に使う。 不要ならnilを指定。 attribute toItem側のattribute。 itemのtopとtoItemのtopが○○px離れてる、というような制約のときに使用。 なければ0。 multiplier 係数。計算式に使う。後述 constant 定数。計算式に使う。後述 コード例 view1の高さを123にする場合。
view1の上端をview2の上端から25px以上空ける制約
view1の幅をview2の高さの2倍より10大きいサイズにする制約
|
2種類ある。
UITabBarDelegateの - (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item; UITabBarItem が引数で渡されてくる。 UITabBarController はすでにこのdelegateを実装している。 (ちなみに、ユーザがタップしてタブを選択したときにだけ呼び出される。プログラム上からタブを選択状態にしたときは呼ばれない) もう一つ UITabBarControllerDelegateの - (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController; こちらはUIViewControllerが引数になる。 UITabBarControllerはこのdelegateを実装していない。 UITabBarControllerを継承したクラスで使用したい場合は、UITabBarControllerDelegateを継承し、さらに
と記述すること。 そうすると、タブを選択したときに上記の2メソッドが両方呼ばれる。 |
UITabBarControllerを使うとき、タブアイテムに画像をセットすることがよくある。
普通、この画像はアイコンだけで、背景はグラフィックに持たせないことが多い。 ・端末によって画面サイズが違っていて、それに伴ってタブの縦横比も違っている。→分かりやすい解説ページ ・タブ数が変わると、1つのアイコンの横幅も変わる …というのが理由である。(当たり前) それでもアイコンに背景を付けたい、という人は 背景用画像をアイコン1つぶんの大きさにリサイズする。 で、[UITabBar appearance] の selectionIndicatorImage にセットする。
となる。 |