Mega Man-style Sprite HUD

User avatar
jorotroid
Posts: 234
Joined: Wed Aug 08, 2018 7:48 pm
Location: California
Contact:

Mega Man-style Sprite HUD

Post by jorotroid » Thu Sep 13, 2018 9:25 am

Image

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.
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: Select all

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: 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
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: Select all

.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: Select all

	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.
Image

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: 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
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: 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
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.
User avatar
WolfMerrik
Posts: 199
Joined: Sat Aug 11, 2018 4:59 pm
Location: Maine
Contact:

Re: Mega Man-style Sprite HUD

Post by WolfMerrik » Thu Sep 13, 2018 11:23 am

Incredible work on this one!
Thanks so much for sharing!
ImageWant to download & share Image NESMaker Resources? Check out My Files on www.NESMakerFiles.com Image
User avatar
Convoy_Avenger
Posts: 101
Joined: Tue Aug 14, 2018 1:42 am

Re: Mega Man-style Sprite HUD

Post by Convoy_Avenger » Thu Sep 13, 2018 12:10 pm

I will definitely be using this, and definitely will be trying to make it start small and grow larger(And probably failing a bunch before giving up, then someone else will have the answer, so it all works out in the end).
User avatar
chronosv2
Posts: 255
Joined: Wed Aug 08, 2018 9:30 pm
Location: KY, USA
Contact:

Re: Mega Man-style Sprite HUD

Post by chronosv2 » Thu Sep 13, 2018 12:17 pm

This is absolutely amazing. Thanks for sharing!
ASM/C# Coder -- Computer + Coffee = Code
ASM Tutorial Series
My NES development projects have, for the time being, stagnated. I apologize for the inconvenience.
User avatar
dale_coop
Posts: 4476
Joined: Fri Feb 16, 2018 7:05 am
Location: France

Re: Mega Man-style Sprite HUD

Post by dale_coop » Thu Sep 13, 2018 12:28 pm

Awesome, jorotroid. Thank you <3
-----
Sorry about my poor english
All I need: A Damn Fine Cup of Coffee
My games: PRESS START GAME / UNDERGROUND ADVENTURE / UNDERGROUND ADVENTURE (Arcade version - Byte-Off-2019)
User avatar
Mihoshi20
Posts: 492
Joined: Tue Mar 06, 2018 11:47 pm

Re: Mega Man-style Sprite HUD

Post by Mihoshi20 » Thu Sep 13, 2018 3:56 pm

I like it a lot. This'll come in handy for a major project of mine down the road. What GPL does this fall under so I can make proper attribution/contribution if I use it.
User avatar
Scythe&GenGames
Posts: 88
Joined: Thu Mar 08, 2018 10:20 pm
Contact:

Re: Mega Man-style Sprite HUD

Post by Scythe&GenGames » Thu Sep 13, 2018 8:31 pm

This will bring the Megaman X Style if I mod this to use 8-bit styles of the Megaman X HUD and my game be somewhat megaman styled - if I do it right.
User avatar
jorotroid
Posts: 234
Joined: Wed Aug 08, 2018 7:48 pm
Location: California
Contact:

Re: Mega Man-style Sprite HUD

Post by jorotroid » Fri Sep 14, 2018 1:11 am

Thanks everyone!
Mihoshi20 wrote:
Thu Sep 13, 2018 3:56 pm
I like it a lot. This'll come in handy for a major project of mine down the road. What GPL does this fall under so I can make proper attribution/contribution if I use it.
Oh, I totally did not think that far ahead. Basically if you feel this warrants crediting, I'd be honored to be mentioned and you can list me as Joe Rossi. But I'm also not going to comb through your credits to make sure you credited me.

Convoy_Avenger wrote:
Thu Sep 13, 2018 12:10 pm
I will definitely be using this, and definitely will be trying to make it start small and grow larger(And probably failing a bunch before giving up, then someone else will have the answer, so it all works out in the end).
Awesome! I'll definitely be interested to see what you come up with. I have some ideas about how I'll go about it, but I'm very anxious to get back to programming mechanics and the health bar can be a later issue for me. Especially considering I barely have enemies at the moment.
User avatar
Scythe&GenGames
Posts: 88
Joined: Thu Mar 08, 2018 10:20 pm
Contact:

Re: Mega Man-style Sprite HUD

Post by Scythe&GenGames » Mon Oct 29, 2018 4:39 am

I'm having the hardest time making this work....I want to make a copy of the HandleUpdateObjects.asm and replace what's needed and create a copy of it so it doesn't clash with the other games of mine and when exported it threw out label already defined error..... I'm like what!?!

I feel I need copies of these so it works as I tried to follow this code.
User avatar
jorotroid
Posts: 234
Joined: Wed Aug 08, 2018 7:48 pm
Location: California
Contact:

Re: Mega Man-style Sprite HUD

Post by jorotroid » Mon Oct 29, 2018 9:08 am

"Label already defined" means that you have made two or more labels with the same name. The console will tell you which line the error happened on, so you can find out with label it was and then search for where it has been repeated.
Post Reply