画像が変化するゲームの構造(FxSprite)

JavaFxを使ったゲームライブラリ

1つのゲームキャラクタを管理するクラスとして、次のFxSpriteクラスを定義した。
座標(x,y)、描画の回転角度(angle)、回転の中心点座標(cx , cy)の情報を持って、 自身のキャラクタを描画するdrawメソッドと、自身の振る舞いを定義するactionメソッドを持つ。
drawメソッドとactionメソッドは、作品のフレームレイト用タイマーなどで、繰り返して呼び出されることを前提している。

package application;
import java.net.URL;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;

public class FxSprite {//スプライト
	public javafx.scene.image.Image image;
	public double x = 0, y=0;	//位置(左上座標)
	//public static String myName;//IPアドレス:ポート番号

	public double angle;//回転角度(単位:デグリー 1回転が360度)-----------
	public double cx =0, cy=0;	//回転角度 の中心座標-----------


	public FxSprite(Image image, double x, double y){
		this.image = image;
		this.x = x;
		this.y = y;
	}
	public void draw(GraphicsContext gc){
		gc.save();     // 座標系記憶
		gc.translate(this.x, this.y);//描画位置(左上端)指定

		//スケールと回転処理
		gc.scale(1, 1);//スケール変更なし
		//もとの画像の点(this.cx, this.cy)を中心に回転させる。
		gc.translate(this.cx, this.cy);
		gc.rotate(this.angle);
		gc.translate(-this.cx, -this.cy);

		gc.drawImage(this.image, 0, 0);//描画する。
		gc.restore();  // 座標系戻す
	}
	public void action() { }
}

このクラスを利用する作品で、次のようなLinkedHashMapのインスタンス変数を用意する。
LinkedHashMap mapSprite = new LinkedHashMap ();
これに作品で使うすべてのFxSpriteインスタンスを、それぞれに付けるユニークな名前をキーにして登録します。
そして、タイマーで、mapSpriteの全ての要素のdarwメソッドで描画して、 全ての要素のactionメソッドを呼び出すようにして利用します。

上記FxSpriteを利用したJavaFx作品例

この例として、2つの銛の画像("harpoon2.png")を使うFxSpriteの継承クラス(MySprite)のインスタンスを用意し、 クリック位置へ10カウントで移動する作品例で示します。
なお、2つのMySpriteのインスタンスは、 "A"と"B"の名前を付けて、mapSpriteで管理します。
そしてこの文字列を表示する2つのRadioButtonを 用意して選択した方の文字列のMySpriteを 移動する対象にする。

下記ではPaneを継承したMyPaneを定義して、 この子としてCanvasを用意して、それを 描画対象にしている。

package application;

import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Random;

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.RadioButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.image.Image;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.util.Duration;

class MySprite extends FxSprite {//スプライト
	static Image image;
	static {
		URL url = SpriteA.class.getResource("harpoon2.png");//画像素材を取得
		image = new Image(url.toExternalForm());
	}
	Point2D targetPt;//移動目標の点
	int downCount;//上記に移動するまでのフレーム回数
	MySprite(){
		super(image,0,0);
		this.cx = 37;
		this.cy = 37;
	}
	@Override
	public void action() {//downCount回で、targetPtの位置まで移動するための1回アクション
		if(targetPt != null){
			if(downCount == 1){
				this.x = targetPt.getX();
				this.y = targetPt.getY();
				targetPt = null;
			} else {
				double dx = (targetPt.getX() - this.x)/downCount;//移動量算出
				double dy = (targetPt.getY() - this.y)/downCount;
				this.angle = Math.atan2(dy, dx) * 180 / Math.PI;
				this.x += dx;
				this.y += dy;
			}
			downCount--;
		}
	}
}

class MyPane extends Pane{
	Random rnd = new Random();
	ToggleGroup togleGroup = new ToggleGroup();
	RadioButton rbA = new RadioButton("A");;
	RadioButton rbB = new RadioButton("B");;
	Canvas canvas = new Canvas(400,400);//(400,400)のサイズのキャンバス
	GraphicsContext gc;//上記canvasに描画するとめのツールを管理する
	LinkedHashMap <String,FxSprite>mapSprite = new LinkedHashMap <String,FxSprite>();

	EventHandler<ActionEvent> actionHandler = new EventHandler<ActionEvent>(){
		@Override
		public void handle(ActionEvent event) {// アニメーション用のタイマー処理
			//System.out.println("actionHandler");
			draw();
			action();
		}
	};
	KeyFrame keyFrame = new KeyFrame(Duration.millis(100),actionHandler);//0.1秒間の指定
	Timeline timer = new Timeline(keyFrame);

	MyPane(){
		this.getChildren().addAll(this.canvas,this.rbA,this.rbB);
		this.rbB.setLayoutX(100);//位置変更
		this.rbA.setToggleGroup(this.togleGroup);
		this.rbB.setToggleGroup(this.togleGroup);

		mapSprite.put("A", new MySprite());
		mapSprite.put("B", new MySprite());

		this.timer.setCycleCount(Timeline.INDEFINITE);//stopされるまで無期限で繰り返す指定
		this.timer.play();//タイマースタート

		this.setOnMousePressed(e->{
			RadioButton toggle = (RadioButton)this.togleGroup.getSelectedToggle();
			if(toggle == null) return;
			MySprite sprite = (MySprite)mapSprite.get(toggle.getText());
			sprite.targetPt = new Point2D( e.getX()-37, e.getY()-37);
			sprite.downCount = 10;//10回でsprite.targetPtまで移動する指定
		});
	}
	synchronized void draw(){//各スプライトをキャンバスへの描画処理
    	gc =canvas.getGraphicsContext2D();//上記キャンバスへの描画ツール取得
    	gc.clearRect(0, 0, 400, 400);
    	for(String key: mapSprite.keySet()){
    		FxSprite sprite = mapSprite.get(key);
    		sprite.draw(gc);
    	}
	}
	synchronized void action(){//各スプライトを動かす
		String [] keys = mapSprite.keySet().toArray(new String[0]);
    	for(int i=0; i<keys.length; i++){
    		FxSprite sprite = mapSprite.get(keys[i]);
    		if(sprite != null)sprite.action();
    	}
	}
}
public class FxSpriteTest extends Application {
	MyPane myPane = new MyPane();

	@Override
	public void start(Stage primaryStage) throws Exception {
		Scene scene = new Scene(myPane,400,400);
		primaryStage.setScene(scene);
		primaryStage.show();
	}

	public static void main(String[] args) {
		launch(args);//上記atartを起動する。
	}
}