1
\$\begingroup\$

I am currently working on a game in Game Maker Studio 2 where the player character is capable of increasing their maximum health beyond a starting value of 30, by increments of 10. I want to represent this graphically by adding a different-colored overlay to the character's health bar for each additional 10 points added, with the last layer added diminishing before the ones behind it, proportional to how much of the last "extra" 10 points remains.

If you are familiar with the Mega Man Zero games, and how some bosses in that game had extra health represented in "layered" health bars that decremented independently of and sequentially to each other, I am basically trying to make that, just using a different health increment for all layers beyond the first.

So far I can get the first layer to work out just fine:

draw_sprite_ext(sprite931, -1,0,0,(min(30, hp_current)/30),1,0,c_white,1);

but I am having some difficulty calculating the xscale argument for the layers representing hp_current values 31-40, 41-50, 51-60, etc. without causing visual glitches.

To summarize, I need the gauge to remain full if the player's current HP is equal or greater than its respective range's maximum, be empty if it is less than its increment range's minimum, and scale between empty and full proportional to where hp_current lies between said layer's maximum and minimum values. I am having trouble figuring out how to make each layer do this independently of each other. Does anyone else have experience with making something similar to this work?

\$\endgroup\$
1
  • \$\begingroup\$ Do you want to treat additional "layers" of health differently from each other as far as assignment / addition / damage reception goes, or are they going to differ only in the visual representation? \$\endgroup\$
    – altskop
    Commented Oct 5, 2017 at 3:27

2 Answers 2

1
\$\begingroup\$

TL;DR
Sample code solution is the very last one at the bottom of this answer. Here is a small in-depth explanation of such code.


I've worked on a personal project in which I implemented exactly what you are looking for. In my scenario, I had to draw a bar representing the player character's shield points (instead of HP), and the shield was capable of reloading and even increase over the normal maximum, in a Halo-like way.

My strategy was simple: the variable in charge of storing HP value was one only to keep the game logic simple, while the Draw Event code drew overlayed healthbars of different colors for each base quantity a full gauge could represent.

Simple solution
Let hp_gauge be the amount of HP a full healthbar can represent, 30 in our case − using a constant or a local variable rather than hard-coded values is slight better practice and more convenient. Since each bar represents intervals, we can draw each bar by telling them how much wide they must be related to their assigned interval:

// Support value
var hp_gauge = 30;
// First bar (interval 0-30)
draw_sprite_ext(sprite931,-1,0,0,(clamp(hp_current-2*hp_gauge,0,hp_gauge)/hp_gauge),1,0,c_red,1);
// Second bar (interval 31-60)
draw_sprite_ext(sprite931,-1,0,0,(clamp(hp_current-hp_gauge,0,hp_gauge)/hp_gauge),1,0,c_yellow,1);
// Third bar (interval 61-90)
draw_sprite_ext(sprite931,-1,0,0,(clamp(hp_current,0,hp_gauge)/hp_gauge),1,0,c_green,1);

When subtracting hp_gauge once or twice, we are telling each bar to draw their amount assuming the rest of HP will be drawn later by other functions. E.g. if HP is 70, we'll draw a full red bar, then a full yellow bar, finally a foreground green bar scaled at 33%. Also, the function clamp() forces the returned value between 0 and 30 for us.

However this method forces us to code the drawing functions for every single gauge that can be drawn − this very snippet assumes maximum HP can be 90. Also, we may draw two or more full bars on top of each other, seeing only the last one while the others would be overdrawn and not visible, and thus wasting CPU time and resources.

Some optimization ideas
Instead of drawing every single healthbar, we can work this out in another way; first, we can find two useful values by using mod and div expressions:

  • Number of full gauges that can be completely filled by the current health value:

var bars = hp_current div hp_gauge;

  • Amount of HP that should be represented with a partial (not full) healthbar:

var hp_partial = hp_current mod hp_gauge;

For example, if the player's HP is 70, the previous values will be: bars = 70 div 30 = 2; and hp_partial = 70 mod 30 = 10. This means there are two full healthbars to be drawn (2 * 30 = 60 total HP), plus a third bar scaled to represent the remaining 10 HP.

The meaning of hp_partial is obvious, these are the points the top scaled bar must represent:

// Foreground bar (unknown intervals)
draw_sprite_ext(sprite931,-1,0,0,(hp_partial/hp_gauge),1,0,c_white,1);

Note that here the xscale value depends on the value of hp_partial. On the other hand, bars tells us how many healthbars virtually lie below our front partial bar (and we need to draw just one bar only):

// Background bar (unknown intervals)
if (bars>0)
    draw_sprite_ext(sprite931,-1,0,0,1,1,0,c_white,1);

Note that here xscale is 1 because we know this bar must be drawn entirely, since hp_current is greater than hp_gauge. Of course, the background bar must be drawn before the foreground one.

Now that we can draw our bars, how can we deal with colors to give the player an idea of the amount of remaining health? We can solve this issue by using an array to store the colors we want to use for our gauges:

// Colors
barColor[0] = c_red;
barColor[1] = c_yellow;
barColor[2] = c_green;

If bars is 0, we draw only the scaled bar, whose color may be red for example. If bars is 1, we have an extra HP gauge, so the bottom one will be red and the top one yellow (respectively indexed as bars-1 and bars). With this last trick, we can draw our healthbars as follows:

// Healthbar colors
barColor[0] = c_red;
barColor[1] = c_yellow;
barColor[..] = ...;    // this array is up to you
// Background bar
if (bars>0)
    draw_sprite_ext(sprite931,-1,0,0,1,1,0,barColor[bars-1],1);
// Foreground bar
draw_sprite_ext(sprite931,-1,0,0,(hp_partial/hp_gauge),1,0,barColor[bars],1);

This way you can use as many colors as you wish for different health levels, or even change the code to use the [merge_color()][3] function for fancier visual effects, although you must care about bars as an array index not to be out of bounds.

Alternative to using the barColor[] array
Since bars tells us what color the healthbar must be, we can get rid of the barColor[] variable and using a multi-frame sprite, each frame colorized as needed (e.g. frame 0 is red, frame 1 is yellow, ...); finally, we choose what frame to draw instead of picking a color:

// No healthbar colors here
// Background bar
if (bars>0)
    draw_sprite_ext(sprite931,barColor[bars-1],0,0,1,1,0,c_white,1);
// Foreground bar
draw_sprite_ext(sprite931,barColor[bars],0,0,(hp_partial/hp_gauge),1,0,c_white,1);

Hope this answer helps, sorry if too long.

\$\endgroup\$
1
\$\begingroup\$

Nevermind, I already found a crude but effective solution that serves my purposes nicely:

draw_sprite_ext(sprite931, 0,0,0,(min(30, global.hp_current)/30),1,0,c_white,1);
if(global.hp_max > 30)
    draw_sprite_ext(sprite931, 1,0,0,(min(10, global.hp_current - 30)/10),1,0,c_white,1);
if(global.hp_max > 40)
    draw_sprite_ext(sprite931, 2,0,0,(min(10, global.hp_current - 40)/10),1,0,c_white,1);
if(global.hp_max > 50)
    draw_sprite_ext(sprite931, 3,0,0,(min(10, global.hp_current - 50)/10),1,0,c_white,1);
if(global.hp_max > 60)
    draw_sprite_ext(sprite931, 4,0,0,(min(10, global.hp_current - 60)/10),1,0,c_white,1);
if(global.hp_max > 70)
    draw_sprite_ext(sprite931, 5,0,0,(min(10, global.hp_current - 70)/10),1,0,c_white,1);
if(global.hp_max > 80)
    draw_sprite_ext(sprite931, 6,0,0,(min(10, global.hp_current - 80)/10),1,0,c_white,1);

I used different frames of the same sprite to represent the different-colored layers of the health bar, just to clarify.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .