Playing Around Practically

The possibilities, even with this simple model, are endless. Let's extend our little CannonTest so we can actually shoot a cannonball. Here's what we want to do:

■ As long as the user drags his finger over the screen, the canon will follow it. That's how we'll specify the angle at which we'll shoot the ball.

■ As soon as we receive a touch-up event, we'll fire a cannonball in the direction the cannon is pointing. The initial velocity of the cannonball will be a combination of the cannon's direction and the speed the cannonball will have from the start. The speed is equal to the distance between the cannon and the touch point. The further away we touch, the faster the cannonball will fly.

■ The cannonball will fly for as long as there's no new touch-up event.

■ We'll double the size of our view frustum to (0,0) to (9.6, 6.4) so that we can see more of our world. Additionally we'll place the cannon at (0,0). Note that all units of our world are now given in meters.

■ We'll render the cannonball as a red rectangle of the size 0.2x0.2 meters, or 20x20 centimeters. Close enough to a real cannonball, I guess. The pirates among you may choose a more realistic size, of course.

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 we apply gravity in each update, the cannonball will just fall straight downward.

Once a touch-up event is received, we set the ball's position back to (0,0) and its initial velocity to (Math.cos(cannonAngle),Math.sin(cannonAngle)). This will ensure that our cannonball flies in the direction the cannon is pointing. We also set the speed by simply 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 let's implement it. I copied over the code from the CannonTest to a new file, called CannonGravityTest.java. I renamed the classes contained in that file to CannonGravityTest and CannonGravityScreen. Listing 8-3 shows the CannonGravityScreen.

Listing 8-3. Excerpt from CannonGravityTest 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. We simply double the size of the view frustum, and reflect that by setting FRUSTUM_WIDTH and FRUSTUM_HEIGHT to 9.6 and 6.2, respectively. This means that we can see a rectangle of 9.2x6.2 meters of our world. Since we also want to draw the cannonball, I added another Vertices instance, called ballVertices, that 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/s2 over the lifetime of our program.

public CannonGravityScreen(Game game) { super(game);

glGraphics = ((GLGame) game).getGLGraphics(); cannonVertices = new Vertices(glGraphics, 3, 0, cannonVertices.setVertices(new float[] { -0.5f,

0.5f, 0.0f, -0.5f, 0.5f }, ballVertices = new Vertices(glGraphics, 4, ballVertices.setVertices(new float[] { -0.1f, ballVertices.setIndices(new short[] {0,

0, 6); false, false); -0.1f, -0.1f, 0.1f, 0.1f }, 0, 8); 2, 3, 0}, 0, 6);

In the constructor we simply create the additional Vertices instance for the rectangle of the cannonball. We again 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). We use indexed drawing, so we also 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 also only changed 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 we get a touch-up event, we prepare our cannonball to be shot. We first transform the cannon's aiming angle to radians, as we'll use FastMath.cos() and FastMath.sin() later on. Next we calculate the distance between the cannon and the touch point. This will be the speed of our cannonball. We then set the ball's position to the cannon's position. Finally we calculate the initial velocity of the cannonball. We use sine and cosine, as discussed in the previous section, to construct a direction vector from the cannon's angle. We multiply this direction vector by the cannonball's speed to arrive at our 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 can reach given air resistance, gravity, and the force applied to it by the cannon. We can cheat here, though, as that acceleration would happen in a very tiny time window (a couple hundred milliseconds). The last thing we 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.gl0rthof(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);

ballVertices.bind();

ballVertices.draw(GL10.GL_TRIANGLES, 0, 6); ballVertices.unbind();

In the present() method, we simply add the rendering of the cannonball rectangle. We do this after rendering the cannon's triangle, which means that we have to "clean" the model-view matrix before we can render the rectangle. We 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.

@0verride public void resume() { }

@0verride 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).

Figure 8-7. A triangle cannon that shoots red rectangles. Impressive!

That's enough physics for our purposes. With this simple model, we can simulate much more than cannonballs. Super Mario, for example, could be simulated much in the same way. If you have ever played Super Mario Brothers, then you will notice that Mario takes a little 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 we shot the cannonball. Mario's current velocity would be adjusted by an initial jump velocity on the y-axis (remember that we can add velocities like any other vectors). If there were no ground beneath his feet, we would apply gravitational acceleration so that he would actually fall back to the ground. The velocity in the x direction is not influenced by what's happening on the y-axis. We could still press left and right to change the velocity on the x-axis. The beauty of this simple model is that it allows us to implement very complex behavior with very little code. We'll actually use a similar this type of physics when we write our next game.

Just shooting a cannonball is not a lot of fun. We want to be able to hit objects with the cannonball. For this we need something called collision detection, which we'll investigate in the next section.

0 0

Post a comment