- 浏览: 811610 次
- 性别:
- 来自: 广州
最新评论
-
mixture:
语句int num1, num2;的频度为1;语句i=0;的频 ...
算法时间复杂度的计算 [整理] -
zxjlwt:
学习了。http://surenpi.com
[问题解决]Error: ShouldNotReachHere() [整理] -
Animal:
谢谢 楼主 好东西
算法时间复杂度的计算 [整理] -
univasity:
gaidandan 写道缓存失败,,模拟器上可以缓存,同样代码 ...
[开发总结]WebView使用中遇到的一些问题&解决 -
blucelee2:
那么麻烦干吗,而且这种方法会导致,当拉太小的时候样式会丢掉,整 ...
[SWT]SashForm中固定单侧大小(&实现面板隐藏)
[JSR-184][3D编程指南]Part IV:M3G built-in collision,light physics and camera perspec
- 博客分类:
- J2me
<!-- 整理收集自网络,收藏以便日后查阅 -->
Compliments of Redikod:
3D programming tutorial part four: M3G built-in collision, light physics and camera perspective
We've now reached part four in the "3D programming for mobile devices using M3G (JSR 184)" series of tutorials from Mikael Baros, Senior Programmer at Redikod. Building on the first three parts, he now takes you into the world of collisions and camera perspective. After explaining some theory, he guides you through creating a 3D Pong game. Below you can download the source code and application package zip files for part four, as well as link to the first three installments of the tutorial. |
||||||
Part four: M3G built-in collision, light physics and camera perspective Source Code (Java classes and resources)>> Application Package (JAR/JAD)>>
Here's Mikael with part four.
Introduction
Since the code is meant for educational purposes it isn't optimal nor does it cover all the errors that might occur. These are more advanced topics that will be addressed later on. What you should know Camera and Perspective When we are viewing 3D objects in M3G, we are doing it by placing a camera at a defined position in space, and view the world around us from there with a specific field of view. For instance; humans don't have the same field of view as birds. Some birds have a field of view of up to 360 degrees while humans usually have about 150–180 degrees. Now, the field of view or FOV (which is what I'll call it from now on, so remember it!) in a 3D game is basically a description of how the camera (think; player) sees the objects in our world. There are four parameters that I'll talk about today. These parameters are the same parameters that are needed in a call to the Camera.setPerspective method in M3G. Here is what it looks like:
setPerspective(float fovy, float aspectRatio, float near, float far)
The y-axis field of view, or fovy for short
Most APIs usually need a field of view, in degrees, to be able to construct a perspective matrix. M3G is no different. The first parameter of the setPerspective method is a float fovy. The fovy stands for the field of view on the y-axis. Normally, people associate this with the field of view on the x-axis (or how wide) you can see. However, M3G is interested in how high you can see. Normal values for this constant range from 45–90 degrees depending on what you're planning to use. If you have a hard time choosing, stick with a fovy of 60 degrees. So what does the fovy do? Well, it simply tells the
camera at what angle objects should be discarded for rendering. For
instance, if an object is at a y-angle of 110 degrees up from the camera
and our fovy is only 60 degrees, we won't be able to see it until we
actually pitch the camera up enough (to about 50 degrees). This is a
very crucial component in making a 3D game's visual component look
realistic. A too small fovy gives the effect of not being able to see
anything except that which is dead ahead while a too large value allows
you to see much more than the normal human eye is accustomed to, and
thus deemed too unrealistic. In this tutorial, you'll see how you can
manipulate the fovy to change the view of your game depending on what
you want to show.
The aspect ratio
This is a pretty simple parameter, since it's a fractional number that tells the engine the relation of the width against the height of the current screen. Most computer screens have a aspect ratio of 4:3 (that is, the height is ¾ of the width. 600/800 = 0.75 = ¾) while normal mobile phones have a wide variety of aspect ratios. To get this variable, all you need to do is to divide the current width of the screen, by the height. I won't say more on this as it's pretty
self-explanatory. You have to know the aspect ratio of your phone screen
to be able to display geometry that isn't skewed in unnatural ways.
The near and far clipping planes
Another very easy parameter. The near and far clipping planes simply define how far away/close an object can be and still be rendered. So for instance, setting the near clipping plane to 0.1 and far to 50 means that all objects that are closer than 0.1 units to the camera won't get rendered. The same goes for objects that are 50 units or more away from the camera. Actually, 0.1 and 50 are pretty normal values but of course you can change these to whatever you need in your game. It all comes down to how you scale your game and use the floating units. In this tutorial, we'll use 0.1 as the near clipping plane parameter, and 50 as the far clipping plane.
Why, oh why, do I need to know this?
Well, there are many answers to the above question, but I'll start by answering the most obvious. Because the default camera in M3G (that is a camera that you don't extract from M3G file) doesn't have a realistic perspective matrix defined. If you try rendering geometry on screen by only using the default camera (Camera cam = new Camera()) you are in for a surprise. If you want to use your own camera (which you will want to 90% of the time, especially when using immediate mode rendering) you'll want to define your own perspective matrix. Also, another good thing about knowing the perspective matrix is that you can accomplish some pretty cool effects by just manipulating the perspective parameters. So how do we create a pure, fresh, camera and assign it a perspective matrix? Like this:
float ar = (float)getWidth() / getHeight();
Camera cam = new Camera(); // This is our fresh camera cam.setPerspective( 60.0f, // fovy ar, // The aspect ratio 0.1f, // Near clipping plane 50.0f ); // Far clipping plane It wasn't that hard, now was it? Actually, a very
similar piece of code is to be found in this tutorial's source code, in a
method called loadCamera. You can check it out later.
Moving objects in three-dimensional space
To move objects you really don't need to do much and you probably have a good idea of how. I'll go through some very light and simple physics methods to move objects in 3D space. Normally, when an object moves in 3D space you need to
account for three different velocities: the x, y and z velocities.
However, most games can combine the x and z velocities into one, it
being the "forward" velocity. (For instance, if your forward velocity is
1.0, then all you need to do is transform it with sine and cosine and
apply to x and z) In this tutorial, we'll make no such discrimination,
instead we'll use all three distinctive velocities to describe a moving
body in 3D space.
The easiest way to do this, is by using three vectors.
1. Coordinate vector
The first vector is the coordinate vector of an object. That's the crucial vector that holds the object's current position in 3D space by keeping track of its x, y and z coordinate. How else will you know where to render the object?
2. Velocity vector
The second vector is the velocity vector. It defines how many units the object's set of coordinates will move the next time movement is computed. So it also needs a x, y and z component. Basically what you do is, each time just add the x velocity to the x coordinate, the y velocity to the y coordinate and the z velocity to the z coordinate. So, if you have a spaceship moving one unit straight up on the y-axis each frame, you'd have a velocity vector of (0, 1, 0).
3. Acceleration vector
The last vector is the acceleration vector. Although we won't be using it in this example, it's still important. The acceleration vector works like the velocity vector, but it operates on the velocity vector rather than on the coordinates. This means that the acceleration vector doesn't manipulate the coordinates directly, it instead increases the velocity each frame, giving the player a feeling of acceleration. Normally, an object can't go from idle to top speed instantly. It needs time and to model a 3D game that resembles reality you need acceleration. Say that the spaceship from the above example wanted to start moving at a speed of one unit per frame and accelerate to a speed of 5 units per frame. That could be done by having a velocity vector of (0, 1, 0) and a acceleration vector of (0, 0.1, 0). This means that after 40 frames, our spaceship will have a velocity of 5 units.
Movement example
Now, let's put our spaceship example in code! This is what it could look like:
float[] spaceShipCoords = {0, 0, 0};
float[] spaceShipVel = {0, 1, 0}; float[] spaceShipAcc = {0, 0.1, 0};
while(gameLoopIsRunning)
{ for(int i = 0; i < 3; i++) { spaceShipCoords[i] = (spaceShipVel[i] += spaceShipAcc[i]); } } See how easy that was? Each game loop we both accelerate and move our spaceship.
Collision
Objects in the three dimensional space are usually opaque and collide with one another. A comet can crash into a planet and a (rather poor) driver's car can crash into a building. However, in our 3D games this is not as obvious as it is in the real world. This is because so far, we've only rendered objects and rendered objects are just mathematical representations of 3D models that get transformed into screen pixels. We haven't really considered how the different objects relate to one-another yet. So if we moved our spaceship into some kind of planet that might exist, the spaceship would just continue going through the planet, and appear on the other side. All because we have no collision. We'll change this now.
Collision by ray intersection
Ray intersection is a very intuitive and easy form of collision detection. At least for normal and simple collision cases. It works like this; the object you wish to check collision from, fires a ray from its center (or somewhere else on its body) in a given direction (usually the object's velocity vector, but as you'll see soon, this is certainly not always the case). This ray then travels through your 3D world until it actually hits something (like a laser). When it hits something, it reports the collision and tells you how far it had to travel until it hit an object. Depending on that distance, you'll determine if the hit was a collision or not. That's mostly because if you know there's a building one thousand feet in front of you, it doesn't mean that you'll run into it. (Well you might eventually, but you sure haven't done it yet)
The M3G approach
M3G gives us an exceptionally easy way to calculate collision. Each Group in our world (remember, a Group is nothing but a gathering of Nodes, which can be anything with exists in our world) has a method called pick. This method is the one that does all the collision for us. It works like this, say that you're working on the next megahit FPS for mobile phones and you want to start by checking if your main character collides with the walls in a room, or if he collides with monsters in the room. So what you do is you create two Groups. One Group for all walls, and one for all monsters. Then you just simply cast a ray in each group, from the character and towards his direction, and see if you collide with anything. M3G handles the collision part of a game with two components. The pick
method and the RayIntersection
class.
The RayIntersection class
This is a fairly simple class that only holds information crucial to a ray cast in our 3D world. The method we'll go through today (and probably the only you'll need for light-weight collision) is the getDistance method. The getDistance method looks like this: public float getDistance() It's just as simple as it looks. It returns the
distance to the object that this particular ray has collided with,
scaled with the direction vector you supplied to the pick method (more
on this later). So by just doing a simple check, you can see if you are
close enough to an object in a Group to determine if you have a
collision. Just look at this piece of code:
// The default collision distance in our game
float collisionDistance = 0.1f; // This method returns a ray that has, or hasn't collided with a wall RayIntersection ray = checkCollisionWithWalls();
// Check distance to wall (if we haven't collided, the distance will be large)
if(ray.getDistance() < collisionDistance) { // Get the wall we collided with Node wall = ray.getIntersected();
// Do something...
} See, not hard at all! That's all the magic in the
RayIntersection class. Now let's see how we can actually get collision
information filled out in it.
The Group.pick method
To fill out the necessary information in a RayIntersection class, you'll need to use the pick method (unless you want to fake a collision and fill out the info yourself. Perhaps you want to implement your own ray intersection algorithm?). The pick method comes in two variants and I'll show you both before proceeding:
public boolean pick(int scope, float ox, float oy, float oz, float dx, float dy, float dz, RayIntersection ri)
public boolean pick(int scope, float x, float y, Camera camera, RayIntersection ri)
First let's talk about the similarities. Well, both
want a scope. The scope simply defines which objects the ray will deem
as collidable. Each Node in the M3G API has a scope, so if you pass a 1
as the scope parameter to the pick method, all
nodes
with a scope equal to 1. By supplying -1 you test against all nodes in
that group. Sometimes supplying a -1 is bad, as you don't want your
algorithm calculating a collision with something you already know your
object isn't close to. Always try to minimize the job the algorithm
needs to do.
The second and last thing both have in common is a
RayIntersection object. This is because they fill out the
RayIntersection class supplied, in case of a collision.
Now, let's go through the first method. This method is
the most used one, as it allows you to define a starting point of your
ray. The ox, oy and oz are three components of the origin vector. That
is, those three define the point in space from which the ray will start.
Usually, you'll set this to the center of collision for your object.
The next three components, the dx, dy and dz, compose the direction
vector. This vector decides in which direction the ray will travel.
You'll always want to supply this as a unit vector (a vector with the
length of 1) as the pick method scales the distance reported by
getDistance depending on the length of your direction vector. Sometimes,
this is a must, but most of the time you won't need it, so just make
sure your direction vector has a length of 1.
So, how does the first method look in code?
// Get our group with walls
Group walls = getWallGroup();
// Create a RayIntersection object for the method to fill out
RayIntersection ri = new RayIntersection();
// Get coordinates of our character
float[] coords = getPlayerCoords();
// Get direction of our character (where is his nose pointing?)
float[] dir = getPlayerDirection();
// Try colliding (method returns true if collision has taken place)
if(walls.pick(-1, coords[0], coords[1], coords[2], dir[0], dir[1], dir[2], ri)) { // We've intersected something, check for distance if(ri.getDistance() < acceptableDistance) { // The player has collided with a wall. Make him explode... // Or something. } } See, not as scary as you expected it to be! It's
actually pretty easy, just define from where you're shooting the ray and
the direction. M3G does the rest for you. Aren't we spoiled?
You might be wondering now, what the other method is
for. It has its uses and it works a bit differently. I won't go over it
here since it's of no use to us in this tutorial. However I highly
recommend reading about it in the M3G API documentation.
Getting down and dirty
Now that I've taught you a few tricks, how about putting them to use? I have a game in mind that'll suit us just fine in complexity. Let's make 3D pong. We won't have two players, but rather you'll be bouncing the ball against the screen and your paddle will be deep in the screen. The playing field will consist of four walls that your ball will bounce against (AHA! Collision!). What we need to do now is break this simple idea down into a few key components.
The playing field and paddle
Our playing field, as I already told you, will consist of a paddle and four walls. We'll use the exact same class for generating planes as we did in part three of the tutorial , so now would be a good time to refresh your memory. The paddle and the walls will all be simple textured
planes. To make it distinguishable, the paddle will have a different
texture than the walls. All of this will be created with the
MeshFactory.createPlane method.
Here is the code snippet that creates the paddle, using the MeshFactory class we built in part three of this tutorial
:
// Create a plane using our nifty MeshFactory class
paddle = MeshFactory.createPlane("/res/paddle.png", PolygonMode.CULL_BACK); Nothing hard there, it should all be very familiar by
now. The paddle will of course need a transform, with which we'll be
able to move it. We'll call it trPaddle and this is how we initialize
it.
// Set the paddle at its initial position
trPaddle = new Transform(); trPaddle.postTranslate(paddleCoords[0], paddleCoords[1], paddleCoords[2]); paddle.setTransform(trPaddle); The paddleCoords variable is a float vector that
defines where the paddle is right now. Its contents are put into the
trPaddle each time the paddle is moved.
Now, just to be sure that our paddle will be
collidable, we'll need to set a few variables. First, we need a generic
way of recognizing our paddle when we're using the
RayIntersection.getIntersected method. It returns a Node class,
remember? So, this is a good time to use the userID variable that each
Object3D has. I created a few variables that define the walls and
paddle, here they are:
// Wall constants public static final int TOP_WALL = 0; public static final int LEFT_WALL = 1; public static final int RIGHT_WALL = 2; public static final int BOTTOM_WALL = 3; public static final int PADDLE_WALL = 4; public static final int PLAYING_FIELD = 5; Having those variables, we can simply call setUserID on
the paddle and supply it the PADDLE_WALL constant. The last thing we
need to do is to make sure our paddle is picking enabled. Remember that
the collision method is called pick? Well, all objects are checked
against two things in that method. First the scope (which we will set to
-1, so we don't have to worry about it) and second the picking enabled
flag. If an object isn't picking enabled, it simply won't be included in
the collision calculation. The flag is easily toggled by invoking the
setPickingEnabled method on the paddle object. Setting the userID and
the picking flag looks like this:
// Make sure it's collidable
paddle.setPickingEnable(true); paddle.setUserID(PADDLE_WALL); There, all done. Our paddle is now both created and
collidable. Now we just add it to a group (which I call the
playingField) and later we can check collision against all entities in
the playingField.
// Add to the playing field
playingField = new Group(); playingField.setUserID(PLAYING_FIELD); playingField.addChild(paddle); There's also some more code at the end of the
createPaddle method, but I'll go over that piece a bit later when we
talk about ball bouncing.
Our four walls are created in a very similar fashion,
so I won't repeat the code. Just check the createWalls method in the
source code if you want to see how it's done.
The ball
Some say the ball is the most crucial part of pong and some say the paddle. I'd say it's an equal mixture of both. Now what does our ball need to be? First of all, it should be a Mesh, that can be rendered onscreen (it helps if this Mesh, in turn, is actually a model of a ball). Second of all, it should hold enough physics so that our ball can move in 3D space and accelerate. Finally, it should be able to calculate its own progress through the 3D world.
Bouncing
In our very simple example, we don't need any hard theory for bouncing a ball off a wall. However, I'm going to give you some theory anyway, just in case you decide making the walls of the game rotate, or actually try doing something real and get things that will bounce off of irregular surfaces. Now, how do you bounce (mirror) a vector? What we need is to simply mirror the ball's direction vector when it hits a wall. We also have to mirror it in the correct plane (mirroring in the xz-plane won't do us much good if we collided with the right wall). This is all manageable with simple vector math. I won't go through the heavy math theory since I hope you already have a background in 3D math since you are trying to make a 3D game. Instead I'll just tell you about the approach. First of all, I've created a VectorOps class that
should be of help while you create and experiment with vectors and
bouncing. It holds a few of the more basic operations you'd like to do
with a vector such as projecting it onto a plane, the dot and cross
products, normalizing, calculating the length, etc... I've also added
the mirror method. That's the method that mirrors a vector around a
given plane (the plane in this case only needs to be defined by its
normal vector).
This means that we'll have to store the normal vectors
of each wall (and paddle) in the game. This isn't hard at all since we
only have four walls and a paddle, but for more complex projects this
could be an issue. Nevertheless, we will construct the normal vector of a
plane using the simple cross product of the two vectors that compose
the plane. (Remember 3D math? Each plane can be described as being
strung up by two vectors. By taking the cross product of these two
vectors you get the normal vector of the plane)
In our application we'll only be using the normal
vectors for mirroring calculations, and we won't worry about orientation
and other things you would want to take into consideration when
calculating normals. This also means that the left and right walls will
have exactly the same normal. As will the top and bottom walls. Normals
are calculated and put into an array of floats, so that our ball will be
able to calculate the bounce by using them. The array will be called
wallVec and here is a code snippet that shows how the left wall's normal
vector is calculated and placed in the wallVec.
float[] v = VectorOps.vector(0.0f, 1.0f, 0.0f);
float[] u = VectorOps.vector(0.0f, 0.0f, 1.0f); float[] normVec = VectorOps.calcNormal(v, u); wallVec[LEFT_WALL][0] = v; wallVec[LEFT_WALL][1] = u; wallVec[LEFT_WALL][2] = normVec; Nothing weird here really. As you all know the left
wall is actually the yz plane translated a few units down on the x-axis,
so that's why it's composed out of the pure y and z vectors. After the
vectors are created the normal vector is calculated by calling the
calcNormal method. It simply does a crossProduct of the two supplied
vectors and then normalizes the result making the vector a unit vector.
The above is done for every wall in the playing field
except for the camera's wall (the player's nose if you like). That
collision is done differently (and in a much simpler way).
The vigilant
reader and programmer
will directly realize that the above is way too much for this simple
application. Since our walls are the xz, xy and zy planes we could just
do a branched if-statement to check which plane we collided with and
mirror the coordinate that the wall represents. However, I wanted to
give you a more elegant and general solution. The above solution works
for any walls, no matter how they are translated. As I already said
once, this isn't a optimization tutorial, so if you want the application
to go a bit faster, remove the mirroring calculations and do a nested
if clause.
The implementation
Since I've shown you the guidelines now, I'll show you the implementation of the ball's render method, that not only renders, but also moves the ball and checks for collision. Let's begin with looking at the first few lines:
// Clear transform
trBall.setIdentity(); // Check if we are moving if(moveVec != null) { // First rotate the ball a bit //trBall.postRotate(rotated += 1.0f, 1.0f, 1.0f, 1.0f);
// Normalize the movement vector
ri = new RayIntersection(); float[] nMove = VectorOps.normalize(moveVec); As you can see the first lines are pretty straight-forward. What we
do here is that we first clear all transformations in the ball's
Transform class, called trBall. Then, we simply do a move check (the
game starts with the ball being still). The ball also has a variable
called rotated, that keeps track of how much the ball has rotated, to
give the impression of a rolling and flying ball. This variable is used
in the method postRotate, which you should know by heart now. After this
the interesting part starts. We allocate the RayIntersection object
we'll use for collision and normalize the ball's velocity vector (called
moveVec). Remember why we normalize? Because the pick method always
scales the distance-to-intersection by the length of your direction
vector. If the length of the direction vector is 1, the
distance-to-intersection gets unscaled. This is exactly how we want it.
Let's continue on with the method, now comes the collision:
// See if there is any collision
if(walls.pick(-1, coords[0], coords[1], coords[2], nMove[0], nMove[1], nMove[2], ri) && ri.getDistance() <= 0.5f) { // We have collided, get the surface we intersected Node n = ri.getIntersected(); // Correct our movement vector by mirroring moveVec = VectorOps.mirror(moveVec, wallVectors[n.getUserID()][2]); So there's the fabled call to Group.pick. As you see, it's
performed on a group called walls, which is actually the playingField
group passed to the ball's render method. So the walls group contains
both all the walls and the paddle. We check if the pick method returns
true and if the distance to object is smaller than 0.5, which is a
pretty reasonable distance in this application.
If we have a collision, we first fetch the node we intersected.
This is one of the walls from our walls Group. Then we fetch the wall's
user ID. Remember those? We used them as indices into the wallVec array
to store the normal vectors of the wall. Here we fetch them by invoking
the getUserID method on the Node. When we have a normal vector we call
the mirror method to mirror the ball's current direction vector around
the intersected wall. That's all there is to it.
The above really isn't that much fun though. Since all walls are
treated equally, the ball will just flatly bounce off of the paddle as well
and will give us some really boring and static gameplay. That's why
we'll make collision with the paddle a bit different. Check this piece
of code out:
// Let user have contol over the movement by moving the paddle
if(n.getUserID() == M3GCanvas.PADDLE_WALL) { // Add extra speed to a maximum amount depending on ball/paddle position float distX = (paddleCoords[0] - coords[0]) / 10.0f; float distY = (paddleCoords[1] - coords[1]) / 10.0f; moveVec[0] = Math.max(-0.3f, Math.min(moveVec[0] - distX, 0.3f)); moveVec[1] = Math.max(-0.3f, Math.min(moveVec[1] - distY, 0.3f)); // After 30 bounces it should be impossibly fast (HAH! A challenge!) moveVec[2] = moveVec[2] + 0.01f; // Increase number of bounces bounces++; } OK, so what are we doing here? Well first we're checking if we
actually are bouncing against the paddle by comparing the Node's user ID
to the PADDLE_WALL constant. Then we use some simple math to calculate
the distance from the ball's center to the paddle's center. We use this
result to skew the resulting direction vector to create more dynamic
gameplay.
Another fun thing is to make the ball go faster with each bounce.
As you know, speed in this game can be just as simple as the velocity
along the z-axis, so for each bounce against the paddle, we accelerate
the speed along the z-axis.
The last thing, incrementing the bounce's variable, is just used as
"scoring". The best player has the most bounces. A pretty simple
concept, but it works.
We've almost seen everything in the Ball.render method now, except
for the bouncing against the camera wall (or the player's nose). This is
done by the following code snippet. I won't comment it, as you should
be able to figure out what it does by just looking at it.
// Check for bouncing against screen
if(coords[2] >= -0.7f) { // Flip over the screen (same as for paddle) moveVec = VectorOps.mirror(moveVec, wallVectors[M3GCanvas.PADDLE_WALL][2]); } There! That's all there is to it. Not really much to talk about, is it?
Perspective and tube-view
I promised you that we'd use the perspective calculations a bit differently in this tutorial and make the game look a bit special. That's exactly what we'll do by just tweaking a single variable of the FOV. We'll change the fovy to a very large number, thus creating a strange shearing effect that looks quite good in this context. I've used 130 degrees here (maximum allowed value by M3G is 180) since it gives the feeling that you're actually looking down a very long and extended tube. You can play around with this value in the source code to see what kind of shearing you'll be able to get.
The game loop
Only one last thing to do now, and that's implement the game loop. You should be able to do this sleeping, with your hands tied to your back, but I'll go through the semantics once again. Let's first look at the whole game loop:
private void draw(Graphics g)
{ // Envelop all in a try/catch block just in case try { // Get the Graphics3D context g3d = Graphics3D.getInstance(); // First bind the graphics object. We use our pre-defined rendering hints. g3d.bindTarget(g, true, RENDERING_HINTS); // Clear background g3d.clear(back); // Bind camera at fixed position in origo g3d.setCamera(cam, trCam); // Render the playing field and ball g3d.render(playingField, identity); ball.render(g3d, playingField, wallVec, paddleCoords); // Check controls for paddle movement if(key[UP]) { paddleCoords[1] += 0.2f; if(paddleCoords[1] > 3.0f) paddleCoords[1] = 3.0f; } if(key[DOWN]) { paddleCoords[1] -= 0.2f; if(paddleCoords[1] < -3.0f) paddleCoords[1] = -3.0f; } if(key[LEFT]) { paddleCoords[0] -= 0.2f; if(paddleCoords[0] < -3.0f) paddleCoords[0] = -3.0f; } if(key[RIGHT]) { paddleCoords[0] += 0.2f; if(paddleCoords[0] > 3.0f) paddleCoords[0] = 3.0f; } // Set paddle's coords trPaddle.setIdentity(); trPaddle.postTranslate(paddleCoords[0], paddleCoords[1], paddleCoords[2]); paddle.setTransform(trPaddle); // Quit if user presses fire if(key[FIRE]) ball.start(); } catch(Exception e) { reportException(e); } finally { // Always remember to release! g3d.releaseTarget(); } // Do some old-fashioned 2D drawing if(!ball.isMoving()) { g.setColor(0); g.drawString("Press fire to start!", 2, 2, Graphics.TOP | Graphics.LEFT); } else { int red = Math.min(255, ball.getBounces() * 12); g.setColor(red, 0, 0); g.drawString("Score: " + ball.getBounces(), 2, 2, Graphics.TOP | Graphics.LEFT); } } So first is the standard immediate mode rendering stuff. (If you can't remember, check part three of the tutorial
).
What we do next is pretty simple too. We just render the whole
playingField group, that holds all the wall meshes and their transforms.
This way we compress the rendering of five entities into a single
group. Scene graphs make for very clean code.
After rendering the playing field we call the Ball.render method,
supply it the necessary values and it does the rest. We've already been
over this method so I won't say anything here.
Next we check the controls. Nothing big here either. We move the
paddle with the joystick and reset the ball's position with FIRE. After
we've done all 3D graphics, we do some old-fashioned 2D drawing since we
want the user to know his score right now and if the ball has gone out
of bounds (and he needs to reset it).
That's the whole enchilada, as people in the fast food industry
would most likely say. Now that you've seen the most intricate workings
of the game, here are a few shots of it in action.
As you can see the choice of 130 as fovy gives us a pretty
interesting tube/tunnel effect. Also, don't forget, this game is
actually pretty fun for being so easy. With a bit of development you
could make this game into a really addictive game. As an exercise, try
making the walls of the playing field rotate! Do remember though; you
will have to rotate all the normal vectors stored in the wallVec array
every time you rotate the walls, for the mirroring to work properly.
Have fun and I hope you learned something.
-------------------------------------------------------
源码见附件:
|
- Redikod_3D_tutorial_part_4_source_code.zip (22.5 KB)
- 下载次数: 6
发表评论
-
对Java的I/O流理解
2011-02-19 23:04 1966这是很久前另一个BLOG上的,现在不用了。转过来吧,方便查看. ... -
A*寻路(J2ME实现)
2011-02-19 23:00 1298这是很久前另一个BLOG上的,现在不用了。转过来吧,方便查看. ... -
J2ME上检测是否支持特定的API
2011-02-19 22:59 1519这是很久前另一个BLOG上的,现在不用了。转过来吧,方便查看. ... -
J2me paint[转]
2011-02-19 22:58 1432这是很久前另一个BLOG上的,现在不用了。转过来吧,方便查看. ... -
[JSR-184][3D编程指南(译文)]第一部分:快速进入移动JAVA 3D编程世界
2011-01-23 00:37 1731[英文原文&源码下载] ... -
[JSR-184][3D编程指南]Part V: Heightmap terrain rendering using M3G
2011-01-22 23:13 1884<!-- 整理收集自网络,收藏以便日后查阅 --> ... -
[JSR-184][3D编程指南]Part III: Particle systems and immediate mode rendering (2)
2011-01-22 22:56 1537<!-- 整理收集自网络,收藏以便日后查阅 --> ... -
[JSR-184][3D编程指南]Part III: Particle systems and immediate mode rendering (1)
2011-01-22 22:48 2222<!-- 整理收集自网络,收藏以便日后查阅 --> ... -
[JSR-184][3D编程指南]Part II: Light 3D theory and orientation
2011-01-22 22:29 1522<!-- 整理收集自网络,收藏以便日后查阅 --> ... -
[JSR-184][3D编程指南]Part I: Quick jump into the world of Mobile Java 3D programming
2011-01-22 22:07 2320<!-- 整理收集自网络,收藏以便日后查阅 --> ... -
[JSR-184][3D编程指南]目录索引
2011-01-22 21:25 1417Series of 3D programming tutori ... -
[Kuix][转]Kuix的事件处理机制
2009-10-08 18:19 1653原文连接 kuix这 ... -
[积累]getResourceAsStream()返回null的问题
2009-03-13 22:04 2669getResourceAsStream()可以获取JAR包内的 ... -
[资料]根据J2ME(MIDP)虚拟机对程序编写的优化方式
2009-02-27 09:39 14461、关于虚拟机 我认为 ... -
[资料]MIDP2.0中如何通过代码画半透明的圆和椭圆
2009-02-27 09:10 1607最近在做一个小Demo时,需要画一个半透明的圆,看遍M ... -
[资料]MIDP设计模式之集结贴[JavaME]
2009-02-23 22:07 13951: 架构性宣言: MI ... -
[资料]MVC在J2ME项目中的应用之MVC慨述
2009-02-23 21:48 1271内容提要: 本文简要的介绍了MVC模式的思想,并分析了M ... -
[资料]基于MVC模式的J2ME应用程序框架设计
2009-02-23 21:24 2856原文:http://www.mcu123.com/ ... -
[资料]线程在J2ME应用中的使用
2009-02-22 17:05 1601简要说明: 非常好的一篇文章,谈论到了线程各个方面的问题 ... -
[JSR-135][资料]渐进式下载
2009-02-22 16:17 1896Progressive download ...
相关推荐
【JSR-184】3D编程指南第二部分:光照3D理论与定向 在3D编程领域,理解和应用光照理论以及物体的定向是至关重要的。JSR-184,全称Java 3D API,是Java平台上的一个标准,它提供了用于创建和操作3D图形的强大工具。...
总之,【JSR-184】的3D编程指南Part V专注于使用M3G在移动设备上实现基于高度图的地形渲染,涉及图像处理、3D网格构建、纹理映射、光照以及移动平台的图形渲染技术。通过实践和理解这些概念,你将能够创建出逼真的3D...
【JSR-184】是Java Micro Edition (Java ME)平台中的一项规范,全称为“Mobile 3D Graphics API”。这个规范旨在为移动设备提供3D图形编程接口,使得开发者能够在小型设备上构建丰富的三维应用程序,如游戏、虚拟...
【JSR-184】是Java Micro Edition (Java ME) 中的一个标准,全称为"Mobile 3D Graphics API",旨在为移动设备提供3D图形编程接口。这个标准允许开发者在小型设备上创建复杂的3D图形应用,比如游戏或者可视化工具。本...
在提供的压缩包"jsr_184_midlet.rar_DEMO_jsr184_jsr184-m3g.jar_m3g"中,我们可以看到与JSR 184相关的几个关键元素: 1. **DEMO**:这是一个演示程序,用于展示JSR 184技术的实际应用。通过这个DEMO,开发者或用户...
java.lang.ClassNotFoundException: javax.measure.converter.ConversionException所需的jar
赠送jar包:undertow-websockets-jsr-2.1.7.Final.jar; 赠送原API文档:undertow-websockets-jsr-2.1.7.Final-javadoc.jar; 赠送源代码:undertow-websockets-jsr-2.1.7.Final-sources.jar; 赠送Maven依赖信息...
赠送jar包:undertow-websockets-jsr-2.1.7.Final.jar; 赠送原API文档:undertow-websockets-jsr-2.1.7.Final-javadoc.jar; 赠送源代码:undertow-websockets-jsr-2.1.7.Final-sources.jar; 赠送Maven依赖信息...
赠送jar包:jsr311-api-1.1.1.jar; 赠送原API文档:jsr311-api-1.1.1-javadoc.jar; 赠送源代码:jsr311-api-1.1.1-sources.jar; 赠送Maven依赖信息文件:jsr311-api-1.1.1.pom; 包含翻译后的API文档:jsr311-api...
**JSR-135编程指导** JSR-135,全称为JavaTM Media Framework API,是Java ME(J2ME)平台中用于多媒体应用开发的重要规范。它为移动和嵌入式设备提供了处理音频、视频和图像的能力,使得开发者能够创建功能丰富的...
赠送jar包:undertow-websockets-jsr-2.2.14.Final.jar; 赠送原API文档:undertow-websockets-jsr-2.2.14.Final-javadoc.jar; 赠送源代码:undertow-websockets-jsr-2.2.14.Final-sources.jar; 赠送Maven依赖信息...
赠送jar包:undertow-websockets-jsr-2.2.14.Final.jar; 赠送原API文档:undertow-websockets-jsr-2.2.14.Final-javadoc.jar; 赠送源代码:undertow-websockets-jsr-2.2.14.Final-sources.jar; 赠送Maven依赖信息...
用jsr184编写的手机3d编程实例,用户可以任意旋转箭头,放大缩小等等。包含如何使用数据定义mesh,如何操作camera如何旋转等等,程序功能较繁杂,但是界面较粗糙(数据定义的模型当然是越简单越好啦),学习意义大于...
赠送jar包:jackson-datatype-jsr310-2.11.4.jar; 赠送原API文档:jackson-datatype-jsr310-2.11.4-javadoc.jar; 赠送源代码:jackson-datatype-jsr310-2.11.4-sources.jar; 赠送Maven依赖信息文件:jackson-...
《3-D Game Development on JSR-184 v1_0_3》是关于使用Java 3D技术在J2ME平台上开发3D游戏的一份重要资料,它为初学者提供了一个宝贵的入门教程。JSR-184,全称为Java ME 3D API,是Java Micro Edition(J2ME)平台...
赠送jar包:jackson-datatype-jsr310-2.12.5.jar; 赠送原API文档:jackson-datatype-jsr310-2.12.5-javadoc.jar; 赠送源代码:jackson-datatype-jsr310-2.12.5-sources.jar; 赠送Maven依赖信息文件:jackson-...
标题:WebBeans -- JSR-299 描述:WebBeans是Gavin King的力作,专注于为Java EE平台提供上下文与依赖注入(Contexts and Dependency Injection)。 ### WebBeans (JSR-299) 知识点详解 #### 一、架构与合同 Web...
赠送jar包:jackson-datatype-jsr310-2.11.4.jar; 赠送原API文档:jackson-datatype-jsr310-2.11.4-javadoc.jar; 赠送源代码:jackson-datatype-jsr310-2.11.4-sources.jar; 赠送Maven依赖信息文件:jackson-...