I am currently working on a 2D Java game, and am currently in the process of implementing dynamic lighting, for e.g. torches, light bulbs and the sun. I however, have run into a problem. Numerous attempts with different implementations have not yet provided the desired result, either not being what I was looking, not working as expected or lagging the whole game. I have tried the following implementations:
- A (BufferedImage) mask created with RadialGradientPaint
- Asynchronous flood-fill
- Synchronous flood-fill
The RadialGradientPaint mask did not prove to be what I was looking for, my implementation of it did not handle any elements on the screen, meaning light would "go through" walls.
The second implementation, an asynchronous flood-fill, did not work properly. It only rendered part of the light, and then called it a day, especially with larger lights.
The third implementation, a synchronous flood-fill, does what I want, but because it is running on the main thread, it hangs it in the process:
The code used to create the last image is as follows:
public void lightQueue(int x, int y, float level, boolean isSun){
float newLevel;
Queue<LightData> queue = new LinkedList<LightData>();
queue.add(new LightData(x,y,level));
while(!queue.isEmpty()){
LightData d = queue.remove();
try{
newLevel = d.level - Level.getBlock(d.x, d.y).getLightBlockAmount();
}catch(NullPointerException e){
continue;
}
if(newLevel <= Level.getBlock(d.x, d.y).getLightLevel()) continue;
if(newLevel > 1f){
Level.getBlock(d.x, d.y).setLightLevel(1f);
} else {
Level.getBlock(d.x, d.y).setLightLevel(newLevel);
}
queue.add(new LightData(d.x + 1, d.y, newLevel));
queue.add(new LightData(d.x - 1, d.y, newLevel));
queue.add(new LightData(d.x, d.y + 1, newLevel));
queue.add(new LightData(d.x, d.y - 1, newLevel));
}
}
public void tick(){
for(Emitter e:emitters){
e.tick();
}
if(Main.tickCount % 20 == 0){
for(Emitter e:emitters){
if(e instanceof Sun){
lightRec((int) e.getLocation().getX(), (int)e.getLocation().getY(), e.lightLevel, true);
} else if(Level.shouldRender(e.getLocation(), Level.camera)){
lightRec((int) e.getLocation().getX(), (int)e.getLocation().getY(), e.lightLevel, false);
}
}
}
}
Edit: The rendering of blocks goes as follows:
public void render(Graphics g, Camera c){
g.drawImage(texture, c.getRelativePixelX(getLocation().getPixelX()), c.getRelativePixelY(getLocation().getPixelY()), null);
g.setColor(new Color(0f, 0f, 0f, 1f - lightLevel));
g.fillRect(c.getRelativePixelX(getLocation().getPixelX()), c.getRelativePixelY(getLocation().getPixelY()), getBounds(c).width, getBounds(c).height);
g.setColor(Color.WHITE);
}
The RelativePixel stuff is only a conversion from the in-game coordinate system to onscreen pixels for proper placement.
This code is being ran twice a second to ensure that the game doesn't instantly freeze. Multiple light sources or light sources brighter than 1f (to increase the radius) will freeze the game.
So my question is, is there a more effective/better way to implement dynamic lighting? If not, is there any way I can improve the current system?
If there is anything unclear please let me know! Thank you