J2ME中3D场景漫游的实现
???? 好吧,你该知道为什么我这叫跳票工厂,其实这玩意还没做完。不过高度图、场景漫游、公告板技术都有,就差碰撞了,希望给需要的朋友提供一点借鉴价值,手机游戏有这么点东西也够做游戏了。话说M3G现在不太吃香,所以果断停止钻研,打算投奔更底层的OpenGL怀抱。
??
运行截图
?
?
???? 几点要说明的:
???? 1)公告板技术(如果你还不了解什么是公告板就去百度一下),一个面就能做一棵树,并且看上去效果还不错。优点是一个面,非常节省资源,缺点是你必须对所有的面进行对齐处理,但往往有的时候很难去判断哪些面在可视范围以内而需要对齐,其中就可能涉及到复杂的判定,况且对齐操作也需要一点点开销。所以大部分3D手机游戏用两个交叉面来做一棵树,开销稍微多了一点,但这样不管在X、Z轴的哪个方向看效果都不错,也比一个面的树更立体一些,更大的优点就是这棵树放到场景里就行,不需要进行对齐操作。
?
???? 2)高度图场景的漫游,漫游的原理主要是根据摄像机当前位置,得到在高度图中对应位置的高度值,然后根据所在面四个顶点的Y值,插值法求出当前点的高度。但是插值法是有误差的,当四边形尺寸越大时误差越明显。本程序中使用的算法还有一点问题,研究的朋友自己解决一下吧。:)
?
???? 主要代码在下面贴出,感兴趣的朋友也可以加我一起讨论。
???? 游戏开发讨论群:50184572
???? 我的QQ:350751373
?
?? 创建高度图的代码
import java.io.IOException;import javax.microedition.lcdui.Image;import javax.microedition.m3g.Appearance;import javax.microedition.m3g.CompositingMode;import javax.microedition.m3g.Group;import javax.microedition.m3g.Image2D;import javax.microedition.m3g.IndexBuffer;import javax.microedition.m3g.Loader;import javax.microedition.m3g.Mesh;import javax.microedition.m3g.PolygonMode;import javax.microedition.m3g.Texture2D;import javax.microedition.m3g.Transform;import javax.microedition.m3g.TriangleStripArray;import javax.microedition.m3g.VertexArray;import javax.microedition.m3g.VertexBuffer;public class HeightMap{ //高度信息数组 private short[] heightMap; //地图数据尺寸 private int mapWidth; private int mapHeight; //保存地图所有的多边形 private Mesh[][] map; //地图编码纹理 private Texture2D landTexture; //高度图扩大到地图尺寸的比例(正常0.20 or 0.10) private float resolution = 0.1f; //高度比例 private short heightResolution = 10; //每块地图数据代表的3D单位距离 private float perUnit = 1f; public HeightMap(String heightMapImgSrc, String textureImgSrc, float perUnit) throws IOException { if (resolution <= 0.0001f || resolution > 1.0f) throw new IllegalArgumentException("Resolution too small or too large"); this.perUnit = perUnit; // 加载文件 Image img = Image.createImage(heightMapImgSrc); //加载并解析高度图 loadMapImage(img); //创建地图纹理 landTexture = createTexture(textureImgSrc); //根据高度图创建整个地图多边形 createQuads(); //按设置比例对地图所有面进行缩放、平移操作 setTransform(); } public HeightMap(short[] heightMap, int mapWidth, int mapHeight, String textureImgSrc, float perUnit) throws IOException { if (resolution <= 0.0001f || resolution > 1.0f) throw new IllegalArgumentException("Resolution too small or too large"); this.heightMap = heightMap; this.mapWidth = mapWidth; this.mapHeight = mapHeight; this.perUnit = perUnit; for (int i = 0; i < heightMap.length; i++) { heightMap[i] = (short) (heightMap[i] * heightResolution); } //创建地图纹理 landTexture = createTexture(textureImgSrc); //根据深度图创建整个地图多边形 createQuads(); //按设置比例对地图所有面进行缩放、平移操作 setTransform(); } private Mesh[][] getQuads() { return map; } public Group getMeshGroup() { Group group = null; try { group = new Group(); Mesh[][] map = getQuads(); for (int x = 0; x < getMapWidth() - 1; x++) { for (int y = 0; y < getMapHeight() - 1; y++) { group.addChild(map[x][y]); } } return group; } catch (Exception e) { System.out.println("Heightmap error: " + e.getMessage()); e.printStackTrace(); } return group; } public int getMapWidth() { return mapWidth; } public int getMapHeight() { return mapHeight; } public float getPositionHeight(float x, float z) { int col0 = (int) (x / perUnit); int row0 = (int) (z / perUnit); int col1 = col0 + 1; int row1 = row0 + 1; if (col1 > mapWidth) col1 = 0; if (row1 > mapHeight) row1 = 0; System.out.println(col0+","+row0+" "+col1+","+row1); float height00 = heightMap[col0 + row0 * mapWidth]; float height01 = heightMap[col1 + row0 * mapWidth + 1]; float height11 = heightMap[col1 + row1 * mapWidth]; float height10 = heightMap[col0 + row1 * mapWidth + 1]; float tx = x / perUnit - col0; float ty = z / perUnit - row0; float txty = tx * ty; float height = height00 * (1.0f + txty - tx - ty) + height01 * (tx - txty) + height11 * txty + height10 * (tx - txty); return height/510; } /** * 根据地图数据和高度数据创建整个地图所有的四边形 */ private void createQuads() { short[] heights = new short[4]; map = new Mesh[mapWidth][mapHeight]; for (int x = 0; x < (mapWidth - 1); x++) { for (int y = 0; y < (mapHeight - 1); y++) { //读取四个顶点高度 heights[0] = heightMap[x + y * mapWidth]; heights[1] = heightMap[x + y * mapWidth + 1]; heights[3] = heightMap[x + (y + 1) * mapWidth]; heights[2] = heightMap[x + (y + 1) * mapWidth + 1]; //根据四个顶点的高度图创造面 map[x][y] = createQuad(heights); } } } /** * 加载高度图,并解析出高度图的高度数据 * @param img 高度图图片 */ private void loadMapImage(Image img) { //根据图片尺寸创建像素信息数组 int[] data = new int[img.getWidth() * img.getHeight()]; //获得像素信息 img.getRGB(data, 0, img.getWidth(), 0, 0, img.getWidth(), img.getHeight()); int imgw = img.getWidth(); int imgh = img.getHeight(); //根据加载图片计算地图尺寸 mapWidth = (int) (resolution * imgw); mapHeight = (int) (resolution * imgh); //初始化高度图 heightMap = new short[mapWidth * mapHeight]; // Calculate height and width offset into image int xoff = imgw / mapWidth; int yoff = imgh / mapHeight; //设置网面每个顶点高度 for (int y = 0; y < mapHeight; y++) { for (int x = 0; x < mapWidth; x++) { heightMap[x + y * mapWidth] = (short) ((data[x * xoff + y * yoff * imgw] & 0x000000ff) * heightResolution); } } // Clear data data = null; img = null; System.gc(); } /** * 按设置比例对地图所有面进行缩放、平移操作 */ private void setTransform() { Transform localTransform = new Transform(); float scaleTimes = perUnit / 510;//地图拉伸倍数(createQuad创建的四边形边长是510) for (int x = 0; x < map.length - 1; x++) { for (int y = 0; y < map[x].length - 1; y++) { //归一化单位矩阵 localTransform.setIdentity(); //每个面平移到指定位置 localTransform.postTranslate(x * perUnit, 0.0f, y * perUnit); //localTransform.postTranslate(x * perUnit, 0.0f, (mapHeight - y) * -perUnit); //缩小网格 localTransform.postScale(scaleTimes, scaleTimes, scaleTimes); // localTransform.postMultiply(t); map[x][y].setTransform(localTransform); } } } /** * 根据高度图创建四边形 * @param heights 四个定点的高度 */ private Mesh createQuad(short[] heights) { //创建顶点数组 short[] POINTS = { -255, heights[0], -255, 255, heights[1], -255, 255, heights[2], 255, -255, heights[3], 255 }; VertexArray POSITION_ARRAY = new VertexArray(POINTS.length / 3, 3, 2); POSITION_ARRAY.set(0, POINTS.length / 3, POINTS); VertexBuffer vertexBuffer = new VertexBuffer(); vertexBuffer.setPositions(POSITION_ARRAY, 1.0f, null); //创建索引缓冲 int INDICES[] = new int[] { 0, 1, 3, 2 }; int[] LENGTHS = new int[] { 4 }; IndexBuffer indexBuffer = new TriangleStripArray(INDICES, LENGTHS); //创建外观模式 Appearance appearance = new Appearance(); //设置颜色融合模式:纹理替代,不融合 CompositingMode compositingMode = new CompositingMode(); compositingMode.setBlending(CompositingMode.REPLACE); appearance.setCompositingMode(compositingMode); //多边形拾选模式:只显示正面、平滑渲染、透视矫正 PolygonMode polygonmode = new PolygonMode(); polygonmode.setCulling(PolygonMode.CULL_FRONT); polygonmode.setPerspectiveCorrectionEnable(true); polygonmode.setShading(PolygonMode.SHADE_SMOOTH); appearance.setPolygonMode(polygonmode); //纹理映射 short[] TEXCOORDS = { 0, 0, 1, 0, 1, 1, 0, 1 }; VertexArray TEXCOORD_ARRAY = new VertexArray(TEXCOORDS.length / 2, 2, 2); TEXCOORD_ARRAY.set(0, TEXCOORDS.length / 2, TEXCOORDS); vertexBuffer.setTexCoords(0, TEXCOORD_ARRAY, 1.0f, null); //纹理贴图 appearance.setTexture(0, landTexture); Mesh mesh = new Mesh(vertexBuffer, indexBuffer, appearance); return mesh; } /* * 纹理贴图 */ private Texture2D createTexture(String textureImgSrc) { Image2D img = null; try { img = (Image2D) Loader.load(textureImgSrc)[0]; } catch (Exception e) { } Texture2D texture = new Texture2D(img); texture.setFiltering(Texture2D.FILTER_NEAREST, Texture2D.FILTER_NEAREST); //设置纹理重复 texture.setWrapping(Texture2D.WRAP_REPEAT, Texture2D.WRAP_REPEAT); return texture; }}
?
创建基于公告板技术树的代码
import javax.microedition.m3g.Appearance;import javax.microedition.m3g.CompositingMode;import javax.microedition.m3g.Group;import javax.microedition.m3g.Image2D;import javax.microedition.m3g.IndexBuffer;import javax.microedition.m3g.Mesh;import javax.microedition.m3g.Node;import javax.microedition.m3g.PolygonMode;import javax.microedition.m3g.Texture2D;import javax.microedition.m3g.TriangleStripArray;import javax.microedition.m3g.VertexArray;import javax.microedition.m3g.VertexBuffer;public class Tree{ private Mesh tree; private Group cameraGroup; // the billboard aligns itself with this camera position public Tree(Image2D image2D, Group camGroup, float x, float z, float size) { cameraGroup = camGroup; //构造顶点缓冲 VertexBuffer vertexBuffer = makeGeometry(); //构造索引缓冲 int[] indicies = { 1, 2, 0, 3 }; // the billboard is one quad int[] stripLens = { 4 }; //三角带索引缓冲 IndexBuffer indexBuffer = new TriangleStripArray(indicies, stripLens); Appearance appearance = makeAppearance(image2D); tree = new Mesh(vertexBuffer, indexBuffer, appearance); float size2 = size * 0.5f; /* The mesh is 2-by-2 in size, and so the extra 0.5 factor in the scaling reduces it to 1-by-1. */ tree.scale(size2, size2, size2); tree.setTranslation(x, size2, z); tree.setAlignment(cameraGroup, Node.Z_AXIS, null, Node.NONE); /* The billboard alignment will be along its z-axis only, no y-axis alignment is employed. */ } /* The geometry defines a square resting on top of the XZ plane, * centered at (0,0), with sides of length 2. * There are no normals, but there are texture coords. */ private VertexBuffer makeGeometry() { /* Create vertices, starting at the bottom left and going counter-clockwise. */ short[] POINTS = { -1, -1, 0, 1, -1, 0, 1, 1, 0, -1, 1, 0 }; VertexArray POSITION_ARRAY = new VertexArray(POINTS.length / 3, 3, 2); POSITION_ARRAY.set(0, POINTS.length / 3, POINTS); // create texture coordinates using the same order as the vertices short[] TEXCOORDS = { 0, 1, 1, 1, 1, 0, 0, 0 }; VertexArray TEXCOORD_ARRAY = new VertexArray(TEXCOORDS.length / 2, 2, 2); TEXCOORD_ARRAY.set(0, TEXCOORDS.length / 2, TEXCOORDS); VertexBuffer vertexBuffer = new VertexBuffer(); vertexBuffer.setPositions(POSITION_ARRAY, 1.0f, null); // no size, bias vertexBuffer.setTexCoords(0, TEXCOORD_ARRAY, 1.0f, null); return vertexBuffer; } /* The image's alpha component will cause the square's * surface to be invisible at those locations. */ private Appearance makeAppearance(Image2D image2D) { Appearance appearance = new Appearance(); //使用透明的颜色融合 CompositingMode compositingMode = new CompositingMode(); compositingMode.setBlending(CompositingMode.ALPHA); appearance.setCompositingMode(compositingMode); //设置多边形模式:只显示背面,允许透视修正 PolygonMode polygonMode = new PolygonMode(); polygonMode.setPerspectiveCorrectionEnable(true); polygonMode.setCulling(PolygonMode.CULL_BACK); appearance.setPolygonMode(polygonMode); if (image2D != null) { Texture2D texture2D = new Texture2D(image2D); texture2D.setFiltering(Texture2D.FILTER_NEAREST, Texture2D.FILTER_NEAREST); texture2D.setWrapping(Texture2D.WRAP_CLAMP, Texture2D.WRAP_CLAMP); texture2D.setBlending(Texture2D.FUNC_REPLACE); // the texture with replace the surface appearance.setTexture(0, texture2D); } return appearance; } public Mesh getMesh() { return tree; } // align the board's z-axis with the current position of the cameraGroup node public void align() { tree.align(cameraGroup); } }
?
?
?