Sign in to follow this  
DarkMortar

[java] Tile Map Generator + Scrolling

Recommended Posts

Well, the goal of this map is not to GENERATE a map, but just to scroll it and control visibility, and it works. But I want to make it faster, with some kind help from here. I would like it if someone would help me make my program better, like faster and I need help with the following: 1) Best way to store map data array positions. 2) Fastest way to render these thingys Screenies: [url=http://img431.imageshack.us/img431/7838/1ot5.png]http://img431.imageshack.us/img431/7838/1ot5.png[/url] [url=http://img98.imageshack.us/img98/134/2ge8.png]http://img98.imageshack.us/img98/134/2ge8.png[/url] [url=http://img98.imageshack.us/img98/5139/3lg0.png]http://img98.imageshack.us/img98/5139/3lg0.png[/url] And also when I use transparent images it is SLOWER, a lot, drops from any high-end fps to like 30-40fps :( Also does it seem slighly clucky, or flickery, i have buffering of course, but just SLIGHTLY? Please someone help me, by running my source code and giving me recommendations, or change it and send it to me. It would be greatly apprecieated, and I'm sorry but im kinda noob at these things, but I promise ill get better if its fixed better. This program is based off Kev glass tuts, yet the actually tile algorithm and visibility controls are mine. I did use his SpriteStore and Sprite class though. But I also wonder if i am using them correctly in TileMap to store the images in only ONE memory location. I think not... :P
Quote:
Kee-World-Ala-Tor v1.0 This program generates (stupid) random maps without corner overlay blending tiles. Yet the point of this program is to render a scrollable tile map, with visibility controls and have it be able to scale to any window resolution (works). Features: + Renders a tile map based on 2D array algorithms + Scrollable + Grid on/off + Coord labels on/off + Transparent interface + 10 Sample Maps + Tile visibility controls (only tiles visible on screen are rendered) (works with any window size as well) + Edge of map scroll stops + Completely scalable interface + Works with any window size (within means) + Works with any tile texture size + Sync’ed scrolling based on fps Issues: + Slow, slow transparency performance. + Slow drawRect and drawString performance (Grid/coord) + a little flickery? + Other things here and there
Download link, look to right and click Download, and then you click the link at the bottom, (sorry if this is a bad way to upload). http://storeandserve.com/download/639271/WorldGenerator.rar.html

Share this post


Link to post
Share on other sites
First this should be a lot of else if statements:

if (currentMapNumber==1) { currentMap = defaultMap; }
else if (currentMapNumber==2) { currentMap = defaultMap2; }
else if (currentMapNumber==3) { currentMap = dunes; }
else if (currentMapNumber==4) { currentMap = forest; }
else if (currentMapNumber==5) { currentMap = farmville; }
else if (currentMapNumber==6) { currentMap = keenan; }
else if (currentMapNumber==7) { currentMap = bando; }
else if (currentMapNumber==8) { currentMap = half; }
else if (currentMapNumber==9) { currentMap = platformer; }
else if (currentMapNumber==10){ currentMap = blank; }






second you shouldn't have done it that way anyway.


TileMap maps[] = new TileMap[10];

map[0] = new TileMap( "Test Map 1a",
40, // width
30, // height
0, // x pos
0, // y pos
setting.getTileSize(), // tile size
setting.getScreenWidth(), // obtain the screen width
setting.getScreenHeight(), // obtain the screen height
1.0, // starting zoom level
true // scrollable?
);

map[1] = // Test Map 1b

//etc

//then later on

currentMap = map[currentMapNumber];






try to do as many decisions outside your game loop as you can.

//this line
if (showBoundrys) { gridString = "ON"; } else { gridString = "OFF"; }
//could be moved here
if (e.getKeyCode() == KeyEvent.VK_G) {
showBoundrys = !showBoundrys;
if (showBoundrys) {
gridString = "ON";
}
else {
gridString = "OFF";
}
}







This next part is very wrong. The painting loop is one of the tightest around and you are making 14 unecessary if statements for each iteration. For a 100x100 map that is 140000 ifs.

if (this.tile[i][j] == LIGHT_GRASS) {
this.tileGraphic = SpriteStore.get().getSprite("\\tile_textures\\lightGrass.png","TILE");
}
if (this.tile[i][j] == DARK_GRASS) {
this.tileGraphic = SpriteStore.get().getSprite("\\tile_textures\\darkGrass.png","TILE");
}
if (this.tile[i][j] == DIRT) {
this.tileGraphic = SpriteStore.get().getSprite("\\tile_textures\\dirt.png","TILE");
}
if (this.tile[i][j] == SAND) {
this.tileGraphic = SpriteStore.get().getSprite("\\tile_textures\\sand.png","TILE");
}
if (this.tile[i][j] == WATER) {
this.tileGraphic = SpriteStore.get().getSprite("\\tile_textures\\water.png","TILE");
}
if (this.tile[i][j] == YELLOW_GRASS){
this.tileGraphic = SpriteStore.get().getSprite("\\tile_textures\\yellowGrass.png","TILE");
}
if (this.tile[i][j] == ROCK_GRASS) {
this.tileGraphic = SpriteStore.get().getSprite("\\tile_textures\\rockGrass.png","TILE");
}
if (this.tile[i][j] == GY_GRASS) {
this.tileGraphic = SpriteStore.get().getSprite("\\tile_textures\\gyGrass.png","TILE");
}
if (this.tile[i][j] == GRASS_2) {
this.tileGraphic = SpriteStore.get().getSprite("\\tile_textures\\grass2.png","TILE");
}
if (this.tile[i][j] == DARK_SAND) {
this.tileGraphic = SpriteStore.get().getSprite("\\tile_textures\\darkSand.png","TILE");
}
if (this.tile[i][j] == RED_SAND) {
this.tileGraphic = SpriteStore.get().getSprite("\\tile_textures\\redSand.png","TILE");
}
if (this.tile[i][j] == BRICK) {
this.tileGraphic = SpriteStore.get().getSprite("\\tile_textures\\brick.png","TILE");
}
if (this.tile[i][j] == TRACK) {
this.tileGraphic = SpriteStore.get().getSprite("\\tile_textures\\track.png","TILE");
}
if (this.tile[i][j] == FARM) {
this.tileGraphic = SpriteStore.get().getSprite("\\tile_textures\\farm.png","TILE");
}

// draw the tile with the newly ontained image from the SpriteStore
// draw the image based on the x and y positions in the array and
// multiply it by the tile size for proper positioning
this.tileGraphic.draw(g,
(int)this.xPos+(this.tileGraphic.getWidth()*i),
(int)this.yPos+(this.tileGraphic.getHeight()*j));






That whole section can be reduced down to:

this.tileGraphic[i][j].draw(g,
(int)this.xPos+(this.tileGraphic[i][j].getWidth()*i),
(int)this.yPos+(this.tileGraphic[i][j].getHeight()*j));

//also you are using 'this' too much
//you can instead do whats below
tileGraphic[i][j].draw(g,
(int)xPos+(tileGraphic[i][j].getWidth()*i),
(int)yPos+(tileGraphic[i][j].getHeight()*j));






You might be thinking that storing all those images is a lot of memory, but you are not actually storing all those images. you are storing a reference to those images. So this way will take exactly the same amount of memory as the way you were doing it.

next you are drawing the entire map every frame when only about 16x12 is visible at once. Using 100x100 map as an example, you are drawing 10000 tiles when you could draw just 192. Or you should give your self a border for scrolling so 18x14 = 252

for (int i = 0; i < this.MAP_WIDTH; i++) {
for (int j = 0; j < this.MAP_HEIGHT; j++) {

//should be instead
for (int i = mapVisibleStartX; i < mapVisibleStartX + mapVisibleWidth; i++) {
for (int j = mapVisibleStartY; j < mapVisibleStartY + mapVisibleHeight; j++) {







just make sure that you put in checks to make sure that mapVisibleStartX never gets larger than MAP_WIDTH - mapVisibleWidth.

Without profiling, you can't really be too sure how much faster this will make it. But if you give it a try, I am sure that it will become at least twice as fast.

Also look over your code to see if you have a lot of 'if' statements that could be 'if else' or you can use 'switch'(just to make it look cleaner). Or those groups of 'if's could be replaced by an array.

There may be other things, but this will get you started.

Share this post


Link to post
Share on other sites
Thank you for the help, especially about just scanning only visible tiles on screen, for way less iteration. Also i dont see why 'else if' is necessary, does it run faster than if statements, or is it just cleaner? I wasnt sure if, 'if' statements really took long at all, well i guess it really would when its working every loop, and there is some minor math in them :P. Yet im quite sure that the draw recs and the draw strings and transparency will be deadly slow.

and also, thanks for doing that TileMap array, I forgot that you can delcare object arrays like map[] and just simply decide currentMap so quickly lol.

I was actually only "drawing" visible tiles. But I was still "iterating" through ALL the tiles if they were visible. before there was any visibility control at all, a large map would run at 5fps.

But, where do you think I should have the little algorithm that decides what texture to draw for each tile (the ton of if statment one) is it faster to put those in an array?

thank you for help.

[Edited by - DarkMortar on December 15, 2006 5:37:02 PM]

Share this post


Link to post
Share on other sites
Instead of this

private int[][] tile = new int[0][0];

public TileMap(String id, int width, int height, int x, int y, int size,
int sWidth, int sHeight, double zoom, boolean scroll) {

this.tile = new int[MAP_WIDTH][MAP_HEIGHT];

}
private void seekMapIDInformation(String id) {

if (id=="Test Map 1a") {
for (int i = 0; i < this.MAP_WIDTH; i++) {
for (int j = 0; j < this.MAP_HEIGHT; j++) {
random = rand.nextInt(70);
if (random>=0 && random<70) { this.tile[i][j] = LIGHT_GRASS; }
if (random>=70 && random<80) { this.tile[i][j] = GY_GRASS; }
if (random>=80 && random<95) { this.tile[i][j] = ROCK_GRASS; }
if (random>=95) { this.tile[i][j] = YELLOW_GRASS; }
}
}
}



do this

private Sprite tileGraphic[][];

public TileMap(String id, int width, int height, int x, int y, int size,
int sWidth, int sHeight, double zoom, boolean scroll) {

tileGraphic = new Sprite[MAP_WIDTH][MAP_HEIGHT];

}
private void seekMapIDInformation(String id) {

if (id=="Test Map 1a") {
for (int i = 0; i < this.MAP_WIDTH; i++) {
for (int j = 0; j < this.MAP_HEIGHT; j++) {
random = rand.nextInt(70);
if(random>=0 && random<70) {
tileGraphic [i][j] = SpriteStore.get().getSprite("\\tile_textures\\lightGrass.png","TILE");
}
else if(random>=70 && random<80) {
tileGraphic [i][j] = SpriteStore.get().getSprite("\\tile_textures\\gyGrass.png","TILE");
}
else if (random>=80 && random<95) {
tileGraphic [i][j] = SpriteStore.get().getSprite("\\tile_textures\\rockGrass.png","TILE");
}
else if (random>=95) {
tileGraphic [i][j] = SpriteStore.get().getSprite("\\tile_textures\\yellowGrass.png","TILE");
}
}
}
}



The reason you use 'else if' is that it will jump out of the rest of the statements as soon as it finds a true value. So if you have an 'if...else if' construct that has 10 separate 'else if' statements and the 2nd one is true, it will skip the rest.

Also transparency will work properly, but that is a different issue. Work on this stuff and come back later with the transparency issue.

Share this post


Link to post
Share on other sites
There is a problem with my code, im trying to compare if my Sprite array compares to the "nodraw" texture, and make it hidden, but the algorithm ends up making ALL of my textures hidden! :(

and if i comment out this if statement, it will draw the actual nodraw texture, which is wasteful.


//draw()
if (tile[i][j] == texture(NODRAW)); {
tileVis[i][j] = HIDDEN;
}


private Sprite texture(int TEXTURE) {

if (TEXTURE == LIGHT_GRASS) {
return SpriteStore.get().getSprite("\\tile_textures\\lightGrass.png","TILE");
}

else if (TEXTURE == DARK_GRASS) {
return SpriteStore.get().getSprite("\\tile_textures\\darkGrass.png","TILE");
}

else if (TEXTURE == DIRT) {
return SpriteStore.get().getSprite("\\tile_textures\\dirt.png","TILE");
}

else if (TEXTURE == SAND) {
return SpriteStore.get().getSprite("\\tile_textures\\sand.png","TILE");
}

else if (TEXTURE == WATER) {
return SpriteStore.get().getSprite("\\tile_textures\\water.png","TILE");
}

else if (TEXTURE == YELLOW_GRASS) {
return SpriteStore.get().getSprite("\\tile_textures\\yellowGrass.png","TILE");
}

else if (TEXTURE == ROCK_GRASS) {
return SpriteStore.get().getSprite("\\tile_textures\\rockGrass.png","TILE");
}

else if (TEXTURE == GY_GRASS) {
return SpriteStore.get().getSprite("\\tile_textures\\gyGrass.png","TILE");
}

else if (TEXTURE == GRASS_2) {
return SpriteStore.get().getSprite("\\tile_textures\\grass2.png","TILE");
}

else if (TEXTURE == DARK_SAND) {
return SpriteStore.get().getSprite("\\tile_textures\\darkSand.png","TILE");
}

else if (TEXTURE == RED_SAND) {
return SpriteStore.get().getSprite("\\tile_textures\\redSand.png","TILE");
}
else if (TEXTURE == BRICK) {
return SpriteStore.get().getSprite("\\tile_textures\\brick.png","TILE");
}

else if (TEXTURE == TRACK) {
return SpriteStore.get().getSprite("\\tile_textures\\track.png","TILE");
}

else if (TEXTURE == FARM) {
return SpriteStore.get().getSprite("\\tile_textures\\farm.png","TILE");
}

else return SpriteStore.get().getSprite("\\tile_textures\\nodraw.png","TILE");
}



private void seekMapIDInformation(String id) {

if (id=="Test Map 1a") {
for (int i = 0; i < MAP_WIDTH; i++) {
for (int j = 0; j < MAP_HEIGHT; j++) {

tile[i][j] = texture(LIGHT_GRASS);
}
}
}







Also, with this new method about only scanning the tiles on the screen, it has sped up the framerate of the larger maps up to 2x! Yet, there are flaws in my stuff. I get out of bounds errors, and im really irriated on the best way to represent these numbers. when i try to load a map that is 0x0 tiles, the draw() method goes crazy and goes out of bounds -1.

Heres whats going on:


/**
* Draw this tile to the graphics context provided
*
* @param g The graphics context on which to draw
*/
public void draw(Graphics2D g, boolean showCoords, boolean showBoundrys, double zoom) {

// scale the tiles based on zoom level
g.scale(zoomLevel, zoomLevel);

// iterate through the tiles shown on the screen, obtaining numerical values that tell the renderer
// which tile to draw at the tile x and y positions
for (int i = mapVisibleStartX; i < (mapVisibleStartX + mapVisibleWidth); i++) {
for (int j = mapVisibleStartY; j < (mapVisibleStartY + mapVisibleHeight); j++) {

// a NODRAW tile is represented the same as a HIDDEN tile (no graphic) (bugged)
/*if (tile[i][j] == texture(NODRAW)); {
tileVis[i][j] = HIDDEN;
}*/

// compare array position's numerical value to the texture value,
// and get the .png from the SpriteStore memory location
if (tileVis[i][j] == VISIBLE) {

// draw the sprite tile image at proper coords
tile[i][j].draw(g,
(int)xPos+(TILE_SIZE*i),
(int)yPos+(TILE_SIZE*j));

// if 'c' was pressed, draw strings on the tiles, and center them
// note: only visible tiles draw these strings
if (showCoords) {
coordDraw = "(" + i + "," + j + ")";
g.drawString(coordDraw,getTileXPos(i)+(TILE_SIZE/2)-coordDraw.length()*3+(int)xPos,
getTileYPos(j)+(TILE_SIZE/2)+(int)yPos+3);
}

// if 'g' was pressed draw a rectangle around the visible tiles
if (showBoundrys) {
g.drawRect(getTileXPos(i-1)+(int)xPos+TILE_SIZE,
getTileYPos(j-1)+(int)yPos+TILE_SIZE,TILE_SIZE,TILE_SIZE);
}
}
}
}
}


/**
* This method alters variables every time a scroll key is pressed,
* to keep track of the new tile visibility x and y positions.
*
* It also knows the x and y positions of the tiles furthest to the
* right of the screen.
*/
private void tileScreenUpdate() {

// Far left visible tile
mapVisibleStartX = (int)(Math.abs(xPos)-TILE_SIZE) / TILE_SIZE;
mapVisibleStartY = (int)(Math.abs(yPos)-TILE_SIZE) / TILE_SIZE;

if (mapVisibleStartX < 0) { mapVisibleStartX = 0; }
if (mapVisibleStartY < 0) { mapVisibleStartY = 0; }

// width/height far tile, draw 1 tile ahead...
mapVisibleWidth = (int)(Math.abs(xPos)+SCREENWIDTH +TILE_SIZE) / TILE_SIZE;
mapVisibleHeight = (int)(Math.abs(yPos)+SCREENHEIGHT+TILE_SIZE) / TILE_SIZE;

// if you are near corners, set the start visibilities at max...
if (mapVisibleStartX >= (MAP_WIDTH - mapVisibleWidth)) { mapVisibleStartX = MAP_WIDTH - mapVisibleWidth; }
if (mapVisibleStartY >= (MAP_HEIGHT - mapVisibleHeight)) { mapVisibleStartY = MAP_HEIGHT - mapVisibleHeight; }
}


The new control methods (ex)

/**
* Move Right : move the map to the right
* @param amount : the amount based off settings
* @param delta : the modifyer that syncs movements w/ current fps
*/
public void moveRightX(double amount, long delta) {

// update of what tiles are on-screen
tileScreenUpdate();

// sync
scrollAmount = (delta * amount) / oneNanoSecond;
// if statement to prevent "slow movement" under high fps
if (scrollAmount < amount) { scrollAmount = amount; }


// test
if ((xPos) > (-(MAP_WIDTH*TILE_SIZE)+SCREENWIDTH-scrollAmount)) {
xPos += scrollAmount;
}
// out of boundrys check
else if ((xPos-SCREENWIDTH-scrollAmount) <= (-(MAP_WIDTH*TILE_SIZE))) {
xPos=(-(MAP_WIDTH*TILE_SIZE)+SCREENWIDTH);
} else return;
}


/**
* Crutial algorithm that saves fps greatly (especially on large maps)
* visibilityControl() is called every operation loop in the WorldGenerator class
* to check for currently visibly tiles within the screen window. The rest of the
* tiles not within the window are HIDDEN, and not drawn. This algorithm does render
* one tile ahead of the screen (+TILE_SIZE), so that no background trailing is seen
* when you scroll the map's edges.
*/
public void visibilityControl() {

// make all tiles in map HIDDEN before decisions (cls tile) (bad - slowing everything down!)
for (int i = 0; i < MAP_WIDTH; i++) {
for (int j = 0; j < MAP_HEIGHT; j++) {
tileVis[i][j] = HIDDEN;
}
}

for (int i = mapVisibleStartX; i < (mapVisibleStartX + mapVisibleWidth); i++) {
for (int j = mapVisibleStartY; j < (mapVisibleStartY + mapVisibleHeight); j++) {

tileVis[i][j] = VISIBLE;
}
}
}


Also i notice under low fps, i get horrible "HIDDEN" trails from tiles not showing up fast enough, i thought that i am rendering 1 tile ahead, but under low fps, i notice black trail flickering, :(.

[Edited by - DarkMortar on December 16, 2006 2:08:31 AM]

Share this post


Link to post
Share on other sites
Ok, here is the source code now, and its bugged though. My loops go out of bounds if the screen size window goes off visible tiles, i think it may be better not to have the seperate tileVis[][] array because i wont need it if i have the loop knowing whats visible and whats not. So if you hit next map at anything over 640*480, there will be out of bounds error because the map is 20x20 and it will fo off the screen :(. Please help me fix this, ive been commenting out stuff and im having trouble.

your jump start truly did help on larger maps when things were working, i got 2x the fps before on large maps.

The new code:
http://storeandserve.com/download/643392/WorldGenerator.rar.html


EDIT: also i just got transparency to run quickly.

by using this:

System.setProperty("sun.java2d.opengl","True");
System.setProperty("sun.java2d.noddraw","");
System.setProperty("sun.java2d.accthreshold", "0");
System.setProperty("sun.java2d.ddforcevram", "true");

yet if i enable the openGL, i will get high speed 1k fps if i dont cap it, but my transparent images are distorted, but it still runs quite fast. Also, but if i comment out the opengl "true", my fps is more like 200-250, EVEN with transparent images, and they are displayed like they should, but its not more than 250 :P

[Edited by - DarkMortar on December 16, 2006 10:33:43 PM]

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this