Tile based Lighting Question

Started by
5 comments, last by Mekuri 11 years, 8 months ago
I've been working on a game for few months now and it is finally starting to look like something.
Now, I've run into another mental block. I have this small issue with my lighting where it doesn't update correctly in the negative direction (Left and up).
Confused? - Well I have a video to show you what I mean.

If you go to 0:30 you'll see me digging to the right, and soon after I'll start digging to the left. As you can see the light does not update correctly, the solid tiles aren't lit up. As soon as I place a torch though, the problem is no longer there.

[media]
[/media]

I think I encountered this problem before, but I can't for the life of me see the solution right now.
My thoughts are that since all the lights are updated in the positive direction (i.e tile 10,10 will be updated before 11,10, etc) it may cause the light to "lean towards" the right.
I think I remember this to be a simple solution- Am I totally off?


Thanks in advance for the help.

As a side note- I wouldn't mind a bit of feedback on what I've made so far smile.png

Check out the game I am making here - http://www.youtube.com/user/NasarethMekuri

Advertisement
Without seeing your code, I can't really help much, but I think your intuition about updating left->right/top->down is on the right track. Fortunately, since you can easily reproduce the bug, you should be able to quickly set a breakpoint and see exactly what's going on with the lighting at that moment. I'd encourage you to try that, because you might find that it's a simple off-by-one sort of issue. Or it could be an edge case in your algorithm that you didn't realize.

Without seeing your code, I can't really help much, but I think your intuition about updating left->right/top->down is on the right track. Fortunately, since you can easily reproduce the bug, you should be able to quickly set a breakpoint and see exactly what's going on with the lighting at that moment. I'd encourage you to try that, because you might find that it's a simple off-by-one sort of issue. Or it could be an edge case in your algorithm that you didn't realize.

Unfortunately I don't have the code with me atm, or I would have posted it :) I just couldn't get my mind off the problem so I figured It ask here. I'll look into it tomorrow when I get home, and post back here. "Off-by-one" triggered something.. I think that might be the problem :D

Check out the game I am making here - http://www.youtube.com/user/NasarethMekuri

I've spend a few hours now going through this, but I really can't seem to hit the nail.

Before you look through the code below there's a few things I'd like to explain.
First off, each tiles position is -not- the center of the tile. but the top left point. Thus some of the for loops will have a + 1 in the positive direction, to offset the difference when determining which area to apply light to.
What I have tried so far:
- substracting 1 on the left side of the apply radius loop.
- Tried removing the plus 1 on the "right" side, to try and duplicate the issue in the positive direction- This will not produce the same results.

I even stumpled upon a few minor bugs, but they were unrelated to this issue.
I have considered updating the light twice, where the 2nd run through would be from right/bottom to left/top´, but this seems like overkill to me?

EDIT: So I tried updating the light twice now, and it partially solved the issue. In the situation shown in the video it works, but there are other scenarios where it won't solve it.
After spending time in deep thought, I think the way I do it won't work, since the light will always be updated first from either direction, and as such give incorrect light in some instances.
On top of this, doing it twice really takes it toll. I'm getting framerate drops down to around 40-45fps after placing just a few torches and digging a few tunnels.

Anyways, here's my entire Light class- Notice the flicker class (unrelated to this problem anyways) is called elsewhere and only when a torch is being drawn to the screen.


public static class Light
{
public static short AmbientLight = 210;
public const short MIN_AMBIENT_LIGHT = 20;
public const short MAX_AMBIENT_LIGHT = 230;
private static Random _random = new Random();
/// <summary>
/// Should be called everytime something related to light is changed.
/// </summary>
/// <param name="world"></param>
/// <param name="pc"></param>
/// <param name="lootList"></param>
public static void UpdateLight(Tile[,] world, Player pc)
{
ResetLightValues(world, pc);
//Only updates a certain amount of tiles around the player, just enough so that light works proberly even with the light source offscreen.
for (int x = (int)(pc.Position.X / Tile.Size.X) - 50; x < (int)(pc.Position.X / Tile.Size.X) + 50; x++)
for (int y = (int)(pc.Position.Y / Tile.Size.Y) - 45; y < (int)(pc.Position.Y / Tile.Size.Y) + 45; y++)
{
//Make sure we don't exceed the boundaries of the world array.
if (x + 1 > world.GetLength(0) - 1 || x - 1< 0 || y + 1> world.GetLength(1) - 1 || y - 1< 0)
continue;
//Only the empty tiles close to non-empty tiles, will apply their light level and radius. They will also if they're next to, or behind the player.
if ((int)world[x - 1, y].Type + (int)world[x - 1, y - 1].Type + (int)world[x, y - 1].Type + (int)world[x + 1, y - 1].Type + (int)world[x + 1, y].Type
+ (int)world[x + 1, y + 1].Type + (int)world[x, y + 1].Type + (int)world[x - 1, y + 1].Type != 0)
//|| Math.Abs((pc.Position.X / Tile.Size.X) - x) <= 1 && Math.Abs((pc.Position.Y / Tile.Size.Y) - y) <= 1)
{
if (world[x, y].Type == TileType.None && world[x, y].BackgroundType == TileType.None)
world[x, y].LightLevel = (short)AmbientLight;
if (world[x, y].LightRadius > 0)
ApplyLightRadius(world, x, y, pc);
}
}
}
/// <summary>
/// Resets every tiles LightLevel, within the draw radius, to zero. - Used by UpdateLight before calculating light levels.
/// </summary>
/// <param name="world"></param>
/// <param name="pc.Position"></param>
private static void ResetLightValues(Tile[,] world, Player pc)
{
for (int x = (int)(pc.Position.X / Tile.Size.X) - 50; x < (int)(pc.Position.X / Tile.Size.X) + 50; x++)
for (int y = (int)(pc.Position.Y / Tile.Size.Y) - 45; y < (int)(pc.Position.Y / Tile.Size.Y) + 45; y++)
{
if (x > world.GetLength(0) - 1 || x < 0 || y > world.GetLength(1) - 1 || y < 0)
continue;
if (world[x, y].Type == TileType.Torch)
continue;
world[x, y].LightLevel = 0;
}
pc.LightLevel = 0;
}
/// <summary>
/// Applies light radius from any tile or player that has a lightradius higher than 0.
/// </summary>
/// <param name="world">The tile array to update light in.</param>
/// <param name="tileX">The X-coordinate of the Source of Light.</param>
/// <param name="tileY">The Y-coordinate of the Source of Light.</param>
/// <param name="pc">The current player.</param>
private static void ApplyLightRadius(Tile[,] world, int tileX, int tileY, Player pc)
{
int radius = world[tileX, tileY].LightRadius;
for (int x = tileX - radius; x < tileX + radius + 1; x++)
{
for (int y = tileY - radius; y < tileY + radius + 1; y++)
{
//Make sure we don't exceed the boundaries of the world array.
if (x < world.GetLength(0) - 1 && x > 0 && y < world.GetLength(1) - 1 && y > 0)
{
if (x == tileX && y == tileY || (world[x, y].Type == TileType.None && world[x, y].BackgroundType == TileType.None) || world[x,y].Type == TileType.Torch)
continue;
//Checks that we're still within the radius of the original tile.
if ((new Vector2(tileX, tileY) - new Vector2(x, y)).Length() <= radius)
{
short tmpLightLevel;

//If the tile isn't solid.
if ((int)world[x, y].Type <= 0)
tmpLightLevel = (short)(world[tileX, tileY].LightLevel - ((new Vector2(tileX, tileY) - new Vector2(x, y)).Length() * 10));
else //else if the tile is solid.
tmpLightLevel = (short)(world[tileX, tileY].LightLevel - ((new Vector2(tileX, tileY) - new Vector2(x, y)).Length() * 40));
if (world[x, y].LightLevel < tmpLightLevel)
world[x, y].LightLevel = tmpLightLevel;
if (world[x, y].LightLevel < 0)
world[x, y].LightLevel = 0;
}
}
}
}
//Apply light to the player if he is within the light radius.
if ((new Vector2(tileX, tileY) - (pc.Position / Tile.Size.X)).Length() < radius)
if (pc.LightLevel < world[tileX, tileY].LightLevel / (int)((new Vector2(tileX, tileY) - (pc.Position / Tile.Size.X)).Length() + 1))
pc.LightLevel = (ushort)(world[tileX, tileY].LightLevel / (int)((new Vector2(tileX, tileY) - (pc.Position / Tile.Size.X)).Length() + 1));
}
/// <summary>
/// Should be called everytime a lightsource that should flicker is drawn.
/// </summary>
/// <param name="world"></param>
/// <param name="tileX"></param>
/// <param name="tileY"></param>
public static void ApplyLightFlicker(Tile[,] world, int tileX, int tileY)
{
world[tileX, tileY].FlickerTimer--;
if (world[tileX, tileY].FlickerTimer <= 0)
{
world[tileX, tileY].LightLevel = (short)_random.Next(330, 370);
world[tileX, tileY].FlickerTimer = (ushort)(_random.Next(5, 25));
}
}
}


I hope one of you out there can point me in the right direction.

Thanks a lot for reading smile.png

Check out the game I am making here - http://www.youtube.com/user/NasarethMekuri

I finally managed to track down the cause of this problem.
The problem is, that I start out with resetting all light values to zero at the start of each cycle, and then apply the light values where necesary followed by applying the light radius.
For example- at the coordinates 50,50 there are some ambient light that applies it light radius. Later at 100,100 there's a torch applying its light radius. This radius causes a tile, among others, at 60,60 to be brighter. Now since I've already has applied the light radius for the tile at 60,60 the "chain of light" is now broken.
So now I've at least located the problem. The only solution that comes to mind right now, is to Update the lights twice pr cycle.. Having already tested that, I don't think that's a viable option, since I get heavy fps drops.
I'll go study this issue some more, and if any of you out there has anything to chip in with, please don't hesitate :)

Thanks.

Check out the game I am making here - http://www.youtube.com/user/NasarethMekuri

Can't you as soon as you place a torch or a new light source in the scene mark the tiles that are in it's radius so the update on the next frame with the correct light values. Even if you do this in the lighting pass and then update the values in the next frame you only have a 2 frame delay. No-one is seeing a 2 frame delay between placing a torch and seeing it's effect at 30FPS.

Worked on titles: CMR:DiRT2, DiRT 3, DiRT: Showdown, GRID 2, theHunter, theHunter: Primal, Mad Max, Watch Dogs: Legion


Can't you as soon as you place a torch or a new light source in the scene mark the tiles that are in it's radius so the update on the next frame with the correct light values. Even if you do this in the lighting pass and then update the values in the next frame you only have a 2 frame delay. No-one is seeing a 2 frame delay between placing a torch and seeing it's effect at 30FPS.


Thank you for you input.

I did manage to get some additional help through the XNA Forums.

Basically what I've changes is seperate Ambient light from Direct Light (Light from torches). This way I only need to updated everything related to ambient light everytime ambient light is changed, instead of on a pr frame basis. Furthermore ambient light can now be updated in small portions, if a tile is added or destroyed.
Light from torches are still updated pr frame, but performance has been increased.

Thanks for the help, I think I am able to continue on my own from here :-)

Check out the game I am making here - http://www.youtube.com/user/NasarethMekuri

This topic is closed to new replies.

Advertisement