注:本系列教程全部翻译完之后可能会以PDF的形式发布。
如果有什么错误可以到http://blog.csdn.net/kakashi8841留言或EMAIL:kakashi9bi@gmail.com给我。
jME版本 :jME_2.0.1_Stable
开发工具:MyEclipse8.5
操作系统:Window7/Vista
迄今为止,我们拥有一个带驾驶参数的box。允许我们创建不同性能类型的vehicle。box在地形上表现得很好。我们开始看一些新的。我们开始获得一些玩游戏所必需的基础。所以,让我们花这一节课来让游戏中的事物变得好看点。是时候增加一些炫的啦。我们将让terrain看起来更真实,用一辆酷的未来主义vehicle代替box,而且让这个酷的未来主义vehicle跟随terrain得更好。让我们开始!
就像之前的向导提到的一样,我们的action正在做一些相同的事,并重复很多代码。此刻,我们为了优化代码将做一些巩固。首先,AccelerateAction和BrakeAction被融合进一个单一的类VehicleRotateRightAction。然而VehicleRotateLeftAction和VehicleRotateRightAction被组合进VehicleRotateAction。
ForwardAndBackAction的update方法决定了我们想要让vehicle前进的方向,并调用vehicle相应的方法。它不再update translation。我过一会将说下这个。
/**
* 这个action调用vehicle的
* accelerate 或 brake 命令去调整它的速度
*
*/
@Override
public void performAction(InputActionEvent evt) {
if( direction == FORWARD){
node.accerate(evt.getTime());
}else if( direction == BACKWARD ){
node.brake(evt.getTime());
}
}
这里的FORWARD和BACKWARD是类的常量,而一个int参数被加到构造方法去定义我们想要移动的方向。
相似的,VehicleRotationAction被改为:
/**
* 由vehicle的转弯速度转弯。
* 如果vehicle正在后退,方向相反。
*/
@Override
public void performAction(InputActionEvent evt) {
//影响方向
if(direction == LEFT) {
modifier = 1;
} else if(direction == RIGHT) {
modifier = -1;
}
//我们想根据我们运动的方向转向不同的方向
if(vehicle.getVelocity() < 0) {
incr.fromAngleNormalAxis(
-modifier * vehicle.getTurnSpeed()
* evt.getTime(),
upAxis
);
} else {
incr.fromAngleNormalAxis(
modifier * vehicle.getTurnSpeed()
* evt.getTime(),
upAxis
);
}
vehicle.getLocalRotation().fromRotationMatrix(
incr.mult(
vehicle.getLocalRotation().toRotationMatrix(tempMa),
tempMb
)
);
vehicle.getLocalRotation().normalize();
}
RIGHT和LEFT做为常量被添加,而direction做为构造参数。modifier值定义了我们将转的方向,它是根据direction决定的。
最后,DriftAction被轻微更新了一下。
@Override
public void performAction(InputActionEvent evt) {
vehicle.drift(evt.getTime());
}
正如你看到的,translation代码被移除了。它被移往哪里?注意,我们将讲一下它。
既然action被修改了,我们需要修改一下handler使用它们。
ForwardAndBackwardAction forward =
new ForwardAndBackwardAction(
node,
ForwardAndBackwardAction.FORWARD
);
addAction(forward,"forward",true);
ForwardAndBackwardAction backward =
new ForwardAndBackwardAction(
node,
ForwardAndBackwardAction.BACKWARD
);
addAction(backward,"backward",true);
VehicleRotateAction rotateLeft =
new VehicleRotateAction(
node,
VehicleRotateAction.LEFT
);
addAction(rotateLeft,"turnLeft",true);
VehicleRotateAction rotateRight =
new VehicleRotateAction(
node,
VehicleRotateAction.RIGHT
);
addAction(rotateRight,"turnRight",true);
所以,最后的改变是Vehicle怎样移动它的位置。这个问题之前都是AccelerateAction / BrakeAction和DriftAction在应用移位的。这实际上导致vehicle每次移动2次。因此,增加一个update方法给Vehicle并在handler的update方法中调用vehicle的update。
public void update(float time){
this.localTranslation.addLocal(
this.localRotation.getRotationColumn(2, tempVa)
.mult(velocity*time)
);
}
和
vehicle.update(time);
增加到handler的update方法。
现在,vehicle移动了它原来速度的一半,所以我将调整vehicle的性能。附加地,我也觉得它转得太快。所以,我也降低了转速。
player.setTurnSpeed(2.5f);
player.setMaxSpeed(25);
player.setMinSpeed(15);
就是那样。所以,让我们用好的素材继续做下去!
7.2、Detail Texture(细节纹理)和TextureCombining(纹理组合)
我们将增加的第一个悦目的东西是detail Texture。一个detail Texture通常是由黑色和白色组合而成的图片,从而表现地面的特性。通常,黑色和白色的texture模拟了不光滑地面凹凸物形成的阴影。所以,这个texture被应用于terrain并和原始terrain的texture组合。terrain已有的颜色texture和这个detail texture的组合允许颜色texture呈现出比它本身更多的细节。
为了完成这个,我们将使用jME的multitexturing特性。这包含将第二张texture放入绘图卡/显卡(Graphics Card)的第二Texture单元,并为texture定义参数,指明它们2个是怎样被组合的。
首先,我们加载detail texture(在buildTerrain方法中进行):
//加载细节纹理并为2个terrain的texture设置组合模型
Texture t2 = TextureManager.loadTexture(
Lesson7.class.getClassLoader()
.getResource("res/Detail.jpg"),
Texture.MinificationFilter.Trilinear,
Texture.MagnificationFilter.Bilinear
);
ts.setTexture(t2, 1);
我们就像我们之前一样使用TextureManager加载一个Detail.jpg图像。我们接着设置它到我们的TextureState做为第二个texture(unit 1, 记住我们从0开始数)。这告诉TextureState ts去保存2张Texture(t1和t2).
下一步,由于这是一种细节纹理,我们想让它……很好,细节。创建一个高分辨率的texture图像不会有什么影响,由于这只是提供为地面基础texture。因此,我们将用一张相当低的分辨率并让它在地面上重复(repeat)很多次。实际上,detail纹理将在terrain上的一边到另一边repeat16次。为了允许这个repeat,我们需要设置texture的wrap模式。
t2.setWrap(Texture.WrapMode.Repeat);
现在,我们需要定义这2张texture是怎样被组合的。Texture定义了很多组合模式(combine mode)。首先,我们将设置每个texture为ApplyMode.Combine,这告诉TextureState,那2张Texture的颜色将被Combine进一个输出颜色。第一个texture将是我们的基础texture,所以我们让它用它的颜色(RGB)乘以第二张texture以得到最后颜色。我们这么做,因为第二张texture是黑色和白色。因此,细节全白,我们将得到完整颜色,而当细节完全黑色,输出颜色将是黑色。我们接着告诉texture 1,所有的输入源将从0到1,而且只适用颜色(RGB),我们不必关心Alpha值。
所以,我们主要的texture(t1)看起来将是这样:
t1.setApply(Texture.ApplyMode.Combine);
t1.setCombineFuncRGB(Texture.CombinerFunctionRGB.Modulate);
t1.setCombineSrc0RGB(Texture.CombinerSource.CurrentTexture);
t1.setCombineOp0RGB(Texture.CombinerOperandRGB.SourceColor);
t1.setCombineSrc1RGB(Texture.CombinerSource.PrimaryColor);
t1.setCombineOp1RGB(Texture.CombinerOperandRGB.SourceColor);
接下来,我们需要修改t2去应用它的颜色值给之前的texture(t1).一切都和t1一样(定义颜色来自哪里),但我们设置CombineFuncRGB为AddSigned,这意味着它将把自己的加到其它的texture单元上,该texture单元在和原始texture相乘之前就存在。
t2.setApply(Texture.ApplyMode.Combine);
t2.setCombineFuncRGB(Texture.CombinerFunctionRGB.AddSigned);
t2.setCombineSrc0RGB(Texture.CombinerSource.CurrentTexture);
t2.setCombineOp0RGB(Texture.CombinerOperandRGB.SourceColor);
t2.setCombineSrc1RGB(Texture.CombinerSource.Previous);
t2.setCombineOp1RGB(Texture.CombinerOperandRGB.SourceColor);
所以,现在已经设置了这2个texture,我们的render到屏幕的结果texture单元将是这2个美丽的结合。
然而,我们仍然没定义怎样将texture应用到terrain本身。那就是,纹理坐标(texture coordinate)。我们本应该为TerrainBlock定义texture coordinate,但幸运的是,我们不必这么做。使用setDetailTexture方法,我们能让它为我们完成。所以,调用:
tb.setDetailTexture(1, 16);
这告诉terrain去设置texture单元1 repeat 16次.那就是沿着texture的高度和宽度repeat 16次。
现在,当你运行例子,我们的box将飞奔在一个看起来有更多细节的terrain上。
7.3、Terrain法向(Normal)和朝向(Orientation)
既然我们的terrain看起来更有细节,让我们让box也在地面上跟得更好。box保持它的高度在terrain上以便它能跟着山的高度变化自己的高度,这很好,但是,它看起来却不是很正确,因为它一直保持相同的朝向(Orientation)。
我们所要做的是使用2个工具方法:getSurfaceNormal和rotateUpTo。getSurfaceNormal方法给我们沿着TerrainBlock上任何点的normal。就像getHeight它将玩家的位置插入height map最近的点并计算那点的normal。这个normal是保存在一个提供的Vector对象中,所以你可以增加:
//保存terrain的任何一个给出点的法向
private Vector3f normal = new Vector3f();
到应用程序的顶部。我们将于每次update循环期间把这个和玩家的位置做为输入传入getSurfaceNormal方法。这将在每帧给我们一个玩家当前位置的normal。
现在,我们知道我们正处的terrain的角度,我们想要调整玩家对齐山。调用rotateUpTo允许我们这样做。简单在player上带着我们从terrain获取的normal调用方法,那么我们就做完了。只是简单的处理就为游戏增加了更多的真实性。
//获取terrain在我们当前位置的normal。
//我们然后将它应用到player上面。
tb.getSurfaceNormal(
player.getLocalTranslation(),
normal
);
if(normal != null) {
player.rotateUpTo(normal);
}
现在,驾驶着box并看它是怎样跟着terrain的。太激动了!现在,让我们处理掉那个丑陋的box。
7.4、加载模型(model)
那么,现在我们想真正驾驶一辆cool的未来主义vehicle。很好,就像很多没艺术的人一样,我开始寻找免费的模型。3D Cafe 有一些未来主义的摩托车。它甚至在两边有漂亮的枪。
所以,我们需要做一些事去让这个进入我们的scene。
1、 加载.3ds文件(它是3DS MAX的格式,相当标准)
2、 转换为.jme(jME二进制格式)
3、 加载二进制.jme格式到scene graph
很明显,每次都将.3ds转为.jme不是必须的。可以优化,我们将在发布游戏之前先这么做,那么以后只需加载.jme。这在下节课的优化将进行更深入的讨论。
加载模型的第一个任务是使用jME的Model Conversion工具。有一些被支持的格式(md2、md3、milkshape、3ds、obj、ase),选择和你的格式对应的Converter。这个converter创建一个.jme二进制文件。转换需要2件事:文件的InputStream,用于保存.jme的OutputStream。所以,我们将创建一个MaxToJme converter和一个ByteArrayOutputStream对象。我们接着创建一个指向我们模型的URL(我们将使用这个URL去获取InputStream)。
调用converter的convert方法将使用jme文件的数据填充我们的ByteArrayOutputStream。
我们现在拥有能加载进scene graph的.jme数据。使用BinaryImporter我们能调用它的load方法去创建一个Node。这个Node将被用于替换vehicle中的box。
如果你继续工作你可能会注意到,model有点大…,事实上,它太巨大了。手动使用你在网上找到的模型并没有精确为我们的游戏制作。所以,我们需要缩放它到适合我们的terrain。事实上,我们将缩放很多(0.0025%)。
我们现在已经加载了bike(将buildPlayer中的box创建部分换成下面)。
Node model = null;
try {
MaxToJme C1 = new MaxToJme();
ByteArrayOutputStream BO = new ByteArrayOutputStream();
URL maxFile = Lesson7.class.getClassLoader()
.getResource("res/bike.3ds");
C1.convert(
new BufferedInputStream(maxFile.openStream()),
BO
);
model = (Node)BinaryImporter.getInstance().load(
new ByteArrayInputStream(BO.toByteArray())
);
//缩放它,让它比原来更小
model.setLocalScale(.0025f);
model.setModelBound(new BoundingBox());
model.updateModelBound();
} catch (IOException e) {
e.printStackTrace();
}
最后,用它代替在vehicle的box位置:
player = new Vehicle("Player Node",model);
我们现在有一个cool的bike用于驾驶。
另一个轻微的改变是我们将在增加model之后update实体scene。这是因为Model是由许多Node(或scene graph中的分支)组成的。由于这样,这个能影响到实体树BoundingVolumes的合并。所以,我们将调用updateGeometricState去重建scene:
scene.updateGeometricState(0, true);
我们已经确实地为我们的游戏添加了一些视觉上具有吸引力的东西。
terrain看起来更好,我们驾驶一辆真正的交通工具而且它精确紧跟着terrain。下一步,我们将再优化一点:保存一个.jme文件并加载,从而不必每次都转化,让bike在转弯时倾斜和车轮旋转。我们将介绍终点Flag,所以我们将有一个目标!继续观看!
分享到:
相关推荐
FlagRush7——拥抱大地让我们驾驶的不再是Box FlagRush8——增加随机的Flag FlagRush9——墙壁检测系统(Detection System) FlagRush10——墙壁检测系统(Detection System) 如果确实没分下载,到我博客看看吧,...
目录 1、 通过SimpleGame创建你的第一个应用程序 4 1.1、SimpleGame介绍 4 ...7、拥抱大地让我们驾驶的不再是Box 88 7.1、优化 88 附录: 88 A、摄像机简介 88 a、Camera 89 b、Camera Node 91 c、ViewFrustum 92
jmonkeyengine是一个面向冒险的java开发人员的三维游戏引擎。它是开源的、跨平台的、尖端的。3.2.4是jmonkeyengine 3 sdk的最新稳定版本,是一个完整的游戏开发套件。我们将发布3.2.x更新,直到主要的3.3版本到来。
jMonkeyEngine(jME)是一个开源的Java 3D引擎,它的效果非常好,速度也不错
Jmonkeyengine 3.0 Beginners Guide
NULL 博文链接:https://hjk685.iteye.com/blog/1912303
jMonkeyEngine 3.0 Beginner's Guide
sdk, 在netbeans上,基于 jMonkeyEngine3 SDK jMonkeyEngine软件开发工具包( SDK ) 欢迎使用jMonkeyEngine软件开发工具包( SDK )的知识库。 这个SDK使用jMonkeyEngine简化了 3D 个应用程序的开发。 它包含开发完整...
jmonkeyengine, 在Java中,一个完整的3D 游戏开发 jMonkeyEngine jMonkeyEngine是一款用于冒险Java开发人员的3D 游戏引擎。 它是开放的,跨平台的,和cutting的。 3.2.0是 jMonkeyEngine 3 SDK的最新稳定版本,一个...
共12章JME中文教程,演示代码经测试无错
jmonkeyengine:纯Java编写的一个完整的3D游戏开发套件
NULL 博文链接:https://snake.iteye.com/blog/578982
jMonkeyEngine软件开发套件(SDK) 欢迎使用jMonkeyEngine软件开发套件(SDK)的存储库。 该SDK简化了使用jMonkeyEngine开发3D应用程序的过程。 它包含开发完整应用程序所需的一切。 我们的SDK(或jmonkeyplatform...
JME学习文档 中文版 jMonkeyEngine 入门学习
jMonkeyEngine是一个众所周知且广泛使用的免费开放源代码游戏引擎。 该演示应用程序展示了最新的jME版本jME 3的功能和某些功能。jMonkeyEngine使用OpenGL作为其图形库。 该软件包含的一些功能包括:粒子效果,水渲染...
赛车游戏 使用 JMonkeyEngine 创建的赛车游戏。 汽车的转向非常僵硬。 按住 F 将车轮摩擦设置为 0 并改为漂移。
Voider 该游戏正在创作中。 这样做的基本思想是创建一个刺客信条Unity副本,但差别很小。... 如果您有什么想法或想加入我们的团队,可以给我发送电子邮件: 团队由一名程序员和一名设计师负责徽标和搅拌器模型。