第 1 回 アドベンチャーゲームの作成

本日の内容


このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。

なお、Android Studio 2.3.1 に対応したプログラムを用意しました。

1-1. 準備

Eclipse

  1. Eclipse でFile new project other で Android Android Application Project を選ぶ。
  2. Configure Project はスルー
  3. Configure the attributes of the icon set もスルー
  4. Select whether to create an activity, and if so, what kind of activity. はBlank Activity を選択
  5. Creates a new blank activity with an action bar. はスルーで Finish を押す

AndroidStudio

    1. Application name は Adventure Sampleとする
    2. Company Domain は jtp.c.dendai.ac.jp とする
    3. Project Location は、指定すると、その直下にすべてのファイル構造が作ら れるので、(適当なフォルダ名)\AndroidProject\(プロジェクト名)の ような 名前にする
  1. Phone and Tablet を選ぶ Android 4.0.3 を選ぶ(これには特に深い意味はない。全員が動かせるような 最低限の番号を選ぶ)
  2. Blank Activity を選ぶ
  3. Menu Resource Name を main とする
  4. SDK Manager で Android 4.0.3 をインストールしておく

1-2. プログラム

src

MainActivity.java


package jp.ac.dendai.c.jtp.adventuresample;
import jp.ac.dendai.c.jtp.adventuresample.scene.AbstractScene;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
public class MainActivity extends ActionBarActivity {
	private Game game;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		AbstractScene.setActivity(this);
		setContentView(R.layout.title);
		game = new Game(this);
		game.start();
	}
	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}
	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		// Handle action bar item clicks here. The action bar will
		// automatically handle clicks on the Home/Up button, so long
		// as you specify a parent activity in AndroidManifest.xml.
		int id = item.getItemId();
		if (id == R.id.action_settings) {
			return true;
		}
		return super.onOptionsItemSelected(item);
	}
}

Game.java


package jp.ac.dendai.c.jtp.adventuresample;
import jp.ac.dendai.c.jtp.adventuresample.scene.GameState;
import jp.ac.dendai.c.jtp.adventuresample.scene.Scene;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.view.View.OnClickListener;
public class Game implements Handler {
	private ActionBarActivity activity;
	private Title title;
	private Scene scene;
	public Game(ActionBarActivity mainActivity) {
		this.activity = mainActivity;
		title = new Title();
	}	
	@Override
	public void step(Scene s) {
		scene = s;
		start();
	}
	public void start() {
		if(scene==null) {
			activity.setContentView(title.getContentView());
			title.init(activity,new OnStartButtonClickListener(true), new OnStartButtonClickListener(false));
		}else{
			activity.setContentView(R.layout.activity_main);
			scene.start(this);
		}
	}
	class OnStartButtonClickListener implements OnClickListener {
		private boolean intialize;
		public OnStartButtonClickListener(boolean b) {
			intialize = b;
		}
		@Override
		public void onClick(View v) {
			if(intialize || scene==null){
				scene=GameState.getInitialScene();
			}
			start();
		}
	}
}

Handler.java


package jp.ac.dendai.c.jtp.adventuresample;
import jp.ac.dendai.c.jtp.adventuresample.scene.Scene;
public interface Handler {
	void step(Scene s);
}

Title.java


package jp.ac.dendai.c.jtp.adventuresample;
import android.support.v7.app.ActionBarActivity;
import android.view.View.OnClickListener;
import android.widget.Button;
public class Title {
	public void init(ActionBarActivity activity, OnClickListener onStartButtonClickListener, OnClickListener onContinueButtonClickListener) {
		Button startButton = (Button) activity.findViewById(R.id.startbutton);
		Button continueButton = (Button) activity.findViewById(R.id.continuebutton);
		startButton.setOnClickListener(onStartButtonClickListener);
		continueButton.setOnClickListener(onContinueButtonClickListener);
	}
	public int getContentView() {
		return R.layout.title;
	}
}

scene/Scene.java


package jp.ac.dendai.c.jtp.adventuresample.scene;
import jp.ac.dendai.c.jtp.adventuresample.Handler;
import android.view.View.OnClickListener;
public interface Scene extends OnClickListener {
	GameState next(int no);
	void start(Handler hand);
	int getMessageId();
	int getImageId();
	int getQuestionId();
	int getDateId();
}

scene/AbstractScene.java


package jp.ac.dendai.c.jtp.adventuresample.scene;
import jp.ac.dendai.c.jtp.adventuresample.Handler;
import jp.ac.dendai.c.jtp.adventuresample.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
public abstract class AbstractScene implements Scene {
	private int index;
	private Handler handler;
	private static Activity activity;
	protected int size(){
		return getMessage().length;
	};
	public static void setActivity(Activity _activity){
		activity = _activity;
	}
	@Override
	public void onClick(View v){
		index++;
		if(index<size()){
			writeMessage();
		}else{
			if(getQuestionId()!=0){
				ImageView imageView = (ImageView) activity.findViewById(R.id.imageView1);
				imageView.setOnClickListener(null);
				askQuestion();
			}else{
				GameState n = next(0);
				Scene scene = n==null ? null : n.getScene();
				handler.step(scene);
			}
		}
	}
	private void askQuestion() {
		Builder builder = new AlertDialog.Builder(activity);
		builder.setCancelable(false);
		builder.setPositiveButton(getQuestion()[0], new Answer(0));
		builder.setNegativeButton(getQuestion()[1], new Answer(1));
		AlertDialog alert = builder.create();
		alert.show();
	}
	private class Answer implements OnClickListener {
		private int no;
		public Answer(int i) {
			no = i;
		}
		@Override
		public void onClick(DialogInterface dialog, int which) {
			handler.step(next(no).getScene());
		}
	}
	@Override
	public void start(Handler h){
		handler = h;
		index=0;
		activity.setContentView(R.layout.activity_main);
		ImageView imageView = (ImageView) activity.findViewById(R.id.imageView1);
		imageView.setOnClickListener(this);
		imageView.setImageResource(getImageId());
		writeMessage();
	}
	private void writeMessage() {
		TextView textView = (TextView) activity.findViewById(R.id.textarea);
		textView.setText(getMessage()[index]);
		TextView textdate = (TextView) activity.findViewById(R.id.textdate);
		textdate.setText(activity.getResources().getString(getDateId()));

	}
	protected String[] getMessage() {
		return getStringArrayById(getMessageId());
	}
	protected String[] getQuestion() {
		return getStringArrayById(getQuestionId());
	}
	protected String[] getStringArrayById(int id) {
		return activity.getResources().getStringArray(id);
	}
}

scene/GameState.java


package jp.ac.dendai.c.jtp.adventuresample.scene;
public enum GameState {
	first(new First()),second(new Second()),
	ending(new Ending()), badend(new BadEnd())
	;
	private Scene scene;
	public Scene getScene(){
		return scene;
	}
	private GameState(Scene s){
		scene = s;
	}
	public static Scene getInitialScene(){
		return first.getScene();
	}
}

scene/First.java


package jp.ac.dendai.c.jtp.adventuresample.scene;
import jp.ac.dendai.c.jtp.adventuresample.R;
public class First extends AbstractScene {
	@Override
	public int getImageId() {
		return R.drawable.first;
	}
	@Override
	public int getMessageId() {
		return R.array.message1;
	}
	@Override
	public int getQuestionId() {
		return 0;
	}
	@Override
	public GameState next(int no) {
		return GameState.second;
	}
	@Override
	public int getDateId() {
		return R.string.date1;
	}
}

scene/Second.java


package jp.ac.dendai.c.jtp.adventuresample.scene;
import jp.ac.dendai.c.jtp.adventuresample.R;
public class Second extends AbstractScene {
	@Override
	public GameState next(int no) {
		switch(no){
		case 0:
			return GameState.ending;
		case 1:
			return GameState.badend;
		}
		return null;
	}
	@Override
	public int getImageId() {
		return R.drawable.second;
	}
	@Override
	public int getMessageId() {
		return R.array.message2;
	}
	@Override
	public int getQuestionId() {
		return R.array.question2;
	}
	@Override
	public int getDateId() {
		return R.string.date2;
	}	
}

scene/Ending.java


package jp.ac.dendai.c.jtp.adventuresample.scene;
import jp.ac.dendai.c.jtp.adventuresample.R;
public class Ending extends AbstractScene {
	@Override
	public int getImageId() {
		return R.drawable.ending;
	}
	@Override
	public int getMessageId() {
		return R.array.messageending;
	}
	@Override
	public int getQuestionId() {
		return 0;
	}
	@Override
	public GameState next(int no) {
		return null;
	}
	@Override
	public int getDateId() {
		return R.string.dateending;
	}
}

scene/BadEnd.java


package jp.ac.dendai.c.jtp.adventuresample.scene;
import jp.ac.dendai.c.jtp.adventuresample.R;
public class BadEnd extends AbstractScene {
	@Override
	public GameState next(int no) {
		return null;
	}
	@Override
	public int getImageId() {
		return R.drawable.badend;
	}
	@Override 
	public int getDateId(){
		return R.string.datebadend;
	}
	@Override
	public int getMessageId() {
		return R.array.messagebadend;
	}
	@Override
	public int getQuestionId() {
		return 0;
	}
}

res

後述のファイルの他、res/drawable-hdpi フォルダ内に下記の画像ファイルを 入れる(PNG形式、480x800px)

  1. first.png
  2. second.png
  3. ending.png
  4. badend.png

layout/activity_main.xml


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="jp.ac.dendai.c.jtp.adventuresample.MainActivity" >
    <ImageView
        android:id="@+id/imageView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:cropToPadding="false"
        android:maxHeight="700dp"
        android:src="@drawable/first"
        android:contentDescription="@string/background"/>
    <TextView
        android:id="@+id/textarea"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_alignRight="@+id/imageView1"
        android:ems="10"
        android:height="100dp" >
        <requestFocus />
    </TextView>
    <TextView
        android:id="@+id/textdate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ems="10" />
</RelativeLayout>

layout/title.xml


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    >
    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="59dp"
        android:text="@string/title" />
    <Button
        android:id="@+id/startbutton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="@string/startb" />
    <Button
        android:id="@+id/continuebutton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/startbutton"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="56dp"
        android:text="@string/continueb" />
</RelativeLayout>

values/strings.xml


<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">AdventureSample</string>
    <string name="hello_world">Hello world!</string>
    <string name="action_settings">Settings</string>
    <string name="background">Back Ground Image</string>
    <string name="title">Data Structures and Algorithms</string>
    <string name="startb">Start</string>
    <string name="continueb">Continue</string>
    <string-array name="message1">
        <item >I am Kentaro Okabe, a sophomore of Department of IC, school of Engineering, Den University.</item>
        <item>Finally, it\'s time to reach the second semester.</item>
        <item >I have a class by the name of "Data Structures and Algorithms" for my first class of Thursday, though.</item>
        <item>Somehow, I feel different atmosphere from other classes.</item>
    </string-array>
    <string name="date1">9/18/2014</string>
    <string-array name="message2">
        <item>My impression is that the class is quite difficult.</item>
    </string-array>
    <string-array name="question2">
        <item>I\'ll study hard.</item>
        <item>For the present, I am seeing whether my friends can do it well.</item>
    </string-array>
      <string name="date2">9/18/2014</string>
    <string-array name="messageending">
        <item>First, I studied it. But, it was so serious.</item>
        <item>Nevertheless, I studied it hard so that I can enjoy it more and more.</item>
        <item>Finally, I received my result. Believe it or not, it is a grade of S.</item>
        <item>Even though I didn\'t mind what result I can receive the while, I am happy for receiving the good result.</item>
        <item>Moreover, since I make sense of programming, I can enjoy programming.</item>
        <item>End.</item>
    </string-array>
      <string name="dateending">3/15/2015</string>
    <string-array name="messagebadend">
        <item>Since my friends said "it is OK," while harmonizing with my friends, the winter holidays came.</item>
        <item>Luckily, I received the programs from somewhere, then, I managed to submit a report to which I included it.</item>
        <item>But, I received a grade of D. I failed the class. I don\'t know what my fault is.</item>
        <item>End.</item>
    </string-array>
      <string name="datebadend">3/15/2015</string>
</resources>

values-ja/strings.xml

Eclipse
  1. res フォルダに対して、「New folder」 を指定して、 values-ja フォルダを作る。
  2. values-ja フォルダを右クリックして、「new xml file」 を指定する。
  3. File name に strings.xml を指定する。
Android Studio
  1. res フォルダに対して、「New Android resource folder」 を指定して、 values-ja フォルダを作る。 しかし、AndroidStudio では フォルダが表示されない。
  2. そこでめげずに res フォルダを右クリックして、「New Android resource file」 を指定する。
  3. File name に strings.xml を指定し、 Directory に values-ja を指定 する。すると、 strings.xml が並んで表示され、一方は日の丸のアイコン になる。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">AdventureSample</string>
    <string name="hello_world">Hello world!</string>
    <string name="action_settings">Settings</string>
        <string name="background">背景画像</string>
    <string name="title">データ構造とアルゴリズム</string>
    <string name="startb">始めから</string>
    <string name="continueb">続きから</string>
    <string-array name="message1">
        <item>僕はデン大工学部アイシー科2年の岡部健太郎だ。いよいよ後期を迎えた。</item>
        <item>木曜日一時間目のデータ構造とアルゴリズムという科目を受けるのだが、どうも他の講義と様子がちがう</item>
    </string-array>
    <string-array name="message2">
        <item>講義の印象だけど、結構難しいと思った</item>
    </string-array>
    <string-array name="question2">
        <item>まじめに勉強する</item>
        <item>とりあえず友達ができるか様子を見る</item>
    </string-array>
    <string-array name="messageending">
        <item>とりあえず勉強してみたが、結構大変だった。</item>
        <item>でも、必死にやっていたら、だんだん面白くなってきた。</item>
        <item>そして、成績発表。なんとS評価をもらった。</item>
        <item>途中から評価なんか忘れていたけど、やっぱり評価されたらうれしい。</item>
        <item>そして、プログラミングが分かって楽しくなった。</item>
        <item>End.</item>
    </string-array>
    <string-array name="messagebadend">
        <item>友達が大丈夫と言うので、とりあえず合わせているうちに冬休みが来た。</item>
        <item>とりあえず、どこかから流れてきたプログラムが送られてきたので、それを貼り付けてなんとかレポートを提出した。</item>
        <item>ところが、評価はD。不合格だった。いったい何が悪かったんだろう?</item>
        <item>End.</item>
    </string-array>
</resources>

坂本直志 <sakamoto@c.dendai.ac.jp>
東京電機大学工学部情報通信工学科