Slick2d tilebased game lights

Hi.
I’m rather new to java programming and i’m trying to learn by doing and seeing examples.
And i’m currently trying to writa a game which use a tilemap.

And i need it to be dark and some light sources.

This i have and it is working fine.

But now to the tricky problem, how can i get the “enemies” to be in the shadows (not visible) until it comes to a light source?
The solution i have now draws the enemy, the redraws the map ontop of the enemy so that the floor i sabove the enemy, and i dont se them.

here is my code:


for(int i=0;i<enemyCount;i++){
	enemies[i].render(container, g);
}
//LIGHTS
            for (int y = 0; y < map.tiledMap.getHeight(); y++) {
                for (int x = 0; x < map.tiledMap.getWidth(); x++) {
                   
                   Image image = map.tiledMap.getTileImage(x, y,   map.tiledMap.getLayerIndex("WALL"));
                   if (image == null) {
                      image = map.tiledMap.getTileImage(x, y,   map.tiledMap.getLayerIndex("FLOOR"));
                   }
                   
                   image.setColor(Image.TOP_LEFT, lightValue[x][y][0], lightValue[x][y][1], lightValue[x][y][2], 1);
                   image.setColor(Image.TOP_RIGHT, lightValue[x+1][y][0], lightValue[x+1][y][1], lightValue[x+1][y][2], 1);
                   image.setColor(Image.BOTTOM_RIGHT, lightValue[x+1][y+1][0], lightValue[x+1][y+1][1], lightValue[x+1][y+1][2], 1);
                   image.setColor(Image.BOTTOM_LEFT, lightValue[x][y+1][0], lightValue[x][y+1][1], lightValue[x][y+1][2], 1);
                   image.draw(x*16, y*16);
                }
             }

I would add a boolean method in… err… enemy:


	public boolean isInShadow(){
		// checks if the object is in the shadows
	}

Then:


	for(int i=0;i<enemyCount;i++){
+ 		if(!enemies[i].isInShadow()){
			enemies[i].render(container, g);
+		}	
 	}

That would only render enemies that are not in shadow. Keeping render stuff separated from logic would make everything easy, as the enemy IS there even if it’s not rendered. Hope it helps!

PS: Sorry, but I wrote that in a hurry before going to bed :slight_smile:
PS2: A better loop that I strongly recommend you to implement:


		for (Enemy enemy : enemies) {
			if(!enemy.isInShadow()){
				enemy.render(container, g);
				}
		}

PS3: What is container? :-\ I got the feeling that you shouldn’t need it, at least not there or not by that name.
Also, enemies must be a List for my code to work :slight_smile:

Thanks, yes that is on way of doing it, it have crossed my mind to do something like that.

But as my shadow have a fading effect on the edges then I would have to fade the enemy likewise, and also all buildings and other stuff that is under the shadows.

Is there any other choice of creating darkness?

I’ve experimented with alphamaps but havent got any result that I liked or even worked any good (low fps, enemy not visible, enemy always visible. etc.)

More exactly what i want is:

Layer 1: tilemap
Layer 2: buildings and units
Layer 3: darkness

So when my gameclock reaches evening Layer 3 would gradually become darker and darker, except where the lightsources are.

@_Al3x
actually your first literation is better, avoid creation of Iterator object.

I solved it, blockwise. Not as nice as I hoped for but if you have any other great ideas, please post :wink:


//dayligt 0,00007/second
double daylight = 0;
if(timeOfDay > 68400){
       	//Past 19:00
       	if(timeOfDay < 82800){
       		//Between 19 n 23
      		daylight = ((timeOfDay - 68400) * 0.00007);
       	}else if(timeOfDay > 82800){
     		daylight = 1f;
       	}
}else if(timeOfDay < 14400){
       	daylight = 1f;
}else if(timeOfDay < 28800){
	//Between 04 n 08
	daylight = 1 - ((timeOfDay - 14400) * 0.00007);
}else{
	daylight = 0f;
}

            
g.setColor(new Color(0f,0f,0f,(float) daylight));       
Rectangle[][] darkness= new Rectangle[map.getWidthInTiles()][map.getHeightInTiles()];
for (int y=0; y<map.getHeightInTiles(); y++) {
    for (int x=0; x<map.getWidthInTiles(); x++) {
         darkness[x][y] = new Rectangle(x*16,y*16,17,17);
         if(lights[x][y] != 1){
            	g.fill(darkness[x][y]);
         }
     }
}

And my addLight function which adds ligth like:

= darktiles

  • = no darktiles
    X = lightsource
    ###########
    ###########
    ########
    ###
    ###
    ###X###
    ###
    ###
    ####
    ####
    ##########

public void addLight(int x, int y){
			lights[map.convPixToTile(x)-1][map.convPixToTile(y)-2] = 1;
			lights[map.convPixToTile(x)][map.convPixToTile(y)-2] = 1;
			lights[map.convPixToTile(x)+1][map.convPixToTile(y)-2] = 1;
			
			lights[map.convPixToTile(x)-2][map.convPixToTile(y)-1] = 1;
			lights[map.convPixToTile(x)-1][map.convPixToTile(y)-1] = 1;
			lights[map.convPixToTile(x)][map.convPixToTile(y)-1] = 1;
			lights[map.convPixToTile(x)+1][map.convPixToTile(y)-1] = 1;
			lights[map.convPixToTile(x)+2][map.convPixToTile(y)-1] = 1;
			
			lights[map.convPixToTile(x)-2][map.convPixToTile(y)] = 1;
			lights[map.convPixToTile(x)-1][map.convPixToTile(y)] = 1;
			lights[map.convPixToTile(x)][map.convPixToTile(y)] = 1;
			lights[map.convPixToTile(x)+1][map.convPixToTile(y)] = 1;
			lights[map.convPixToTile(x)+2][map.convPixToTile(y)] = 1;
			
			lights[map.convPixToTile(x)-2][map.convPixToTile(y)+1] = 1;
			lights[map.convPixToTile(x)-1][map.convPixToTile(y)+1] = 1;
			lights[map.convPixToTile(x)][map.convPixToTile(y)+1] = 1;
			lights[map.convPixToTile(x)+1][map.convPixToTile(y)+1] = 1;
			lights[map.convPixToTile(x)+2][map.convPixToTile(y)+1] = 1;
			
			lights[map.convPixToTile(x)-1][map.convPixToTile(y)+2] = 1;
			lights[map.convPixToTile(x)][map.convPixToTile(y)+2] = 1;
			lights[map.convPixToTile(x)+1][map.convPixToTile(y)+2] = 1;
			
		}

@DasKaktus:

I wouldn’t keep individual Rectangle objects per tile. Just draw them with g.drawRect(x, y, width, height) instead, no need for individual instances. And why are you having both a darkness and a light array? One should be enough.

And then for your addLight() method: Loops.


public void addLight(float lightX, float lightY, float radius){
    float radiusSqrd = radius*radius;

    int minX = (int)Math.floor(lightX - radius), minY = Math.floor(lightY - radius);
    int maxX = (int)Math.ceil(lightX + radius), maxY = Math.ceil(lightX + radius);

    for(int y = minY; y < maxY; y++){
        for(int x = minX; x < maxX; x++){
            float dx = x+0.5f - lightX, dy = y+0.5f - lightY;
            if(dx*dx + dy*dy < radiusSqrd){
                //Tile (x, y) is in range of the light
            }
        }
    }
}

Thanks, did’nt even realise that I had 2 arrays. ::slight_smile:
And that addlight function is much easier to use with radius and everything.

PS: I’ve made little changes to the addLight.

Chnged the maxY it took lightX instead of lightY :wink:
And added check to se if it tried to mark “tiles” outside of the gamebox as lit.


public void addLight(float lightX, float lightY, float radius){
		    float radiusSqrd = radius*radius;

		    int minX = (int)Math.floor(lightX - radius), minY = (int)Math.floor(lightY - radius);
		    int maxX = (int)Math.ceil(lightX + radius), maxY = (int)Math.ceil(lightY + radius);

		    for(int y = minY; y < maxY; y++){
		        for(int x = minX; x < maxX; x++){
		            float dx = x+0.5f - lightX, dy = y+0.5f - lightY;
		            if(dx*dx + dy*dy < radiusSqrd){
		            	if(x >= 0 && y >= 0 && x <= map.getWidthInTiles() && y <= map.getHeightInTiles()){
		            		lights[x][y] = 1; //Tile (x, y) is in range of the light
		            	}
		            }
		        }
		    }
		}

PS2:
And now the FPS is much better, thank you again.
Too little thought behind my code… :confused:

o_O


public void addLight(float lightX, float lightY, float radius){
		    float radiusSqrd = radius*radius;

		    int minX = (int)Math.floor(lightX - radius), minY = (int)Math.floor(lightY - radius);
		    int maxX = (int)Math.ceil(lightX + radius), maxY = (int)Math.ceil(lightY + radius);

		    minX = Math.max(0, Math.min(minX, map.getWidthInTiles());
		    maxX= Math.max(0, Math.min(maxX, map.getWidthInTiles());

		    minY = Math.max(0, Math.min(minY, map.getHeightInTiles());
		    maxY= Math.max(0, Math.min(maxY, map.getHeightInTiles());

		    for(int y = minY; y < maxY; y++){
		        for(int x = minX; x < maxX; x++){
		            float dx = x+0.5f - lightX, dy = y+0.5f - lightY;
		            if(dx*dx + dy*dy < radiusSqrd){
		            	if(x >= 0 && y >= 0 && x <= map.getWidthInTiles() && y <= map.getHeightInTiles()){
		            		lights[x][y] = 1; //Tile (x, y) is in range of the light
		            	}
		            }
		        }
		    }
		}

O(1) is slightly better than O(n^2) for light clipping IMO. =S

@theagentd
I don’t see difference between those codes.


public void addLight(float lightX, float lightY, float radius){
		    float radiusSqrd = radius*radius;

		    int minX = (int)Math.floor(lightX - radius), minY = (int)Math.floor(lightY - radius);
		    int maxX = (int)Math.ceil(lightX + radius), maxY = (int)Math.ceil(lightY + radius);

		    minX = Math.max(0, Math.min(minX, map.getWidthInTiles());
		    maxX= Math.max(0, Math.min(maxX, map.getWidthInTiles());

		    minY = Math.max(0, Math.min(minY, map.getHeightInTiles());
		    maxY= Math.max(0, Math.min(maxY, map.getHeightInTiles());

		    for(int y = minY; y < maxY; y++){
		        for(int x = minX; x < maxX; x++){
		            float dx = x+0.5f - lightX, dy = y+0.5f - lightY;
		            if(dx*dx + dy*dy < radiusSqrd){
		                lights[x][y] = 1; //Tile (x, y) is in range of the light
		            }
		        }
		    }
		}

Fail of me. The 4 extra lines eliminates the need for testing each tile against the map bounds.

you are able to leave out the Math.min for the minx and the Math.max for the max values.
It wont do any iterations in these for-loops, if your max is bigger or equal than your min.

Bounds… y u no auto cast negative value to 0 on array indexing… LoL ;D

Oh, right, yeah, brain fail there. But my point still stands. It’s better to clip the light’s bounding box than to check if each tile is inside the world.

y u no make sense?

@theagentd
“No sense” is what appear on JGO threads that are solved ;D

Thats right :slight_smile: