Jump to content
IGNORED

Sprite Collision Methods in TI Extended BASIC


zacharyfruhling

Recommended Posts

As I mentioned in another thread, I've decided to use TI Extended BASIC instead of regular TI BASIC for the game I'm working on: a re-created version of a game my dad made for me in TI BASIC in the early 1980s. While I'm sure this is well-trodden territory here, please pardon the newbie questions! I would appreciate suggestions related to any or all of the following questions about sprite collision detection in TI Extended BASIC. While I have a MOSTLY working version of the game already, there is definitely room for improvement in sprite collision detection:

 

  1. Which method of sprite collision detection between two single-character-sized sprites is generally more reliable, CALL COINC or CALL DISTANCE?
  2. How would you go about reliably detecting when a sprite has left the screen? I've brute-forced this in my game, and it's MOSTLY working, although not all collisions between sprites and screen-edge hits are being detected, even when I add what I consider to be a rather generous tolerance level on the distance between the sprite and any of the screen edges.
  3. Clearly part of the problem I'm having involves the fact that the CALL COINC and/or CALL DISTANCE commands (I've used both back-to-back to maximize the chances of successfully detecting a collision) are not being called in the game loop when the sprite collision actually happens (80% of the time, perhaps). One idea I've had to is create a separate subroutine strictly for sprite collision detection and to call that subroutine at the very beginning of every other subroutine to check for sprite collisions much more frequently as the program runs. This strikes me as a heavy-handed approach (if, perhaps, the best solution), but am I overlooking other possible approaches to detecting sprite collisions more reliably as the program churns away?

 

Thank you in advance to anyone who spends time offering up helpful suggestions; it is much appreciated! 🙂

Edited by zacharyfruhling
Minor clarification
Link to comment
Share on other sites

Really, what you generally want to do is use CALL COINC(ALL,X) for your detection of a collision in the game loop (which is quite fast, in XB terms, as the VDP status register flags collisions inherently, but only by indicating that one has occurred).  Then, only if necessary and unavoidable, test whether it is a specific collision (which will be significantly slower). 

 

Many of the best 80s era XB action games simply pursue a design which is wholly dependent on CALL COINC(ALL,X).  Which is to say, only one category of collision may occur (e.g., player touches one of the things that will kill the player), and when a collision (any collision) occurs, its consequence is therefore assumed.  And that tendency is just a consequence of testing for individual collisions being so much slower. 

 

Still, the method I'd mentioned, where CALL COINC(ALL,X) reports a collision (in general), and CALL DISTANCE or CALL POSITION is then (and only in that case) used to determine which of multiple possible collisions has occurred - that too can work acceptably. 

 

Another approach is to ditch automotion and just position your sprites with CALL SPRITE or CALL LOCATE (which are roughly equivalent in performance).  Then, just determine collisions based on the already known coordinates of the sprites. 

  • Like 1
Link to comment
Share on other sites

30 minutes ago, pixelpedant said:

Really, what you generally want to do is use CALL COINC(ALL,X) for your detection of a collision in the game loop (which is quite fast, in XB terms, as the VDP status register flags collisions inherently, but only by indicating that one has occurred).  Then, only if necessary and unavoidable, test whether it is a specific collision (which will be significantly slower). 

 

Many of the best 80s era XB action games simply pursue a design which is wholly dependent on CALL COINC(ALL,X).  Which is to say, only one category of collision may occur (e.g., player touches one of the things that will kill the player), and when a collision (any collision) occurs, its consequence is therefore assumed.  And that tendency is just a consequence of testing for individual collisions being so much slower. 

 

Still, the method I'd mentioned, where CALL COINC(ALL,X) reports a collision (in general), and CALL DISTANCE or CALL POSITION is then (and only in that case) used to determine which of multiple possible collisions has occurred - that too can work acceptably. 

 

Another approach is to ditch automotion and just position your sprites with CALL SPRITE or CALL LOCATE (which are roughly equivalent in performance).  Then, just determine collisions based on the already known coordinates of the sprites. 

This is very helpful; thank you! While I don't think I can get away with having only one type of collision overall, with the game containing three different types of sprites, I'll try reworking the sprite collision detection subroutine in the manner you suggest (checking for any collision and then conditionally checking whether the two sprites in questions have collided) and see how much of a performance boost that gives me.

 

While we're on the subject of optimization, are subroutines expensive from a performance standpoint? I've been making liberal use of subroutines, trying to make the program as modular as possible. But it occurs to me that perhaps the subroutine calls themselves are slowing things down.

Link to comment
Share on other sites

6 minutes ago, pixelpedant said:

Named subroutines aren't too bad from a performance standpoint, but I still more or less never use them, favouring GOSUBs and the extremely useful (and exceedingly fast) ON GOSUB, with both of those being exceedingly fast. 

Oh, I've been using GOSUB, not named subroutines. I've never used ON GOSUB before, as I'm still getting reacquainted with the TI again after more than a few years. I'll have to spend a little time with the TI Extended BASIC manual and wrap my head around how ON GOSUB works and how I could use it in the program I'm working on.

Edited by zacharyfruhling
Typo
Link to comment
Share on other sites

ON GOSUB is definitely worth mastering.  For complex conditionals with many possible input values, it can produce structures which are both faster and more memory-economic than any equivalent set of IF conditionals. 

 

The main 'trick' to it is just understanding that TI BASIC numeric variables are always and only floats, and accordingly, ON GOSUB does not care if its argument is precisely "1" or "2" or "3" or what have you.  It only cares that it is closer to "1" (or 2, or 3, and so on) than to any other integer.  And this creates a great deal of flexibility, where arbitrary input values can be arithmetically strong-armed into sufficient proximity to these integers.  So for example, an ON GOTO from the program I'm currently working on is the following:

 

7720 ON (B-169)*(B-169)/97 GOTO 7800,7600,7500,6800,7000,6600,6400,6300

 

The purpose of the equation (B-169)*(B-169)/97 being, to take values of B ranging from 176 to 197, and conflate certain ranges therein, such that ON GOTO sees this as a set of sequential input values ranging from 1 to 8 (with different outcomes for each).  Though in practice, it is a set of input values consisting of successive ranges (and in some cases, individual values) from 176 to 197. 

  • Like 5
Link to comment
Share on other sites

ON GOSUB is similar to a case statement in principle. You create a list of addresses to call, and execute the list depending on an index variable.

ON index gosub 1100, 1200, 1300 will send you to one of the three subroutines depending on if index is 1, 2 or 3. It's a fast method to decide what to do, provided index can be calculated efficiently.

 

index = (y1>y2)*-2-(x1>x2)+1 will calculate an index in the range 1..4 for the different combinations of one vertical position being larger or smaller than the other and the same for one horizontal position. Once you have the index, you can do an ON GOSUB with four choices, depending on which quadrant one thing is in, compared to the other.

 

Just one example of how you can combine computations that seemingly would require several IF statements to evaluate with a more traditional approach. Usually, making decisions based on calculations is faster than those based on a pretty large number of tests.

 

Ah, I just noted @pixelpedant posted a similar thing while I was writing.

Edited by apersson850
  • Like 3
Link to comment
Share on other sites

Pixelpedant in a video mentioned the problem with CALL COINC did not tell you the Row and Column of where the collision took place.

And when you did a CALL POSITION would miss where they collided by way to many pixels.

 

So in RXB I created CALL COLLIDE(#sprite-number,#sprite-number,tolerance,dot-row,dot-column) tells you exactly where they collided.

dot-row and dot-column are zero if there is no collision, but if there is a collision it reports the dot-row and dot-column where they collided.

 

CALL COLLIDE uses ASSMBLY unlike CALL POSTION and CALL DISTANCE that are GPL commands.

 

In the video I even tell you how fast each command in XB is dealing with sprite hits or locations.

  • Like 1
Link to comment
Share on other sites

On 1/4/2023 at 6:04 AM, zacharyfruhling said:

While I don't think I can get away with having only one type of collision overall, with the game containing three different types of sprites

The CALL COINC(ALL,X) could be used for a general call of coincidences ... and then if there is one , it does a check in a loop ... example here;

I don't have fully the time to test this as I'm going out, but the program sets off 3 sprites in auto-motion and what we're looking for is the sprite number that hits sprite number 1.

Sprite #1 is the blue asterisk.

 

10 CALL SPRITE(#1,42,5,100,1,0,16,#2,42,13,100,255,0,-16,#3,42,10,184,120,-16,0)

20 GOSUB 1000 :: GOTO 20   ! KEEPS CHECKING FOR A COLLISION IN GENERAL.

 

 

! * GENERAL COINC *

! (IF NO COINCIDENCES, NOT MUCH BEARING ON SPEED IS HAD HERE AS IT'S NOT LOOPING THROUGH SPRITES EACH TIME, *ONLY* WHEN SOMETHING HITS)

1000 CALL COINC(ALL,X) :: IF X=-1 THEN 2000 

1010 RETURN

 

! * COINC LOOP *

! SOMETHING HAS HIT, SO WE NOW COUNT THROUGH THE LOOP OF ENEMY SPRITES THAT HIT SPRITE #1

! THIS IS IGNORED COMPLETELY IF NO COLLISIONS HAVE TAKEN PLACE AT ALL.

2000 FOR L=2 TO 3    ! THERE WOULD BE MANY MORE SPRITES TO CHECK THAN JUST 2 ....

2010 CALL COINC(#L,#1,8,X) :: IF X=-1 THEN 2020 ELSE 2030

2020 DISPLAY AT(15,15):L :: STOP   ! REPORT THE SPRITE NUMBER THAT HIT SPRITE #1 AND STOP

2030 NEXT L

2040 GOTO 1010 ! BRANCH BACK TO THE RETURN (I DON'T THINK THE CODE WOULD GET THIS FAR!)

 

Hope this helps.  I need to start doing this method myself!

Link to comment
Share on other sites

Checking each and every sprite in lines 2000 to 2040 is super slow.

This is why I wrote CALL COLLIDE as it does this in ASSEMBLY not GPL.

 

Also I would change line 2010

2010 CALL COINC(#L,#1,8,X) :: IF X THEN 2020 ELSE 2030

 

I know it is a minor change to speed up the check, but X not equal to zero is faster than checking if X=-1

  • Like 1
Link to comment
Share on other sites

1 hour ago, RXB said:

Checking each and every sprite in lines 2000 to 2040 is super slow

If you do it like i did , no it is not slow.  It's not literally doing this every "update" ... it's only ever doing the loop if a sprite collision takes place..... the main sprite collision routine is a simple call coinc (all,x) .... which is initially quick ..... it's NOT doing the loop every update.  ;)  

Link to comment
Share on other sites

20 minutes ago, Retrospect said:

If you do it like i did , no it is not slow.  It's not literally doing this every "update" ... it's only ever doing the loop if a sprite collision takes place..... the main sprite collision routine is a simple call coinc (all,x) .... which is initially quick ..... it's NOT doing the loop every update.  ;)  

Yes same as CALL COLLIDE works the same way, and yes the CALL COINC is using the VDP hardware flag which is fast but finding which sprite hit is way slower.

That was my point in making CALL COLLIDE was it takes a longer time to find the right sprite that hit the other sprite.

Doing this in Assembly is way faster than a XB program section that uses GPL.

  • Like 1
Link to comment
Share on other sites

Here is an idea I experimented with for checking sprite coincidence - without using CALL COINC. Running in XB it is as slow as expected. The intent was to use it with the compiler. Use arrow keys to generate a coincidence with the red cross. 

 

There is no error checking so if you leave the screen it's Game Over.

 

 

100 CALL CLEAR :: CALL SCREEN(2):: FOR I=1 TO 12 :: CALL COLOR(I,15,2):: NEXT I :: CALL MAGNIFY(3):: DISPLAY AT(24,1):"COINC:"
110 S$="!-30!-31!-21!-12!-13!03!13!12!21!31!30!3-1!2-1!1-2!1-3!0-3!-1-3!-1-2!-2-1!-3-1!20!02!0-2!-20!10!00!-10!01!0-1"::Y=100 :: X=100
120 CALL CHAR(124,"0000000000010107070101000000000000000000008080E0E0808",128,"0000000000000001010000000000000000000000000000808")
130 CALL SPRITE(#2,124,7,80,X,#1,128,5,Y,X,#3,124,14,50,50,0,10,#4,124,14,50,50,0,-5,#5,124,14,50,50,0,5)
140 CALL KEY(0,A,B):: IF A<8 OR A>11 THEN 140
150 ON A-7 GOTO 160,170,180,190
160 X=X-1 :: GOTO 200
170 X=X+1 :: GOTO 200
180 Y=Y+1 :: GOTO 200
190 Y=Y-1
200 CALL LOCATE(#1,Y,X):: CALL POSITION(#2,A,B):: A=A-Y :: B=B-X :: A$="!"&STR$(A)&STR$(B):: C=POS(S$,A$,1):: IF C=0 THEN A$="NO" ELSE A$="YES"
210 DISPLAY AT(24,7):A$ :: GOTO 140
  • Like 2
Link to comment
Share on other sites

Before using compiled XB, I would do specific coinc calls but in the order of "movement"

 

That is, if I made a Space Invader type game where I was shooting bottom to top...  I would call coinc for the "lowest" invader first, then the next highest etc.

With a bit of trial an error, I could get the speed of the shot to move with the execution of the program, so that the call coinc would occur when the shot would be at the ROW of the invader.

 

Of course, if the game loop changed, it would require a tweak.  So, this was something done near the end of development.

 

NOW,  I just compile and have not had much of an issue!

 

I do like Rich's  Position info!  Would make it easy to place the Explosion!  etc.

Edited by 1980gamer
  • Like 1
Link to comment
Share on other sites

1 hour ago, unhuman said:

Man, I never knew about ALL being fast before...

 

What would be amazing is a subprogram that determined and returned a list of all collisions...  Then, the program could analyze all the collisions...  Wishful thinking.

ALL is HARDWARE on chip check of a hit and puts it into status.

That is why I created CALL COLLIDE in RXB as this told you the sprites that hit, but all the sprites even in assembly would suck.

The checking is not the slowdown the problem is putting all the data into all the variables that would slow the check down.

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

On 1/16/2023 at 1:42 PM, RXB said:

ALL is HARDWARE on chip check of a hit and puts it into status.

That is why I created CALL COLLIDE in RXB as this told you the sprites that hit, but all the sprites even in assembly would suck.

The checking is not the slowdown the problem is putting all the data into all the variables that would slow the check down.

That is nice. 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...