PC Games
• Orb Tutorials
• 2D shoot 'em up Latest Updates
SDL2 Versus game tutorial
Download keys for SDL2 tutorials on itch.io
The Legend of Edgar 1.37
SDL2 Santa game tutorial 🎅
SDL2 Shooter 3 tutorial
Tags • android (3) • battle-for-the-solar-system (10) • blob-wars (10) • brexit (1) • code (6) • edgar (9) • games (43) • lasagne-monsters (1) • making-of (5) • match3 (1) • numberblocksonline (1) • orb (2) • site (1) • tanx (4) • three-guys (3) • three-guys-apocalypse (3) • tutorials (17) • water-closet (4) Books A North-South Divide For over a hundred years, messenger Duncan has wandered the world, searching for the missing pieces of an amulet that will rid him of his curse; a curse that has burdened him with an extreme intolerance of the cold, an unnaturally long life, and the despair of watching all he knew and loved become lost to the ravages of time. But now, Duncan is close to the end of his long quest. |
— Creating a lookup system — Note: this tutorial assumes knowledge of C.
Introduction While not directly related to creating a game (and not dependent on SDL at all), some might find this approach to mapping constants to text values rather useful. When loading and saving data, enumerated types and flag values will be stored as numbers. This can make it difficult later to understand what those values represent. Unlike languages such as Java, C is not a reflective language. In other words, there is no way to load a string named "FOOD_PIE" and know that it has a value of 12, even if it is defined in the code in such a way; all that data is lost when the code is compiled. Consider the JSON below, from TBFTSS : The Pandoran War, used to define one of the starfighters: { "name" : "Unique Sphinx", "description" : "", "affiliation" : "", "health" : 999, "shield" : 999, "speed" : 1.6, "reloadTime" : 14, "shieldRechargeRate" : 0, "texture" : "gfx/fighters/zakSphinx.png", "guns" : [ { "type" : "BT_PLASMA", "x" : 0, "y" : -4 }, { "type" : "BT_PLASMA", "x" : 9, "y" : 0 }, { "type" : "BT_PLASMA", "x" : -9, "y" : 0 }, { "type" : "BT_LASER", "x" : 0, "y" : 0 } ], "missiles" : 2, "flags" : "EF_TAKES_DAMAGE+EF_IMMORTAL+EF_MISSION_TARGET+EF_AI_LEADER", "aiFlags" : "+AIF_WANDERS+AIF_UNLIMITED_RANGE+AIF_ZAK_SUSPICIOUS" } For the guns, we have two type - BT_PLASMA and BT_LASER. These are defined in the code as 2 and 3. I'm sure you'll agree that BT_PLASMA and BT_LASER are easier to read and understand instead of the arbitary numbers. Now consider the flags: EF_TAKES_DAMAGE+EF_IMMORTAL+EF_MISSION_TARGET+EF_AI_LEADER. These resolve in the code as 36888. Oof. Not exactly helpful if, like myself, you edit a lot of files by hand. It was because of issues such as this that I created the lookup system, so that I could understand at a glance what an object's definition was. In this tutorial, we'll look at a way to output (and handle input) of data in a human readable fashion. Extract the archive, run cmake CMakeLists.txt, then make, and then use ./lookups to run the code. You will see console output like the screenshot above. Inspecting the code As this tutorial results in console-only output, there isn't too much setup involved. Starting with defs.h:
We've added in a number of enums, some for fruit, some for vegetables, and some for colours. We've also got a few defines, to act as flags to describe attributes. If we now move over to structs.h, we can see we have a single struct defined:
Lookup will be used to handle our lookup data. `name` is the name of the lookup (the key, if you prefer), `value` is the value of the lookup, and `next` is a pointer to the next Lookup in our linked list. With that done, we can head over to lookups.c, where all our lookup logic lives. A number of functions are to be found here, but none overly complicated. Starting with initLookups:
We first memset a variable called `head`, which is acting as the head of our lookup chain. We're then setting `tail` (the end) of the linked list to `head`. After that, we're calling addLookup a number of times, for each of the enum values and defines that we're interested in. We'll see more on this function at the end. For now, know that it is adding a key-value lookup to our lookup linked list. So, at the end of the function, we will have a mapping of our enum and define names to their number values. Coming to the `lookup` function next:
This function takes one argument - `name`, being the name of the lookup to search for. We loop through all our lookups in our linked list until we find the matching name, and then return the value. That's all. Notice how if we fail to find one, we're printing an error and then exiting. This might seem quite heavy handled, but it does help to guard against typos or lookups that you might have forgotten to add. If you preferred, you could omit the exit call. Keep in mind you would need to always return something, which could lead to some unexpected results in your program (the return 0 here exists to keep the compiler happy). Moving onto getLookupName next:
This funciton takes two arguments: `prefix`, the prefix of the define to look for (such as FRUIT_ or COLOR_), and `value`, the value to search for. Once again, we're looping through all our lookups, but now we're searching for a lookup with the same `value` as the value we passed in, and also with a `name` that starts with the `prefix` that was passed in. So, if we passed in "FRUIT_" and 2, the code would find FRUIT_GRAPE. We have several other lookups with values of 2, but since their names don't begin with "FRUIT_", they won't be matched. Once again, notice that we're calling exit if no matching lookup is found, for the same reasons as outlined before. flagsToLong comes next:
This function takes a single argument: `in`, a char array that will be holding our flags. The string is expected to be of the format "FLAG_NAME_1+FLAG_NAME_2", etc. So, the name of the flags, delimited by plus signs. We start by setting a variable called `total` to 0. This variable will hold the return value. We then malloc a variable called `flags`, to the length of `in`, plus one. Next, we copy the contents of `in` into `flags` (using our STRNCPY macro to limit the number of character copied, and also add the null terminator). Next, we use strtok on `flags`, delimiting by "+", and assigning the resulting token to a pointer called `flag`. We're making a copy of the string passed into the function because the strtok function modifies it, and this may be undesirable. We then use a while-loop, calling `lookup` and passing `flag` over to the function. The result of the call to lookup is added to `total`. strtok is called again at the end of each loop (passing through NULL, to continue working with the current data). We finally free `flags`, and return the `total`. We now have the ability to convert a human-readable list of flags into their number value equivalent. Next, we have getFlagValues:
This function operates much like getLookupName, except it works on flags. It takes two parameters: `prefix` and `flags`. We start by memsetting a char array named flagStr (with a length of MAX_LINE_LENGTH - 1024 characters). We then begin looping through all our lookups, performing a bitwise test against each one's `value` with the `flags` we passed into the function, to see if there is a match. If so, we'll also test the `name` of the lookup against the `prefix` passed over. If this check returns true, we have found a flag value bit to match those passed in. Next, we check to see if flagStr contains any data already; we can do this by checking if the first character is a NULL terminator. If it's not, we'll want to first add a + (plus) to the string, to act as a delimiter. We then concatenate the name of the lookup to the string. We continue this until we reach the end of our lookup list, and then return flagStr. This allows us to feed in a number such as 6 and a prefix such as "FLAG_", and convert it to a string containing "FLAG_SWEET+FLAG_SOUR". The final function to consider is addLookup:
This is a basic function to create a lookup using the name and value passed into the function, and add it to our linked list. That's all our code for lookups done, so we can now move onto main.c, where we can see our lookups being put to use. Starting with main:
We're first calling initLookups, to create our lookup table. After that, we're calling a number of functions, to demonstrate the lookup usage: printValue, printFlagValues, printKeyName, and printFlagNames. We'll go through these one at a time. Starting with printValue:
This function takes the name of the lookup value we want to print (as `key`), calls lookup, passing over `key`, and uses printf to print the output. Next, we have printFlagValues:
Again, this function takes the name of the lookup we want to print (`key`), calls flagsToLong, passing over key, and uses printf to print the output. printKeyName follows:
This function is being used to display the output of getLookupName. We're passing over the prefix and num (the value) parameters to the function, and using printf to print the output. Finally, we have printFlagNames:
Once again, we're passing over the prefix and flags parameter to getFlagValues, to show the function working. printf is once more used to display the output. And there we have it, a system to allow us to translate key-value pairs between their human readable format and numbers. I'm sure you'll agree that having the values printed out as text makes them a lot easy to work with and understand. This lookup system has been used in many of our games, and continues to be useful. Download This code is available to download by clicking the link below. lookups.tar.gz (3Kb) |