【JavaFX:横スクロールアクションゲーム】プレイヤーキャラ、敵キャラの表示と動きを実装

2018年8月22日

キャラクタークラスを作成し、プレイヤーキャラを表示させます。

前の記事 横スクロールアクションゲームTop 次の記事

CharaObject

CharaObjectクラスはすべてのキャラクターオブジェクトが継承するクラスです。このゲームでは、プレイヤーキャラと敵キャラに分岐させます。

汎用性を持たせるなら、プレイヤーという操作込みのクラスには分けないほうが良い気がしますが、今回は一緒こたにしちゃいます。

CharaObjectクラスはupdateメソッドを実装し、必ず処理させたい内容を記述します。実装しておきたい処理は下記。

・移動前の座標を保持する処理
・無敵状態の処理
・抽象メソッド、moveの呼び出し

まずは移動前座標を保持する理由ですが、ブロック判定でより簡単に判定処理を実装させることができます。移動前座標を保持して判定を行う方法は他のゲームを制作する時も役にたつと思います。

次に無敵状態の処理ですが、この無敵状態がないと、フレームごとにキャラクターがダメージを受けてしまいます。その他にも前回ダメージからの時間を計算して実装する方法もありますが、無敵時間を指定するほうが簡単そうなのでこっちを採用。

抽象メソッド、moveはオーバーライド先で個々の動きを実装していきます。Playerならマウス右クリックでジャンプするとかかな。updateメソッド内で呼び出すようにしておきます。

これ以外にもキャラクターが今地上にいるか、空中にいるかなどのフラグも保持します。

キャラクターの落下処理はサブクラスで実装予定ですが、アクションゲームでは下へ落下する動きがデフォルトの場合が多くなるので、この処理もCharaObjectに実装したほうが楽かもしれませんね。

	//キャラの無敵時間
	int invisibleCount;

	double prel;
	double pret;
	double prer;
	double preb;

	//空中にいるかどうか
	protected boolean isAir;

	//地上にいるかどうか
	protected boolean isGround;

	public CharaObject(Node viewNode, Node hitNode) {
		super(viewNode, hitNode);
		positionInit();
	}

	@Override
	public void update() {
		if ( isInvisible() ) {
			invisibleCount--;
			getViewNode().setOpacity(0.5);
		} else {
			getViewNode().setOpacity(1);
		}

		//移動前の座標を保持しておく
		positionInit();
		move();
	}

	//現在の座標を記録しておく
	protected void positionInit() {
		Bounds b = getHitNode().getBoundsInParent();
		prel = b.getMinX();
		pret = b.getMinY();
		prer = b.getMaxX();
		preb = b.getMaxY();
	}

	//無敵状態かを返す
	public boolean isInvisible() {
		return invisibleCount > 0;
	}

	//キャラがダメージを負った時に呼び出すメソッド
	public void onDamage() {}

	//移動処理を行う抽象メソッド
	public abstract void move();

Player

Playerクラスはプレイヤーキャラを表すクラスで、マウス操作を検出し、移動処理やジャンプを実装します。

	double fallSpeed = 0;
	int speed = 3;
	boolean isHighJump = false;

	public Player(Node viewNode, Node hitNode) {
		super(viewNode, hitNode);
		((ImageAnimationView)viewNode).setAnimationRange(1, 4);
	}

	@Override
	public void move() {
		boolean isRight = PlayerControllerManager.isRight();

		if ( isGround ) {
			//地上だったら落下速度0
			fallSpeed = 0;

			if ( isHighJump || PlayerControllerManager.isJump()) {
				//ジャンプしたなら落下速度を逆にする
				fallSpeed = isHighJump ? -20 : -15;
				AppManager.playSE("se4");
				isHighJump = false;
			}
		} else if ( isAir ) {
			if ( fallSpeed < 15 ) {
				//空中だったら落下速度アップ
				fallSpeed++;
			}
		}

		//移動処理と画像反転処理
		ImageAnimationView img = (ImageAnimationView) getViewNode();
		if ( isRight ) {
			//右へ移動
			moveX(speed);
			img.setScaleX(1);
		}

		//画像の切り替え処理
		if ( isAir ) {
			if ( img.getStatus() == Animation.Status.RUNNING ) {
				img.stop();
			}
			img.setIndex(img.getMaxIndex());
		} else if ( isRight ) {
			if ( img.getStatus() != Animation.Status.RUNNING ) {
				img.setIndex(1);
				img.play();
			}
		} else {
			if ( img.getStatus() == Animation.Status.RUNNING ) {
				img.stop();
			}
			img.setIndex(0);
		}

		//上下移動処理
		moveY(fallSpeed);
	}

	public void onDamage() {
		invisibleCount = 120;
		if ( speed > 5 ) {
			speed-=3;
		} else if ( speed > 4 ) {
			speed-=2;
		} else if ( speed > 1 ) {
			speed--;
		}
	}

	@Override
	public void mouseAction() {
		//クリックされたらハイジャンプ
		if ( isGround ) {
			isHighJump = true;
		}
	}

PlayerControllerManager

マウス操作はPlayerControllerManagerクラスにて実装します。このクラスは単純にマウス操作を検出し、プレイヤーの操作内容を取得できるだけのクラスです。

Playerクラスから、このクラスへアクセスし、移動処理を実装していきます。

	//マウスマネージャー
	static MouseManagerFX mMng = GTKManagerFX.getMouseManager();

	//このフラグがオフの場合は常にキー入力を無効化する
	static boolean isActive=true;

	static {

		//検出マウスボタンの登録
		mMng.regist(MouseButton.PRIMARY);
		mMng.regist(MouseButton.SECONDARY);
	}

	//このフラグがオフの場合は常にアクションフラグを無効化する
	public static void setActive(boolean isActive) {
		PlayerControllerManager.isActive =isActive;
	}

	//ジャンプすべきか()
	public static boolean isJump() {
		return isActive && mMng.isPress(MouseButton.SECONDARY);
	}

	//ジャンプボタンを押下し続けているか
	public static boolean isJumping() {
		return mMng.isDown(MouseButton.SECONDARY);
	}

	//右移動すべきか
	public static boolean isRight() {
		return isActive;
	}
	//マウスの左クリック
	public static boolean isMPressLeft() {
		return isActive && mMng.isPress(MouseButton.PRIMARY);
	}
	//マウスのX座標を取得します
	public static double getMX() {
		return mMng.getPoint().x;
	}
	//マウスのY座標を取得します
	public static double getMY() {
		return mMng.getPoint().y;
	}

Enemy

敵キャラを表すスーパークラスです。

敵キャラはデフォルトでは受けると倒せるようにしておきます。onDamageメソッドはキャラがダメージを受けたときに呼び出されます。

	@Override
	public void onDamage() {
		//ダメージ受けたら倒される
		isAlive = false;
	}

各種敵キャラはこのクラスを継承して個々の処理を記述します。

今回は敵キャラは2体のみ

・スライム
 Slimeクラスとして定義し、右側へ地上を移動するだけの単純処理

・バード
 Birdクラスとして定義し、右側へ空中移動するだけの単純処理

例えばスライムクラスのmoveメソッドはこんな感じにしました。

	@Override
	public void move() {
		if ( isGround ) {
			//地上だったら落下速度0
			fallSpeed = 0;
		} else if ( isAir ) {
			//空中だったら落下速度アップ
			fallSpeed++;
		}

		//上下移動処理
		moveY(fallSpeed);

		//左右移動
		moveX(speed);
	}

ステージの表示とキャラクターの実装サンプル

最後にプレイヤーと敵キャラを表示させる例を紹介しておきましょう。

import static com.nompor.gtk.fx.GTKManagerFX.*;

import com.nompor.gtk.fx.GTKManagerFX;
import com.nompor.gtk.fx.GameViewFX;
import com.nompor.gtk.fx.animation.ImageAnimationView;
import com.nompor.gtk.fx.image.ImageManagerFX;

import javafx.animation.Animation;
import javafx.application.Application;
import javafx.scene.image.ImageView;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Test2 extends Application {

	public static void main(String[] args) {
		launch(args);
	}

	@Override
	public void start(Stage primaryStage) throws Exception {
		final int WIDTH = 800, HEIGHT = 600;
		GTKManagerFX.start(primaryStage,WIDTH, HEIGHT);

		//GameViewFXはGroupを継承したクラスで、processメソッドはゲームループの処理を実装する
		GTKManagerFX.changeView(new GameViewFX() {

			ImageManagerFX imgMng = GTKManagerFX.getImageManager();
			Player p;
			Enemy e;
			GameField fields;

			@Override
			public void start() {

				//ウィンドウ表示領域800*600に合わせて二次元配列構築
				final int ROW=HEIGHT/50,COL=WIDTH/50;
				Field[][] field = new Field[ROW][COL];

				//画面一番下に地面ブロック配置
				final int UNDER_INDEX = (ROW-1);
				for ( int i = 0;i < COL;i++ ) {
					Rectangle rect = new Rectangle(i * 50,UNDER_INDEX*50,50,50);//当たり判定Node
					ImageView imgView = new ImageView(imgMng.getImage("img/jimen.png"));//表示Node
					imgView.setTranslateX(rect.getX());
					imgView.setTranslateY(rect.getY());
					field[UNDER_INDEX][i] = new Block(imgView, rect);
				}

				//画面一番下から二つ上にいくつかブロック配置
				final int BLOCK_INDEX = (ROW-3);
				for ( int i = 5;i < 11;i++ ) {
					Rectangle rect = new Rectangle(i * 50,BLOCK_INDEX*50,50,50);//当たり判定Node
					ImageView imgView = new ImageView(imgMng.getImage("img/block.png"));//表示Node
					imgView.setTranslateX(rect.getX());
					imgView.setTranslateY(rect.getY());
					field[BLOCK_INDEX][i] = new Block(imgView, rect);
				}

				//装飾を追加
				ImageView imgView = new ImageView(imgMng.getImage("img/kusa.png"));//表示Node
				final int idxRow = UNDER_INDEX-1,idxCol = 3;
				imgView.setTranslateX(idxCol*50);
				imgView.setTranslateY(idxRow*50);
				field[UNDER_INDEX-1][3] = new Field(imgView);

				//GameFieldオブジェクトの構築
				fields = new GameField(field);
				Field[][] fieldList = fields.getFieldList();
				for ( int i = 0;i < fieldList.length;i++ ) {
					for ( int j = 0;j < fieldList[i].length;j++ ) {
						Field fld = fieldList[i][j];
						if ( fld != null ) {
							getChildren().add(fld.getViewNode());
						}
					}
				}

				//プレイヤーの作成
				ImageAnimationView view = createImageAnimationView(Duration.millis(500), imgMng.getImage("img/mogmol.png"), 50, 50);//表示ノード
				view.setCycleCount(Animation.INDEFINITE);
				view.setIndex(0);
				Rectangle rect = new Rectangle(10,510,30,37);//当たり判定
				view.setTranslateY(500);
				p = new Player(view, rect);
				view.play();

				//プレイヤーの表示
				getChildren().add(p.getViewNode());

				//敵の作成
				ImageAnimationView img = createImageAnimationView(Duration.millis(300), imgMng.getImage("img/slime.png"),50,50);//表示ノード
				img.setCycleCount(Animation.INDEFINITE);
				img.setIndex(0);
				Rectangle r = new Rectangle(300+8,550+26,35,20);//当たり判定
				img.setTranslateX(300);
				img.setTranslateY(550);
				e = new Slime(img, r);

				//敵表示
				getChildren().add(e.getViewNode());
			}

			@Override
			public void process() {
				//ゲームループ
				move(p);
				move(e);
			}

			private void move(CharaObject o) {
				o.moveX(1.5);
				o.moveY(7);

				//ウィンドウ表示領域より下に行ったら止まるようにする
				double y = o.getHitNode().getBoundsInParent().getMaxY();
				if(y>=HEIGHT) {
					o.moveY(-(y-HEIGHT));
					o.isGround = true;
				} else {
					o.isGround = false;
				}
				o.isAir = !o.isGround;
			}
		});
	}

}
実行結果

本稿の全ソースはこちらです。

JavaJavaFX

Posted by nompor