好吧,你该知道为什么我这叫跳票工厂,其实这玩意还没做完。不过高度图、场景漫游、公告板技术都有,就差碰撞了,希望给需要的朋友提供一点借鉴价值,手机游戏有这么点东西也够做游戏了。话说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);
}
}
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。