This blog is bananas, b a n a n a s.
But seriously, let’s talk about callbacks (aka delegates for you C#’ers out there). For a really (read: really really really) long time I did my programming with zero understanding of callback functions. I had heard the term before, but I had no idea what they were, and though I have an almost endless curiosity when it comes to programming, I had somehow locked that subject away in some kind of vault of fear. Callbacks were something that only the best of the best knew how to use, and though I’d strive for that, I thought I wasn’t close to being there. As it turns out, callbacks aren’t so tough to understand after all, and they’re incredibly useful for both game programming and general programming.
Let me start off by saying that callbacks aren’t required for much of anything. I’ve written plenty of decent code without ever having used them. But saying that callbacks aren’t required is kind of like saying that object oriented programming isn’t required, or that a coding IDE isn’t required. It’s technically true, but there is a lot of proven usefulness to it. I’m bringing this up because I want you to feel like you’re a bad programmer if you don’t know what a callback is. We all have blindspots in our knowledge base; I’m sure I have tons of them. If you’ve gotten by without using callbacks before, you’ll probably continue to do so, but if you want to expand your knowledge base a little bit, you’ll find that what callbacks add to your coding repertoire is both useful and cool.
So what exactly is a callback? In it’s simplest form it’s a parameter you pass to a function, but it’s also a function. So basically you’re passing a function to another function. Weird right? I mean, it kind of flies in the face of what traditionally makes sense. As far as most of us have been taught functions take in parameters like ints and strings. Sometimes they take in more complex objects, but the building blocks are generally the same. They’re typically all values. In a way it’s like we’re trained that functions take in nouns – things – not verbs, not actions. Run is a function. It’s something that a thing, like a person, might do. But a jump can’t run; a swim can’t run. Those statements don’t make any sense. So until you wrap your mind around what a callback is, it feels incredibly unnatural. Here’s where it get’s a little easier.
Sure, a callback is a function passed as a parameter, but what helps to understand is the way in which a callback is used. I used the analogy above that ‘a jump can’t run’, but that’s not exactly what a callback is attempting to do anyway. A callback is really more like saying, run and then jump. Why is that interesting though? We can already do that, right? We can call a function called Run and then call a function called Jump. Why would we want to pass the jump function to the run function. In that case we might not, but there are many, many cases where we callbacks can make our lives much easier.
A simple application.
One very simple application for callbacks is key-binding. In almost every modern game you can change your control scheme. If you don’t want X to be kick, you can typically change that. Callbacks make changing the function of the X button incredibly easy.
First, let’s imagine how we’d do loose key-binding without callbacks. To start we might create a function called Press_X. Whenever the player pressed the X button, it would run this function. Inside that function we’d write some code for performing a kick. What we’ve just created is a tight key-bind. Press_X can only do a kick. That’s not what we want though, so now we have to back up and think outside the box a little. Maybe it would make sense Press_X to call a function called Perform_Action. Press_X would pass a parameter of “kick”, and in Perform_Action we’d have a switch statement, or a series of if statements. If the parameter passed was “kick”, we’d do one thing, if it was “punch”, we’d do another. That’s a step in the right direction because we can just have all of our buttons call Perform_Action but just pass different parameters.
That solution certainly works, but in order for players to be able to re-bind those buttons, the parameters have to be stored somewhere and then mapped to the proper buttons. Basically, we have to keep a table where we map all the button values to their appropriate functions; ‘X,- kick, A – Punch, B – Jump’; that sort of thing. Every time we go to perform an action we have to reference that table, get the value that matches the button and then pass that value to another function which then has to iterate through all the possibilities, and then finally perform some action. That’s a lot of work for something we’re doing constantly.
Now let’s look at how we’d do that with a callback. First, we’d create the same function, Press_X. This time it would take in a parameter of a callback function. Then we’d create a function called Kick. We’d then pass Kick as the parameter that for Press_X. Press_X would effectively have one job; execute whatever function got passed to it. It might be Kick, it might be Punch, or Jump, or something else. Press_X could care less what the function is, it just runs it. So now when we rebind our keys we simply re-map what function Press_X is in charge of executing and we’re good. To be fair, we still have to store that somewhere permanent if we want those keybinds to be stored across play sessions, but now, once the game is loaded, there’s no looking up anything in a table, no iterating through possible actions; it’s all right there. You tell X what to do and it does it.
Filling an empty vessel.
Callbacks are often used to give meaning to UI elements, and for good reason. They de-couple the pressing of a button from what the button actually does. If you’ve ever written any .Net code you’re more than likely familiar with this, even if you don’t know it. When you drag a button onto a webpage or a form and then you double click it to add code to it, you’re essentially creating a callback. .Net knows that you want to do when this button is pressed but it can’t even begin to imagine what you’re intention is. The button is an empty vessel and your callback fills it with meaning. In this way, callbacks are incredibly useful for generalizing things that, truthfully, should be general. The X button shouldn’t be tied to punching or kicking or anything really. It, in and of itself, only performs the function of recognizing that the button has been pressed. What that actually means to your game is yours to decide.
Getting a little deeper.
The use of callbacks gets a lot deeper and more interesting than what I’ve laid out here, and I don’t presume to be an expert on the subject. What I think is most important to understand though is that callbacks make it easy to create program shells that can be re-purposed many different ways. Press_X can mean one thing to one game and something different to another. It can even mean something different depending on the screen or the menu that you’re looking at. Press_X itself is a shell, and it’s re-purposed constantly. From a unit-testing perspective callbacks are a dream because if you know that the shell works then that’s one less piece of code you have to test. The wheel get’s reinvented far less often when using callbacks.