jorotroid
Member
A Small Edit: Here is the link to my post on how to disable the built in HUD, but I also need to update that post. If you follow the instructions there, it will disable the HUD, but there will still be some code elsewhere related to the HUD that is just taking up a little bit of space. So I'll hunt those down at some point and add them to that post.
http://www.nesmakers.com/viewtopic.php?f=3&t=896
I am going to divide this post into 3 sections: The Pros and Cons of doing this, an attempt at a High-Level Explination on what the code does, and finally the code itself and How to Hook it Up. Read them all, or just the ones that are relevant to you.
Pros and Cons
Pros:
-Uses way less memory than the built in HUD to give you more room to work with other things.
-Gives an alternative look.
Cons:
-One less sprite available on the each scanline if you are just doing a health bar. Two less if you are also displaying a weapons bar.
-Less sprites to uses on screen in general. For me, I am using 8 sprites per bar, plus 4 sprites for the caps at the top and bottom which means 20 sprites are going to this health bar. Don't forget that you are limited to 64 sprites at a time. For me, I think that should be fine for what I have planned.
-You have to use some graphical space in either your game objects or each of your monster files. I went with putting the graphics in my game objects.
-Unless you're using also textboxes, the HUD graphics may end up going unused which means that some of your background graphics space will go to waste. I bet there is a way you could re-purpose that space, but I haven't looked into it, so I don't know what would be involved.
-More juggling of your sprite palettes.
-The way I have it written, the health bar goes from 0 to 255, but it only has 32 increments. This means if you are at full health and take 1 point of damage, you will not see a visual change. You need to take about 8 points of damage to see the bar go down 1 step. The easy "fix" to this is to have all enemies give at least 8 points of damage. It wouldn't be difficult modify the code to make the health bar have more divisions, but that would either mean more sprites on screen, more graphic space being used, or both.
Ok, I think you have been sufficiently warned. Now on with the show!
High-Level Explanation
The health bar is made up of 3 sections: Full bar segments, empty bar segments and sometimes one partial bar segment. Each bar segment is a sprite and there are 8 sprites used for the entire health bar. If you are unfamiliar with binary numbers, then here is a quick crash course on finding the value of a binary number. If a bit is equal to one then that adds a certain value to the total of binary number. Using the chart below, you can find out which bits add which value.
Code:
Bit number: 76543210 Added value:
|||||||+--- 1
||||||+---- 2
|||||+----- 4
||||+------ 8
|||+------- 16
||+-------- 32
|+--------- 64
+---------- 128
So this means that if you have 1000 0000, then the only on bit is bit 7 which has a value of 128, so the value of the number is 128. Or to make a more complex example, if you have 1010 1001, you have the bits on for the values 128, 32, 8, and 1 so you add them up to get 169. As I said, the health bar is made up of 8 sprites, each accounting for 32 health points. So if the player's health is 128 or 1000 0000 in binary then I already know that the total health bar is 4 bar segments full (because 128/32 = 4). Or another way you could look at it is that 128 is half of 255ish, so the health bar is half full. So by looking at the last 3 bits (the ones on the left), I can tell how many full bar segments the health bar needs. once I have the number of full bar segments, I can subtract that number from 8 to get the number of empty bar segments. If I have any remaining bits then I can do a check to see what kind of partial bar segment needs to be displayed between the full and empty bar segments. So here is that bit chart again, but this time also showing how much of the health bar each bit represents.
Code:
Bit number: 76543210 Added value: Number of Bar Segments:
|||||||+--- 1 \
||||||+---- 2 > If any of these bits = 1, it displays 1/4 of a Segment
|||||+----- 4 /
||||+------ 8 1/2 of a Segment
|||+------- 16 3/4 of a Segment
||+-------- 32 1 Segment
|+--------- 64 2 Segments
+---------- 128 4 Segments
So all of that determines what needs to be displayed. From there we do a loop to display all the full segments, then display a partial segment if we have one, and finally do another loop to display the empty segments. And that's it. I skipped some finer details, but if you made it this far, you should be able to understand the rest from looking at the code.
How to Hook it Up
Step 1:
Open HandleUpdateObjects.asm which is in Routines/System. At line 31 you should see this line of code.
Code:
.include SCR_SPRITE_PREDRAW
A brief explanation of what we're doing here.
You may recall in the Adventure module that when you get you sword, it will display a little sword sprite in the HUD. This is where that happens in the code, so we're going to use that. But first we need to do some modifications. The code for the sprite HUD bar is pretty long, so we need to set up a trampoline. When you use the JMP command you can only move 128 lines in either direction, and because the code is so long we need to use a JSR to "bookmark" our position so we can find our way back, to put it simply.
Step 2:
Replace line 31 with the following code:
Code:
JSR drawSpriteHUDTrampoline
JMP pastDrawSpriteHUDTrampoline
drawSpriteHUDTrampoline:
JMP checkForSpriteHUDDraw
.include SCR_SPRITE_PREDRAW
pastDrawSpriteHUDTrampoline:
Save that and we are done with HandleUpdateObjects.asm
Step 3:
Go into NESmaker. Go to Project>Project Settings, then click on the Script Settings tab. Find which file is defined for "Handle Sprite Pre-Draw." Here is an image of what it looks like for me, but keep in mind that I have it pointing to a different location than the default. I think the default was in the Adventure module scripts.
Step 4:
Now that you know which file we're going to be working in, go ahead and open it up. If you're using 4.0.11, then this file is probably all commented out. We can add our code underneath it.
What we are doing here
First we're checking to see if the game is in the main game state. Then if it is, we go on to make draw the 4 sprites for the caps at the top and bottom of the bars. Next we check if the player is still alive because weird things happen after we die. And finally call my macros for drawing a status bar.
Here is the code for this step. The main thing you need to know is on the DrawSprite lines, you need to determine what values you want for your game. In particular, the 3rd argument determines which graphic it will use. If you put #$00, it will use the top left graphic of your game objects sheet. If you use #$FF, I will uses the bottom right graphic whatever monster sheet is currently loaded in game. Just remember the first digit represents the column of the graphic in your sheet, and the second digit represents the row. I had my graphics aroung the lower right of my game objects sheet.
Code:
checkForSpriteHUDDraw:
LDA gameState ; We want only want to make the HUD in the main gameState
BNE drawingSpriteHud
RTS
drawingSpriteHud:
;; Draws a cap sprite at the top of the health bar.
DrawSprite #$08, #$10, #$1F, #%00000001, spriteOffset
inc spriteOffset
inc spriteOffset
inc spriteOffset
inc spriteOffset
;; Draws a cap sprite at the top of the magic bar.
DrawSprite #$10, #$10, #$6E, #%00000000, spriteOffset
inc spriteOffset
inc spriteOffset
inc spriteOffset
inc spriteOffset
;; Draws a cap sprite at the bottom of the health bar.
DrawSprite #$08, #$58, #$7E, #%00000001, spriteOffset
inc spriteOffset
inc spriteOffset
inc spriteOffset
inc spriteOffset
;; Draws a cap sprite at the bottom of the magic bar.
DrawSprite #$10, #$58, #$7F, #%00000001, spriteOffset
inc spriteOffset
inc spriteOffset
inc spriteOffset
inc spriteOffset
LDX player1_object ; When the player is alive, player1_object == #$00, but
BEQ thePlayerIsAlive ; when dead, player1_object == #$FF, so we need to ensure
LDA #$00 ; #$00 gets passed in for the health value when the player
STA temp3 ; when the player is dead.
JMP letsGetThisHUDStarted
thePlayerIsAlive:
LDA Object_health,x
STA temp3
letsGetThisHUDStarted:
;; Draw the Health Bar
DrawStatusBar #$08, #$18, temp3, #$2F, #$3F, #$4F, #$5F, #$6F, #%00000001
;; Draw the Magic Bar
DrawStatusBar #$10, #$18, #$6A, #$2F, #$3F, #$4F, #$5F, #$6F, #%00000000
RTS
Final Step:
This is a whopping huge macro, but it makes it easier to draw both a health bar and a magic bar, and any other bars you may feel inclined to include in your game. I left a comment at the top of the macro to explain what each argument does, so check that out. If you want to know how it works, just check out the High-Level Explanation section above. You can either put this code in the Macros.asm file in Routines/System, or do what I did and make my own Macro file. If you do the latter, be sure to include you file in around where Macros.asm gets included in MainASM.asm around line 8.
Code:
MACRO DrawStatusBar arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8
;; arg0 x position of bottom bar segment
;; arg1 y position of bottom bar segment
;; arg2 value bar is displaying
;; arg3 location of empty graphic
;; arg4 location of 1/4 graphic
;; arg5 location of 1/2 graphic
;; arg6 location of 3/4 graphic
;; arg7 location of full graphic
;; arg8 attribute data (bits 0 and 1 determine which palette is used)
LDA #$00
STA temp2 ;Number of full sprites
STA temp1 ;if there is a partial sprite and what is it
LDA #$08
STA temp ;Number of empty sprites
LDA arg2
BEQ readyToDrawHealthBar
AND #%11111000
CMP #%11111000
BNE checkForHalf
;; Full
LDA #$08
STA temp2
JMP drawHealthBar
checkForHalf:
LDA arg2
AND #%10000000
BEQ checkForQuarter
;;; Half Full
LDA #$04
STA temp2
checkForQuarter:
LDA arg2
AND #%01000000
BEQ checkForEighth
;;; Quarter full
INC temp2
INC temp2
checkForEighth:
LDA arg2
AND #%00100000
BEQ checkForOneMore
;;; Eighth full
INC temp2
checkForOneMore:
LDA arg2
AND #%00011000
CMP #%00011000
BNE checkForPartialThreeQuarters
INC temp2
JMP drawHealthBar
checkForPartialThreeQuarters:
LDA arg2
AND #%00010000
BEQ checkForPartialHalf
;; Partial is 3/4 full
LDA arg6
STA temp1
JMP drawHealthBar
checkForPartialHalf:
LDA arg2
AND #%00001000
BEQ checkForPartialQuarter
;; Partial is half full
LDA arg5
STA temp1
JMP drawHealthBar
checkForPartialQuarter:
LDA arg2
AND #%00000111
BEQ drawHealthBar
;; Partial is quarter full
LDA arg4
STA temp1
drawHealthBar:
LDA temp
SEC
SBC temp2 ;Number of full sprites
STA temp ;Number of empty sprites
BEQ readyToDrawHealthBar ;If we only have full sprites, then we can go ahead and draw
LDA temp1
BEQ readyToDrawHealthBar ;Does an empty sprite need to be replaced with a partial sprite?
DEC temp
readyToDrawHealthBar:
LDX temp
LDA arg1
STA temp ;Now temp is the y position of drawing from bottom up
emptySpritesLoop:
TXA
BEQ partialSpriteDraw
DrawSprite arg0, temp, arg3, arg8, spriteOffset
inc spriteOffset
inc spriteOffset
inc spriteOffset
inc spriteOffset
LDA temp
CLC
ADC #$08
STA temp
DEX
JMP emptySpritesLoop
partialSpriteDraw:
LDA temp1
BEQ readyToDrawEmptyBars
DrawSprite arg0, temp, temp1, arg8, spriteOffset
inc spriteOffset
inc spriteOffset
inc spriteOffset
inc spriteOffset
LDA temp
CLC
ADC #$08
STA temp
readyToDrawEmptyBars:
LDX temp2 ;Loaded into X for iteration
fullSpritesLoop:
TXA ;8
BEQ doneWithHealthBar
DrawSprite arg0, temp, arg7, arg8, spriteOffset
inc spriteOffset
inc spriteOffset
inc spriteOffset
inc spriteOffset
LDA temp
CLC
ADC #$08
STA temp
DEX
JMP fullSpritesLoop
doneWithHealthBar:
ENDM
And that's it! I hope I didn't make too many typos writing up this post. At some point I will probably modify this macro to have a variable height so that the health bar can be expanded after getting items, but I probably won't make it handle more than 8 segment because I don't want to waste sprites. Instead I will probably make the health bar start short an then work up to 8 sprites tall. But for now I am eager to get back to programming mechanics for my game.