服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

用java开发3d游戏之创建浮动的球体


  在本系列的第一篇中,我们讨论了如何在开发java 3d图形程序中创建形状、灯光和背景。在随后的本篇中,我将向你展示如何为checkers3d程序创建浮动的球体。

  一、 地板

  地板是由瓷砖(用我的colouredtiles类创建)和轴标签(用java 3d中的text2d工具类构建)组成的。图5显示了地板分支子图,以前隐藏在图3中的一个"floor branch"方框中。

用java开发3d游戏之创建浮动的球体(图一)
图5.场景图的地板分支子图

  这个地板子图是用我的checkerfloor类的一个实例构建的,它可以通过调用getbg()方法来使用:

scenebg.addchild(new checkerfloor().getbg());//添加地板

  这个checkerfloor()构造器使用嵌套的循环来初始化两个arraylists。bluecoords列表包含蓝色瓷砖相关的所有坐标,而greencoords包含绿色瓷砖相关的坐标。一旦填充完arraylists,连同用于生成瓷砖的颜色被一起传递到colouredtiles对象中。colouredtiles对象是一个shape3d的子类,因此可以被直接添加到地板图中:

floorbg.addchild( new colouredtiles(bluecoords, blue) );
floorbg.addchild( new colouredtiles(greencoords, green) );

  原点处的红色方格(见于图1)是用相似的方式构建的:

point3f p1 = new point3f(-0.25f, 0.01f, 0.25f);
point3f p2 = new point3f(0.25f, 0.01f, 0.25f);
point3f p3 = new point3f(0.25f, 0.01f, -0.25f);
point3f p4 = new point3f(-0.25f, 0.01f, -0.25f);
arraylist ocoords = new arraylist( );
ocoords.add(p1); ocoords.add(p2);
ocoords.add(p3); ocoords.add(p4);
floorbg.addchild( new colouredtiles(ocoords, medred) );

  该方格的中心在xz平面的(0,0)处并且在y轴稍微向上(+0.01单位)的地方,这样就可以在瓷砖上看到它。

  该方格的每一边的长度为0.5单位。在arraylist中的四个point3f点以逆时针方向存储。对于在bluecoords和greencoords中的每一组的四个点都是如此。图6显示出方格中的点的顺序。

用java开发3d游戏之创建浮动的球体(图二)
图6.从上方看上去的origmarker

  二、 着色的瓷砖

  我的colouredtiles类扩展了shape3d并用相同的颜色定义瓷砖的几何体和外观。该几何体使用一个java 3d quadarray来描述瓷砖为一系列的四边形。其构造器是:

quadarray(int vertexcount, int vertexformat);

  vertexformat是一个静态整数集合-它指定稍后被初始化的该四边形的各种信息如坐标、颜色和法线。在colouredtiles中,quadarray平面是用下面一行代码创建的:

plane=new quadarray(coords.size(),geometryarray.coordinates|geometryarray.color_3);

  size()方法返回在arraylist中的坐标数目。坐标和颜色数据是在creategeometry()中提供的:

int numpoints=coords.size();
point3f[] points=new point3f[numpoints];
coords.toarray(points);//arraylist->数组
plane.setcoordinates(0,points);
color3f cols[]=new color3f[numpoints];
for(int i=0;i<numpoints;i++)
cols[i]=col;
plane.setcolors(0,cols);

  一个四边形的坐标顺序的正确指定是极为重要的。一个多边形的前面是指顶点形成一个逆时针方向环时的那一面。区别开前面与后面对于光线和隐藏面的选择是非常重要的;并且默认情况下,在一个场景中只有多边形的前面是可见的。在这个应用程序中,瓷砖是有向的-它们的前面朝向y轴。

  必须确保一个凸的平面多边形的每个四边形中的顶点可以是折衷的(compromised)。然而,在坐标数组中的每个四边形不需要连接到邻近其它的四边形-正好适于表达瓷砖。既然一个四边形的几何体不包括法线信息,那么一个material结点组件不可能被用来指定该四边形的在照亮时的颜色。我可以使用一个coloringattributes,但是第三种选择是设置几何体中的颜色,例如使用语句"plane.setcolors(0,cols);"。这个颜色将固定不变-不受场景中灯光的影响。

  一旦完成,用下列语句设置shape3d的几何体:

setgeometry(plane);

  形状的外观是由createappearance()管理的,这个方法使用一个java 3d polygonattribute组件来隐藏后面的显示。polygonattribute可能被用于以点或线形式生成多边形(也就是以线框架形式),并且用于翻转后面形状的法线:

appearance app=new appearance();
polygonattributes pa=new polygonattributes();
pa.setcullface(polygonattributes.cull_none);
app.setpolygonattributes(pa);

  一旦外观全部指定,它就被用下列语句固定在形状中:

setappearance(app);

  三、 地板的轴标签

  地板的轴标签是在checkerfloor()中用labelaxes()和maketext()方法生成的。labelaxes()使用两个循环来沿着x和z轴创建标签。每个标签是由maketext()构建的,然后被添加到地板的branchgroup(见图5):

floorbg.addchild(maketext(pt,""+i));

  maketext()使用text2d工具类创建一个2d串来指定颜色、字体、点大小及字体风格:

text2d message=new text2d(text,white,"sansserif",36,font.bold);
//36点粗体的sans serif

  text2d对象是一个具有一个四边形几何体(一矩形)的shape3d对象,并且外观是由一个字符串表达的材质贴图(图像)来确定的-贴图被放在前面。默认地,后面是隐藏的;如果用户移动到轴标签的后面,那么对象成为不可见的。

  点大小被转换成虚拟世界单位-方法把其是与256相除。通常,在text2d()构造器使用太大的点是一种糟糕的主意,因为这有可能导致文字的不正确着色。我建议把一个transformgroup放置在形状上方并且把它缩放到必要的大小。
每个标签的放置是由在形状上方的一个transformgroup来实现的:

transformgroup tg=new transformgroup( );
transform3d t3d=new transform3d();
t3d.settranslation(vertex);//标签的位置
tg.settransform(t3d);
tg.addchild(message);

  settranslation()仅仅影响该形状的位置。transformgroup tg被添加到地板场景图。

  四、 观察者位置

  图3中的场景图并不包括视图分支图;该分支图显示在图7中。

用java开发3d游戏之创建浮动的球体(图三)
图7.视图分支图

  这个分支是通过在wrapcheckers3d()构造器中调用simpleuniverse构造器构建的:

su=new simpleuniverse(canvas3d);

  simpleuniverse提供到视图分支图的简化存取-这是经由viewingplatform和viewer类实现的,而且这两个类被映射到图上(在图7中显示为用点画线绘制的矩形)。

  viewingplatform被用在inituserposition()中以存取viewplatform结点上方的transformgroup:

viewingplatform vp=su.getviewingplatform();
transformgroup steertg=vp.getviewplatformtransform();

  steertg相应于图7中的tg结点。它的transform3d组件分别用lookat()和invert()方法加以提取和改变:

transform3d t3d=new transform3d();
steertg.gettransform(t3d);
t3d.lookat( userposn,new point3d(0,0,0),new vector3d(0,1,0));
t3d.invert();
steertg.settransform(t3d);

  lookat()是在虚拟的世界中设置观察者位置的一种方便的方法。这个方法需要观察者的所在位置、他的观察点和一个指定向上方向的矢量。在这个应用程序中,观察者的位置是userposn((0,5,20)坐标);他向着原点(0,0,0)观看,并且沿着正向y轴向上看。这可以由图8展示。

用java开发3d游戏之创建浮动的球体(图四)
图8.lookat()的图形描述

  既然位置是相对于观察者而不是相对于场景中的一个对象,所以必须调用invert()。

  五、 观察者移动

  用户能够通过场景移动-利用视图图中的java 3d orbitbehavior工具类实现。观察者的位置是通过控制键和鼠标按钮结合方式完成移动和旋转的。

  行为是在wrapcheckers3d中的orbitcontrols()方法中建立的:

orbitbehavior orbit = new orbitbehavior(c, orbitbehavior.reverse_all);
orbit.setschedulingbounds(bounds);
viewingplatform vp = su.getviewingplatform( );
vp.setviewplatformbehavior(orbit);

  reverse_all标志保证观察点的移动沿着与鼠标一样的方向。

  提示: 还有其它许多标志和方法影响旋转、平移和缩放特性,详见orbitbehavior类文档的有关解释。

  mouserotate、mousetranslate和mousezoom是一些经常出现在许多java 3d例子中的相类似的行为类;它们与orbitbehavior的主要的差别是它们影响场景中的对象而不是影响观察者。

  提示: 大多数游戏,例如第一人称射手(fps),要求较强地控制观察者的移动,甚至超出这些工具行为能提供的功能;因此我将在后面实现我自己的行为。

六、 观看场景图

  本文中已经使用场景图来展示所讨论的编码技术,而场景图是一个理解(和检查)代码的相当有用的方法。

  我使用daniel selman的java3dtree包来帮助我实现绘图。它创建一个jframe-它用一个文本树来描述场景图(图9)。

用java开发3d游戏之创建浮动的球体(图五)
图9.checkers3d场景图的java3dtree描述

  该树(一个jtree对象)在开始时是最小化的,并且可以通过点击子文件夹图标来检查分支。当前选择结点信息出现在底部窗口中。该包包含在j3dtree.jar中,它是从http://www.manning.com/selman/下载的源代码(selman的"java 3d编程文本")的一部分。

  扩充代码来生成jtree是简单的。为了显示jframe树,wrapcheckers3d必须导入j3dtree包并且声明一个全局变量:

import com.sun.j3d.utils.behaviors.vp.*;
private java3dtree j3dtree;

  由wrapcheckers3d()构造器创建j3dtree对象:

public wrapcheckers3d(){
//另外的代码
su = new simpleuniverse(canvas3d);
j3dtree = new java3dtree( );//为sg创建一个显示树
createscenegraph( );
inituserposition( );
orbitcontrols(canvas3d);
su.addbranchgraph( scenebg );
j3dtree.updatenodes( su );//构建树显示窗口
}

  在完成场景图之后(也就是,在构造器的最后),该树的显示是用一行代码实现的:

j3dtree.updatenodes( su);

  然而,在这之前,必须调整场景图结点的能力:

j3dtree.recursiveapplycapability(scenebg);

  应该在完成内容分支组(scenebg)之后而编译或使其成为现场的之前执行这个操作。在我的代码中,这意味着在createscenegraph()中添加一行代码:

private void ceatescenegraph(){
scenebg = new branchgroup();
//创建场景的其它代码
j3dtree.recursiveapplycapability( scenebg );
scenebg.compile( );
}

  不幸的是,你不能仅调用:

j3dtree.recursiveapplycapability(su);

  在此,没有产生错误-因为simpleuniverse()构造器已经使得viewingplatform成为现场的,它可以防止进一步改变它的能力。

  既然只有内容分支的能力得到调整,那么当遇到在locale结点下的视图分支时,对updatenodes()的调用将生成一些警告消息。

  注意 在编译和执行中必须把j3dtree.jar包括在classpath中。我比较喜欢的方式是用命令行参数来实现:

javac -classpath "%classpath%;j3dtree.jar" *.java
java -cp "%classpath%;j3dtree.jar" checkers3d

  提示: 如果重复地输入classpath不适合你,象上面这样的命令行可以被隐藏在批文件或外壳脚本内部。

  java3dtree对象是一个文本方式的场景描述,这意味着我们必须自己绘制场景图。但其优点是树的生成对于程序的其它部分所产生的影响可以忽略。

  另一种方法是使用java 3d场景图编辑器(http://java3d.netbeans.org/j3deditor_intro.html)。这里显示场景图的一个图形化的版本但是也有其负面-其安装和用法是复杂的并且内存要求对于一些机器来说可能很苛刻。

扫描关注微信公众号