GM Version: GameMaker:Studio 1.4 (concept should work in earlier versions)
Target Platform: ALL
Example Download: https://csanyk.com/cs/releases/games/LD48-36_AncientTechnologies/LD48_36_Ancient_Technologies_csanyk.gmz
How to make an object that handles multiple collision zones by using multiple sub-objects
I used this technique in my recent Ludum Dare game, Ancient Technologies. The project has a TON of extraneous code in it, some of which is pretty messy, so I’ll replace the download link with a cleaner example project of just this technique in coming days.
If you look at the Atari 2600 console in this game, it is actually multiple objects, made to look like a single object. This enables each switch to have its own collision detection and mouse events.
I accomplished this with a few simple techniques that work really well:
The console itself is the main object. Then there are 6 objects for the 6 switches on the console.
I then took the image I wanted to use for the sprite, and cut out all the different clickable regions, putting each into its own subimage by itself. Then once all the subimages were created, I removed each subimage into its own sprite, and set the collision mask to cover just the pixels in the image
As you can see, this approach results in a lot of “wasted” space in the sprite sheet in the form of transparent pixels, but if you’re concerned about this, you could always achieve the same effect by using sprite origin and sprite offset to position the images without all the whitespace in the sprite sheet. I skipped using sprite origin because I didn’t feel like bothering with counting pixels and setting each sprite origin differently. That would have been tedious, and if I had ever needed to change the sprites, could have thrown everything off and required extensive rework to recalculate again. If your sprites are tiny, it probably doesn’t matter too much, but if they’re quite large, then you should probably use sprite origin/offset.
(Note: With the small pieces, you’d need to use origin numbers that are larger than the dimensions of the sprite itself, or possibly negative values for the origin. You’d also need to carefully calculate the distance to set the origin so that the images all line up precisely. The goal is to make all the images align when each instance’s x and y are identical. This makes them easy to move around together as a group, without having to deal with offsets and so on. To make everything align easy, I created sprites that have the same height and width, so that getting them to align takes no extra effort or calculation at all.)
Next, use the “main” object to generate the collision zone objects, in the create event:
Note the example code below isn’t exactly the code in my project, but is cleaned up a bit for easier comprehension.
///oConsole Create Event: power_switch = instance_create(x, y, oPowerSwitch); tv_type_switch = instance_create(x, y, oTVTypeSwitch); left_difficulty_switch = instance_create(x, y, oLeftDifficultySwitch); right_difficulty_switch = instance_create(x, y, oRightDifficultySwitch); select_switch = instance_create(x, y, oSelectSwitch); reset_switch = instance_create(x, y, oResetSwitch);
Since these objects all use sprites that are the same size, they all line up together when their x,y position is the same. Now you have an easy way to place just the main object and have it create all of its collision zone objects, perfectly aligned.
If the main object needs to be able to move, you just need to update the x,y of all the objects whenever you need to.
Another great thing is that each of these instances can have its own animation which is completely separate from the rest of the collection of objects. This means you can do really sophisticated animation rigging.
Also, you can access the instance variables of the collision instances by using the main instance, like so:
oConsole.power_switch.position = UP;
is (essentially) equivalent to:
oPowerSwitch.position = UP;
This allows you to use the collision zone instances’ instance variables like you would use properties of a helper class in another language, which may make accessing these variables easier if you’re used to other programming languages.
As well, if each of these switch instances were of the same object, using the
id returned by
instance_create() would allow you to differentiate between them. This could be useful for, say, a battleship with multiple turrets that are all of the same object type.
Finally, you’ll want to be mindful that if you destroy the main object, you’ll likely want to destroy all the sub-objects. Perhaps not — maybe you’re doing a spaceship with an escape pod or a rocket with multiple stages that break away. But in that case you’ll wan to be sure that once the main object is gone, you are handling whatever remains somehow.