Bullet Spray routines
Bullet Spray routines
Hi! Does anyone know of any good bullet spray routines i could borrow some bytes or ideas from? Musing how much effort it would be to do something like Gravity Force II on the speccy.
Re: Bullet Spray routines
Is it more the vector aspect you're after, or the bullet-hell part? In Jon Cauldwell's book, there's a section on vector movement, and if you're up for disasssembling, there's Lunaris which is pretty much GF2 by the looks of it! There's some vector table code from my own game, Tardigrade, the source code is at the bottom of the page under 'additional file downloads'.
Re: Bullet Spray routines
If you want to have a bullet flying from any point to another point then you will in most cases need the old, good Bresenham algorithm
https://en.wikipedia.org/wiki/Bresenham ... _algorithm
Drawing a flying bullet is almost the same as drawing a line, you just don't clear the old positions in case of the line
Sometimes you may decide to make it easier and allows movement only on basic paths like horizontal, vertical and 45 degrees path which don't need Bresenham calculations.
And drawing 100 bullets is the same as drawing one bullet, just repeated 100 times
https://en.wikipedia.org/wiki/Bresenham ... _algorithm
Drawing a flying bullet is almost the same as drawing a line, you just don't clear the old positions in case of the line
Sometimes you may decide to make it easier and allows movement only on basic paths like horizontal, vertical and 45 degrees path which don't need Bresenham calculations.
And drawing 100 bullets is the same as drawing one bullet, just repeated 100 times
Re: Bullet Spray routines
i have my own int based vector math i'll probably use if it provides any benefit - it's the drawing 100+ bullet-pixels to screen fast enough part that strikes me as possibly both simultaneously:
- something fascinating i could happily spend a few decades exploring and still failing to map out completely
- a fully explored playground with don't bother pitfalls and dizzily fast screen slides already well known
Thanks so much for the links - read through the first dozen pages or so and can already tell that book is going to be a solid reference!R-Tape wrote: ↑Tue Dec 05, 2023 9:56 am In Jon Cauldwell's book, there's a section on vector movement,
That is really nice!! I get a GF2 vibe from it too.
I like how smooth that looks, and the effort put into making the different levels distinct.R-Tape wrote: ↑Tue Dec 05, 2023 9:56 amThere's some vector table code from my own game, Tardigrade, the source code is at the bottom of the page under 'additional file downloads'.
One of my favourite little touches in GF2 is firing at an angle while accelerating, and you see how the arc of bullets morphs in response to your velocity. i kinda have a high standard for this though - i think it should be possible to achieve at minimumRalf wrote: ↑Tue Dec 05, 2023 10:15 am If you want to have a bullet flying from any point to another point then you will in most cases need the old, good Bresenham algorithm
https://en.wikipedia.org/wiki/Bresenham ... _algorithm
Drawing a flying bullet is almost the same as drawing a line, you just don't clear the old positions in case of the line
Sometimes you may decide to make it easier and allows movement only on basic paths like horizontal, vertical and 45 degrees path which don't need Bresenham calculations.
And drawing 100 bullets is the same as drawing one bullet, just repeated 100 times
- Destructible scenery
- 2 player local with shared+split screen
- no sense of bullet austerity
- ParadigmShifter
- Manic Miner
- Posts: 868
- Joined: Sat Sep 09, 2023 4:55 am
Re: Bullet Spray routines
Bresenham is only really useful if you want to move 1 pixel at a time, which bullets rarely will want to I expect.
It's probably better to just use vectors but maintain fractional part (say as the lower 8 bits) to keep track of the overflow.
If bullets move too fast (so they can pass through things) you'll need line segment collision though.
I think 100 bullets may be a tad ambitious on the speccy
It's probably better to just use vectors but maintain fractional part (say as the lower 8 bits) to keep track of the overflow.
If bullets move too fast (so they can pass through things) you'll need line segment collision though.
I think 100 bullets may be a tad ambitious on the speccy
Re: Bullet Spray routines
There is a modern game called Chibi Akumas which may reach 100 bullets at its busiest moments:I think 100 bullets may be a tad ambitious on the speccy
https://spectrumcomputing.co.uk/entry/3 ... ibi_Akumas
Click the link and check the video. About 34 minute for example.
It's very unplayable but damn, you asked for bullet hell so you have it
- ParadigmShifter
- Manic Miner
- Posts: 868
- Joined: Sat Sep 09, 2023 4:55 am
Re: Bullet Spray routines
Well yeah it looks bad and chugs terribly.
I heard he had a big sulk and left here (his website articles about z80 programming are very good though).
I heard he had a big sulk and left here (his website articles about z80 programming are very good though).
Re: Bullet Spray routines
Let's just say that it's much easier to get lots of bullets on screen, if you're going to do this on a PC.
You will probably need a lot of tricks to get lots of stuff on screen on a Spectrum.
There are many games that can get a lots of bullets on screen, although most of the time they are horizontal bullets for speed reasons.
Rex or Cybernoid or even (sorry for the plug) Future Looter can easily do lots of stuff on screen, you should probably just look at them.
I'm not really a bullet hell fan though.
EDIT: By the way, Gravity Force II? Is that the game that looks like that Thrust game on the Spectrum?
You will probably need a lot of tricks to get lots of stuff on screen on a Spectrum.
There are many games that can get a lots of bullets on screen, although most of the time they are horizontal bullets for speed reasons.
Rex or Cybernoid or even (sorry for the plug) Future Looter can easily do lots of stuff on screen, you should probably just look at them.
I'm not really a bullet hell fan though.
EDIT: By the way, Gravity Force II? Is that the game that looks like that Thrust game on the Spectrum?
Re: Bullet Spray routines
if each bullet was one pixel that would be easy to draw. It might provide a challenge...but I dont know how playable that would be overall.
Are these 100 bullets going to come from a boss? if so, you'd have to decide are they going to shoot bullets out at the 8 traditional directions..or at every angle in a circle.(one for every 3 degrees)
After looking at gravity force it seems like most of the bullets will be coming from turrets? and some from the player.
i did implement line of sight code for turrets a long time ago, in a basic type language. Also the code to make a turret turn towards a player turned out to be an absolute nightmare in a basic type language.
if i were you i'd code it in this language first:
https://blitzmax.org/
you would need code to decide if the turret was facing the player..
bresenham line algorithm to see if there is a clear path to the player
then you would need the code to turn the turret towards the player
and of course shooting, which is the easiest thing.
You'd also need yer vector library to add gravity and acceleration to the player.
Are these 100 bullets going to come from a boss? if so, you'd have to decide are they going to shoot bullets out at the 8 traditional directions..or at every angle in a circle.(one for every 3 degrees)
After looking at gravity force it seems like most of the bullets will be coming from turrets? and some from the player.
i did implement line of sight code for turrets a long time ago, in a basic type language. Also the code to make a turret turn towards a player turned out to be an absolute nightmare in a basic type language.
if i were you i'd code it in this language first:
https://blitzmax.org/
you would need code to decide if the turret was facing the player..
bresenham line algorithm to see if there is a clear path to the player
then you would need the code to turn the turret towards the player
and of course shooting, which is the easiest thing.
You'd also need yer vector library to add gravity and acceleration to the player.
Re: Bullet Spray routines
This is amazing - thankyou! In that it's a lot of things happening on the screen at once which is maybe 5x what i think i need. But even at 2x playspeed the animation looks less stuttery - so it gives me much hope.Ralf wrote: ↑Thu Dec 07, 2023 7:49 pm There is a modern game called Chibi Akumas which may reach 100 bullets at its busiest moments:
https://spectrumcomputing.co.uk/entry/3 ... ibi_Akumas
Click the link and check the video. About 34 minute for example.
It's very unplayable but damn, you asked for bullet hell so you have it
So i realise i didn't state the problem well - this is the original creators of GF2 playing together in their modern remake:Wall_Axe wrote: ↑Thu Dec 07, 2023 9:07 pm if each bullet was one pixel that would be easy to draw. It might provide a challenge...but I dont know how playable that would be overall.
Are these 100 bullets going to come from a boss? if so, you'd have to decide are they going to shoot bullets out at the 8 traditional directions..or at every angle in a circle.(one for every 3 degrees)
After looking at gravity force it seems like most of the bullets will be coming from turrets? and some from the player.
it shows all the bullets of varying speeds flying around and hecka collision detection, but i way overestimated the number of bullets typically on screen to handle. but i picked GF2 as an example since it just requires vertical smooth scrolling and is smaller scale
was it the trig, slow pixel reads, or something else?
some basics try to be too helpful
ok, done: https://sourceforge.net/projects/gp2/Wall_Axe wrote: ↑Thu Dec 07, 2023 9:07 pm if i were you i'd code it in this language first:
https://blitzmax.org/
i love blitz basic! i have an amiga port of Chaos i wrote using it kicking around somewhere, and so much more on forgotten disks..
anyway, wrote the above in blitz basic on an coughing 486 as one of the example demos for the release of BlitzPC. shame since i was so rushed i never had time to clean up the code - worst example code for a language ever - and now it runs 20x too fast to be playable
- ParadigmShifter
- Manic Miner
- Posts: 868
- Joined: Sat Sep 09, 2023 4:55 am
Re: Bullet Spray routines
You don't need trig for facing towards/which way should I turn/am I pointing directly at my target.
Dot product for the win.
Dot product for the win.
Re: Bullet Spray routines
nice! i'm probably gonna avoid using a cartesian coordinate system if at all possible though, e.g. https://www.shadertoy.com/view/DdVBWG
Re: Bullet Spray routines
Oh that's cool that you have it done in blitz basic already.
I don't remember exactly what I did but I wanted to know if the player was in a 90 degree sight cone of an enemy.
So the enemy wouldn't see you unless he was pointing in the correct one of the four main directions ( as in up down left right)
I had trig code that gave the angle between two points. That's one of the well known and simplest things to do.
Then I compared that to the angle that the enemy was facing.
If the enemy is facing right his angle would be zero.
Down,his angle is 90.
Left is 180.
If it was within 90 degrees difference then the player is within the cone of sight.
You could then use that number to decide if a turret should turn left or right to aim at the player.
The difficulty came in dealing with the threshold of when the angle goes from 359 back to zero.
I don't remember exactly what I did but I wanted to know if the player was in a 90 degree sight cone of an enemy.
So the enemy wouldn't see you unless he was pointing in the correct one of the four main directions ( as in up down left right)
I had trig code that gave the angle between two points. That's one of the well known and simplest things to do.
Then I compared that to the angle that the enemy was facing.
If the enemy is facing right his angle would be zero.
Down,his angle is 90.
Left is 180.
If it was within 90 degrees difference then the player is within the cone of sight.
You could then use that number to decide if a turret should turn left or right to aim at the player.
The difficulty came in dealing with the threshold of when the angle goes from 359 back to zero.
- ParadigmShifter
- Manic Miner
- Posts: 868
- Joined: Sat Sep 09, 2023 4:55 am
Re: Bullet Spray routines
Really, don't use trig for that. Dot products are much easier (and faster)
Let p be the player position vector. Let f be the player facing vector (probably want this normalised i.e. length is 1).
Let e be the enemy position vector.
Vector from player to enemy = e - p [this is just vector componentwise subtraction]
To see if the player is facing towards the enemy (180degrees spread)
isFacingTowardsEnemy = dot_product((e - p), f) > 0
For an arbitrary cone angle, you need to normalise (e - p) and f must be normalised also (I assume it will be).
then
dot_product(normalise(e - p), f) > cos (theta/2)
tells you if you are within the sightcone or not.
So you would use cos 45 degrees = 1/sqrt(2) for a 90 degree view cone.
If 2 vectors have a dot_product of 0 they are perpendicular.
If 2 normalised vectors have a dot_product of 1, they are pointing in the same direction. -1 if they are pointing in opposite directions.
That comes from this formula:
dot_product(a, b) = length(a) * length(b) * cos(angle between a and b).
Remember most programming languages use radians instead of degrees though. 360 degrees = 2pi radians
where the angle between a and b is the smallest angle i.e. between 0 and 180 degrees.
dot_product(a, b) = a.x * b.x + a.y * b.y // + a.z * b.z if using 3d vectors
EDIT: If you can only face in 8 directions the normalised facing vectors are
East: (1, 0)
Northeast: (1/sqrt(2), 1/sqrt(2))
North: (0, 1)
Northwest: (-1/sqrt(2), 1/sqrt(2))
etc.
normalise(v) = sqrt(dot_product(v, v)) * v
since dot_product(v, v) gives you the magnitude squared
Let p be the player position vector. Let f be the player facing vector (probably want this normalised i.e. length is 1).
Let e be the enemy position vector.
Vector from player to enemy = e - p [this is just vector componentwise subtraction]
To see if the player is facing towards the enemy (180degrees spread)
isFacingTowardsEnemy = dot_product((e - p), f) > 0
For an arbitrary cone angle, you need to normalise (e - p) and f must be normalised also (I assume it will be).
then
dot_product(normalise(e - p), f) > cos (theta/2)
tells you if you are within the sightcone or not.
So you would use cos 45 degrees = 1/sqrt(2) for a 90 degree view cone.
If 2 vectors have a dot_product of 0 they are perpendicular.
If 2 normalised vectors have a dot_product of 1, they are pointing in the same direction. -1 if they are pointing in opposite directions.
That comes from this formula:
dot_product(a, b) = length(a) * length(b) * cos(angle between a and b).
Remember most programming languages use radians instead of degrees though. 360 degrees = 2pi radians
where the angle between a and b is the smallest angle i.e. between 0 and 180 degrees.
dot_product(a, b) = a.x * b.x + a.y * b.y // + a.z * b.z if using 3d vectors
EDIT: If you can only face in 8 directions the normalised facing vectors are
East: (1, 0)
Northeast: (1/sqrt(2), 1/sqrt(2))
North: (0, 1)
Northwest: (-1/sqrt(2), 1/sqrt(2))
etc.
normalise(v) = sqrt(dot_product(v, v)) * v
since dot_product(v, v) gives you the magnitude squared
- ParadigmShifter
- Manic Miner
- Posts: 868
- Joined: Sat Sep 09, 2023 4:55 am
Re: Bullet Spray routines
You don't even need trig to draw circles
e.g. here is an efficient way to draw a circle in BASIC
Stuff REM'd out since it was based on a version which drew 8 pixels at a time (version shown draws horizontal lines 4 at a time).
No trig and no division either EDIT: Also - only multiplication is by 2 which makes it especially good for ASM
Original version which draws 8 pixels at a time
EDIT: I also optimised that and converted it to ASM Does not do clipping though (but it does draw filled circles as well)
- code is very long (sjasmplus ASM syntax)
e.g. here is an efficient way to draw a circle in BASIC
Code: Select all
5 REM cx,cy are the circle centre, r is the radius
10 LET cx=128: LET cy=88: LET r=80
15 REM x, y are the offsets from the centre
16 REM d is the difference between the distance from the centre
17 REM squared, and the actual radius squared
20 LET x=0: LET y=r: LET d=0
25 REM plot all eight quadrants
30 REM PLOT cx+x,cy+y
40 REM PLOT cx+x,cy-y
50 REM PLOT cx-x,cy+y: DRAW x+x,0
60 REM PLOT cx-x,cy-y: DRAW x+x,0
70 REM PLOT cx+y,cy+x
80 PLOT cx-y,cy+x: DRAW y+y,0
90 REM PLOT cx+y,cy-x
100 PLOT cx-y,cy-x: DRAW y+y,0
105 REM compute the new d if we increase x
106 REM d=(x*x)+(y*y)-(r*r)
107 REM d2=(x+1)*(x+1)+(y*y)-(r*r)=(x*x)+(2*x)+1+(y*y)-(r*r)
108 REM d2-d = (2*x)+1
110 LET d=d+x+x+1
120 LET x=x+1
125 REM check if we should move closer to the circle
126 REM e=new d if y=y-1 using similar equation as above
130 LET e=d-y-y+1
140 IF e<0 THEN GO TO 170
150 LET d=e
151 PLOT cx-(x-1),cy+y: DRAW x+x-2,0
152 PLOT cx-(x-1),cy-y: DRAW x+x-2,0
160 LET y=y-1
165 REM once y<x we are past 45 degrees, so stop
170 IF y>=x THEN GO TO 30
175 REM keep drawing smaller circles down to zero
180 CLS : IF r=0 THEN STOP
190 LET r=r-1
200 GO TO 20
No trig and no division either EDIT: Also - only multiplication is by 2 which makes it especially good for ASM
Original version which draws 8 pixels at a time
Code: Select all
5 REM cx,cy are the circle centre, r is the radius
10 LET cx=128:LET cy=88:LET r=80
15 REM x, y are the offsets from the centre
16 REM d is the difference between the distance from the centre
17 REM squared, and the actual radius squared
20 LET x=0:LET y=r:LET d=0
25 REM plot all eight quadrants
30 PLOT cx+x,cy+y
40 PLOT cx+x,cy-y
50 PLOT cx-x,cy+y
60 PLOT cx-x,cy-y
70 PLOT cx+y,cy+x
80 PLOT cx-y,cy+x
90 PLOT cx+y,cy-x
100 PLOT cx-y,cy-x
105 REM compute the new d if we increase x
106 REM d=(x*x)+(y*y)-(r*r)
107 REM d2=(x+1)*(x+1)+(y*y)-(r*r)=(x*x)+(2*x)+1+(y*y)-(r*r)
108 REM d2-d = (2*x)+1
110 LET d=d+x+x+1
120 LET x=x+1
125 REM check if we should move closer to the circle
126 REM e=new d if y=y-1 using similar equation as above
130 LET e=d-y-y+1
140 IF e<0 THEN GO TO 170
150 LET d=e
160 LET y=y-1
165 REM once y<x we are past 45 degrees, so stop
170 IF y>=x THEN GO TO 30
175 REM keep drawing smaller circles down to zero
180 IF r=0 THEN STOP
190 LET r=r-1
200 GO TO 20
- code is very long (sjasmplus ASM syntax)
Spoiler
Code: Select all
ORG #8000
RETURN_TO_BASIC EQU 1
NOPS_AT_START EQU 0
SCRBUF_BASEADDR EQU #4000
SPECIAL_CASE_LINE256 EQU 0
MACRO ldim regpair, val1, val2
ld regpair, ((val1&#FF)<<8)|(val2&#FF)
ENDM
codestart:
IF NOPS_AT_START
nop
nop
nop
nop
ENDIF
main:
IF RETURN_TO_BASIC
ld (stashed_iy), iy
exx
ld (stashed_hl_alt), hl
ld (stashed_sp), sp
ENDIF
.mainloop
ld a, 7
out (#fe), a
halt
ld a, 1
out (#fe), a
IF 1
ldim hl, 128, 96
ld d, 63
call draw_circle
jp .mainloop
ENDIF
IF 0;1
ldim hl, 128, 96
ld d, 95
call draw_circle
ld b, 50
.morehalt
halt
djnz .morehalt
ld a, 0|(7<<3)
call cls
ldim hl, 128, 96
ld d, 95
call draw_fill_circle
ld b, 50
.morehalt2
halt
djnz .morehalt2
ld a, 0|(7<<3)
call cls
jp .mainloop
ENDIF
IF 1;0
ldim hl, 128, 96
ld d, 95
.nextrad
push de
push hl
call draw_circle
pop hl
pop de
dec d
jp p, .nextrad
ENDIF
IF RETURN_TO_BASIC
returntobasic:
ld iy, (stashed_iy)
ld hl, (stashed_hl_alt)
ld sp, (stashed_sp)
exx
ENDIF
ret
; H - cx
; L - cy
; D - radius
draw_circle:
; B = xoffset
xor a
; A' = sx
ex af, af'
xor a
; C = yoffset
ld b, a
ld c, d
ld d, a
IF 1
; push everything
; do the very first line, just draw 2 pixels
push de
push bc
push hl
; cy+x
;ld a, l
;add b
;ld l, a ; B is 0 first line
; cx-y
ld a, h
sub c
ld b, a
; cx+y
ld a, h
add c
ld c, a
xor a ; A=0 and clear carry
sla l
ld h, tbl_scraddr/256 ; this requires tbl_scraddr be aligned to a 256 byte boundary
adc h ; A = H + carry flag
ld h, a ; write it back
; look up screen address from the table
ld a, (hl)
inc l
ld h, (hl)
ld l, a
ld a, c
and ~7
rrca
rrca
rrca
add l
ld l, a
ld a, c
ld de, col2pix
and 7
add e
ld e, a
ld a, (de)
or (hl)
ld (hl), a
ld a, l
and ~31
ld l, a
ld a, b
and ~7
rrca
rrca
rrca
add l
ld l, a
ld a, b
ld de, col2pix
and 7
add e
ld e, a
ld a, (de)
or (hl)
ld (hl), a
jp .doneveryfirstline
ENDIF
.nextline
push de
push bc
push hl
; cy+x
ld a, l
add b
ld l, a
; cx-y
ld a, h
sub c
ld b, a
; cx+y
ld a, h
add c
ld c, a
xor a ; A=0 and clear carry
sla l
ld h, tbl_scraddr/256 ; this requires tbl_scraddr be aligned to a 256 byte boundary
adc h ; A = H + carry flag
ld h, a ; write it back
; look up screen address from the table
ld a, (hl)
inc l
ld h, (hl)
ld l, a
ld a, c
and ~7
rrca
rrca
rrca
add l
ld l, a
ld a, c
ld de, col2pix
and 7
add e
ld e, a
ld a, (de)
or (hl)
ld (hl), a
ld a, l
and ~31
ld l, a
ld a, b
and ~7
rrca
rrca
rrca
add l
ld l, a
ld a, b
ld de, col2pix
and 7
add e
ld e, a
ld a, (de)
or (hl)
ld (hl), a
pop hl
pop bc
push bc
push hl
; cy-x
ld a, l
sub b
ld l, a
; cx-y
ld a, h
sub c
ld b, a
; cx+y
ld a, h
add c
ld c, a
xor a ; A=0 and clear carry
sla l
ld h, tbl_scraddr/256 ; this requires tbl_scraddr be aligned to a 256 byte boundary
adc h ; A = H + carry flag
ld h, a ; write it back
; look up screen address from the table
ld a, (hl)
inc l
ld h, (hl)
ld l, a
ld a, c
and ~7
rrca
rrca
rrca
add l
ld l, a
ld a, c
ld de, col2pix
and 7
add e
ld e, a
ld a, (de)
or (hl)
ld (hl), a
ld a, l
and ~31
ld l, a
ld a, b
and ~7
rrca
rrca
rrca
add l
ld l, a
ld a, b
ld de, col2pix
and 7
add e
ld e, a
ld a, (de)
or (hl)
ld (hl), a
.doneveryfirstline
pop hl
pop bc
pop de
push hl
ld h, 0
ld l, d
ld d, h
ld e, b
add hl, de
add hl, de
inc hl
inc b
ld a, l ; A is new D if we jump to .nexty soon
ld d, 0
ld e, c
or a
sbc hl, de
or a
sbc hl, de
inc hl
bit 7, h
ld d, a
jr nz, .nexty
ld d, l
pop hl
ld a, b
sub c
dec a
jr z, .justdecy ; we already drew this line (at 45 degrees)
push de
push bc
push hl
; cy-y
ld a, l
sub c
ld l, a
; cx - x + 1
ex af, af'
ld e, a
ex af, af'
ld a, h
sub b
inc a
ld c, a
; b = sx - x
ld a, b
sub e
ld b, a
xor a
sla l
ld h, tbl_scraddr/256 ; this requires tbl_scraddr be aligned to a 256 byte boundary
adc h ; A = H + carry flag
ld h, a ; write it back
; look up screen address from the table
ld a, (hl)
inc l ; this won't overflow because of align 256
ld h, (hl)
ld l, a
push hl
call draw_line_horz_knowaddr
pop de
pop hl
pop bc
push bc
push hl
; cx + sx
ld a, h
ex af, af'
ld h, a
ex af, af'
add h
ld c, a
; b = x - sx
ld a, b
sub h
ld b, a
ld h, d
ld l, e
call draw_line_horz_knowaddr
pop hl
pop bc
push bc
push hl
; cy+y
ld a, l
add c
ld l, a
; cx - x + 1
ld a, h
sub b
inc a
ld c, a
; b = sx - x
ld a, b
ex af, af'
ld b, a
ex af, af'
sub b
ld b, a
xor a
sla l
ld h, tbl_scraddr/256 ; this requires tbl_scraddr be aligned to a 256 byte boundary
adc h ; A = H + carry flag
ld h, a ; write it back
; look up screen address from the table
ld a, (hl)
inc l ; this won't overflow because of align 256
ld h, (hl)
ld l, a
push hl
call draw_line_horz_knowaddr
pop de
pop hl
pop bc
push bc
push hl
; cx + sx
ld a, h
ex af, af'
ld h, a
ex af, af'
add h
ld c, a
; b = x - sx
ld a, b
sub h
ld b, a
ld h, d
ld l, e
call draw_line_horz_knowaddr
pop hl
pop bc
pop de
.justdecy
dec c
ex af, af'
ld a, b
ex af, af'
push hl
.nexty
ld a, c
sub b
pop hl
jp p, .nextline
ret
; H - cx
; L - cy
; D - radius
draw_fill_circle:
; B = xoffset
xor a
; C = yoffset
ld b, a
ld c, d
ld d, a
; push everything
push de
; draw the very first line
push bc
push hl
; cy+x
;ld a, l
;add b
;ld l, a ; B is 0 here
ld a, c
add c
inc a
ld b, a
; cx-y
ld a, h
sub c
ld c, a
call draw_line_horz_xor
jp .doneveryfirstline
.nextline
push de
push bc
push hl
; cy+x
ld a, l
add b
ld l, a
ld a, c
add c
inc a
ld b, a
; cx-y
ld a, h
sub c
ld c, a
call draw_line_horz_xor
pop hl
pop bc
push bc
push hl
; cy-x
ld a, l
sub b
ld l, a
ld a, c
add c
inc a
ld b, a
; cx-y
ld a, h
sub c
ld c, a
call draw_line_horz_xor
.doneveryfirstline
pop hl
pop bc
; work out new d
pop de
push hl
ld h, 0
ld l, d
ld d, h
ld e, b
add hl, de
add hl, de
inc hl
inc b
ld a, l ; A is new D if we jump to .nexty soon
ld d, 0
ld e, c
or a
sbc hl, de
or a
sbc hl, de
inc hl
bit 7, h
ld d, a
jr nz, .nexty
ld d, l
pop hl
ld a, b
sub c
dec a
jr z, .justdecy ; we already drew this line (at 45 degrees)
push de
push bc
push hl
; cy-y
ld a, l
sub c
ld l, a
; cx - x + 1
ld a, h
sub b
inc a
ld c, a
; b = x+x-1
ld a, b
add b
dec a
ld b, a
call draw_line_horz_xor
pop hl
pop bc
push bc
push hl
; cy+y
ld a, l
add c
ld l, a
; cx - x + 1
ld a, h
sub b
inc a
ld c, a
; b = x+x-1
ld a, b
add b
dec a
ld b, a
call draw_line_horz_xor
pop hl
pop bc
pop de
.justdecy
dec c
push hl
.nexty
ld a, c
sub b
pop hl
jp p, .nextline
ret
IF 0
; C - column
; L - row
plot:
xor a
sla l
ld h, tbl_scraddr/256 ; this requires tbl_scraddr be aligned to a 256 byte boundary
adc h ; A = H + carry flag
ld h, a ; write it back
; look up screen address from the table
ld a, (hl)
inc l
ld h, (hl)
ld l, a
ld a, c
and ~7
rrca
rrca
rrca
add l
ld l, a
ld a, c
exx
ld hl, col2pix
and 7
add l
ld l, a
ld a, (hl)
exx
or (hl)
ld (hl), a
ret
ENDIF
; B - number of pixels
; C - column offset
; L - row
draw_line_horz:
xor a
sla l
ld h, tbl_scraddr/256 ; this requires tbl_scraddr be aligned to a 256 byte boundary
adc h ; A = H + carry flag
ld h, a ; write it back
; look up screen address from the table
ld a, (hl)
inc l ; this won't overflow because of align 256
ld h, (hl)
; work out the column address
ld l, a
draw_line_horz_knowaddr:
ld a, c
and ~7
rrca
rrca
rrca
add l
ld l, a
; if C&7 == 0 and B&7 == 0, just draw the middle of the line
ld a, c
or b
and 7
jr z, .drawlinemiddle
.notmiddleonly
; work out (C&7), amount of pixels to draw on left hand side
ld a, c
and 7
jr z, .justdrawrhs
ld e, a ; remember (C&7) in E. will subtract this from line length in a bit
; if 8-C&7 > B (line length), we only draw 1 cell and not all of the pixels
ld a, 8
sub e
cp b
jr nc, .draw1cellpartial
ld c, a
ld a, e ; amount to shift down
exx
ld hl, srlFFtable
add l
ld l, a
ld a, (hl)
exx
; put lefthandside
or (hl)
ld (hl), a
inc l
ld a, b
sub c ; adjust line length for pixels we just drew
ld b, a
and 7
.justdrawrhs
ld e, b ; remember B&7 in E
ld a, b
and ~7
rrca
rrca
rrca
ld b, a
or a
jr z, .dontdrawmiddle
ld a, #FF
.middleloop
ld (hl), a
inc l
djnz .middleloop
.dontdrawmiddle
; work out (B&7), amount of pixels to draw on left hand side
ld a, e
and 7
dec a
ret m
jr z, .putlinerhsnoshift ; no need to shift
exx
ld hl, sra80table
add l
ld l, a
ld a, (hl)
exx
; put righthandside
or (hl)
ld (hl), a
ret
.putlinerhsnoshift
ld a, #80
or (hl)
ld (hl), a
ret
.draw1cellpartial
dec b
ld a, #80
jr z, .nosra
ld a, b
exx
ld hl, sra80table
add l
ld l, a
ld a, (hl)
exx
.nosra
ld b, e
.rrcaagain
rrca
djnz .rrcaagain
or (hl)
ld (hl), a
ret
.drawlinemiddle
ld a, b
rrca
rrca
rrca
ld b, a
ld a, #FF
.middleonlyloop
ld (hl), a
inc l
djnz .middleonlyloop
ret
; B - number of pixels
; C - column offset
; L - row
draw_line_horz_xor:
xor a
sla l
ld h, tbl_scraddr/256 ; this requires tbl_scraddr be aligned to a 256 byte boundary
adc h ; A = H + carry flag
ld h, a ; write it back
; look up screen address from the table
ld a, (hl)
inc l ; this won't overflow because of align 256
ld h, (hl)
; work out the column address
ld l, a
ld a, c
and ~7
rrca
rrca
rrca
add l
ld l, a
; if C&7 == 0 and B&7 == 0, just draw the middle of the line
ld a, c
or b
and 7
jr z, .drawlinemiddle
.notmiddleonly
; work out (C&7), amount of pixels to draw on left hand side
ld a, c
and 7
jr z, .justdrawrhs
ld e, a ; remember (C&7) in E. will subtract this from line length in a bit
; if 8-C&7 > B (line length), we only draw 1 cell and not all of the pixels
ld a, 8
sub e
cp b
jr nc, .draw1cellpartial
ld c, a
ld a, e ; amount to shift down
exx
ld hl, srlFFtable
add l
ld l, a
ld a, (hl)
exx
; put lefthandside
xor (hl)
ld (hl), a
inc l
ld a, b
sub c ; adjust line length for pixels we just drew
ld b, a
and 7
.justdrawrhs
ld e, b ; remember B&7 in E
ld a, b
and ~7
rrca
rrca
rrca
ld b, a
or a
jr z, .dontdrawmiddle
;ld a, #FF
ld c, #FF
.middleloop
ld a, c
xor (hl)
ld (hl), a
inc l
djnz .middleloop
.dontdrawmiddle
; work out (B&7), amount of pixels to draw on left hand side
ld a, e
and 7
dec a
ret m
jr z, .putlinerhsnoshift ; no need to shift
exx
ld hl, sra80table
add l
ld l, a
ld a, (hl)
exx
; put righthandside
xor (hl)
ld (hl), a
ret
.putlinerhsnoshift
ld a, #80
xor (hl)
ld (hl), a
ret
.draw1cellpartial
dec b
ld a, #80
jr z, .nosra
ld a, b
exx
ld hl, sra80table
add l
ld l, a
ld a, (hl)
exx
.nosra
ld b, e
.rrcaagain
rrca
djnz .rrcaagain
xor (hl)
ld (hl), a
ret
.drawlinemiddle
ld a, b
rrca
rrca
rrca
ld b, a
ld c, #FF
.middleonlyloop
ld a, c
xor (hl)
ld (hl), a
inc l
djnz .middleonlyloop
ret
cls:
di ;disable interrupt
ld (.stack+1), sp ;store current stack pointer
ld sp, 16384 + 6144 + 768
ld b, 128 ; clear attribs in 128 * 3 pushes
ld h, a
ld l, a
.attribloop
push hl
push hl
push hl
djnz .attribloop
ld hl, 0
ld b, l ;set B to 0. it causes that DJNZ will repeat 256 times
.loop1
push hl ;store hl on stack
push hl ;next
push hl ;these four push instruction stores 8 bytes on stack
push hl
push hl ;store hl on stack
push hl ;next
push hl ;these four push instruction stores 8 bytes on stack
push hl
push hl ;store hl on stack
push hl ;next
push hl ;these four push instruction stores 8 bytes on stack
push hl
djnz .loop1 ;repeat for next 12*2 bytes
.stack
ld sp, 0 ;parameter will be overwritten
ei
ret
data_section:
IF RETURN_TO_BASIC
stashed_iy dw 0
stashed_hl_alt dw 0
stashed_sp dw 0
ENDIF
ALIGN 256
; screen address table. This must be 256 byte aligned
tbl_scraddr dw SCRBUF_BASEADDR + #0000, SCRBUF_BASEADDR + #0100, SCRBUF_BASEADDR + #0200, SCRBUF_BASEADDR + #0300, SCRBUF_BASEADDR + #0400, SCRBUF_BASEADDR + #0500, SCRBUF_BASEADDR + #0600, SCRBUF_BASEADDR + #0700
dw SCRBUF_BASEADDR + #0020, SCRBUF_BASEADDR + #0120, SCRBUF_BASEADDR + #0220, SCRBUF_BASEADDR + #0320, SCRBUF_BASEADDR + #0420, SCRBUF_BASEADDR + #0520, SCRBUF_BASEADDR + #0620, SCRBUF_BASEADDR + #0720
dw SCRBUF_BASEADDR + #0040, SCRBUF_BASEADDR + #0140, SCRBUF_BASEADDR + #0240, SCRBUF_BASEADDR + #0340, SCRBUF_BASEADDR + #0440, SCRBUF_BASEADDR + #0540, SCRBUF_BASEADDR + #0640, SCRBUF_BASEADDR + #0740
dw SCRBUF_BASEADDR + #0060, SCRBUF_BASEADDR + #0160, SCRBUF_BASEADDR + #0260, SCRBUF_BASEADDR + #0360, SCRBUF_BASEADDR + #0460, SCRBUF_BASEADDR + #0560, SCRBUF_BASEADDR + #0660, SCRBUF_BASEADDR + #0760
dw SCRBUF_BASEADDR + #0080, SCRBUF_BASEADDR + #0180, SCRBUF_BASEADDR + #0280, SCRBUF_BASEADDR + #0380, SCRBUF_BASEADDR + #0480, SCRBUF_BASEADDR + #0580, SCRBUF_BASEADDR + #0680, SCRBUF_BASEADDR + #0780
dw SCRBUF_BASEADDR + #00a0, SCRBUF_BASEADDR + #01a0, SCRBUF_BASEADDR + #02a0, SCRBUF_BASEADDR + #03a0, SCRBUF_BASEADDR + #04a0, SCRBUF_BASEADDR + #05a0, SCRBUF_BASEADDR + #06a0, SCRBUF_BASEADDR + #07a0
dw SCRBUF_BASEADDR + #00c0, SCRBUF_BASEADDR + #01c0, SCRBUF_BASEADDR + #02c0, SCRBUF_BASEADDR + #03c0, SCRBUF_BASEADDR + #04c0, SCRBUF_BASEADDR + #05c0, SCRBUF_BASEADDR + #06c0, SCRBUF_BASEADDR + #07c0
dw SCRBUF_BASEADDR + #00e0, SCRBUF_BASEADDR + #01e0, SCRBUF_BASEADDR + #02e0, SCRBUF_BASEADDR + #03e0, SCRBUF_BASEADDR + #04e0, SCRBUF_BASEADDR + #05e0, SCRBUF_BASEADDR + #06e0, SCRBUF_BASEADDR + #07e0
dw SCRBUF_BASEADDR + #0800, SCRBUF_BASEADDR + #0900, SCRBUF_BASEADDR + #0a00, SCRBUF_BASEADDR + #0b00, SCRBUF_BASEADDR + #0c00, SCRBUF_BASEADDR + #0d00, SCRBUF_BASEADDR + #0e00, SCRBUF_BASEADDR + #0f00
dw SCRBUF_BASEADDR + #0820, SCRBUF_BASEADDR + #0920, SCRBUF_BASEADDR + #0a20, SCRBUF_BASEADDR + #0b20, SCRBUF_BASEADDR + #0c20, SCRBUF_BASEADDR + #0d20, SCRBUF_BASEADDR + #0e20, SCRBUF_BASEADDR + #0f20
dw SCRBUF_BASEADDR + #0840, SCRBUF_BASEADDR + #0940, SCRBUF_BASEADDR + #0a40, SCRBUF_BASEADDR + #0b40, SCRBUF_BASEADDR + #0c40, SCRBUF_BASEADDR + #0d40, SCRBUF_BASEADDR + #0e40, SCRBUF_BASEADDR + #0f40
dw SCRBUF_BASEADDR + #0860, SCRBUF_BASEADDR + #0960, SCRBUF_BASEADDR + #0a60, SCRBUF_BASEADDR + #0b60, SCRBUF_BASEADDR + #0c60, SCRBUF_BASEADDR + #0d60, SCRBUF_BASEADDR + #0e60, SCRBUF_BASEADDR + #0f60
dw SCRBUF_BASEADDR + #0880, SCRBUF_BASEADDR + #0980, SCRBUF_BASEADDR + #0a80, SCRBUF_BASEADDR + #0b80, SCRBUF_BASEADDR + #0c80, SCRBUF_BASEADDR + #0d80, SCRBUF_BASEADDR + #0e80, SCRBUF_BASEADDR + #0f80
dw SCRBUF_BASEADDR + #08a0, SCRBUF_BASEADDR + #09a0, SCRBUF_BASEADDR + #0aa0, SCRBUF_BASEADDR + #0ba0, SCRBUF_BASEADDR + #0ca0, SCRBUF_BASEADDR + #0da0, SCRBUF_BASEADDR + #0ea0, SCRBUF_BASEADDR + #0fa0
dw SCRBUF_BASEADDR + #08c0, SCRBUF_BASEADDR + #09c0, SCRBUF_BASEADDR + #0ac0, SCRBUF_BASEADDR + #0bc0, SCRBUF_BASEADDR + #0cc0, SCRBUF_BASEADDR + #0dc0, SCRBUF_BASEADDR + #0ec0, SCRBUF_BASEADDR + #0fc0
dw SCRBUF_BASEADDR + #08e0, SCRBUF_BASEADDR + #09e0, SCRBUF_BASEADDR + #0ae0, SCRBUF_BASEADDR + #0be0, SCRBUF_BASEADDR + #0ce0, SCRBUF_BASEADDR + #0de0, SCRBUF_BASEADDR + #0ee0, SCRBUF_BASEADDR + #0fe0
dw SCRBUF_BASEADDR + #1000, SCRBUF_BASEADDR + #1100, SCRBUF_BASEADDR + #1200, SCRBUF_BASEADDR + #1300, SCRBUF_BASEADDR + #1400, SCRBUF_BASEADDR + #1500, SCRBUF_BASEADDR + #1600, SCRBUF_BASEADDR + #1700
dw SCRBUF_BASEADDR + #1020, SCRBUF_BASEADDR + #1120, SCRBUF_BASEADDR + #1220, SCRBUF_BASEADDR + #1320, SCRBUF_BASEADDR + #1420, SCRBUF_BASEADDR + #1520, SCRBUF_BASEADDR + #1620, SCRBUF_BASEADDR + #1720
dw SCRBUF_BASEADDR + #1040, SCRBUF_BASEADDR + #1140, SCRBUF_BASEADDR + #1240, SCRBUF_BASEADDR + #1340, SCRBUF_BASEADDR + #1440, SCRBUF_BASEADDR + #1540, SCRBUF_BASEADDR + #1640, SCRBUF_BASEADDR + #1740
dw SCRBUF_BASEADDR + #1060, SCRBUF_BASEADDR + #1160, SCRBUF_BASEADDR + #1260, SCRBUF_BASEADDR + #1360, SCRBUF_BASEADDR + #1460, SCRBUF_BASEADDR + #1560, SCRBUF_BASEADDR + #1660, SCRBUF_BASEADDR + #1760
dw SCRBUF_BASEADDR + #1080, SCRBUF_BASEADDR + #1180, SCRBUF_BASEADDR + #1280, SCRBUF_BASEADDR + #1380, SCRBUF_BASEADDR + #1480, SCRBUF_BASEADDR + #1580, SCRBUF_BASEADDR + #1680, SCRBUF_BASEADDR + #1780
dw SCRBUF_BASEADDR + #10a0, SCRBUF_BASEADDR + #11a0, SCRBUF_BASEADDR + #12a0, SCRBUF_BASEADDR + #13a0, SCRBUF_BASEADDR + #14a0, SCRBUF_BASEADDR + #15a0, SCRBUF_BASEADDR + #16a0, SCRBUF_BASEADDR + #17a0
dw SCRBUF_BASEADDR + #10c0, SCRBUF_BASEADDR + #11c0, SCRBUF_BASEADDR + #12c0, SCRBUF_BASEADDR + #13c0, SCRBUF_BASEADDR + #14c0, SCRBUF_BASEADDR + #15c0, SCRBUF_BASEADDR + #16c0, SCRBUF_BASEADDR + #17c0
dw SCRBUF_BASEADDR + #10e0, SCRBUF_BASEADDR + #11e0, SCRBUF_BASEADDR + #12e0, SCRBUF_BASEADDR + #13e0, SCRBUF_BASEADDR + #14e0, SCRBUF_BASEADDR + #15e0, SCRBUF_BASEADDR + #16e0, SCRBUF_BASEADDR + #17e0
col2pix db #80, #40, #20, #10, #08, #04, #02, #01
; #FF >> 0..7
srlFFtable db #FF, #7F, #3F, #1F, #0F, #07, #03, #01
; same but for arithmetic shift right
sra80table db #80, #C0, #E0, #F0, #F8, #FC, #FE, #FF
endofprog:
Last edited by ParadigmShifter on Fri Dec 08, 2023 7:19 pm, edited 2 times in total.
Re: Bullet Spray routines
So doing it your way means you never have to deal with the threshold thing. Cos you are just checking for bigger than zero.
Sounds pretty good.
Sounds pretty good.
Re: Bullet Spray routines
exactly!ParadigmShifter wrote: ↑Thu Dec 07, 2023 7:24 pm Bresenham is only really useful if you want to move 1 pixel at a time, which bullets rarely will want to I expect.
i've been developing simple integer-only hexgrid-voronoi collision detection algorithms which should obviate that pre-requisite.ParadigmShifter wrote: ↑Thu Dec 07, 2023 7:24 pm It's probably better to just use vectors but maintain fractional part (say as the lower 8 bits) to keep track of the overflow.
If bullets move too fast (so they can pass through things) you'll need line segment collision though.
i'm not sure what you mean by using vectors - i understand it to mean the relationship between two points - but does it refer to a specific speccy progamming technique?
Challenge AcceptedParadigmShifter wrote: ↑Thu Dec 07, 2023 7:24 pm I think 100 bullets may be a tad ambitious on the speccy
See ya in 10 years!
Olde BlitzPC code requires some porting effort to run in BlitzMax - got halfway through porting an old BlitzPC Complex-X remake i originally made for revstu into BlitzMax before putting it on hold - though it'd probably be a lot quicker for someone who wasn't learning the newest version of the language as they went along!
I love how you can learn so much just from playing around from game problems like this I learned how to pass trig exams in school, but i didn't start to understand it until well after i found reason to learn it. Math lessons teach us to avoid trig - it's such a sin.
- ParadigmShifter
- Manic Miner
- Posts: 868
- Joined: Sat Sep 09, 2023 4:55 am
Re: Bullet Spray routines
You need to check against the cosine of the cone half angle (so 45 degrees for a 90 degree cone, cos(45deg) = 1/sqrt(2)) - which I did mention.
> 0 only works for detecting in front (and < 0 means behind)
> 0 only works for detecting in front (and < 0 means behind)
- ParadigmShifter
- Manic Miner
- Posts: 868
- Joined: Sat Sep 09, 2023 4:55 am
Re: Bullet Spray routines
A vector is just a set of coordinates (relative to the origin for position vectors, relative to the object position for a facing vector).
So if you have a player at position (2, 1) the position vector is just (2, 1).
Facing northeast is relative to the player position so it's just (1/sqrt(2), 1/sqrt(2)).
So vectors as in maths rather than say the C++ container/vector CPU instructions or whatever.
So if you have a player at position (2, 1) the position vector is just (2, 1).
Facing northeast is relative to the player position so it's just (1/sqrt(2), 1/sqrt(2)).
So vectors as in maths rather than say the C++ container/vector CPU instructions or whatever.
- ParadigmShifter
- Manic Miner
- Posts: 868
- Joined: Sat Sep 09, 2023 4:55 am
Re: Bullet Spray routines
Easy way to get around that issue - in assembler or any language which has fixed size integer variables is to split your circle into 256 units instead of 360 and use integer wrap around.
Although any power of 2 also works well (doubt you'll want more than 256 units of rotation on a speccy though, you may want less e.g. number of possible facings (8 or 16 say)). PS1 (which only had integer maths, no floating point, all fixed point) used 4096 units for 360 degrees.
Then you can use a lookup table for sin and cos if you really need that (chances are you don't need to use sin or cos very often if at all).
Re: Bullet Spray routines
Well, if you want to move two pixels at a time you can always call it twiceBresenham is only really useful if you want to move 1 pixel at a time, which bullets rarely will want to I expect.
- ParadigmShifter
- Manic Miner
- Posts: 868
- Joined: Sat Sep 09, 2023 4:55 am
Re: Bullet Spray routines
Nah you just want to use vectors again.
If you want to move from a to b in x frames do this:
position at time t = a + (b-a) * (t / x)
and let t range from 0 to 1. With that method you can use a curve instead of a straight line to vary the speed along the path if you want.
This function (lerp - Linear intERPolation)
lerp(a, b, t) = a + (b-a) * t // move from a to b, returns a when t is 0, b when t is 1
is so useful it deserves its own function.
If you want to use a bullet velocity instead (don't care about the endpoint) just use
position at time t = initial pos + vel * t
or just do
pos.x = pos.x + vel.x
pos.y = pos.y + vel.y
; and z if in 3d
each frame (looks crap if frame rate is not constant).
For non constant frame rates do
pos.x = pos.x + deltaT * vel.x
pos.y = pos.y + deltaT * vel.y
instead (deltaT is number of frames since last render). That looks crap if your velocity changes and the frame rate is variable though
where t is the bullet lifetime counter and vel is the velocity.
You want to use vectors instead of the y = mx + c equation of a line since that does not work for firing straight up or down. Vectors work for any direction (and in any number of dimensions).
You'll want to use fixed point or floating point numbers to be accurate of course.
- ParadigmShifter
- Manic Miner
- Posts: 868
- Joined: Sat Sep 09, 2023 4:55 am
Re: Bullet Spray routines
Missed that joke first time around lol Here's 2 morelexi wrote: ↑Fri Dec 08, 2023 6:07 pm I love how you can learn so much just from playing around from game problems like this I learned how to pass trig exams in school, but i didn't start to understand it until well after i found reason to learn it. Math lessons teach us to avoid trig - it's such a sin.
- ParadigmShifter
- Manic Miner
- Posts: 868
- Joined: Sat Sep 09, 2023 4:55 am
Re: Bullet Spray routines
Mistake in my normalise function... should have been
normalise(v) = v / sqrt(dot_product(v, v))
normalise(v) = v / sqrt(dot_product(v, v))
Re: Bullet Spray routines
I get that bullet code you write in regards to velocity but the other stuff goes over my head.
I'd need to study normalise and dot product to really understand what's going on.
Such a sin... d'ohhh
And the TAN line
Would it easy using this maths to get the computer to control a car on super sprint?
Codemasters seem to have cheated on their early games as the computer car can't be interrupted. It drives on rails.
Or to allow the computer to move the ship in gravity force to a certain place.
I'd need to study normalise and dot product to really understand what's going on.
Such a sin... d'ohhh
And the TAN line
Would it easy using this maths to get the computer to control a car on super sprint?
Codemasters seem to have cheated on their early games as the computer car can't be interrupted. It drives on rails.
Or to allow the computer to move the ship in gravity force to a certain place.