Beginning Android 2D Video Games

Android上で2Dゲーム作成を始めてみたい方へ

Androidアプリの基本

Android上で実行するゲームは,SDKで開発する限りAndoroidアプリの1つですので,まずはAndoroidアプリの実行を理解しておく必要があります.
Androidアプリの基本単位は画面であるActivityを継承したクラス*1となりますが,このActivityは決まった状態遷移で動作します.
これを,Activityのライフサイクルと呼びます.

以下では,このActivityのライフサイクルとインテントを使って1つのActivityから別のActivityを呼び出す簡単な事例を示します.

Activityのライフサイクル

Activityのライフサイクルは,下図に示すように,onCreate()→onStart()→onResume()実行→onPause()→onStop()→onDestroy()を基本の状態遷移としています.

f:id:hidenaoA:20130430230233p:plain

それぞれの状態がどのように呼ばれるか,については,アプリ作成時に特に意識しておく必要があります.

onCreate()は,Activityの起動時のほか,画面の向きが変化した際にも呼ばれますので,注意が必要です.
onCreate()を単なる初期化メソッドのように書いてしまうと,onCreate()が呼ばれる度にすべてが最初からやり直しになってしまいます.
さらに,画面の向き変化は機器のセンサが感知し,AndroidOSが感知して実行されることになります.このため,機器毎に使用感の差異も生じます.
画面の向きを変化しないようにするためには,画面の向きを固定するようActivityの中で指定するか,どの向きでも対応できるように工夫してから実行中フラグなどでonCreate()の初期化動作を制御する必要があります.

onStart()やonRestart()など,オーバーライドが必要であれば,記述を行います.

onPause(),onStop(),onDestroy()については,OSからの指示で自動的に実行されますが,意図的に実行させることもできます.
onPause()やonRestart()は,例えば,メニューやボタンでアプリの一時停止と再開をする時にそれぞれ呼び出し,ゲームのプレー状態の一時保存と読み出しを実行します.
onStop()とonDestroy()は,finish()というメソッドでonStop()→onDestroy()という順番で呼び出されます.
finish()は,メニューやボタンからアプリを停止させるときに呼び出します.

以上の働きをまとめたものが,ActivityLifeCycleTestクラスです.
以下にコードを示しますが,Eclipse上で開発を行っている場合は,"Android Activity"を「新規(New)」から作成のうえ,コード部分を書き換えることになります.

package jp.ac.bunkyo.a2dgames;

/**
 * Android ActivityのライフサイクルをテストするActivity
 * @author Hidenao Abe (hidenao@shonan.bunkyo.ac.jp)
 */

/* Copyright 2013 Hidenao Abe (hidenao@shonan.bunkyo.ac.jp)

本ソースコードは,Apache License Version 2.0(「本ライセンス」)に基づいてライセンスされます。あなたがこのファイルを使用するためには、本ライセンスに従わなければなりません。本ライセンスのコピーは下記の場所から入手できます。
http://www.apache.org/licenses/LICENSE-2.0
適用される法律または書面での同意によって命じられない限り、本ライセンスに基づいて頒布されるソフトウェアは、明示黙示を問わず、いかなる保証も条件もなしに「現状のまま」頒布されます。本ライセンスでの権利と制限を規定した文言については、本ライセンスを参照してください。
(この日本語訳は,http://sourceforge.jp/projects/opensource/wiki/licenses%2FApache_License_2.0によるものです)
*/

import android.os.Bundle;
import android.app.Activity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import android.widget.Toast;

public class ActivityLifeCycleTest extends Activity { //Activityのライフサイクルと典型的なデバッグ方法の紹介

	StringBuilder str_builder = new StringBuilder();
	TextView textview;

	private void logging(String text){
		//android.util.Logを使うことで,LogCatタブで指定した文字列が表示される
		Log.d("LifeCycleTest",text);

		str_builder.append(text);
		str_builder.append('\n');

		textview.setText(str_builder.toString());
	}


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_activity_life_cycle_test);

        textview = new TextView(this);
        textview.setText("Hello!");
        setContentView(textview);
        //Toastは画面上に文字列の入ったボックス型の領域をポップアップする
        Toast.makeText(this, "LifeCycleTest Created", Toast.LENGTH_SHORT).show();

        logging("LifeCycleTest Created");
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {//Menuボタンを押したときのメニューを有効にする
        getMenuInflater().inflate(R.menu.activity_activity_life_cycle_test, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {//Menuボタンを押して出たメニューが選ばれたときの動作を記述
        super.onOptionsItemSelected(item);
        switch(item.getItemId()){ //それぞれのitemの表示はres/menu/activity_activity_life_cycle_test.xmlで行う
        case R.id.menu_finishing :
            //強制的にActivityを終了する
        	finish();
            return true;
        }
        return false;
    }

    @Override
    public void onStart(){
    	super.onStart();

    	logging("LifeCycleTest Started");
    }

    @Override
    public void onRestart(){
    	super.onRestart();

    	logging("LifeCycleTest Restarted");
    }

    @Override
    public void onResume(){
    	super.onResume();

    	logging("LifeCycleTest Resumed");
    }

    @Override
    public void onPause(){
    	super.onPause();

    	logging("LifeCycleTest Paused");

    	if(isFinishing()){
    		logging("LifeCycleTest Finishing");
    	}
    }

    @Override
    public void onStop(){
    	super.onStop();

    	logging("LifeCycleTest Stoped");
    }

    @Override
    public void onDestroy(){
    	super.onDestroy();

    	logging("LifeCycleTest Destroyed");
    }
}

複数のActivityを起動するランチャー(明示的インテントの利用)

ここでは,明示的インテントを発行するActivityを作成してみます.
このActivityはListActivityを基に作成していますが,ListActivityはリスト表示に特化したActivityであり,簡単に項目を追加できるため,今回はこれを利用します.

package jp.ac.bunkyo.a2dgames;

/**
 * 各種Activityを明示的インテントによって起動するActivity
 * @author Hidenao Abe (hidenao@shonan.bunkyo.ac.jp)
 */

/* Copyright 2013 Hidenao Abe (hidenao@shonan.bunkyo.ac.jp)

本ソースコードは,Apache License Version 2.0(「本ライセンス」)に基づいてライセンスされます。あなたがこのファイルを使用するためには、本ライセンスに従わなければなりません。本ライセンスのコピーは下記の場所から入手できます。
http://www.apache.org/licenses/LICENSE-2.0
適用される法律または書面での同意によって命じられない限り、本ライセンスに基づいて頒布されるソフトウェアは、明示黙示を問わず、いかなる保証も条件もなしに「現状のまま」頒布されます。本ライセンスでの権利と制限を規定した文言については、本ライセンスを参照してください。
(この日本語訳は,http://sourceforge.jp/projects/opensource/wiki/licenses%2FApache_License_2.0によるものです)
*/

import android.os.Bundle;
import android.app.Activity;
import android.app.ListActivity;
import android.content.Intent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.support.v4.app.NavUtils;

public class IOTestListActivity extends ListActivity {//リストを表示することに特化したListActivityを継承

	//リストの対象とするアクティビティのクラス名を列挙(表示とクラス名を分ける場合はMapなどを使用する)
	String[] test_activities = { "ActivityLifeCycleTest", "SingleTouchTest", "MultiTouchTest",
			"AccelerometerTest","GeoSensorTest","InnerStorageTest","ExtraStorageTest","IntentSendTest","SoundTest","VibrationTest",
			"RenderViewTest", "SurfaceViewTest","WebViewTest","HTTPClientTest"
	};

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_iotest_list);
        //android.R.layoutはgen/R.javaとは異なるので注意↓
        setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,test_activities));
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_iotest_list, menu);
        return true;
    }


    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                NavUtils.navigateUpFromSameTask(this);
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    protected void onListItemClick(ListView list, View view, int position, long id){

    	super.onListItemClick(list, view, position, id);
    	String item_name = test_activities[position];
    	try{
    		//"Class は raw 型です。総称型 Class<T> への参照は、パラメーター化する必要があります"
    		//という注意が表示されますが,対処の必要はありません.
    		Class class_name = Class.forName("jp.ac.bunkyo.a2dgames."+item_name);
    		Intent intent = new Intent(this,class_name);
    		startActivity(intent);
    	}
    	catch (ClassNotFoundException e){
    		e.printStackTrace();
    	}

    }

}

*1:以降,単にActivityと呼びます.