Home‎ > ‎Teaching‎ > ‎Programming Arts 1‎ > ‎

Game Programming Primer

ゲームプログラミング入門


ここでは、演習として簡単なゲームを作成する。
プログラミングを簡単にするため制約を設ける。
+ 画像の代わりに文字を使う。 
+ 入力を待つ。(アクションはない)
つまり、アドベンチャーゲームを作る。

ゲームのクラスGameを作る。
public class Game {
    public static void main(String[] args) {
        Game game = new Game();
        game.play();
    }
    public void play() {
        // TODO
    }
}

playメソッドを作る。
public class Game {
    public static void main(String[] args) {
        Game game = new Game();
        game.play();
    }

    boolean isOver = false;

    public void play() {
        opening();
        while(! isOver) {
            display();
            input();
            update();
        }
        ending();
    }

    // オープニングの表示
    private void opening() {
    }

    // エンディングの表示
    private void ending() {
    }

    // 画面の表示
    private void display() {
    }

    // 入力
    private void input() {
    }

    // モデルの更新
    private void update() {
        isOver = true; // 最初のうちはすぐに終わる
    }
}

openingメソッドとendingメソッドを作る。
openingメッセージを表示して、改行を待つ。
    Scanner scan = new Scanner(System.in);

    // オープニングの表示
    private void opening() {
        System.out.println("Enter to start Game");
        scan.nextLine();
    }

    // エンディングの表示
    private void ending() {
        System.out.println("Fin");
    }

画面を表示する。しかし、その前にゲーム画面のモデルを作成する。
ゲーム画面は2次元の配列fieldで表す。fieldの数値に対応する画像(文字)をimageで表す。
    private static String[] image = { //
        " ", // 0 背景
        "~", // 1 海
        ".", // 2 野
        "Λ", // 3 山
        "田", // 4 町
        "♀", // 5 人
    };
    private int[][] field = { //
        { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, //
        { 0, 1, 1, 1, 1, 1, 1, 1, 0 }, //
        { 0, 1, 2, 2, 2, 2, 2, 1, 0 }, //
        { 0, 1, 2, 2, 3, 2, 2, 1, 0 }, //
        { 0, 1, 3, 3, 3, 3, 2, 1, 0 }, //
        { 0, 1, 2, 4, 3, 2, 2, 1, 0 }, //
        { 0, 1, 2, 2, 2, 2, 2, 1, 0 }, //
        { 0, 1, 1, 1, 1, 1, 1, 1, 0 }, //
        { 0, 0, 0, 0, 0, 0, 0, 0, 0 } };

    private void display() {
        for(int y=0; y<field.length; y++) {
            for(int x=0; x<field[y].length; x++) {
                System.out.print(image[field[y][x]]);
            }
            System.out.println();
        }
    }

入力メソッドを作る。
ここで、Scannerクラスは文字列を字句解析するクラスである。
nextLineで一行読み取り、nextで単語ごとに読み取る。
    private void update() {
        // モデルの更新
        // isOver = true; // 繰り返すのでいらない
    }

private void input() {
System.out.print("?");
String line = scan.nextLine();
Scanner sc = new Scanner(line);
String command = sc.next();
if (command.equalsIgnoreCase("up")) {
System.out.println("go up");
}
else if (command.equalsIgnoreCase("down")) {
System.out.println("go down");
}
else if (command.equalsIgnoreCase("left")) {
System.out.println("go left");
}
else if (command.equalsIgnoreCase("right")) {
System.out.println("go right");
}
else if (command.equalsIgnoreCase("end")) {
isOver = true;
}
else {
System.out.println("cannot "+command);
}
}

キャラクタクラスを作成する。
キャラクタのモデルは、マップ上の座標(x,y)を持つ。
getter/setterは自動生成する。
public class Character {
private int x, y;

public Character(int x, int y) {
this.x = x;
this.y = y;
}

public int getX() {
return x;
}

public void setX(int x) {
this.x = x;
}

public int getY() {
return y;
}

public void setY(int y) {
this.y = y;
}

}

キャラクタを表示する。
画面は背景と人物の仮想的なレイヤーに分かれている。
両方を合成した画面をmapとする。
Character player = new Character(3,3);

private void display() {
// 仮想画面の作成
int[][] map = new int[9][9];
// 背景を仮想画面に表示
for(int y=0; y<field.length; y++) {
for(int x=0; x<field[y].length; x++) {
map[y][x] = field[y][x];
}
}
// 人物を仮想画面に表示
map[player.getY()][player.getX()] = 5;
// 仮想画面の表示
for(int y=0; y<map.length; y++) {
for(int x=0; x<map[y].length; x++) {
System.out.print(image[map[y][x]]);
}
System.out.println();
}
}

キャラクタを動かす。
private void input() {
System.out.print("?");
String line = scan.nextLine();
Scanner sc = new Scanner(line);
String command = sc.next();
if (command.equalsIgnoreCase("up")) {
player.setY(player.getY() - 1);
}
else if (command.equalsIgnoreCase("down")) {
player.setY(player.getY() + 1);
}
else if (command.equalsIgnoreCase("left")) {
player.setX(player.getX() - 1);
}
else if (command.equalsIgnoreCase("right")) {
player.setX(player.getX() + 1);
}
else if (command.equalsIgnoreCase("end")) {
isOver = true;
}
else {
System.out.println("cannot "+command);
}
}

private void ending() {
display();
System.out.println("Fin");
}

private void update() {
// モデルの更新
// キャラクタを動かす(すでにinputで動かしている)
// ゴールの判定
if (field[player.getY()][player.getX()] == 4) {
isOver = true;
}
}

終わり方を2通り表示する。
ゴールしたらハッピーエンド(勝ち)、あきらめたらバッドエンド(負け)。
boolean goaled;

private void ending() {
display();
if (goaled)
System.out.println("You win!");
else
System.out.println("You lose!");
System.out.println("Fin");
}

private void update() {
// モデルの更新
// キャラクタを動かす(すでにinputで動かしている)
// ゴールの判定
if (field[player.getY()][player.getX()] == 4) {
isOver = true;
goaled = true;
}
}

地形によって移動を制限する。
山や海の移動を禁止する。
private static boolean[] canMove = {
false, // 0 背景
false, // 1 海
true, // 2 野
false, // 3 山
true, // 4 町
true, // 5 人
};

private void input() {
System.out.print("?");
String line = scan.nextLine();
Scanner sc = new Scanner(line);
String command = sc.next();
int x = player.getX();
int y = player.getY();
if (command.equalsIgnoreCase("up")) {
if (canMove[field[y-1][x]])
player.setY(y - 1);
}
else if (command.equalsIgnoreCase("down")) {
if (canMove[field[y+1][x]])
player.setY(y + 1);
}
else if (command.equalsIgnoreCase("left")) {
if (canMove[field[y][x-1]])
player.setX(x - 1);
}
else if (command.equalsIgnoreCase("right")) {
if (canMove[field[y][x+1]])
player.setX(x + 1);
}
else if (command.equalsIgnoreCase("end")) {
isOver = true;
}
else {
System.out.println("cannot "+command);
}
}

キャラクタによって表示を変える。
キャラクタにイメージを持たせる。get/setImageも作成する。
public class Character {
private int x, y;
private String image;

public Character(int x, int y, String image) {
this.x = x;
this.y = y;
this.image = image;
}

表示メソッドを変更する。
Character player = new Character(3,3,"");

private void display() {
// 仮想画面の作成
String[][] map = new String[9][9];
// 背景を仮想画面に表示
for(int y=0; y<field.length; y++) {
for(int x=0; x<field[y].length; x++) {
map[y][x] = image[field[y][x]];
}
}
// 人物を仮想画面に表示
map[player.getY()][player.getX()] = player.getImage();
// 仮想画面の表示
for(int y=0; y<map.length; y++) {
for(int x=0; x<map[y].length; x++) {
System.out.print(map[y][x]);
}
System.out.println();
}
}

キャラクタのサブクラスを作る。
Human 
public class Human extends Character {

public Human(int x, int y, String image) {
super(x, y, image);
}

public Human(int x, int y) {
this(x, y, "♀");
}

}

Hobbit
public class Hobbit extends Character {

public Hobbit(int x, int y, String image) {
super(x, y, image);
}

public Hobbit(int x, int y) {
this(x, y, "Ω");
}

}

キャラクタを変更する。
Character player = new Hobbit(3,3);

課題
このプログラムを自由に拡張してみよう。
アイデアを思いつかない人は以下を参考にしてもよい。拡張はこれらに限らない。
  • マップを広げよう。
  • マップを迷路にしてみよう。
  • 表示できるオブジェクトを増やそう。
  • 複数のマップを自由に行き来できるようにしよう。例えば、町に着いたら町のマップに切り替えてみよう。
  • コマンドを拡張しよう。
  • 2語以上の単語で会話できるようにしてみよう。例えば、upの代わりにgo upと命令できるようにする。
  • 複数の移動手段を使えるようにしよう。例えば、walk, swim, fly, warpなど。swimなら海の上も移動でき、flyなら海も山も移動できる。
  • 好きなキャラクタを作成してみよう。
  • 遭遇したら話を聞けるようにしてみよう。
  • 移動途中で他のキャラクタと遭遇するようにしてみよう。
  • 敵と戦ってみよう。例えば、強さ(STR)と体力(HP)を持ち、0からSTRまでの乱数で相手のHPを減らす。
  • 勝ったら経験値(EXP)をもらえるようにしてみよう。
  • HPを回復させる方法を考えよう。例えば、宿屋に泊ったり、キャンプしたりする。宿屋は安全に回復できるが、町にしかない。キャンプは屋外でできるが、襲われることもある。
  • レベルアップさせてみよう。
  • 新しい種族を作ろう。例えば、Dwarf, Elfなど。
  • 新しい職業を作ろう。例えば、Humanのサブクラスとする。人間だけが職業を持つ。
  • 所持金を持てるようにしよう。
  • 宝箱を見つけたら乱数で所持金が増えるようにしてみよう。
  • アイテムを所持できるようにしよう。
  • お店を作ってみよう。
  • アイテムを売り買いできるようにしてみよう。
  • 宝箱でアイテムが見つかるようにしてみよう。
  • 武器を装備できるようにしよう。
  • 面白いシナリオを考えよう。


ċ
pa1.zip
(7k)
上原稔,
2013/05/06 21:06
Comments