In this section, you'll use a very simple and limited version of physics. Games are all about being good fakes. They cheat wherever possible in order to avoid potentially heavy calculations. The behavior of objects in a game does not need to be 100 percent physically accurate; it just needs to be good enough to look believable. Sometimes you won't even want physically accurate behavior (that is, you might want one set of objects to fall downward, and another, crazier, set of objects to fall upward).
Even the original Super Mario Brothers used at least some basic principles of Newtonian physics. These principles are really simple and easy to implement. Only the absolute minimum required for implementing a simple physics model for your game objects will be discussed.
Your main concern is with the motion physics of so-called point masses, which refers to the change in position, velocity, and acceleration of an object over time. Point mass means that you approximate all objects with an infinitesimally small point that has an associated mass. You do not have to deal with things like torque—the rotational velocity of an object around its center of mass—because that is a complex problem domain about which more than one complete book has been written. Just look at these three properties for an object:
When you know the properties of an object for a given point in time, you can integrate them in order to simulate the object's path through the world over time. This may sound scary, but you already did this with Mr. Nom and your Bob test. In those cases, you didn't use acceleration; you simply set the velocity to a fixed vector. Here's how you can integrate the acceleration, velocity, and position of an object in general:
Vector2 position = new Vector2();
Vector2 velocity = new Vector2();
Vector2 acceleration = new Vector2(0, -10);
while(simulationRuns) {
float deltaTime = getDeltaTime();
velocity.add(acceleration.x * deltaTime, acceleration.y * deltaTime);
position.add(velocity.x * deltaTime, velocity.y * deltaTime);
}
This is called numerical Euler integration, and it is the most intuitive of the integration methods used in games. You start off with a position at (0,0), a velocity given as (0,0), and an acceleration of (0,−10), which means that the velocity will increase by 1 m/s on the y-axis. There will be no movement on the x-axis. Before you enter the integration loop, your object is standing still. Within the loop, you first update the velocity, based on the acceleration multiplied by the delta time, and then update the position, based on the velocity multiplied by the delta time. That's all there is to the big, scary word integration.
NOTE: As usual, that's not even half of the story. Euler integration is an “unstable” integration method and should be avoided when possible. Usually, one would employ a variant of the so-called verlet integration, which is just a bit more complex. For your purposes, however, the easier Euler integration is sufficient.
You might wonder where the acceleration comes from. That's a good question, with many answers. The acceleration of a car comes from its engine. The engine applies a force to the car that causes it to accelerate. But that's not all. A car will also accelerate toward the center of the earth, due to gravity. The only thing that keeps it from falling through to the center of the earth is the ground, which it can't pass through. The ground cancels out this gravitational force. The general idea is this:
force = mass × acceleration
You can rearrange this to the following equation:
acceleration = force / mass
Force is given in the SI unit Newton. (Guess who came up with this.) If you specify acceleration as a vector, then you also have to specify the force as a vector. A force can thus have a direction. For example, the gravitational force pulls downward in the direction (0,−1). The acceleration is also dependent on the mass of an object. The greater the mass of an object, the more force you need to apply in order to make it accelerate as fast as an object of less weight. This is a direct consequence of the preceding equations.
For simple games you can, however, ignore the mass and force, and just work with the velocity and acceleration directly. In the preceding pseudocode, you set the acceleration to (0,−10) m/s per second (again, not a typo), which is roughly the acceleration of an object when it is falling toward the earth, no matter its mass (ignoring things like air resistance). It's true, ask Galileo!
Use the preceding example to play with an object falling toward earth. Let's assume that you let the loop iterate ten times, and that getDeltaTime()
will always return 0.1 s. You'll get the following positions and velocities for each iteration:
time=0.1, position=(0.0,-0.1), velocity=(0.0,-1.0)
time=0.2, position=(0.0,-0.3), velocity=(0.0,-2.0)
time=0.3, position=(0.0,-0.6), velocity=(0.0,-3.0)
time=0.4, position=(0.0,-1.0), velocity=(0.0,-4.0)
time=0.5, position=(0.0,-1.5), velocity=(0.0,-5.0)
time=0.6, position=(0.0,-2.1), velocity=(0.0,-6.0)
time=0.7, position=(0.0,-2.8), velocity=(0.0,-7.0)
time=0.8, position=(0.0,-3.6), velocity=(0.0,-8.0)
time=0.9, position=(0.0,-4.5), velocity=(0.0,-9.0)
time=1.0, position=(0.0,-5.5), velocity=(0.0,-10.0)
After 1 s, your object will fall 5.5 m and have a velocity of (0,−10) m/s, moving straight down to the core of the earth (until it hits the ground, of course).
Your object will increase its downward speed without end, as you haven't factored in air resistance. (As mentioned before, you can easily cheat your own system.) You can simply enforce a maximum velocity by checking the current velocity length, which equals the speed of the object.
All-knowing Wikipedia indicates that a human in free fall can have a maximum, or terminal, velocity of roughly 125 mph. Converting that to meters per second (125 × 1.6 × 1000 / 3600), you get 55.5 m/s. To make your simulation more realistic, you can modify the loop, as follows:
while(simulationRuns) {
float deltaTime = getDeltaTime();
if(velocity.len()< 55.5)
velocity.add(acceleration.x * deltaTime, acceleration.y * deltaTime);
position.add(velocity.x * deltaTime, velocity.y * deltaTime);
}
As long as the speed of the object (the length of the velocity vector) is smaller than 55.5 m/s, you can increase the velocity by the acceleration. When you've reached the terminal velocity, you simply stop increasing it by the acceleration. This simple capping of velocities is a trick that is used heavily in many games.
You can add wind to the equation by adding another acceleration in the x direction, say (–1,0) m/s². For this, add the gravitational acceleration to the wind acceleration before you add it to the velocity:
Vector2 gravity = new Vector2(0,-10);
Vector2 wind = new Vector2(-1,0);
while(simulationRuns) {
float deltaTime = getDeltaTime();
acceleration.set(gravity).add(wind);
if(velocity.len()< 55.5)
velocity.add(acceleration.x * deltaTime, acceleration.y * deltaTime);
position.add(velocity.x * deltaTime, velocity.y * deltaTime);
}
You can also ignore acceleration altogether and let your objects have a fixed velocity. You did exactly this in the BobTest
. You changed the velocity of each Bob only if he hit an edge, and you did so instantly.
The possibilities, even with this simple model, are endless. Let's extend your little CannonTest
so that you can actually shoot a cannonball. Here's what you want to do:
Initially, the position of the cannonball will be (0,0)—the same as the cannon's position. The velocity will also be (0,0). Since you apply gravity in each update, the cannonball will simply fall straight down.
Once a touch-up event is received, set the ball's position back to (0,0) and its initial velocity to (Math.cos(cannonAngle)
,Math.sin(cannonAngle)
). This will ensure that the cannonball flies in the direction the cannon is pointing. Also, set the speed simply by multiplying the velocity by the distance between the touch point and the cannon. The closer the touch point to the cannon, the more slowly the cannonball will fly.
Sounds easy enough, so now you can try implementing it. Copy over the code from the CannonTest
to a new file, called CannonGravityTest.java
. Rename the classes contained in that file to CannonGravityTest
and CannonGravityScreen
. Listing 8–3 shows the CannonGravityScreen
.
class CannonGravityScreen extends Screen {
float FRUSTUM_WIDTH = 9.6f;
float FRUSTUM_HEIGHT = 6.4f;
GLGraphics glGraphics;
Vertices cannonVertices;
Vertices ballVertices;
Vector2 cannonPos = new Vector2();
float cannonAngle = 0;
Vector2 touchPos = new Vector2();
Vector2 ballPos = new Vector2(0,0);
Vector2 ballVelocity = new Vector2(0,0);
Vector2 gravity = new Vector2(0,-10);
Not a lot has changed. You simply doubled the size of the view frustum, and reflected that by setting FRUSTUM_WIDTH
and FRUSTUM_HEIGHT
to 9.6 and 6.2, respectively. This means that you can see a rectangle of 9.2×6.2 m of the world. Since you also want to draw the cannonball, add another Vertices
instance, called ballVertices
, which will hold the four vertices and six indices of the rectangle of the cannonball. The new members ballPos
and ballVelocity
store the position and velocity of the cannonball, and the member gravity
is the gravitational acceleration, which will stay at a constant (0,−10) m/s² over the lifetime of your program.
public CannonGravityScreen(Game game) {
super(game);
glGraphics = ((GLGame) game).getGLGraphics();
cannonVertices = new Vertices(glGraphics, 3, 0, false, false);
cannonVertices.setVertices(new float[] { -0.5f, -0.5f,
0.5f, 0.0f,
-0.5f, 0.5f }, 0, 6);
ballVertices = new Vertices(glGraphics, 4, 6, false, false);
ballVertices.setVertices(new float[] { -0.1f, -0.1f,
0.1f, -0.1f,
0.1f, 0.1f,
-0.1f, 0.1f }, 0, 8);
ballVertices.setIndices(new short[] {0, 1, 2, 2, 3, 0}, 0, 6);
}
In the constructor, simply create the additional Vertices
instance for the rectangle of the cannonball. Define it in model space with the vertices (−0.1,−0.1), (0.1,−0.1), (0.1,0.1), and (−0.1,0.1). Use indexed drawing, and thus specify six vertices in this case.
@Override
public void update(float deltaTime) {
List<TouchEvent> touchEvents = game.getInput().getTouchEvents();
game.getInput().getKeyEvents();
int len = touchEvents.size();
for (int i = 0; i < len; i++) {
TouchEvent event = touchEvents.get(i);
touchPos.x = (event.x / (float) glGraphics.getWidth())
* FRUSTUM_WIDTH;
touchPos.y = (1 - event.y / (float) glGraphics.getHeight())
* FRUSTUM_HEIGHT;
cannonAngle = touchPos.sub(cannonPos).angle();
if(event.type == TouchEvent.TOUCH_UP) {
float radians = cannonAngle * Vector2.TO_RADIANS;
float ballSpeed = touchPos.len();
ballPos.set(cannonPos);
ballVelocity.x = FloatMath.cos(radians) * ballSpeed;
ballVelocity.y = FloatMath.sin(radians) * ballSpeed;
}
}
ballVelocity.add(gravity.x * deltaTime, gravity.y * deltaTime);
ballPos.add(ballVelocity.x * deltaTime, ballVelocity.y * deltaTime);
}
The update()
method has changed only slightly. The calculation of the touch point in world coordinates and the angle of the cannon are still the same. The first addition is the if
statement inside the event-processing loop. In case you get a touch-up event, you prepare the cannonball to be shot. Transfomr the cannon's aiming angle to radians, as you'll use FastMath.cos()
and FastMath.sin()
later on. Next, calculate the distance between the cannon and the touch point. This will be the speed of the cannonball. Set the ball's position to the cannon's position. Finally, calculate the initial velocity of the cannonball. Use sine and cosine, as discussed in the previous section, to construct a direction vector from the cannon's angle. Multiply this direction vector by the cannonball's speed to arrive at the final cannonball velocity. This is interesting, as the cannonball will have this velocity from the start. In the real world, the cannonball would, of course, accelerate from 0 m/s to whatever it could reach given air resistance, gravity, and the force applied to it by the cannon. You can cheat here, though, as that acceleration would happen in a very tiny time window (a couple hundred milliseconds). The last thing you do in the update()
method is update the velocity of the cannonball and, based on that, adjust its position.
@Override
public void present(float deltaTime) {
GL10 gl = glGraphics.getGL();
gl.glViewport(0, 0, glGraphics.getWidth(), glGraphics.getHeight());
gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
gl.glOrthof(0, FRUSTUM_WIDTH, 0, FRUSTUM_HEIGHT, 1, -1);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glTranslatef(cannonPos.x, cannonPos.y, 0);
gl.glRotatef(cannonAngle, 0, 0, 1);
gl.glColor4f(1,1,1,1);
cannonVertices.bind();
cannonVertices.draw(GL10.GL_TRIANGLES, 0, 3);
cannonVertices.unbind();
gl.glLoadIdentity();
gl.glTranslatef(ballPos.x, ballPos.y, 0);
gl.glColor4f(1,0,0,1);
ballVertices.bind();
ballVertices.draw(GL10.GL_TRIANGLES, 0, 6);
ballVertices.unbind();
}
In the present()
method, simply add the rendering of the cannonball rectangle. You do this after rendering the cannon's triangle, which means that you have to “clean” the model-view matrix before you can render the rectangle. Do this with glLoadIdentity()
, and then use glTranslatef()
to convert the cannonball's rectangle from model space to world space at the ball's current position.
@Override
public void pause() {
}
@Override
public void resume() {
}
@Override
public void dispose() {
}
}
If you run the example and touch the screen a couple of times, you'll get a pretty good feel for how the cannonball will fly. Figure 8–7 shows the output (which is not all that impressive, since it is a still image).
That's enough physics for your purposes. With this simple model, you can simulate much more than cannonballs. Super Mario, for example, could be simulated in much the same way. If you have ever played Super Mario Brothers, then you will notice that Mario takes a bit of time before he reaches his maximum velocity when running. This can be implemented with a very fast acceleration and velocity capping, as in the preceding pseudocode. Jumping can be implemented in much the same way as shooting the cannonball. Mario's current velocity would be adjusted by an initial jump velocity on the y-axis (remember that you can add velocities like any other vectors). You would always apply a negative y acceleration (gravity), which makes him come back to the ground, or fall into a pit, after jumping. The velocity in the x direction is not influenced by what's happening on the y-axis. You can still press left and right to change the velocity of the x-axis. The beauty of this simple model is that it allows you to implement very complex behavior with very little code. You can use this type of physics when you write your next game.
Simply shooting a cannonball is not a lot of fun. You want to be able to hit objects with the cannonball. For this, you need something called collision detection, which you can investigate in the next section.
3.142.130.250