LDC beta, DConf blog link, Adam introduces gamehelpers.d

Posted 2019-03-25

I forgot to post last week, so I slapped that post up without commentary, and now for this week, I write a little about a module that has been sitting in my repo for a long time, but hasn't been very useful. Now, it is minimally useful.

Core D Development Statistics

In the community

Community announcements

See more at the announce forum.

What Adam is working on

I had some time to kill offline this week, and I decided to spend it hacking on my gamehelpers.d file. This is one of my framework modules - one that takes pieces from several other (usually unopinionated) independent modules and makes an opinionated whole out of them - which is aimed toward making simple games, in the style of older consoles. This means 2d graphics output, sound output, and gamepad input (and keyboard+mouse is available too, just not the focus here).

However, until this last weekend, I never actually did very much work on this. It imported a few modules and offered a few simple helper functions, but nothing else. That has changed. Let's look at the pieces together.

Overall model

gamehelpers imports arsd.simpledisplay, arsd.simpleaudio, and arsd.joystick to act as the foundation of the framework, and provides an abstract class for you to inherit from to get started. Instead of writing event handlers, you write two primary functions: drawFrame and update, and a few initialization/configuration functions. Your main function calls runGame, passing the instance of your class and the max frame rate you want to shoot for. (BTW I'm of the opinion that the best games ever made were on hardware much less than 1/100 the power of modern computers, so there is no reason for my games to need even 1% of my CPU or memory. I personally like to use 20 fps, and my libraries are liable to cap you out at 60, even if you ask for more.)

The framework passes a Duration argument to update, telling you how much time has elapsed since the last call. You may use this to maintain constant real-time game speed, even if the function calls aren't as frequent as you like. Or, you can simply ignore it and assume your frame rate is close enough. update returns a bool value - true if anything actually changed, false if it didn't. It uses this to optimize redrawing during dead periods, like if your game is paused. You are free to just always return true if you don't want to think about it.

update should, well, update the game's state, but it is also responsible for checking the state of the user's controller and acting accordingly.

After calling update, the framework will (eventually, it might limit the framerate more than it limits calls to update) call drawFrame. drawFrame is responsible for drawing the current state of the game and shouldn't change anything.

Meanwhile, in the background, an audio output thread is created, if you request it by implementing override bool wantAudio() { return true; } in your class. You can send messages to it via the audio object.

My libraries do not support Mac OS (yet). Much of the underlying code is Windows or Linux specific, and thus only those two systems are supported.

Input

gamehelpers builds off arsd.joystick and arsd.simpledisplay. joystick provides a way to check state of gamepads and joysticks (mostly focused on playstation and xbox 360 controllers), and simpledisplay, of course, gives keyboard and mouse input events. gamehelpers saves the events in state tables, with one abstraction in particular: a SNES controller.

I usually use an old PSone controller on my Linux box, and an XBox One controller on Windows. I also have some XBox 360 controllers I sometimes use for player two. As such, my joystick.d library is heavily biased towards these pieces of hardware. It, via a compile-time version, will convert input from one of these devices to the other, so you just pick the api you like. It defaults to PS1, since it is comparably less functional; when in doubt, I like to default to the lowest common denominator.

Which brings me to the SNES choice for gamehelpers' abstraction: it is a couple steps backwards in console game controller evolution to the point where it was easy for me to fully support from a keyboard.

So, the virtual controller in gamehelpers can be used from any of these devices - keyboard or game controller - with no loss. If a game needs analog input, or other keys on the keyboard, it can just access the underlying objects directly, bypassing the snes layer.

Graphics

gamehelpers includes a function create2dWindow which initializes a SimpleWindow with an old-style OpenGL context ready for simple 2d drawing.

It also has a OpenGlTexture class which helps make a drawable texture out of an image. When combined with arsd.png, this makes loading images easy. I am working on .apng file support, coming soon.

Sound

arsd.simpleaudio is pretty bare-bones right now, but it does include an ogg player and some functions to make simple sounds, like beeps, noise, and boops (the function names reflect this literally). It builds on the Windows multimedia API and Linux' ALSA. It also has MIDI support, but that's not usable outside special circumstances right now.

You simply call, for example audio.beep(); and it beeps, mixing in up to 16 simultaneous sounds automatically in a background thread.

So far, it is Atari 2600 style sounds :) But I will add more later.

Example

1 import arsd.gamehelpers;
2 
3 import std.random;
4 
5 final class MyGame : GameHelperBase {
6 	SimpleWindow window;
7 	this() {
8 		// create a window
9 		window = create2dWindow("My Game Title", 512, 512);
10 
11 		// and initialize my state
12 		x = uniform(0, 512 - squareSize);
13 		y = uniform(0, 512 - squareSize);
14 
15 		dx = uniform(-squareSize, squareSize);
16 		dy = uniform(-squareSize, squareSize);
17 	}
18 
19 	final override SimpleWindow getWindow() {
20 		return window;
21 	}
22 
23 	// state for my game
24 	int squareSize = 32;
25 	int x, y;
26 	int dx, dy;
27 
28 	final override bool update(Duration changeInTime) {
29 		// update what changed since last update
30 		x += dx;
31 		y += dy;
32 
33 		// bounce off walls
34 		if(x <= 0)
35 			dx = -dx;
36 		if(y <= 0)
37 			dy = -dy;
38 
39 		if(x + squareSize > 512)
40 			dx = -dx;
41 		if(y + squareSize > 512)
42 			dy = -dy;
43 
44 		// and also check user control input and adjust speed with that
45 		with(VirtualController.Button) {
46 			if(snes[Left]) {
47 				dx--;
48 				audio.noise();
49 			}
50 			if(snes[Right]) {
51 				dx++;
52 				audio.noise();
53 			}
54 			if(snes[Up]) {
55 				dy--;
56 				audio.noise();
57 			}
58 			if(snes[Down]) {
59 				dy++;
60 				audio.noise();
61 			}
62 			if(snes[B]) {
63 				dx = 0;
64 				audio.beep();
65 			}
66 			if(snes[A]) {
67 				dy = 0;
68 				audio.boop();
69 			}
70 		}
71 		return true;
72 	}
73 
74 	override bool wantAudio() { return true; }
75 
76 	final override void drawFrame() {
77 		/* uses old style opengl */
78 		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_ACCUM_BUFFER_BIT);
79 		glLoadIdentity();
80 
81 		glColor3f(1.0, 1.0, 1.0);
82 		glTranslatef(x, y, 0);
83 		glBegin(GL_QUADS);
84 
85 		glVertex2i(0, 0);
86 		glVertex2i(squareSize, 0);
87 		glVertex2i(squareSize, squareSize);
88 		glVertex2i(0, squareSize);
89 
90 		glEnd();
91 	}
92 }
93 
94 void main() {
95 	auto game = new MyGame();
96 	runGame(game);
97 }

That just makes a white box bouncing around the window, that you can influence with the controller. Pressing buttons will make some sound - beware of volume, it can be loud!

Future directions

Well, I have a lot to do to make this fun, but my goal is pretty simply to make 8 or 16 bit style games. I am writing animated png file support (and I have an associated homemade editor using arsd.minigui, I will talk about that eventually) and probably some state transition helpers.

But after this weekend's slapping together, gamehelpers is basically usable now. I just need to find the time to finish all my crazy codes!