These are mostly development notes for my own use so I have them in one spot. Feel free to ignore. I will provide some bit of context for anyone interested though.
Background
All, I don't know if you noticed in some of my example videos, but I'm a big fan of dynamic HUDs. At minimum, I require the following:
Have segments that drain individually, with visual indication through clipping, color, or a combination of both.
Sine wave color cycling, working in conjunction with (i.e. not overriding) existing colors and drawmethods.
Fully support animation at each level (meter, segment, etc.), separate from above features.
Support dynamic text and vector graphics, again without interfering with other features.
Change background and other elements in response to player status.
Optionally display all active enemies' statuses on screen.
Row and column breaks must be automatic, adjust to screen size, and avoid overlapping other items like the timer.
As enemies are defeated, their status must stay momentarily on screen, then fade away using dynamic transparency.
When an enemy HUD expires and occupies an area preceding other HUDs, the remaining HUDs move smoothly to fill the gap left behind. Rows and columns rearrange themselves accordingly. Time between movements must be constant regardless of distance.
Additionally, a personal requirement right off the bat is to never use models. No offense to @Bloodbane and @maxman, I think using models for any part of the HUD is a very clunky and sub-optimal way to go about things. Unfortunately, getting all that stuff to work required a LOT of hard coding. Every project had its own unique setup, and that's a big reason I've never managed a full release. Recently I devised a two part system to eliminate much of the hard code.
Part 1
Modelless animations. I'll cover this in detail later. Basically, I use the file-stream system to read a custom sheet of my own, styled similar to native model sheets. The parser creates a series of nested arrays with sprites, delays, drawmethods, etc. to build animation objects I can reference and play back with other scripts.
Part 2
Text based HUD. Similar to the above. I've built a HUD system that assembles meters by reading from text sheets. See notes below.
Meters are subdivided to allow a dynamic representation of values. The names of each subdivision are roughly based on the design of an electrical meter, albeit with some additions. To illustrate we will use the classic Zelda heart meter.
Meter – Meter is the primary container. It houses all the other units to indicate a status on screen.
Register – Registers are the first level of a meter. In our Zelda example, it is the row of hearts. One meter may have multiple registers, and each register can measure one value. Although this arrangement allows a meter to handle disparate status values (i.e., HP and MP), this is not the intent nor recommended. Rather, it should be more analogous to the multiple hands of a clock. In gaming terms an example would be one register indicating current hit points, while a second register also indicates hit points, but with a much slower rolling effect to convey received damage, as is common in modern fighting games. Registers are also used to add “back fill”. For example, a static row of black hearts to provide backdrop as the register indicating hit points with red hearts depletes.
Unit – Units are the segments of a meter and act as containers for dials. In a register of hearts, one unit houses the dials that represent an individual heart. For instance, in a register of four hearts, the fourth unit is indicative of 75% - 100% hit points, and its dials will represent hearts for that value range.
Dial – Dials are range triggered sub-divisions of a unit. Dials are only active if a sub-percentage the parent unit represents falls with the dial’s range setting. In the four-heart example, if the hit points are at 75%, then the first three hearts sub percentages are 100%, while the fourth is 25%. In turn we might set up four dials to represent sub percentages up to 25%, 50%, 75%, and 100% respectively. This allows us to give each unit dynamic behavior based on the status value. Again, using the Zelda model, this is how we would replicate the half heart behavior.
Pointer – Pointers are containers within a dial. Each pointer houses one animation, drawmethod settings, and other support adjustments. This allows each dial to have multiple graphical elements active at once. An example would be a "back" and "front" pointer, with the front pointer tied to meter's value and the back pointer acting as a static backdrop for the meter unit.
Code:
meter player_hp
offset 29 24
register fill
value_key fill_fraction
# offset 0 0
unit 0
# offset 0 0
dial 0
range 1.0
# offset 0 0
# container back
pointer fill
# offset 0 0
animation hp_horizontal_front
clip_config vertical_down unit
dm_config active reset_pre reset_post
dm_enabled 1
dm_tint_mode 1
dm_tint_color 0 100 0
dm_tint_cycle_amplitude 25 25 25
dm_tint_cycle_wavelength 3.0
dial 1
range 0.75 1.0
# offset 0 0
pointer back
# offset 0 0
animation hp_horizontal_front
pointer fill
# offset 0 0
animation hp_horizontal_front
clip_config vertical_down unit
dm_config active reset_pre reset_post
dm_enabled 1
dm_tint_mode 1
dm_tint_color 170 170 0
dm_tint_cycle_amplitude 25 25 25
dm_tint_cycle_wavelength 3.0
dial 2
range 0.5 0.75
# offset 0 0
pointer back
# offset 0 0
animation hp_horizontal_front
pointer fill
# offset 0 0
animation hp_horizontal_front
clip_config vertical_down unit
dm_config active reset_pre reset_post
dm_enabled 1
dm_tint_mode 1
dm_tint_color 150 100 0
dm_tint_cycle_amplitude 25 25 25
dm_tint_cycle_wavelength 3.0
dial 3
range 0.0 0.5
# offset 0 0
pointer back
# offset 0 0
animation hp_horizontal_front
pointer fill
# offset 0 0
animation hp_horizontal_front
clip_config vertical_down unit
dm_config active reset_pre reset_post
dm_enabled 1
dm_tint_mode 1
dm_tint_color 200 0 0
dm_tint_cycle_amplitude 25 25 25
dm_tint_cycle_wavelength 3.0
unit 1
offset 13 0
dial 0
range 1.0
# offset 0 0
# container back
pointer fill
# offset 0 0
animation hp_horizontal_front
clip_config vertical_down unit
dm_config active reset_pre reset_post
dm_enabled 1
dm_tint_mode 1
dm_tint_color 0 100 0
dm_tint_cycle_amplitude 25 25 25
dm_tint_cycle_wavelength 3.0
dial 1
range 0.75 1.0
# offset 0 0
pointer back
# offset 0 0
animation hp_horizontal_front
pointer fill
# offset 0 0
animation hp_horizontal_front
clip_config vertical_down unit
dm_config active reset_pre reset_post
dm_enabled 1
dm_tint_mode 1
dm_tint_color 170 170 0
dm_tint_cycle_amplitude 25 25 25
dm_tint_cycle_wavelength 3.0
dial 2
range 0.5 0.75
# offset 0 0
pointer back
# offset 0 0
animation hp_horizontal_front
pointer fill
# offset 0 0
animation hp_horizontal_front
clip_config vertical_down unit
dm_config active reset_pre reset_post
dm_enabled 1
dm_tint_mode 1
dm_tint_color 150 100 0
dm_tint_cycle_amplitude 25 25 25
dm_tint_cycle_wavelength 3.0
dial 3
range 0.0 0.5
# offset 0 0
pointer back
# offset 0 0
animation hp_horizontal_front
pointer fill
# offset 0 0
animation hp_horizontal_front
clip_config vertical_down unit
dm_config active reset_pre reset_post
dm_enabled 1
dm_tint_mode 1
dm_tint_color 200 0 0
dm_tint_cycle_amplitude 25 25 25
dm_tint_cycle_wavelength 3.0
unit 2
offset 26 0
dial 0
range 1.0
# offset 0 0
# container back
pointer fill
# offset 0 0
animation hp_horizontal_front
clip_config vertical_down unit
dm_config active reset_pre reset_post
dm_enabled 1
dm_tint_mode 1
dm_tint_color 0 100 0
dm_tint_cycle_amplitude 25 25 25
dm_tint_cycle_wavelength 3.0
dial 1
range 0.75 1.0
# offset 0 0
pointer back
# offset 0 0
animation hp_horizontal_front
pointer fill
# offset 0 0
animation hp_horizontal_front
clip_config vertical_down unit
dm_config active reset_pre reset_post
dm_enabled 1
dm_tint_mode 1
dm_tint_color 170 170 0
dm_tint_cycle_amplitude 25 25 25
dm_tint_cycle_wavelength 3.0
dial 2
range 0.5 0.75
# offset 0 0
pointer back
# offset 0 0
animation hp_horizontal_front
pointer fill
# offset 0 0
animation hp_horizontal_front
clip_config vertical_down unit
dm_config active reset_pre reset_post
dm_enabled 1
dm_tint_mode 1
dm_tint_color 150 100 0
dm_tint_cycle_amplitude 25 25 25
dm_tint_cycle_wavelength 3.0
dial 3
range 0.0 0.5
# offset 0 0
pointer back
# offset 0 0
animation hp_horizontal_front
pointer fill
# offset 0 0
animation hp_horizontal_front
clip_config vertical_down unit
dm_config active reset_pre reset_post
dm_enabled 1
dm_tint_mode 4
dm_tint_color 200 0 0
dm_tint_cycle_amplitude 35 35 35
dm_tint_cycle_wavelength 3.0
Whoa! The MP looks animated! How do you ever make that animated instead of using models for animation? You don't seem to use animated BG layers, do you? I believe not. It's astonishing!
Whoa! The MP looks animated! How do you ever make that animated instead of using models for animation? You don't seem to use animated BG layers, do you? I believe not. It's astonishing!
An often forgotten capability of OpenBOR is it can read and write text files. I took advantage of that to build a modeless animation system that allows you to create scripted animations by filling out an animation sheet with a format similar to native model texts. Each animation is identified by a name, which other scripts (like the HUD) can then call and play back.
It does a lot of things native animations don't, like belt scrolling or drawing text. These are my own notes at the top of the sheet that show the things you can add. It's made in such a way I can very quickly expand it to support more effects and properties. Ex: Right now, the only drawmethod available is tinting, but I could add all the others in just a couple of minutes.
Code:
####################################
###### Commands and Attributes #####
####################################
#
# animation <name> - Name of animation. This might be used by a hard
# coded script draw in an animation set.
#
# belt_loop <string args> - What to do when scroll reaches defined min or max. Accepts one or more of the following:
# x_return - Move to the opposite X boundary.
# x_reverse - Reverse current belt_notch_x.
# x_stop - Stop auto scroll on X axis.
# y_return - Move to the opposite Y boundary.
# y_reverse - Reverse current belt_notch_y.
# y_stop - Stop auto scroll on Y axis.
#
# belt_delay <int centiseconds> - Belt auto scroll delay.
#
# belt_notch_x <int> - Pixels to auto scroll frame on horizontal axis.
#
# belt_notch_y <int> - Pixels to auto scroll frame on vertical axis.
#
# belt_pos_x_min <int> - Minimum horizontal scroll position.
#
# belt_pos_x_max <int> - Maximum horizontal scroll position.
#
# belt_pos_y_min <int> - Minimum vertical scroll position.
#
# belt_pos_y_max <int> - Maximum vertical scroll position.
#
# dm_alpha <int> - Drawmethod alpha mode.
#
# dm_config <string args> - Drawmethod config. This is not a native drawmethod property. Accepts one or more of the following:
# active - Use animation frame drawmthods. If not present, drawmethods are not applied at all.
# reset_pre - Reset drawmethods before drawing frame.
# reset_post - Reset drawmethods after drawing frame.
#
# dm_enabled <int> - Drawmethod on (1) or off (0).
#
# dm_tintcolor <int red> <int green> <int blue> - Drawmethod tint color.
# dm_tintmode <int> - Drawmethod tint mode.
#
# delay <int centiseconds> - Delay time.
#
# frame <path sprite> <path alpha mask (optional)> - Animation frame. Use "none" to add a frame with no sprite.
#
# loop <string args> - Looping behavior for animation. Accepts one or more of the following:
# enable - Animation loops to frame 0 after last frame plays.
#
# next <int frame> - Move to <next> frame index instead of subsequent index when the frame delay expires.
#
# offset <int x> <int y> - Position of frame.
#
# position <int x> <int y> - Position of animation.
#
# text_config <string args> - text behavior for string output. Accepts one or more of the following:
# enable - Enable drawing the string. String does not draw otherwise.
# center_x - Place horizontal center of string at frame position.
# center_y - Place vertical center of string at frame position (i.e. in a three line string, position is vertical center of second line).
#
# text_string <string> - Text to draw into frame. Use @~<variable name>~@ to insert value from a
# local variable in the parent code.
#
# text_line_break <string> - Discard each instance of <string> in the text_string and start a new line.
#
# text_font <int font#> - Font to draw text.
#
# END - Immediately stop parsing of the animation list at this point.
#
The animations themselves are constructed using multidimensional arrays. The parent script supplies the animation playback function with the desired animation object, and it draws the appropriate sprite with any drawmethods or other effects specified. Here's the whole MP entry from the HUD system. Note the animation names. That tells the HUD system what animation to play depending on ranges and sub ranges. The HUD system then adds its own effects and adjustments, and that's your end result on screen.
Rich (BB code):
meter player_mp
offset 29 24
register full
offset 2 -1
value_key fill_fraction_mp
range 1.0 1.0
unit 0
dial 0
range 0.0 1.0
pointer full
animation hero_full
register fill
value_key fill_fraction_mp
range 0.0 0.999999
unit 0
dial 0
range 0.0 1.0
pointer frame
animation hero_h_frame
pointer fill
offset 1 1
animation hero_h_fill
clip_config vertical_down unit
dm_config active reset_pre reset_post
dm_enabled 1
dm_tint_mode 3
dm_tint_color 200 0 0
dm_tint_cycle_amplitude 25 25 25
dm_tint_cycle_wavelength 3.0
unit 1
offset 13 0
dial 0
range 0.0 1.0
pointer frame
animation hero_e_frame
pointer fill
offset 1 1
animation hero_e_fill
clip_config vertical_down unit
dm_config active reset_pre reset_post
dm_enabled 1
dm_tint_mode 3
dm_tint_color 200 0 0
dm_tint_cycle_amplitude 25 25 25
dm_tint_cycle_wavelength 3.0
unit 2
offset 26 0
dial 0
range 0.0 1.0
pointer frame
animation hero_r_frame
pointer fill
offset 1 1
animation hero_r_fill
clip_config vertical_down unit
dm_config active reset_pre reset_post
dm_enabled 1
dm_tint_mode 3
dm_tint_color 200 0 0
dm_tint_cycle_amplitude 25 25 25
dm_tint_cycle_wavelength 3.0
unit 3
offset 39 0
dial 0
range 0.0 1.0
pointer frame
animation hero_o_frame
pointer fill
offset 1 1
animation hero_o_fill
clip_config vertical_down unit
dm_config active reset_pre reset_post
dm_enabled 1
dm_tint_mode 3
dm_tint_color 200 0 0
dm_tint_cycle_amplitude 25 25 25
dm_tint_cycle_wavelength 3.0