So I’m intending to use JME3 to make a 2D top-down RTS, I have the following code:
TileMap.java
[java]/*
- To change this template, choose Tools | Templates
- and open the template in the editor.
*/
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.math.Transform;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.BatchNode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.texture.Texture;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
import org.json.JSONObject;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.util.BufferUtils;
public class TileMap extends Node {
HashMap<String, Integer> nameToIndex=new HashMap<String, Integer>();
ArrayList<Tile> tiles=new ArrayList<Tile>();
Mesh mymesh = new Mesh();
Material tileSheet;
int rows = 1;
int cols = 1;
float tileSize;
int mapSize;
Tile[] map;
BatchNode myBatch;
public TileMap(String name, float tileSize, AssetManager manager, String tilemap) {
super(name);
myBatch = new BatchNode(name + "-Batch");
myBatch.setLocalTransform(Transform.IDENTITY);
this.tileSize=tileSize;
generateMesh(tileSize);
tileSheet = new Material(manager, "/MatDefs/SpriteMaterial.j3md");
//<SNIP LOADING CODE> PSUEDOCODE FOLLOWS
//get texture name from asset
tileSheet.setTexture("ColorMap",manager.loadTexture(texName));
//load asset from tilemap string
//set rows, cols from asset
//for each tile type in the asset
//load tile name, index, terrain data, and material index from asset
Tile t = new Tile(name,tileIndex,terrainstuff, tileSheet.clone());
t.material.setInt("Index", tileIndex);
nameToIndex.put(name,tileIndex);
tiles.add(t);
}
public int getIndexFromName(String tileName){
return nameToIndex.get(tileName);
}
public int getIndex(int x, int y){
return x*mapSize+y;
}
public void generateTerrain(/*<SNIP PARAMS>*/){
mapSize=128;//<SNIPPED PARAM REFERENCE>
map=new Tile[mapSize*mapSize];
for(int x = 0; x<mapSize; x++){
for(int y = 0; y<mapSIZE; y++){
//<SNIPPED ACTUAL IMPL>
int tile = (int)(Math.random()*getNumTiles());//<NOT REAL IMPL>
setTile(x,y,tile,false);
}
}
myBatch.batch();
}
public void setTile(int x, int y, int tile) {
setTile(x,y,tile,true);
}
public void setTile(int x, int y, int tile,boolean rebatch) {
Geometry cur =(Geometry) myBatch.getChild(getTileName(x, y));
if(cur==null){
cur = new Geometry(getTileName(x, y), mymesh);
cur.setMaterial(tiles.get(tile).mat);
cur.getMaterial().setInt("Index", tile);
cur.setUserData("myTile", tile);
myBatch.attachChild(cur);
cur.setLocalTranslation(x*tileSize, 0, y*tileSize);
} else {
myBatch.detachChild(cur);
cur.setMaterial(tiles.get(tile).mat);
cur.setUserData("myTile", tile);
myBatch.attachChild(cur);
}
map[getIndex(x,y)]=tiles.get(tile);
if(rebatch) {
rebatch();
}
}
public void rebatch(){
myBatch.batch();
}
private String getTileName(int x, int y) {
return "tile("+x+","+y+")";
}
private void generateMesh(float len) {
mymesh = new Mesh();
float hlen = len/2;
final float EPS = 0.01f;
hlen*=(1+EPS);//REMOVES SEAMS VISIBLE AT SOME ZOOM LEVELS
Vector3f[] verts = {
new Vector3f(-hlen,0,-hlen),
new Vector3f(-hlen,0,hlen),
new Vector3f(hlen,0,hlen),
new Vector3f(hlen,0,-hlen)
};
//<SNIP BOILER PLATE UV'S NORMS AND BUFFER GENERATION>
}
public int getSize() {
return mapSize;
}
public int getNumTiles(){
return tiles.size();
}
}
[/java]
/MatDefs/SpriteMaterial.j3md
[java]MaterialDef SpriteMaterial {
MaterialParameters {
Texture2D ColorMap
Int Index
Int Cols
Int Rows
}
Technique {
VertexShader GLSL100: /Shaders/SpriteVertShader.vert
FragmentShader GLSL100: /Shaders/SpriteFragShader.frag
WorldParameters {
WorldViewProjectionMatrix
}
RenderState
{
Blend Alpha
}
}
}
[/java]
both shaders:
[java]//////////////////////////////////////
//Shaders/SpriteVertShader.vert
//////////////////////////////////////
uniform mat4 g_WorldViewProjectionMatrix;
uniform int m_Cols;
uniform int m_Rows;uniform int m_Index;
attribute vec3 inPosition;
attribute vec2 inTexCoord;
varying vec2 texCoord1;void main(){
int xInd= int(mod(m_Index, m_Cols));
int yInd= m_Index / m_Cols; float xCoord = inTexCoord.x/m_Cols;
float yCoord = inTexCoord.y/m_Rows;
xCoord = xCoord + (xInd * (1.0/float(m_Cols)));
yCoord = yCoord + (yInd * (1.0/float(m_Rows)));
xCoord = xCoord;
yCoord = 1-yCoord; texCoord1 = vec2(xCoord,yCoord);
gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0);
}
//////////////////////////////////////////////////////
//Shaders/SpriteFragShader.frag
////////////////////////////////////////////////////////
uniform sampler2D m_ColorMap;
varying vec2 texCoord1;void main(){
vec4 color = vec4(1.0); color *= texture2D(m_ColorMap, texCoord1); gl_FragColor = color;
}[/java]
within an event handler:
[java]
for(int i = 0; i<30; i++){
int x = FastMath.nextRandomInt(0, map.getSize()-1);
int y = FastMath.nextRandomInt(0, map.getSize()-1);
int n = FastMath.nextRandomInt(0, map.getNumTiles()-1);
map.setTile(x, y, n, false);
}
map.rebatch();
[/java]
This will run just fine currently using 128 as number of tiles per side of map at ~200 fps with all in view, but when the event handler changes a tile it causes a massive frame rate drop and stutter. (without batching was getting <10 fps) Is there a better way to do a tile-map, or is there a way to offload the BatchNode.batch() method to another thread to avoid the frame-rate drop?
Am I going in an entirely wrong direction for this problem?
NOTE: I’m using the same j3md material for my animated sprites which animate by changing the Index into the sprite map.
NOTE 2: I know I’m not overriding the save/load methods, and do intend to do so, but I wanted to get it working nicely first.