[4.5.6] Sprite Cycling (flicker)

AllDarnDavey

Active member
flicker456.gif

I highly recommend you watch the Advanced Brawler Tutorial as it explains ordering sprites and is the basis for how this works. We're just going to tweak it to either sort objects by their Y position, or just constantly adjust their order to add flicker when going over the max number of sprites per scanline.

First, just like in the Adv. Brawler Tut we'll need a few variables.
SpriteOrderVariables.PNG
In ZeroPageRAM, make sure you have drawOrder & drawOrderTemp. drawOrder should be the same number as the amount of max objects you have set in Object Variables. drawOrderTemp should just equal 1.

Also, just like in the Adv. Brawler Tut, you'll need to add these lines somewhere in your project's initialization script.
Code:
	LDA #$00
	STA drawOrder
	LDA #$01
	STA drawOrder+1
	LDA #$02
	STA drawOrder+2	
	LDA #$03
	STA drawOrder+3
	LDA #$04
	STA drawOrder+4	
	LDA #$05
	STA drawOrder+5	
	LDA #$06
	STA drawOrder+6
	LDA #$07
	STA drawOrder+7
	LDA #$08
	STA drawOrder+8	
	LDA #$09
	STA drawOrder+9	
	LDA #$0A
	STA drawOrder+10
	LDA #$0B
	STA drawOrder+11
	LDA #$0C
	STA drawOrder+12

You should have a line added to your initialization for the number of Max Objects you have minus 1 (because we start at 0, 13 objects goes up to drawOrder+12).

Make a new file called doHandleObjects_cycle.asm, and assign it in your script settings. Use this code in the file:
Code:
	doHandleObjects:
	LDA gameStatusByte
	AND #%00000001 ;;; this will skip object handling.
	BEQ dontSkipObjectHandling
		RTS
dontSkipObjectHandling:
	LDA gameHandler
	AND #%10000000
	BEQ dontSkipObjectHandling2
		;;; this was in the middle of a screen transition
		RTS
dontSkipObjectHandling2

	;;;; RESET ANY VARIABLES THAT NEED RESET BEFORE EVALUATION
	LDA npcTrigger
	AND #%11111110
	STA npcTrigger
	;;;; Create state
	LDA #$00
	STA drawOrderTemp
		;;; update the draw order
		JSR UpdateDrawOrder
	LDX #$00
		
	loop_doHandleObjectsLoop:
		LDA drawOrder,x
		TAX
		
		LDA Object_status,x
		AND #%11000000
		BNE objectHasActivity
			;;;; this object is not being created
			;;;; and is not active.
			JMP doObjectIsInactive
			
		objectHasActivity:
			LDA Object_status,x
			AND #OBJECT_QUEUED_FOR_DESTRUCTION
			BEQ doNotDestroyThisObject
				;;;; Destroy 
				.include SCR_DESTROY_STATE
				LDA #$00
				STA Object_status,x
				JMP doObjectIsInactive ;; destroying will automatically set this object to inactive.
				;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
				;;;;;;;;END OF THE LINE FOR THIS OBJECT ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
			
		doNotDestroyThisObject:
		
			LDA Object_status,x
			AND #OBJECT_IS_ACTIVE
			BEQ checkForObjectActivation
				JMP doActiveObject
			checkForObjectActivation:
			
				LDA Object_status,x
				AND #OBJECT_QUEUED_FOR_ACTIVATION
				BNE doCreateThisNewObjectd
					JMP doObjectIsInactive
				doCreateThisNewObjectd:
			;;; create the new object.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Here, we create a new object.  It will exist upon the next frame.
					.include SCR_CREATE_STATE
					
					JSR doHandleCreateState
					SwitchBank #$1C
						LDY Object_type,x
						LDA ObjectFlags,y
						STA Object_flags,x
					ReturnBank
					
					JMP doObjectIsInactive
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Below is everything that happens for an active object.
		doActiveObject:
		
			;; ORDER OF OPERATIONS
				; 1) Check to see if this object reads input (status byte).  If not (0), skip input read.
					; 2) We have the states from each button (8 buttons, two controllers) from input management.  	
							; So now we compare it to this object, and whether or not it should
							; have any behaviors associated with it's pressed, down, released, or up states.
				; 3) Switch banks to the one that contains our LUTs for object data (sizes and whatnot)
				; 4) Check to see if this object observes physics (status byte).  If not (0), skip physics.
					
					; 5) Do a basic physics update for this object.
					; 6) Check to see if this object observes tile collisions.  If not (0), skip tile collisions.
							; 7) Check tile collisions.
					; 8) Check to see if this object observes object collisions.  If not (0), skip object collisions.
							; 9) Check object collisions.
					; 10) Check bounds.
				; 11) Update the object's position and behavior based on above.
				; 12) Check to see if this object should be drawn.  If not (0), skip drawing.
					; 13) Draw this object.
				; 14) Return to main bank.

				;; So status byte would be:
				;; #% 7 6 5 4 3 2 1 0
				;; 	  | | | | | | | + - Queued for deactivation (?)
				;; 	  | | | | | | + --- Observes drawing
				;; 	  | | | | | + ----- Observes Object Collisions
				;; 	  | | | | + ------- Observes Tile Collisions
				;; 	  | | | + --------- Observes Physics
				;; 	  | | + -----------	Observes Input
				;; 	  | + ------------- Queued for activation
				;; 	  + --------------- Active
			
			LDA Object_type,x
			STA tempObjType ;; not corrupted by any other routines
							;; used in timer handlings so no reference to bank1c is needed.
			SwitchBank #$18
				LDY Object_type,x
				LDA ObjectReaction,y
				STA EdgeSolidReaction ;; temporarily holds this data.
		
			ReturnBank
					
			LDA Object_status,x
			AND #OBJECT_OBSERVES_INPUT
			BNE ObjectReceivesInput
			JMP ObjectDoesNotRecieveInput
			ObjectReceivesInput:
				;;;; Input state
				.include SCR_INPUT_STATE
				
				
				
					
				
			;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
			ObjectDoesNotRecieveInput:
			;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
			;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

			LDA Object_status,x
			AND #OBJECT_OBSERVES_PHYSICS
			BNE ObjectDoesObservePhysics
			JMP ObjectDoesNotObservePhysics
			ObjectDoesObservePhysics:
				;;;;; SYSTEM PHYSICS
				;; we will use bank #$1C for physics since it has our lut tables in it.
				SwitchBank #$1C
					TXA
					PHA
					.include SCR_HANDLE_PHYSICS
					PLA
					TAX
					;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;DEMO PHYSICS
				ReturnBank
			
			;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
			ObjectDoesNotObservePhysics:
			
			SwitchBank #$18
				JSR doTileObservationLogic
			ReturnBank
			
			LDA Object_status,x
			AND #OBJECT_OBSERVES_OBJECTS
			BNE ObjectDoesObserveObjects
				JMP ObjectDoesNotObserveObjects
			ObjectDoesObserveObjects:
				;; Object collisions
			SwitchBank #$1C
				JSR doObjectCollisions_bank1C
			ReturnBank
		
			;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
			ObjectDoesNotObserveObjects:
			;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
			;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
			
			
			SwitchBank #$18
				;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
				
				;;;: Bounds reached
			;	.include SCR_BOUNDS_STATE - this would be handled inside of physics.  If a potential position
			;								represents the "bounds", this would be triggered.
			
				;;;; Update state
			;; At ths point, physics and positioning are all figured out.
			;; 			now we update the object.  This includes pushing temp positions to new positions,
			;; 			handling timers, doing AI mode updates and whatnot, etc, and anything that should happen
			;;			specifically for this object in the update state.
				TXA
				PHA
				JSR doUpdateState
				JSR doHandleObjectUpdate
				PLA
				TAX
			ReturnBank
			
			SkipUpdateObjectPos:
		
				;;;; Timer
				;.include SCR_TIMER_STATE - timer's are handled inside of update state.
			JustDrawObject:	
			SwitchBank #$1C
				TXA
				PHA
				JSR doUpdateActionTimer
				PLA
				TAX
		;;;;;;;;;
			LDA Object_status,x
			AND #OBJECT_OBSERVES_DRAWING
			BEQ ObjectDoesNotDraw
				;;; Lastly, we draw the sprite.
				;;; The sprite should be drawn anyway, HERE.
				
				TXA
				PHA
				TYA
				PHA
				JSR doDrawSprites
				PLA
				TAY
				PLA
				TAX
				
				JSR doUpdateSpriteTimer
				
				
				;;; And additional object specific drawing stuff would go here. 
				
				.include SCR_DRAW_STATE
			;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
			ObjectDoesNotDraw:
			JSR doUpdateSpriteTimer
			ReturnBank
		doObjectIsInactive:
		;; cycle through objects.
			; CPX camObject
				; BEQ isCamObjectForScrollHandle
					; JMP notCamObjectForScrollHandle
				; isCamObjectForScrollHandle:
					; .include SCR_HANDLE_H_SCROLL
					; .include SCR_HANDLE_V_SCROLL
				; notCamObjectForScrollHandle:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; End Object Update.
	;INX
	INC drawOrderTemp
	LDA drawOrderTemp
	TAX
	CPX #TOTAL_MAX_OBJECTS ;; a number that is configured
							;; in the Object Variables tab
							;; of project settings.
							
							;; This space makes use of ObjectRam.  By default, it is one page in size (256 bytes),
							;; however, it could stretch into the scratch ram if desired allowing for 512 bytes for object use.
	BEQ DoneHandlingObjects
	JMP loop_doHandleObjectsLoop
	
DoneHandlingObjects:
;;;;;;;;;;;;

;;;;;;;;;;;;;;;;;;;;;;;;;;;
	RTS
	
	
;;;;; order objects based on Y value	
;UpdateDrawOrder:
;	;;; sort objects by their Y value
;	LDX #$01
;	OrderLoop:
;		LDY drawOrder,x
;		LDA Object_y_hi,y
;		STA temp
;		LDY drawOrder-1,x
;		LDA Object_y_hi,y
;		CMP temp
;		BCS doneWithSwapObject
;			;;here we swap them
;			LDA drawOrder,x
;			STA drawOrder-1,x
;			TYA
;			STA drawOrder,x
;		doneWithSwapObject:
;			INX 
;			CPX #TOTAL_MAX_OBJECTS
;			BNE OrderLoop
;RTS

;;;; cycle objects flicker, include player
;UpdateDrawOrder:
;	;; start cycle with random number (keep under max object number)
;	;; this helps keep it having 1 object disappear longer then others
;	JSR doGetRandomNumber
;	AND #%00000111
;	ORA #$00000001  ;;make sure we never start at 0
;	TAX
;	OrderLoop:
;		LDA drawOrder,x
;		LDY drawOrder-1,x
;		LDA drawOrder,x
;		STA drawOrder-1,x
;		TYA
;		STA drawOrder,x
;		INX
;		CPX #TOTAL_MAX_OBJECTS
;		BNE OrderLoop
;RTS	

;;;; cycle objects flicker, but ignore player
UpdateDrawOrder:
	;; start cycle with random number (keep under max object number)
	;; this helps keep it having 1 object disappear longer then others
	JSR doGetRandomNumber
	AND #%00000111
	TAX
	OrderLoop:
		LDA drawOrder,x
		LDY drawOrder-1,x
		CPY player1_object ;; if previous is player object, don't swap them.
		BEQ doneWithSwapItem	
		LDA drawOrder,x
		STA drawOrder-1,x
		TYA
		STA drawOrder,x
	doneWithSwapItem:
		INX
		CPX #TOTAL_MAX_OBJECTS
		BNE OrderLoop
RTS

The cycle code is at the very end of the file, and if you look I have 3 versions included (2 are commented out, just uncomment out the one you want, and comment out the other 2 to use).
The 3 versions are
1.No cycle, just order by Y value (same as in the Adv. Brawler Tut)
2.Cycle all objects INCLUDING player.
3.Cycle all objects EXCEPT player.
 

vanderblade

Active member
Hey Davey. I know this is a long shot, but I'm trying to implement a working cycling/flicker in my shooter 4.1.5 project.

I saw your post from 2019 here: http://nesmakers.com/viewtopic.php?t=2455. But the real issue I'm having is projectiles from larger bosses disappearing when the player is on the same horizontal line, and I know you said in that thread that it doesn't impact larger bosses.

Was wondering if you could offer any help.
 

vanderblade

Active member
This is what I am using right now (again, in 4.1.5 in my HandleUpdateObjects.asm.

Code:
UpdateDrawOrder:
;;Now with sprite cycling	
	LDX #$1
OrderLoop:

	LDA drawOrder,x
	LDY drawOrder-1,x
	CPY player1_object ;; if previous is player object, don't swap them.
	BEQ doneWithSwapItem	
	LDA drawOrder,x
	STA drawOrder-1,x
	TYA
	STA drawOrder,x
doneWithSwapItem:
	INX
	CPX #TOTAL_MAX_OBJECTS
	BNE OrderLoop
	RTS

This seems to work on enemy projectiles, BUT ONLY IF I keep firing my player weapon. The moment I stop firing, the enemy projectiles go invisible again (if on the same horizontal line as the player and the larger boss).

So, I think it has something to do with the "Update" part of UpdateDrawOrder, and that this "Update" is somehow linked to my player firing the projectile.
 

AllDarnDavey

Active member
vanderblade said:
This seems to work on enemy projectiles, BUT ONLY IF I keep firing my player weapon. The moment I stop firing, the enemy projectiles go invisible again (if on the same horizontal line as the player and the larger boss).

So, I think it has something to do with the "Update" part of UpdateDrawOrder, and that this "Update" is somehow linked to my player firing the projectile.

Huh... there is nothing in there connected to player projectiles. It should treat all objects except the player as part of the cycle. Unless your enemy projectiles are somehow being created and drawn outside of the HandleUpdateObjects subroutine. You could try using sprite cycle version that doesn't ignore the Player. It will add your player to the flicker, but that will give it more to work with, especially with larger bosses and projectiles.

You could also try adding CutterCross's optimization that ignores drawing any sprite using a blank tile. I know that's meant for 4.5.6, but it could be converted to 4.1.5 as well. Then you could bake some flicker into the projectiles animation (use a blank tile every other frame). Contra does this, only drawing bullets every other frame. But you need something like Cutter's code to ignore drawing blank sprites, or even a blank tile would still count to your sprite limits.
 

weapon121

Member
Nice! Works great, but all my animations are running twice as fast now, LOL. Doubled the speed on them and they are back to "normal".
 

AllDarnDavey

Active member
weapon121 said:
Nice! Works great, but all my animations are running twice as fast now, LOL. Doubled the speed on them and they are back to "normal".

Oops. I think that's from a different change I made to the file beside adding sprite cycling.
Just comment out line 232
Code:
;JSR doUpdateSpriteTimer

By default it doesn't update the animation timer if the object doesn't draw. I had a different code change I made that needed this changed and I just worked around the faster speed, I didn't notice because I made this change a long time ago and forgot about it. I think sprite cycling will still work with this turned back off.
 

vanderblade

Active member
Returning to this for my brawler project. I see we can't have both Y draworder sorting AND flicker activated, right? That's too bad, since the former is so important for immersion with a brawler, while the latter helps when too many sprites appear on screen. Anyway to combine them, or is that too resource intensive?
 

AllDarnDavey

Active member
Returning to this for my brawler project. I see we can't have both Y draworder sorting AND flicker activated, right? That's too bad, since the former is so important for immersion with a brawler, while the latter helps when too many sprites appear on screen. Anyway to combine them, or is that too resource intensive?
I'm sure it's possible, the NES does have fighting games like TMNT that have flicker. It's tricky though, you want to sort front to back but also shift draw order. So far I can think of ways to make things flicker, but not without causing constant sorting issues even when not needed to fix the sprites per scanline issue.
It's not so much resource intensive as it is finding a way to both sort a deck of cards in order, but also kinda shuffle the ones that you know won't show up.
 

CutterCross

Active member
could this help with slowdown on fights with multiple objects? I've been trying to reduce the amount of lag when the bad guys start shooting.
Sprite cycling in this method does practically nothing to counteract slowdowns. It's a workaround for multiplexing more than 8 hardware sprites on each scanline. Without this, extra hardware sprites on that scanline simply don't render.

Actually combatting slowdown requires you to save more CPU cycles through optimization. NESmaker's object system is very bloated CPU-wise, so around 5-6 full-blown objects or so on screen is enough to cause slowdown [also taking into account everything else that takes up a good chunk of CPU cycles, like nametable updates w/ scrolling, etc.]. You'd want to either not use NESmaker objects for some of these elements, and/or optimize how the object system works for your project.
 

9Panzer

Well-known member
@CutterCross rats. I thought that might be an easy fix.
Sounds pretty in depth and out of my reach for the time being. I saw you and everyone else chatting about it in the discord but you guys are on another level lol
 

vanderblade

Active member
Sprite cycling in this method does practically nothing to counteract slowdowns. It's a workaround for multiplexing more than 8 hardware sprites on each scanline. Without this, extra hardware sprites on that scanline simply don't render.

Actually combatting slowdown requires you to save more CPU cycles through optimization. NESmaker's object system is very bloated CPU-wise, so around 5-6 full-blown objects or so on screen is enough to cause slowdown [also taking into account everything else that takes up a good chunk of CPU cycles, like nametable updates w/ scrolling, etc.]. You'd want to either not use NESmaker objects for some of these elements, and/or optimize how the object system works for your project.
Makes me wish Ellen was still around to drop some optimization tips for version 4.5.x. There were some pretty choice ones for the last version of NESMaker that allowed my shmup to run smooth as butter (especially getting rid of checks on game objects, like bullets, that don't necessarily need them).
 
Top Bottom