Need help with Health bar thing

Started by Woten, Tue 05/02/2013 18:23:34

Previous topic - Next topic

Woten

Hello everyone.

I'm working on a fledgling game and I want implement a zelda-style life system where my main character has hearts that empty as he loses lives. I was also thinking that he might get into a scrape or too with other characters and with them then having hearts above their heads (as in not in a gui like the main character) that decrease as you take blows against them or what have you.

I have been scouring the forums and I found this handy little thing here: http://www.adventuregamestudio.co.uk/forums/index.php?topic=43954.msg584732#msg584732

But I can't really seem to get it working.

If anybody more experienced that me has any pointers then that would be great.

.M.M.


Khris

You need

a) a global variable that stores health in some way. If the player can lose a 1/4 heart, I'd go with an integer variable that's set to number_of_hearts * 4 initially.

b) a global function that gets called whenever the player looses or gains hearts. It'll update the variable based on the change, then redraw the hearts. The easiest way is to create a non-clickable GUI with background and border color set to 0 (this makes it entirely transparent), then add a button to it.
The button's image is used to display the hearts; it is a DynamicSprite that is cleared, then gets hearts drawn to it after each change.

There are a few pitfalls involved, but we can only help you if you tell us the exact problem.

Woten

Made the variable and this little piece of literature for the gui:
GlobalScript.ash
Code: AGS

import function set_heartgui();

this in the game start function in GlobalScript.asc
Code: AGS

number_of_hearts = 4;

Then this for the gui:
Code: AGS
function set_heartgui()
{
 DynamicSprite *heart4 = DynamicSprite.CreateFromExistingSprite(103); 
 DynamicSprite *heart3 = DynamicSprite.CreateFromExistingSprite(104);
 DynamicSprite *heart2 = DynamicSprite.CreateFromExistingSprite(105);
 DynamicSprite *heart1 = DynamicSprite.CreateFromExistingSprite(106);
 //DynamicSprite *heart1 = DynamicSprite.CreateFromExistingSprite(106.Graphic);
 btnHearts.NormalGraphic = 102;
      
  if (number_of_hearts == 4)
  {
    btnHearts.NormalGraphic = 102;
    heart3.Delete(); 
    heart2.Delete(); 
    heart1.Delete(); 
    heart4.Resize(40, 9);
    btnHearts.NormalGraphic = heart4.Graphic;
    Wait(100);
  }
  else if (number_of_hearts == 3)
  {
    btnHearts.NormalGraphic = 102;
    heart4.Delete(); 
    heart2.Delete(); 
    heart1.Delete();
    heart3.Resize(40, 9);
    btnHearts.NormalGraphic = heart3.Graphic;
    Wait(100);
  }
  else if (number_of_hearts == 2)
  {
    btnHearts.NormalGraphic = 102;
    heart3.Delete(); 
    heart4.Delete(); 
    heart1.Delete();
    heart2.Resize(40, 9);
    btnHearts.NormalGraphic = heart2.Graphic;
    Wait(100); 
  }
  else if (number_of_hearts == 1)
  {
    btnHearts.NormalGraphic = 102;
    heart3.Delete(); 
    heart2.Delete(); 
    heart4.Delete();
    heart1.Resize(40, 9);
    btnHearts.NormalGraphic = heart1.Graphic;
    Wait(100);
    heart1.Delete(); 
  }
  else if (number_of_hearts == 0)

  {
    btnHearts.NormalGraphic = 102;
    //here is where you put the deathscreen and/or your gruesome death animation
  }
  
}


Application is something like this:

Typical health potion (I'm using MI style):
Code: AGS

function iHPpotion_Interact()
{
 if (UsedAction(eGA_Drink))
    player.LockView(22);
    player.Animate(0, 7, eOnce, eBlock, eForwards);
    player.UnlockView();
    if (number_of_hearts == (4))
    set_heartgui();
    else
    number_of_hearts += 1;
    set_heartgui();
}


Damage taken for touching lava:
Code: AGS

function hlava_Interact()
{
 player.Say("Ouch! motherfuck!");
  number_of_hearts -= 1;
  set_heartgui();
}


I'm not really sure if I'm using the button's "NormalGraphic" definition correctly. Also, this is only a health bar for the player character right now. Furthermore the graphic vanishes after set_heartgui which would be cool if I could correct, because I would like health to be shown at the top of the screen (like in dangerous areas) without that happening, is there anyway to make the dynamicsprite graphic stick around?

As for health bar's for the enemies, I was thinking something along the lines of displaying their health bar's above their heads within in the character sprite and then just use maybe 4 different set's of views for each enemy with each sprite having one heart drained etc. I haven't got to that yet so i'm going to stop here before I get ahead of myself.

Khris

If you declare the DynamicSprite within the function, it is deleted at the end of the function. You need to declare it outside and above any function that uses it in order for it and its image to be retained.
I'm not sure how your sprites look and why you're resizing them, the good thing is that you don't need all that.

A button's image can be clipped (set its ClipImage property to true in the editor); therefore, putting a sprite with four hearts on the button and resizing the button when hearts are gained/lost is enough.
Plus, you can pass the change as function parameter and return the new value:

Code: ags
function UpdateHeartGUI(int change) {
  number_of_hearts += change;
  if (number_of_hearts < 0) number_of_hearts = 0;
  if (number_of_hearts > 4) number_of_hearts = 4;
  btnHearts.Width = number_of_hearts * 10;
  return number_of_hearts;
}


Now you can simply call
Code: ags
// player gains two hearts
  UpdateHeartsGUI(2);

// player loses one heart
  if (UpdateHeartsGUI(-1) == 0) Die();


You only need a DynamicSprite if simply resizing the button isn't enough. In that case, you'd draw the hearts onto the DynamicSprite's DrawingSurface to update the button's image.


As for putting hearts above enemies, using four different views for each character is precisely what not to do. It gets the job done, but is the most inconvenient way to do it by a long shot.
There are multiple options:
- using one DynamicSprite for each frame in the view, the hearts are drawn to the original sprites dynamically during the game
- similar to the player's hearts bar, each player gets their own GUI and button
- only one GUI with multiple buttons is used
- not GUI buttons but Overlays are used

The best way to do it depends on the maximum number of enemies on screen at once and whether enemy hearts are supposed to disappear behind walkbehinds/objects along with the characters and possibly other factors as well.

Woten

#5
Copied the UpdateHeartGui function and it returned this:

Error: Function declaration has wrong number of arguments to prototype.

However it looks very dynamic and is probably exactly what i'm looking for.


What I did do was uploading 4 different sprites, each with for hearts, so the code now looks like this:

Code: AGS


function ChangeofHeartGUI()
{     
  if (number_of_hearts == 4)
  {
    btnHearts.NormalGraphic = 103;
  }
  else if (number_of_hearts == 3)
  {
    btnHearts.NormalGraphic = 104;
 }
  else if (number_of_hearts == 2)
  {
    btnHearts.NormalGraphic = 105;
  }
 else if (number_of_hearts == 1)
  {
   btnHearts.NormalGraphic = 106;
  }
  else if (number_of_hearts == 0)

  {
    btnHearts.NormalGraphic = 0;
    //die
  }
  
} 


Khris

The import line has to reflect the declaration exactly:
Code: ags
// GlobalScript.ash
import function UpdateHeartGUI(int change);


How are your sprites different it they all show four hearts...?

Woten

I'm using four different images, each with, one, two, three & four hearts,respectively. Then I made them blink each time the player loses or gains a life, with the waiting doubling for animation and as a minor penalty.

Code: AGS

function UpdateHeartGUI()
{     
  if (number_of_hearts == 4)
  {
    btnHearts.NormalGraphic = 103;
    Wait(10);
    btnHearts.NormalGraphic = 0;
    Wait(10);
    btnHearts.NormalGraphic = 103;
    Wait(10);
    btnHearts.NormalGraphic = 0;
    Wait(10);
    btnHearts.NormalGraphic = 103;
  }
  else if (number_of_hearts == 3)
  {
    btnHearts.NormalGraphic = 104;
    Wait(10);
    btnHearts.NormalGraphic = 0;
    Wait(10);
    btnHearts.NormalGraphic = 104;
    Wait(10);
    btnHearts.NormalGraphic = 0;
    Wait(10);
    btnHearts.NormalGraphic = 104;
 }
  else if (number_of_hearts == 2)
  {
    btnHearts.NormalGraphic = 105;
    Wait(10);
    btnHearts.NormalGraphic = 0;
    Wait(10);
    btnHearts.NormalGraphic = 105;
    Wait(10);
    btnHearts.NormalGraphic = 0;
    Wait(10);
    btnHearts.NormalGraphic = 105;
  }
 else if (number_of_hearts == 1)
  {
   btnHearts.NormalGraphic = 106;
    Wait(10);
    btnHearts.NormalGraphic = 0;
    Wait(10);
    btnHearts.NormalGraphic = 106;
    Wait(10);
    btnHearts.NormalGraphic = 0;
    Wait(10);
    btnHearts.NormalGraphic = 106;
  }
  else if (number_of_hearts == 0)

  {
    btnHearts.NormalGraphic = 0;
    //die
  }
  
}


I did however use your suggestion to make a very nice mana bar, so:

Code: AGS

function UpdateMana(int change)
{
  Manabar += change;
      if (Manabar < 0) Manabar = 0;
      if (Manabar > 12) Manabar = 12;
      btnMana.Width = Manabar * 3;
  return Manabar;
}



Khris

Your first piece of code represents a classic example of how not to code.
Take a look at this:
Code: ags
function UpdateHeartGUI()
{
  btnHearts.NormalGraphic = 107 - number_of_hearts;
  int i;
  while (i < 2) {
    Wait(10);
    btnHearts.Visible = false;
    Wait(10);
    btnHearts.Visible = true;
    i++;
  }
}

This requires a single change should you want to use more than 4 hearts, and again a single change, should you want to make it blink longer.

Also note that again, you have to always change number_of_hearts, then call UpdateHeartsGUI(), when you could've easily kept the way I suggested to do it.

SMF spam blocked by CleanTalk