I recently stumbled upon the excellent book The Nature of Code by Daniel Shiffman. It's one of the best books I've read so far when it comes starting with learing about motion and physics in games. Not only do you get a very good written lecture, but Daniel also made a video series to supplement the book. And it can be read on the web for free. That book was an instant buy.
The code is written in Processing, but that can be easily translated to JavaFX.
I leave the details to the book and only describe here how the code is translated to JavaFX. One thing to notice is that the motion and physics are based on vector calculations. The JavaFX Point2D class provides us the means to work with, hower in the end it won't suffice, it does for now. The main reason is that the Point2D class can't be modified, you'll always get new instances of it when you add, subtract, multiply, etc. That's not very performing. As a replacement you could write your own vector class or use the PVector class from Processing, since it's written in Java (please note the license of Processing).
The code example is from chapter 2.10 Everything Attracts Everything. What we need is a sprite, i. e. a Mover class with attributes like
- location
- velocity
- acceleration
- mass
For sake of simplicity the Mover is a Circle. From the book we learn that the movement consists of 3 steps:
- apply force(s) and get the acceleration
- apply acceleration to velocity
- apply velocity to location
This is basically the required code, what it does mathematically is very well explained in the book:
Point2D location; Point2D velocity; Point2D acceleration; ... public void applyForce(Point2D force) { Point2D f = new Point2D( force.getX(), force.getY()); f = f.multiply(1/mass); acceleration = acceleration.add(f); } public void move() { // set velocity depending on acceleration velocity = velocity.add(acceleration); // limit velocity to max speed double mag = velocity.magnitude(); if( mag > Settings.MOVER_MAX_SPEED) { velocity = velocity.normalize(); velocity = velocity.multiply(mag); } // change location depending on velocity location = location.add(velocity); // clear acceleration acceleration = new Point2D(0,0); } public Point2D attract(Mover m) { // force direction Point2D force = location.subtract(m.location); double distance = force.magnitude(); // constrain movement distance = constrain(distance, Settings.ATTRACTION_DISTANCE_MIN, Settings.ATTRACTION_DISTANCE_MAX); force = force.normalize(); // force magnitude double strength = (Settings.GRAVITATIONAL_CONSTANT * mass * m.mass) / (distance * distance); force = force.multiply(strength); return force; } public void display() { setCenterX( location.getX()); setCenterY( location.getY()); }
And then we create a game loop in which we apply the forces, move the sprites and update them in the UI:
gameLoop = new AnimationTimer() { @Override public void handle(long now) { // force: attraction for (Mover m1 : allMovers) { for (Mover m2 : allMovers) { if (m1 == m2) continue; // calculate attraction Point2D force = m1.attract(m2); // apply attraction m2.applyForce(force); }; }; // move allMovers.forEach(Mover::move); // update in fx scene allMovers.forEach(Mover::display); } }; gameLoop.start();
That's it, that's the core, nothing much to it. You can as easily apply other forces like wind and gravity, as described in the book.
Just in case your pc isn't powerful enough to handle this many nodes, or just for the sake of quick performance improvement, you may want to use a parallel stream() in order to apply the force like this:
allMovers.stream().parallel().forEach(m1 -> { allMovers.stream().parallel().forEach(m2 -> { if (m1 != m2) { // calculate attraction Point3D force = m1.attract(m2); // apply attraction m2.applyForce(force); } }); });
Here's a video about how it looks like. Random movers are placed on the screen, everything attracts everything:
Interested ones can get the full JavaFX source from this gist.
No comments:
Post a Comment