クロノス・クラウン - 記事検索

おすすめ
自作の本やゲーム他を販売
便利なWebアプリが多数
ソフトウェア
めもりーくりーなー Winのメモリーを掃除
Novel Supporter 小説推敲補助ソフト
PCソフト まとめ
Webサービス
Kindle Unlimited マンガまとめ 巻数の多いマンガを集計
ゲームブック
闇の聖杯の儀式 電書のゲームブック
ゲーム
Little Land War... Win向けSRPG
Little Bit War Switch向け高速RTS
TinyWar... 1面数分の8bit風RTS
EX リバーシ 変形盤面、盤面多数
http://army-and-maiden.blogspot.com/2010/10/androidarmy-maiden2.html
2010年10月02日 05:07:17
「Army & Maiden」 Android版 各種仕事と平行作業で開発しているAndroid版の「Army & Maiden」ですが、ようやくエミュレーターと実機で、普通にプレイできる速度で動くようになりました。

 「Army & Maiden」は、PCとAndroidのコンパチで動くライブラリを作りながら開発しています。

 このライブラリは既に完成しています。そして、同じゲームのプログラムで、PCとAndroidコンパチでゲームが動いています。

 データも完全に互換性を保っています。データに関しては、Zipで固めたデータを、Androidアプリの「res/raw」フォルダに放り込むだけで使えるようになっています。

 このように、けっこう面白いライブラリ群ができています。



 さて、このようなライブラリができたわけですが、全てが順調というわけではありません。

 PCとAndroidでは基本スペックが違います。なので、低スペックのAndroid端末でも動くように、ゲームのルーチンなどを高速化する改良が必要になります。

 PCとAndroidの主な違いは、CPUとメモリーとJVM(Java仮想マシン)です。

 このうち、メモリーに関しては、既にPC版で16MB以内で動作するように作り込んでいます。なので残りはCPUと対VM用の改良ということになります。

 というわけで、ここ数日間取り組んだ、対Androidアプリ用の高速化のまとめを書いておこうと思います。



●ZIP内ファイルの高速読み取りライブラリの作成

 Androidの「res/raw」フォルダに入れたZIPファイルを普通に読み込もうとすると、リソースIDからインプット・ストリームを取得して、ZipInputStreamを使って内部のファイルを1つずつ走査していく必要があります。

 この方式では、大量のファイルをZIP内から読み込む際には非常に時間がかかります(毎回走査しないといけないので)。

 この問題を回避する1つの方法として、ファイルをZIPで固めずに、バラバラにresフォルダに格納することも考えました。しかしこの方法は「PCとAndroidコンパチ」という開発コンセプトから外れるので却下しました。

 最終的に取った手法は、アプリ起動時に、リソース内のZIPファイルのヘッダー情報を走査してマップを作り、実際にアクセスする際はリソースのインプット・ストリームを、必要な分だけスキップして、対象のファイルだけを取り出すというものです。

 ZIPファイルは、内部的には、「ヘッダ情報」「データ本体」「ヘッダ情報」「データ本体」…と続いています。

 なので、初回に、この「ヘッダ情報」だけをまとめて読んで、必要な情報をメモリ上にまとめておき、必要に応じて、インプット・ストリームをスキップして、データ本体を解凍するようにしたわけです。

 この改良前は、全ファイルの読み込みに数十秒掛かっていました。この改良で、実用可能なアプリになりました。



●デフォルト背景のキャッシュを持つように変更

 Androidでは、ビットマップ描画の際のオーバーヘッドがかなり大きいです。

 そのため、デフォルトの背景(毎回タイル描画していた)を、画像キャッシュとして持つようにして、描画回数を減らしました。

 PC版だと、画面サイズ分の画像を持つので、けっこうメモリを食うのですが、PCだとそれほどメモリの制限もないので、まあいいやと割り切りました。

 他にも、成績表などもキャッシュを持つように変更しました。



●画面外描画の厳密な禁止

 前述の通り、Androidでは描画命令のコストがけっこう大きいので、ゲームルーチン側で判定して、描画命令を極力呼び出さないようにしました。



●低スペック・モードの切り替えスイッチの導入

 設定ファイルに、低スペック・モードの切り替えスイッチを追加しました。

 低スペックモードでは、sleepの時間を変えるなど、細かいところで、CPUに余裕がないことが前提のルーチンに切り替えるようにしました。



●移動アルゴリズムの改良

 ユニットの移動アルゴリズムを改良して、コストを大幅に減らしました。計算回数を可能な限り減らして、処理時間を短縮しました。



●移動計算の時間分散処理に、1フレーム内の最大計算回数を設定

 これまでは、単位時間内に、なるべく均等に処理を割り振るアルゴリズムにしていました。

 この方式では、ユニット数が増えた時にAndroidで処理落ちすることが分かったので、1フレーム内に計算可能なユニット数の制限を設けました。

 そうすると当然、単位時間をオーバーして計算が続くようになってしまいます。この場合でも、見た目には今までと同じようにユニットが動き続けるように、アルゴリズムを改良しました。

 Androidでの処理速度が、考えていたほど速くなかったので、ここらへんはかなり書き換えが必要でした。



●ボトルネック部分を低スペック・プログラミングに変更

 Androidの開発ツールには、Traceviewという各メソッドの処理時間を集計してくれるツールが付いています。

 このTraceviewは、各メソッドの呼び出し元と、内部で呼び出しているメソッドを、親子関係のリンクで示してくれます。

 また、処理時間順でメソッドをソートすることができます。さらに、計測した時間が帯グラフで表示されるので、どのメソッドがCPU時間のどの帯域を食いつぶしているのかが、一目で分かります。

 このツールは、高速化を行う際には非常に便利です。

 これで、呼び出し回数が多いメソッドを確認しながら、携帯Java用の低スペックな書き方にプログラムを書き換えていきます。



 以下、低スペック向けプログラムへの書き換え方です。

○グローバル変数は、ローカル変数に参照を渡してから使う。

解説:ローカル変数の方が、アクセスが高速。

○2次元配列は、2次元目を1次元配列に参照を渡してから使う。

解説:2次元配列は、参照の回数が2回なので、1次元配列に渡して、参照の回数を1回にしてから利用する。

○3回以上呼び出されるオブジェクトのプロパティは変数に格納する。

解説:たとえばarray.lengthが3回以上呼び出されるなら、その値を変数に格納しておき再利用する。2回なら、可読性を犠牲にするほどではないので無視する。

○メソッドを極力呼び出さないようする。

解説:メソッドの呼び出しコストが高いので、インライン展開できるところはインラインで書き直す。可読性とメンテナンス性が下がるので、あまりやりたくない部分。

○メソッドは可能ならstaticメソッドにする。

解説:staticメソッドの方が呼び出しが高速。

○JDKのライブラリのうち、低速のものを自前で書き直す。

解説:配列のソートや文字列の置換など。後述。



 基本的に、低スペック向けプログラミングを行うと、ソースの可読性とメンテナンス性は下がります。

 なので、あまりやりたくない部分ではあります。

 以下、その他の高速化(低スペックに関わらず、いつも行っている部分)を書きます。

・書き換えない変数は定数にして、static finalにする。

・floatは極力使わない。

・forループの終了判定部分には、アクセスコストの低い変数か定数を使う。

解説:「for (〜;i < arr.length;〜)」みたいなことはしない。「for (〜;i < len;〜)」とする。

・キャッシュ可能な計算結果は、なるべくキャッシュを取る。



●GCを呼び出さないことでの高速化

 これはAndroid版開発前にやっていたことです。Javaは、GCが出ないようにすれば、処理落ちは最小限に抑えられます。

 そのために必要なのは、オブジェクトを極力生成しないことです。

 また、作成する際も、オブジェクトの数は極力固定にして、一度作ったら処理落ちしてもよい時(画面の切り替えタイミングなど)まで廃棄しないようにします。

 そして、オブジェクトがGCで確実に廃棄されるように、不要になったオブジェクトにはnullを入れます。

 また、Javaの基本ライブラリのメソッドには、内部的にやたらとオブジェクトを生成するものもあります。そういったメソッドは、開発コストと効果を考えて、自前のメソッドに置き換えます。



●低速なAPIを、自前のAPIで高速化

 いくつかのクラスのメソッドは、内部的にはかなり重かったりします。これらのソース・コードは、「Google Code Search」で検索して、どこに無駄があるのかを自分で確認して、置き換えるべきかどうかを判断します。

□Google Code Search
http://www.google.com/codesearch

 以下、私が置き換えた命令です。

○配列のソート

 Javaのオブジェクト配列のソートは、速度はともかくメモリーを非常に食います。内部的に、新しい配列の参照を作り、ソートに利用しているからです。なので、配列のソートを毎フレーム行うと、GCが出まくります。

 そこで自前で、オブジェクトの新規作成を行わないソートクラスを作り、そのメソッドを利用するようにします。

 また、Javaのオブジェクト配列のソートは、比較子内の比較メソッドで比較を行います。これは、オブジェクトのソートで非常に多数のメソッドが呼び出されることになります。

 そこで、特定のオブジェクトの、特定のソート方法に特化したメソッドを作ります。これで、メモリを消費せずに、それなりに高速でソートを行うことができるようになります。

○文字の置換

 Javaの文字列の置換は正規表現なのですが、これはコストが高いです。

 代替クラスのJakarta langには、正規表現を使わない文字列の置換があるのですが、こちれは2つの点で改良の余地があります。

 1つは、内部でStringBufferを使っていることです。スレッドの同期を取る必要がなければ、これをStringBuilderに置き換えることで若干高速化できます。

 もう1つは、置換数のオプションがあることです。どうせ、全部置換か最初の1つ置換ぐらいしか使わないので、このオプションを取ったメソッドを作ることで、もう少し高速化できます。

・String.format

 formatメソッドはかなり便利なのですが、これは内部的に重い処理を行っているのでゲームのループ内で使うことはできません。

 今回のゲームでは、データを文字列で表示する部分があるので、このformatに一部準拠したメソッド(対応は「%s」「%d」「%+d」「%2〜9d」「%.1〜9f」)を作り、高速で文字列を置換するようにしました。



●回転表示を極力しないように変更

 回転表示は、各ドットの描画位置の変換が入るので処理コストが大きいです。

 そのため、低スペック・モードではキャラの回転表示をなしにしました(スイングではなく、ステップに動き方を変えました)。



 その他もろもろ弄ったのですが、もうだいぶ忘れかけています。

 取りあえず、備忘録として記録に残しておこうと思います。
最新20件 (全て見る)

オススメ電書 (全て見る

動画講座 (全て見る

サイト目次

おすすめ

PCソフト/Webアプリ

ゲーム

マンガ

記事

柳井の同人活動

開発

携帯・スマホ

アナログ・ゲーム

Cronus Crown(クロノス・クラウン)のトップページに戻る
(c)2002-2024 Cronus Crown (c)1997-2024 Masakazu Yanai
ご意見・お問い合わせはサイト情報 弊社への連絡までお願いします
個人情報の取り扱い、利用者情報の外部送信について