No Time To Play

Making simple games in sdlBasic

by on Sep.14, 2017, under Gamedev

When I first seriously got into making games for the desktop (it wasn’t my first attempt), the biggest problem appeared to be finding a cross-platform development system that would be reasonably easy to use. No C/SDL for me, sorry. I want to make games, not fiddle with bits and worry about memory leaks.

Turns out, an even bigger problem is ease of distribution, because any framework or library for games seems to be composed of countless DLLs, that in turn depend on other DLLs, and putting them all together is like herding cats.

This quickly led me to check out the various Basic compilers out there, which normally practice static linking, and whose runtimes include support for graphics and input as a matter of fact. Most of them however are Windows-only, some quite expensive, or with an ugly API, or no built-in sound support…

After a failed experiment or two, the situation seemed desperate, when I happened upon something a bit different: sdlBasic, an interpreter (not compiler!) that packages a complete set of bindings to the eponymous library into a stand-alone executable you can just copy around without a worry. It’s even open source!

Don’t be put off by the age of official packages: newer, unofficial builds can be found on the forums. Which, by the way, I warmly recommend. The community is small, but very friendly and helpful, always up for discussing and improving a cool demo, or suggesting workarounds for any issues you might run into.

But enough of an introduction. Put the following code into a file called hello.sdlbas or some such (the extension is important):

setdisplay(800, 600, 32, 1)
setcaption("Hello, world!")

This will open an 800×600 window with 32 bits per pixel (that’s what you probably want nowadays) and fixed size as requested by the fourth argument, set the window title, then wait for a keypress.

The language proper is a procedural Basic with all the usual syntax, and two unusual traits:

  1. It’s dynamically typed — there are no “as whatever” declarations.
  2. It has associative arrays (that can’t contain other arrays, but still).

You can omit the parentheses when calling a subroutine or function with no arguments, and it’s better that way, as sdlBasic will warn you outright if it’s not defined yet.

Now to put some color on the screen. Let’s start with the drawing primitives:

dim yellow = rgb(255, 255, 0)
paper(rgb(0, 0, 255))
rectangle(x, y, width, height, filled) ' true/false
fillcircle(x, y, radius) ' Just circle() for an outline.
line(x1, y1, x2, y2)

There are more of them, but no way to draw thick lines or arcs. To show more complex graphics, you’ll want to load them from files on disc. PNG works fine:

dim slot = 0
loadimage("mycoolart.png", slot)
pasteicon(x, y, slot)
' or
pastebob(x, y, slot)

You only need the latter if you’re working with palette-based art; the alpha channel in full-color images is always recognized. Note the numbered slot system: the prebuilt sdlBasic binaries come with a lot of slots enabled, so a few dozen definitely won’t be a problem. It’s also possible to apply various effects to your images, such as

zoomimage(slot, x_scale, y_scale)

and once you have a nice picture on the screen, you can save it back out to file:

grab(slot, x, y, width, height)
saveimage("screenshot.bmp", slot)

though only as a BMP. A sprite system is also available, but I didn’t get around to using it yet.

Moving on to text, sdlBasic has the good old print statement, that outputs to the console (only good for debugging), and a prints() subroutine which works on the graphics screen, but only in a tiny bitmap font. Short of rolling your own solution, for any serious job you’re going to need a vector font, e.g. from the Open Font Library.

setfont("mycoolfont.ttf") ' .otf is just as good.
text(x, y, height, "Hello, world!")

Easy, but this way the origin point will always be in the top left. To change that, first pre-render the text to an image slot so you can take its width and height and compute another position:

textrender("Hello, world!", height, slot)

sub center_image(img, x, y)
    dim x1 = x - (imagewidth(img) \ 2)
    dim y1 = y - (imageheight(img) \ 2)
    pastebob(x1, y1, img)
end sub

center_image(slot, cx, cy)

For those unfamiliar, the backslash does flooring division in Basic. There’s also a hotspot()subroutine to set the origin point of an image, in case you need to render it repeatedly, but for text that changes all the time the above method works just as well.

As for audio, that’s rather more awkward. Unlike in HTML5 or Pygame, you can only load one piece of music at a time, and for sound effects you have to juggle channels manually:

if musicexists() then
    playmusic(how_many_times) ' Usually 1
end if

loadsound("mysound.ogg", slot)
if soundexists(slot) then
    playsound(slot, channel)
end if

Like with images, you load sounds into numbered slots. You don’t get nearly as many, but there are still enough that a dozen or two won’t be a problem. As for channels, you only need a handful — just consider which effects might need to play at the same time. Setting the volume works as expected, except it goes from 0 to 128, and you set the volume per channel, not per sound:

print musicvolume()
musicvolume(64) ' 50%
print soundvolume(channel)
soundvolume(channel, volume)

While running under Wine, the Windows build of sdlBasic consistently refused to load .ogg files. People running it in Windows say it works, though, and .wav files load just fine in any event.

You have many more ways to play with audio, so check the documentation.

With the multimedia side figured out, let’s see how sdlBasic handles input, the other half of the gaming equation. So far, the two games I made with the language are both turn-based, which is a problem because you see, the API is very much designed for real time.

Now, if you only need keyboard input, it’s tempting to simply call waitkey, then check the return value of inkey right away. Both however only work for keys with an ASCII code attached. You’ll want a more advanced solution:

function keyevent()
    dim k = 0
    while k = 0
        if key(k_left) then
            k = k_left
        elseif key(k_down) then
            k = k_down
        elseif key(k_up) then
            k = k_up
        elseif key(k_right) then
            k = k_right
            k = inkey
        end if
    return k
end function

Of course, most special keys have constants defined, not just the arrows. Note the call to waitvbl (wait for vertical blank), which prevents your idle loop from taking over the CPU by capping it to the monitor’s refresh rate. Last but not least, the wait() at the end — in milliseconds — is so that the game won’t read the same key 5 or 6 times before the player has time to lift the finger again.

Another way is to essentially run the game in real time, but only redraw the screen if anything changed. In turn, you’ll be able to use the mouse:

if mousebutton > 0 then
    dot(mousex, mousey)
end if

Again, much more is possible, but I only needed the basics (no pun intended). Likewise with controllers:

fps(1) ' Enable framerate counting.
dim has_gamepad = numjoysticks > 0

    if has_gamepad then
        prints(getaxisjoystick(0, 0) / 32768)
        prints(getaxisjoystick(0, 1) / 32768)
    end if

Unlike most SDL wrappers, this particular Basic returns the raw 16-bit value of analog axes, forcing you to make an extra division in interpreted code. Oh well. At least getting the framerate count is easy enough.

Stopping here would make for a very brief guide. So far I’ve only used a small fraction of what sdlBasic can do. Couldn’t even figure out how to work with screens and double buffering! But before wrapping up there are some caveats you need to keep in mind.

One is a minor quirk: unlike in most dialects, the rnd() function takes an integer argument n and returns another integer between 1 and n – 1! To avoid surprises, you might want to abstract random number generation behind a function such as

function diceRoll(dice, size)
    dim roll = 0
    dim i
    for i = 1 to dice
        roll = roll + rnd(size + 1)
    return roll
end function

which also helps ground your game design in the physical world — always a good thing.

More noticeable are a couple of issues with the data() construct. While sdlBasic has one (and a read function to go with it), you need to be aware that anything you store gets turned into a string! Make sure to cast values back before use if needed, with int() or similar.

Not so funny is that if you store enough data — more than 14700 values or so — the interpreter will outright crash! And if it looks like a pretty large limit, well, the figure plunges fast once you start adding your own code and variables to the program. The obvious solution is to store game data in files instead, but the file input # construct only reads whole lines at once, so you’ll have to do your own string parsing.

(Last, do note that on Linux you’ll need the SDL 1.2 libraries installed, but all major distros carry them, to the best of my knowledge.)

So I stumbled a few times while using the language, and the third game I was planning to make with it will have to wait. Despite that, sdlBasic is a keeper. Packaging your games is as easy as putting a renamed copy of the interpreter in the archive with your main program (you can always include more files). The API is no-nonsense. Documentation is plentiful, if not quite complete, and there are actual people you can talk to. And if more sophisticated games would have to reinvent features that other languages give you for free, a lot of genres are simpler than they appear.

Use the right tool for the job, and don’t put all your eggs in a single basket.

Creative Commons License
Making simple games in sdlBasic by Felix Pleşoianu is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.


Leave a Reply

Posts by date

September 2017
« Aug    

Posts by month