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