【JavaFX】当たり判定

2018年1月18日

本稿はJavaFXで当たり判定に使えそうなメソッドを紹介します。

目当ての判定ができそうにない場合は、swingで紹介したほうを使うのもいいかもしれません。

処理速度を考慮すると、矩形同士の判定円同士の判定をオススメします。他の判定はどうしても実装したいときに利用しましょう。矩形同士の当たり判定はJavaFXでもメソッドは用意されています。

矩形同士の当たり判定

全てのNodeオブジェクトは矩形同士の当たり判定を実装できます。

Nodeオブジェクトのintersectsメソッドを呼び出します。引数BoundsはNodeオブジェクトのgetBoundsInLocalメソッドで取得できます。

※2018/01/18追記
tranlateX等の変換系の処理を考慮した判定をする場合はgetBoundsInParentを使用してください。

画像だろうが円だろうが、テキストだろうが矩形として判定します。


import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class Test extends Application{
	@Override
	public void start(Stage primaryStage) throws Exception {
		View v = new View();
		Scene scene = new Scene(v, 400, 300);
		primaryStage.setScene(scene);
		primaryStage.show();
	}
}

class View extends Group{
	public View() {
		Rectangle rect1 = new Rectangle(100, 120, 100, 100);
		Rectangle rect2 = new Rectangle(70, 140, 100, 100);
		rect1.setStroke(Color.RED);
		rect1.setFill(null);
		rect2.setStroke(Color.BLUE);
		rect2.setFill(null);
		getChildren().add(rect1);
		getChildren().add(rect2);
		Text text = new Text("当たっていません。");
		text.setFont(new Font(20));

		//矩形同士の当たり判定
		if ( rect1.intersects(rect2.getBoundsInLocal()) ) {
			text.setText("当たっています。");
			text.setFill(Color.RED);
		}
		text.setY(100);
		text.setX(200 - text.getBoundsInLocal().getWidth() / 2);
		getChildren().add(text);
	}
}
実行結果

点とオブジェクトの不透明部分との当たり判定

NodeオブジェクトのcontainsメソッドはNodeの不透明部分を感知して点と当たり判定できます。

例えば画像の不透明部分をクリック判定として使用できます。


import java.io.File;

import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class Test extends Application{
	@Override
	public void start(Stage primaryStage) throws Exception {
		View v = new View();
		Scene scene = new Scene(v, 400, 300);
		primaryStage.setScene(scene);
		primaryStage.show();
		scene.setOnMouseClicked(e -> v.mouseClicked(e));
	}
}

class View extends Group{
	Text text = new Text(0, 100, "");
	ImageView img = new ImageView(new File("img.png").toURI().toString());
	public View() {
		img.setX(160);
		img.setY(130);
		text.setFont(new Font(20));
		text.setFill(Color.RED);
		text.setStroke(Color.RED);
		getChildren().add(text);
		getChildren().add(img);
	}


	//クリック判定処理
	public void mouseClicked(MouseEvent e) {
		if ( e.getButton() == MouseButton.PRIMARY ) {
			if ( img.contains(e.getX(), e.getY()) ) {
				text.setText("画像がクリックされました。");
				text.setX(200 - text.getBoundsInLocal().getWidth() / 2);
			} else {
				text.setText("");
			}
		}
	}
}
実行結果

図形と図形の当たり判定

いいメソッドは用意されていなかったのですが、Shapeクラスのintersectメソッドで代用できそうです。

このメソッドであれば、Rectangle、Ellipse、Pathなどとの当たり判定が実装できます。

ただし、処理速度が心配なので、どうしてもというときのみに使用するようにし、基本的には使用しないほうがいいでしょう。

Shapeクラスのintersectメソッドは、二つの図形の共通部分を取得しますが、共通部分が取得できない場合は横幅や縦幅が-1になります。(Java9の時点で)それを判断基準にすればよさそうです。注意点は、不透明部分の共通領域を返すため、fillプロパティの指定は必須となります。デフォルトで黒が指定されているオブジェクトもある為、その場合は指定しなくても良い。

それではサンプルをご覧ください。


import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.ClosePath;
import javafx.scene.shape.Ellipse;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.QuadCurveTo;
import javafx.scene.shape.Shape;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class Test extends Application{
	@Override
	public void start(Stage primaryStage) throws Exception {
		View v = new View();
		Scene scene = new Scene(v, 400, 300);
		primaryStage.setScene(scene);
		primaryStage.show();
	}
}

class View extends Group{
	public View() {
		Path shape1 = new Path();
		Ellipse shape2 = new Ellipse(240, 200, 100, 80);
		shape1.setStroke(Color.RED);
		shape2.setStroke(Color.BLUE);
		shape1.setFill(new Color(0,0,0,0.1));
		shape2.setFill(new Color(0,0,0,0.1));
		getChildren().add(shape1);
		getChildren().add(shape2);
		Text text = new Text("当たっていません。");
		text.setFont(new Font(20));

		//パスの描画
		shape1.getElements().add(new MoveTo(150,120));
		shape1.getElements().add(new LineTo(170,130));
		shape1.getElements().add(new QuadCurveTo(100, 300, 50, 80));
		shape1.getElements().add(new ClosePath());

		//図形同士の当たり判定
		if ( Shape.intersect(shape1, shape2).getBoundsInLocal().getWidth() != -1 ) {
			text.setText("当たっています。");
			text.setFill(Color.RED);
		}
		text.setY(100);
		text.setX(200 - text.getBoundsInLocal().getWidth() / 2);
		getChildren().add(text);
	}
}
実行結果

JavaJavaFX

Posted by nompor