Tuesday, March 22, 2011

Forum rescue #001: Sun

Welcome to the first 'Forum rescue' post! Here we will pick up a problem/request posed on the official Construct forum and try to provide a solution. With detailed explanations and the cap file to go along with it.

Here's what Dewaldt wrote (original thread):

So let's try our luck with a glowy sun that emits a bunch of light rays. For that we just need two sprites: a white circle with a soft edge and a line that's faded from white to transparency (left to right). Simple stuff anyone can make in his favorite graphics application. Let's import these into Construct and go from there.

It is important that your sun sprite has a centered hotspot and your beam sprite's hotspot is set to the left middle edge (press 5 on the numblock). Make sure that's the case, then add another layer below the current one and insert a gradient object. Resize it to fill the whole layout. It's just less boring than having a solid color in the background.

> Download current state cap file <

Time to move on to the event sheet. Add a 'Start of layout'-event that places the sun sprite (your circle) in the middle of the screen.

In case you're not familiar with the expressions ScrollX and ScrollY: they describe the centered X and Y position of the currently visible area, the position of the game camera so to speak.

Now add a second event. This time chose 'Every X Milliseconds'-condition. Try a value of 200. For an action select 'Spawn another object' of your sun sprite and chose the beam sprite. Add another action right away. Change the angle of the beam sprite to Random(360).

So when you run the project, you'll notice that beams will be spawned randomly around your sun. Of course they're just statically sitting there and adding up. An easy way to bring some motion into play, is to give the beam sprite the Rotation behavior. Do so with a suggested speed of 16 in anti-clockwise direction.

Let's also give the beams a distort map when they're being spawned. Just add the action 'Set distort map size' to the second event. Leave the initial settings. But why would we even need to use a distort map? Let me briefly demonstrate a little something:

What you see above is an animation showing the gradient beam sprite. First image without any distort map, the second image visualizes a 1,1 resolution distort map, and in the third picture we see how the sprite will look once we displace points of the distort map. With this method we can change the shape of the beam easily.

That's exactly what we would want to do. Add actions to set the relative Y displacement of the coordinates 1,0 and 1,1 (as seen above) to -80 and 80 respectively.

Run the game to see how this changed the beam's shape. That's not bad, but it is still all static. So let's try to narrow down the angle of the shape continously until we are back to just a line. For this we add an always event and substract permanently from the displacement at the two coordinates.

What you would actually write for the Y displacement looks a bit like this:

Since you're permanently changing the displacing, you need to apply TimeDelta. The expression Beam.VertexY(Column,Row) returns the already existing Y displacement at the coordinate. Do the same for the coordinate 1,1, remember to substract 16*TimeDelta instead of adding it.

Now you can see how the beams will narrow down, but also they'll get bigger again when they cross over 0. This can be prevented by restricting your displacement to a min/max value of 0. The clamp expression returns a value in between set bounds. That's very handy in many situations.

Instead of spawning beams with no end, you should limit the number of objects of course. Let's try a global variable that stands for the maximal number of beam sprites we want to have. So make a global and set it to 32 for testing. Now we need to add a condition to our spawning event, that compares the number of beam sprite objects to the global variable.

It would make some sense to destroy the oldest object, when the object count reaches the limit defined by the global. So there would be a constant spawn and destroy cycle. Add an event with a Compare condition. Check if the number of beam objects is equal to the global variable.

But how the heck can one pick the oldest beam object? Every sprite is given an unique ID (UID), so the earliest spawned object will have the lowest UID. To make picking the beam sprite with the lowest UID easier, we give it a private variable which we set to the object's UID. Add such an action to the spawn event.

Now you can make use of the 'Pick object with lowest variable' condition in the event you created one step earlier.

With this setup, the oldest beam sprite will be destroyed when the amount of sprites reached the limit. And a new sprite will be spawned again the next tick. Circle of life.

> Download current state cap file <

When you review how the sun looks right now, you'll notice that it's not bad at all already, but the spectrum of lightrays has ugly gaps randomnly appearing.

What could be the solution for this? Spawning even more beam sprites? No, of course it would be better to use a method to distribute the beams more evenly around the sun circle instead of simply spawning them with absolutely random angles.

As a first humble measure, remove the action which sets the beam's angle randomly on start. Add a new global, call it 'NewAngle' or something appropriate, make a new action in the spawn event which sets the global to Random(8)*45 instead. With this we get either 0, 45, 90, 135, 180, 225, 270 or 315 degrees. Now how does this help?

Not so fast. First we bring a new player into the game: insert a Hash table object. With this object we can basically add and delete variables at runtime, notice that they're called keys in this case. The plan is to add every angle that a beam is spawned on to the hash table and only allow angles that don't already exist, so we achieve a more uniform distribution around the sun object.

If you're thinking 'Loop' now, you're right. Insert a subevent to the spawning event and add a While loop condition. Hash table objects have a very handy condition, that lets you check if a key exists. We use that, and check if a key name exists which matches the value that the global 'NewAngle' currently has.

What this is supposed to do is changing the angle to be, as long as it's not different from an angle that already exists as stored key in the hash table.

A key name is always a string and since the value of the global is obviously a number, we need to add the string expression in this case. Also drag the action down into this subevent which you made one step earlier.

As you know there are eight different possible results for the value of 'NewAngle', so you only want to insert it as a key into the hash table if the number of keys is below 8. So add a new event in the same level of hierarchy with a compare condition accordingly and add an insert key action that uses the value of 'NewAngle' as the name of the key.
 If there are all eight possibilities already stored in the hash table, the plan is to clear it, so the whole process can start over again. The event for this would simply look the following:

And yes, it would be a good idea to insert the key again in this case, so the last angle carries over into the new cycle. What's left to do is applying the angle to the spawned beam sprite. Add an always event that is still a subevent to the spawn event and have its action setting the angle of the beam to the value of 'NewAngle'.

> Download current state cap file <

Time to test your glorious creation to see if all the trouble was worth it.
Much better, isn't it? Still there's a simple yet effective way to improve this sun effect quite significantly. Add to your general always event an action that constantly increases the width of the beam sprite.

> Download current state cap file <

Now that's a sun I could see myself getting skin cancer from:
There is still so much you can do with this. Toy around with the different values used, add the additive effect to the beam sprite and play with color filters. Sunny times!
That's it for the first forum rescue post. I hope you liked reading and maybe took one or two hints from the things mentioned. Do go ahead and comment if you're having trouble with anything or a passage seems to be incomprehensive or you just feel like giving your two cents. Until next time!

> Download final cap file <

1 comment:

  1. Very comprehensive I must say but well I wouldn't expect nothing else from you