【JavaFX:横スクロールアクションゲーム】プレイヤーキャラ、敵キャラの表示と動きを実装
キャラクタークラスを作成し、プレイヤーキャラを表示させます。
| 前の記事 | 横スクロールアクションゲーム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;
}
});
}
}
本稿の全ソースはこちらです。










ディスカッション
コメント一覧
まだ、コメントがありません