How to prevent projectile and "medusa head" slowdowns

FrankenGraphics

New member
Okay, the metroid case for only checking for tile collision every nth position (every 8th pixel in the case of this game) through AND:ing is quite simple: Let's say samus' velocity is high enough to move say 4 pixels each frame. Load an index register with that difference attempt moving her 1 pixel at a time in a countdown loop. AND:ing makes sure only valid positions (evey 8th pixels of position + radius) are examined. If there is a modulo of 8, the pixel is fine to move.

Well, i know you can be pushed (wrong word) into a wall in one cirumstance: Stand in a space with a regenerative block. Once the block has regenerated, you get insideSolid status. This removes the upward collision barrier, so you're able to jump through out of the pickle, though it may take you to unexpected places and is a sequence breaking exploit.
 
FrankenGraphics said:
Cool! How did you go about implementing 1- and 4-point collisions?

Another big improvement that could be had would be to only check for tiles every 16th pixel position on each axis. the foundation of this is simple

Code:
lda Object_x_hi,x 
and #15 
bne +       ;this filters out any x position that isn't divisible by 16
	jsr myCheckHorzCollisionRoutine
	+

This doesn't work with NESmaker without some extensive modifications, though. From what i've gathered, NESmaker collision statuses are cleared at the start of the cleared, and so the collision needs to be continuously flagged right after regardless screen position. So the quite dramatic time reduction that could be had by only checking for collisions when on grid doesn't work, since the game relies on being checked at every position or else it is considered a noncollision, which is how the walkable tile script contains no code whatsoever and still works. There may also be trouble with priorities if colliding with severaly types of tiles. Not sure yet.

Very easy, but probably not super efficient.
I just copied the checkforhorizontalcollision macro to create 2 more macros:
checkforhorizontalcollision4pt ( just comment out the code for middle left and middle right)
checkforHorizontalcollision1pt: (basically just check collision point zero, but offset it to the middle of an assumed 8x8 sprite since it is only used for projectiles.)
Code:
MACRO CheckForHorizontalCollisionSinglePoint

;;;; RESET SOLID
	LDA #$00
    STA tile_solidity
	Sta collisionPoint0
	STA collisionPoint1
	STA collisionPoint2
	STA collisionPoint3
	STA collisionPoint4
	STA collisionPoint5
	
;; Check only a single point
;; Use this for objects that do not need an accurate collision box, only a single point is necessary.
;; EX: Projectiles
;; Note that because of this special use, it is assumed the object is only a 1x1 sprite.

checkSinglePointForCollision:
	LDA xHold_hi ;; Left edge of the sprite
	CLC
	;ADC Object_left,x ;;Left Edge of the hitbox
	ADC #$08 ;;Offset to the center of the sprite (upgrade to auto detect size?)
	STA tileX
	
	LDA Object_y_hi,x ;; Top edge of the sprite
	CLC
	;ADC Object_top,x ;;Top Edge of the hitbox
	ADC #$08 ;;Offset to the center of the sprite (upgrade to auto detect size?)
	STA tileX
	
	;; Get the collision type of that tile position
	JSR GetTileAtPosition
	LDA #$00
	STA temp
	DetermineCollisionTableOfPoints temp
	
	;; Store that collision data and check for solid collision
	STA collisionPoint0
	JSR CheckForCollision
	LDA tile_solidity
	AND #%00000001
	BEQ +
	JMP HandleSolidCollision ;; Hit a solid so won't update position
  +:
	LDA tile_solidity
	AND #%00000010
	BEQ +
	LDA Object_physics_byte,x
	AND #%00000010
	BNE +
  +:
	
	ENDM
 
chronicleroflegends said:
FrankenGraphics said:
Cool! How did you go about implementing 1- and 4-point collisions?

Another big improvement that could be had would be to only check for tiles every 16th pixel position on each axis. the foundation of this is simple

Code:
lda Object_x_hi,x 
and #15 
bne +       ;this filters out any x position that isn't divisible by 16
	jsr myCheckHorzCollisionRoutine
	+

This doesn't work with NESmaker without some extensive modifications, though. From what i've gathered, NESmaker collision statuses are cleared at the start of the cleared, and so the collision needs to be continuously flagged right after regardless screen position. So the quite dramatic time reduction that could be had by only checking for collisions when on grid doesn't work, since the game relies on being checked at every position or else it is considered a noncollision, which is how the walkable tile script contains no code whatsoever and still works. There may also be trouble with priorities if colliding with severaly types of tiles. Not sure yet.

Very easy, but probably not super efficient.
I just copied the checkforhorizontalcollision macro to create 2 more macros:
checkforhorizontalcollision4pt ( just comment out the code for middle left and middle right)
checkforHorizontalcollision1pt: (basically just check collision point zero, but offset it to the middle of an assumed 8x8 sprite since it is only used for projectiles.)
Code:
MACRO CheckForHorizontalCollisionSinglePoint

;;;; RESET SOLID
	LDA #$00
    STA tile_solidity
	Sta collisionPoint0
	STA collisionPoint1
	STA collisionPoint2
	STA collisionPoint3
	STA collisionPoint4
	STA collisionPoint5
	
;; Check only a single point
;; Use this for objects that do not need an accurate collision box, only a single point is necessary.
;; EX: Projectiles
;; Note that because of this special use, it is assumed the object is only a 1x1 sprite.

checkSinglePointForCollision:
	LDA xHold_hi ;; Left edge of the sprite
	CLC
	;ADC Object_left,x ;;Left Edge of the hitbox
	ADC #$08 ;;Offset to the center of the sprite (upgrade to auto detect size?)
	STA tileX
	
	LDA Object_y_hi,x ;; Top edge of the sprite
	CLC
	;ADC Object_top,x ;;Top Edge of the hitbox
	ADC #$08 ;;Offset to the center of the sprite (upgrade to auto detect size?)
	STA tileX
	
	;; Get the collision type of that tile position
	JSR GetTileAtPosition
	LDA #$00
	STA temp
	DetermineCollisionTableOfPoints temp
	
	;; Store that collision data and check for solid collision
	STA collisionPoint0
	JSR CheckForCollision
	LDA tile_solidity
	AND #%00000001
	BEQ +
	JMP HandleSolidCollision ;; Hit a solid so won't update position
  +:
	LDA tile_solidity
	AND #%00000010
	BEQ +
	LDA Object_physics_byte,x
	AND #%00000010
	BNE +
  +:
	
	ENDM

But this adds quite a bit of bloat to the tile collision script.
 

Kasumi

New member
Here's the no loop method.

Code:
lda curPos
sta temp;store old pos temporarily (if positions are 16bit, you only need the old low byte)
clc
adc pixelsToMove
sta curPos
eor temp;If both bits were set, or if both were clear (same tile) you'll get zero in that bit's place
and #%00010000;Isolate the bit
beq nocollision
jsr horizcollision
nocollision:
 

FrankenGraphics

New member
btw, with the tail calls in place, this whole section is made redundant. you can comment out the write to currentObject as well since there's no way that could have changed since you last wrote x to currentObject. Also, the jmp to a directly subsequent addr is without purpose. The label is never used elseplace either so let's remove that too.

It doesn't gain you much except a few bytes of freed space and a few cycles and a bit of clarity but:

JSR updateHorizontalPosition
JMP updateVerticalPosition

;;;; THis is the end of top down scrolling physics. It automatically jumps to the proper place.
;;;; Otherwise, if there was no collision, we jump to updating the position.

; JMP DoneWithTileChecksAndUpdatePosition
;;; NO POINTS WERE SOLID
;;;; update hold
; DoneWithTileChecksAndUpdatePosition:
; ldx currentObject
; RTS

everything i've marked with comments can be deleted. remember to change jsr updateVerticalPosition to jmp updateVerticalPosition.
 
Apologies, having trouble following the evolution of this. What method should I use to include a few game objects/enemies (but not so many that it itself causes slow down, as someone suggested it might)? Thank you :)
 

FrankenGraphics

New member
The slowdown that WillElm was experiencing was due to that they'd seemingly had made many collision exceptions, but in practice only one of them was actually functioning as an exception, and the rest of the intended performance improvements were ignored by the program. The error was this:

cmp this
cmp that
cmp another thing
branch if some condition is met

this doesn't work because comparisons don't add up - the latest cmp overwrites the status flags from the previous one. so at the branch, you're only checking the last cmp; not all of them. hence, slowdown was still happening because most of the items on that list weren't handled.

what you should do is:
cmp this
branch
cmp that other thing
branch

this is what kasumi pointed out.

Then, ideally, you can gain a little bit extra margin if you try to order your evaluations so that
-the most likely or frequent evaluation happens first (probably evaluating a range of enemies, or something that's always present, like bullets in a shooter)
-you have as few evaluations as possible (by checking for ranges of object ID:s rather than individual object ID:s, if possible - you can accomodate for this by ordering your monsters/objects that should ignore vs tile collision together in the monster list. Or well, the GUI doesn't let you reorder monsters, but you can try to plan ahead).

This is not terribly important, but in some cases, every little bit of optimization may help. A bigger improvement would be simplifying the collision routines themselvs (both object vs tiles and object vs object - unfortunately, these aren't performance oriented and that's one of the bigger issues with the NM engine), so the thread started to derail a bit towards that direction; talking about different hypothetical or historical collision topologies (which are not present in NESmaker currently).

Any mention of tail calls and removal of redundant instructions are still applicable. Again, the margin gain is minimal, but it's good practice to do this.

Let me know if something i wrote needs better explaining.
 
Note to anybody following this thread:
Use my alterations to the code with caution: I just discovered a pretty nasty bug that I nearly could not track down.
When I made my projectiles only use one point for collision, they did not behave correctly in some rare cases.

Most of the time it worked fine, but there were exceptions where a projectile would go through a wall in certain position, or 'miss' certain enemies. Or 'hit' a solid that didn't actually exist.
So if you want one-point collision, you will have to use a different method than the one I used, because I just realized it doesn't work correctly.
However, you can still reduce certain objects, including projectiles to only use 4-point collision. From all my testing so far that seems to work fine.
 

WillElm

New member
chronicleroflegends said:
checkforhorizontalcollision4pt ( just comment out the code for middle left and middle right)

What exactly am I gonna comment out here? I got rid of everything from the left middle comment to the doRightHorColCheck label, and also got rid of what I thought was the same corresponding code for the right. This is the code I got rid of

It didn't seem to hurt anything, but I also can't tell if it made any difference in my game.

I hope this isn't too ridiculous. I've learned a good bit of asm in my short time, mostly to do with movement and variables, but I'm having a tough time following most collision related stuff.

Code:
  ;;; IF YOU NEED TO FIND OUT THE MID POINT HORIZONTALLY
    ;LDA Object_right,x
    ;SEC
    ;SBC Object_left,x
    ;LSR
    ;STA temp2 ;; temp 2 now equals half the width of the object, so left+temp2 = mid point horizontally.
    
    LDA Object_bottom,x
    SEC
    SBC Object_top,x
    LSR 
    STA temp3 ;; temp 3 now equals half of the height of the object, so top+temp3 = mid point vertically.
    

    LDA Object_y_hi,x
    CLC
    ADC Object_top,x
    CLC
    ADC temp3
    STA tileY

	LDA xHold_hi
    CLC
    ADC Object_right,x
    STA tileX
     
    JSR GetTileAtPosition
    ;JSR DetermineCollisionTable
	LDA Object_right,x
	STA temp
	DetermineCollisionTableOfPoints temp
    STA collisionPoint5
    JSR CheckForCollision
    LDA tile_solidity
	AND #%00000001
    BEQ +
    JMP HandleSolidCollision ;; hit a solid so won't update position.
+
	LDA tile_solidity
	AND #%00000010
	BEQ +
	LDA Object_physics_byte,x
	AND #%00000010
	BNE +
	;;; DO LADDER STUFF
	JSR DoLadderStuff
+


Code:
 ;;; IF YOU NEED TO FIND OUT THE MID POINT HORIZONTALLY
    ;LDA Object_right,x
    ;SEC
    ;SBC Object_left,x
    ;LSR
    ;STA temp2 ;; temp 2 now equals half the width of the object, so left+temp2 = mid point horizontally.
    
    LDA Object_bottom,x
    SEC
    SBC Object_top,x
    LSR 
    STA temp3 ;; temp 3 now equals half of the height of the object, so top+temp3 = mid point vertically.
    

    LDA Object_y_hi,x
    CLC
    ADC Object_top,x
    CLC
    ADC temp3
    STA tileY

	LDA xHold_hi
    CLC
    ADC Object_right,x
    STA tileX
     
    JSR GetTileAtPosition
    ;JSR DetermineCollisionTable
	LDA Object_right,x
	STA temp
	DetermineCollisionTableOfPoints temp
    STA collisionPoint5
    JSR CheckForCollision
    LDA tile_solidity
	AND #%00000001
    BEQ +
    JMP HandleSolidCollision ;; hit a solid so won't update position.
+
	LDA tile_solidity
	AND #%00000010
	BEQ +
	LDA Object_physics_byte,x
	AND #%00000010
	BNE +
	;;; DO LADDER STUFF
	JSR DoLadderStuff
+
 
WillElm said:
chronicleroflegends said:
checkforhorizontalcollision4pt ( just comment out the code for middle left and middle right)

What exactly am I gonna comment out here? I got rid of everything from the left middle comment to the doRightHorColCheck label, and also got rid of what I thought was the same corresponding code for the right. This is the code I got rid of

It didn't seem to hurt anything, but I also can't tell if it made any difference in my game.

I hope this isn't too ridiculous. I've learned a good bit of asm in my short time, mostly to do with movement and variables, but I'm having a tough time following most collision related stuff.

This is the block of code for middle left for example: (commented out)
Code:
;;;;; left middle
	; LDA Object_bottom,x
    ; SEC
    ; SBC Object_top,x
    ; LSR 
    ; STA temp3 ;; temp 3 now equals half of the height of the object, so top+temp3 = mid point vertically.
  
	
    ; LDA xHold_hi
    ; CLC
    ; ADC Object_left,x
    ; STA tileX

    ; LDA Object_y_hi,x
    ; CLC
    ; ADC Object_top,x
    ; CLC
    ; ADC temp3
    ; STA tileY
    
    ; JSR GetTileAtPosition
    ;;JSR DetermineCollisionTable
	  ; LDA #$00
	; STA temp
	; DetermineCollisionTableOfPoints temp
    ; STA collisionPoint4
    ; JSR CheckForCollision
    ; LDA tile_solidity
	; AND #%00000001
    ; BEQ +
    ; JMP HandleSolidCollision ;; hit a solid so won't update position.
; +
    	; LDA tile_solidity
	; AND #%00000010
	; BEQ +
	; LDA Object_physics_byte,x
	; AND #%00000010
	; BNE +
	;; DO LADDER STUFF
	; JSR DoLadderStuff
; +
	; JMP doneWithHorColCheck
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 

WillElm

New member
thanks, looks like my instincts were right for once. I meant to paste the left one into the first code box in my last post.

I just A/B compared, and it has a very slight effect on performance and every little bit counts. Right now my game has very intermittent slowdown, but I feel like it affects gameplay as much as more consistent slowdown, because it's very sudden starts and stops.
 

Razzie.P

Member
Thanks for this! Now that I've finished my rough build, I'm going back to work on the polish and details, and this seems like a great place to start. I'm having a bit of trouble wrapping my head around how I can apply this to my specific game situation, though, so any help you can offer would be greatly appreciated in a thread I created here to avoid cluttering this tutorial!

http://nesmakers.com/viewtopic.php?f=60&t=2870
 

axbakk

Member
FrankenGraphics said:
If you're doing an action game, this modification is for you. There are even better ways, but this way is quick and simple.

Projectiles and medusa heads are just popular examples of enemy actors that could benefit from this mod. ANYTHING that doesn't benefit from tile collision should really go on this list. It is also beneficial to include stationary enemies on the list (and they wouldn't even need to update positions .. they can actually skip a lot of code in another routine (HandleUpdateObjects.asm) this tutorial doesn't cover, but let's start here).

What objects should go on the exception list is something you need to decide game-for-game and may change as your design is worked on. In the example, we're only making an exception for the single projectile type.


1) Identify what game object(s) you're using as projectiles. In my case, i've set the object that is called "projectile" by default in the adventure module as my monster projectile, whereas it was originally set to 1 (which would be the one named "melee" in the gui). So, counting from the player object, which is object type 0, my monster projectile is in other words id #3.

2) Search and open HandleTileCollison.asm

3) Right after line 1 (the label), press enter and copy the following:
Code:
	lda Object_type,x
	cmp #3
	bne +
		jsr updateHorizontalPosition
		jsr updateVerticalPosition
		rts
		+

note that you have to edit that #3 if you have another or several objects you wish to be excempt from tile collisions.

3) Save a copy. In project settings, under the script settings link to the new copy.

4) Done. No more slowdowns... up to a limit of bullets, of course. I just tested 4 bullet generators in my quick test and that worked just fine.

Some precautions and notes:
-The projectile types included in the exception will no longer be able to react against solids. If this is important to your game design, you can insert a custom 1-point collision subroutine into the exception which would be loads leaner than the "do everything" 6-point collision that is currently used for all objects and still be accurate enough for small projectiles and the like. That's for another day. Usually you want player bullets to be wall blockable...

-If you swap around your objects, be sure to swap around the cmp arguments too to match.

-Even better would be to remove all the reduntant JSR / RTS calls done by HandleUpdateObjects and its various subroutines. Those add up to quite a bit of unnecessary overhead, especially since they're repeated once for every active object. This would increase the speed of collision checks for all objects a bit. JSR / RTS pairs are something you want a minimum of in any often-repeated code.

EDIT:
In case anyone is wondering, i was able to get from 4-5 objects (player included) without slowdown to 7-8 objects (player included) without slowdown on average. Both cases including a portion of aimed physics. So it's a significant improvement, but could still be better. Your mileage will vary depending on your constellations of objects that need/doesn't need tile collisions.



Made this change and yes the game runs much smoother. But i also want my player projectile to go away if hit a solid. You talked about a 1 point subroutine that would work. Tried to find it in the thread but did not get it. What do i need to do with the script to make that change?

Regards // Axbakk
 

Amon26

New member
so, if i'm trying to disable collisions on multiple objects/monsters is it supposed to look like this? Forgive me, I'm very new to assembly syntax.

Code:
	lda Object_type,x
	cmp #16
	bne +
		jsr updateHorizontalPosition
		jsr updateVerticalPosition
		rts
		+
	lda Object_type,x
	cmp #17
	bne +
		jsr updateHorizontalPosition
		jsr updateVerticalPosition
		rts
		+
	lda Object_type,x
	cmp #8
	bne +
		jsr updateHorizontalPosition
		jsr updateVerticalPosition
		rts
		+
	lda Object_type,x
	cmp #10
	bne +
		jsr updateHorizontalPosition
		jsr updateVerticalPosition
		rts+
 

Razzie.P

Member
Curious if anyone has been able to adapt this, or come up with something similar to reduce slowdown for 4.5.6?

I'm chugging through the tutorials as time permits, and see that Joe addresses some coding to reduce slowdown in the shooter tutorial, but (and I could be mistaken) it doesn't seem as simple to implement as this neat bit of coding.
 

JamesNES

Well-known member
I used Object_vulnerability flags to skip collision checks. You have to set it for each action step on an object but I think it's better than checking for each object?1613548836407.png
 

DrJondo

New member
is there a way to use this in 4.5.9 ?
i dont have this Action Step Flags, do i have to add them?
 

JamesNES

Well-known member
You have to code the behaviour for each one yourself, then select which ones you want for each of the object's action steps. You can check each flag for an object in x by loading Object_vulnerability,x and ANDing it. So if you wanted to set up the invincible one, you'd put this in your monster hurt script:

Code:
LDA Object_vulnerability,x
AND #%00000001 ;; first flag
BEQ +hurtThisMonster
   ;;invincible flag is on
   JMP +doSkipHurtingThisMonster
+hurtThisMonster

For collisions you can put something similar in doHandleObjectCollisions I think it was.

For the editor you can rename each one in the project settings.
 
Top Bottom