3

I'm scanning the internet and old C64 books for the question without finding an answer, so in the end I just had to post it here. I love the good old times of C64 coding and while I'm not currently programming a game on this platform I would like to know how some hardware limitations were overcomed at that times.

In all modern game programming books and tutorials the way to find the right direction to launch enemy bullets towards the player is to use vector math with floats, more or less like in this pseudo code:

bullet_velocity = (player.position - bullet.position).normalize();

Now, taking into account the C64 limitations, the massive use of sine tables for speed I saw in source codes and, maybe I'm distracted, but I never saw a word about vector math when reading old C64 books or commented programs from C64 programmers, I'm wondering how the same goal was obtained at the times.

Please answer, I have thousand of doubts like this, but answering this question maybe I can find myself a response for the rest! :-)

EDIT: Just a note: examples of C64 games with bullets targeting players are Silkworm and Cybernoid.

  • This brings back memories... From my recollection of most C64 games, they solved this problem simply by making all action happen in strictly horizontal or vertical direction, making this whole point moot. – Sam Varshavchik Oct 27 '15 at 12:31
  • 1
    @1Garrett2010: Could you give an example of an specific game/level? Because different games probably did things differently. I would think a lot of old games only allowed the enemies to shoot in a straight horizontal/vertical line, with a fixed velocity. – Michael Oct 27 '15 at 12:33
  • 1
    Also the targets are not point-sized so some inaccuracy in the angle would still give you a hit, and the resolution of the screen wasn't that big either. Fixed point maths should be doable with a small-ish lookup table. But that's just guessing on my part :) – Jester Oct 27 '15 at 12:46
  • @Michael: Hi, the game Silkworm, the stage 1 bosses (and not only them) fire at player, also some enemies in Cybernoid. But that games list I think could be a lot wider. – 1Garrett2010 Oct 27 '15 at 13:38
  • 3
    I don't know for sure, but they could certainly have used Bresenham's algorithm or the floating point Basic routines in ROM. – Nick Westgate Oct 27 '15 at 20:49

2 Answers2

5

Supposing you're happy with a sufficiently small number of output directions, it's easiest to do via a lookup table. E.g. for 64 output directions, grab the vector (x, y) from source to destination, and if both are positive then shift both left until one of them fills the sign bit, then form a four-bit table index from the top two bits of each and look out the output vector.

Assuming you're at 160x200 then I guess you'll need to throw away a bit of precision before going in.

Mirror appropriately to deal with the other quadrants. Assuming 8.8 fixed point for object locations and a bullet velocity of 1 then that's a 32-byte lookup table.

So, naively, something like:

xPosYPos:

    ; assuming ($00) has the positive x difference
    ; and ($01) has the positive y difference

    lda $00
    ora $01

shiftLoop:
    asl $00
    asl $01
    asl a
    bpl shiftLoop

    ; load A with table index
    lda #$00
    rol $00
    rol a
    rol $00
    rol a
    rol $01
    rol a
    rol $01
    rol a

    ; look up vector
    tax
    lda xVec, x
    ; ... put somewhere ...
    lda yVec, x
    ; ... put somewhere ...

... with a smarter solution probably involving something more like:

    lda $00
    ora $01

    asl a
    bmi shift1
    asl a
    bmi shift2
    ... etc ...

shift1:

... etc, but you can shift directly to form
the table index rather than going to all the work
of shifting the vector then piecing together from
the top bits ...

Or you could create a 256-byte lookup table to look up a routine address based on x|y (which will always be at most 127 because they're both positive) and jump directly to the shifting without counting out bits.


Primer on object locations and fixed point:

Assuming you're in 160x200 mode then you can store each component of an object's location as a single byte. So one byte for x, one byte for y. What many games do is instead store each location as two bytes. So four bytes in total for x and y.

One of those bytes is the same as in a single byte format — it's the integer position. The other is a fractional part. So if you have a position and a velocity (and Euler integration) then you do a 16-bit addition of velocity to position. Then you just use the top byte for position.

This is usually called fixed point arithmetic. Unlike floating point, the location within the integer where the decimal point rests is fixed. In the scheme described here it's always eight bits in.

So, e.g. to add an offset with just byte quantities:

clc
lda xPosition
adc xVelocity
sta xPosition

sta SomeHardwareRegisterForSpritePosition

To add an offset with a fixed point scheme:

clc

lda xFractionalPosition
adc xFractionalVelocity
sta xFractionalPosition

lda xPosition
adc xVelocity
sta xPosition

sta SomeHardwareRegisterForSpritePosition

The advantage is that your velocity vector can now be as small as 1/256th of a pixel in any direction. So e.g. you can store a velocity that says that each frame your bullet will move one pixel to the left and 32/256ths of a pixel down. And all it costs to move that bullet with subpixel precision is an extra couple of bytes of storage per vector and an extra couple of ADCs.

With the above suggestion you'd get the vector from source to destination by subtracting one byte of one from one byte of the other. The result would be a two single bytes, both of which would be the fractional parts of the output. So e.g. you might decide to fire off a bullet with a vector of (87/256, 239/256), i.e. an angle of 20 degrees.

Tommy
  • 99,986
  • 12
  • 185
  • 204
  • Thanks, I have some doubts: 1) for "grab the vector (x,y) from source to destination" you intend take the bullet start&end position vectors? If a vector should be calculated from source to destination, floating math is involved and I don't see the advantages! 2) On C64 sources usually I see look up tables (sine tables, for example) with the already calculated positions (x,y) in which to move the object/sprite. Here you intend to store a single float number couple values (x, y) to add at each movement step to the bullet sprite? So some float calculations are "allowed" on C64 game programming? – 1Garrett2010 Oct 28 '15 at 10:56
  • No, no floats required. If positions are integers then the vector between them is also an integer. But for motion that isn't just left, right, up, down or diagonal you'd normally use fixed point — e.g. one byte for the integer part of x, another byte for the fractional part. Let carry flow from the one to the other. Would expanding my answer to cover fixed point be helpful? It's difficult to fit into a comment. – Tommy Oct 28 '15 at 11:09
  • Expanding the answer covering fixed point would be absolutely helpful. Thanks! – 1Garrett2010 Oct 28 '15 at 12:26
1

You could also (ab)use Bresenham's line drawing algorithm to aim your bullets, with the starting coordinates of the "line" being the enemy's coordinates and the end coordinates those of your ship. Instead of adding pixels to a line however you just replot the bullet at the current position on each iteration.

tiny tim
  • 11
  • 1