Jump to content
pvmpkin

Timers/Delays

Recommended Posts

hey everyone,

 

I am wondering if there is a way to briefly delay a branch from executing but not fail to do so.

 

I have a timer that increments to 10 then resets, I compare against it but of course a branch fails to execute.

 

I looked at INTIM but that results in flash so I suspect is improper use of this.

Share this post


Link to post
Share on other sites

Please supply your code here and explain your purpose. If we know what you are trying to achieve (and why), we could probably help you out!

  • Like 1

Share this post


Link to post
Share on other sites

Of course, apologies.

lda punch				;check if punch is not zero
beq .Damage				;if zero jump to branch that handles player damage

lda #$0f				;set white
sta COLUP1				;set GRP1 white

lda timer				;load timer value (0-8)
cmp #8					;check if timer reaches 8
bne .Safe				;jump out

lda #0					
sta type				;remove enemy type

jmp .Safe

I have enemies that appear on screen that disappear if punched, of course if GRPx are colliding.

The intention is to have GRP1 flash white briefly before it disappears.

 

I hope this makes sense.

Share this post


Link to post
Share on other sites

I would be happy to help, but there's not enough context, either in your initial description, or in the subsequent code, for me to understand exactly what you're trying to do. You set the sprite colour to white. You don't show where you reset it (to black?). Or, where or why you increment timer - assuming 8 is your delay.  Why not post all the code, then it will be easier for people to review/help.  Good job, though, making an effort to program in assembler!

Share this post


Link to post
Share on other sites

Here is the full mess of code. I am very bad at commenting things.

 

The initial colour of GRP1 is defined outside of the kernel depending on what the enemy type is. If the type is 0 it is not drawn. 

 

My intention is merely to have it flash white briefly before it disappears.

main.asm

Share this post


Link to post
Share on other sites

I'm a bit confused. The game runs and enemies flash white when hit. My fist guess is that you noticed some flashes are shorter than others. This is what I believe you are doing (based on a quick look at your code):

 

When an enemy is hit, set it's color to white until timer is a chosen value (8 in snippet, 1 in main.asm), after which the enemy is removed from the game.

But the time it takes for timer to get to that number after an enemy is hit is not constant, because you do not reset its value when the enemy is hit.

timer is instead incremented every frame (?) and simply loops from 1 - 9.

 

I also noticed a lot of lines like these:
 

cmp TREEHOUSE

 

and 

 

ldx FOREST

 

Did you mean: cmp #TREEHOUSE and ldx #FOREST here as these are constants and not labels? If I'm not mistaken, these lines currently are instructing the Atari to compare the accumulator against the current value of spritecolourptr and load into X the current value of spriteprt instead of the values of TREEHOUSE and FOREST.

Edited by MLdB

Share this post


Link to post
Share on other sites
Just now, MLdB said:

I'm a bit confused. The game runs and enemies flash white when hit. My fist guess is that you noticed some flashes are shorter than others. This is what I believe you are doing (based on a quick look at your code):

 

When an enemy is hit, set it's color to white until timer is a chosen value (8 in snippet, 1 in main.asm), after which the enemy is removed from the game.

But the time it takes for timer to get to that number after an enemy is hit is not constant, because you do not reset its value when the enemy is hit.

timer is instead incremented every frame (?) and simply loops from 1 - 9.

 

I also noticed a lot of lines like these:
 

cmp TREEHOUSE

 

and 

 

ldx FOREST

 

Did you mean: cmp #TREEHOUSE and ldx #FOREST here as these are constants and not labels? If I'm not mistaken, these lines currently are instructing the Atari to compare the accumulator against the current value of spritecolourptr and load into X the current value of spriteprt instead of the values of TREEHOUSE and FOREST.

Thank you for looking at this.

 

The problem is that sometimes because it is not really a timer, sometimes it fails which results in the enemy remaining on screen but turned white. Essentially I want to delay the code that removes the enemy briefly so that the flash is noticeable. I had planned to give it the (colour cycle) cycle so that it is not always white, was going to see how that looked after I had fixed this issue.

 

As for some of the comparisons you are correct. I should be doing #FOREST, but it was a typo to leave out the # and the code worked regardless. 

Share this post


Link to post
Share on other sites

I'll get some time to look further at your code later today.
A brief peek, though, prompts me to explain a better way to do multiple if statements. 

Rather than checking for a value and branching, checking for another and branching, etc., you can use tables where the value you check is an index, and the table points to code to execute. A jump vector table, in other words.

 

For example, where you have ...

	    lda worldpos

		cmp #$80
		beq .Fireball
		cmp #$83
		beq .SnekRT
		cmp #$84
		beq .Brute
		cmp #$85
		beq .Brute
		cmp #$86
		beq .Brute
		cmp #$87
		beq .Spikes
		cmp #$88
		beq .Bats
		cmp #$89
		beq .Bats
		cmp #$8a
		beq .Insect

 

Well there we can use (worldpos*2) as an index to a table of words, like this...

 

		lda worldpos
		asl						; drop the high bit, and *2 for the rest
		tax

		lda VectorTable,x
		sta zp
		lda VectorTable+1,x
		sta zp+1

		jmp (zp)				; vector to the handling code

; The table...

VectorTable
	.word .Fireball		; handles $80
	.word .None			; unhandled $81 - dummy handler
	.word .None			; $82
	.word .SnekRT		; $83, etc...
	.word .Brute
	.word .Brute
	; etc. We need to make sure there is an entry in the table for every single possible value of worldpos (the low 7 bits) that is actually possible.

; The handlers...

.None	rts

.Fireball	
	; whatever the fireball thing does here
	rts

.SnekRT
	; whatever the "snekRT' thing does here
	rts

.Brute
	; whatever the 'brute' thing does here
	rts

This is a much better way, as it doesn't involve a chain of compares.

That is, the time to process any single one is slightly slower but if you were for example checking the last one in the earlier code, you have to run through all the other comparisons. 

There are a few places in your code where you can use this idea of vectors instead of multiple compares. I'd advise you to review/consider.

 

 

Edited by Andrew Davie
  • Like 1

Share this post


Link to post
Share on other sites

Thank you for this suggestion. I honestly didn't know another way of doing it but knew that there had to be an alternative I couldn't think of. It is absolutely something I want to look into, I ran up against a number of branch out of range errors with the implementation I had.

Share this post


Link to post
Share on other sites
3 minutes ago, pvmpkin said:

As for some of the comparisons you are correct. I should be doing #FOREST, but it was a typo to leave out the # and the code worked regardless. 

Miraculously :)

 

It will fail whenever the value stored at address FOREST (or TREEHOUSE) changes between the time it was written to variable XYZ and when it is loaded back in from XYZ and compared to the value stored at address FOREST again.

 

  • Like 1

Share this post


Link to post
Share on other sites
Just now, MLdB said:

It will fail whenever the value stored at address FOREST (or TREEHOUSE) changes between the time it was written to variable XYZ and when it is loaded back in from XYZ and compared to the value stored at address FOREST again.

 

Of course. I have since fixed the # in my code, I had some constants doing that.

Share this post


Link to post
Share on other sites

Andrew, your code mentions zp, is that a variable that I don't have ? It also indicates jmp(zp) which I have never seen before. I was going to ask if there was a way to jump to a subroutine based on a variable, is that what this does ?

 

Also I have some values that count backwards, is this handled in the code ? Do I only need to include a 'range' ?

Edited by pvmpkin

Share this post


Link to post
Share on other sites

zp is short for Zero Page, meaning any variable in RAM ($80-$FF).

 

jmp (zp) will jump to the address stored in ram locations zp and zp+1 (16 bits combined)

  • Like 1

Share this post


Link to post
Share on other sites
12 minutes ago, MLdB said:

zp is short for Zero Page, meaning any variable in RAM ($80-$FF).

 

jmp (zp) will jump to the address stored in ram locations zp and zp+1 (16 bits combined)

thank you but I don't understand this as an answer to my question. It seems to be a yes but I don't know if I am missing something.

Share this post


Link to post
Share on other sites
3 hours ago, pvmpkin said:

Andrew, your code mentions zp, is that a variable that I don't have ? It also indicates jmp(zp) which I have never seen before. I was going to ask if there was a way to jump to a subroutine based on a variable, is that what this does ?

 

Also I have some values that count backwards, is this handled in the code ? Do I only need to include a 'range' ?

Yes, zp is placeholder name for a variable that needs to be in the zeropage (Atari 2600 memory is in the zeropage).  You need to add it in your variable table... and you should probably rename it something like enemyRoutinePtr to clarify it's purpose.

 

 

  • Like 1

Share this post


Link to post
Share on other sites
4 hours ago, pvmpkin said:

Andrew, your code mentions zp, is that a variable that I don't have ? It also indicates jmp(zp) which I have never seen before. I was going to ask if there was a way to jump to a subroutine based on a variable, is that what this does ?

 

Also I have some values that count backwards, is this handled in the code ? Do I only need to include a 'range' ?

 

"jmp (addr)" will jump to the address stored in the zero page variable "addr". More specifically, the low byte of the destination in "addr" and the high byte of the destination in "addr+1". I used "zp" to indicate that it sould be a variable in zero page. You can, and should, call the variable anything relevant to your intended usage. But it must be in zero page.

 

Another way to jump to a routine is to push the routine's address (-1) onto the stack, and then call "rts".  The rts instruction pulls a 2-byte address off the stack, adds one, and puts that value in the PC address counter, so execution will then continue from that address. This is an oddbod usage, but perfectly valid. I forget if you have to push the high byte onto the stack first, or the low byte. Would be pretty easy to find out. One will work as expected, and the other will work... as expected ;)

 

  • Like 1

Share this post


Link to post
Share on other sites
48 minutes ago, Andrew Davie said:

I forget if you have to push the high byte onto the stack first, or the low byte.

I guess it will be high first in, so low is first out as 6502 is little endian, expecting the low byte to be read first.

  • Like 1

Share this post


Link to post
Share on other sites

Thank you all for the explanation, I believe I understand it now so I'll see if I can implement this.

 

Coming back to my original question, is there no way to briefly delay execution of code in the way I need ?

Share this post


Link to post
Share on other sites

You need one timer variable per event that you want to delay and one frame counter variable if you need to delay one or more events for more than 256 frames. 

 

For the case you describe, let's name these variables framecounter and deathseqtim (for death sequence timer). Once every frame you do:

 

	inc framecounter

 

Before an enemy enters the screen you make sure to clear the death sequence timer:

	lda #0
	sta deathseqtim         ; as long as deathseqtim is 0, the enemy is still alive


When an enemy is killed you do:

	lda #DEATHSEQDURATION   ; duration of sequence in frames.
	sta deathseqtim         ; a non-zero value indicates the enemy died.

 

Now as long as an enemy (alive or dead) is on screen, you do:

	lda deathseqtim
	beq SkipDeathSeq        ; Remember that 0 means the enemy is still alive.

	; deathsegtim is non-zero, the enemy has died.
	
	lda #DEADENEMYCOLOR
	sta COLUP0              ; Or "sta enemy_sprite_color" if needed

	; You only need the next three lines if the death sequence should take longer 
	; than 256 frames. It enables you to have the timer count down only on every 
	; 2nd, 4th, 8th, etc frame.
	lda framecounter
	and #%00000001          ; Count down on even frames only, as an example
	bne SkipDeathSeq
	
	dec deathseqtim
	bne SkipDeathSeq
    
	; The sequence timer ran out, remove the enemy from the screen.
	; (insert whatever you do to remove the enemy)
    
SkipDeathSeq:

 

 

Different combinations of values for DEATHSEQDURATION and the value used to and the framecounter with, will result in different durations for the sequence. My values here decrement the timer every even frame meaning the 30 counts will expire in 60 frames or 1 second. The timing will be very stable, just a single frame of max difference because of the low value for the and.

 

 

 

Edited by MLdB

Share this post


Link to post
Share on other sites

thank you, seems like I got it working. I needed to change some things around but I am happy with this !

hitflash2.gif

Share this post


Link to post
Share on other sites

hey @Andrew Davie

 

;        FOREST RIGHT
ForestRT        subroutine

                lda #1
                sta visible

                lda worldpos
                asl			
                tax

                lda VectorTable,x
                sta zp
                lda VectorTable+1,x
                sta zp+1

                jmp (zp)		

; The table...

VectorTable
                .word .Well
                .word .Gremlin
                .word .None
                .word .Well

.Well:          jsr SetWell
				jmp .Done
.Gremlin:       jsr SetGremlin
				jmp .Done

.None:			ldx #0
				stx type

.Done:          rts

 

I have this in as a test and it appears to be working from $80 onwards, but I am not seeing how I would count from $7F down, am I missing something glaringly obvious or does this code not work like that ?

 

also I assumed that the zp value needed to be a .word ? I tried as a byte and that didn't work for me.

Share this post


Link to post
Share on other sites
1 hour ago, pvmpkin said:

hey @Andrew Davie

 

;        FOREST RIGHT
ForestRT        subroutine

                lda #1
                sta visible

                lda worldpos
                asl			
                tax

                lda VectorTable,x
                sta zp
                lda VectorTable+1,x
                sta zp+1

                jmp (zp)		

; The table...

VectorTable
                .word .Well
                .word .Gremlin
                .word .None
                .word .Well

.Well:          jsr SetWell
				jmp .Done
.Gremlin:       jsr SetGremlin
				jmp .Done

.None:			ldx #0
				stx type

.Done:          rts

 

I have this in as a test and it appears to be working from $80 onwards, but I am not seeing how I would count from $7F down, am I missing something glaringly obvious or does this code not work like that ?

 

also I assumed that the zp value needed to be a .word ? I tried as a byte and that didn't work for me.

 

It's mostly OK.

Yes, "zp" is two bytes (.word, or better "ds 2"). You're storing into both of them with the "sta zp" and "sta zp+1".

Since you had the top bit ($80) set in your values, I advised how to strip this off and still use the values to index the table.

So, in other words, the top bit is ignored (discarded by the "asl"). If you want to use $7F down, then you have to re-think things.

One way, and NOT the way I would recommend, is to think about what you actually have when you use $7F.  The "asl" will shift it left (multiply by 2) to give you a value of %11111110 (i.e., 254).  Since you're indexing a word table that's 127th entry.  So, if you extend that table and put a handler pointer in at entry #127 then $7F would work. But as I said, this is NOT the way to do it.

 

Consider instead if your worldpos value was simply a number counting from 0 (and not $80).  Then you simply take the worldpos, asl to get an index to a word table, and that's all you have to do.  It's your original use of adding $80 to the top bit that makes things "wrong", and why $7F doesn't work. Well, it works, but not as you would expect - you don't have an entry in the 127th position of the table.

 

A small optimisation - instead of indexing into a single word table, try dropping the "asl" and use the worldpos as a direct index into TWO tables of byte values - one pointing to the low address of each handler, and the other to the high address of the handler.  You can do that with nifty macros... here's how I do it...

 

    MAC POSVAL
    .byte {1}Handler0
    .byte {1}Handler1
;etc...
    ENDM

LoTable    POSVAL <
HiTable    POSVAL >

 

So the access would be...

                ldx worldpos
                lda LoTable,x
                sta zp
                lda HiTable,x
                sta zp+1
                jmp (zp)	

 

Again, this relies on you dropping $80 on the top of worldpos, or masking it out before use above.

You can name LoTable and HiTable anything you want - this is just an example of "efficient" macro usage to make table definition easier.

 

Share this post


Link to post
Share on other sites

Thank you so much for steering me towards this. 

 

It was suggested to me to subtract the worldpos from $80 and then I was able to count down from $7F and works perfectly.

 

After rewriting all of the area data it's saved me a lot of ROM space, much less cycle use and I don't get that issue with out of range branches !

 

 

  • Like 1

Share this post


Link to post
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...