action games timing

페이지 1/2
| 2

By snake

Resident (53)

snake의 아바타

10-11-2016, 15:31

I wanted to keep constant music playback speed even when graphics slow down (for instance: Aleste 2 with lot of sprites on screen).
Assuming that game logic + play a chunk of music + play sound effects + rendering "ordinary" frames always takes less than 1 interrupt, in case of "crowded" frames i'm thinking to split rendering across 2 interrupts, while still performing music playback each time.
Something like that:

1) update logic
2) play music and effects
3) wait for vsync (through checking a flag).
4) as for ordinary frame, just render it and go back to 1; as for "crowded" frames, render a first part, then go to point 5
5) wait for vsync.
6) render the remaining part and play another chunk of music.
7) go back to point 1.

1) set the flag.

I'm not very expert in Msx programming, probably i wrote tons of nonsense, so please, give me your advise Smile

Login or 등록 to post comments

By Sandy Brand

Master (245)

Sandy Brand의 아바타

10-11-2016, 22:16

Why not just process the music update completely inside the interrupt handler? Smile
Usually these updates should be fast enough to not cause any issues, unless you are doing some very time critical line-interrupt timings.

By santiontanon

Paragon (1524)

santiontanon의 아바타

10-11-2016, 22:55

Exactly, I was thinking the same thing. If the music and effects are handled in the interrupt, then regardless of whether the game slows down or not, music and effects will keep playing


Enlighted (5889)


11-11-2016, 09:39

No, no, no... In your mind you may think this causes cool "bullet time"-effect, but in real life it doesn't work quite like that... When player dies without seeing the bullet hitting him, he may be frustrated... When player dies because he lost sense of time he just pushes the power button and evaluates the game as "unplayable".

Without clear picture of technical details it is hard to give any exact tips as they are not always very universal. How ever the problem in your approach is that the game speed is either full speed or half speed and for player switching between them seems quite random and unpredictable. Making poor Z80 to wait is also something that you should really try to avoid... It is like taking a kid to play a football match and not giving him any time on the field to show his skills.

If i would be doing SHMUP like Aleste 2 I would be probably using screen 4 and the game engine would look something like this:

1) Select "ready page" as current display page
2) Rotate color palette values
3) Handle input controllers
4) Handle player, enemies & bullet movement in memory
5) Do hit detection and calculate points.
6) play music
7) exit

1) output background to "VRAM write buffer"
2) output points to "VRAM write buffer"
3) output player, enemies & bullets to "VRAM write buffer"
4) set "ready page" = "VRAM write buffer"
5) "VRAM write buffer" = "VRAM write buffer" + 1 : if "VRAM write buffer" = 3 then "VRAM write buffer" = 0
6) go to step 1

If you get my idea, this way you can use all of the "extra CPU power" to draw the screen that is generally the most slow thing to do. When there is lot of stuff happening the frame rate (FPS) may drop, but the game & music speed is still constant.

By Sandy Brand

Master (245)

Sandy Brand의 아바타

12-11-2016, 00:15

I don't see why you need to put all your game logic inside the interrupt handler. If you do that then your music will slow down when it takes more than 1 frame to update the entire world, which is bad.

The only thing you need in the interrupt handler is to update music + sound, page swaps, and maybe the input scanning code in order to make sure you don't miss any key up/down events. Other than that almost everything can go in the main thread (well, it depends of course how complex you want to make access to the VDP, but you catch my drift).

By snake

Resident (53)

snake의 아바타

12-11-2016, 00:47

Thanks for suggestions, soon i will try to write some code Smile
I have one doubt: i know that while performing a vdp command interrupts must be disabled. If vdp commands are in the main program, does that may cause unexpected and unwanted interrupt skipping?

By Sandy Brand

Master (245)

Sandy Brand의 아바타

12-11-2016, 01:45

There is actually no need to disable interrupts during a VDP command Smile

The only thing you need to be careful of is that, under certain conditions, modifying certain VDP registers while a copy command is being performed can cause VRAM data corruption. As far as I know this is only the case for VDP register 18, also see VDP programming guide by Grauw.

So as long as your interrupt routine doesn't do anything crazy, this should be fine (in fact, this is the best way to improve performance on your MSX by having the Z80 and VDP perform operations in parallel).


Enlighted (6567)


12-11-2016, 08:18

Grauw says:
"This issue only occurs when you have the sprites (or screen) disabled. If you use the screen position adjust register while executing a copy command and with sprites disabled, it will corrupt the byte the VDP command is currently processing. "
This is very interesting, but actually I didn't get the same result.
In my tests the missing dots were less frequent but still there...
Is there someone who tested specifically this aspect?


Enlighted (5889)


13-11-2016, 13:28

Sandy Brand wrote:

I don't see why you need to put all your game logic inside the interrupt handler. If you do that then your music will slow down when it takes more than 1 frame to update the entire world, which is bad.

I did put it in there because I did put also input handling there. The game logic it self should not ever take more than 1 frame... unless you are trying to implement Angry birds type of physics calculations with complex kinetics or you are doing something horribly wrong... Usually the "expensive" task is drawing the screen, not calculating bullets and ships X & Y locations. If you move this to main program the speed differences in screen draw & logic directly affects the gameplay speed... You can solve this by adding waits that make the speed always same, but as I said... waiting is bad idea if you can avoid that. Other possibility is to add time counter to interrupt handler and use the time in game logic as input parameter... How ever this just makes the logic handling extremely complicated.

By Sandy Brand

Master (245)

Sandy Brand의 아바타

13-11-2016, 14:13


The game logic it self should not ever take more than 1 frame... unless you are trying to implement Angry birds type of physics calculations with complex kinetics or you are doing something horribly wrong...

Hmm, well that is maybe nice in theory but in practice virtually impossible to achieve in even a moderately complex game (even without proper physics) Smile

There are so many variables at any given time: amount of enemies remaining on the screen, amount of powerups a player has collected, player might make some poor or unexpected decisions, unforeseen balancing issues, music/sound routines need varying amounts of CPU cycles depending on speed and complexity of part of a song, etc. etc. etc.

So you need to add contingency for this in your code and prioritize the most critical elements; players are much more forgiving towards subtle visual slowdowns than to audio stutters.

Either that, or you need to grossly under utilize the CPU in an attempt to be able to handle worst-case scenarios (but how do you quantify the size of such spikes with any degree of certainty?)

Just take a look at all the shoot'em-ups, for example: Gradius, Aleste, Space Manbow. They all have gameplay slowdown here and there, but the music plays at a constant rate.

By snake

Resident (53)

snake의 아바타

28-11-2016, 17:09

I agree with sandy: both logic and drawing may unpredictably slow down the game, so they should be in main cycle.

See point 2.3)
TMS9918 requires interrupts to be disabled during two consecutive accesses to vdp port 0x99, but fortunately this doesn't seem to cause timing issues.

update game logic
draw a frame
syncronize with screen refresh

play music

This is my test code in screen 2 (SDCC, Msxdos target, no libraries required, only a suitable crt). It puts sprites, scrolls the background and plays 2 musical notes :)
Build with:
1) sdcc --code-loc 0x106 --data-loc 0 -mz80 --no-std-crt0 msxdos.rel test.c
2) hex2bin -e com test.ihx
Then put the file in a disk image with Msxdos installed.

Drawing is divided in phase 1 (1 sprite only, full speed), and phase 0 (background scroll + all 32 sprites + complex logic, very slow).
Syncronization is keep by setting and checking a flag.


// io ports for vdp and psg
__sfr __at (0x98) VDP_port1;
__sfr __at (0x99) VDP_port2;
__sfr __at (0xa0) PSG_register;
__sfr __at (0xa1) PSG_value;

// vram map
#define PATTERN_GENERATOR_2 2048
#define PATTERN_GENERATOR_3 4096
#define PATTERN_LAYOUT 6144
#define PATTERN_COLOR_1 8192
#define PATTERN_COLOR_2 10240
#define PATTERN_COLOR_3 12288
#define SPRITE_PATTERNS 14336

void memcpy_reverse (void * dst, void * src, unsigned int n) __naked {
// like memcpy but the direction is downwards.
// most code from SDCC port of Solid-C libraries.
push ix
ld ix,#0
add ix,sp
ld e,4(ix)
ld d,5(ix)
ld l,6(ix)
ld h,7(ix)
ld c,8(ix)
ld b,9(ix)
pop ix
ld a,b
or c
ret z
dst; src; n; // avoid warnings about unused variables

void blit (unsigned int target, void * source, unsigned int length) {
// copy ram to vram; 'target' is vram destination address
unsigned char *s;
s=(unsigned char*)source;
__critical {
while (length>0) { VDP_port1=*s; length--; s++; }

// music is stored in a vector that is scanned by a
// pointer during playback
volatile unsigned char music [256];
volatile unsigned char music_delay;
volatile unsigned char * music_position;

volatile int sync_flag;
volatile char vdp_status;

void interrupt_routine () __critical __interrupt(0)
__preserves_regs(a,b,c,d,e,h,l,iyl,iyh) {

// read the vdp status
vdp_status = VDP_port2;

music_delay++; // increment every interrupt

// Play a chunk of music every 30 interrupts.
// A chunk is a raw sequence of values to write
// on psg registers:
// register-value-register-value...
// When a register is expected but it's 255, the
// chunk ends. Two consecutive 255 are the end
// of the song (so the playback pointer rewinds).
if (music_delay>30) {
while (*(music_position)!=255) {
if (*music_position==255) music_position=music;

sync_flag=1; // set the syncronization flag to 1

void main () {
// counters for graphics
unsigned char frame_counter=120;
char phase=0;
unsigned char scroll_phase=0;
unsigned char scroll_frame_counter=0;

// generic counters
unsigned int kk;
unsigned char k;

// sprites and background ram buffers
unsigned char pattern_buffer [768];
unsigned char sprite_buffer [256];

// music sequence. Each chunk corresponds to a
// musical note.
music[0]=1; music[1]=1; music[2]=0;
music[3]=222; music[4]=8; music[5]=16;
music[6]=13; music[7]=9; music[8]=11;
music[9]=232; music[10]=12; music[11]=3;
music[13]=1; music[14]=1; music[15]=0;
music[16]=170; music[17]=8; music[18]=16;
music[19]=13; music[20]=9; music[21]=11;
music[22]=232; music[23]=12; music[24]=3;
music[25]=255; music[26]=255;

// define 1 sprite pattern
for (k=0;k<32;k++) sprite_buffer[k]=255;
blit (SPRITE_PATTERNS,sprite_buffer,32);

// define 3 background patterns
for (kk=0;kk<24;kk++) {
char c;
blit (PATTERN_GENERATOR_1,pattern_buffer,24);
blit (PATTERN_GENERATOR_2,pattern_buffer,24);
blit (PATTERN_GENERATOR_3,pattern_buffer,24);
blit (PATTERN_COLOR_1,pattern_buffer+24,24);
blit (PATTERN_COLOR_2,pattern_buffer+24,24);
blit (PATTERN_COLOR_3,pattern_buffer+24,24);

// set screen 2 with 16x16 sprites
__critical {
VDP_port2=0b00000010; VDP_port2=0b10000000;
VDP_port2=0b11100010; VDP_port2=0b10000001;
VDP_port2=0b00000110; VDP_port2=0b10000010;
VDP_port2=0b11111111; VDP_port2=0b10000011;
VDP_port2=0b00000011; VDP_port2=0b10000100;
VDP_port2=0b00110110; VDP_port2=0b10000101;
VDP_port2=0b00000111; VDP_port2=0b10000110;
VDP_port2=0b00001111; VDP_port2=0b10000111;

// interrupt setup
__critical { *((unsigned int*)57)=(unsigned int)interrupt_routine; }

/***** main cycle *****/
while (1) {
// change the phase every 120 frames
if (frame_counter>120) {
if (phase==0) phase=1; else phase=0;

switch (phase) {
case 0: // heavy phase
// scroll the background every 12 frames
if (scroll_frame_counter>12) {
scroll_phase++; if (scroll_phase>2) scroll_phase=0;
memcpy_reverse (pattern_buffer+767,pattern_buffer+735,736);
for (kk=0;kk<32;kk++) pattern_buffer[kk]=scroll_phase;
blit (PATTERN_LAYOUT,pattern_buffer,768);
// draw 31 sprites
for (k=1;k<32;k++) {
// each sprite plane is 4 bytes
sprite_buffer[k*4]=k*6+16; // y choordinate
if (sprite_buffer[k*4+1]>200) sprite_buffer[k*4+1]=0;
sprite_buffer[k*4+1]+=k; // x choordinate
sprite_buffer[k*4+2]=0; // sprite pattern
sprite_buffer[k*4+3]=12; // color
blit (SPRITES_ATTRIBUTES+4,sprite_buffer+4,124);
// simulate a complex logic :)
for (kk=0;kk<3000;kk++);
case 1: // light phase
// draw 1 sprite
sprite_buffer[0]=0; sprite_buffer[1]++;
if (sprite_buffer[1]>230) sprite_buffer[1]=0;
sprite_buffer[2]=0; sprite_buffer[3]=4;
blit (SPRITES_ATTRIBUTES,sprite_buffer,4);

// sync_flag is 0 -> no interrupt occurred since the
// beginning of main cycle, so wait for next interrupt.
// sync_flag is 1 -> 1 or more interrupts have already
// occurred, so don't wait.
while (sync_flag==0);

페이지 1/2
| 2