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.
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
-Uses way less memory than the built in HUD to give you more room to work with other things.
-Gives an alternative look.
-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!
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: Select all
Bit number: 76543210 Added value: |||||||+--- 1 ||||||+---- 2 |||||+----- 4 ||||+------ 8 |||+------- 16 ||+-------- 32 |+--------- 64 +---------- 128
Code: Select all
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
How to Hook it Up
Open HandleUpdateObjects.asm which is in Routines/System. At line 31 you should see this line of code.
Code: Select all
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.
Replace line 31 with the following code:
Code: Select all
JSR drawSpriteHUDTrampoline JMP pastDrawSpriteHUDTrampoline drawSpriteHUDTrampoline: JMP checkForSpriteHUDDraw .include SCR_SPRITE_PREDRAW pastDrawSpriteHUDTrampoline:
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.
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: Select all
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
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: Select all
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