Game Development Community

Progress bar made with SceneObjects

by Joe Rossi · in Torque Game Builder · 10/05/2006 (5:26 pm) · 19 replies

I'm trying to draw some health bars for my players, using SceneObjects instead of the GUIs. I'm running into a few problems..

My first idea was to draw the base of the bar, with no health in it, then mount a health bar over it and try to make it look right by scaling the health and moving the link point around.. but that's not working so great. Mostly due to the fact that when scaling the width both the left and right sides of the bar are scaled. I wish there was some way to crop a sceneobject off at one edge.. (is there??)

I'm trying to avoid making an animated object with one frame per damage point, but that's looking like the only option at this point.. can anyone think of a better way?

#1
10/05/2006 (5:51 pm)
Can you post a screenshot showing why the scaling isn't working?

Having a staticSprite and calling setFrame() seem slike it would be a good alternative, unless you want a continuous graph of the health instead of a stepped health.
#2
10/05/2006 (6:21 pm)
Well scaling works, it's just that it scales both edges of the health, so it ends up looking wrong. If there were a way to pin the left edge of the health to the left side of the base of the health bar, and only scale the right side of it, then it would probably work great.

I can't do a screenshot but Ascii art might help illustrate.

Base of the bar (with health depleted):
__________
[]_________[]

Full green health bar fits inside of base:
__________
[_________]

Scaling the health bars width does this:

-->[__:(__]<--

I'd like to somehow do this when damage is taken:
____________________________
[](green health)___| <<--( black )_ []
#3
10/05/2006 (6:32 pm)
There are two options that I can think of for this. The first one, which I use myself, is to pad out the left side of the sprite with empty space. Position the center of the sprite (which now corresponds with the left edge of the bar) at the left edge of the health bar base. Now when you scale the bar, it will appear to scale to the right, while the left edge stays aligned.

The other way would be to scale and reposition with setPosition().
#4
10/05/2006 (6:33 pm)
I wonder if this is the sort of thing a scroller image would be good for? I've never looked into them, but who knows, maybe there's a trick there.

I've given it some thought, too as my current system of having 5 tick marks doesn't allow for the player getting tougher from upgrades etc. I was thinking about checking out the RTS example on TDN, is that method using a GUI?
#5
10/05/2006 (6:55 pm)
@Teck Lee Tan,
I just tried padding out the sprites and that seems to work, but, I think that's kind of wasteful on the resources for such a simple effect. Also pretty annoying to create and edit..

@Don Hogan,
The RTS example does use the GUI system.. which I'm trying to avoid. I think you're on to something with the scroller though, I'll mess around and see.
#6
10/05/2006 (6:57 pm)
Sorry, I can't understand the ascii art diagrams.
#7
10/05/2006 (7:53 pm)
It's not that hard to reposition it based on the scale amount. Like, if you decrease the Xsize by 6, move it 3 to the left.
#8
10/05/2006 (9:23 pm)
Regarding padding and scaling

All you really need is on pixel of full alpha and one pixel of whatever color your bar is. That is two pixels. You aren't wasting any resources or taking up unnecessary room that way. You can then scale that two pixel bar to whatever horizontal and vertical size you need. I have done several things where I just made a one pixel image of a color I needed and scaled it up to thousands of percent its original size.
#9
10/05/2006 (11:11 pm)
Well it took me all night, but I did it.. still feel like there should be a better/easier way but oh well.. until I find one, or someone else is kind enough to offer one, I guess it's going to be "whatever works"...

I was close to having something working using scrollers, but I think it's a dead end because:

*setScrollX apparently can't use negative values. So..I can decrease health, but not increase it.

*For some reason, calling setScrollPositionX() on my scroller has no effect, or at least not what I'd expect. I'm calling it with 0.5, and it always sets the position to 0.00988924...?

I wish it could work so I/we could have cooler looking bars like with gradients and etc.. but hey..


Here's what I came up with, more along the lines of what Ben Vesco and Tom Eastman were talking about. A one pixel image on a static sprite used for the health filler, and an empty bar backing also on a staticsprite. Set a link on the backing sprite and mount the health on it.
Edit: and make sure the backing has it's class set to "healthbar"...

function healthbar::setvalue( %this, %x ) {
     
    %borderoffset = 0.05; // here you can tweak it to fit inside your backing...
    
    // health is the bar filler sprite, add/subtract from width here
    // You'll need one filler sprite per healthbar class..
    health.setWidth(  health.getWidth() -  %x   );       
     
    // Adjust the link point to keep it looking right.  Ugly math... is this really necessary?? 
    %this.setLinkPoint( 1 , 
       - ((getword( %this.getLocalPoint( %this.getsize()    ) , 0 )/2) - %borderoffset)
        +  
         getword( %this.getLocalPoint( health.getsize()  ) /2 , 0 )  , 0 ); 
}

By feeding this positive values the health goes down, negatives make the health go up.
Another good thing is you can mess with blending to change the color of the bar.
#10
10/05/2006 (11:34 pm)
Quote:I wish it could work so I/we could have cooler looking bars like with gradients and etc.. but hey..
If the background of the healthbar container is black (or white), you can stick another sprite on top of the actual health bar with the appropriate blend mode (multiply, if the background is black, or screen if it's white). This way it will affect the color of the health bar (with, say, a gradient, or funky pattern), but not affect the container.
#11
10/05/2006 (11:49 pm)
Here's how I would do it:

function ScoreBar::onLevelLoaded(%this, %scenegraph)
{
   %this.leftX = %this.getPositionX() - %this.getSizeX()/2;  //Note: world-relative
   %this.maxX = %this.getSizeX();  //Note: the width of the bar
   
   %this.maxScore = 2000;     //the value that the starting length indicates
   
   %this.setScore(0);
}

 
function ScoreBar::setScore(%this, %score)
{
   %this.setSizeX(%score * %this.maxX / %this.maxScore);
   %this.setPositionX(%this.leftX + %this.getSizeX()/2);
}

The key is to start the bar at the maximum width. When I've made bars before, I did just what Ben suggested and used a one pixel image, stretched it to fill the GUI area it should, and then used this code (well, actually I added in a some scheduling so the score doesn't jump - it fills in up to the score needed).

Hope that helps and made sense!
#12
10/06/2006 (6:42 am)
Just scanning through the suggestions this morning, I had another thought. ( Actually, first off, sorry the scroller idea was a dead end, Joe. )

Wouldn't you be able to get a slightly cooler effect if the stylized bar ( gradient, design, whatever ) was the background of the container and as health changed you either covered or revealed it with the black ( or whatever ) sprite?

An idea this sparked for me was that if I used black and white as suggested, the color of the health bar could me modulated according to how much health the player has. It would be easy to change it at even percentages ( 25, 50, 75, 100 for example ) and I'll bet with a little work you could make it interpolate pretty smoothly at even finer increments. You could probably even get a hook in to make it pulse when it's really low.

Also, since all of this is hooked into a score / damage / pickup system even if you get really fancy it still shouldn't use too much overhead.

Edit: spelling
#13
10/06/2006 (8:14 am)
I have used the effect you're talking about with having the actual bar be static and masking it with something from the "top end" of the scale that graphically looks like it is under the health bar but layer-wise it is above. It works really great, and you still only need one pixel!

If you have the TGB Pro I'd also like to encourage you to check out the t2d primitives resource which has a built in health bar thingy.
#14
10/06/2006 (1:00 pm)
Well, I ended up making it out of the one pixel and blank backing, and using Tom Eastman's suggestion for making it scale relative to the maximum health. Also added some schedule functions to smoothly damage or heal the bar up to a certain value smoothly over a certain period of time. It's not as fancy as I was hoping for, but it works. Maybe someday I'll mess with the masking/blending and using a cool texture for the bar filler. I'm trying to put it all into a class now so I can make 100's of them at a time hopefully in script. Thanks everyone!
#15
10/06/2006 (1:15 pm)
Something just occurred to me. If you created the health (or whatever) bar as a separate scenewindow, wouldn't the masking essentially be automatic? That way you wouldn't even have to mess with scaling, and just reposition (or better yet, moveTo()) the bar. This way you could have something like, say, a test tube sort of thing that fills up (moving a textured bar would be more convincing than, say, scaling it, or unmasking it).
#16
10/06/2006 (1:33 pm)
I thought about that.. and I didn't try it until now.. but that does seem to work :) I have to leave for work now but I'll definitely test this tonight!
#17
10/07/2006 (2:31 am)
Here's what I came up with.. basically you create a scenewindow and scenegraph and mount it on an object with attachgui. The thing I'm worried about with this is performance..not sure how good it is to have lots of scenegraphs/windows. Anyway it's one more way to do it and this method will allow better looking bars.. I'm sure lots of other neat effects can be done this way too.

This code assumes a few things - you have a sprite of class ebar (with your empty bar texture) in your level and your games main scenewindow is named "sceneWindow2D". You may need to play with these values.

function ebar::onlevelloaded( %this, %scenegraph ){
 
if (!isobject(%this.fillscenegraph))  
 %this.fillscenegraph = new t2DSceneGraph( ) ;  
  
if (!isobject(%this.fillwindow)) 
  %this.fillwindow = new t2dSceneWindow() {
         canSaveDynamicFields = "0";
         Profile = "GuiDefaultProfile";
         HorizSizing = "right";
         VertSizing = "bottom";
         Position = "388 309";
         Extent =   "267 15";
         MinExtent = "8 2";
         canSave = "1";
         Visible = "1";
         internalName = "fillwindow";
         hovertime = "1000";
         lockMouse = "0";
         useWindowMouseEvents = "1";
         useObjectMouseEvents = "0";
         scenegraph =  %this.fillscenegraph ;
      };      
        
    %this.fillwindow.setscenegraph(  %this.fillscenegraph );
     
   if (!isobject( %this.health ) ) %this.health  = new t2dStaticSprite(){  scenegraph =  %this.fillscenegraph ;};
    %this.health.setPosition(%this.fillwindow.Extent); 
    %this.health.setSize("25 2");  //whatever size  
    %this.health.setImageMap( gradientimagemap ) ; //your health filling
     
    //set the camera position to the full size of the bar
    %this.fillwindow.setCurrentCameraPosition( %this.health.getPosition() SPC  %this.health.getsize() );

    //mount the gui window to the bar base, and set the size to fit it.
    %this.attachGui(  %this.fillwindow , sceneWindow2D, 1 );   

    %this.init(  1000  );  // set defaults.
}


function ebar::init(%this, %maxhealth ){       
   %this.maxX          =  %this.health.getSizeX()  ;  
   %this.maxposx    =  %this.health.getPositionX() ;  
   %this.maxhealth  = %maxhealth ;  //value that the starting length indicates    
}  
 
function ebar::setvalue( %this,  %x ) {  
   %this.health.setpositionx( (%this.maxposx-%this.maxX ) + ( %x * ( %this.maxX / %this.maxhealth ) )) ;
}


function ebar::fill(%this ){ 
          %this.health.setpositionx(  %this.maxposx ) ; 
          %this.health.setAtRest(); 
          %this.health.stopConstantForce() ;
}


function ebar::damageto( %this,  %x , %speed ) {   
    %this.health.moveto( -%this.health.getPositionX() SPC  %this.health.getPositionY(),  %speed  );
     
   if ( %this.health.getPositionX() > (%this.maxposx-%this.maxX ) + ( %x * ( %this.maxX / %this.maxhealth ) ) )
   {    
        %this.schedule( 100, damageto,  %x , %speed );
   }else { 
       
        %this.health.setAtRest(); 
        %this.health.stopConstantForce() ;
   }
}

function ebar::healto( %this,  %x, %speed  ) {   
   %this.health.moveto( %this.maxposx SPC  %this.health.getPositionY(),  %speed  );
     
   if ( %this.health.getPositionX() < (%this.maxposx-%this.maxX ) + ( %x * ( %this.maxX / %this.maxhealth ) ) )
   {    
        %this.schedule( 100, healto,  %x, %speed  );
   }else {  
        %this.health.setAtRest(); 
        %this.health.stopConstantForce() ;
   }
}

function ebar::onLevelEnded( %this){  
   %this.detachGui();      
   %this.fillwindow.delete(); 
   %this.fillscenegraph.delete();  
}
#18
10/14/2006 (9:25 am)
Jow the best way to do health bar is not with object, the best to do it is with gui element such us GuiBitmamControler.when you do it with this element the position of the bitmap is the left top corer.
if you use an object then every time you make your bar smaller and you align your object to the middle in this way you will not get a good health bar.
#19
10/14/2006 (7:23 pm)
Nir, I really think this is the best way. At least for me it is. With the gui version of this I have less flexibility. I ran into the problem of not being able to control where the GUI was being drawn, without hacking the source..which I'd rather not do for this. Also using this method I can add neat effects by mounting particles to the end of the health and etc. Trust me, that's good code I posted! Try it out... I'm drawing some verrry nice health bars underneath a ton of objects, and it's not even causing much of a performance hit. I expected it to be inefficient, but it's not bad at all.