応用プログラミング 第9


この授業の目的

ゲームの応用例を紹介します.今回はシューティングゲームです.

 


出席メール

出席メールで「授業中の課題の実施」を確認します.メールを出した時刻,PCの場所(アドレス)を確認します.

to:jhoshino@esys.tsukuba.ac.jp
subject: 応用プログラミング 第9回.氏名,学籍番号
本文: なし(書かなくて結構です)

 


応用プログラムの紹介

今回はシューティングゲームを例にオブジェクト指向を利用したゲームプログラムの制御構造について説明します.
ソースコードとプログラム中で使う画像はhttp://www.entcomp.iit.tsukuba.ac.jp/java/9kai/src.zipにあります.実行結果

 

インタラクティブ(対話的)なシステムの基本的な制御構造

ゲームのように,自分の操作に対して反応が返ってくることを,「インタラクティブ」あるいは「対話的」であるといいます.ゲーム以外にも,パソコンで使っているワープロなどのほとんどのプログラムが対話的な操作を受け付けます.このプログラム例も複雑に見えるかもしれませんが,第7回で紹介したものと同じmainApplet.javaとmyCanvas.javaを使用しています.比較してみると分かりやすいと思いますが,基本部分は,

  • アプレットの定義
  • キャンバスとダブルバッファの記述
  • イベントの定義(Listnerインターフェースの継承)

です.キー入力やマウスなどのイベントが起きたときに,表示を書き換えています.基本的な制御部分はほぼ共通で,あとは個々のゲームに特有な描画や条件判定を加えるだけで,様々なゲームを作成することができます.

 

シューティングゲームの説明

今回のシューティングゲームでは自分で操作する戦闘機(Shipクラス)と敵(Enemysクラス),戦闘機から射出される弾丸(Bulletsクラス),背景(Starsクラス),これらを全てを管理するShootingGameクラスを定義しています.

Ship,Enemys,Bullets,Starsの共通の機能として個々の要素の(X,Y)座標とその数(num),移動処理(moveメソッド),描画処理(drawメソッド)が挙げられます.これらをGameObjectクラス

abstract class GameObject{
  int X[];
  int Y[];
  int num;
  GameObject(int num){
    this.num = num;
    X = new int[num];
    Y = new int[num];
  }
  abstract void move(int xSize,int ySize,int speed);
  abstract void draw(Graphics g,myCanvas canvas);
}

として定義しています.

Shipクラスを例にとって説明します.Shipクラスは以下のようになっています.

public class Ship extends GameObject{
  public int health;
  public int score;
  boolean shiftFlag;
  int xx,yy;
  Image image;

  //戦闘機の初期化処理
  Ship(int xSize,int ySize){
    super(1);
    health = 100;
    score = 0;
    X[0] = (xSize / 2) - 17;
    Y[0] = ySize - 80;
  }
  //戦闘機の移動処理
  void move(int xSize,int ySize,int speed){
    if (yy < Y[0] - 32) speed = 9;
    else speed = 6;

    if (shiftFlag) {
      if (yy < ySize & yy > 100)  Y[0] = yy - 16;
    }
    if (xx < xSize & xx > 1)  X[0] = xx - 16;
  }
  //戦闘機の描画処理
  void draw(Graphics g,myCanvas canvas){
    Dimension d = canvas.getSize();
    g.drawImage(image, X[0], Y[0], canvas);
    g.setColor(Color.yellow);
    g.drawString("Score = " + score, 5, 20);
    g.drawString("health = " + health + "%",d.width - 80, 20);
  }
  //マウス座標を取得
  void getMousePoint(int xx,int yy){
    this.xx = xx;
    this.yy = yy;
  }
}

コンストラクタでは戦闘機の初期化処理を行っています.superメソッドは上記のGameObjectクラスのコンストラクタを呼び出しています.
moveメソッドでは戦闘機の移動処理,drawメソッドでは戦闘機の描画処理を行っています.

戦闘機独自の機能として体力(health),得点(score),戦闘機画像(image) を定義しています.shiftFlagはshiftが押されているかどうかの判別フラグ,xx,yyはマウス座標を入れるための変数です.

他のEnemys,Bullets,Starsも同じようにGameObjectクラスを継承し,初期化処理をコンストラクタ,移動処理をmoveメソッド,描画処理をdrawメソッド,独自の変数とメソッドという構造になっています.


次にこれらを管理しているShootingGameクラスについて間単に説明していきます.
まず変数宣言部分は

  boolean outFlag;           //マウスがキャンバス外にあるとき衝突判定を行わないようにするためのフラグ
  int timeInc ;              //次の敵の作成までの時間

  Ship ship;
  Stars stars;
  Bullets bullets;
  Enemys enemys;

  public int xSize = 640, ySize = 480;
  public int speed = 6;

  Applet myApplet;
  myCanvas canvas;
となっています.xSize,ySizeは描画領域のサイズを示しています.Ship,Stars,Bullets,Enemyクラスのオブジェクトもここで定義しています.
次に初期化(init)メソッドを示します.
public void init (Applet myApplet,myCanvas canvas) {
    this.myApplet = myApplet;
    this.canvas = canvas;

    outFlag = false;

    timeInc = 0;
    ship = new Ship(xSize,ySize);
    enemys = new Enemys();
    stars = new Stars(xSize,ySize);
    bullets = new Bullets();

    ship.image = myApplet.getImage(myApplet.getCodeBase(), "./image/ship.gif");
    enemys.image1 = myApplet.getImage(myApplet.getCodeBase(), "./image/enemy1.gif");
    enemys.image2 = myApplet.getImage(myApplet.getCodeBase(), "./image/enemy2.gif");
    enemys.exImage = myApplet.getImage(myApplet.getCodeBase(), "./image/ex.gif");
    bullets.image1 = myApplet.getImage(myApplet.getCodeBase(), "./image/b1.gif");
    bullets.image2 = myApplet.getImage(myApplet.getCodeBase(), "./image/b2.gif");
  }


各オブジェクトやフラグ,値の初期化 ,画像の読み込みを行っています.再スタートの際にもこのメソッドを使用しています.

今回も第6回や第7回と同様にmainApplet.javaとmyCanvas.javaを使用しています.制御部分となるmyCanvasオブジェクトのrun()で呼ばれるメソッドを同じ名前のrunメソッドとして定義してます.

 public void run() {
    if (ship.health == 0) init(myApplet,canvas);  //shipの体力が0になった時点で再スタート
    timeInc++;
    if (timeInc == enemys.randomTime) {
      enemys.newEnemy(xSize,timeInc);
      timeInc = 0;
    }
    enemys.move(xSize,ySize,speed);
    stars.move(xSize,ySize,speed);
    bullets.move(xSize,ySize,speed);
    collisions();  //衝突判定
  }

newEnemyは新しく敵を作るメソッドです.Math.random()を用いてランダムに作成しています.
moveメソッドで各オブジェクトを移動させています.ここでshipの移動がないのはマウスで移動させるからです.
collisionsメソッドでは「戦闘機と弾」,「弾と敵」,「敵と戦闘機」の衝突判定を行っています.

全てのオブジェクトの描画を表すdrawメソッドを定義しています.
public void draw(Graphics g) {
    stars.draw(g,canvas);
    bullets.draw(g,canvas);
    enemys.draw(g,canvas);
    ship.draw(g,canvas);
}


ここでは各オブジェクトの描画(draw)メソッドを呼び出しているだけです.
マウスのイベント処理部分について示します.

  public void mouseDragged (MouseEvent e) {
    if(e.isControlDown()) bullets.controlFlag = true;
    else bullets.controlFlag = false;
    bullets.rdischarge(ship.X[0],ship.Y[0]);

    ship.getMousePoint(e.getX(),e.getY());
    if(e.isShiftDown()) ship.shiftFlag = true;
    else ship.shiftFlag = false;
    ship.move(xSize,ySize,speed);
  }
  public void mouseMoved (MouseEvent e) {
    ship.getMousePoint(e.getX(),e.getY());
    if(e.isShiftDown()) ship.shiftFlag = true;
    else ship.shiftFlag = false;
    ship.move(xSize,ySize,speed);

  }
  public void mouseEntered (MouseEvent e) {outFlag = true;}
  public void mouseExited (MouseEvent e) {outFlag = false;}
  public void mousePressed (MouseEvent e) {
    if(e.isControlDown()) bullets.controlFlag = true;
    else bullets.controlFlag = false;
    bullets.discharge(ship.X[0],ship.Y[0]);
  }
弾丸の発射処理や戦闘機の移動処理を行っています.isControlDown()やisShiftDownはキーボードのCtrlやShiftが押されているかを判別しています.

操作説明

マウスクリック :弾丸発射
Ctrlを押しているときのマウスクリック :スピードの速い弾丸の発射
マウスドラッグ :弾丸の連続発射
Ctrlを押しているときのマウスドラッグ :スピードの速い弾丸の連続発射
マウスの移動 :x軸に平行に移動
shiftが押されているときのマウスの移動 :y軸方向にも移動できる


練習課題

(練習課題はやや難しいため,できたところまでで結構です.)

上記のシューティングゲームにクラスBossEnemyを追加して,ボスキャラクタが出てくるように改良して下さい.

  1. クラスBossEnemyは,抽象クラスGameObjectを継承すること
  2. クラスBossEnemyに次の機能を実装して下さい

提出先のアドレスはjhoshino@esys.tsukuba.ac.jpです.Subject欄に「応用プログラミング 第9回課題,氏名,学籍番号」を書いて下さい.本文にソースコード,実行結果,考察を記載して下さい.

締め切りは次の授業の開始時まで.ソースコードはメール本文へのコピーあるいは添付どちらでも結構です.