jpct-ae开发3D赛车游戏
jpct-ae游戏引擎的资料比较少,本人是在官网中helloworld程序的基础上进行编写的。
- 首先,先说一下游戏的框架模块。游戏包括渲染模块(场景渲染、赛车渲染和效果渲染)、游戏逻辑模块(碰撞检测和重力感应计算)、音效模块和数据模块四部分。在JPCT-AE游戏引擎的基础上,通过将3DMAX制作的赛道和赛车模型文件导入到游戏中,同时添加碰撞渲染效果,完成游戏的渲染模块;通过保持赛车位置不动,而移动赛道完成赛车的行进;通过Android中内置加速度传感器来计算手机的重力感应,从而控制赛车的左右移动;通过碰撞检测线程完成赛车越界(赛道)、赛车碰撞障碍物及赛车到达终点线终止;通过其它线程完成游戏开始倒计时、速度仪表盘转动等;数据模块存储程序的全局常量;音效模块实现了碰撞音效和游戏背景音乐。
- 以下,直接摘自我的毕设论文了啊。。
4.1.2 JPCT-AE加载赛车和赛道模型及添加纹理
JPCT-AE加载赛车和赛道模型:首先通过Autodesk 3ds Max 软件对赛车和赛道模型进行建模,生成三维文件的格式,然后通过JPCT-AE引擎导入Assert文件夹下的obj和3ds文件,来加载赛车和赛道Object3D模型到World中。最后,World中的所有对象会被绘制到FrameBuffer中,完成模型的加载。
加载赛车obj格式文件(carbody.obj):其中mergeAll()是合并数组中的所有对象成一个大的对象。loadOBJ()是加载OBJ格式的文件到一个对象数组中。
Object3D.mergeAll(Loader.loadOBJ(getResources().getAssets().open("carbody.obj"),null, scale));
加载赛道3ds格式文件(straightroad.3ds):与载入MD2格式不同的是,如果载入一个3ds文件它是没有动画效果的。其中load3DS()是将3DS格式的文件加载到一个对象数组中。3ds是一个支持很多转换工具的3D-Studio格式。
Loader.load3DS(getAssets().open("straightroad.3ds"), scale);
同理,加载游戏场景中的其它模型。下面是绘制小车的代码:
try {
carBody= Object3D.mergeAll(Loader.loadOBJ(getResources()
.getAssets().open("carbody.obj"),null, 0.4f));
} catch (IOException e) {
e.printStackTrace();
}
carBody.calcTextureWrapSpherical();
carBody.setTexture("texture");
carBody.rotateX((float) Math.PI);
carBody.build();
world.addObject(carBody);
carBody.strip();
JPCT-AE添加纹理:为了使赛车和赛道显得更加逼真,需要增添光照、纹理,并放置在屏幕中合适的位置。
纹理定义了物体表面的结构,如花纹,图案,皱纹等等。有了纹理,模型世界才会更加丰富多彩。纹理实际上是一个二维数组,其元素是一些颜色值,每一元素称之为纹理像素。纹理对象是一个内部数据类型,存储着纹理数据。
通常一个纹理映射的步骤是:首先创建纹理对象。就是获得一个新的纹理句柄ID。其次指定纹理。就是将数据赋值给ID的纹理对象,在这一步,图像数据正式加载到了ID的纹理对象中。之后绑定纹理对象。就是将ID的纹理作为下面操作的纹理。最后纹理映射。将已绑定纹理的数据绘制到屏幕上去,在这一步,就能看到贴图的效果了。我们要将一个图像的一部分绘制到屏幕上,称之为纹理映射,就是将图像根据上述坐标系计算出要绘制的部分的各个点的纹理坐标,然后一一对应到屏幕上的坐标中去。
TextureManager tm = TextureManager.getInstance();
Texture roadTexture = newTexture(BitmapHelper.rescale( BitmapHelper.convert(rs.getDrawable( R.drawable.road)), 64,64));
tm.addTexture("roadTexture",roadTexture);
straightRoad.setTexture("roadTexture");
4.1.4JPCT-AE实现赛车行进及赛车转弯
赛车行进:本系统采用的方法是保持车的相对位置不变,通过使赛道相对于赛车向后移动来实现赛车在赛道上向前行进的效果。
在JPCT-AE中,translate(float, float, float)-com.threed.jpct.Object3D类中的方法:在世界空间中通过修改平移矩阵平移(“移动”)对象。
straightRoad.translate(0, 0.1f * SPEED, -0.241421393f * SPEED);其中SPEED的值不同,赛车行进速度也不同。
2) 赛车转弯:赛车转弯一共需要两步完成。第一步,保持车身位置不动,只是向左右转头,通过赛车绕Y轴旋转w角度,达到车头左右转的效果,需要配合重力感应检测来逆时针或顺时针旋转车头。
在JPCT-AE中,rotateY(float)- com.threed.jpct.Object3D类中的方法:用给定的w角度绕y轴旋转对象的旋转矩阵。(弧度,顺时针为正值)。
carBody.rotateY(touchTurn)
第二步,保持车的左转右转状态,利用平移方法来向左向右平移车身。
在JPCT-AE中,translate(float, float, float)-com.threed.jpct.Object3D类中的方法:在世界空间中通过修改平移矩阵平移(“移动”)对象。
carBody.translate(vector)
4.1.5JPCT-AE绘制障碍物
第一步,即通过Object3D o = Primitives.getCube(4f)实现障碍物的生成。Primitives - com.threed.jpct中的Primitives提供了一些基元(基本3D-对象)。包括返回盒子Box,圆锥体Cone,立方体Cube,圆柱Cylinder,双锥DoubleCone,椭球Ellipsoid,平面Plane,棱锥Pyramide,球体Sphere等。
第二步,将障碍物是作为子对象加到赛道中,使障碍物可以作为赛道的一部分,随赛道一起做平移运动。这一步是必需的。straightRoad.addChild(o)
Object3D o = Primitives.getCube(4f);
o.setAdditionalColor(new RGBColor(0, 255, 0));
for (int i = 0; i < obstacles.length; i++) {
obstacles[i] = o.cloneObject();
obstacles[i].setOrigin(OBSTACLES_VECTOR[i]);
obstacles[i].build();
straightRoad.addChild(obstacles[i]);
world.addObject(obstacles[i]);
obstacles[i].strip();
}
4.1.6JPCT-AE实现碰撞检测
通过添加线程来实现碰撞检测,如果不添加线程来实现,则很可能会阻塞主线程,导致Android中ANR异常等。
通过判断赛车的x轴方向的坐标是否超过赛道的边界来判断是否越界;通过计算障碍物的y坐标和障碍物与赛车x坐标的差值,来判断赛车是否撞上障碍物。
public class CheckObstacleThread extends Thread { public void run() { while (flag) { try { Thread.sleep(100); } catch (Exception e) { e.printStackTrace(); } if (HelloWorld.obstacles[i].getTransformedCenter().y > 2) { if(Math.abs(HelloWorld.carVector.x-HelloWorld.obstacles[i].getTransformedCenter().x)<10){ HelloWorld.obstacles[i].setAdditionalColor(new RGBColor(255, 0, 0)); SCORE -= 20; } i++; } if(i==30){ flag = false; } } } }
4.1.6JPCT-AE实现碰撞效果(爆炸)
通过设置3D对象的广告板模式,使对象总是面向摄像机,来实现碰撞效果。在JPCT-AE中setBillboarding(boolean)- com.threed.jpct.Object3D类中的方法,启用/禁用此对象的billboarding模式。
Object3D collision = Primitives.getPlane(1, 10);
collision.setBillboarding(true);
collision.setTexture("collision");
collision.setTransparency(20);
4.1.7JPCT-AE绘制起止线
通过建立公共DrawLine类,提供创建直线的通用方法createLine()实现绘制起止线。算法中通过JPCT-AE提供的addTriangle(SimpleVector, SimpleVector, SimpleVector,TextureInfo)- com.threed.jpct.Object3D类中的方法,添加一个三角形对象到此对象中,来添加8个三角形到直线对象中,从而绘制成直线。
Object3D startLine = DrawLine.createLine(new SimpleVector(-30f, -10f, -170f), new SimpleVector(30f, -10f, -170f), 1f, "line"); Object3D endLine = startLine.cloneObject(); straightRoad.addChild(startLine); straightRoad.addChild(endLine);
4.1.8JPCT-AE绘制动态仪表盘及分数牌
绘制仪表盘:添加仪表盘纹理和指针纹理到平面,添加线程来实现指针的旋转。每次旋转pointer.rotateZ(-(float) Math.PI /8f)个单位。最小速度为1,最大速度为11。
public class DashBoardThread extends Thread { public void run() { while (flag) { if (CHANGED_SPEED != 0 && SPEED > 0 ) { if (CHANGED_SPEED == 2) { CHANGED_SPEED = 0; pointer.rotateZ(-(float) Math.PI / 8f); pointer.rotateZ(-(float) Math.PI / 8f); } } i--; if (!TravelingFlag && i < 0) { flag = false; pointer.strip(); } try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } } }
1) 绘制分数牌(动态显示数字):在游戏中我们常常想要动态的显示数字。在FrameBuffer中有一个blit方法,它的功能是将Texture中的一部分贴到屏幕上,有点类似于Overlay,不过这个方法更加灵活,可以只贴一部分。
通过JPCT-AE中blit(Texture, int, int, int, int, int, int, int, int, int,boolean, RGBColor)实现- com.threed.jpct.FrameBuffer 类中的方法,blit()的特殊版本,允许尺寸变换,也就是说,他不会一对一复制,但可以按比例放大或缩小。第一个参数是指下面的图,Texuture对象第二、三个参数是你要在Texture对象上截下来的那个部分的左上角。第四、五个参数,是你想要放在屏幕上的位置,也是左上角。第六、七两个参数是指截下来那部分的宽和高,贴在屏幕上也是这个宽和高。在显示的时候将这个方法放在FrameBuffer的display之前,必须放在清屏(FrameBuffer的clear)和渲染与显示世界之后。否则图像会被清屏了,或者被世界中的物体挡住。
public static void blitNumber(FrameBuffer fb,Texture src,int number, int x, int y) { if (src != null) { String sNum = Integer.toString(number); for (int i = 0; i < sNum.length(); i++) { char cNum = sNum.charAt(i); int iNum = cNum - 48; fb.blit(src, iNum * 5, 0, x, y, 5, 9,30,54, 0,false,null); x += 30; } } }
4.1.9JPCT-AE实现倒计时
JPCT-AE实现倒计时牌的绘制,即绘制纹理到平面中。添加线程,通过每一秒变换一次纹理来实现倒计时效果。实现倒计时功能,可以使玩家做好开始游戏的准备,倒计时结束后,游戏正式开始。
public class CountdownThread extends Thread { public void run() { while (flag) { if (drawFlag == 3) { countdown.setTexture("countdown_3"); } else if (drawFlag == 2) { countdown.setTexture("countdown_2"); } else if (drawFlag == 1) { countdown.setTexture("countdown_1"); } else if (drawFlag == 0) { countdown.setTexture("countdown_go"); } if (drawFlag < 0) { countdown.translate(100f, 0f, 0f); countdown.strip(); flag = false; TravelingFlag = true; } try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } drawFlag--; } }