Dynamic Lighting, huge performance issuses


(KleinerNull) #1

Hi, I try to implent a rogue like dynamic light system to my game. At first I a raycasting method to get a Vector of Points that will form a shape to lighten up the scenery. With this method I want to create an image that forms my light source:

public function calculateSightfield(rays:Vector.<Point>):FXImage
	{
		FP.sprite.graphics.clear();
		FP.sprite.graphics.beginFill(0x333333, 1);
		
		var p:Point = new Point();
		for each (p in rays)
		{
			if (p == Player.pos)
			{
				FP.sprite.graphics.moveTo(p.x, p.y);
			}
			else {
				FP.sprite.graphics.lineTo(p.x, p.y);
			}
		}
		FP.sprite.graphics.endFill();
		var data:BitmapData = new BitmapData(dist*4, dist*4, true, 0xffffff);
		data.draw(FP.sprite);

		
		var img:FXImage = new FXImage(data);
		img.color = 0x000000;
		img.alpha = 1;
	
		
		return img;

	}

This works great in my 512x512 test level. But if I have a bigger level, f. e. 4096x2048 the light will only show up in area 512x512. So I fit the BitmapData dimensions to the map dimensions, now the light shows up, but the performance is very bad. I think the problem is the Sprite I create, it is as big as my map, filled with transparent pixels and a few that represents my light. How can I fix this?

PS: Sorry for my bad english!


Help with Dynalight!
(Jacob Albano) #2

Your English is quite good; no need to apologize. :smile:

I would suggest only having the Sprite be the size of the screen, and draw the rays at an offset based on the camera’s position. Copying a huge bitmap is quite wasteful if you only see part of it at a time.


(KleinerNull) #3

Thanks for the advise. Maybe something like this:

...var p:Point = new Point();
	for each (p in rays)
	{
		if (p == Player.pos)
		{
			FP.sprite.graphics.moveTo(p.x%FP.screen.width, p.y%FP.screen.height);
		}
		else {
			FP.sprite.graphics.lineTo(p.x%FP.screen.width, p.y%FP.screen.height);
		}
	}
	FP.sprite.graphics.endFill();
	var data:BitmapData = new BitmapData(FP.screen.width, FP.screen.height, true, 0xffffff);...

And then I set the lightsource only to light.x = world.camera.x ...etc. I can’t test it yet, I am sitting at work :wink:


(KleinerNull) #4

Yes! I manage to fix the problem. Now only an image with the dimensions of the screen will draw, its follows my camera. At first it doesn’t change the FPS, then I switcht from debug mode to release mode and, look, now I have about 50 FPS. THEN I had another weirder problem: My mouse collision didn’t work, and and additional light source only show up in the upper corner and not in the whole map. But if I turn off the light everthing went normal. I used this code:

for each (p in rays)
		{
			if (p == Player.pos)
			{
				
				var len:Number = FP.distance(world.camera.x, world.camera.y, p.x, p.y);
				var ang:Number = FP.angle(world.camera.x, world.camera.y, p.x, p.y);
				FP.angleXY(p, ang, len);
				
				
				
				FP.sprite.graphics.moveTo(p.x, p.y);
			}
			else {
				
				var len2:Number = FP.distance(world.camera.x, world.camera.y, p.x, p.y);
				var ang2:Number = FP.angle(world.camera.x, world.camera.y, p.x, p.y);
				FP.angleXY(p, ang2, len2);
				
				
				FP.sprite.graphics.lineTo(p.x, p.y);
			}
		}

Man, I searched and searched for the mistake, then I saw that my Player position was clamped to the rectangle of the upper corner, why? I looked and now I saw the problem, the FP.angleXY(...) acts strange and change not only the point p, no it also change the point Player.pos. Why? I fixed it with create a empty point that used for the changing:

for each (p in rays)
		{
			if (p == Player.pos)
			{
				var p1:Point = new Point();
				var len:Number = FP.distance(world.camera.x, world.camera.y, p.x, p.y);
				var ang:Number = FP.angle(world.camera.x, world.camera.y, p.x, p.y);
				FP.angleXY(p1, ang, len);
				
				
				
				FP.sprite.graphics.moveTo(p1.x, p1.y);
			}
			else {
				var p2:Point = new Point();
				var len2:Number = FP.distance(world.camera.x, world.camera.y, p.x, p.y);
				var ang2:Number = FP.angle(world.camera.x, world.camera.y, p.x, p.y);
				FP.angleXY(p2, ang2, len2);
				
				
				FP.sprite.graphics.lineTo(p2.x, p2.y);
			}
		}

Is the FP.anglyXY-function broken, or I misread some facts?


(azrafe7) #5

I don’t know how you populate your array of points, but I think that what’s happening is linked to what you think this line does:

Underneath it does not compare the x and y values of the respective Points, but checks if they are a reference to the same object.

So, when the condition is satisfied, you’re actually working with the Player.pos point and not a copy of it.


(Jacob Albano) #6

This is correct. To compare point values, use the equals() function.


(KleinerNull) #7

Thanks so much, I really didn’t know that I can’t use Points like Numbers or other Types! I thought that == check if the properties are the same and === checks wheater it is linked to the same onject. That the Point-type acts different is new to me!


(Jacob Albano) #8

The strict equality operator is a bit strange. It checks not only the value of an object but also its type. For example:

1 == "1";    // true, "1" is coerced to Number and compared
1 === "1";    // false, "1" is a String and 1 is a Number

For reference values, both operators check to see if both references point to the same object.


(KleinerNull) #9

Thanks guys, I fixed alot to make the lighting faster and faster. Now the image of the lightsource is exactly as big as it have to and not more as large as the screen. No I test a little and I figured out that draw the Bitmapdata cost at most performance. Guys have you some ideas to make this even faster? Because now I am at avg. 45-50 FPS, ok I will clamp the game at 30 FPS, but I haven’t set game logic or simple AI into the game, I am a little afraid that this will cost also alot FPS…


(azrafe7) #10

Do you mind posting the full source code (a streamlined project containing just your lighting code would do as well), so that we can take a look at it and see if we can make it faster?


(KleinerNull) #11

Oh yes of course. This is the code to calculate the raypoints that later forms the shape of the light:

public function rays(flashAng:Number, flashStep:Number):Vector.<Point>
	{
		var angle:Number = FP.angle(Player.pos.x, Player.pos.y, x, y) - flashAng * 0.5;
		
		var rays:Vector.<Point> = new Vector.<Point>;
		
		
		rays.push(Player.pos);
		for (var i:int = 0; i <= flashStep; i++)
		{
			angle += flashAng/flashStep;
			var rayPoint:Point = new Point();
			var tmpPoint:Point = new Point();
			FP.angleXY(tmpPoint, angle, dist, Player.pos.x, Player.pos.y);
			if (FP.world.collideLine("level", Player.pos.x, Player.pos.y, tmpPoint.x, tmpPoint.y, 1, rayPoint))
			{
				
				FP.angleXY(rayPoint, angle, 24, rayPoint.x, rayPoint.y);
				rays.push(rayPoint);
			}
			else {
				
				rays.push(tmpPoint);
			}
			
			
			
		}
		
		
		return rays;
		
		
		
	}

And this is the code to draw the polygon:

public function calcSight(rays:Vector.<Point>):FXImage
	{
		FP.sprite.graphics.clear();
		FP.sprite.graphics.beginFill(0x333333, 1);
		
		var p:Point = new Point();
		for each (p in rays)
		{
			if (p == Player.pos)
			{
				var p1:Point = new Point();
				var len:Number = FP.distance(Player.pos.x - dist, Player.pos.y - dist, p.x, p.y);
				var ang:Number = FP.angle(Player.pos.x - dist, Player.pos.y - dist, p.x, p.y);
				FP.angleXY(p1, ang, len);
				
				
				
				FP.sprite.graphics.moveTo(p1.x, p1.y);
			}
			else {
				var p2:Point = new Point();
				var len2:Number = FP.distance(Player.pos.x - dist, Player.pos.y - dist, p.x, p.y);
				var ang2:Number = FP.angle(Player.pos.x - dist, Player.pos.y - dist, p.x, p.y);
				FP.angleXY(p2, ang2, len2);
				
				
				FP.sprite.graphics.lineTo(p2.x, p2.y);
			}
		}
		FP.sprite.graphics.endFill();
		var data:BitmapData = new BitmapData(dist * 2, dist * 2, true, 0xffffff);
		data.draw();
		
		
		
		
		var img:FXImage = new FXImage(data);
		
		img.color = 0x000000;
		img.alpha = 1;
		
		
		return img;
		
		
		
		
		
		
		
	}

The I use the lit-libary that post in the forum, it basicly performs an alpha blending over the seen screen. Maybe you can find some fixes! And really thanks that somebody is interested to help my beginner problems!


(azrafe7) #12

The main thing to note is that you’re instantiating new objects at every render/update cycle, and that will surely slow things down, along with increasing memory usage a lot.

I see the code you posted is incomplete (what args you’re passing to rays(), data.draw() missing a parameter, dist not declared, etc.).

Also…

  • How many lights are you computing this way (only the one for the player)?
  • Is the level type set on singular Entities or are you using a Tilemap/Grid?

But above all I think that showing an swf of what you have so far could be of great help in understanding what you’re trying to accomplish.


(KleinerNull) #13

Yes I calculate and render the light every frame. It shall simulate a flashlight. The var flashAng describes the angle range of the light, default is 90°, but it can decrease or increase from 18° - 150°. The var flashSteps only defines the number of rays to calculate, default is at the time is 9. The dist is camps between 100 and 500.

This is the only light I calculate this way, the other light sources will be static. I also use a tilemap and all tiles that are solid have this type also other entities like doors etc. the light can’t get through. Oh and your right data.draw() is false, it should be data.draw(FP.sprite)M

Here is the swf of the this project: https://www.dropbox.com/s/3amr6s5ysmbltne/test.swf WASD for movement, LMB to shoot, mouse + E to open doors, +/- to shange the dimension of the torch and F12 for debug view, not much there at the time.


(azrafe7) #14

Ooohh… playing with your code… so it’s something like this (not optimized and without making use of the Lit library yet).

Still curious to see what you have so far. :wink:

EDIT: missed your post published one minute before this!


(azrafe7) #15

Still playing with the code…

I have this so far - along with lots of ideas/suggestions on it. Maybe not much faster than the previous implementation, but I laid out a couple of classes to generalize things a bit.

I’ll probably write a post with my thoughts and worries in the next couple of days.


(KleinerNull) #16

WOW!!! I testet your code with max parameters, max length, ray and span and I come down to only 45 FPS, but that is awesome! What weird magic do you use to calculate this so fast? :wink: I am very happy to read your future suggestions to improve my code. Thank you very very much!


(azrafe7) #17

New demo.

I’ll clean up the code and go through all the changes and additions I’ve made to it since the first implementation in a following post.

If you’re feeling in the mood to dig through the messy code I have now, you can download it here.


(azrafe7) #18

– Grand father, tell me a story.

– All right, go and get your storybook.

– No, no, not one of those, a real story!

– A real story?

– Yes! Tell me about when you were a boy.

– Well, then, I shall have to take you back with me, a long way in time…


… I grabbed the code @KleinerNull (OP) posted up here and set up a TestWorld to see what the output would look like, sooo…

Hope you like bullets?!?

  • figured out a rough idea of what his code was doing
  • putting up a simple LevelEntity (type “level”) - with a Tilemap inside and an associated Grid mask - seemed a straightforward decision for testing it
  • run the whole thing through rays() and calcSight() functions and then drew the resulting polygon Sprite on screen
  • adjusted it so it showed up on top of the player
  • nice! the shape displayed represented the expected FieldOfView from the player position (and OP had already managed to make the resulting Image be of contained size)
  • started refactoring things a bit to try and speed it up:
    • promoted local variables to class members to minimize new instantiations
    • removed unnecessary Points
    • removed the creation of a new Image in every cycle
  • now I still hadn’t clear how to use this as a lighting system, but re-reading his post I notice he mentions the “lit-library”, and that leads me to rediscover this great piece of code (yeah, I forgot about that @SHiLLySiT: nicely done! :wink: )
  • fine… fiddling with it just some minutes (handling colors, alpha, scale, etc.) got me the full view with lights and shadows
  • at this point I decide to move the raycasting bits to their own class (with static methods at first, but those get later changed to non-static ones)
  • FPS where not so bad at this point, but what if I throw some more non-Grid entities (but with the same “level” type) into the game?
  • well, that didn’t play well. As long as the count remained low… no problem. But for moderately large numbers of them scattered along the stage the framerate was in pain
  • obviously the engine was going through all the entities of that type to see if collideLine() would hit any of them
  • further checking the collideLine() method I see there’s a precision parameter in there that defaults to 1, meaning that the raycast checks one pixel at a time. Fiddling with that and tweaking the value in relation to the size of your tiles greatly improves performance
  • in the meantime I reworked a bit the calcSight() function so that it returns a BitmapData and uses a Shape instead of a Sprite (the latter being more resource consuming)
  • at this time I also push up the BitmapData (along with the Shape) as class variable and repeatedly use this for new calculations, recreating it only if the size of the light is bigger (or smaller)
  • an FXImage is added so I can use its setSource() method to assign the calculated BitmapData to it
  • getting back to non-Grid entities: I realized I’d like to also check for entities with a type different from “level”. Like “enemy”, “door”, you name it!
  • this thought led me to readapt World.collidePoint() and World.collideLine() (which - in all sincerity - I only slighlty modified) to take a different first parameter (the type of entities you want to check for), to support a list of types or pass in an already populated list of entities
  • still with me?.. then I will tell you the story of how I met your mother… naah, just kidding… By adding a simple utility function (getEntitiesPool() which needs to be carefully looked into for mem leaks, hehe :grin: ) I was now able to cast(["level", "obstacle", "enemy"], ...)
  • well what about the look of the light? It showed at full power as of now, no shades as I had seen in the Lit library examples. Time to change the calcSight() function to use a gradient fill. Also wrote a RayCasterSettings class to hold all the options - quite a bunch already - used by the RayCaster class (see this article to find out what the createGradientBox() parameters are for)
  • sure, those couple (triple?) of collide...() methods proved to be helpful, but didn’t address performance
  • before diving into writing a class to easily retrieve entities near a specific position I gave Google a chance and… and Grant Skinner!
  • here’s his ProximityManager class that fits the task perfectly (supports DisplayObjects but… a bit of adjustments and it’s FlashPunk-ready), just mind to properly set the grid size and the bounds rectangle
  • now the “obstacle” entities worked right, but the grid did not, why? because the grid entity has a fixed position of (0, 0) although it spans through the whole world, so is never in proximity of, say, (400, 250) although a tile is probably there
  • to cope with this here’s what I do:
    • create an empty pool of entities
    • prefill it with the grid entity
    • add the single entities (“obstacle”) to the proximity manager
    • retrieve the “obstacles” near the player from the proximity manager and add them to the pool of entities
    • pass the pool as first parameter of RayCaster.cast()
  • done. As a final touch added moving objects and a blurFX to the FXImage. Because!

Thoughts: To obtain better performance one can use scaled down assets and tell FlashPunk to scale them back up. The light BitmapData could also be reduced in size when the light is not a full circle (but that’ll require some more calculations to center it properly). Reworking the ProximityManager (also trying to auto-handle the bounds parameter) to fit natively in FlashPunk would be a nice experiment to do.


**tl;dr**: had some fun playing with OP's code, [Lit][4], [ProximityManager][5] and [punk.fx][6] [commented code][7] | [flash demo][8]

(Abel Toy) #19

I also have a similar creation here:

http://abeltoy.com/previews/horrorarena/020912-2020.swf

I did this a while ago. Collisions not working.


(Jacob Albano) #20

That’s impossibly smooth. I love the way the aiming feels too.