tut_ball.md 13 KB

Bouncing Ball

In this tutorial we'll create a program that bounces a ball on the screen.

Displaying the Ball

First things first, start meg4 and select the Sprite Editor. Select the first sprite on the right, and edit it on the left.

Drawing the ball

Now go to the Code Editor. At first, our program will be an empty skeleton.

#!c

void setup()
{
  /* Things to do on startup */
}

void loop()
{
  /* Things to run for every frame, at 60 FPS */
}

Let's place this newly drawn sprite on screen! You can do this by calling the [spr] function. Go to the body of the setup() function, start typing, and at the bottom in the statusbar you'll see the required parameters.

We can see that the first two arguments are x, y, the screen coordinate where we want to place the sprite (if it's not obvious from the name what a parameter does, just press F1 and a detailed help will show up. Pressing Esc there will bring you back to the code editor). The screen is 320 pixels wide and 200 pixels tall, so in order to place at the centre, let's use 160, 100. Next argument is the sprite. We've drawn our ball at the 0th sprite, so use 0. After comes sw (sprite width) and sh (sprite height), which tells how many sprites we want to display. Our ball only occupies 1 sprite, so simply write 1, 1. Then comes scale, but we don't want to magnify our ball, so just use 1 here too. Finally, the last parameter is type, which can be used to display the sprite transformed. We don't want any transformation, so just use 0.

#!c

void setup()
{
  /* Things to do on startup */
  <hl>spr(160, 100, 0, 1, 1, 1, 0);</hl>
}

void loop()
{
  /* Things to run for every frame, at 60 FPS */
}

Now try running this code by pressing Ctrl+R, and let's see what happens. If you have made some mistake by typing the code, an error message will show up in the status bar, and the cursor will be positioned to the faulting part.

If everything went well, then the editor screen will disappear, and black screen with the ball in the middle will appear instead. Our ball isn't exactly at the centre, because we forgot to subtract the half of the sprite's size from the coordinates (we are displaying only one sprite here (sw = 1 and sh = 1), so 8 x 8 pixels in total, half of that is 4). Press F2 to go back to the editor, and let's fix this.

#!c

void setup()
{
  /* Things to do on startup */
  spr(<hl>156, 96</hl>, 0, 1, 1, 1, 0);
}

void loop()
{
  /* Things to run for every frame, at 60 FPS */
}

Let's run this again! We have the ball at proper position, but we can still see it at the wrong position! That's because we haven't cleared the screen. We can do that by calling [cls], so let's add that before drawing the sprite.

#!c

void setup()
{
  /* Things to do on startup */
  <hl>cls(0);</hl>
  spr(156, 96, 0, 1, 1, 1, 0);
}

void loop()
{
  /* Things to run for every frame, at 60 FPS */
}

Now everything is fine, our ball shown at exactly the centre of the screen.

Moving the Ball

There's a problem in our code. We display the ball in setup() which only runs once when our program starts. To move the ball, we have to display it over and over again, at different positions. To achieve this, let's move the code that displays our ball into the loop() function. This runs every time when the screen is redrawn.

#!c

void setup()
{
  /* Things to do on startup */
}

void loop()
{
  /* Things to run for every frame, at 60 FPS */
<hm>  cls(0);
  spr(156, 96, 0, 1, 1, 1, 0);</hm>
}

If we press Ctrl+R now, then the ball will be displayed exactly as before. What we can't see is that the ball is now drawn not only once, but over and over again.

Let's move that ball! It is displayed at the same position, because we have used constant coordinates. Let's fix this by introducing two variables, which will store the ball's current position on screen.

#!c

<hl>int x, y;</hl>

void setup()
{
  /* Things to do on startup */
}

void loop()
{
  /* Things to run for every frame, at 60 FPS */
  cls(0);
  spr(156, 96, 0, 1, 1, 1, 0);
}

Replace the coordinates in drawing call to these variables, and let's assign them values on program start.

#!c

int x, y;

void setup()
{
  /* Things to do on startup */
<hm>  x = 156;
  y = 96;</hm>
}

void loop()
{
  /* Things to run for every frame, at 60 FPS */
  cls(0);
  spr(<hl>x, y</hl>, 0, 1, 1, 1, 0);
}

If we run our program now, there'll be still no change. However using variables we can now change the ball's position in run-time, let's do that.

#!c

int x, y;

void setup()
{
  /* Things to do on startup */
  x = 156;
  y = 96;
}

void loop()
{
  /* Things to run for every frame, at 60 FPS */
  cls(0);
  spr(x, y, 0, 1, 1, 1, 0);
<hm>  x = x + 1;
  y = y + 1;</hm>
}

Run this program, and you'll see the ball moving!

Bouncing the Ball

We are not ready yet, because our ball disappears pretty quickly from the screen. This is because we constantly increasing its coordinates, and we don't change its direction when it reaches the edge of the screen.

Just like as we did with the coordinates at first, we are using a constant and now we want to change the direction in our program dynamically. The solution is the same, we replace the constants with two new variables.

#!c

int x, y<hl>, dx, dy</hl>;

void setup()
{
  /* Things to do on startup */
  x = 156;
  y = 96;
  <hl>dx = dy = 1;</hl>
}

void loop()
{
  /* Things to run for every frame, at 60 FPS */
  cls(0);
  spr(x, y, 0, 1, 1, 1, 0);
  x = x + <hl>dx</hl>;
  y = y + <hl>dy</hl>;
}

Great! As mentioned before, the screen is 320 pixels wide and 200 pixels tall. This means that the valid values for the x coordinate are between 0 and 319, and for y between 0 and 199. But we don't want our ball to disappear from the screen, so we have to subtract the sprite's size (8 x 8 pixels) from these. This gives 0 to 311 for x, and 0 to 191 for y. If our ball's coordinate reaches one of these values, then we must change it's direction so that it won't leave the screen.

#!c

int x, y, dx, dy;

void setup()
{
  /* Things to do on startup */
  x = 156;
  y = 96;
  dx = dy = 1;
}

void loop()
{
  /* Things to run for every frame, at 60 FPS */
  cls(0);
  spr(x, y, 0, 1, 1, 1, 0);
  x = x + dx;
  y = y + dy;
<hm>  if(x == 0 || x == 311) dx = -dx;
  if(y == 0 || y == 191) dy = -dy;</hm>
}

Run this program by pressing Ctrl+R. Congratulations, you have a moving ball that bounces off the screen edges!

Adding a Bat

A game that we can't play with isn't very interesing. So we'll add a bat that the player can control.

Go to the Sprite Editor and let's draw the bat. This time we'll make it three sprites wide. You can draw these one by one, or you can select multiple sprites on the right and edit them together on the left.

Drawing the bat

Just like with the ball, we'll use [spr] to display it on screen. But we also know that we'll need a variable to store its position, so let's add that too at once.

#!c

int x, y, dx, dy<hl>, px</hl>;

void setup()
{
  /* Things to do on startup */
  x = 156;
  y = 96;
  dx = dy = 1;
}

void loop()
{
  /* Things to run for every frame, at 60 FPS */
  cls(0);
  spr(x, y, 0, 1, 1, 1, 0);
  <hl>spr(px, 191, 1, 3, 1, 1, 0);</hl>
  x = x + dx;
  y = y + dy;
  if(x == 0 || x == 311) dx = -dx;
  if(y == 0 || y == 191) dy = -dy;
}

So the x argument becomes px, and we don't want to move the bat vertically, just horizontally, so the y argument is a constant 191. Because we draw the bat at the 1st sprite, the sprite parameter is 1. And because it is three sprites wide, sw is 3, but it is still just one sprite tall, so sh is 1.

So far so good, but how will the player move this bat with the mouse? We'll set the mouse coordinate to the px variable for that. If you go the memory map, then under [pointer] you can see that the mouse's X coordinate is stored on 2 bytes at address 00016. To get this value, we use the [inw] function (word, because we need 2 bytes). But we also must not allow moving the bat off screen, so we clamp if the coordinate is bigger than the screen size minus the bat's size (which is three sprites, so 24 pixels). One more thing, the offsets in the memory map are given in hexadecimal, so we need the 0x prefix to tell the compiler that this is a hexadecimal number.

#!c

int x, y, dx, dy, px;

void setup()
{
  /* Things to do on startup */
  x = 156;
  y = 96;
  dx = dy = 1;
}

void loop()
{
  /* Things to run for every frame, at 60 FPS */
  cls(0);
  spr(x, y, 0, 1, 1, 1, 0);
<hm>  px = inw(0x16);
  if(px > 296) px = 296;</hm>
  spr(px, 191, 1, 3, 1, 1, 0);
  x = x + dx;
  y = y + dy;
  if(x == 0 || x == 311) dx = -dx;
  if(y == 0 || y == 191) dy = -dy;
}

Let's run this program! We can see the bat and we can also move it around with the mouse. But there's a problem. The ball doesn't care where the bat is. Let's fix this by modifying the screen bottom check to a bat position check so that it would bounce on the bat only. Don't forget that the ball is 8 pixels tall, so we must check at a smaller coordinate.

#!c

int x, y, dx, dy, px;

void setup()
{
  /* Things to do on startup */
  x = 156;
  y = 96;
  dx = dy = 1;
}

void loop()
{
  /* Things to run for every frame, at 60 FPS */
  cls(0);
  spr(x, y, 0, 1, 1, 1, 0);
  px = inw(0x16);
  if(px > 296) px = 296;
  spr(px, 191, 1, 3, 1, 1, 0);
  x = x + dx;
  y = y + dy;
  if(x == 0 || x == 311) dx = -dx;
  if(y == 0 || <hl>(y == 183 && x >= px && x <= px + 24)</hl>) dy = -dy;
}

This means the y coordinate is zero, or it is 183 and at the same time the x coordinate is between px and px + 24 (where px is the bat's position).

Game Over

Now that we have changed the bottom check, we have to add a new check to see if the ball has left the screen. Of course this would mean game over.

First, remember that loop() runs constantly, so to stop moving the ball any further, we set the dx and dy variables to zero. Second, we want to display a game over message.

#!c

int x, y, dx, dy, px;
<hm>str_t msg = "GAME OVER!";</hm>

void setup()
{
  /* Things to do on startup */
  x = 156;
  y = 96;
  dx = dy = 1;
}

void loop()
{
  /* Things to run for every frame, at 60 FPS */
  cls(0);
  spr(x, y, 0, 1, 1, 1, 0);
  px = inw(0x16);
  if(px > 296) px = 296;
  spr(px, 191, 1, 3, 1, 1, 0);
  x = x + dx;
  y = y + dy;
  if(x == 0 || x == 311) dx = -dx;
  if(y == 0 || (y == 183 && x >= px && x <= px + 24)) dy = -dy;
<hm>  if(y > 199) {
    dx = dy = 0;
    text(184, (320 - width(2, msg)) / 2, 90, 2, 112, 128, msg);
  }</hm>
}

We can display text on screen using the [text] function. At the bottom, the quick help shows what arguments it has. The first one is palidx, which is a palette index, the color of the text. Press F3 and click on the desired color. At the bottom, we'll see the index in hexadecimal and in parenthesis in decimal as well.

I have choosen a red color, by the index B8 or 184 in decimal. Let's go back to the code editor by pressing F2, and enter this number. The next two arguments are x and y, the position on screen. We could have count the number of pixels in the text, but we are lazy, so we use the [width] function for that. Subtracting this from the width of the screen and dividing by two will place the text exactly at the middle. Because we are too lazy to type the text twice, we've also used a string variable msg to store the message. Therefore we use msg once when we calculate its size, and also when we pass it to the display function to say what to print. After the coordinates comes the type, which is the font's type, or more specifically its size. We want big letters, so we've used 2, double size. After this comes the shadow, shidx, which is also a color index. I've choosen a darker red here. For a shadow, it is important how transparent it is, we can specify this in the sha argument. That is an alpha channel value from 0 (fully transparent) to 255 (fully opaque). By using 128, which is half way between these values, I've told to use half transparent. And finally the str argument specifies the text to be displayed, which we store in the msg variable.

Restart

And last, if the player clicks, we want to restart the game.

#!c

int x, y, dx, dy, px;
str_t msg = "GAME OVER!";

void setup()
{
  /* Things to do on startup */
  x = 156;
  y = 96;
  dx = dy = 1;
}

void loop()
{
  /* Things to run for every frame, at 60 FPS */
  cls(0);
  spr(x, y, 0, 1, 1, 1, 0);
  px = inw(0x16);
  if(px > 296) px = 296;
  spr(px, 191, 1, 3, 1, 1, 0);
  x = x + dx;
  y = y + dy;
  if(x == 0 || x == 311) dx = -dx;
  if(y == 0 || (y == 183 && x >= px && x <= px + 24)) dy = -dy;
  if(y > 199) {
    dx = dy = 0;
    text(184, (320 - width(2, msg)) / 2, 90, 2, 112, 128, msg);
    <hl>if(getclk(BTN_L)) setup();</hl>
  }
}

To query if the user has clicked, we use the [getclk] function, with a BTN_L argument, meaning is the left button clicked. We've used the setup() function to set the default values for our little bouncing ball game. This is very convenient, because calling setup() now will therefore simply reset our game.