在JavaFX中使用物理引擎JBox2D

周末突然想到了,游戏引擎里自然是需要一个物理引擎的,于是研究了一下JBox2D(至于为什么是JBox2D,也只是因为觉得JBox2D的可移植性更好,是否采用其他的物理引擎等以后再说)。

由于本人开发的JavaFX的游戏引擎WJFXGame目前在赶工中,里面的物理引擎的例子发出来也不容易学习。所以我又重新写了个。

其实很简单,JBox2D的物理操作和图形渲染是分开的,我们只需要负责处理图形渲染的部分即可。


示例地址: 点击

我们在这里使用的是官网下载的最新版的JBox2D,如下图所示:


首先,我们新建一个JBox2D中的刚体与JavaFX里面的图形结合的一个类WBody。

import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Paint;

import org.jbox2d.dynamics.Body;
public abstract class WBody{
    protected Body mBody;
    protected Paint mColor;
    protected final static int RATE = 30;
    protected double x,y,width,height;
	public Body getBody() {
		return mBody;
	}
	public void setBody(Body mBody) {
		this.mBody = mBody;
	}
	public Paint getColor() {
		return mColor;
	}
	public void setColor(Paint mColor) {
		this.mColor = mColor;
	}
	
	public abstract void draw(GraphicsContext gc);
	
	public abstract void update();
	
	public double getX() {
		return x;
	}
	public void setX(double x) {
		this.x = x;
	}
	public double getY() {
		return y;
	}
	public void setY(double y) {
		this.y = y;
	}
	
	public void setLocation(double x,double y){
		setX(x);
		setY(y);
	}
	public double getWidth() {
		return width;
	}
	public void setWidth(double width) {
		this.width = width;
	}
	public double getHeight() {
		return height;
	}
	public void setHeight(double height) {
		this.height = height;
	}
	
}

这个类中,包含了一个刚体body,和一系列位置大小等属性的设置。为了简单,这里都是用的普通的属性,没有使用可绑定的属性(在WJFXGame中,我都是使用的JavaFX的可绑定的属性)。

RATE是屏幕与世界的比例。通常这个比例越大,由于重力速度相同,在屏幕里看到的会运动的更快。

然后我们创建一个圆的WBody,叫WCircleBody。

import org.jbox2d.dynamics.Body;

import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Paint;

public class WCircleBody extends WBody {

	protected DoubleProperty radius;
	
	public WCircleBody(Body body, double radius, Paint color){
		this.mBody = body;
		this.radius = new SimpleDoubleProperty(radius);
		this.mColor = color;
	}
	
	@Override
	public void draw(GraphicsContext gc) {
		gc.save();
		gc.setFill(mColor);
		gc.fillOval(getX(), getY(),  getRaidus() * 2, getRaidus() * 2);
		gc.restore();
	}

	@Override
	public void update() {
		setX(mBody.getPosition().x * RATE - getRaidus());
		setY(mBody.getPosition().y * RATE - getRaidus());
	}
	
	public double getWidth(){
		return 2 * getRaidus();
	}
	
	public double getHeight(){
		return 2 * getRaidus();
	}

	public DoubleProperty radiusProperty(){
		return radius;
	}
	
	public double getRaidus(){
		return radius.get();
	}
	
	public void setRadius(double radius){
		this.radius.set(radius);
	}
}

这个继承于WBody,draw方法中根据x,y和半径,来绘制圆。在update方法中,将圆的图形位置设置为Body刚体的位置。由于刚体里的position是正中心的,所以我们需要自己进行计算左上角的位置。

然后创建一个矩形的WBody,叫WBoxBody。

import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Paint;

import org.jbox2d.dynamics.Body;

public class WBoxBody extends WBody {
	public WBoxBody(double width,double height, Body body, Paint paint) {
		this.mBody = body;
		this.mColor = paint;
		this.width = width;
		this.height = height;
	}

	@Override
	public void draw(GraphicsContext gc) {
		gc.save();
		gc.setFill(mColor);
		gc.fillRect(getX(), getY(), getWidth(),getHeight());
		gc.restore();
	}

	@Override
	public void update() {
        setX(mBody.getPosition().x * RATE - getWidth() / 2);
        setY(mBody.getPosition().y * RATE - getHeight() / 2);
	}

}
同样的,WBoxBody里跟WCircleBody里类似。就不做过多解释了。

下面我们来新建一个类继承Canvas,来负责所有的绘制和更新操作。

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javafx.util.Duration;

import org.jbox2d.collision.AABB;
import org.jbox2d.collision.shapes.CircleShape;
import org.jbox2d.collision.shapes.PolygonShape;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.dynamics.BodyType;
import org.jbox2d.dynamics.FixtureDef;
import org.jbox2d.dynamics.World;
import org.wing.jfx.physics.test.body.WBody;
import org.wing.jfx.physics.test.body.WBoxBody;
import org.wing.jfx.physics.test.body.WCircleBody;

public class CanvasScreen extends Canvas {
	private GraphicsContext gc;
	private Timeline mTimeline;
	private double duration = 10;
	private static final int RATE = 10;
	private World world;
	private AABB aabb;
	private BodyDef bd;

    private List<WBody> bodys = new CopyOnWriteArrayList<>();
	public CanvasScreen(double width, double height) {
		super(width, height);

		Vec2 gravity = new Vec2(-1.0f, 10.0f);
		world = new World(gravity);

		Vec2 minV = new Vec2(-100.0f, -100.0f);
		Vec2 maxV = new Vec2(100.0f, 100.0f);

		aabb = new AABB();
		aabb.lowerBound.set(minV);
		aabb.upperBound.set(maxV);

		world.queryAABB(null, aabb);
		bd = new BodyDef();

		gc = getGraphicsContext2D();
		init();
		initObjects();
	}

	public void init() {
		mTimeline = new Timeline();
		mTimeline.setCycleCount(Timeline.INDEFINITE);
		KeyFrame frame = new KeyFrame(Duration.millis(duration), new EventHandler<ActionEvent>() {

			@Override
			public void handle(ActionEvent event) {
				draw(gc);
				update();

			}
		});
		mTimeline.getKeyFrames().add(frame);
		mTimeline.play();
	}

	public void initObjects(){
		createPolygon(10, 500, (int) getWidth() - 10 * 2, 10, 0.3f, 0.6f, true);
		createPolygon(10, 10, 10, 500, 0.3f, 0.6f, true);
		createPolygon((int) getWidth() - 10 * 2, 10, 10, 500, 0.3f, 0.6f, true);
		float restitution[] = new float[] { 0.9f, 0.9f, 0.9f, 0.9f, 0.75f, 0.9f, 1.0f, 0.6f, 0.8f, 0.4f, 0.9f, 0.9f,
				0.9f, 0.9f, 0.75f, 0.9f, 1.0f, 0.6f, 0.8f, 0.4f, 0.9f, 0.9f, 0.9f, 0.9f, 0.75f, 0.9f, 1.0f, 0.6f, 0.8f,
				0.4f, 0.9f, 0.9f, 0.9f, };
		for (int i = 0; i < restitution.length; i++) {
			createCircle(80 + i * 20, 5 * i, 15, 0.3f, restitution[i], false);
		}
	}
	
	public void draw(GraphicsContext gc) {
		gc.clearRect(0, 0, getWidth(), getHeight());
	    gc.setFill(Color.BLACK);
	    gc.fillRect(0, 0, getWidth(), getHeight());
		for(WBody mBody : bodys){
			mBody.draw(gc);
		}
	}

	public void update() {
		for(WBody body : bodys){
			body.update();
		}
		
		world.step(1.0f / 60.0f, 8, 3);
	}

	/**
	 * 创建矩形
	 */
	public void createPolygon(float x, float y, float w, float h, float friction, float restitution, boolean isStatic) {
		// 创建多边形皮肤
		PolygonShape shape = new PolygonShape();
		shape.setAsBox(w / 2 / RATE, h / 2 / RATE);

		FixtureDef fd = new FixtureDef();
		fd.shape = shape;
		fd.density = 1.0f; // 设置密度
		fd.friction = friction; // 设置摩擦力
		fd.restitution = restitution; // 设置多边形的恢复力

		// 设置刚体初始坐标
		bd.type = isStatic ? BodyType.STATIC : BodyType.DYNAMIC;
		bd.position.set((x + w / 2) / RATE, (y + h / 2) / RATE);

		// 创建物体
		Body body = world.createBody(bd); // 物理世界创建物体

		// 此方法改变了
		// body.createShape(pDef); //为body添加皮肤
		body.createFixture(fd);

		WBoxBody boxBody = new WBoxBody(w, h, body, Color.WHITE);
		boxBody.setLocation(x, y);
		bodys.add(boxBody);
	}

	/**
	 * 创建圆形
     */
	public void createCircle(float x, float y, float r, float friction, float restitution, boolean isStatic) {
		CircleShape shape = new CircleShape();
		shape.m_radius = r / RATE;

		FixtureDef fd = new FixtureDef();
		fd.shape = shape;
		fd.density = 1.0f;
		fd.friction = friction;
		fd.restitution = restitution;

		// 创建刚体
		bd.type = isStatic ? BodyType.STATIC : BodyType.DYNAMIC;
		bd.position.set((x + r) / RATE, (y + r) / RATE);

		// 创建一个body
		Body body = world.createBody(bd);
		body.createFixture(fd);

		WCircleBody circleBody = new WCircleBody(body, r, Color.RED);
		circleBody.setLocation(x, y);
		bodys.add(circleBody);
	}
}

在这个CanvasScreen里,主要做了以下几个操作。

1.创建了JBox2D的世界World

2.创建了一个Timeline,增加了一个Keyframe,通过无限循环来模拟游戏的更新操作。

3.通过createPolygon和createCircle来创建Body并初始化WBody元素。这里需要注意Body的position为中心位置,而我们绘制的Object是以左上为position,需要自行处理一下。还有就是屏幕与世界的比例RATE,在进行position的时候的处理。

4.在keyframe中,绘制WBody列表并在update中更新WBody列表。

5.更新World,我们在update中world.step(...)进行更新JBox的World里的物理变化。如果没有world.step,所有的物理操作将无法进行。


接下来是我们的主类。

import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class MainClass extends Application {
	private static final int WIDTH = 800;
	private static final int HEIGHT = 600;

	@Override
	public void start(Stage primaryStage) {
		CanvasScreen mCanvas = new CanvasScreen(WIDTH, HEIGHT);
		Group root = new Group();
		root.getChildren().add(mCanvas);
		Scene scene = new Scene(root, WIDTH, HEIGHT);

		primaryStage.setTitle("JavaFX中JBox2D的使用");
		primaryStage.setScene(scene);
		primaryStage.show();
	}

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

大家可以看看运行效果。

点击


本人使用的是JDK7,大家看提示下载吧。

效果图:

在JavaFX中使用物理引擎JBox2D_第1张图片

转载请注明出处: http:.//blog.csdn.net/ml3947

另外我的个人博客地址: http://www.wjfxgame.com




你可能感兴趣的:(JavaFX)