日期:2014-05-20  浏览次数:20876 次

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);
    }
    
}

?

?

?