アプリ置き場

アプリ置き場

http://www.moreread.net/

アプリ公開 つづき

とりあえず全プラットフォームで公開されました。
Xamarin+CocosSharpなクロスプラットフォーム開発アプリ。
 
Android版は相変わらずダウンロードほぼゼロ。iOS版は無事公開されたけど、言語が英語になってるわ、カテゴリがアドベンチャーだわでどうしたらいいんだ。
PC版は「ふりーむ!」でレビューが2件もついてる!うれぴ!
モンスターランドやビックリマンワールドのアレなのでレトロゲーすきな方は是非プレイしてくださいまし。
 
 
iOS
 
PC版
 

 
さて、忘れないうちにまた何か作りたいものだけど。うーむ。

アプリ公開

初のAppStore申請。
①VisualStudioでReleaseビルドでipaファイルを生成する
iTunes Connectでアプリ追加したりバンドルID発行したりする
XcodeのApplicationLoaderからipaファイルをアップロードする
はじめてでよくわかっておらず、アップロードしようとするとエラーが発生。
ERROR ITMS-90161: "Invalid Provisioning Profile. ~
 
Provisioningファイルとやらが何かイマイチ理解してないけど、VisualStudioからは下記のところをDistributionにしたらなんかうまくいった気がする。

他にもアイコンの透過PNGやめーや的なエラーとかも出た。
とりあえず無事に審査待ちに。
 
Androidのほうは公開してから1日以上たってるんだけど、自分以外ダウンロード数0!
新着アプリって紹介されたりしないの?

どこからもリンクされないんじゃダウンロードされんわ。
どうすればいんだー。
 

Xamarin + CocosSharp を使ってみたい ⑧

iPadで画面の向きが変わってしまう問題
 
Info.plist

にチェックを入れたらiPadでも向き変更抑制が効くようになった。めでたし。
public override bool ShouldAutorotate()
{
    return true;
}
 
public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations()
{
    return UIInterfaceOrientationMask.Landscape;
}
 
 
CocosSharpのフォント描画
 
フォントの読み込み方法がわからない。うぐぐ。
CCLabel label = new CCLabel(string, "/Resources/Fonts/hoge.ttf", font.size);
 
ふむ?
/Resource/Content/hoge.ttfとおいて、プロパティから「コンテンツ」を選択すればよさげ?
CCLabel label = new CCLabel(string, "hoge.ttf", font.size);
読み込めた!
 
が、読み込みがくっそ遅い。Labelの数が多いと使うのは不可能なレベル。
CCLabel生成すると、それぞれフォント読み込んでるくさいなぁ。
 
あとCCRenderTextureに描画しているんだけど、初回描画はアンチエイリアシングOFFにしてもぼやける。一度描画して文字列変えたらようやくOFFにある、なんじゃこりゃ。
 
文字列描画まわりもあやしいなぁ。
できるだけ画像リソースで文字列をもっておいて描画するしかないか。。。
 
 
ロケールの判断
 
iPhoneで常にen-USじゃねーか(怒)
CultureInfo.CurrentCulture //en-US
 
しょうがないのでiPhoneネイティブで取って渡す。
だんだん構造がきたなくなってくるぞお。
NSLocale.CurrentLocale.LocaleIdentifier //ja_JP
 
 
Android版のログ出力抑制
 
AndroidEnvironmentで設定すればいいらしい
MONO_LOG_LEVEL=message
 
 
広告対応
 
AdMob登録後、
をNuGetしてきて、適当にこのへん見たりして実装してみる。
 
must be called on the main UI thread.
っておこられた。RunOnUiThreadというのを使うらしい。
activity.RunOnUiThread(() =>
{
    if (interstitialAd != null && interstitialAd.IsLoaded)
    {
        interstitialAd.Show();
    }
});
 
Interstitial広告は閉じると再度ロードが必要らしい。
AdListener.OnAdClosedでもっかい作る。
public override void OnAdClosed()
{
    base.OnAdClosed();
    interstitialAd.Dispose();
    //requestInterstitialAd();
}
 
 
アプリ名(Android)
 
アプリ名がどこ設定していいかわからなくて悩む。Activityのラベルだった。
[Activity(Label = "ここ", MainLauncher = true, Icon = "@drawable/icon", AlwaysRetainTaskState = true, LaunchMode = LaunchMode.SingleInstance, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden)]
public class MainActivity : Activity
 
 
アイコンの作成
 
必要なアイコンをまとめて作ってくれるWebアプリ。すばらっ!
 
パワポでシートを正方形にして、適当に高解像度の画像を作る。
それをPNG出力して、先ほどのサイトに食わせると、30種類くらいの画像ができました。まじか、そんなに使うのか……。
 

 
 
Androidアプリのライフサイクル
 
ライフサイクルとか全然きにしてなかったけど、ふと戻るボタン押したらActivity死ぬじゃないの。初代Xperia時代から久しぶりの開発で、完全に忘れてた。
とりあえず、下記で戻るボタンをHOMEボタン化できた。
public override bool OnKeyDown([GeneratedEnum] Keycode keyCode, KeyEvent e)
{
    if (keyCode == Keycode.Back)
    {
        Intent homeIntent = new Intent(Intent.ActionMain);
        homeIntent.AddCategory(Intent.CategoryHome);
        homeIntent.SetFlags(ActivityFlags.NewTask);
        StartActivity(homeIntent);
        return true;
    }
    return base.OnKeyDown(keyCode, e);
}
 
あとは、Sleepボタン押したとき、他のアプリが動いたときにたまにActivityは死ぬ。
死んだら、CCSpriteやCCLabelなど、CocosSharpのUIコンポーネントのバッファがおかしくなってる。作り直さないとだめぽい。
もう完全にプロセス死んで再起動したほうがましなんだが……。なんでこんなゾンビ状態で動いてるの。プロセス殺す命令はAndroid5.0かららしい。4.xで動かしたいしなぁ。
 
 
ようやく
公開できそうなくらいにはなってきた。
くっそー1週間で作ったゲームの移植に3週間かかったなー。
iPhoneAndroidの知識の不足分を除いても、なかなかの地雷原ぷりでイニシャルコストは結構高いんじゃないかな。でもメンテは楽だと信じてる。あとやっぱC#だけで書けるのが大きい。

Xamarin + CocosSharp を使ってみたい ⑦

AndroidGC発生しまくりでカクカク問題
 
nursery-sizeをでっかく指定しておけばとりあえずGCを我慢してくれるようだけど。。。いろいろパラメータ指定したら、なんかときどき実行中にエラーでて落ちるように。
MONO_GC_PARAMS=bridge-implementation=tarjan,nursery-size=128m,soft-heap-limit=512m
 
The Xamarin.Android garbage collector can be configured by setting the MONO_GC_PARAMS environment variable. Environment variables may be set with a Build action of AndroidEnvironment.
The MONO_GC_PARAMS environment variable is a comma-separated list of the following parameters:
  • nursery-size = size : Sets the size of the nursery. The size is specified in bytes and must be a power of two. The suffixes k , m and g can be used to specify kilo-, mega- and gigabytes, respectively. The nursery is the first generation (of two). A larger nursery will usually speed up the program but will obviously use more memory. The default nursery size 512 kb.
  • soft-heap-limit = size : The target maximum managed memory consumption for the app. When memory use is below the specified value, the GC is optimized for execution time (fewer collections). Above this limit, the GC is optimized for memory usage (more collections).
  • evacuation-threshold = threshold : Sets the evacuation threshold in percent. The value must be an integer in the range 0 to 100. The default is 66. If the sweep phase of the collection finds that the occupancy of a specific heap block type is less than this percentage, it will do a copying collection for that block type in the next major collection, thereby restoring occupancy to close to 100 percent. A value of 0 turns evacuation off.
  • bridge-implementation = bridge implementation : This will set the GC Bridge option to help address GC performance issues. There are three possible values: old , new , tarjan.
  • bridge-require-precise-merge: The Tarjan bridge contains an optimization which may, on rare occasions, cause an object to be collected one GC after it first becomes garbage. Including this option disables that optimization, making GCs more predictable but potentially slower.
For example, to configure the GC to have a heap size limit of 128MB, add a new file to your Project with a Build action of AndroidEnvironment with the contents:
MONO_GC_PARAMS=soft-heap-limit=128m
 
.Androidプロジェクトにテキストファイルを追加して、プロパティウィンドウからタイプを
AndroidEnvironment
にすればよいらしい。要調整。
 

音が鳴らなくなる問題①
 
iOSバイスでしばらく動作テストしてると、必ず音が鳴らなくなって、そのうちフリーズ。ホワッツ
うそやろ……1024回で打ち止めって、しかも2年以上前から直ってないの?
たしかに1024回で「Failed to generate OpenAL data buffer:」と吐き出して、それ以上の再生が不可能になる。何かリークしてるのか?再生途中でストップをかけた分は回収されていて、寿命が延びてる気がする。
CCAudioEngine.SharedEngine.End();
とかしてみたが、特に意味はなかった。
 
自前でAudioエンジンのインスタンスを作ってみる。
CCAudioEngine engine =new CCAudioEngine();
 
~略~
 
engine.End();
engine.Dispose();
engine = new CCAudioEngine();
1024回をカウントして作り直してみるが効果なし。
が、なんということでしょう。CCAudioEngineを作り直したあとにGCかけると復活する。
System.GC.Collect();
 
AudioEngine作り直す度にBGMが止まったらうんこすぎるので、AudioEngineをBGM用とSE用を分けて2個にしてみる。おし、いけた。
余裕のあるタイミングでときどき初期化しとくほうがいいかなー。自前でインスタンス作ったから、PauseとResumeの伝達も自前になるんよね、たぶん。はぁ……。
 

音が鳴らなくなる問題②
 
iOSバイスでBGMが鳴らなくなってフリーズするのは別件だった。Audioは地雷原か……。
MP3をループで再生したところ、終了時点でおかしくなった。ループせず、以降のBGMの再生できず。午後のこ~だをひっぱりだしてきて、MP3→WAV→MP3と再変換してみたが直らず。データが悪いわけではなく、やはり何か問題を抱えてそうだ。
いくつか試した感じだとビットレートなどは関係なく、短めのBGMをループさせると、ちょくちょく再生終了時に死ぬ。誰も使ってないのかよ。
しょうがないので自前でループ機構を作る。終了を検知できないから、曲の再生時間を覚えておいて、フレームカウントから経過時間算出だよ!
いまのところちゃんと動いてる気がする。スリープ、レジュームすると狂いそうだなぁ。
 

固有実装
 
いざとなったらごりごり書くのか?書くしかないのか?
  • OnPlatform (XF 1.0 ~)
  • DependecyService (XF 1.0 ~)
  • Plugins for Xamarin
  • Custom Renderer (XF 1.0 ~)
  • Effects (XF 2.1 ~)
  • Native Embedding (XF 2.2 ~)
 

ジョイパッド対応
 
手元のGPD XDでデバッグしやすいようにパッド対応しようと思った。
あっさりできてうれぴ。Xamarinとはあまり関係ないけど。
どこにマッピングするのか試行錯誤がしやすいように割り当てを辞書に入れた。
// MainActivity.cs
public override bool OnKeyDown([GeneratedEnum] Keycode keyCode, KeyEvent e)
{
    if (dicPadmap.ContainsKey(keyCode))
    {
        Pad.getInstance().dwButtons |= dicPadmap[keyCode];//Padは独自クラス
        return true;
    }
    return base.OnKeyDown(keyCode, e);
}
 
public override bool OnKeyUp([GeneratedEnum] Keycode keyCode, KeyEvent e)
{
    if (dicPadmap.ContainsKey(keyCode))
    {
        Pad.getInstance().dwButtons&= ~dicPadmap[keyCode];
        return true;
    }
    return base.OnKeyUp(keyCode, e);
}
 
あとAndroidでステータスバーなしのフルスクリーン。Activityでセット。
Window.AddFlags(WindowManagerFlags.Fullscreen);
 

FPS計測
 
CocosSharpって簡単にFPSとる方法ってあるんだろうか?
よくわからないから自前で実装してみる。
どういう構造になっているかいまだによく理解してないが、VisitRendererとやらが実際に描画されたときに呼ばれるようだ。ここでカウントすればよさげ。
 
protected override void VisitRenderer(ref CCAffineTransform worldTransform)
{
    base.VisitRenderer(ref worldTransform);
 
    frameCounter.nextFrame();
}
 

最適化
 
GCの発生頻度を抑えるべくAndroidEnvironmentに加えて、CCNode系のクラスの生成/破棄をフレームごとに行うのをやめた。さすがに無理があったようだ。
そしたらGPD XD(2015年のミドルクラスくらい?)でも60FPSでるようになったのでこれで十分かな?

 

Xamarin + CocosSharp を使ってみたい ⑥

回転抑制
画面の向きを変えると表示位置がおかしくなる。
手持ちのAndroid機にいたってはフリーズ。どうすれば……。
→ めんどっちいからとりあえず横向き固定化だ!
 
ここを参考にまずiOS
UIViewControllerを継承したクラスにて
public override bool ShouldAutorotate()
{
    return true;
}
 
public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations()
{
    return UIInterfaceOrientationMask.Landscape;
}
としてみるが、シミュレータでは期待通りに動作だが、実機(iPad mini)ではまったく効果なし。回転しまくり。わからん。
[Export("application:supportedInterfaceOrientationsForWindow:")]
public UIInterfaceOrientationMask GetSupportedInterfaceOrientations(UIApplication application, IntPtr forWindow)
{
    return UIInterfaceOrientationMask.LandscapeLeft;
}
各所で見かけたAppDelegateのおまじないも効果なし。
 
解決の糸口がつかめぬまま数時間、あーでもないこーでもない。しかし、ふとiPhoneで試すと問題なく回転を抑制できてた。どういうこと……。iPadで回転しないゲームとかあるよね?iPadiPhoneで何が違うの……。iPadは一旦保留で。
 
Androidマニフェスト弄ればいけた。
<application android:label="RetroAction.Android" android:largeHeap="true">
    <activity
        android:name=".MainActivity"
        android:screenOrientation="landscape" >
    </activity>
</application>
ついでにandroid:largeHeap="true"というおまじないもつけておいた。
 
 
バックグラウンドからのレジューム対応
iOS(ipad mini4)で復帰時に音が戻らないことがある。なんで?
iphone7は問題ないぽいな。性能依存だろうか。
それとも、レジューム周りの処理は自分で書く必要があるのだろうか?
ふむふむ。Androidは勝手にやってくれると。
iOSはテンプレートにCCGameView.pause = true / false は書いてあったな。
 
CCGameViewの中を見るとちゃんとpauseのsetterでAudioのPauseとResumu呼ばれてる。うーん。
public bool Paused
{
    get { return paused; }
    set
    {
        if (gameStarted && paused != value)
        {
            paused = value;
            previousTicks = gameTimer.Elapsed.Ticks;
 
            if (paused)
            {
                AudioEngine.PauseBackgroundMusic();
                AudioEngine.PauseAllEffects();
            }
            else
            {
                AudioEngine.ResumeBackgroundMusic();
                AudioEngine.ResumeAllEffects();
            }
 
            PlatformUpdatePaused();
        }
    }
}
 
いろいろ試してたところ、Preloadしたら直った気がする。原因は不明。メモリ食いそうだからPreloadはSEだけにしてたんだけどまぁいいか。
CCAudioEngine.SharedEngine.PreloadBackgroundMusic(path);
 
端末再起動
テストしてるとiphoneが画面真っ黒操作不能になってびっくり。スリープボタン長押しもきかない。調べたら1段階上の強制終了として[音量下げボタン]+[スリープボタン]長押しがあるそうで。
 
動作を軽くする
CCDrawNodeは可能な限り再利用しつつ、矩形描画等は最小限に、CCLabelもDictionaryにキャッシュした。加えて、フレームレートを30FPSに下げた。
といっても、ロジックは60FPSで組んじゃったので描画だけ間引く。
そしたらそこそこ動いてるかな。
60FPSでぬるぬる動かすにはCocosネイティブ造りにしないと無理そうだ。
 

Xamarin + CocosSharp を使ってみたい ⑤

部分描画(画像の一部を切り出して描画する)
Sprite.TextureRectInPixels = new CCRect(sposx, sposy, sw, sh);
Sprite.ContentSize = new CCSize(sw, sh);
 
文字列の折り返し
CCLabel.Dimensions = new CCSize(w, h);
 
仮想キーパッド(てきとう)
protected override void AddedToScene()
{
var eventListener = new CCEventListenerTouchOneByOne();
eventListener.OnTouchBegan = CCEventListener_TouchBegan;
eventListener.OnTouchCancelled = CCEventListener_TouchEnded;
eventListener.OnTouchEnded = CCEventListener_TouchEnded;
AddEventListener(eventListener, this);
}
 
Dictionary<int, uint> keyDic = new Dictionary<int, uint>();
private Boolean CCEventListener_TouchBegan(CCTouch touch, CCEvent touchEvent)
{
if (button_left.BoundingBox.ContainsPoint(touch.Location))
{
keyDic[touch.Id] = BUTTON_LEFT;
pad.buttons |= BUTTON_LEFT;
}
elseif (button_right.BoundingBox.ContainsPoint(touch.Location))
{
keyDic[touch.Id] = BUTTON_RIGHT;
pad.buttons |= BUTTON_RIGHT;
}
return true;
}
 
private void CCEventListener_TouchEnded(CCTouch touch, CCEvent touchEvent)
{
if (keyDic.ContainsKey(touch.Id))
{
pad.buttons &= ~keyDic[touch.Id];
}
}
 
private void CCEventListener_TouchMoved(CCTouch touch, CCEvent touchEvent)
//TODO
}
 
iPhone実機の接続
なかなかiPhone実機で動かせなくてイライラ。3~4時間かかってしまった。
罠回避ポイント。
  • Info.plistの配置ターゲット
  • .iOSプロジェクト設定のビルド→サポートアーキテクチャ
  • iPhone認証後のMacエージェントの切断、再接続
  • バンドル識別子変更後のソリューションクリーンビルド
  • 他いろいろあったけど忘れた
とはいえなんとか環境は整ったので、あとは作り込むのみ。
 
iPhone実機でマルチタッチが効かない
View.MultipleTouchEnabled =true;
 
iPhone実機で透過PNGが透過されない
ググるiOSでは8bit pngは透過しない?まじか。いや、32bitでも透過されない場合があるぞ。
どうもirfanViewで作ったPNGがよろしくないようだ。透過色、背景色の設定によるんだろうか?
あたらめてirfanViewPNG変換をいろいろやってみると、色がおかしくなったりしてる。PNG変換まわりはなんかおかしい。標準仕様外の最適化でもしてるんじゃろか。違うソフトで変換するかー。
 
あと.iOSプロジェクトのプロパティにあるPNG画像を最適化するにチェックいれると透過できない場合があったのでこれも注意。
 
描画が重い

 
どうしよう、実機でもカックカク。
メインループは60回まわってきてても画面の描画が反映されてるのはかなり少なそう。ダメージを受けたときのエフェクトとして、1フレームごとに表示/非表示の点滅をしたら、消えっぱなしに。
いろいろオーバーヘッドあるけどバッファの解像度は320x180とめっさ小さいので、iPhone7なら余裕だろうと思ってたんだけどなぁ。
 
ちょいとしらべてみるとDrawNodeでDrawRectangleしまくってるのが遅い。画像描画したほうがましだった。描画自体が遅いのか、毎フレームのノードの生成が遅いのか、生成と破棄ともなうGCが遅いのかは調べてないけど。
がんばって最適化するかー。
 
描画のずれ
iPhoneで画像の描画が1ドットずれる。上側1ドットが表示されず、下側1ドットが2ドットに引き延ばされてる。0.5ドットずれてるとかそういうアレだろうか?厳密なドット単位の処理がきついなぁ。とりあえず0.5f足したらなおった気がする。
 
 
 

Xamarin + CocosSharp を使ってみたい ④

我慢できずに中古のMac miniを購入
APPLE Mac mini (2.6GHz Dual Core i5/8GB/1TB/Intel Iris) MGEN2J/A
6万円なり。てか新品でも7万やないか。新品でもよかったな……。
傷もなく良品でテカテカひかってるからまぁいいや。
アルミの削りだしボディはかっこいい。

なにやら画像が乱れて、ジジジッっという異音がしていたが、Thunderboltな口から古いDisplayPort⇔HDMI変換アダプタを経由してディスプレイと接続していたせいかもしれない。しらんけど。HDMIで直接つないだら直ったきがする。
さてもっかいMacをセットアップだ。
 
座標反転
Xamarinを試すまえにC#の標準ライブラリ(と幾何かのWin32API)だけで簡単なアクションゲームを作ってたので移植を試みる。描画まわりはSystem.Drawingだけという男らしい造りだ。
 
とりあえず前回の調査でCCRenderTextureとやらを使えばなんとかなりそうな感触を得ていたのだが、どう考えても描画まわりのロジック全部、Y座標系を逆にするのが面倒すぎる。CocsSharpは左下が基点となる。
いろいろ調べてみたところ、スケールを-1にすればいけそう。
 
Sprite.ScaleY = -1;
 
線描画、矩形描画等はCCRenderTextureにそのまま描画し、画像はYスケールを-1にしてから描画、描画完了したCCRenderTextureをYスケール-1で設置すれば、左上基点の座標系でいける。
 
グラフィックまわりのラッパー的なものをCCRenderTextureを使ってごりごり書いたらなんとなく動いた。Xamarinしゅごい。けど、表示がいろいろおかしい。

 
まず色がおかしかったのだが、CCColor3B、CCColor4Bのコンストラクタの引数の型がfloatで0~1.0ぽい。0~255つっこんだら、もれなく黒か白になってた。ちゃんとByte型のやつもあったのでキャストしたらなおりました。

 
なんかくっそ重いなぁ。大丈夫かなぁ。
画像を切り出して部分描画するところもうまくいってないなぁ。
タッチ入力もよくわからんなぁ。先は長い。