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

ツイート @rutenさんをフォロー
クロクロ ショップ 本、ゲーム、同人誌他を販売
クロクロ ツールズ 便利なWebアプリを多数収録
IT用語大辞典 IT用語を解説
ソフトウェア
めもりーくりーなー Winのメモリーをお掃除するソフト
Novel Supporter 小説推敲補助ソフト
PCソフト 便利なソフトを多数公開
Webサービス 便利で楽しいサービス多数
レトロゲームファクトリー レトロゲーム移植会社のお仕事小説
#電書ハック 電子書籍編集部のお仕事小説
顔貌売人 IT系ミステリ
裏切りのプログラム IT系ミステリ
ゲーム
Little Land War SRPG Win向けSRPG
Little Bit War Switch向け高速RTS
TinyWar high-speed 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件 (全て見る)

柳井が執筆した本や商品 (全て見る

マンガでわかるJavaScriptのPromise
JavaScriptのPromiseをマンガで解説。無料。
レトロ風RPG 全コード
JSのレトロゲーム風RPGの全コードを掲載&解説。
タワーディフェンス 全コード
JSのタワーディフェンスの全コードを掲載&解説。
レトロゲームファクトリー
過去のゲームを最新機用に移植する会社のお仕事小説。新潮社より発売中。
#電書ハック
電子書籍編集部のお仕事小説。文藝春秋より発売中。
顔貌売人 ハッカー探偵 鹿敷堂桂馬
シリーズ第2弾。文藝春秋より発売中。
裏切りのプログラム ハッカー探偵 鹿敷堂桂馬
松本清張賞の最終候補に残った拙作小説(デビュー作)。

サイト目次

PCソフト/Webアプリ/ゲーム

記事/マンガ

柳井の同人活動

開発

携帯・スマホ

アナログ・ゲーム

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