package org.newdawn.slick.examples.lights;
import java.util.ArrayList;
import org.newdawn.slick.BasicGame;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.SpriteSheet;
import org.newdawn.slick.util.Bootstrap;
/**
* This example shows using vertex colours on a tile map to producing a lighting
* effect. The approach is very efficient and pretty flexible. It can be extended
* in many ways.
*
* The down side is that the resolution of your lighting map is the same as that of your
* tile map. So the small area you'll see a change in lighting across is 32x32 pixels in
* this case (the size of a single tile). This can be worked round by rendering black tiles
* over an existing map at a different resolution. For instance, you may use 16x16 black tiles
* over a 32x32 pixels tiled map to get a better resolution of lighting.
*
* This example essentially generates a random map of tiles, creates a set of lights and calculates
* their effect on each of vertexs in the tiled map. When rendering we apply those calculated values
* to the tile image vertex colours before rendering it. This gives us the effect of lighting
* the tiles.
*
* @author kevin
*/
public class LightTest extends BasicGame {
/** The width of the tile map in tiles */
private static final int WIDTH = 15;
/** The height of the tile map in tiles */
private static final int HEIGHT = 15;
/** True if we're going to render with lighting */
private boolean lightingOn = true;
/** True if we're going to render coloured lighting .... oooooh disco inferno! */
private boolean colouredLights = false;
/** The sprite sheet we're using for our tiles */
private SpriteSheet tiles;
/** The tile map we'll randomly generate and render */
private int[][] map = new int[WIDTH][HEIGHT];
/**
* The values calculated for each vertex of the tile map,
* note how it's one more to account for the bottom corner of the map.
* The 3 dimension is for colour components (red, green, blue) used for coloured lighting
*/
private float[][][] lightValue = new float[WIDTH+1][HEIGHT+1][3];
/** The lights we've defined */
private ArrayList lights = new ArrayList();
/** The main light that we'll move around with the mouse, held seperately so we can update it */
private Light mainLight;
/**
* Create the example game
*/
public LightTest() {
super("Light Test");
}
/**
* Initialise our resources for the example
*
* @param container The game container the game is running in
*/
public void init(GameContainer container) throws SlickException {
tiles = new SpriteSheet("testdata/tiles.png", 32,32);
generateMap();
}
/**
* Randomly generate a tile map
*/
private void generateMap() {
// cycle through the map placing a random tile in each location
for (int y=0;y<HEIGHT;y++) {
for (int x=0;x<WIDTH;x++) {
map[x][y] = 0;
// 20% of tiles have features
if (Math.random() > 0.8) {
map[x][y] = 1 + (int) (Math.random() * 7);
}
}
}
// create and add our lights
lights.clear();
mainLight = new Light(8f,7f,4f,Color.white);
lights.add(mainLight);
lights.add(new Light(2,2,2f,Color.red));
lights.add(new Light(2,11,1.5f,Color.yellow));
lights.add(new Light(12,2,3f,Color.green));
// finally update the lighting map for the first time
updateLightMap();
}
/**
* Update the vertex values for lighting based on the current
* light configuration.
*/
private void updateLightMap() {
// for every vertex on the map (notice the +1 again accounting for the trailing vertex)
for (int y=0;y<HEIGHT+1;y++) {
for (int x=0;x<WIDTH+1;x++) {
// first reset the lighting value for each component (red, green, blue)
for (int component=0;component<3;component++) {
lightValue[x][y][component] = 0;
}
// next cycle through all the lights. Ask each light how much effect
// it'll have on the current vertex. Combine this value with the currently
// existing value for the vertex. This lets us blend coloured lighting and
// brightness
for (int i=0;i<lights.size();i++) {
float[] effect = ((Light) lights.get(i)).getEffectAt(x, y, colouredLights);
for (int component=0;component<3;component++) {
lightValue[x][y][component] += effect[component];
}
}
// finally clamp the components to 1, since we don't want to
// blow up over the colour values
for (int component=0;component<3;component++) {
if (lightValue[x][y][component] > 1) {
lightValue[x][y][component] = 1;
}
}
}
}
}
/**
* Update the game
*
* @param container The container the game is running in
* @param delta The amount of time that passed since last update (in seconds)
*/
public void update(GameContainer container, int delta)
throws SlickException {
// toggle the lighting on/off
if (container.getInput().isKeyPressed(Input.KEY_L)){
lightingOn = !lightingOn;
}
// toggle the use of coloured lighting on/off
if (container.getInput().isKeyPressed(Input.KEY_C)){
colouredLights = !colouredLights;
// we need to recaculate the lighting values because
// colours may now be involved
updateLightMap();
}
}
/**
* Notification that the mouse was dragged
*
* @param oldx The old x coordinate of the mouse
* @param oldy The old y coordinate of the mouse
* @param newx The new x coordinate of the mouse
* @param newy The new y coordinate of the mouse
*/
public void mouseDragged(int oldx, int oldy, int newx, int newy) {
mousePressed(0, newx, newy);
}
/**
* Notification that mouse was pressed
*
* @param button The button that was pressed
* @param x The x coordinate the mouse was pressed at
* @param y The y coordinate the mouse was pressed at
*/
public void mousePressed(int button, int x, int y) {
mainLight.setLocation((x-64)/32.0f,(y-50)/32.0f);
updateLightMap();
}
/**
* Render the tile map and lighting to the game window
*
* @param container The container the game is running in
* @param g The graphics context to which we can render
*/
public void render(GameContainer container, Graphics g)
throws SlickException {
// display some instructions on how to use the example
g.setColor(Color.white);
g.drawString("Lighting Example", 440, 5);
g.drawString("Press L to toggle light", 80, 560);
g.drawString("Press C to toggle coloured lights", 80, 575);
g.drawString("Click or Drag to move the main light", 80, 545);
// move the display to nicely position the tilemap
g.translate(64,50);
tiles.startUse();
// cycle round every tile in the map
for (int y=0;y<HEIGHT;y++) {
for (int x=0;x<WIDTH;x++) {
// get the appropriate image to draw for the current tile
int tile = map[x][y];
Image image = tiles.getSubImage(tile % 4, tile / 4);
if (lightingOn) {
// if lighting is on apply the lighting values we've
// calculated for each vertex to the image. We can apply
// colour components here as well as just a single value.
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);
} else {
// if lighting is turned off then use "1" for every value
// so we just have full colour everywhere.
float light = 1;
image.setColor(Image.TOP_LEFT, light, light, light, 1);
image.setColor(Image.TOP_RIGHT, light, light, light, 1);
image.setColor(Image.BOTTOM_RIGHT, light, light, light, 1);
image.setColor(Image.BOTTOM_LEFT, light, light, light, 1);
}
// draw the image with it's newly declared vertex colours
// to the display
image.drawEmbedded(x*32,y*32,32,32);
}
}
tiles.endUse();
}
/**
* Entry point to the example game
*
* @param argv The arguments provided at the command line
*/
public static void main(String[] argv) {
Bootstrap.runAsApplication(new LightTest(), 600, 600, false);
}
}