- 浏览: 816074 次
- 性别:
- 来自: 广州
-
最新评论
-
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 1987这是很久前另一个BLOG上的,现在不用了。转过来吧,方便查看. ... -
A*寻路(J2ME实现)
2011-02-19 23:00 1337这是很久前另一个BLOG上的,现在不用了。转过来吧,方便查看. ... -
J2ME上检测是否支持特定的API
2011-02-19 22:59 1539这是很久前另一个BLOG上的,现在不用了。转过来吧,方便查看. ... -
J2me paint[转]
2011-02-19 22:58 1456这是很久前另一个BLOG上的,现在不用了。转过来吧,方便查看. ... -
[JSR-184][3D编程指南(译文)]第一部分:快速进入移动JAVA 3D编程世界
2011-01-23 00:37 1777[英文原文&源码下载] ... -
[JSR-184][3D编程指南]Part V: Heightmap terrain rendering using M3G
2011-01-22 23:13 1911<!-- 整理收集自网络,收藏以便日后查阅 --> ... -
[JSR-184][3D编程指南]Part III: Particle systems and immediate mode rendering (2)
2011-01-22 22:56 1566<!-- 整理收集自网络,收藏以便日后查阅 --> ... -
[JSR-184][3D编程指南]Part III: Particle systems and immediate mode rendering (1)
2011-01-22 22:48 2254<!-- 整理收集自网络,收藏以便日后查阅 --> ... -
[JSR-184][3D编程指南]Part II: Light 3D theory and orientation
2011-01-22 22:29 1559<!-- 整理收集自网络,收藏以便日后查阅 --> ... -
[JSR-184][3D编程指南]Part I: Quick jump into the world of Mobile Java 3D programming
2011-01-22 22:07 2377<!-- 整理收集自网络,收藏以便日后查阅 --> ... -
[JSR-184][3D编程指南]目录索引
2011-01-22 21:25 1447Series of 3D programming tutori ... -
[Kuix][转]Kuix的事件处理机制
2009-10-08 18:19 1674原文连接 kuix这 ... -
[积累]getResourceAsStream()返回null的问题
2009-03-13 22:04 2705getResourceAsStream()可以获取JAR包内的 ... -
[资料]根据J2ME(MIDP)虚拟机对程序编写的优化方式
2009-02-27 09:39 14671、关于虚拟机 我认为 ... -
[资料]MIDP2.0中如何通过代码画半透明的圆和椭圆
2009-02-27 09:10 1629最近在做一个小Demo时,需要画一个半透明的圆,看遍M ... -
[资料]MIDP设计模式之集结贴[JavaME]
2009-02-23 22:07 14301: 架构性宣言: MI ... -
[资料]MVC在J2ME项目中的应用之MVC慨述
2009-02-23 21:48 1286内容提要: 本文简要的介绍了MVC模式的思想,并分析了M ... -
[资料]基于MVC模式的J2ME应用程序框架设计
2009-02-23 21:24 2870原文:http://www.mcu123.com/ ... -
[资料]线程在J2ME应用中的使用
2009-02-22 17:05 1629简要说明: 非常好的一篇文章,谈论到了线程各个方面的问题 ... -
[JSR-135][资料]渐进式下载
2009-02-22 16:17 1923Progressive download ...
相关推荐
嵌入式八股文面试题库资料知识宝典-华为的面试试题.zip
训练导控系统设计.pdf
嵌入式八股文面试题库资料知识宝典-网络编程.zip
人脸转正GAN模型的高效压缩.pdf
少儿编程scratch项目源代码文件案例素材-几何冲刺 转瞬即逝.zip
少儿编程scratch项目源代码文件案例素材-鸡蛋.zip
嵌入式系统_USB设备枚举与HID通信_CH559单片机USB主机键盘鼠标复合设备控制_基于CH559单片机的USB主机模式设备枚举与键盘鼠标数据收发系统支持复合设备识别与HID
嵌入式八股文面试题库资料知识宝典-linux常见面试题.zip
面向智慧工地的压力机在线数据的预警应用开发.pdf
基于Unity3D的鱼类运动行为可视化研究.pdf
少儿编程scratch项目源代码文件案例素材-霍格沃茨魔法学校.zip
少儿编程scratch项目源代码文件案例素材-金币冲刺.zip
内容概要:本文深入探讨了HarmonyOS编译构建子系统的作用及其技术细节。作为鸿蒙操作系统背后的关键技术之一,编译构建子系统通过GN和Ninja工具实现了高效的源代码到机器代码的转换,确保了系统的稳定性和性能优化。该系统不仅支持多系统版本构建、芯片厂商定制,还具备强大的调试与维护能力。其高效编译速度、灵活性和可扩展性使其在华为设备和其他智能终端中发挥了重要作用。文章还比较了HarmonyOS编译构建子系统与安卓和iOS编译系统的异同,并展望了其未来的发展趋势和技术演进方向。; 适合人群:对操作系统底层技术感兴趣的开发者、工程师和技术爱好者。; 使用场景及目标:①了解HarmonyOS编译构建子系统的基本概念和工作原理;②掌握其在不同设备上的应用和优化策略;③对比HarmonyOS与安卓、iOS编译系统的差异;④探索其未来发展方向和技术演进路径。; 其他说明:本文详细介绍了HarmonyOS编译构建子系统的架构设计、核心功能和实际应用案例,强调了其在万物互联时代的重要性和潜力。阅读时建议重点关注编译构建子系统的独特优势及其对鸿蒙生态系统的深远影响。
嵌入式八股文面试题库资料知识宝典-奇虎360 2015校园招聘C++研发工程师笔试题.zip
嵌入式八股文面试题库资料知识宝典-腾讯2014校园招聘C语言笔试题(附答案).zip
双种群变异策略改进RWCE算法优化换热网络.pdf
内容概要:本文详细介绍了基于瞬时无功功率理论的三电平有源电力滤波器(APF)仿真研究。主要内容涵盖并联型APF的工作原理、三相三电平NPC结构、谐波检测方法(ipiq)、双闭环控制策略(电压外环+电流内环PI控制)以及SVPWM矢量调制技术。仿真结果显示,在APF投入前后,电网电流THD从21.9%降至3.77%,显著提高了电能质量。 适用人群:从事电力系统研究、电力电子技术开发的专业人士,尤其是对有源电力滤波器及其仿真感兴趣的工程师和技术人员。 使用场景及目标:适用于需要解决电力系统中谐波污染和无功补偿问题的研究项目。目标是通过仿真验证APF的有效性和可行性,优化电力系统的电能质量。 其他说明:文中提到的仿真模型涉及多个关键模块,如三相交流电压模块、非线性负载、信号采集模块、LC滤波器模块等,这些模块的设计和协同工作对于实现良好的谐波抑制和无功补偿至关重要。
内容概要:本文探讨了在工业自动化和物联网交汇背景下,构建OPC DA转MQTT网关软件的需求及其具体实现方法。文中详细介绍了如何利用Python编程语言及相关库(如OpenOPC用于读取OPC DA数据,paho-mqtt用于MQTT消息传递),完成从OPC DA数据解析、格式转换到最终通过MQTT协议发布数据的关键步骤。此外,还讨论了针对不良网络环境下数据传输优化措施以及后续测试验证过程。 适合人群:从事工业自动化系统集成、物联网项目开发的技术人员,特别是那些希望提升跨协议数据交换能力的专业人士。 使用场景及目标:适用于需要在不同通信协议间建立高效稳定的数据通道的应用场合,比如制造业生产线监控、远程设备管理等。主要目的是克服传统有线网络限制,实现在不稳定无线网络条件下仍能保持良好性能的数据传输。 其他说明:文中提供了具体的代码片段帮助理解整个流程,并强调了实际部署过程中可能遇到的问题及解决方案。
基于C#实现的检测小说章节的重复、缺失、广告等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档 基于C#实现的检测小说章节的重复、缺失、广告等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档~ 基于C#实现的检测小说章节的重复、缺失、广告等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档 基于C#实现的检测小说章节的重复、缺失、广告等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档 基于C#实现的检测小说章节的重复、缺失、广告等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档 基于C#实现的检测小说章节的重复、缺失、广告等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档