Pages

Tuesday, May 24, 2011

Forum rescue #004: Bitmap font damage

This will be a pretty lengthy one, so prepare yourself.













Okay, from just reading what Shiru wrote (original thread)you can't really figure out the problem. He linked to a gameplay video of Casltevania, where enemies are being hit, displaying the damage they took above their heads.


How would one do that in Construct, using a custom bitmap font of course and not just plain text? The answer is relatively simple, although there are a few things to consider. But let's start at the beginning, shall we.

The first step is as simple as adding a sprite to your blank game project and importing your custom bitmap font, which in this case contains the numbers 0-9, as frames. This should look a bit like the following picture:
The order is important of course. The frame number minus one is the actual value our bitmap represents. This simple fact is basically the most important thing to get this to work. It's also important to set the speed of the animation to 0, otherwise we will have rapidly changing numbers in the end.

> Download current state cap file <

To be able to get the positioning of the displayed damage completely right later on, it is vital to know the width of every single one of our number frames. For this purpose add an array object. You can leave it at the default size, which fits us perfectly. A one dimensional array with the size of 10 obviously can hold 10 variables, matching our 10 different frames/numbers.

And on to the event sheet we go. Time to use a loop right on startup to store the width of all ten of our frames in the array. A 'For'-loop, running from 1 to 10 should do a great job at this. Inside the loop we simply change the frame of the number sprite to the loopindex, therefore iterating through all the frames. At the same time we store the current width in the array, giving the loopindex as array index this time. Voila!







Also add an event which destroys the 'Numbers'-sprite, because it's useless to us after we grabbed the widths.

> Download current state cap file <

This example is supposed to simulate damaging an enemy, so creating a sprite to represent the enemy seems like a good idea (I name it 'BadGuy'). Also drop an 'Edit Box' and a 'Button'-object in there. We will use them to enter the desired damage in the edit box and deal it by clicking the button. And last but not least: do add the ever so useful 'Function'-object as well!

Create an event with a 'On Button clicked'-condition. Now maybe we look at the function object and add its action 'Add parameter'. This action can carry over any value/string to a function, which then can be accessed within that function with the expression 'Function.param(number)'. Think of it as a variable which exists only within the function being called. Of course we do add the content of the edit box as a parameter, since it does represent the amount of damage to deal. Next we do call the actual function, here called 'DMG'.




For the function event we are about to create, we do still need a couple of variables. Create three globals, called 'temp', 'width' and 'spacing'. 'spacing' is supposed to control the spacing of our numbers, the space between individual characters in pixels. I like this setting to be at about 2 pixels for this particular font. The other variables are used each time by the function itself. We will see how exactly soon.

> Download current state cap file <

Time to create the function 'DMG'. Use the 'On function'-condition and insert a subevent with another 'For'-loop right below. With this we want to find out the length of the current damage number which is going to be displayed. Therefore we need to take apart the string we got from the edit box and add the value at the respective array index for each individual character.

I think I do need to get a bit deeper into explaining this: imagine you entered a damage of 135 into the edit box. Clicked on the button and that value of 135 was passed to the function as the first parameter. Next there's supposed to be a loop, picking the value apart into 1, 3 and 5. Then we look for the value at the respective array index, which is the width of the character. Add all that up and we have the actual width of the damage that will be displayed.

Let's create this loop called 'width'. Start it at 1 and end it at the length of the current damage string(number of characters), using the text expression 'len'. As an action do add to the global 'width'. The value to add we find at the respective array index. To pick the correct array index, another text expression is needed: 'mid'. With this expression one can get any number of characters within a string starting at a defined index. Take a look:









Looking off-putting much?!? Don't worry, I'm gonna try to elaborate further. The action of the loop is a common 'Add to value'-action of the 'System'-object. To get the value of the correct array index, the expression 'mid' is being used. This is what the Construct wiki has to say about this expression:

Mid(string, index, N) 
Returns the N characters after index in string.

Every loop iteration, we want to check one individual character of that string and find its counterpart in the array. There we have the last parameter of the expression (N) simply set to 1. The string which we are checking, is the parameter passed from the edit box to the function: string = Function.Param(1). And as index we simply use the loopindex, that's why we are doing this with a loop in the first place, to iterate through all the characters.

You can see that there is another expression being used called 'int'. This turns a string or float into an integer value. Most of the time you don't need to do this manually, but because we do need to add 1 to the result (for the array is 1-based), 'int' is needed because Construct doesn't let you use math with the result of a text expression.


As you can see in the picture there's also another action above which I didn't even mention yet. Well, as you know we do intend to find the width of the shown damage. For that we also need to add the spacing that will occur between the characters. This is done with the first action, which multiplies the spacing width with the character length minus one. 

> Download current state cap file <

I can't spare you even another loop. This is the one that actually creates the number sprites and therefore displays the damage. The condition of the previous loop can just be copied, rename the name of to loop to 'display'. As first action we do create the number sprite, using the globals 'width', 'temp' and some object expressions. Here's what to set the X/Y positions to:

X:
BadGuy.Left+BadGuy.Width/2-Global('width')/2+Global('temp')
Y:
BadGuy.Top

For the Y-position it's pretty straightforward. The 'Top'-expression returns the top Y position of a sprite. So the damage will always be displayed right above the enemy.

Things get a bit more complicated with the X-position. We do want the damage to be displayed centered above the enemy. Regardless of the hotspot of a sprite, logic has it you can find its center by adding half its width to the left edge. That's why 'Badguy.Left+BadGuyWidth/2'.

A good thing we already found the width the number sprites are going to have and stored it in the global 'width'. We substract half the width, which will return the left starting position at which the first character will be created. To that the global ('temp') is added, which offsets the position as more sprites are being spawned.

Next we do set the correct frame for the number sprite, using an almost identical method as before to find the fitting array index. Also again with '+1', animations are 1-based as well, meaning the first frame of every animation has the index of 1 and not 0.




And a third action which adds the width of the currently created number sprite plus the spacing to the global 'temp'. This variable will grow while the iteration is going on, to make sure the characters aren't created overlapping each other.

I do realize this gets cumbersome to look at, but here it is:

















Notice that there's also an action setting global('temp') to 0 at the top. This is needed to get a fresh offsetting of the X position the number sprites are being spawned at each time the function is called. It's basically just resetting the global variable to be used anew.

Time to see if this works at all. Run the project and deal some damage!

> Download current state cap file <

What should be apparent is that while you keep dealing damage, the number sprites stack up which is pretty nasty. Usually in games the damages move upwards and are being faded out. That's what is needed here to properly finish up this example.

For the movement and the fading we do simply use two actions in an 'Always'-event. One is changing the Y-position, the other lowering constantly the opacity of the sprite.




Lastly get rid of the sprite when it isn't visible anymore.




This means you're done. Run it again and marvel at the sight of damage numbers traveling north and fading into oblivion.

> Download final cap file <

Oh my god, I bet this was as much a pain in the ass to read as it was to me writing this one up. This time I had a lot of explaining to do for just a few events, and I'm not confident at all I did so in an acceptable manner. Let me know what you think, for me it's time to fire up Steam now for some gaming. Take care!

2 comments: