A brand new game
for Windows, Linux, Mac, HTML5, 3DS Homebrew and more
SmileBASIC 4 - OneScreen - Tennis
25th April, 2020
Let's code a really simple Tennis/Ping Pong game, shall we?
Create the project
Name the project
and enter the project.
Hit F4, clear out the default code, and get ready to create!
You can hit F5 at any stage along the way, to see what's happening to the game as you code.
Don't forget to frequently hit Ctrl+S to save things as we go.
We'll start by figuring out what we'll need in our game.
Simple.. A bat on either side of the screen, and a ball.
We'll store the data inside Arrays, so we can easily cram them together into easy to use variable names.
An Array is basically a series of variables with the same name, and a number to signify which of those slots we're referring to.
Instead of saying "LeftBat_Position", we can say "Bat". The right bat will be Bat.
This will also give us the ability to write smaller repetitive code, which will work for both bats, by cycling through Bat and Bat automatically, using a simple loop.
Write the instructions for the left bat, and they should work fine for the right bat, too.
Let's have one Array called Bat, to store our two bats, and the other Ball.
We'll give ourselves 4 slots in each array to start with.
For each player we'll only be using 2 slots. One for the Y position on the screen, and a second for the player's score. 2 bats x 2 values = 4 slots.
For the ball, we need an X and Y co-ordinate, and we'll also need the X and Y direction that the ball is moving in. That's 4 slots we'll be needing there, as well, then.
We can cram both of these "DIMensions" onto a single line, to keep things short.
Next, we'll add in a simple loop that will hold our game together, and set up the screen.
ACLS clears everything. Backgrounds, sprites, etc. This gives us a fresh starting point for our game.
XScreen is used to set the resolution that we'll be using. My favourite resolution is 420x240, which seems to fill up the screen nicely.
You can increase this all the way up to 1280x720, if you'd prefer, but the lower resolution gives us nice chunky pixels to look at! Hurray for chunky pixels!
Loop and Endloop are our never ending loop, in which our game will run.
VSync is used at the end of the loop to keep it at the required framerate of the system, esesntially it tells the game to wait for the frame to be drawn to the screen before starting on the next one.
Without a VSync, the game will run WAY too fast!
Let's add out two players, first.
A simple For-Next loop runs through numbers 0 to 1, which will refer to our two bats. (Note : A common coding practice is to start from 0, instead of starting from 1. 0 is a number, too!)
Inside the For-Next loop, anywhere we refer to "P" it will be 0 first, then 1 Next.
First in the loop, we need to define out X position for each bat. When P is 0, it should be on the left, and when P is 1, over on the right.
X co-ordinates are 0 on the left and whatever the screen width is (in our case, 420) on the right.
Y co-ordinates similarly go from 0 at the top to screen height (240, here) at the bottom.
We'll set X to be 16 pixels from the left by default, but when P is 1, it will instead be 16 pixels away from the right edge (420-16 pixels.)
Note how we use the colon ( : ) to place multiple statements on a single line.
We can draw a couple of sprites here, too.
We'll use the P values to refer to each sprite's number. Set them both to image 0 with SPSet (by default, this is a strawberry!)
We then move them using SPOfs, to X,100 (100 being a temporary value until we add movement later), and set their "SPHome" position to 8,8
The Home position tells the program where the sprite should be drawn from. Since the default sprites are 16x16 pixels, if we set the Home to be 8,8 that means that every time we draw it, it will be centered at that position.
If we leave the Home at the default (0,0), then every time we draw the sprite it will extend 16 pixels to the right and down from the position we ask.
For the purposes of this game, everything will be easier if done from the middle, but this command is here to allow for much more variety in your coding.
Run the program to see our two Strawberries.
Strawberries aren't really all that handy for hitting a ball, though, so let's look for something better.
Hit F9 (on the Onscreen keyboard, hit Ctrl, then the 1st green button which should say "SBSMILE.PRG" on it.)
This will bring up the media selection program. From this, use the right trigger on the controller (or number 2 on the keyboard) to make your way to the SPDef tab.
If you scroll down on this, you'll find that number 241 is a nice looking rounded square. This will be ideal!
Head back to the code, and change the sprite SPSet value to be 241.
Next, we'll need to stretch them a little. Can't play Tennis with squares, can we? We need rectangular paddles.
A simple SPScale will suffice. We'll make the paddles 3 times as high as they are wide.
Scale the sprites to be 1 times wide, but 3 times high.
Next up, adding movement to our two paddles.
We'll access the "Stick" positions using the obviously named Stick command. We'll request input from Port 1, and hope that the player's playing with that controller.
The left stick has an ID 0, and the right stick has an ID of 1. Luckily, these are the same numbers as our P value, so we can request stick "P", and get the correct value.
Note : This is very lazily done, but if you want more complicated player controls, you can play around with the various options at a later point. For now, we'll stick with the basics.
We OUTput the values of the controller's stick to StickX and StickY variables, and then simply add the StickY value to that of each Bat.
Don't forget to also set the Y position of our sprites, replacing the 100 in the SPOfs command.
If you run the project now, you'll find two issues. First, the paddles start offscreen, and second, they're very slow.
Let's make a Function!!
A Function is like creating our own command. We can use it at any time, the program will jump to that section of code, run it, and then return to where it was.
We'll DEFine a function called StartGame, at the very bottom of our code, and in there set the two bats to be in the middle of the screen. (120 pixels being the middle of our 240 pixel screen height)
Call our function at the very top, before the main loop starts, and now when we run the game, our two paddles start off in the middle of the screen.
To speed up the paddle movement, a simple multiplier can be used on the position code.
We'll multiply by 8. A random number that I've plucked from thin air, but one that (when you try running the code) should feel nice enough.
If it feels to slow, increase the multiplier. Too fast, reduce it.
Play around with this value until you find a speed for the paddles which is comfortable for you.
Next we should limit the paddle to within the screen's height.
A couple of simple If commands will suffice. If the bat's above the top, move it back down towards the top. If it's below the bottom, move it up towards the bottom.
Note that I've given the top and bottom the same 16 pixel "buffer" that we gave to the left and right sides of the screen, earlier.
For fun, let's also add a Vibrate command, so the controller goes "boop" when we hit the extents of the screen.
Port 1, Side "P", Vibrate ID 4. 4 being a simple "bip".
You can use the F9 media tool to bring up a list of all the different vibrations available.
Right then.. Time to add the ball.
We'll create Sprite 2 using the same 241 square image that we used earlier. As long as we don't scale this sprite, it'll stay as a square, instead of a stretched paddle.
We'll offset it to the position of the ball (Ball values 0 and 1), and center it using SPHome.
Running the program, you'll see the ball snuggled up in the top left of the screen.
We'll have to set its start position like we did the paddles.
Also, every time we lose the ball, we need to respawn it in the center of the screen.
Since we'll be doing this over and over, we'll make a StartRound function to do it.
And don't forget to call it at the start of the game.
OK, let's move the ball.
The X co-ordinate (0) should be moved by the X (2) speed, and the Y (1) by the Y speed (3).
We'll also add limits to the top and bottom of the screen like we did for the Paddles, changing it's Y speed whenever it does.
For this, we'll use the Abs() command. Abs() always gives us the ABSolute (Positive) value of a number. So if we use Abs(5) or Abs(-5), they'll both give us the number 5.
Remember that Y is somewhat inverted, such that higher values are lower on the display.
If the ball hits the top of the screen, we want to change the speed to be positive, so set it to Abs(The Current Speed).. The ball's new speed will always be a positive number and thus head down the screen.
If the ball, instead, hits the bottom of the screen, we take this same positive value and subtract it from 0, giving us a negative number!! This will make the ball head upwards.
If you run at this point, you'll see the ball bounce off the bottom of the screen, then fly off into the abyss.
A couple more If statements will let us retrigger the start of a round, when that happens.
If the ball is further right than the screen width + 16, then that's a Player One win.
If the ball if further left than minus 16, then that's a Player Two win.
When either of these instances we'll simply restart the round, which will bring the ball back to the center of the screen.
Running the game now, you'll see that the ball always starts in the same direction. Let's add some randomness to that.
Rnd(High Number) gives us a random number between 0 and the High Number. If we check when Rnd(10)<5 then that gives us roughly a half/half chance of something happening.
When it does happen, we'll simply reverse the direction of the ball, in the StartRound loop.
We write two of these, one for the X and one for the Y, meaning we have 4 possible start directions for the ball. (up/left, up/right, down/left, down/right)
The randomness isn't perfect, but it'll do for these purposes.
Now we need to collide the ball with the paddles. We'll set up SPCol (Collisions) for the three sprites.
Add an SPCol command to the end of the lines of both the Bat sprite definition and the ball's.
By setting SPCol to 1, it means that any collision detection also take any SPScale into consideration. This way, our bats will still collide accurately, even though they've been stretched.
We can use the SPHitSP command to check for when one Sprite hits another.
SPHitSP(2) will tell us about the Ball (sprite 2) hitting anything else.
It will either return -1 to say it's not collided with anything, or the sprite number of anything it HAS collided with.
If it hits 0 and is moving leftwards (The speed is less than 0), then it's hit the left paddle. Bounce it off the left paddle, by making the X Speed go rightwards.
Similarly, if it hits 1, and is moving right, then that's a bounce off the right paddle. Change it to go left.
We can also add a vibrate, because.. Why not!
Adding the word Beep also causes the ball to ... Beep!
You can now run the game, and paddle the ball back and forth. But we've still no scores, yet.
Luckily, we already left space in the array for storing scores, so all we have to do is add a single point to the player's score whenever the ball is lost.
Bat increases when the ball goes off the right of the screen, and Bat increases when it's off the left.
We can then Print those values to the top of the screen. Ball goes on the left, Ball goes on the right.
Note that the "Locate" function works within the realm of a text grid, not the pixel-precise nature of the sprites.
Divide the screen's width and height by 8, and that'll give you the co-ordinates for Locate.
And that's as far as I'll be going with this example.
But there's lot of things you can do to make the game more interesting.
Use the F9 media list to pick out some different sprites. You can give the left and right paddles their own sprites, have a banana as the ball.
Why not try drawing your own sprites, too, using the F10 image editor.
Best of Ten
Once a player's score reaches 10, play a whistle sound, and stop the game.
If anyone hits a Button(), then call the StartGame function, for another play.
You'll also need to reset the player's score inside that function, too.
Instead of relying on Port One, try creating a two-controller version.
You'll need to change player two's port to port two, and also make use of the XCtrlStyle function, too. (Type in the command XCtrlStyle, then hit F1 to bring up the help for it.)
Look into the methods of writing text to the screen, and see if you can find with a way to display both players scores on either side of the "table", left and right, so that if you had both players holding on to the two sides of a handheld system, they can play facing each other.
The controls already work perfectly for that, but it'd be nice to have a better readout for the scores, right?
Can you rotate the text on either side, so that the numbers face the players?
Using the RndF() command, you can add small floating-point (decimal numbers) additions to the speed of the ball.
Do this whenever it hits a wall or paddle, and you can get some variations in the direction that the ball bounces, making the game a little less predictable.
Whenever the ball hits the bat, you can work out the distance between their Y positions.
Add an amount of this onto the ball's Y speed, and you can give the player a bit of control whenever they hit the ball, with it bouncing up from the top of the paddle, or down from the bottom.