Forums

Sega Master System / Mark III / Game Gear
SG-1000 / SC-3000 / SF-7000 / OMV
Home - Forums - Games - Scans - Maps - Cheats - Credits
Music - Videos - Development - Hacks - Translations - Homebrew

View topic - devkitSMS tutorial

Reply to topic Goto page 1, 2  Next
Author Message
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
devkitSMS tutorial
Post Posted: Mon Jan 25, 2016 3:05 pm
Last edited by sverx on Wed Nov 22, 2017 10:27 am; edited 3 times in total
NOTICE: this tutorial is (almost) two years old but it's still mostly valid.
-
assets2banks is now the suggested tool to manage assets instead of the older folder2c. It's way more powerful.
- SMS_finalizeSprites has been deprecated and will be removed. You can already stop using it as it's useless now.

****----****----****----****----****----****----****----****----****----****


Here's a simple tutorial on how to use devkitSMS/SMSlib. We'll do a two players only squash game, similar to Pong. It should be easy enough to do that in a couple days, in a couple of 'lessons'. Please forgive the appearance of the game, I really suck at drawing.
I'll assume you properly installed SDCC and devkitSMS, and you know how to use the tools I name, Maxim's BMP2Tile, for instance.

Lesson 1: the game field
In my plan, both the players will be on the right side of the screen and bouncing the ball on the wall on the left, top and bottom of the screen.
'Port A' player will be 'Red' player and 'Port B' player will be 'Green' player. So the field simply feature the three walls and a scorekeeper, see the attached image.

We take field.png, which is a 16 colors image, and generate three files from it, using BMP2Tile:
- field (tiles).psgcompr - the tiles that make up the background, duplicates removed, in PSGaiden compressed format
- field (tilemap).stmcompr - the tilemap (how to place the tiles to 'build' the background) in STM compressed format
- field (palette).bin - the palette for the background in binary form
and we place these files into a subfolder of our project, called 'assets'.

Using devkitSMS' folder2c we will generate a .C and a header file from these data.

Then let's start coding the source. Create a main.c and place this inside:

#include "SMSlib.h"   // we're including the library with the functions we will use
#include "assets.h"   // we're including the assets we created before


then let's code the function that will load our assets to Video RAM (and palette):

#define    BG_TILES     0
void loadAssets (void) {
  SMS_loadPSGaidencompressedTiles(field__tiles__psgcompr, BG_TILES);
  SMS_loadSTMcompressedTileMap(0, 0, field__tilemap__stmcompr);
  SMS_loadBGPalette(field__palette__bin);
}


With SMS_loadPSGaidencompressedTiles we're loading our tiles into VRAM starting from tile 0. Since we might decide to relocate that later, let's use a #define. Then, with SMS_loadSTMcompressedTileMap we're loading the tilemap on the upper left corner (0,0) of the map, filling the whole screen. Then we load the used colors, with SMS_loadBGPalette. Note that the name of all the pointers (array or char) used are the names of the files you placed in assets folder, replacing each space, dot and bracket with an underscore.

Then let's write our main():
void main (void) {
    loadAssets();
    SMS_displayOn();
    for (;;);
}


For now, we're simply going to call our asset loading function and turn on the screen, then there's an infinite loop to 'freeze' everything.

Copying the following contents into a batch file (I call it "build.bat") you'll have an easy way to build the asset source file, compile it, compile the main program and link it and create the final output.sms file in one go:

@echo off
echo Build assets.c and assets.h from assets folder
folder2c assets assets
sdcc -c -mz80 assets.c
if %errorlevel% NEQ 0 goto :EOF
echo Build Main
sdcc -c -mz80 main.c
if %errorlevel% NEQ 0 goto :EOF
echo Linking
sdcc -o output.ihx -mz80 --data-loc 0xC000 --no-std-crt0 crt0_sms.rel main.rel SMSlib.lib assets.rel
if %errorlevel% NEQ 0 goto :EOF
ihx2sms output.ihx output.sms


Run output.sms on your favorite emulator (I suggest Emulicious and MEKA). You'll see the playing field appear.

P.S. ... if someone wants to provide better graphics for this small tutorial it's very welcome :)

edit: I forgot to mention that BMP2Tile doesn't come with the ability to create .stmcompr file out-of-the-box.
You should download the plug-in here (on my github repo) and copy the file in your BMP2Tile program folder.
field.png (363 B)
the playing field (yeah, it really sucks)
field.png

  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Tue Jan 26, 2016 2:37 pm
Lesson 2: the paddles
Let's add the paddles. We're going to use (8x16 pixel) sprites.
The attached image sprites.png contains the two paddles (red and green) and the ball, which we'll use later.

Again, we convert that image to assets we'll use in our code:
- sprites (tiles).psgcompr - the tiles for the sprites, no removing of duplicates and created using 'treat as 8x16' (even if in this case it didn't change anything) in PSGaiden compressed format.
- sprites (palette).bin - the palette for the sprites in binary form
we place these files into assets subfolder, together with the other stuff there.

Now we need to add a few more #defines and to load the new assets into VRAM

#define    SPRITE_TILES     256
#define    RED_PADDLE_TILES       SPRITE_TILES
#define    GREEN_PADDLE_TILES     (SPRITE_TILES+4)
#define    BALL_TILES             (SPRITE_TILES+8)

void loadAssets (void) {
  SMS_loadPSGaidencompressedTiles(field__tiles__psgcompr, BG_TILES);
  SMS_loadSTMcompressedTileMap(0, 0, field__tilemap__stmcompr);
  SMS_loadBGPalette(field__palette__bin);
  SMS_loadPSGaidencompressedTiles(sprites__tiles__psgcompr, SPRITE_TILES);
  SMS_loadSpritePalette(sprites__palette__bin);
}

(notice how we added SMS_loadPSGaidencompressedTiles for sprite tiles and SMS_loadSpritePalette for sprite palette to our loadAssets function)

We then need a few variables to keep player's paddle position and which player is supposed to hit the ball next:

unsigned char playerPosition[2];
unsigned char turn;


of course we need a function to initialize these variables when the game starts:
void initGame (void) {
  playerPosition[0]=44;
  playerPosition[1]=114;
  turn=0;
}


Then finally the important parts: first we need to read user input, and change paddles' Y position according to the pressed keypad keys. We then write a function for this:
void movePaddles (void) {
#define MINPADDLEY  8
#define MAXPADDLEY  (192-8-32-1)
  unsigned int ks=SMS_getKeysStatus();
  if ((ks & PORT_A_KEY_UP) && (playerPosition[0]>MINPADDLEY)) {
    playerPosition[0]-=2;
  } else if ((ks & PORT_A_KEY_DOWN) && (playerPosition[0]<MAXPADDLEY)) {
    playerPosition[0]+=2;
  }
  if ((ks & PORT_B_KEY_UP) && (playerPosition[1]>MINPADDLEY)) {
    playerPosition[1]-=2;
  } else if ((ks & PORT_B_KEY_DOWN) && (playerPosition[1]<MAXPADDLEY)) {
    playerPosition[1]+=2;
  }
}


SMS_getKeysStatus returns a value that can be masked against constant values to see which keys are being pressed. So ks & PORT_A_KEY_UP is true if the UP key on the pad connected on port A is pressed.

The second important part is the function that 'declares' where the paddle sprites should be placed. As each paddle is 32 pixel in height and our sprites are 16 pixel in height only, we need to use two sprites per paddle.
Also, since the sprites are drawn front to back in the order we declare them, we should make sure that the player who'll need to hit the ball is drawn on top of the other player.

void drawPaddles (void) {
#define PADDLEX  244
  SMS_addSprite (PADDLEX, playerPosition[turn],    (turn?GREEN_PADDLE_TILES:RED_PADDLE_TILES));
  SMS_addSprite (PADDLEX, playerPosition[turn]+16, (turn?GREEN_PADDLE_TILES:RED_PADDLE_TILES)+2);
  SMS_addSprite (PADDLEX, playerPosition[1-turn],    (turn?RED_PADDLE_TILES:GREEN_PADDLE_TILES));
  SMS_addSprite (PADDLEX, playerPosition[1-turn]+16, (turn?RED_PADDLE_TILES:GREEN_PADDLE_TILES)+2);
}


So, finally, here's our new main:

void main (void) {
  SMS_setSpriteMode (SPRITEMODE_TALL);
  loadAssets();
  initGame();
  SMS_displayOn();
  for (;;) {
    movePaddles();
    SMS_initSprites();
    drawPaddles();
    SMS_finalizeSprites();
    SMS_waitForVBlank();
    SMS_copySpritestoSAT();
  }
}


First we tell the hardware we're going to always use 8x16 sprites, using SMS_setSpriteMode.
Then, in our still endless for loop, we have to remember that before declaring the first sprite we should instruct the library we're going to do that, using SMS_initSprites and also tell the library when done, using SMS_finalizeSprites.

Then we have to wait for a very important moment to come: when the screen will be in VBlank phase, which is when we will update sprites. So SMS_waitForVBlank will ensure our program will wait for the right moment and SMS_copySpritestoSAT will move our previously declared sprites to the hardware SAT (Sprite Attribute Table) so that our VDP will be able to display them.
sprites.png (266 B)
sprites - paddles and ball
sprites.png

  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Thu Jan 28, 2016 1:32 pm
Last edited by sverx on Thu Jan 28, 2016 1:41 pm; edited 1 time in total
Lesson 3: the ball
It's time to add the bouncing ball. The simpler ball I can think of. This ball just bounces at 45° diagonal against walls and paddles.
We start defining the 4 possible directions:
#define    DIR_NE           0
#define    DIR_SE           1
#define    DIR_SW           2
#define    DIR_NW           3

and we add some other constants to define ball speed and size and wall positions:
#define    BALL_SPEED       2
#define    BALL_RADIUS      2
#define    LEFT_WALL        15
#define    RIGHT_WALL       (256-4)
#define    TOP_WALL         7
#define    BOTTOM_WALL      (192-8)

#define    PADDLEX          244

(I also moved the defined PADDLEX to top of source, as it was misplaced where it was in drawPaddles function.)

We need some variables now:
unsigned char BallX, BallY, BallDir;


and we of course need to update the function that initializes them:
void initGame (void) {
  playerPosition[0]=44;
  playerPosition[1]=114;
  turn=0;
  BallX=230;
  BallY=96;
  BallDir=DIR_NW;
}


Now let's define the function that will handle the ball movement:
void moveBall (void) {
  switch (BallDir) {
    case DIR_NE:BallX+=BALL_SPEED; BallY-=BALL_SPEED; break;
    case DIR_SE:BallX+=BALL_SPEED; BallY+=BALL_SPEED; break;
    case DIR_SW:BallX-=BALL_SPEED; BallY+=BALL_SPEED; break;
    case DIR_NW:BallX-=BALL_SPEED; BallY-=BALL_SPEED; break;
  }

  if ((BallY-BALL_RADIUS)<=TOP_WALL) {
    // top wall collisions
    if (BallDir==DIR_NW) BallDir=DIR_SW;
    else if (BallDir==DIR_NE) BallDir=DIR_SE;
  } else if ((BallY+BALL_RADIUS)>=BOTTOM_WALL) {
    // bottom wall collisions
    if (BallDir==DIR_SW) BallDir=DIR_NW;
    else if (BallDir==DIR_SE) BallDir=DIR_NE;
  }

  if ((BallX-BALL_RADIUS)<=LEFT_WALL) {
    // left wall collisions
    if (BallDir==DIR_NW) BallDir=DIR_NE;
    else if (BallDir==DIR_SW) BallDir=DIR_SE;
    // and it's next player turn!
    turn=1-turn;
  }

  if (((BallX+BALL_RADIUS)>=PADDLEX) && ((BallX+BALL_RADIUS)<=(PADDLEX+6))) {
    // check if we're hitting the active player's paddle
    if (((BallY+BALL_RADIUS)>=playerPosition[turn]) &&  ((BallY-BALL_RADIUS)<=playerPosition[turn]+32)) {
      if (BallDir==DIR_NE) BallDir=DIR_NW;
      else if (BallDir==DIR_SE) BallDir=DIR_SW;
    }
  }
}


The switch on top will calculate the new position of the ball according on current position and direction, then we will check if we need to change the current direction, which will happen if the ball hits a wall or a paddle. We are also starting next player's turn when the ball hits the left (distant) wall, but we're still not addressing when players miss the ball, we'll see that later.

Then, of course, we also need a function to draw the ball:
void drawBall (void) {
  SMS_addSprite (BallX-BALL_RADIUS, BallY-BALL_RADIUS, BALL_TILES);
}


Finally, it's time to update our main:
void main (void) {
  SMS_setSpriteMode (SPRITEMODE_TALL);
  loadAssets();
  initGame();
  SMS_displayOn();
  for (;;) {
    movePaddles();
    moveBall();
    SMS_initSprites();
    drawPaddles();
    drawBall();
    SMS_finalizeSprites();
    SMS_waitForVBlank();
    SMS_copySpritestoSAT();
  }
}


we just had to add moveBall (right after movePaddles) and drawBall, which I placed after drawPaddles but of course if you prefer the ball 'over' the paddles you can swap them. You'll hardly notice any difference, though.
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Thu Jan 28, 2016 1:38 pm
A small gift for the lazy ones among the readers ;)
main.c (3.69 KB)
complete source as of lesson #3

  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Fri Jan 29, 2016 2:14 pm
Last edited by sverx on Fri Jan 29, 2016 3:12 pm; edited 1 time in total
Lesson 4: the scorekeeper
Now we're going to add the scorekeeper. The attached score.png contains the score from 0 to 15 as an array of 16 32x32 pixel images. We're going to convert them (using BMP2Tile) to tiles and tilemap:

- score (tiles).psgcompr - the tiles, duplicates removed, in PSGaiden compressed format
- score (tilemap).bin - the tilemap, in uncompressed format, because we need to access the data with random access

We should load the tiles somewhere in Video RAM, so we define a suitable location:
#define    SCORE_TILES            24


and we update our loadAssets function so that it loads the tiles:
void loadAssets (void) {
  SMS_loadPSGaidencompressedTiles(field__tiles__psgcompr, BG_TILES);
  SMS_loadSTMcompressedTileMap(0, 0, field__tilemap__stmcompr);
  SMS_loadBGPalette(field__palette__bin);
  SMS_loadPSGaidencompressedTiles(sprites__tiles__psgcompr, SPRITE_TILES);
  SMS_loadSpritePalette(sprites__palette__bin);
  SMS_loadPSGaidencompressedTiles(score__tiles__psgcompr, SCORE_TILES);
  SMS_setBGPaletteColor(4, RGB(3,3,3));
}


(we're also setting background palette color 4 to a color we like, say white, because color 4 is what our image uses)

Then we need two variables for keeping the players scores:
unsigned char playerPoints[2];


and of course we should reset them when starting, so we update our initGame:
void initGame (void) {
  playerPosition[0]=44;
  playerPosition[1]=114;
  turn=0;
  BallX=230;
  BallY=96;
  BallDir=DIR_NW;
  playerPoints[0]=0;
  playerPoints[1]=0;
}


Then we define where the score should appear on screen, for both players. We're going to update screen map, so these values are in chars, not in pixel:
#define    SCOREKEEPERY     2
#define    SCOREKEEPERX_0   9
#define    SCOREKEEPERX_1   15


and of course we need a function that updates the score when it changes:
void updateScore (unsigned char player) {
  unsigned int x,y;
  const unsigned int *pnt;
  for (y=0;y<4;y++) {
    SMS_setNextTileatXY ((player==0)?SCOREKEEPERX_0:SCOREKEEPERX_1,SCOREKEEPERY+y);
    pnt=&score__tilemap__bin[playerPoints[player]*32+y*8];
    for (x=0;x<4;x++) {
      SMS_setTile (*pnt+++SCORE_TILES);
    }
  }
}


To update the screen map, we use SMS_setNextTileatXY and SMS_setTile. The former sets at which position (column, row) we're going to update a tile and the latter sets the new tile value to be written there. Upon setting a new value, the SMS hardware will set the 'screen update pointer' to the next tile, that is the tile that comes on the right (or the first of the next line when line is over) so we can update four adjoining tiles without reissuing any set new position command in between.
(in specific detail: this function it's using the map we generated thru BMP2Tile as a reference map, and it's updating 16 tiles (4 lines of 4 tiles) on screen according to which player score we should update and its current points)

Of course we still need to add the part that adds a point to a player when the other misses the ball. In moveBall, we need to add checking for the 'right wall', which isn't really there but you get the point:
 [...]
  if ((BallX-BALL_RADIUS)<=LEFT_WALL) {
    // left wall collisions
    if (BallDir==DIR_NW) BallDir=DIR_NE;
    else if (BallDir==DIR_SW) BallDir=DIR_SE;
    // and it's next player turn!
    turn=1-turn;
  } else if ((BallX+BALL_RADIUS)>=RIGHT_WALL) {
    // right wall is missing, so collision here means the ball escaped, so
    // the other player scores a point:
    if (playerPoints[1-turn]<15) {
      playerPoints[1-turn]++;
      updateScore(1-turn);
    }
    // restart ball
    BallX=230;
    BallY=96;
    BallDir=DIR_NW;
  }
 [...]

(the added code is the 'else' branch...)

Finally, we should make sure that the game starts with displaying the starting score 0 - 0: we update the main and we call our updateScore for each player right before turning on the screen:

void main (void) {
  SMS_setSpriteMode (SPRITEMODE_TALL);
  loadAssets();
  initGame();
  updateScore(0);
  updateScore(1);
  SMS_displayOn();
[...]

score.png (1.03 KB)
points 0 to 15
score.png

  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Fri Jan 29, 2016 2:16 pm
In case you want to test the outcome...
output.rar (3.13 KB)
squash ROM, after lesson #4

  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Mon Feb 01, 2016 1:27 pm
Lesson 5: start a new game
Of course you might want to play once more after some of the two players reached 15 points, so we're going to make the SMS pause key act as a global reset.

We simply modify our main, adding a few lines at the end of our endless for loop:

void main (void) {
  SMS_setSpriteMode (SPRITEMODE_TALL);
  loadAssets();
  initGame();
  updateScore(0);
  updateScore(1);
  SMS_displayOn();
  for (;;) {
    movePaddles();
    moveBall();
    SMS_initSprites();
    drawPaddles();
    drawBall();
    SMS_finalizeSprites();
    SMS_waitForVBlank();
    SMS_copySpritestoSAT();
    if (SMS_queryPauseRequested()) {
      SMS_resetPauseRequest();
      initGame();
      updateScore(0);
      updateScore(1);
    }
  }
}


We're going to query if pause key has been pressed since last frame - SMS_queryPauseRequested returns a boolean, and if so we will init the game again, resetting score and scorekeeper appearance. Of course we also need to 'acknowledge' the pause request, so we use SMS_resetPauseRequest (which simply resets the flag).

This one was simple, uh? :)
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Tue Feb 02, 2016 12:40 pm
Lesson 6: background music
We're now going to add a background music to our game, using PSGlib.
First, you need to prepare a .PSG file, which is simply a converted VGM. You can track your music in Mod2Psg2, DefleMask, or VGM Music Maker, and export your tune in VGM. Then you have to optimize it and convert to PSG using vgm2psg tool, which is part of PSGlib.

So you've got your music file, theTune.psg and you put it into your asset folder.

In your source, include the header file for the library:
#include "PSGlib.h"


then in your code you should start your tune and ensure that the PSG hardware gets updated with proper data each frame, so you change your main:
void main (void) {
  SMS_setSpriteMode (SPRITEMODE_TALL);
  loadAssets();
  initGame();
  updateScore(0);
  updateScore(1);
  PSGPlay (theTune_psg);
  SMS_displayOn();
  for (;;) {
    movePaddles();
    moveBall();
    SMS_initSprites();
    drawPaddles();
    drawBall();
    SMS_finalizeSprites();
    SMS_waitForVBlank();
    SMS_copySpritestoSAT();
    PSGFrame();
    if (SMS_queryPauseRequested()) {
      SMS_resetPauseRequest();
      initGame();
      updateScore(0);
      updateScore(1);
    }
  }
}


PSGPlay will tell the library which tune you'll play and PSGFrame will actually make the 'magic' happen. It's important that this function it's called at a constant pace, so it's right after the SMS_waitForVBlank and SMS_copySpritestoSAT calls.

Of course you should tell the linker you're going to use the library, so you should modify the call to the linker in your batch that does the work:
[...]
echo Linking
sdcc -o output.ihx -mz80 --data-loc 0xC000 --no-std-crt0 crt0_sms.rel main.rel SMSlib.lib PSGlib.rel assets.rel
[...]
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Wed Feb 03, 2016 12:17 pm
Lesson 7: sound effects
Now we will add SFXs, one when the ball hits a wall and another when it hits a paddle. We prepare two VGM tunes, using only PSG channel 2 and/or 3 (noise) and we convert them to PSG files using vgm2psg, remembering that we should tell the converter which channels are used by the sound effect we're converting.

So we got our sfx_1.psg and sfx_2.psg and we put them in our asset folder.

Then we're going to modify our code to call PSGSFXPlay when we want to fire a SFX:

void moveBall (void) {
  switch (BallDir) {
    case DIR_NE:BallX+=BALL_SPEED; BallY-=BALL_SPEED; break;
    case DIR_SE:BallX+=BALL_SPEED; BallY+=BALL_SPEED; break;
    case DIR_SW:BallX-=BALL_SPEED; BallY+=BALL_SPEED; break;
    case DIR_NW:BallX-=BALL_SPEED; BallY-=BALL_SPEED; break;
  }

  if ((BallY-BALL_RADIUS)<=TOP_WALL) {
    // top wall collisions
    if (BallDir==DIR_NW) BallDir=DIR_SW;
    else if (BallDir==DIR_NE) BallDir=DIR_SE;
    PSGSFXPlay(sfx_1_psg,SFX_CHANNEL2);
  } else if ((BallY+BALL_RADIUS)>=BOTTOM_WALL) {
    // bottom wall collisions
    if (BallDir==DIR_SW) BallDir=DIR_NW;
    else if (BallDir==DIR_SE) BallDir=DIR_NE;
    PSGSFXPlay(sfx_1_psg,SFX_CHANNEL2);
  }

  if ((BallX-BALL_RADIUS)<=LEFT_WALL) {
    // left wall collisions
    if (BallDir==DIR_NW) BallDir=DIR_NE;
    else if (BallDir==DIR_SW) BallDir=DIR_SE;
    // and it's next player turn!
    turn=1-turn;
    PSGSFXPlay(sfx_1_psg,SFX_CHANNEL2);
  } else if ((BallX+BALL_RADIUS)>=RIGHT_WALL) {
    // right wall is missing, so collision here means the ball escaped, so
    // the other player scores a point:
    if (playerPoints[1-turn]<15) {
      playerPoints[1-turn]++;
      updateScore(1-turn);
    }
    // restart ball
    BallX=230;
    BallY=96;
    BallDir=DIR_NW;
  }


  if (((BallX+BALL_RADIUS)>=PADDLEX) && ((BallX+BALL_RADIUS)<=(PADDLEX+6))) {
    // check if we're hitting the active player's paddle
    if (((BallY+BALL_RADIUS)>=playerPosition[turn]) &&  ((BallY-BALL_RADIUS)<=playerPosition[turn]+32)) {
      if (BallDir==DIR_NE) BallDir=DIR_NW;
      else if (BallDir==DIR_SE) BallDir=DIR_SW;
      PSGSFXPlay(sfx_2_psg,SFX_CHANNELS2AND3);
    }
  }
}


on each call we need to tell the audio engine which channels the SFX will use. Possible values are SFX_CHANNEL2, SFX_CHANNEL3 and SFX_CHANNELS2AND3 if the SFX uses both.

Then, we also need to modify our main so that it calls PSGSFXFrame after each PSGFrame call, this way the tune and the SFXs will be processed correctly in sync.
[...]
    SMS_waitForVBlank();
    SMS_copySpritestoSAT();
    PSGFrame();
    PSGSFXFrame();
[...]
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Thu Feb 04, 2016 9:48 am
Addendum: SEGA ROM header and SDSC header
This doesn't deal with programming, really. It's also not compulsory as long as you play with emulators OR you run your game using Master EverDrive / EverDriveGG.
SEGA ROM header is compulsory if you want to burn your own ROM and build your cartridge, as the Master System BIOS will check your ROM to see if it's 'legit' (there's a text written in a specific location) and if it's 'undamaged' (there's a CRC to be validated).

To add a SEGA ROM header to your ROM you can use the specific macro:
SMS_EMBED_SEGA_ROM_HEADER(9999,0);

This adds the ROM header, the first parameter is the ROM ID (each game in SEGA catalog has one, 9999 should be free and I guess it's a nice number for the homebrew releases) and the second parameter is the revision (0 to 15), so you might want to increment it if by chance you release a revision of your game. Whatever the values anyway the ROM header and the CRC field will be fitted in the correct place (and ihx2sms will take care of setting the correct value)

SDSC header, on the other hand, is something invented here and now a standard for SMS/GG homebrew tagging. You can place information about the software, the version, the author, the release date and even comments into the ROM. Emulators will display these.

To add a SDSC tag you can use:
SMS_EMBED_SDSC_HEADER_AUTO_DATE(1, 0, "sverx\\2016", "Squash", "a small SEGA Master System game tutorial\nBuilt using devkitSMS & SMSlib");

The first parameter is the major version, the second is the minor - from 0 to 99 (so this is 1.00), the third parameter is the author field, then comes the software name and finally the description/notes. Current date will be automatically fitted by ihx2sms tool when it finalizes your ROM.

I like placing both of them at the end of my main source file, but they can be placed anywhere you like. Just be aware that if your ROM would fit into 16 KB without these, these will make your ROM 32 KB.
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Fri Feb 05, 2016 8:36 am
Epilogue: everything else
Of course in this tutorial I really didn't use all the SMSlib function/features, so here's a list of what has been left out.

Functions to activate/deactivate VDP features:
void SMS_VDPturnOnFeature (unsigned int feature);
void SMS_VDPturnOffFeature (unsigned int feature);


Features you can turn on/off are:
VDPFEATURE_EXTRAHEIGHT
VDPFEATURE_SHIFTSPRITES
VDPFEATURE_HIDEFIRSTCOL ( or VDPFEATURE_LEFTCOLBLANK )
VDPFEATURE_LOCKHSCROLL
VDPFEATURE_LOCKVSCROLL
VDPFEATURE_240LINES
VDPFEATURE_224LINES

(those controlled by other function/macros are removed from this list)

Also, macro and functions are provided to configure VDP parameters/mode:
SMS_displayOn();                                    /* macro - turns on screen */
SMS_displayOff();                                   /* macro - turns off screen */
void SMS_setBGScrollX (int scrollX);                /* scroll the background horizontally */
void SMS_setBGScrollY (int scrollY);                /* scroll the background vertically */
void SMS_setBackdropColor (unsigned char entry);    /* set which sprite palette entry will be used for backdrop */
void SMS_useFirstHalfTilesforSprites (_Bool usefirsthalf);  /* use tiles 0-255 for sprites if true, 256-511 if false */
void SMS_setSpriteMode (unsigned char mode);


modes for SMS_setSpriteMode are:
SPRITEMODE_NORMAL
SPRITEMODE_TALL
SPRITEMODE_ZOOMED
SPRITEMODE_TALL_ZOOMED


there are other functions to load uncompressed tiles/tilemaps/tilemap areas to VRAM:
void SMS_loadTiles (void *src, unsigned int Tilefrom, unsigned int len);
void SMS_loadTileMap (unsigned char x, unsigned char y, void *src, unsigned int len);
void SMS_loadTileMapArea (unsigned char x, unsigned char y,  unsigned int *src, unsigned char width, unsigned char height);


There are sprite clipping/windowing functions (you can define a window and make all the sprite outside the window disappear):
void SMS_setClippingWindow (unsigned char x0, unsigned char y0, unsigned char x1, unsigned char y1);   /* set the sprite window. sprites completely outside the window will be clipped */
_Bool SMS_addSpriteClipping (int x, int y, unsigned char tile);  /* declare a sprite inside the window - returns false if sprite isn't added */


You can query keypad according to current status compared to previous status, to recognize when a key has just been pressed or released, or if a key is held pressed:
unsigned int SMS_getKeysStatus (void);    /* the current status of the keys */
unsigned int SMS_getKeysPressed (void);   /* the keys that were up last frame and down now */
unsigned int SMS_getKeysHeld (void);      /* the keys that were down last frame and still down now */
unsigned int SMS_getKeysReleased (void);  /* the keys that were down last frame and up now */


The same for a Genesis/MegaDrive pad additional buttons (Port A only):
unsigned int SMS_getMDKeysStatus (void);  /* the current status of the extended keys on a MD controller */
unsigned int SMS_getMDKeysPressed (void); /* the extended keys that were up last frame and down now on a MD controller */
unsigned int SMS_getMDKeysHeld (void);    /* the extended keys that were down last frame and still down now on a MD controller */
unsigned int SMS_getMDKeysReleased (void); /* the extended keys that were down last frame and up now on a MD controller */


There are also functions/macros to handle line interrupts:
void SMS_setLineInterruptHandler (void (*theHandlerFunction)(void));  /* link your own handler to the line interrupt */
void SMS_setLineCounter (unsigned char count);                        /* choose on which line trigger the IRQ */
SMS_enableLineInterrupt()                                             /* macro - turns on line IRQ */
SMS_disableLineInterrupt()                                            /* macro - turns off line IRQ */


Finally, there are low level functions, which you might want to use for dirty tricks:
void SMS_VRAMmemcpy (unsigned int dst, void *src, unsigned int size);              /* memcpy to VRAM */
void SMS_VRAMmemcpy_brief (unsigned int dst, void *src, unsigned char size);       /* memcpy to VRAM (256 bytes max) */
void SMS_VRAMmemset (unsigned int dst, unsigned char value, unsigned int size);    /* memset to VRAM */


and functions/macros to move data to VRAM as fast as possible, which means that screen should be blanked (off or in vBlank phase) or you'll experience screen corruption:
void UNSAFE_SMS_copySpritestoSAT (void);                         /* copy sprites to Sprites Attribute Table */
void UNSAFE_SMS_VRAMmemcpy32 (unsigned int dst, void *src);      /* copy 32 bytes to VRAM */
void UNSAFE_SMS_VRAMmemcpy64 (unsigned int dst, void *src);      /* copy 64 bytes to VRAM */
void UNSAFE_SMS_VRAMmemcpy128 (unsigned int dst, void *src);     /* copy 128 bytes to VRAM */
UNSAFE_SMS_load1Tile(src,theTile)           /* copy ONE tile to VRAM */
UNSAFE_SMS_load2Tiles(src,tilefrom)         /* copy TWO tiles to VRAM */
UNSAFE_SMS_load4Tiles(src,tilefrom)         /* copy FOUR tiles to VRAM */


PSGlib functions that weren't used in the tutorial are:
void PSGCancelLoop (void);
void PSGPlayNoRepeat (void *song);
void PSGStop (void);
unsigned char PSGGetStatus (void);

void PSGSFXPlayLoop (void *sfx, unsigned char channels);
void PSGSFXCancelLoop (void);
void PSGSFXStop (void);
unsigned char PSGSFXGetStatus (void);

you can read more details on PSGlib functions and how it works here.

I hope you enjoyed the read! :) Comments/feedback welcome! :)
  View user's profile Send private message Visit poster's website
  • Joined: 23 Mar 2013
  • Posts: 611
  • Location: Copenhagen, Denmark
Reply with quote
Post Posted: Sat Feb 06, 2016 7:00 am
Thanks for this great tutorial, sverx! I think it is clear, well-structured and convincing. You should link to it from within Development>Getting Started>Tutorials. I'm very happy with assembler for now, so even though I'm not fiddling with devkitSMS and the C language, I've got a lot of inspiration from your tutorial regarding how to structure a small game (the elements in the for-loop, initialization, etc.)

Question: Should the for-loop not start with waiting for v-blank, then gfx-processing and then the rest (updating positions, etc.)?
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Sat Feb 13, 2016 8:08 am
hang-on wrote
Should the for-loop not start with waiting for v-blank, then gfx-processing and then the rest (updating positions, etc.)?


As long as gfx processing comes right after vblank has been hit, it really doesn't matter if the logic is before or after those, as you're anyway inside an endless loop and you're repeating just three parts: waiting for vblank, gfx processing, game logic (there are only two ways to endlessly repeat three parts, the other way is the wrong one ;) )
  View user's profile Send private message Visit poster's website
  • Joined: 15 Mar 2010
  • Posts: 49
  • Location: Florida
Reply with quote
Post Posted: Wed Mar 02, 2016 4:46 pm
is in C? imposibre!!
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Thu Mar 03, 2016 9:58 am
Chicho wrote
is in C? imposibre!!


I'll take that as a compliment ;)
  View user's profile Send private message Visit poster's website
  • Joined: 07 Mar 2016
  • Posts: 4
Reply with quote
Post Posted: Wed Mar 09, 2016 10:17 pm
Hi, friends!

I am a new user here and I have completed the activities of this brilliant tutorial (until now).

Please, could you help me to write an example with a larger background (bigger than 256x192 pixels) and a scrolling screen in this background?

For example, we could consider the hockey ring of the game "Slap Shot". The rink is bigger than the screen size, so the screen only shows a part of the rink and we could use left, right, up or down buttons of the joystick to see other parts of the rink.

Thank you so much!
SlapShot-SMS-Rink.png (4.19 KB)
Hockey Rink of the game "Slap Shot"
SlapShot-SMS-Rink.png

  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Thu Mar 10, 2016 10:19 am
I quickly made a very simple example, which scrolls only vertically, but I guess you'll get the point and add the horizontal scroll by yourself.
We're here to help anyway! :)

The point is: you update an offscreen row of tiles each time you're very close to displaying it. Update a row is of course simpler than update a column, but that can be done too :)

#include "SMSlib.h"
#include "assets.h"

unsigned int scroll=0;

void loadAssets (void) {
  SMS_loadPSGaidencompressedTiles(MM__tiles__psgcompr, 0);
  SMS_loadTileMap(0, 0, MM__tilemap__bin, 32*25*2);     // 32 tiles * 25 lines * 2 bytes each
  SMS_loadBGPalette(MM__palette__bin);
}

void main (void) {
  unsigned int ks;
  loadAssets();
  SMS_displayOn();
  for (;;) {
    SMS_waitForVBlank();
    ks=SMS_getKeysStatus();
    if ((ks & PORT_A_KEY_DOWN) && (scroll<32*8-1)) {
      scroll++;
      SMS_setBGScrollY(scroll);
      if ((scroll % 8)==0) {
        SMS_loadTileMap(0,(24+(scroll/8))%28,&MM__tilemap__bin[(24+(scroll/8))*32*2],32*2);  // 32 tiles * 2 bytes each
      }
    } else if ((ks & PORT_A_KEY_UP) && (scroll>0)) {
      scroll--;
      SMS_setBGScrollY(scroll);
      if ((scroll % 8)==0) {
        SMS_loadTileMap(0,(27+(scroll/8))%28,&MM__tilemap__bin[((scroll/8)-1)*32*2],32*2);  // 32 tiles * 2 bytes each
      }
    }
  }
}


in asset folder you should place the three files:
- MM (tiles).psgcompr: the tiles (PSGaiden compressed format)
- MM (tilemap).bin: the tilemap (uncompressed binary format)
- MM (palette).bin: the palette (binary)

that you can generate using BMP2Tile on the attached image.
MM.png (5.45 KB)
MM background
MM.png

  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Fri Mar 11, 2016 2:29 pm
side note: coding this example I realized the current implementation of
SMS_setBGScrollY()

works correctly only between -255 and +255. I'll fix that in the next release of the library, where I'll make both functions
SMS_setBGScrollY()
SMS_setBGScrollX()

accept an unsigned byte instead of a (signed) int. It may seem weird, but it's closer to how the hardware works (and anyway I *have* to fix it somehow, uh?)
  View user's profile Send private message Visit poster's website
  • Joined: 07 Mar 2016
  • Posts: 4
Reply with quote
Post Posted: Mon Mar 14, 2016 9:26 pm
Last edited by zenac on Mon Mar 14, 2016 9:31 pm; edited 2 times in total
Thank you, sverx!

Your example envolving vertical scrooling is great!

I have tried to make another example for horizontal scrolling. By the way, it is necessary for a beat 'n up and the future second "Double Dragon" game for Master System. :)

I am really new in Master System programming. So, I don't know if my example is a good one. Since now, I thank everyone who gives more tips.

In this example, I took the background of the game "Great Volleyball":

The width of the image from this site (smspower) is 374 pixels, I have changed it to 368 pixels (multiple of 8), removing the last 6 pixels on the right.

I have tried to convert the changed PNG image using BMP2Tile but this program gave me this message:

"Image format not supported. Can't process it."


I don't know why BMP2Tile doesn't accept my PNG file. So I have converted the image for BMP format (the colors are different from the original because Paint program for Windows has changed the colors, but it is not important now).

The files generated by BMP2Tile are:

- volleyball (tiles).psgcompr: the tiles (PSGaiden compressed format)
- volleyball (tilemap).bin: the tilemap (Raw uncompressed binary format)
- volleyball (palette).bin: the palette (binary)

Trying the horizontal scrolling, I have noticed that the first column of the screen has a strange and awful effect. Then I have hidden the first column using the following command:

SMS_VDPturnOnFeature(VDPFEATURE_HIDEFIRSTCOL);


If you want to see the strange effect, comment this line of the code.

In this example, press left or right directions of joystick.

To draw tiles during scrolling

for (ytile=1; ytile<Y_TILE_MAX; ytile++)
            {
               SMS_loadTileMap(X_TILE_MAX+scrollRightDivided8,ytile-1,&volleyball__tilemap__bin[((BG_TILE_WIDTH*ytile)+(X_TILE_MAX+scrollRightDivided8))*2],2);  // 1 tile * 2 bytes
            }


I don't know why but I had problems with the first row of tiles. Then I have not changed the first row of the scrolling image. The first index inside "for" loop is 1 (ytile=1) and I have no idea I needed to use "y" minus one (ytile-1) in the second argument of "SMS_loadTileMap" function. Shouldn't be only "y" as second argument of the function?

Full "main.c" file in the next message...

  View user's profile Send private message
  • Joined: 07 Mar 2016
  • Posts: 4
Reply with quote
Post Posted: Mon Mar 14, 2016 9:27 pm
#include "SMSlib.h"
#include "assets.h"

#define BG_TILE_WIDTH 46
#define X_TILE_MAX 32
#define Y_TILE_MAX 24

unsigned int scroll=0;
unsigned int scrollDivided8=0;
unsigned int scrollRight=0;
unsigned int scrollRightDivided8=0;
unsigned int xtile=0;
unsigned int ytile=0;

void loadAssets(void)
{
   SMS_loadPSGaidencompressedTiles(volleyball__tiles__psgcompr, 0);
   
   
   for (ytile=1; ytile<Y_TILE_MAX; ytile++)
   {
      SMS_loadTileMap(0,ytile,&volleyball__tilemap__bin[BG_TILE_WIDTH*ytile*2],X_TILE_MAX*2);  // 32 tiles * 2 bytes each
   }
   
   SMS_loadBGPalette(volleyball__palette__bin);
}


void main (void)
{
   unsigned int ks;
   SMS_VDPturnOnFeature(VDPFEATURE_HIDEFIRSTCOL);
   loadAssets();
   SMS_displayOn();
   for (;;)
   {
      SMS_waitForVBlank();
      ks=SMS_getKeysStatus();
      if ((ks & PORT_A_KEY_RIGHT) && ((scrollRightDivided8<(BG_TILE_WIDTH-X_TILE_MAX)) || (scroll==0)))
      {
         scroll--;
         scrollRight++;
         scrollRightDivided8=scrollRight/8;
         SMS_setBGScrollX(scroll);
         
         if ((scrollRight % 8)==1)
         {
            for (ytile=1; ytile<Y_TILE_MAX; ytile++)
            {
               SMS_loadTileMap(X_TILE_MAX+scrollRightDivided8,ytile-1,&volleyball__tilemap__bin[((BG_TILE_WIDTH*ytile)+(X_TILE_MAX+scrollRightDivided8))*2],2);  // 1 tile * 2 bytes
            }
               
         }
// CONTINUES...
  View user's profile Send private message
  • Joined: 07 Mar 2016
  • Posts: 4
Reply with quote
Post Posted: Mon Mar 14, 2016 9:29 pm
// CONTINUING THE PREVIOUS CODE
      }
      else if ((ks & PORT_A_KEY_LEFT) && (scrollRight>0))
      {
         scroll++;
         scrollRight--;
         scrollRightDivided8=scrollRight/8;
         SMS_setBGScrollX(scroll);
         
         if ((scrollRight % 8)==0)
         {
            for (ytile=1; ytile<Y_TILE_MAX; ytile++)
            {
               SMS_loadTileMap(X_TILE_MAX+scrollRightDivided8,ytile-1,&volleyball__tilemap__bin[((BG_TILE_WIDTH*ytile)+scrollRightDivided8)*2],2);  // 1 tile * 2 bytes
            }
         }
         
         
      }
   }
}
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Tue Mar 15, 2016 8:05 am
zenac wrote
I have tried to convert the changed PNG image using BMP2Tile but this program gave me this message:

"Image format not supported. Can't process it."


Weird. As long as it's a 16 colors PNG it should work! :|

zenac wrote
Trying the horizontal scrolling, I have noticed that the first column of the screen has a strange and awful effect. Then I have hidden the first column using the following command:

SMS_VDPturnOnFeature(VDPFEATURE_HIDEFIRSTCOL);


Correct. Since the VRAM tilemap is 256 pixel wide (32 tiles of 8 pixel each) and the screen has the same width, you are actually showing the whole width of the map on the screen so you really can't update offscreen parts, as I did with my vertical scroll example. Hiding the first column on screen is the correct approach for smooth horizontal scroll :)

zenac wrote
for (ytile=1; ytile<Y_TILE_MAX; ytile++)
            {
               SMS_loadTileMap(X_TILE_MAX+scrollRightDivided8,ytile-1,&volleyball__tilemap__bin[((BG_TILE_WIDTH*ytile)+(X_TILE_MAX+scrollRightDivided8))*2],2);  // 1 tile * 2 bytes
            }


I don't know why but I had problems with the first row of tiles. Then I have not changed the first row of the scrolling image. The first index inside "for" loop is 1 (ytile=1) and I have no idea I needed to use "y" minus one (ytile-1) in the second argument of "SMS_loadTileMap" function. Shouldn't be only "y" as second argument of the function?


First row of tile is row 0, so you should change your loop into
for (ytile=0; ytile<Y_TILE_MAX; ytile++)

also, since you're loading a single tile, you could use
SMS_setNextTileatXY (unsigned char x, unsigned char y);
SMS_setTile (unsigned int tile);
instead of SMS_loadTileMap... but that's just a small optimization anyway :)
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Fri Mar 18, 2016 10:30 am
I just realized I always forgot to mention that using ConTEXT editor user defined function keys you can speed up things making it run your build.bat or your emulators of choice at the pressing of a key: here are three images explaining that.
F9.png (11.57 KB)
F9 to build your ROM
F9.png
F10.png (12.06 KB)
F10 to run your ROM in MEKA
F10.png
F11.png (12.06 KB)
F11 to run your ROM in Emulicious
F11.png

  View user's profile Send private message Visit poster's website
  • Joined: 28 May 2015
  • Posts: 118
Reply with quote
Post Posted: Tue Mar 22, 2016 9:03 pm
How do i remove what's drawn on screen, and load/display the next background and sprites?

What i am looking to do is, go from the title screen to the play field screen.


Ninja Edit

Never mind, i fixed it with the help of Calindro. Thanks though! 8)
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Tue Mar 22, 2016 10:04 pm
You do exactly the same: load tiles, tilemap and palettes. If you switch from title screen to play screen I suggest you turn off the display in between.

edit: good Calindro :)
  View user's profile Send private message Visit poster's website
  • Joined: 22 Mar 2015
  • Posts: 228
Reply with quote
Post Posted: Wed Apr 13, 2016 1:30 pm
Hi, I went through the lessons and the lesson 4 dont work for me this is the ouput console:

> Executing: C:\Archivos de programa\ConTEXT\ConExec.exe "C:\Archivos de programa\ConTEXT\build.bat"

Build assets.c and assets.h from assets folder
*** sverx's folder2c converter ***
Info: converting field (palette).bin ...
Info: converting field (tilemap).stmcompr ...
Info: converting field (tiles).psgcompr ...
Info: converting score (tilemap).bin ...
Info: converting score (tiles).psgcompr ...
Info: converting sprites (palette).bin ...
Info: converting sprites (tiles).psgcompr ...
Info: conversion completed. File "assets.c" defines 1926 total bytes.
Build Main
main.c:41: warning 112: function 'loadAssets' implicit declaration
main.c:42: warning 112: function 'initGame' implicit declaration
main.c:43: warning 112: function 'updateScore' implicit declaration
main.c:44: warning 112: function 'updateScore' implicit declaration
main.c:47: warning 112: function 'movePaddles' implicit declaration
main.c:48: warning 112: function 'moveBall' implicit declaration
main.c:50: warning 112: function 'drawPaddles' implicit declaration
main.c:51: warning 112: function 'drawBall' implicit declaration
main.c:43: error 101: too many parameters
main.c:44: error 101: too many parameters
main.c:132: warning 112: function 'updateScore' implicit declaration
main.c:132: error 101: too many parameters
> Execution finished.


I dont know what is wrong I already tested SDCC installation, the main.c is attached
main.c (4.82 KB)

  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Wed Apr 13, 2016 2:03 pm
In C one has to declare the functions before using them, so usually
void main (void)

is the last one. Try moving it at the end of the source file.

in general, if you still get a
Quote
warning 112: function 'somefunc' implicit declaration

it means you're using (calling) a function before its declaration. Move up its declaration and you fix that.
  View user's profile Send private message Visit poster's website
  • Joined: 22 Mar 2015
  • Posts: 228
Reply with quote
Post Posted: Wed Apr 13, 2016 2:50 pm
Thank you I did as you said now the source is like this:

void loadAssets (void)
void initGame (void)
void movePaddles (void)
void drawPaddles (void)
void updateScore (unsigned char player)
void moveBall (void)
void drawBall (void)
void main (void)


I dont know if it's well structured but It works I'm moving to the lesson 5.
thanks.
  View user's profile Send private message
  • Joined: 22 Mar 2015
  • Posts: 228
Reply with quote
Post Posted: Wed Apr 13, 2016 10:47 pm
How wonderful this kit is, I was wondering how could I show sprites of 3 frames moving in an infinite loop like a gif image??, but in the sms screen, they are 2h x 4v and 4h x 6v in tiles.
  View user's profile Send private message
  • Site Admin
  • Joined: 19 Oct 1999
  • Posts: 14689
  • Location: London
Reply with quote
Post Posted: Thu Apr 14, 2016 6:14 am
Either put all the tiles in video memory and change the indexes every N frames, or upload new tiles for the sprite every N frames. The size of the sprite, length of any loop (or other animation) and so forth are entirely up to you, which also means the framework won't do it all for you.
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Thu Apr 14, 2016 8:35 am
Also, if you don't need to move these images over a background, you could use the background itself, changing the tiles according to your frame. That's what I would call an 'animated background', e.g. a torch burning while hanging on a wall, as in Prince of Persia for instance...

Really, it everything depends on how many tiles you need. If you can cram everything into VRAM at loading stage, it's easy then. If you can't fit everything into VRAM, then you'll be 'streaming' tiles...
  View user's profile Send private message Visit poster's website
  • Joined: 22 Mar 2015
  • Posts: 228
Reply with quote
Post Posted: Sat Apr 16, 2016 8:46 pm
Thanks for your suggestions, I'm in trouble I cant display the complete sprite 40x48 I was only able to add the tiles 8x16 over the line that the paddles (tutorial) must be so I cant change the horizontal position to show the 40x48 sprite properly, and I dont know how to use the SMS_addSprite() function, could anyone explain me the syntaxis, please?
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Sat Apr 16, 2016 10:05 pm
First parameter is the X position, second is Y and third is image (tile).
So you probably want to place your 8x16 sprites to:

1) x,y
2) x+8,y
3) x+16,y
4) x+24,y
5) x+32, y
6) x+40, y
7) x,y+16
8) x+8,y+16
9) x+16,y+16
10) x+24, y+16

... and so on :)
  View user's profile Send private message Visit poster's website
  • Joined: 04 May 2016
  • Posts: 5
Reply with quote
background not displaying correctly. something with encoding? or me being stupid?
Post Posted: Mon May 09, 2016 2:08 pm
[quote="sverx"]
#define    BG_TILES     0
void loadAssets (void) {
  SMS_loadPSGaidencompressedTiles(field__tiles__psgcompr, BG_TILES);
  SMS_loadSTMcompressedTileMap(0, 0, field__tilemap__stmcompr);
  SMS_loadBGPalette(field__palette__bin);
}


Hi people. A question, I'm new to sms development. And I'm really enjoying this SMSLib. A lot easier than what I started out with. Z80 ASM is not really my forté.

Anyways. I was having trouble displaying the background correctly.

The image and the emulator view are attached. The code and rom are here: https://github.com/fritzvd/melancholy-sweats
title.png (716 B)
the input image
title.png
bgsms.png (15.33 KB)
background in the emulator
bgsms.png

  View user's profile Send private message Visit poster's website
  • Joined: 07 Oct 2015
  • Posts: 114
Reply with quote
Post Posted: Mon May 09, 2016 3:05 pm
It seems that the way you converted your graphics is not compatible with the way you are loading them.

How did you convert your pattern data?
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Mon May 09, 2016 4:13 pm
I see you're using BMP2Tile command line... make sure STM plugin dll is present...

edit: ... or it may easily be the -8x16 option, which is usually meaningful for 8x16 sprites only. Try removing that from the conversion of title.png
  View user's profile Send private message Visit poster's website
  • Joined: 04 May 2016
  • Posts: 5
Reply with quote
Post Posted: Mon May 09, 2016 9:11 pm
sverx wrote
I see you're using BMP2Tile command line... make sure STM plugin dll is present...

edit: ... or it may easily be the -8x16 option, which is usually meaningful for 8x16 sprites only. Try removing that from the conversion of title.png


Ah. man. How could I have missed that. That was it. Thanks so much!

Edit:
Something else though. How would you go about creating sidescrolling WITH compression? Sverx, you created an example with uncompressed data. I'd there a way to do it with compressed data?

And another thing is how would you go about checking for collisions with background tiles? Do you created another tilemap that tracks that?

Anyways. I'm really enjoying this library and this community. Thanks in advance.
yes.png (14.62 KB)
now it works
yes.png

  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Tue May 10, 2016 7:55 am
scrolling a compressed map isn't impossible per se, but you'll need a custom compression that will support accessing the slice of data you need (either a column or a row, according to the way you're going to scroll). Anyway you could store uncompressed maps in RAM that comes from compressed data in ROM, but you'll probably eat your whole RAM quickly, if the map is large.

collision with background: there are a few approaches to that. One is to have a collision map in RAM, of course, another is to store collision information in VRAM tiles, but I'm not suggesting that as it involves reading from VRAM...
  View user's profile Send private message Visit poster's website
  • Joined: 07 Oct 2015
  • Posts: 114
Reply with quote
Post Posted: Tue May 10, 2016 7:13 pm
I personally assign a "behaviour byte" to each tile index. In most of the cases, this is enough. A rock is always a rock and will always be an obstacle.

To calculate collision I just read the correct metatile index from the map in ROM and then look up the associated behaviour in my table of behaviours. You can use individual bits for different stuff, or do your own arbitrary values-based encoding.
  View user's profile Send private message
  • Joined: 22 Mar 2015
  • Posts: 228
Reply with quote
Post Posted: Tue May 10, 2016 7:38 pm
Hi, need help how could I made the sprites to move horizontally I made something like this :

#include "SMSlib.h"   // we're including the library with the functions we will use
#include "assets.h"   // we're including the assets we created before

#define    BG_TILES     0
#define    SPRITE_TILES     256
#define    CHAR_A          SPRITE_TILES
#define    CHAR_B          (SPRITE_TILES+24)
#define    CHAR_C          (SPRITE_TILES+48)               
//2x2 sprites 8x16 pixels
#define    xtiles           2
#define    ytiles           2

void loadAssets (void) {
.....
}
unsigned char playerPosition[2];
unsigned char turn;

void initGame (void) {                                     
  playerPosition[0]=44;                                     
  playerPosition[1]=114;
 
}

void moveCharacters (void) {
#define MINCHARACTERX  8
#define MAXCHARACTERX  (256-8-32-1)                         // 192 before 256 now, UP->LEFT, DOWN->RIGHT
  unsigned int ks=SMS_getKeysStatus();
  if ((ks & PORT_A_KEY_LEFT) && (playerPosition[0]>MINCHARACTERX)) {
    playerPosition[0]-=2;
  } else if ((ks & PORT_A_KEY_RIGHT) && (playerPosition[0]<MAXCHARACTERX)) {
    playerPosition[0]+=2;
  }
  if ((ks & PORT_B_KEY_LEFT) && (playerPosition[1]>MINCHARACTERX)) {
    playerPosition[1]-=2;
  } else if ((ks & PORT_B_KEY_RIGHT) && (playerPosition[1]<MAXCHARACTERX)) {
    playerPosition[1]+=2;
  }
}

void drawFrame_CA (void){
int x=0;
int y=0;
int i;
int j;
int tile=0;

for (j=0;j<ytiles;j++){
    for(i=0;i<xtiles;i++){
                    SMS_addSprite(8+x,y+128,tile);       
                    x=x+8;
                    tile=tile+2;
    }
    x=0;
    y=y+16;
}
}

void drawFrame_CB (void){
int x=0;
int y=0;
int i;
int j;
int tile=xtiles*ytiles*2;

for (j=0;j<ytiles;j++){
    for(i=0;i<xtiles;i++){
                    SMS_addSprite(80+x,y+128,tile);     
                    x=x+8;
                    tile=tile+2;
    }
    x=0;
    y=y+16;
}

void main (void) {
  SMS_setSpriteMode (SPRITEMODE_TALL);
  loadAssets();
  initGame();
  SMS_displayOn();
  for (;;) {
        moveCharacters();
        SMS_initSprites();
        drawFrame_CA();
        drawFrame_CB();
        SMS_finalizeSprites();
        SMS_waitForVBlank();
        SMS_copySpritestoSAT();
    }
}


I used the tutorial's movePaddles() function and edited to pretend the characters to move horizontally and I made my own drawFrame_CA,_CB() functions to draw the characters, but pressing the left or right doesn't have effect, I used the tutorial's initGame() even though I dont know how this function works in my program.
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Tue May 10, 2016 8:00 pm
na_th_an wrote
I personally assign a "behaviour byte" to each tile index. In most of the cases, this is enough. A rock is always a rock and will always be an obstacle.


In (the beta of the porting -test- of) Anguna there was an even simpler approach: all the (meta)tiles above a given number are passable, the others were obstacles. Or the other way around, but you get the point.

@FeRcHuLeS: you're changing the value of playerPosition[0] and playerPosition[1], according to pressed keys... but you're not using these values when drawing the sprites. The whole point of having sprites placed freely on screen is to place them where your variables tell you to draw them :)
  View user's profile Send private message Visit poster's website
  • Joined: 22 Mar 2015
  • Posts: 228
Reply with quote
Post Posted: Wed May 11, 2016 12:21 am
@sverx thank you, I improved my function to draw sprites and they move no matter the size they are they move as I'm pressing left or right, and I figured out how to increase or decrease the speed of movement aswell.

Now I'll try the sprites to move by themselves, I found some algoritms to move sprites they worked in Game Maker engine for somebody in another site, my hope is I can use them with no modifications or a little at least. Thanks again !! :)
  View user's profile Send private message
  • Joined: 22 Mar 2015
  • Posts: 228
Reply with quote
Post Posted: Tue May 31, 2016 4:44 pm
Hi everybody, Could anyone explain me how to use and where to find the vgm2psg tool? isnt this an .exe right?

EDIT: Calindro already taught me how to do it using KiddEd editor thanks anyway. :)
  View user's profile Send private message
  • Joined: 30 Mar 2009
  • Posts: 282
Reply with quote
Post Posted: Thu Jun 02, 2016 2:19 pm
I think i made a mistake when creating the assets?

Lesson 3, doesn't have a moving ball.
All i got is a paddle that is half red and half green that is controlled by port A, and the ball which is controlled by port B.

In order to see if my code was right, i tried compiling the lesson 3 main.c provided, and.. same thing.

The only difference from my code, to yours, is that mine is giving this warning when compiling:

Build Main
main.c:84: warning 110: conditional flow changed by optimizer: so said EVELYN the modified DOG
Linking

I attached the screenshot of what i got so far.
output000.jpg (4.31 KB)
output000.jpg

  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Thu Jun 02, 2016 4:39 pm
... maybe you just made some mistake converting the sprites? ( no removing of duplicates and created using 'treat as 8x16' (even if in this case it didn't change anything) in PSGaiden compressed format )
  View user's profile Send private message Visit poster's website
  • Joined: 30 Mar 2009
  • Posts: 282
Reply with quote
Post Posted: Thu Jun 02, 2016 5:33 pm
Remove duplicates it was.
Now it's working. :)

Thanks sverx, and on to lesson 4.
  View user's profile Send private message Visit poster's website
  • Joined: 12 Oct 2015
  • Posts: 183
  • Location: Ireland
Reply with quote
Post Posted: Thu Sep 15, 2016 12:47 pm
tibone wrote
Remove duplicates it was.
Now it's working. :)

Thanks sverx, and on to lesson 4.


Hi all. Just to add to the thread: I was struggling to even get "Lesson 1 : the game field" working.
I couldn't even load the field tiles / tilemap / palette!

However, I believe I realized what I was doing wrong: at first I was using an older version of bmp2tile, namely bmp2tile040.
I've now updated to the latest version bmp2tile041 and re-did the lesson and now can load (and see) the field OK.

Therefore, making a note of this now just in case anyone else has similar problem :)
BTW: am using the latest version of the devkitSMS from Github
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Thu Sep 15, 2016 1:42 pm
IIRC there were just a couple of small bugs in BMP2Tile 0.40, and none of them should have caused problems in this particular case. But anyway, surely you do better using the latest 0.41 :)
  View user's profile Send private message Visit poster's website
  • Joined: 12 Oct 2015
  • Posts: 183
  • Location: Ireland
Reply with quote
Post Posted: Sun Oct 02, 2016 11:32 am
sverx wrote
IIRC there were just a couple of small bugs in BMP2Tile 0.40, and none of them should have caused problems in this particular case. But anyway, surely you do better using the latest 0.41 :)


Hi sverx - awesome tutorial! I managed to get everything working now except the soundFX. Do you have the sfx_1.psg and sfx_2.psg files available? I'm sure the code is all good but when I try to use my own soundFX I can't get them to play as expected...
E.g. I created a small "soundFX" using Mod2Psg2 on channel 2 only and exported to VGM then used the vgm2psg tool to create the necc PSG file. I code it up to use SFX_CHANNEL2
Again, I'm sure the code is OK but if you have sfx_1.psg and sfx_2.psg files available then I could verify this first. Thanks
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Sun Oct 02, 2016 2:55 pm
I might have these SFXs somewhere, not really sure at the moment :|
BTW I wonder why your own SFX isn't working, it looks like you're doing it the correct way. Channel #2 only, export to VGM, convert to PSG (are you using the -2 switch on vgm2psg converter, right?) and you're done.
If you can't hear that, check if you aren't either firing the SFX at all or you're not calling the PSGSFXFrame() function.
  View user's profile Send private message Visit poster's website
Reply to topic Goto page 1, 2  Next



Back to the top of this page

Back to SMS Power!