テーマ、知りたいこと
Androidのゲームアプリを開発する際に、不具合、かくつき、スマホの発熱が少なく、かつメンテナンス性の高いコーディングができるような設計方法を知りたい。
背景、状況
13年間自営でシステム開発等をしております。
主に個人事業主向けの業務システムをJava言語で開発しておりましたが、これからはスマホアプリ開発を主軸にと考えています。
今回初のスマホアプリ開発(ゲーム)を始めようとしているところですが、デスクトップアプリやWEBアプリ開発と勝手が大きく違い色々と飲み込み切れていない状況です。
難しく考え過ぎているのかもしれないですが頭の中を整理するためにも、まずは僕の理解や考えを書きますのでご意見ご指摘をいただけたら嬉しいです。
Javaで開発する予定で最初はハクスラRPGのような描画頻度の少ないゲームを作り、後々は2Dアクションゲームにもチャレンジしたい。
Androidアプリ開発についての自己理解
Android Studioを触って理解したことを箇条書きにしてみます。
- AndroidManifest.xmlのactivityタグ内にintent-filterタグを書くとファーストビューのactivityを指定できる。
- エントリポイント自体はAndroid SDKが隠蔽していて、OSの判断でアプリのプロセスが破棄される。
- Applicationクラスのサブクラス内のonCreateメソッドがプログラマーにとっての実質的なエントリポイント。
- onDestroyメソッドできちんと終了処理を書かないと、ユーザーに迷惑をかけるアプリになりかねないかも。
- Activityクラスはアプリ画面を構築するためのクラス。intentメソッドを呼ぶと他のActivityに遷移できる。
- savedInstanceState変数はアプリ再開時の復元に必要で、スマホを横にする別のアプリを開いた等ちょっとしたことで復元が必要になるため重要。
- Serviceクラスはバックグラウンド処理を書くためのクラスで、ここに書いたことをOS側が把握して何らかの恩恵があるんじゃないか?と思っている。
- 描画用のコンポーネントとしてはViewはゲームに向かない。再描画中にユーザー操作を受け付けられない仕組みになってしまっている。
- SurfaceViewは別スレッド(SurfaceHolderの実装クラス)で描画するのでViewよりはゲーム向き。
- Open GLは3Dゲーム向き。SurfaceViewより処理が速い反面、2Dゲームを前提とした作りではない。
- libgdxはアンチウィルスソフトに弾かれる。
- VulkanはC++。
- 現在はKotlinでJetpack ComposeというUIツールキットを活用することをGoogle公式は推している。
- Jetpack Composeは再描画のタイミングをツールキット側が自動で判断する。
自分の考えと疑問点
描画関係は2DゲームならSurfaceView、3DゲームならOpen GLと考えている。
libgdxは良さそうだけど、アンチウィルスの誤作動だとしても弾かれるのはビジネス用途的にNG。
VulkanはC++の学習コストが高そうで、Composeは再描画方式がゲーム向きでないと考えている。
再描画やアニメーションのスレッド処理は、ScheduledExecutorServiceクラスのscheduleWithFixedDelayメソッドの利用を考えている。
万が一、処理が1フレームに収まらない可能性を考えると、1フレームの時間が多少ずれてもscheduleメソッドの方が良い?
Runnableでwhileループする方法はCPU消費が激しいのでNG。
ゲームの画面遷移については2通りの方法を思いついているけど、知識も経験も乏しくどちらにする決めかねている。
他にもっと良い方法があれば知りたいし、そもそもにどのようにするのが一般的なのかを知らない。
- 素直にゲーム画面毎にActivityとSurfaceViewを作る。
まずはタイトル画面のActivityとSurfaceViewを作り、ゲーム画面で「はじめから」や「つづきから」をタップすると、次の画面のActivityにintentするイメージ。
実装が楽そうでメンテナンス性も良さそう。
半面、複数のActivityを用意することや頻繁にActivity遷移を行うことがオーバーヘッドに繋がるのではないかという懸念がある。
また、画面遷移する毎に前画面のスレッドを止めて、遷移後画面のスレッドを開始する実装にも違和感がある。
ゲーム開始から終了まで単一のスレッドを使いまわしても良いのでは?という疑問。
- それぞれひとつのActivityとSurfaceViewの中で描画、イベント処理等を行う。
まずは下記のように画面の状態をenum型でラベル化する。
Java
1public enum ViewType{ 2 TITLE, OPTION, LOAD, SAVE, STORY, SETUP, BATTLE; 3}
下記のように描画、イベント処理等を行う。
画面状態に対して、SurfaceViewに描画処理するクラスやイベント処理するクラスをマッピングする。
あくまでイメージを伝えるために大雑把に書いていますので、細かい部分のツッコミは無しでお願いします。
Java
1public class GameView extends SurfaceView implements SurfaceHolder.Callback{ 2 private ViewType currentViewType; 3 //Drawerのサブクラスで画面毎の初期描画を行う。 4 private HashMap<ViewType, Drawer> drawerMap; 5 //Touchableインターフェースが実装されたDrawerサブクラスにタッチイベント処理を書く。 6 private HashMap<ViewType, Touchable> touchableMap; 7 private AnimationQueue animationQueue; 8 public GameView(Context context, int defaultFrameRate){ 9 super(context); 10 ...中略... 11 drawerMap.get(currentViewType).draw(); 12 ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(); 13 service.scheduleWithFixedDelay(() -> { 14 animationQueue.drawFrames(); 15 }, 0, 1000000 / defaultFrameRate, TimeUnit.NANOSECONDS); 16 } 17 public final boolean onTouchEvent(MotionEvent event) { 18 touchableMap.get(currentViewType).touch(event); 19 } 20}
アニメーションは上記コードにあるDrawerクラスのコレクションとして実装する。
コレクションの要素に何も描画しないEmptyDrawerクラスを含める事もできる。
描画済みのDrawerクラス要素はコレクションから削除される。
主にタッチイベントからアニメーションが追加されたり、ループしたいアニメーションが画面表示時に追加されるイメージ。
Java
1public final class Animation extends CopyOnWriteArrayList<Drawer>{ 2 public Animation(Drawer... drawers){ 3 ...略... 4 } 5 public final void drawFrame(){ 6 remove(0).draw(); 7 } 8}
現在の画面でアニメーション描画が必要になる度このキューにAnimationインスタンスが追加される。
各フレームごとにキューに追加されたアニメーションが描画される。
空になった要素(Animationインスタンス)はコレクションから削除される。
画面遷移時はキューの中身が強制的に破棄される。
Java
1public final class AnimationQueue extends CopyOnWriteArrayList<Animation>{ 2 public final void drawFrames(){ 3 if(isEmpty())return; 4 TreeSet<Integer> removeSet = new TreeSet<>(); 5 for(int i = 0; i < size(); i++){ 6 get(i).drawFrame(); 7 if(get(i).isEmpty()){ 8 removeSet.add(i); 9 } 10 } 11 for(int i: removeSet.descendingSet()){ 12 remove(i); 13 } 14 } 15}
上手く実装出来ればライブラリ化して今後様々な作品に使いまわせそうだけど、回りくどく実装する分拡張性に問題が出てきそうでもある。
回答2件
あなたの回答
tips
プレビュー