How to prevent projectile and "medusa head" slowdowns

User avatar
FrankenGraphics
Posts: 177
Joined: Wed Mar 07, 2018 11:36 am

Re: How to prevent projectile and "medusa head" slowdowns

Post by FrankenGraphics » Wed Apr 03, 2019 12:18 am

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.
www.frankengraphics.com - NES homebrew blog
User avatar
chronicleroflegends
Posts: 180
Joined: Thu Sep 06, 2018 3:51 am

Re: How to prevent projectile and "medusa head" slowdowns

Post by chronicleroflegends » Wed Apr 03, 2019 12:42 am

FrankenGraphics wrote:
Tue Apr 02, 2019 10:05 pm
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: Select all

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

	
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
~Do you believe in legends? ~
My Games: Nix: The Paradox Relic
User avatar
chronicleroflegends
Posts: 180
Joined: Thu Sep 06, 2018 3:51 am

Re: How to prevent projectile and "medusa head" slowdowns

Post by chronicleroflegends » Wed Apr 03, 2019 12:44 am

chronicleroflegends wrote:
Wed Apr 03, 2019 12:42 am
FrankenGraphics wrote:
Tue Apr 02, 2019 10:05 pm
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: Select all

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

	
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.
~Do you believe in legends? ~
My Games: Nix: The Paradox Relic
User avatar
Kasumi
Posts: 252
Joined: Fri Mar 09, 2018 11:13 pm

Re: How to prevent projectile and "medusa head" slowdowns

Post by Kasumi » Wed Apr 03, 2019 1:58 am

Here's the no loop method.

Code: Select all

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:
User avatar
FrankenGraphics
Posts: 177
Joined: Wed Mar 07, 2018 11:36 am

Re: How to prevent projectile and "medusa head" slowdowns

Post by FrankenGraphics » Wed Apr 03, 2019 9:48 am

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.
www.frankengraphics.com - NES homebrew blog
User avatar
BentPawGames
Posts: 162
Joined: Wed Jan 16, 2019 10:29 pm

Re: How to prevent projectile and "medusa head" slowdowns

Post by BentPawGames » Sun Apr 07, 2019 3:53 am

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 :)
User avatar
FrankenGraphics
Posts: 177
Joined: Wed Mar 07, 2018 11:36 am

Re: How to prevent projectile and "medusa head" slowdowns

Post by FrankenGraphics » Sun Apr 07, 2019 6:40 am

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.
www.frankengraphics.com - NES homebrew blog
User avatar
BentPawGames
Posts: 162
Joined: Wed Jan 16, 2019 10:29 pm

Re: How to prevent projectile and "medusa head" slowdowns

Post by BentPawGames » Sun Apr 07, 2019 1:43 pm

Thanks very much that helps a lot! :)
User avatar
chronicleroflegends
Posts: 180
Joined: Thu Sep 06, 2018 3:51 am

Re: How to prevent projectile and "medusa head" slowdowns

Post by chronicleroflegends » Mon Apr 08, 2019 9:31 pm

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.
~Do you believe in legends? ~
My Games: Nix: The Paradox Relic
WillElm
Posts: 75
Joined: Wed Mar 20, 2019 3:56 am

Re: How to prevent projectile and "medusa head" slowdowns

Post by WillElm » Wed May 15, 2019 12:50 am

chronicleroflegends wrote:
Wed Apr 03, 2019 12:44 am

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

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

 ;;; 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
+
Post Reply