Today I'd like to write a little about one of the early hurdles I've had to get over in developing Innkeep!: Masks. It's going to get slightly jargonish, but I will try and keep it relatively simple for non-coders/developers.
A basic part of the design of this game is that it is a kind of fake 3D which makes use of forced perspective trickery. It's 2D, but angled (unlike platformers). It also allows for you to select things with the mouse. An early problem for me was how to bring these things together. The solution required me to deal with GameMaker's mask system and get inventive. First though, what is a mask in game maker? A simple explanation is due. It might help if we first compare masks with sprites.
Here is the current stand-in sprite I am using for the player character, when he is facing away from the screen. A sprite is one, or multiple images, comprised of pixels. It can be still, or animated (for example when walking). By comparison, a mask delimits a certain area in two-dimensional space, which can be used for the purposes of checking if something (like a cursor) is over it, or if something is running into it, i.e. functioning as a "bounding box". They are not visible in-themselves when playing the game.
Suppose that the box we see here is the mask. Using this, we can do some useful things. For example, we can:
- Use it to run a check to see if the object has run into another object, such as a table.
- Use it to run a check to see if a mouse is over it (a check that can be run in conjunction with clicking, to see if you have clicked on the guest.
In an angled perspective 2D environment such as used in Innkeep!, it should be fairly clear why the mask is only covering the bottom part of the character, as opposed to say, a platformer, where most masks would be covering the entire player. Objects can be near or far away, behind or in-front of each other. If the mask was the whole of an object such as the innkeeper, then when trying to walk up to a table you would be bumping your head into it. Instead we get something like this (minus the white box around you of course):
Only when those boxes run into-each other does the game prevent you from moving further "up". Likewise you can go "behind" objects. The order in which the sprites are drawn depends on how high they are on the Y axis. A lower value (i.e. being higher on the Y axis), means you will be drawn before something with a higher number. The point being checked is a specific pixel (the so-called origin point) of the object, which you can select yourself, and typically needs to be inside the objects mask. So this is how we get the layering, and a nice fake 2.5D perspective effect is born. So far, so good. But in Innkeep! you don't simply control a single character with a keyboard, walking around and interacting with things using space etc. Rather I have a Don't Starve esq mouse-based control system that allows me to click on the environment in order to interact with it. But wait. As mentioned before, it is also the bounding box which we use to run our checks such as to see if the mouse is "over" an object. The bounding box is the physical extension of the object in 2D space, with the sprite in a sense just being the clothing it wears. And if the bounding box for an object is smaller than the sprite, then you end up with this weird situation where you can only click on a table or a guest if you click on a certain specific part of that table or guest, a part which is actually invisible. Not good. It seemed that I would actually need two masks. One for clicking, such as above, and one for running into things:
The problem is that an object can't have two masks at once. What to do? My initial attempt at a solution was to have the object swap its masks (by swapping the sprite the mask is mapped to), for just a split second whenever a check for clicking was being done. Everything in the game which could be clicked on, would constantly be checking to see if the mouse was clicked. If it was, it swapped its mask to a version that was as large as itself, in order to see if the player wanted to click on it. A split second later, it was back to its normal "mini" bounding-box mask. This turned out to be complicated to implement, and rather buggy. When you are constantly swapping things in such a manner you need to have a very good understanding of the sequence in which the game is running things. There were too many situations in which it wasn't functioning as it should, and I wasn't sure how to fix it. The solution turned out to be a bit more straight forward than that (although maybe a bit silly sounding for experienced coders, I admit): Two objects! Each with their own purpose mask.
In Innkeep!, clickable objects in the world such as tables or guests, actually have a secret body-double, a unique black-and-white doppelganger instance which is linked to its owner's ID when created. You never see it, as it is perfectly behind the original object holding all the real code, and its mask, though covering its whole body, is not counted as "solid", so ghost-like it perfectly moves around wherever the original object goes. For checks on clicking a table or a guest, it is this second object which is doing the checking. If it is clicked on, it tells its partner "hey, you've been clicked on mate!". So far, so good. But as we know, it is possible for these ghost objects to overlap. What if this is what we end up with?
Unless I click only on his head, clicking on this guest mask would also mean I'm clicking on the table mask at the same time. But I only want one, the one which is "in front" to be selected. What to do? If you are running your checks inside the "step" event of your object (an event which runs code every "step" (typically 1/30 or 1/60th of a second, the equivalent of a frame of animation), then you might end up with a random outcome.
The solution, it turns out, is to run the check within the "draw" function of the object, the function which is responsible for.. you get it, drawing the object. As we mentioned above, this is done in order, according to where the object is on the Y axis. So, say we had a global variable called "Selected." When we click, we are not actually clicking on two static objects, we are clicking on objects which are constantly refreshing themselves, re-drawing themselves in the correct order. Game maker will run the draw function of the object which is higher on the Y first. So actually, when we click: ...first we are clicking on the table, which is being drawn first... Selected = ID of the Table ...and next we are clicking on the guest, which is being drawn next (still the same click as before) ... Selected = ID of the Guest. The global variable has just been over-written. For a fraction of a second, you DID click on the table, but that was quickly overwritten just as fast as the guest was then drawn in-front. It's pixel perfect, and works every time.
Thanks for reading. You can find regular updates concerning Innkeep! here.