Hide Behind Tile [4.5.6]

voltopt

Member
I've posted elsewhere about this, but now that the full modules are out with tutorials, I wanted to see if anyone had figured this one out.

In 4.1.5 the following code is included with the modules. It flips the priority bit to send the object behind the background (bit 6, 00100000)

Code:
;; walk behind tile
;; this will push the entire sprite of an object behind the background,
;; so it is best used in conjunction with an "open door" that is transparent.

	LDA Object_state_flags,x
	ORA #%00100000
	STA Object_state_flags,x

In 4.5.6, my best guess is that Object_state_flags are replaced with ObjectUpdateByte. A sample of code:

SolidTile.asm flips the first bit to make solid:

Code:
;;solid tile

LDA ObjectUpdateByte
ORA #%00000001
STA ObjectUpdateByte ;; makes solid
rts

So, in 4.5.6 this should work - it complies, and i create the tiletype and update collisions, but nothing happens

Code:
LDA ObjectUpdateByte
ORA #%00100000
STA ObjectUpdateByte ;; makes tile foreground
rts
 

Pauldalyjr

Active member
If you want a tile you walk behind completely, make it change the action state of the player to one with no graphics. The player will disappear when they touch it. There is code in the forums to hide behind all but the transparent color on a tile also, but I don't know if it works in 4.5.
 

voltopt

Member
That's an interesting idea regarding action state.

I'm trying to adapt that forum code to 4.5 but it hasn't worked quite yet.
 

Pauldalyjr

Active member
Check this out, I think there are some good clues in there to do what you are looking for...

http://nesmakers.com/viewtopic.php?f=40&t=5353
 

voltopt

Member
Ha using the byte here with AND instead of ORA works, but the player, once gone, is gone forever lol
 

AllDarnDavey

Active member
I looked into this a bit myself. My unfinished 4.1.5 design used WalkBehindTiles for pseudo stealth game play. Enemies would chase the player if they were within range, but only if they were not hidden behind a bush or column or tree or underground.

I think you're in the right area, according to the NESdev website sprite priority sets if the sprite is drawn in front of background tile, or behind them and in front of the clear color. This priority bit is bit 5, and setting it to 1 = behind background tiles.

It doesn't look like your attempt is changing the sprite bit, but the tile attribute bits. We call them WalkBehindTiles, but they don't actually change any attributes of how the background tile is drawn, they change the priority of sprites that touch them instead to push them behind the background.

The new code should look very similar to the old code, load the objects attribute bits, flip bit 5 to 1 and save it... we just need to figure out what these attributes are named now, because "Object_state_flags" isn't it.
 

voltopt

Member
Apparently neither are the many Object_ code I found in Object_RAM; I've tried almost all of them and nothing has happened. I've also poked around the Zero Page and Overflow, and none of the variables seem to point to this.

However, under System\Object Behaviors\ there is a blank script -
Code:
SCR_CHANGE_SPRITE_PRIORITY EQU "Routines\BASE_4_5\System\ObjectBehaviors\oChangeSpritePriority.asm"

I'm guessing these write upon compile? Either way, it doesn't get us any closer.
 

AllDarnDavey

Active member
I found the draw sprite loop in doDrawSprites.asm at about line 250.
I can add an ORA #%00100000 line like below to force all sprites to draw behind.
But I'm having the hardest time reading through the doDrawSprites code to find where it's pulling those attribute values from, so I can figure out how to alter them before they get sent off to be drawn. It's a mess of temp variables and hard to follow instructions, but I know I'm getting close.

Code:
doDrawSpritesLoop:
	
			LDA (temp16),y
			clc
			ADC temp3
			STA tempC ;; the calculated table position, with offest.
						;; tempC becomes the "tile to draw".
			INY
			LDA (temp16),y 
			ORA #%00100000
			STA tempD ;; the next value is the attribute to draw.
			INY 		;; increasing again sets us up for the next sprite.
						;; now we can use tempA-D to draw our sprite using the macro.
						
								
			DrawSprite tempA, tempB, tempC, tempD
 

voltopt

Member
So close! I found the similar code under macros using gamehandler to hide all sprites, I added it to the tiletype and it shuts the whole sprite down (the sprite in question is 16x32 and the tile is 16x16, but as soon as there is a collision the entire sprite disappears)

Code:
	LDA gameHandler
	ORA #%01000000
	STA gameHandler

ShowSprites.asm brings them back, I suppose you could add code to the blank tile to turn sprites back on but that seems clunky, and all sprites would likely turn off anyway, I think...

Code:
	LDA gameHandler
	AND #%10111111
	STA gameHandler

This isn't the priority bit or the right way but shows how deep we are digging, Between this and palette cycling (which I can't get the code we figured out to work for adventure, arcade platform, or scrolling platform)
 

voltopt

Member
temp16 first shows up at line 138

Code:
		LDA ObjectAnimationSpeedTableLo,y
		STA temp16
		LDA ObjectAnimationSpeedTableHi,y
		STA temp16+1
		
		
		
		LDY temp1 ;; the current action number.

		LDA (temp16),y ;; this now points to the proper spot on the anim speed table.
						;; we need to capture that number.
						;; Its high nibble is the "animation type"


And again at line 211

Code:
		LDA (tempPointer_lo),y
		STA temp16
		LDA (tempPointer_hi),y
		STA temp16+1
		
		
		LDY #$00
		LDA (temp16),y
		STA animationFrameHolder ;; holds the total number of animation frames for this type of animation.
 

voltopt

Member
Actually looks like you are flipping tempD - which is Object_type,x I think

Line 31:

Code:
	LDA Object_type,x
	STA tempD
 

voltopt

Member
But I just tried it, and it did what one would expect - freaked out and rapidly cycled through exploding versions of different objects, lol.
 
I look back in 4.1.5 to see how this work. This is from in Routines\Basic\ModuleScripts\MainScripts\DrawSprites.asm I spilt into two codes, so you can find white font ;;;; FIND OUT IF THIS SPRITE SHOULD BE DRAWN BEHIND THE BACKGROUND easier.

Code:
HandleDrawingSprites:
	;;; HANDLE HURT FLICKER
	;;;;; if you don't want your objects to flicker when hurt, delete this block
	;;; also look down for another block when drawing y value
	LDA Object_status,x
	BNE objectNotDeactivated
	RTS
objectNotDeactivated:
	
	LDA Object_status,x
	AND #HURT_STATUS_MASK
	BEQ ignoreHurtFlicker
	LDA vBlankTimer
	AND #%00000001
	BEQ ignoreHurtFlicker
	LDA #$01
	STA DrawFlags ;; if DrawFlags is 1, do flicker.  If 0, do not.
	JMP gotFlickerValue
ignoreHurtFlicker:
	LDA #$00
	STA DrawFlags
gotFlickerValue:
	;;;;;;;;;;;;;;;;;;;;;;;;;;;
	;;; END HANDLE HURT FLICKER
	
	
	;; still at the *object* level right now.
	LDA Object_type,x
	CMP #$10
	BCC dontAddTilesetOffset
	;; this offset is added for monsters.
	LDA #$80
	STA tileset_offset
	JMP gotTilesetOffset
dontAddTilesetOffset:
	LDA #$00
	STA tileset_offset
gotTilesetOffset:
	LDA gameHandler
	AND #%10000000 ;; is updating sprites turned on
	BNE skipGettingStartingOffset
	RTS ;; draw no sprites
skipGettingStartingOffset:
	LDY Object_type,x
	LDA ObjectSize,y
	AND #%00000111
	STA tempy ;; is good for height
	LDA ObjectSize,y
	LSR
	LSR
	LSR 
	STA tempx ;; is good for width.
	STA tempz ;; double up on this one, because we'll need to restore it
			;; and won't have access to tempx anymore
			
	;; we also need to know how far beyond our offset we should be.
	;; this can be calculated by the number of ((tiles x 2) x animation offset).  We can find it with a quick loop.	
	LDA #$00
	STA sprite_tileOffset
	LDA Object_animation_frame,x
	BEQ noNeedToFactorForOffset ;; if it is zero, no need for an offset.
	STA temp2

	LDY Object_type,x
GetFrameOffset:
	LDA Object_total_sprites,y
	ASL
	CLC
	ADC sprite_tileOffset
	STA sprite_tileOffset
	DEC temp2
	LDA temp2
	BNE GetFrameOffset

;;;now temp should contain the offset.
noNeedToFactorForOffset:

	
	
	
	;; we want to cycle forward through the sprites if odd frame, backwards through the sprites if even frame?
	;; first, we need to determine where our starting position is:
	LDA Object_movement,x
	AND #%00000111 ;; this is where we'll store direction
	STA temp
	;;;;;multiply that by 8 (directions) to get the starting point of pointers.
	;; so we have to do a quick look up of the anim speed table.
	;; bits 7654 represent the offset, while 3210 represent the animation speed.
	LDA Object_animation_offset_speed,x
	LSR
	AND #%11111000
	CLC 
	ADC temp
	;;; effectively, we're shifting the offset 4 bits,
	;; but then multiplying it by 8 since each "action offset" is 8 values (one for each direction)
	;; so this is a shortcut.  
	
	TAY
	
	
	
	
	LDA Object_x_hi,x
	SEC
	SBC xScroll
	STA spriteDrawLeft
	
	LDA Object_table_lo_lo,x
	STA temp16
	LDA Object_table_lo_hi,x
	STA temp16+1
	LDA (temp16),y

	STA sprite_pointer
	
	LDA Object_table_hi_lo,x
	STA temp16
	LDA Object_table_hi_hi,x
	STA temp16+1
	LDA (temp16),y
	STA sprite_pointer+1
	

	
	;;;now we have the right grouping for the right direction for the right object.

	;; now, should be able to run right through the values pretty easily.
	;; we're going to pick up wherever the last object left off drawing
	LDA Object_x_hi,x
	SEC
	SBC xScroll
	STA temp1 ;; this will be used to keep track of horizontal placement, increasing by 8 and 
				;; decreaseing tempx to see if we're out of horizontal spacing.
	LDA Object_y_hi,x
	sec
	sbc #$01
	STA temp2	;; this will be used to keep track of vertical placement, increasing by 8 and
				;; decreasing tempy to see if we're out of vertical spacing (ie-done).

;;;; FIND OUT IF THIS SPRITE SHOULD BE DRAWN BEHIND THE BACKGROUND
Code:
LDA Object_state_flags,x
	AND #%00100000
	 STA temp3  ;; holds whether or not this object's sprite is drawn behind backgrounds.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	TXA
	PHA

	LDY #$00
	LDA (sprite_pointer),y
	STA objectFrameCount
	
	LDX spriteOffset 
	LDA sprite_tileOffset  
	CLC 
	ADC #$01
	TAY
DrawSpritesForThisObjectLoop:
	LDA DrawFlags ;; are we flickering?
	BEQ noFlicker
	LDA #$fe
	JMP drawTileOffScreen_flicker
noFlicker:
	LDA temp2 ;; LDA the y value, not drawn off screen
drawTileOffScreen_flicker:
	STA SpriteRam,x ;; store it to the y value byte for this sprite
	INX ;; increase the index to draw to
	LDA (sprite_pointer),y  ;; load the table pointer to the tile number
	CLC
	ADC tileset_offset

	STA SpriteRam,x ;; store to the tile index
	INX ;; increase index
	INY ;; increase the position to read in the table (which alternates tile/attribute)
	LDA temp3
	ORA (sprite_pointer),y ;; load the table pointer to the attribute
	STA SpriteRam,x ;; store it to the attribute index
	INY ;; increase the position to read in the table...this next value is for the next sprite.
	INX ;; increase index
	LdA temp1 ;; load the x value
	STA SpriteRam,x ;; store to the x index
	;;=======================================
	;;;; DONE WITH THIS SPRITE.
	INX  ;; increase index to get ready to draw the next sprite.
	
	DEC tempx ;; decrease the variable holding the width.
	BEQ doneWithSpriteColumns ;; if it is at zero, that means the column is over
	;;; more sprites to draw in this column.
	
	;;;;;;;;;; increase the x value to the next 'column' to draw the next sprite.
		;;;; conceivably, this is where we could put a horizontal offset. 
	LDA temp1 
	CLC
	ADC #$08   ;; each tile is 8 wide.
	STA temp1
	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	JMP DrawSpritesForThisObjectLoop
doneWithSpriteColumns:
	DEC tempy ;; decrease the variable holding height
	BEQ doneDrawingThisObjectsSprites ;; if there is no horizontal, and no vertical tiles left to count
										; then this sprite is done being drawn
	;;; there are still sprites to draw here
	;;; so now, we must:
	LDA tempz
	STA tempx ;; restore the temp size of the column
	;;======= increase the y value by 8px / one tile
	LDA temp2 
	CLC
	ADC #$08
	STA temp2 
	;;==============================================
	;and
	;;======= move horizontal position back all the way to the left
	LDA spriteDrawLeft
	STA temp1
	JMP DrawSpritesForThisObjectLoop
	
doneDrawingThisObjectsSprites:
	TXA
	STA spriteOffset
	;; restore x so we're talking about the right object
	PLA
	TAX
	
	
	JSR getNewEndType
	;;;Handle This object's animation:
	;;; fist, count down the animation timer.
	DEC Object_animation_timer,x
	LDA Object_animation_timer,x
	BNE notAtEndOfFrame
	;; is at end of frame.
	LDA Object_animation_frame,x
	CLC
	ADC #$01
	CMP objectFrameCount
	BNE notTheLastFrameYet
	;; this is the last frame
	;;=========================== check for end animation
	LDA Object_end_action,x
	
	LSR
	LSR
	LSR
	LSR

	BNE checkForEndAnimType

	;;; looping type animation - just reset the frame and continue on.
	LDA #$00
	JMP notTheLastFrameYet 
checkForEndAnimType:
	JSR doMoreThanResetTimer ;; this is in handle update objects
								;;uses the same table as 
								;;action timer end
	LDA #$00 ;; to reset frames.
	;;===================================================
notTheLastFrameYet:
	STA Object_animation_frame,x
		LDA Object_animation_offset_speed,x
		;;; set the initial animation timer
		AND #%00001111 ;; now we have the animation speed value displayed in the tool
						;; are 16 values enough for the slowest?  Or do we want to have multiples?  Or maybe a table read?	
	
		;ASL
		;ASL
		ASL
		STA Object_animation_timer,x

	
notAtEndOfFrame:
	

	
	
	RTS
 

voltopt

Member
This has pushed me to the files in ScreenData - not to change them, but to read through them. I know this is compiled code NESmaker spits out and is something we have to affect at the subroutine level. At compile, it looks like all of the addresses are written here, and the bits set for each 8x8 tile in each object. How to flip the bit? How to get from the tiletype, to the drawSprites script, to impact the screen data tables?

Here's an example:
GameItem0 is object 0, in this case the player object

Code:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; 0 GameItem: Player-Claudia
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Monster = Player-Claudia
;; Size = 2 by4
;

GameItem0_0 is object 0 animation 0, each column shows an 8x8 tile, I think, and its attributes. For instance, since this player is 16x32, there are 8 spots.
Each row is a frame of animation, so with 8 frames there are 8 rows

Code:
; Animation = StandingDown
GameItem0_0:
.db #$8; frame count
.db #$42, #%00000000, #$43, #%00000000, #$52, #%00000000, #$53, #%00000000, #$62, #%00000001, #$63, #%00000001, #$72, #%00000001, #$73, #%00000001
.db #$44, #%00000000, #$45, #%00000000, #$54, #%00000000, #$55, #%00000000, #$64, #%00000001, #$65, #%00000001, #$74, #%00000001, #$75, #%00000001
.db #$07, #%00000000, #$08, #%00000000, #$17, #%00000000, #$18, #%00000000, #$27, #%00000001, #$28, #%00000001, #$37, #%00000001, #$38, #%00000001
.db #$05, #%00000000, #$06, #%00000000, #$15, #%00000000, #$16, #%00000000, #$25, #%00000001, #$26, #%00000001, #$36, #%01000001, #$35, #%01000001
.db #$00, #%00000000, #$01, #%00000000, #$10, #%00000000, #$11, #%00000000, #$20, #%00000001, #$21, #%00000001, #$31, #%01000001, #$30, #%01000001
.db #$05, #%00000000, #$06, #%00000000, #$15, #%00000000, #$16, #%00000000, #$25, #%00000001, #$26, #%00000001, #$36, #%01000001, #$35, #%01000001
.db #$46, #%00000000, #$47, #%00000000, #$56, #%00000000, #$57, #%00000000, #$66, #%00000001, #$67, #%00000001, #$76, #%00000001, #$76, #%01000001
.db #$44, #%00000000, #$45, #%00000000, #$54, #%00000000, #$55, #%00000000, #$64, #%00000001, #$65, #%00000001, #$74, #%00000001, #$75, #%00000001

GameItem0_1 is the same 16x32 size but only 1 frame, so 8 columns and 1 row.

Code:
;; Animation = WalkingDown
GameItem0_1:
.db #$1; frame count
.db #$00, #%00000000, #$00, #%00000000, #$00, #%00000000, #$00, #%00000000, #$00, #%00000000, #$00, #%00000000, #$00, #%00000000, #$00, #%00000000

Here, GameItem 2 is 8x8 and 1 frame, so it's one column and one row.

Code:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; 2 GameItem: Attack
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Monster = Attack
;; Size = 1 by1
;; Animation = AttackRight
GameItem2_0:
.db #$1; frame count
.db #$24, #%00000001
;; Animation = AttackLeft
GameItem2_1:
.db #$1; frame count
.db #$24, #%01000001

Anyway, this is compiled code and should not /can't be edited, but how to get the compile to include the flipped fifth bit? We are getting closer. We need to figure how to get from the tiletype which flips the bit in a variable, to the drawSprites script which flips the bit on the screen for compile I think....
 

AllDarnDavey

Active member
So it looks like the old sprite loop did a flag check for draw behind. The current 4.5.6 codebase does not do this check.
I did a search all through the code for Object_flags checks in the 4.5.6 code, and found this:
Code:
Object_flags,x
#%00000100
  ||||||||
  |||||||+-
  ||||||+-- 1 = Player or Collisions Maybe?
  |||||+--- 1 = Weapon
  ||||+---- 1 = Monster
  |||+----- 1 = Monster Weapon?
  ||+------ 1 = Powerup?
  |+-------
  +-------- 1 = Object is active
Best I can decipher the bit checks used so far. There's a few that don't appear to be used yet. We'd probably have to make a tile priority flip one of these unused flags, and then add a check in the sprite update loop.

Not sure if this check should go in doDrawSprites or if it would be better in doSpritePreDraw.
 

voltopt

Member
Is PreDraw executed each Vblank? Or is it for objects that will not be on screen at first, or are offscreen?
 

AllDarnDavey

Active member
So I almost got it working...
I changed doDrawSprites draw loop aroud 250 to this:
Code:
	doDrawSpritesLoop:
			LDA (temp16),y
			clc
			ADC temp3
			STA tempC ;; the calculated table position, with offest.
						;; tempC becomes the "tile to draw".
			INY
			LDA Object_flags,x
			AND #%01000000
			BNE +noPriFlip
				LDA (temp16),y			
				JMP +doneFlip
			+noPriFlip
			LDA (temp16),y
			ORA #%00100000
			+doneFlip
			STA tempD ;; the next value is the attribute to draw.
			INY 		;; increasing again sets us up for the next sprite.
						;; now we can use tempA-D to draw our sprite using the macro.
						
								
			DrawSprite tempA, tempB, tempC, tempD

My WalkBehind tile script is this:
Code:
LDA Object_flags,x
ORA #%01000000
STA Object_flags,x
RTS

It does indeed change the player and monsters to behind priority when coming into contact with the tile... problem is, then stay that way and don't change back afterwards. I guess we could make other tile scripts flip that bit the other way, but there's probably a better way to do this.
 

AllDarnDavey

Active member
Hmm... looks like it doesn't execute any tile code if the tile it hits is tile00 (at least not in the maze module I'm testing this in)... I found a similar thing in the ArcadePlatform module, some of the tile stuff is hard coded.

The flip priority back to front null script does work...
Code:
LDA Object_flags,x
AND #%10111111
STA Object_flags,x
RTS

It just cannot be set as TileCollision00 or TileCollision01 ... module expects those to be null and solid. I need to see if I can clean it up a bit, things like the coin pickup auto change to TileCollision00 after pickup, so it would be nice if I can find where TileCollision00 behavior is hard coded, and just add the priority flip there. But it's more or less working.
walkBehind.gif
 

voltopt

Member
Sweet! So we can either create a new null tile with this code for screens with a walk behind tile, or find the hard code, which seems cleaner.
 
Top Bottom