2D Scroller using meshes

Hi to everybody.

I’m trying to implement a vertical infinte scrolling using this tecnique:

  1. I draw a plane made of quads, bigger than screen, then look it from the top
  2. move the mesh on y axis
  3. when a row of the matrix disappears, detach it, change the textures, and retach in the other side of the plane

The first attempt was done using Quads and LOTS of node: one for each quad. It works but it’s too slow (300 fps on a i7 with a grid of 32x25.

Here the core:

[code] for (int i = 0; i < GRID_HEIGTH; i++) {
bgr[i] = new Node();
for (int j = 0; j < GRID_WIDTH; j++) {
bgn[i * GRID_WIDTH + j] = new Node();
qn[i * GRID_WIDTH + j] = new Quad(1, 1);
gn[i * GRID_WIDTH + j] = new Geometry(i + “,” + j, qn[i * GRID_WIDTH + j]);
// gn[i * GRID_WIDTH + j] = new Geometry(j + “,” + i, qn[0]);
int idx = (int) (Math.random()*3);

            Material m = null;
            
            switch (idx) {
                case 0:
                    m = mat1;
                    break;
                case 1:
                    m = mat2;
                    break;
                case 2:
                    m = mat3;
                    break;
            }
            
            gn[i * GRID_WIDTH + j].setMaterial(m);
            
            
            bgn[i * GRID_WIDTH + j].attachChild(gn[i * GRID_WIDTH + j]);
            bgn[i * GRID_WIDTH + j].setLocalTranslation(j-16, i-6, 0);
            bgr[i].attachChild(bgn[i * GRID_WIDTH + j]);
        }
        n0.attachChild(bgr[i]);
    } [/code]

So i’ve tried to create a single mesh by myself without using nodes and quads, but pure triangles.

package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.util.BufferUtils;

/**
 * test
 *
 * @author normenhansen
 */
public class Main extends SimpleApplication {

    private static final int GRID_WIDTH = 64;
    private static final int GRID_HEIGTH = 48;
    private Node n0;
    Material mat3;
    private Vector3f[] vertices;
    private Vector2f[] texCoord;
    private int[] indexes;

    public static void main(String[] args) {
        Main app = new Main();

        app.start();
    }

    @Override
    public void simpleInitApp() {
        /*
         0.___.1    2.___.3
          |  /|      |  /|
          | / |      | / |
         4./__.5    6./__.7
         8.___.9   10.___.11
          |  /|      |  /|
          | / |      | / |
       12 ./__.13  14./__.15 
        
         */
        viewPort.setBackgroundColor(ColorRGBA.Blue);
        cam.setLocation(new Vector3f(GRID_WIDTH / 2, -GRID_HEIGTH / 2, 24));

        // luce
        DirectionalLight sunLight = new DirectionalLight();
        sunLight.setDirection(new Vector3f(1, -1, -2).normalizeLocal());
        sunLight.setColor(ColorRGBA.White);
        rootNode.addLight(sunLight);

        n0 = new Node("n0");

        vertices = new Vector3f[GRID_WIDTH * GRID_HEIGTH * 4];
        texCoord = new Vector2f[GRID_WIDTH * GRID_HEIGTH * 4];
        indexes = new int[GRID_WIDTH * GRID_HEIGTH * 6];

        // prepara griglia
        int count = 0;
        for (int i = 0; i < GRID_HEIGTH; i++) {
            for (int j = 0; j < GRID_WIDTH; j++) {

                count = j * 2 + i * GRID_WIDTH * 4;
                vertices[count] = new Vector3f(j, -i, 0);
                texCoord[count] = new Vector2f(0, 0);
                System.out.println("1: " + count + " - " + vertices[count]);
                vertices[count + 1] = new Vector3f(j + 1, -i, 0);
                texCoord[count + 1] = new Vector2f(1, 0);
                System.out.println("2: " + (count + 1) + " - " + vertices[count + 1]);
                vertices[count + GRID_WIDTH * 2] = new Vector3f(j, -(i + 1), 0);
                texCoord[count + GRID_WIDTH * 2] = new Vector2f(0, 1);
                System.out.println("3: " + (count + GRID_WIDTH * 2) + " - " + vertices[count + GRID_WIDTH * 2]);
                vertices[count + GRID_WIDTH * 2 + 1] = new Vector3f((j + 1), -(i + 1), 0);
                texCoord[count + GRID_WIDTH * 2 + 1] = new Vector2f(1, 1);
                System.out.println("4: " + (count + GRID_WIDTH * 2 + 1) + " - " + vertices[count + GRID_WIDTH * 2 + 1]);

                System.out.println("--------------------------");
            }
            //n0.attachChild(bgr[i]);
        }

        count = 0;
        for (Vector3f v : vertices) {
            System.out.println(count + " " + v);
            count++;

        }
        // prepara griglia
        count = 0;
        int countV = 0;
        for (int i = 0; i < GRID_HEIGTH; i++) {
            for (int j = 0; j < GRID_WIDTH; j++) {

                countV = i * GRID_WIDTH * 4 + j * 2;
                indexes[count++] = countV;
                indexes[count++] = countV + GRID_WIDTH * 2;
                indexes[count++] = countV + 1;

                indexes[count++] = countV + 1;
                indexes[count++] = countV + GRID_WIDTH * 2;
                indexes[count++] = countV + GRID_WIDTH * 2 + 1;

            }
            countV++;
        }

        Mesh mesh = new Mesh();
        mesh.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vertices));
        mesh.setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(texCoord));
        mesh.setBuffer(Type.Index, 3, BufferUtils.createIntBuffer(indexes));
        mesh.updateBound();

        Geometry geo = new Geometry("OurMesh", mesh); // using our custom mesh object

        Material mat1 = new Material(assetManager,
                "Common/MatDefs/Misc/Unshaded.j3md");
        mat1.setColor("Color", ColorRGBA.Green);
        mat1.getAdditionalRenderState().setWireframe(true);
        geo.setMaterial(mat1);
        n0.attachChild(geo);
        rootNode.attachChild(n0);

 
    }

    @Override
    public void simpleUpdate(float tpf) {
        Vector3f local = n0.getLocalTranslation();
        local.y -= tpf;
        n0.setLocalTranslation(local);

    }

    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
    }
}

And the result is much better: 3/4000 fps with 128x128

Now i’m stucked: how can i use a different texture for each small quad? TextureArray? Can i use soo much textures inside?
Each quad has uv coords: 0,0 - 0,1 first row, 1,0 - 1,1 second row and i need to recreate a behaviour like previous screenshot.

Any idea?

Thank you

One geometry per texture or use a texture atlas.

One geometry per texture could be a simpler idea but if i create 128x128 different geometry objects will i have similar performance issues like the one i had using 128x128 nodes ?

Do you really have a 128 * 128 different textures? Looked to me like there were only a handful of different textures and they were reused. So if you had five textures you’d end up with five geometries.

Are you aiming to adding lights, bloom, normal mapping and such? Because if not, maybe you could have just one big quad, link a texture to it and have a 2D lib take care of generating/modifying the texture, ideally in another thread.

The question raises more questions. What are your intentions with the quads? Are they going to be modified, breakable, static, changeable…?

I’m doing a vertical scroller. Each row of quads has potentially different textures and different positions.
The background is made of “tile” created using ad editor (do you remember SEUCK ?), so i cannot assume that there’s only a handful of different textures, if the scroller is displaying a complex background (for example, a city) is hard for me to understand how to “optimize” the geometry.
Each tile has the same shape, the only thing to change is the texture.

Another approach could be to use only one tile for row, and it should reduce the number of triangles, then i create on the fly a texture made appending every tile. Then i should apply the texture on the rectangle.
But i’m wondering if java is powerful enough to join blitmaps together without decrease too much the performance of the rendering.

Something like this:

I’d like to do a shoot-em up in a “old style” way using 3d…

At some point, there is a maximum number of textures you will have. There has to be. That is the maximum number of meshes you will ever need. It’s not the triangles that are at issue… you could have a million of them and it wouldn’t be a problem. It’s the object count that is killing you.

So, when you are generating the mesh for your screen… build lists of quads per texture using a hashmap or whatever (or a guava multimap) then build one mesh per key containing all of the different quads that use that one texture.

Also, I only count 22x17 in your screen shot… which would perform reasonably if that’s all you loaded at a time. Not ideal but you could worry about optimization later.

Else if you really want to load the whole 128x128 world at once, just split them based on texture as I already described.

the count, actually, is low (keep in mind that lots of quads are out of screen for now). For now all is like an “experiment”, and i’ve not choosed the dimension of final grid.
But i think that should be at least 64x48 if i want a good resolution to create complex background.
And i’m also thinking to add another grid below to implement a parallax scrolling using the alpha channel.

Are your textures really 100 separate files or are they already in an atlas like 99% of tile-based games already use?

For now there’s nothing, only experiments :slight_smile:
i think they will be in an atlas (not sure what ad “atlas is”, i imagine something like a collage of tiles to use to recreate the background using an index: the first tile, the fourth, the first again, the tenth, and so on)

https://www.google.com/search?q=texture+atlas&ie=utf-8&oe=utf-8

https://www.google.com/search?q=tile+sheet&biw=1281&bih=816&tbm=isch&tbo=u&source=univ&sa=X&ved=0ahUKEwj48I7i4YbMAhVCeCYKHfUXA68QsAQIWQ

Ok, yes, i want to use an atlas

Shouldn’t that be one Material instance per geometry? I have exactly the same use case, and I reuse the same Geometries over and over, but (re)apply the appropriate Material from a pool…

In this picture:

…he could get away with three materials, three geometries, three meshes. However you want to say it.

…there are only three textures so you only need three meshes (of many quads).

If he has all of them in an atlas then he only needs one mesh per atlas.

Checked the wiki, didn’t get it. Isn’t a Geometry for each “visible item”? There are more than three on the screenshot. :confused:

Not if he batches his quads together by texture. Then there would be a mesh for water, a mesh for grass, and a mesh for trees… each with a few dozen quads in them.

I’m not sure how better to explain this.

In the example above the texture are three… but it’s an example. I don’t know how many texture can be showed in the same time… sure more than three, i think, on a 64x48 grid could have also more than 1000 differen tile on average.

Yes, I’m just trying to explain the concept to him. And anyway, you will be using an atlas so you will really only have one texture per atlas… even more savings.