【Java:的当てゲーム】的を出現させる

2018年8月16日

前の記事 的当てゲーム制作Top 次の記事

今回は的を出現させるプログラムを作成していきたいと思います。

的クラスの作成

まずは的を表すクラスを作成していきます。

Targetという名前のクラスにでもしておきますか。

的はCircleクラスを利用し、移動処理と描画処理のメソッドを作成しておきます。

使用するCircleクラスはライブラリのクラスを利用しますが、JavaFXのCircleクラスやawtのEllipseなどを利用しても問題ありません。

では的のクラスを作成していきます。

Target.java

import java.awt.Graphics;

import com.nompor.gtk.geom.Circle;

//的を表すクラス
public class Target{
	private Circle circle;
	private int mx;
	private int my;

	public Target(int x, int y, int mx, int my) {

		//円オブジェクト(引数x,yは画像の左上座標を想定しているため+50が的の中央
		circle = new Circle(x+50, y+50, 25);

		//移動量
		this.mx = mx;
		this.my = my;
	}

	//描画
	public void draw(Graphics g) {

		circle.cx+=mx;
		circle.cy+=my;

		g.drawImage(AppManager.getTargetImage()
				, circle.getICX()-50, circle.getICY()-50, circle.getICX()+50, circle.getICY()+50
				, 0, 0, 100, 100
				, null);

	}
	//的の左端座標を返す
	public int getLeft() {
		return circle.getILeft();
	}
	//的の上端座標を返す
	public int getTop() {
		return circle.getITop();
	}
	//的の右端座標を返す
	public int getRight() {
		return circle.getIRight();
	}
	//的の下端座標を返す
	public int getBottom() {
		return circle.getIBottom();
	}
}

コンストラクタのx,yは画像の左上の座標を指定する形式にしました。

new Circle(x+50, y+50, 25);のように+50しているのはCircleクラスが円の中心のx,y座標を指定するものだからです。

対して、画像は1枚100×100なのでその差分を考慮して+50というわけです。

引数mx,myは移動量を表します。そのまんまx移動量,y移動量です。drawメソッドで、座標加算するようにしています。

次にdrawImageですが、もとの的画像は以下のように複数枚が結合されているため、一番左端の画像だけ表示するように引数を指定しています。

これで、的クラスはひとまずOKかなあ。

的を出現させる

最初に的のセッティングプログラムを作成します。

的は、上下に20個左右に20個ランダム配置します。的の移動方向は上なら下へ、下なら上へ、右なら左、左なら右へ移動させます。

以下のイメージのように的を配置し、移動させるプログラムを作成したいです。

さて、ここでゲーム制作では、かなりお世話になるであろうRandomクラスを使用していきます。使い方は以下の記事にて説明しております。

また、二つの引数の範囲の乱数を取得するrandBetween関数をGTKMathクラスに定義しておきましたので、こっちを使用するほうが少しプログラムが楽になるかもしれません。

次に配置オブジェクトの管理をどうするかですが、これはArrayListに全部持たせましょうか。

こんな感じでコンストラクタに配置プログラムを作成しました。

GameMode.java
	//全的のセッティングリスト
	ArrayList initTargetList = new ArrayList<>();
	
	//初期化コンストラクタ
	public GameMode() {
		Random rand = new Random();
		
		//上下にランダムで的をセッティング
		for ( int i = 0;i < 20;i++ ) {
			boolean r = rand.nextBoolean();
			int y = -50;
			int my = 2;
			if ( r ) {
				y = 600;
				my = -2;
			}
			Target t = new Target(rand.nextInt(700), y, 0, my);
			initTargetList.add(t);
		}
		//左右にランダムで的をセッティング
		for ( int i = 0;i < 20;i++ ) {
			boolean r = rand.nextBoolean();
			int x = -50;
			int mx = 2;
			if ( r ) {
				x = 800;
				mx = -2;
			}
			Target t = new Target(x, rand.nextInt(450) + 50, mx, 0);
			initTargetList.add(t);
		}
		//的の順番をシャッフル
		Collections.shuffle(initTargetList);

	}

次に的を画面外から画面内側に移動させるのですが、既に移動処理はTargetクラス内のdrawメソッドに記述しているので、drawメソッドを呼び出せばいいです。

ただし、このままリストを全ループさせてしまうと一度に大量の的が出現してしまいますので、もう一つのリストを出現用リストとして定義し、タイマーの処理で1秒おきくらいで出現用リストへ移動するようにプログラムします。

今回の修正でプログラムはこんな感じになりました。

GameMode.java
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;

import com.nompor.gtk.GameView;
import com.nompor.gtk.animation.FadeAnimation;
import com.nompor.gtk.animation.ParallelAnimation;
import com.nompor.gtk.animation.ScaleAnimation;
import com.nompor.gtk.draw.DrawLabel;
import com.nompor.gtk.draw.GraphicsUtil;

public class GameMode extends GameView{

	boolean isGameStart = false;

	//ゲームカウント
	int gameCountDown=30;

	//終了後の遅延カウント
	int endDelayCount = 2;

	//成績を記録
	int combo = 0;
	int maxCombo=0;
	int score = 0;
	int breakNum = 0;

	//上部のステータス用フォント
	Font statusFont = new Font(Font.MONOSPACED, Font.BOLD, 30);

	//開始カウントダウン用フォント
	Font countDownFont = new Font(Font.MONOSPACED, Font.BOLD, 120);

	//開始前カウント
	int prepareCountDown=3;

	//現在画面に表示されている的リスト
	ArrayList viewTargetList = new ArrayList<>();

	//全的のセッティングリスト
	ArrayList initTargetList = new ArrayList<>();

	//STARTアニメーションラベル
	DrawLabel startLabel;
	ParallelAnimation startAnime;

	//タイマー
	Timer timer = new Timer();
	TimerTask task = new TimerTask() {

		@Override
		public void run() {
			if ( prepareCountDown > 0 ) {
				//ゲーム開始前
				prepareCountDown--;
				if ( prepareCountDown == 0 ) {
					AppManager.startSE();
				} else {
					AppManager.countSE();
				}
			} else if ( gameCountDown > 0 ) {

				//ゲーム中は一秒に1,2個の的を出現させる
				isGameStart = true;
				gameCountDown--;
				if ( initTargetList.size() > 0 ) {
					viewTargetList.add(initTargetList.remove(initTargetList.size() - 1));
				}
				if ( initTargetList.size() > 0 && gameCountDown % 2 == 0 ) {
					viewTargetList.add(initTargetList.remove(initTargetList.size() - 1));
				}

			} else {
				//タイマーの終了
				timer.cancel();
			}
		}
	};

	//初期化コンストラクタ
	public GameMode() {
		Random rand = new Random();

		//上下にランダムで的をセッティング
		for ( int i = 0;i < 20;i++ ) {
			boolean r = rand.nextBoolean();
			int y = -50;
			int my = 2;
			if ( r ) {
				y = 600;
				my = -2;
			}
			Target t = new Target(rand.nextInt(700), y, 0, my);
			initTargetList.add(t);
		}
		//左右にランダムで的をセッティング
		for ( int i = 0;i < 20;i++ ) {
			boolean r = rand.nextBoolean();
			int x = -50;
			int mx = 2;
			if ( r ) {
				x = 800;
				mx = -2;
			}
			Target t = new Target(x, rand.nextInt(450) + 50, mx, 0);
			initTargetList.add(t);
		}
		//的の順番をシャッフル
		Collections.shuffle(initTargetList);

		//タイマーの起動
		timer.scheduleAtFixedRate(task, 1000, 1000);

	}

	//windowにパネルが配置された後に呼び出される
	public void start() {
		//windowにパネルが配置された後ならGraphicsオブジェクトを取得できる
		startLabel = new DrawLabel(getGraphics(), "START", countDownFont, AppManager.getW()/2, AppManager.getH()/2);
		startAnime = new ParallelAnimation(
				new ScaleAnimation(startLabel, 0.02)
				, new FadeAnimation(startLabel, -0.03f)
		);
	}

	public void draw(Graphics g) {

		int width = AppManager.getW();
		int height = AppManager.getH();

		//アンチエイリアスの有効化
		GraphicsUtil.setTextAntialiasing(g, true);

		//背景の描画
		g.drawImage(AppManager.getBackImage(), 0, 0, null);

		if ( isGameStart ) {

			//ゲーム中の描画処理
			for ( int i = viewTargetList.size() - 1;i >= 0;i-- ) {
				Target t = viewTargetList.get(i);

				//Targetオブジェクトの削除フラグがtrueか画面外に出たら削除
				if ( t.getRight() < -50
					|| t.getBottom() < -50
					|| t.getLeft() > width+50
					|| t.getTop() > height+50) {

					viewTargetList.remove(i);
				} else {
					//Targetの移動処理と描画
					t.draw(g);
				}
			}
		} else {
			g.setColor(Color.WHITE);

			if ( prepareCountDown == 0 ) {
				//STARTアニメーション
				startAnime.update();
				startLabel.draw(g);
			} else {
				//カウントダウンの描画
				g.setFont(countDownFont);
				GraphicsUtil.drawCenteringString(g, ""+prepareCountDown, width / 2, height / 2);
			}
		}

		//ステータスの描画
		g.setColor(Color.BLACK);
		g.fillRect(0, 0, width, 50);

		g.setColor(Color.WHITE);
		g.setFont(statusFont);
		GraphicsUtil.drawCenteringString(g, "破壊数:"+breakNum, 400, 25);
		GraphicsUtil.drawCenteringString(g, "得点:"+score, 200, 25);
		GraphicsUtil.drawCenteringString(g, "コンボ:"+combo, 600, 25);
	}
}
実行結果

これで的を出現させるプログラムは完成です。

ソースコードが一気に増えてしまいましたねぇ・・・あんまり長くなるようならGameViewクラスを入れ子にしたらいいかもしれんね。ライブラリに入れ子版GameViewクラス追加しても良いかもしれんな。

今回はArrayListでオブジェクト管理していますが、このケースだとLinkedListでも良さそうですね。そっちのほうがプログラムもわかりやすくなりそうです。ただIterator使用はダルイんよねぇ~。

Java

Posted by nompor