Making simple games in sdlBasic =============================== 2017-09-14 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! [sdlBasic]: http://www.sdlbasic.altervista.org/main/ 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!") waitkey This will open an 800x600 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) ink(yellow) paper(rgb(0, 0, 255)) cls 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][ofl]. [ofl]: https://fontlibrary.org/ 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: loadmusic("mymusic.ogg") 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 waitvbl 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 else k = inkey end if wend wait(150) 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 do cls prints(fps) if has_gamepad then prints(getaxisjoystick(0, 0) / 32768) prints(getaxisjoystick(0, 1) / 32768) end if waitvbl loop 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) next 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.