Basic Game Programming Part 3


Here’s an experiment to try purely in C++ (or the language of your choice). Print any character you want on a blank terminal screen at a specific location (say 10 characters over and on the 4th line). This is a crucial idea in game programming, because even something as simple as using the arrow keys to move something around a screen requires you to do this.

You’ll find it gets tedious quickly. Let me guess what you came up with. You have two auxiliary functions. One of them prints “\n” an appropriate number of times to move you to the correct line. The other prints a bunch of ” ” to move you over. It works.

Now suppose you want more information on the screen like health remaining or an inventory list. You can’t use that method anymore, because it depends on where the cursor ended during the last thing you drew. This, in turn, depends on information that could be changing (Health: 9 vs Health: 10 places the cursor one more space over).

Matters get even worse if you want to draw something on the right edge of the screen, because you have no idea what the player is looking at. One could use a standard terminal size, another could have maximized it to fullscreen. This changes the number of spaces on a line.

Until you actually try to do these things, or at least seriously attempt a thought-experiment doing it, you will never appreciate why adding an external library to handle this stuff is important. There are tons of these in existence, but probably the most common and universal ones are built from SDL (we will definitely not get into that today).

The simplest for C++ is probably ncurses. It stands for “new curses” and has a pretty interesting history (the name implies there was a curses). It effectively emulates a terminal, which sounds ridiculous at first (why not just use the terminal!) until you think through all the problems above. Today, we’ll make an “@” symbol move around the screen using the arrow keys.

For our purposes, the two most useful functions in the library will be clear() and mvaddch(). As we saw last time, clear() was annoying because it was OS and terminal dependent. Since ncurses emulates a terminal, there is one function that clears the screen independent of OS. The function mvaddch takes 3 arguments, the first two are ints and give the y-coordinate and x-coordinate where you want to place a character, and the third argument is a character that gets placed in that location.

Even though I want to think in terms of x and y coordinates, mvaddch actually takes the row then the column number. This means you put in y first and x second and the y-coordinate counts down from the top of the screen which can be confusing to new users of ncurses.

At this point, we understand the structure of the game loop, so you should basically be able to fill in the rest of the program. There are quite a few subtleties, though, so I’ll go slowly through it.

The only variables I need this time are the x-coordinate and y-coordinate, so I’ll make these global. I started it at (5,5) randomly. We get input using getch(). This returns an integer, but we don’t need to worry about what that integer is. Since we are using the arrow keys we can use KEY_UP, KEY_DOWN, etc which are integers corresponding to the correct arrow directions. Also, using integers is nice rather than characters, because in C++ we can more cleanly write a bunch of “else if” statements with a “switch.”

This lets us make the whole Update() function by changing the x and y coordinates depending on which arrow direction was pressed.

void Update() {
	switch(userInput) {
		case KEY_LEFT:
			XPOS = XPOS - 1;
			break;
		case KEY_RIGHT:
			XPOS = XPOS + 1;
			break;
		case KEY_UP:
			YPOS = YPOS - 1;
			break;
		case KEY_DOWN:
			YPOS = YPOS + 1;
			break;
	}
}

As before, our draw function clears the whole screen and then draws everything. Because of how ncurses works, nothing will be drawn to the terminal until refresh() is called. This is basically so you can store a whole bunch information about what is going to be displayed, and then it can work out how to display it separately:

void Draw() {
	clear();
	mvaddch(YPOS, XPOS, PLAYER);
	refresh();
}

The only thing left is to initialize a bunch of stuff, but the content of the program is really that simple. I commented the initialization lines so you can see what they do, but for the most part, you’ll just copy and paste that whole chunk in every use of ncurses.

#include <ncurses.h>

int XPOS = 5;
int YPOS = 5;
char PLAYER = '@';
int userInput;

void Update() {
	switch(userInput) {
		case KEY_LEFT:
			XPOS = XPOS - 1;
			break;
		case KEY_RIGHT:
			XPOS = XPOS + 1;
			break;
		case KEY_UP:
			YPOS = YPOS - 1;
			break;
		case KEY_DOWN:
			YPOS = YPOS + 1;
			break;
	}
}

void Draw() {
	clear();
	mvaddch(YPOS, XPOS, PLAYER);
	refresh();
}

int main() {

	//Initialize ncurses

	initscr(); //Tells it to make a terminal screen.
  	clear();   //Clears the screen.
  	noecho();  //When user types input, it doesn't appear on the screen.
  	cbreak();  //Typed character is immediately available.
  	keypad(stdscr, TRUE);  //Standard screen.
  	curs_set(0);  //Starts the cursor at (0,0).
  	mvaddch(YPOS, XPOS, PLAYER); //Draw '@' in the initial location.
  	refresh();  //Updates the screen to display the '@'.

	while(true) {

		//Input()
		userInput = getch();

		Update();
		Draw();

		if (userInput == 'q') {
			break;
		}
	}

	endwin();  //Closes the terminal screen.

	return 0;
}

This will probably be the last post in this series. I have one more topic I could do (decoupling the draw loop from the game logic loop). For the most part, you should now be in a position to look up tutorials on using fancier stuff like Unity or Monogame.

The problem I found with those tutorials is that they focus solely on how to use their engine and not on what these underlying components of the engine are (and why they exist). Hopefully after these posts, these concepts make more sense allowing you to more easily jump in.

(For the record, these posts keep getting the C# tag even though I type C++. It autocorrects it upon hitting the publish button.)

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s