1 示例5:TestPong
这是一个类似于弹球的游戏,开始让画面动起来了。效果图如下:
按数字键1、2或9、0分别移动两个绿色的弹板,球碰到板子或四周的墙壁就会反弹。
1.1 源代码
package jmetest.TutorialGuide;
import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingBox;
import com.jme.bounding.BoundingSphere;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.Text;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Sphere;
import com.jme.scene.state.MaterialState;
import com.jme.scene.state.MaterialState.ColorMaterial;
/**
* TestPong
*/
public class TestPong extends SimpleGame {
// Side walls and goal detectors
private Node sideWalls;
private Box player1GoalWall;
private Box player2GoalWall;
// Ball
private Sphere ball;
private Vector3f ballVelocity;
// Player 1
private Box player1;
private float player1Speed = 100.0f;
private int player1Score = 0;
private Text player1ScoreText;
// Player 2
private Box player2;
private float player2Speed = 100.0f;
private int player2Score = 0;
private Text player2ScoreText;
public static void main(String[] args) {
TestPong app = new TestPong();
app.setConfigShowMode(ConfigShowMode.AlwaysShow);
app.start();
}
protected void simpleUpdate() {
// Player 1 movement
if (KeyBindingManager.getKeyBindingManager().isValidCommand("PLAYER1_MOVE_UP", true)) {
player1.getLocalTranslation().z -= player1Speed* timer.getTimePerFrame();
}
if (KeyBindingManager.getKeyBindingManager().isValidCommand("PLAYER1_MOVE_DOWN", true)) {
player1.getLocalTranslation().z += player1Speed* timer.getTimePerFrame();
}
player1.getLocalTranslation().z = FastMath.clamp(player1.getLocalTranslation().z, -38, 38);
// Player 2 movement
if (KeyBindingManager.getKeyBindingManager().isValidCommand("PLAYER2_MOVE_UP", true)) {
player2.getLocalTranslation().z -= player2Speed* timer.getTimePerFrame();
}
if (KeyBindingManager.getKeyBindingManager().isValidCommand("PLAYER2_MOVE_DOWN", true)) {
player2.getLocalTranslation().z += player2Speed* timer.getTimePerFrame();
}
player2.getLocalTranslation().z = FastMath.clamp(player2.getLocalTranslation().z, -38, 38);
// Collision with player pads
if (player1.hasCollision(ball, false) || player2.hasCollision(ball, false)) {
ballVelocity.x *= -1f;
}
// Collision with side walls
if (sideWalls.hasCollision(ball, false)) {
ballVelocity.z *= -1f;
}
// Checking for goals (ie collision with back walls)
if (player1GoalWall.hasCollision(ball, false)) {
System.out.println("1 撞);
player1Score++;
player1ScoreText.getText().replace(0, player1ScoreText.getText().length(), "" + player1Score);
ball.getLocalTranslation().set(0,0,0);
} else if (player2GoalWall.hasCollision(ball, false)) {
System.out.println("2 撞);
player2Score++;
player2ScoreText.getText().replace(0, player2ScoreText.getText().length(), "" + player2Score);
ball.getLocalTranslation().set(0,0,0);
}
// Move ball according to velocity
ball.getLocalTranslation().addLocal(ballVelocity.mult(timer.getTimePerFrame()));
}
protected void simpleInitGame() {
display.setTitle("jME - Pong");
// Initialize camera
cam.setFrustumPerspective(45.0f, (float) display.getWidth()
/ (float) display.getHeight(), 1f, 1000f);
cam.setLocation(new Vector3f(-150, 200, 80));
cam.lookAt(new Vector3f(-30, 0, 10), Vector3f.UNIT_Y);
cam.update();
// Create ball
ball = new Sphere("Ball", 8, 8, 2);
ball.setModelBound(new BoundingSphere());
ball.updateModelBound();
ball.setDefaultColor(ColorRGBA.blue);
rootNode.attachChild(ball);
// Initialize ball velocity
ballVelocity = new Vector3f(100f, 0f, 50f);
// Create Player 1 pad
player1 = new Box("Player1", new Vector3f(), 2, 5, 10);
player1.setModelBound(new BoundingBox());
player1.updateModelBound();
player1.getLocalTranslation().set(-100, 0, 0);
player1.setDefaultColor(ColorRGBA.green);
rootNode.attachChild(player1);
// Create Player 2 pad
player2 = new Box("Player2", new Vector3f(), 2, 5, 10);
player2.setModelBound(new BoundingBox());
player2.updateModelBound();
player2.getLocalTranslation().set(100, 0, 0);
player2.setDefaultColor(ColorRGBA.green);
rootNode.attachChild(player2);
// Create side walls
sideWalls = new Node("Walls");
rootNode.attachChild(sideWalls);
Box wall = new Box("Wall1", new Vector3f(), 112, 2, 2);
wall.setModelBound(new BoundingBox());
wall.updateModelBound();
wall.getLocalTranslation().set(0, 0, 50);
sideWalls.attachChild(wall);
wall = new Box("Wall2", new Vector3f(), 112, 2, 2);
wall.setModelBound(new BoundingBox());
wall.updateModelBound();
wall.getLocalTranslation().set(0, 0, -50);
sideWalls.attachChild(wall);
// Create back wall, goal detector for player 1
player1GoalWall = new Box("player1GoalWall", new Vector3f(), 2, 2, 50);
player1GoalWall.setModelBound(new BoundingBox());
player1GoalWall.updateModelBound();
player1GoalWall.getLocalTranslation().set(110, 0, 0);
rootNode.attachChild(player1GoalWall);
// Create back wall, goal detector for player 2
player2GoalWall = new Box("player2GoalWall", new Vector3f(), 2, 2, 50);
player2GoalWall.setModelBound(new BoundingBox());
player2GoalWall.updateModelBound();
player2GoalWall.getLocalTranslation().set(-110, 0, 0);
rootNode.attachChild(player2GoalWall);
// Assign key bindings
KeyBindingManager.getKeyBindingManager().set("PLAYER1_MOVE_UP", KeyInput.KEY_1);
KeyBindingManager.getKeyBindingManager().set("PLAYER1_MOVE_DOWN", KeyInput.KEY_2);
KeyBindingManager.getKeyBindingManager().set("PLAYER2_MOVE_UP", KeyInput.KEY_9);
KeyBindingManager.getKeyBindingManager().set("PLAYER2_MOVE_DOWN", KeyInput.KEY_0);
// Create score showing items
player1ScoreText = Text.createDefaultTextLabel("player1ScoreText", "0");
player1ScoreText.setRenderQueueMode(Renderer.QUEUE_ORTHO);
player1ScoreText.setLightCombineMode(Spatial.LightCombineMode.Off);
player1ScoreText.setLocalTranslation(new Vector3f(0, display.getHeight()/2, 1));
rootNode.attachChild(player1ScoreText);
player2ScoreText = Text.createDefaultTextLabel("player2ScoreText", "0");
player2ScoreText.setRenderQueueMode(Renderer.QUEUE_ORTHO);
player2ScoreText.setLightCombineMode(Spatial.LightCombineMode.Off);
player2ScoreText.setLocalTranslation(new Vector3f(display.getWidth() - 30, display.getHeight()/2, 1));
rootNode.attachChild(player2ScoreText);
// Make the object default colors shine through
MaterialState ms = display.getRenderer().createMaterialState();
ms.setColorMaterial(ColorMaterial.AmbientAndDiffuse);
rootNode.setRenderState(ms);
}
}
1.2 详细说明
// Initialize camera
cam.setFrustumPerspective(45.0f, (float) display.getWidth()
/ (float) display.getHeight(), 1f, 1000f);
cam.setLocation(new Vector3f(-150, 200, 80));
cam.lookAt(new Vector3f(-30, 0, 10), Vector3f.UNIT_Y);
cam.update();
首先初始化摄像头的角度,让我们从上到下看有一个很好的视角。如果不设置cam效果是怎样呢:
按w、a、s、d、q、z等按键调整一下角度再看:
要想调个好视角还得费一番功夫,上图的视角也很不理想。
后面一段分别定义了弹球及其速度、绿色弹板(player1和player2)、两边的围墙(wall、sideWalls)、和后墙(两个玩家的目标探测仪goal detector)。这里的wall绑定到sideWalls节点上。
下面来看看怎么让画面动起来。
我们需要重载simpleUpdate函数。因为在main函数中调用app.start()启动游戏,start函数里面有个while循环,一刻不停地更新状态。 update(-1.0f);这个update是在BaseSimpleGame类里实现的,它调用了simpleUpdate。所以要想小球动起来,只需要在simpleUpdate中修改它的位置坐标即可,让它产生一定规律的运动。
// Move ball according to velocity
ball.getLocalTranslation().addLocal(
ballVelocity.mult(timer.getTimePerFrame()));
这就让小球延一个方向跑过去。ballVelocity是个速度向量,它乘以一个标量仍然是个向量。如果要使用ball.getLocalTranslation().add,产生的新的向量并没有赋给ball的localTranslation,所以用add小球不动。
下面来看看碰撞检测的方法,用hasCollision函数即可:
if (ball.hasCollision(sideWalls, false)) {
System.out.println("撞上侧墙了");
}
撞上之后需要改变小球的运动方向(向量),可以参考光线反射原理:
if (ball.hasCollision(sideWalls, false)) {
ballVelocity.z *= -1f;
}
if (KeyBindingManager.getKeyBindingManager().isValidCommand(
"PLAYER1_MOVE_UP", true)) {
player1.getLocalTranslation().z -= player1Speed
* timer.getTimePerFrame();
}
这是移动挡板,只需修改位置向量即可。没有限制的话挡板就会移到围墙外面去。
player1.getLocalTranslation().z = FastMath.clamp(player1.getLocalTranslation().z, -38, 38);
就限制了z的移动范围。函数FastMath.clamp的源代码如下:
public static float clamp(float input, float min, float max) {
return (input < min) ? min : (input > max) ? max : input;
}
如果给定的input值小于min,则返回min;如果大于max,就返回max;如果在min和max之间就返回input自己。这样我们就把一个数组固定在某个范围之内了。
现在我们基本上把这个例子弄清楚了:要想使一个物体运动只需在重载函数simpleUpdate中修改位置向量即可。可以通过hasCollision函数检测两个物体是否发生碰撞。
我在调试的时候遇到一个问题:如果小球的速度向量设置不合理,有可能检测不到碰撞,这时小球会飞出界外。比如:ballVelocity = new Vector3f(100f, 0f, 50f);时就从后挡板飞出去了,ballVelocity = new Vector3f(50f, 0f, 50f);就能在围墙内自由弹跳。我想可能是jME时钟造成的,运动的两个位置跳跃过大使之检测不到碰撞。比如我把速率调小:ballVelocity.mult((float) 0.1)又能在范围内弹跳了。