gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

Coding > Percentile graphical indicators - Do or don't?

#51261 - Istondil - Wed Aug 17, 2005 7:33 pm

Hello all,

I'm working on a game that contains some basic hit-point statistic as well as a semi real-time / semi turn-based mechanism which determines who's character "turn" it is to act. (People who have played Grandia might recognise some similarities, or on a plainer level, look at any Final Fantasy game and search for the ATB gauge slowly filling up till a character can move again).

Currently the game displays the hit-points textually (ergo HP:29/51) and the turn-timer isn't even displayed on screen. For gameplay tactical reasons I would like this timer to be displayed for multiple characters simultaniously. Keep into consideration that the maximum hit-points and time until next action is variable per character.

Because I intend both the health gauge and the turn-gauge to have a fixed width I would need to calculate the percentage of remaining time and health and adjust the graphical indicators accordingly.

I'm aware that divisions can seriously slow down the code and I since I'm planning on implementing a graphic heavy environment (as well as trying to save resources for some form of AI eventually) I need some tips on how to avoid these resource heavy calculations and what alternatives are available.

Summary:
- HP gauge only needs an update after damage is inflicted to a player character.
- Turn gauge position needs to be recalculated every screen update.
- The gauges use BG tiles and up to three small sprites total.
- I don't need a 100% accurate solution, integer based results are sufficient.

Any help or pointer to the right document is greatly appreciated. If you feel I need a kick in the arse for trying to optimise something this trivial reply as well :)

( EDIT: in C code )

#51262 - neonext - Wed Aug 17, 2005 7:38 pm

just a few quick thoughts about this. there's no reason you absolutely need to do division for this, and also you dont necessarily have to recalculate the value every time the screen refreshes

#51265 - Istondil - Wed Aug 17, 2005 8:09 pm

neonext wrote:
just a few quick thoughts about this. there's no reason you absolutely need to do division for this, and also you dont necessarily have to recalculate the value every time the screen refreshes


I need a way to calculate HP1_PERC = HP1_CUR / HP1_MAX, and then display the gauge usingBG tiles according to it's percentage. Same goes for the turn gauge, but the turn gauge is synchronised with HBLANK (and as such deserves an update every redraw).

Additional illustration:
- HP gauges are 32 pixels wide (and have 16 different variations of being filled through empty)
- The turn gauge is 200 pixels wide, but uses a sprite to indicate the character's position in queue. Let's assume a character's "DELAY1_MAX" stat is 37 (needs 37 ticks before it can act again) and 17 ticks passed since the last turn gauge reset, I'd need to calculate DELAY1_PERC = DELAY1_CUR / DELAY1_MAX and place the spriteX = (DELAY1_PERC * 200) + 20.

As mentioned, useful alternatives are welcome.

#51266 - poslundc - Wed Aug 17, 2005 8:09 pm

Don't make your maximums variadic (ie. make them fixed constants) and the compiler should optimize your divides into inverse-multiplication.

Use even powers of 2 for all of your maximum values when you can. 128, 256, 65536, etc. The compiler will optimize divisions by these values into simple shifts. (Signed numerators need some special consideration, but are basically the same.)

In the case where the maximum fluctuates and must be visible to the user (such as HP), there isn't very much you can do to avoid division. If the range is constrained tightly enough (such as it might be with HP) you can use a reciprocal lookup table and multiply instead. At the same time, don't be afraid of dividing once or twice per frame; just make sure you divide only once and record and use the result throughout the rest of the code.

Dan.

#51273 - neonext - Wed Aug 17, 2005 10:10 pm

Istondil wrote:

I need a way to calculate HP1_PERC = HP1_CUR / HP1_MAX, and then display the gauge usingBG tiles according to it's percentage. Same goes for the turn gauge, but the turn gauge is synchronised with HBLANK (and as such deserves an update every redraw).
.


You can calculate the HP every time it changes (every time there is a hit or heal) rather than every frame, and store it so you can just graphically display the percentage rather than calculating it every frame. You can even probably get away with using division for this, seeing how it will only happen a single time every so often, instead of constantly happening every refresh

#51307 - gladius - Thu Aug 18, 2005 4:45 am

Another neat trick to use if you really need to do divisions by a constant value is to do something like this:

(a / fixed) becomes ((a * b) >> c)

Where 2^c / b ~= fixed. Simply set c to the desired accuracy and you are good to go.

For example, to do a division by 21, you can do the following: (a * 3121) >> 16. This gives perfect results.

I'm not sure if you can use this trick, but it has helped me out of many situations, and it's an optimization the compiler can not usually do.

#51352 - poslundc - Thu Aug 18, 2005 5:44 pm

gladius wrote:
I'm not sure if you can use this trick, but it has helped me out of many situations, and it's an optimization the compiler can not usually do.


On the contrary; it's an optimization the compiler performs by default, I believe, although you may have to have at least O1 turned on.

Dan.

#51353 - Istondil - Thu Aug 18, 2005 5:56 pm

Thanks for the helpful replies. Health bars shouldn't pose much of a problem if you avoid effects like Poison or Regeneration. (And even then you could for example deal poison damage or health regen in a big chunk once every 10 seconds instead of a small amount every tick).

I might end up sacrificing some accuracy on the turn guage by simply allowing only 16 different indicator positions like the health gauge and using a form of lookup chart initiated at the start of battle to determine the actual x,y position throughout battle events.

#51357 - DekuTree64 - Thu Aug 18, 2005 6:36 pm

Eh, I wouldn't limit your design over it. A couple of divides every frame won't kill you, and using Gladius's method of storing the reciprocal of your max HP, you'll only need them when your max HP changes, which I'm assuming won't be very often.
Like when you get a level up or something that changes your max HP, do this:
Code:
dude.maxHP += howeverMuchHeGot;
dude.reciprocalMaxHP = (1 << 16) / dude.maxHP;

Then in your menu code:
Code:
barLevel = dude.curHP * BAR_MAX * dude.reciprocalMaxHP >> 16;

_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku

#51376 - Istondil - Thu Aug 18, 2005 10:00 pm

Yeah, as I said, the HP gauges won't pose much of a problem as I don't plan on updating it often. I'm more worried about the turn gauge.

I've dug up an ancient Grandia screenshot that perfectly displays a sample turn gauge. Click here. (Courtesy of gamescreenshots.com).

I doubt it'll be a very good idea codewise to recalculate percentages (x3) every other second when the gauge needs updating. The speed at which the gauge moves depends on Haste / Slow effects, the difficulty of the action (for example, defend actions are instantaniously but complex spells would take additional charging to simulate casting time) and any exhaustion/cooldown penalties or bonusses.

Doing it just with numbers is easy as pie. Displaying such a dynamic gauge properly without affecting performance sounds like a nightmare.

#51381 - gladius - Thu Aug 18, 2005 10:52 pm

poslundc wrote:
On the contrary; it's an optimization the compiler performs by default, I believe, although you may have to have at least O1 turned on.

You are correct, but the compiler does not usually use the method I presented above as it (a) has the potential for overflow (b) is not perfectly accurate. It is however, fast :).

Gcc will produce code similar to the following for an integer division by 21:
Code:

   0:   e3a035c3        mov     r3, #817889280  ; 0x30c00000
   4:   e2833bc3        add     r3, r3, #199680 ; 0x30c00
   8:   e2833031        add     r3, r3, #49     ; 0x31
   c:   e0c21093        smull   r1, r2, r3, r0
  10:   e1a00fc0        mov     r0, r0, asr #31
  14:   e0600142        rsb     r0, r0, r2, asr #2

Which is pretty good, but still has the 64 bit multiply, and some sign extension code that isn't really neccesary if you know the variable will be smaller than 65536 (gcc does the same thing on a short or byte, along with a bit of extra fluff).

But still, it's a lot better than a divide function call :).

#51383 - gladius - Thu Aug 18, 2005 10:57 pm

Istondil wrote:
Doing it just with numbers is easy as pie. Displaying such a dynamic gauge properly without affecting performance sounds like a nightmare.

Actually, this should be pretty easy. What you need to do is store the position in fixed point, then you can have a velocity for the picture. The only time you have to recalculate your velocity is when it changes of course (ie. when a slow is cast, or something like that).

So, you might have something like this:
Code:

int position, velocity;
position = 0;
velocity = 16; // (this is really 16/256, or 0.0625 pixels per frame)

while (battle) {
 position += velocity;
 displayPicture(position >> 8);
}

#51387 - Istondil - Thu Aug 18, 2005 11:37 pm

gladius wrote:
Actually, this should be pretty easy. What you need to do is store the position in fixed point, then you can have a velocity for the picture. The only time you have to recalculate your velocity is when it changes of course (ie. when a slow is cast, or something like that).


*slaps himself* Yeah, it can be something as simple as that. Just requires some planning and minor tweaks in my current system to make the calculations easier to perform. I was simply staring myself blind at something that didn't need to be complex at all. Once more, thanks for the suggestions.