周末突然想到了,游戏引擎里自然是需要一个物理引擎的,于是研究了一下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,大家看提示下载吧。
效果图:
转载请注明出处: http:.//blog.csdn.net/ml3947
另外我的个人博客地址: http://www.wjfxgame.com