Tutorial 4 : Spirits and Sound

In this tutorial, we will cover some of the more basics of

  • Simple sound output in TRSE
  • Sprite handling
  • Joystick input
  • Program structure

 

In this program, we will display two sprites. One will move about using the joystick while making walking sounds and pooping a hearty bullet.

The program starts off without any fuzz


PROGRAM tutorial3;
VAR
	a,b, c, i, j,k,val,mainloop, time : byte;
	x,y : byte;
	sprite0data: IncBin("data/sprite_blob.bin");

	sprite_time : integer;
	sprite_x : integer;
	sprite_y : integer;
	bullet_x : integer;
	bullet_y : integer;

The only thing worth noting here is the IncBin function, which inclues binary files directly into the assembler. In addition, IncBin lets the user specify both load address and load offset of binary files. The sprites were created with CBM PRG Studio, and contains 3 sprites of each 64 bytes. Wonderful. I seriously needed four beers before managing to produce this lovely piece of art.


procedure InitializeSprites();
begin
	sprite_x:=160;
	sprite_y:=180;
	bullet_x:=400;
	bullet_y:=180;
	
	setSpriteLoc(0, $0D);
	setSpriteLoc(1, $0E);

        poke(SPRITE_BITMASK,0, %00000011);
	poke(SPRITE_COLOR,0, YELLOW);
	poke(SPRITE_COLOR,1, GREEN);

        memcpy(sprite0data, 0, SPRITE_LOC1, 63);
        memcpy(sprite0data, 128, SPRITE_LOC2, 63);
end;

This method intializes the sprites. Note that

  • Integers can have >255 assignments
  • SetSpriteLoc takes two constants: the sprite ID together with a fixed memory location, which really is multiplied with 64. The new calculated value is automatically stored in the SPRITE_LOC1-9 constant
  • BITMASK regulates which sprites are displayed. For instance, #%00000001 would only display sprite 1, while #%10010001 would display sprite 8, 5 and 1.
  • The memcpy function has a handy memory shift: it loads from the memory position of [parameter 1 + parameter 2], and copies [parameter 4] values to [parameter 3]. In this case, sprite 1 data is copied into¬† sprite 1 memory location, and sprite 3 data is copied in to sprite 2 memory location. I wish you had more memories of me.

procedure MoveSoundEffect(freq_move:byte);
begin
	 //Volume, hi byte freq, attack voice 1, sustain=16* + release, waveform, release waveform 
	PlaySound(SID_CHANNEL1, 
	7,  // Volume
	freq_move,  // Hi byte frequency 
	0*16+0,  // Attack voice 1
	3*16 + 3,   // Sustain = 16*15 + release=6
	1 +SID_NOISE,  // Waveform
	SID_NOISE);  // waveform

 end;

The PlaySound method let’s the user play simple sounds through the SID chip. The three channels are supported through constants SID_CHANNEL1-3, and waveforms are SID_NOISE, SID_TRI, SID_PULSE and SID_SAW. The parameters of PlaySound are as such:

  • Channel 1-3
  • Volume 0-15
  • Hi byte of frequency
  • Attack voice
  • Sustain *16 + release
  • Waveform

procedure MoveSprite();
begin
	
	spritepos(sprite_x, sprite_y, 0);
	Joystick();
	
	sprite_x := sprite_x - joystickleft*2;
	sprite_x := sprite_x + joystickright*2;
	sprite_y := sprite_y + joystickdown*2;
	sprite_y := sprite_y - joystickup*2;
	
	rand(15, 45, x);
	if (joystickleft=1 or joystickright=1) then
		MoveSoundEffect(x);
	if (joystickup<>0 or joystickdown <>0) then
		MoveSoundEffect(x);
	
	if joystickbutton=1 then begin
		ShootSoundEffect(SID_SAW, 15);
		bullet_x:=sprite_x;
		bullet_y:=sprite_y;
		
	end;
end;

This method sets the sprite #0 position, and increases the x/y values based on joystick input. For the joystick input to be updated, the Joystick() method needs to be called. Repeatedly. Like, when you have to call your ex 60 times per second repeatedly. In addition, when the joystick is moved, a sound is played. The same happens when the joystick button is pressed, and the bullet position is set to the current player position.


procedure UpdateSprite();
begin
	sprite_time := sprite_time +1;
	if sprite_time>20 then begin
		sprite_time:=0;
	end;
	
	if sprite_time=10 then begin
		setSpriteLoc(0, $3C, 0);
	end;
	if sprite_time=0 then begin
		setSpriteLoc(0, $3D, 0);
	end;
end;

This method increases a global timer and sets the sprite data accordingly, toggling between the two sprites of our fabulous protagoinist.


begin
	poke(SCREEN_BG_COL, 0, RED);
	poke(SCREEN_FG_COL, 0, BLACK);
	ClearScreen($20, SCREEN_CHAR_LOC);
	
	
	mainloop:=1;
	time:=0;
	sprite_time := 0;
	
	InitializeSprites();
	While 1<>2 do begin
		time:=time+1;
		UpdateSprite();
		PrintText();
		
		MoveSprite();
		MoveBullet();
		WaitForRaster(1);
	end;
	
END.

The main routine. Note a couple of things:

  • There is a simple DrawText procedure that will render the time (in hex format) to the screen
  • The WaitRaster will wait for the raster line to hit line 0 before continuing, maintaining a healthy 60 frames per second.