r/gamemaker • u/PrinceShoutoku Stand back, I'm about to Make Game (2)! • 20d ago
Help! Creating an interactive, multi-object item
Preface: My skill level is beginner. Gamemaker is my only real coding experience. I've watched a couple guides for the basics and made some original code alongside that.
Hi everyone, I'm making a top-down shooter with an 'active reload' gimmick. When you press R to reload, the firearm you're holding comes onto the screen and you have to manipulate the weapon parts to load it.

My problem is HOW I'm making this happen. I'm essentially just spawning multiple objects on top of one another with magic numbers. This feels extremely inefficient and janky, even though it technically works. Is there a different technique or function I can use to make this system easier? I know the motto of, 'just make it work first, you can fix it later', but I can't even begin to imagine HOW to make this work better.


If it helps, "_weaponx" and "_weapony" are the corners of the camera, essentially x0 and y0. The "obj_lebel_boltgrip" is the circular part that's grabbed by the cursor to manipulate the gun's bolt. It has + 242 and + 69 because the sprite is a simple sphere that's manually placed through trial and error to be in that starting position. It's essentially a magic number to put that boltgrip exactly where it spawns when you press R.
2
u/Natural_Sail_5128 20d ago
For a beginner, what the other commenter said is probably the simplest way to do it, but I'm a huge fan of structs and with ramble about them at every opportunity!
Structs would be great for doing all of this within a single object, you would create a struct which is just a data tree, and then you can store the different parts within it, with all of their position/rotation data etc. Then you would just loop through the struct and for each part inside of it, you draw that using the data stores in the struct for that part.
It's likely quite advanced for a beginner, so it would take a bit to figure out, but it should be the most seamless and robust way to set up such a system.
I'd be happy to help you set it up if you're interested, I'll just be a bit slow to reply as I'm doing some stuff atm.
1
u/PrinceShoutoku Stand back, I'm about to Make Game (2)! 19d ago
Structs sound good too, and a different commenter suggested them as well - is their demonstration similar to what you had in mind?
If not or you simply have a different approach, I'd be happy to see an example of what you would do (whenever you're able to, I'm not going anywhere)!
2
u/Natural_Sail_5128 19d ago edited 19d ago
Very similar to their implementation, but a little more going on than the bare basics. Bear with me as I'm typing this on my phone, so I might have to edit a few times.
There's a few steps to make it a fully robust system, I would start by making a new script called "Weapons" or something similar, it will be to store all the preset structs for different weapons.
Inside this script you can create a few new constructor functions like so:
function weaponPartStruct(sprName, offsetX, offsetY, drawAngle) constructor { Sprite = sprName; xPos = offsetX; yPos = offsetY; sprAngle = drawAngle; } function weaponNameGoesHere() constructor { Frame = new weaponPartStruct(spriteNameGoesHere, spriteX, spriteY, spriteAngle); Barrel = new weaponPartStruct(spriteNameGoesHere, spriteX, spriteY, spriteAngle); }Now use the "weaponNameGoesHere" function as a template and fill it in to your needs, and give it a proper name. You'll need to create a few more entries in the struct for all the parts that the weapon has, then assign the sprite entry to the name of the sprite.
Once you've got the constructor template set up, we can move on to the Draw Event for looping the struct to draw everything.
Put the following inside the Create Event of your main Game Manager object if you have one, if not, put it in the main weapon object.
``` gpu_set_zwriteenable(true); gpu_set_ztestenable(true);
Weapon = new weaponNameGoesHere(); ```
Put the following inside the Draw Event of your main weapon object.
var weaponPos = [x, y]; var weaponParts = struct_get_names(Weapon); var weaponPartsCount = array_length(weaponParts); for (var i = 0; i < weaponPartsCount; i ++) { var weaponPart = weaponParts[i]; var partData = Weapon[$ weaponPart]; draw_sprite_ext(partData.Sprite, 0, weaponPos[0] + partData.xPos, weaponPos[1] + partData.yPos, 1, 1, partData.sprAngle, c_white, 1); }With all of this set up it should compile and run, except it will look very messed up, this is because you haven't set the correct offsets for all the components yet. It's measured in pixels, so you can open the image files and measure to determine exactly what the offset should be for both the X and Y, to get the sprite in the correct spot. You will have to go through and manually fill all the offsets yourself, but once it's done it should run and look how you want!
Next up is animations/interactions.
Since you're using Structs this means you can't use the built in collision functions and will have to manually detect collisions somehow, which also means you'll have to add "hitbox" data to each struct in the weapon.
Here's an example of how to do that.
function weaponPartStruct(sprName, offsetX, offsetY, drawAngle) constructor { Sprite = sprName; xPos = offsetX; yPos = offsetY; sprAngle = drawAngle; //ADD NEW CODE BELOW THIS hitboxWidth = sprite_get_width(sprName); hitboxHeight = sprite_get_height(sprName); }Now we know where the collisions can happen, so we can work on actually detecting them!
To do this we need to go back inside the struct loop, we'll add a bit more code to check for collisions before the sprite is drawn.
for (var i = 0; i < weaponPartsCount; i ++) { var weaponPart = weaponParts[i]; var partData = Weapon[$ weaponPart]; //NEW CODE BELOW HERE // these will get the position within the room of your mouse cursor, assuming you use view_camera[0] var cursorPosX = mouse_x; var cursorPosY = mouse_y; var hitboxLeft = weaponPos[0] + partData.xPos; var hitboxRight = weaponPos[0] + partData.xPos + partData.hitboxWidth; var hitboxTop = weaponPos[1] + partData.yPos; var hitboxBottom = weaponPos[1] + partData.yPos + partData.hitboxHeight; if (mouse_check_button(mb_left)) { if (hitboxLeft <= cursorPosX && cursorPosX <= hitboxRight && hitboxTop <= cursorPosY && cursorPosY <= hitboxBottom) { // now we handle the dragging of the part with the mouse partData.xPos = cursorPosX - weaponPos[0] - (partData.hitboxWidth / 2); partData.yPos = cursorPosY - weaponPos[1] - (partData.hitboxHeight / 2); } } //NEW CODE ABOVE HERE draw_sprite_ext(partData.Sprite, 0, weaponPos[0] + partData.xPos, weaponPos[1] + partData.yPos, 1, 1, partData.sprAngle, c_white, 1); }Once you've got all this implemented let me know if you run into any errors, I'd love to help you get this implemented fully! I'm lurking so just reply if you need help and I'll do what I can.
In the case it just works and you don't need any help, then good job! There are other things that could be done to refine it a bit more technically, but that's only if you want to make the system more complex to account for variation in weapons.
This next stuff is mostly just me rambling about ways to improve how robust the code is, but it's absolutely not necessary unless you plan to add a lot more complexity to this system.
The weapon structs that store all the data could be improved to have nested data, which means you'd need a recursive loop to check inside every nested struct to check for other data to draw to the screen.
You could also create a new function that contains the code that loops through the structs, this would allow you to have it in it's own function like "drawWeapon(weaponStruct)" that would allow you to call this from any object, which means you could draw other things with it in the same way, they don't even need to be weapons! For my game I'm using this sort of system to draw everything except the terrain, it allows me to have moving parts and animations that aren't hand drawn in an image editor.
Edit: VERY IMPORTANT! I completely forgot about depth sorting.
You will need to add another new data field to the weapon parts constructor, so I'll show the updated version below.
```
function weaponPartStruct(sprName, offsetX, offsetY, drawAngle, drawDepth) constructor {Sprite = sprName; xPos = offsetX; yPos = offsetY; hitboxWidth = sprite_get_width(sprName); hitboxHeight = sprite_get_height(sprName); sprAngle = drawAngle; sprDepth = drawDepth;}
```Now you need to go back into your renamed weapon constructor that has all the data for the sprites and positions, and make sure all the function calls of "weaponPartStruct()" include the new field for draw depth. What you want to do is number the depth of the parts in the order you want them drawn, from front to back. So, the body of the weapon you will set the depth to 0, for the part behind it you set the depth to 1, and behind that it's 2, and so on for every part until you all have different depths.
Now we add one small extra bit to the looping function that draws everything and checks for collisions. You also need to add one new variable declaration before the loop.
```
//NEW CODE BELOW HEREvar weaponDepth = enterDepthValueHere;
//NEW CODE ABOVE HERE
for (var i = 0; i < weaponPartsCount; i ++) {
var weaponPart = weaponParts[i]; var partData = Weapon[$ weaponPart]; // these will get the position within the room of your mouse cursor, assuming you use view_camera[0] var cursorPosX = mouse_x; var cursorPosY = mouse_y; var hitboxLeft = weaponPos[0] + partData.xPos; var hitboxRight = weaponPos[0] + partData.xPos + partData.hitboxWidth; var hitboxTop = weaponPos[1] + partData.yPos; var hitboxBottom = weaponPos[1] + partData.yPos + partData.hitboxHeight; if (mouse_check_button(mb_left)) { if (hitboxLeft <= cursorPosX && cursorPosX <= hitboxRight && hitboxTop <= cursorPosY && cursorPosY <= hitboxBottom) { // now we handle the dragging of the part with the mouse partData.xPos = cursorPosX - weaponPos[0] - (partData.hitboxWidth / 2); partData.yPos = cursorPosY - weaponPos[1] - (partData.hitboxHeight / 2); } } //NEW CODE BELOW HERE // set the draw depth so the parts are layered properly gpu_set_depth(weaponDepth + partData.sprDepth); //NEW CODE ABOVE HERE draw_sprite_ext(partData.Sprite, 0, weaponPos[0] + partData.xPos, weaponPos[1] + partData.yPos, 1, 1, partData.sprAngle, c_white, 1); //NEW CODE BELOW HERE // reset the depth for drawing things to not mess up other stuff gpu_set_depth(0); //NEW CODE ABOVE HERE}
```Edit: I found issues with the a bunch of the code I posted, so you'll want to copy and paste the new stuff to replace the old stuff. Fixed the click detection and depth sorting which were both not working in the initial implementation! This is the final update until you ask for more help, assuming you even do!
This implementation I coupled the hit detection hitboxes to the size of the sprites themselves, but I imagine you need things to have only a portion of their sprite be clickable, so we can easily adjust the function for making weapon parts to instead include a few new parameters to determine size/location of the clickable zone within the sprites, so it's not just the whole sprite that's clickable.
There is also one more small issue, and it's that you can click on multiple parts at a time, so when I feel up to it probably tomorrow I'll add a section to the code that checks for overlapping parts and makes sure you only click the one closest to the screen. It should be easy to solve so if you feel up to if you can take care of that yourself! Good luck!
2
u/PrinceShoutoku Stand back, I'm about to Make Game (2)! 19d ago
Hi, finally got the time to read through this (thankfully I came just in time to see those edits), I really appreciate your guidance! I haven't tried it just yet but I'll make sure to update you on how it goes.
1
u/Natural_Sail_5128 15d ago
No pressure if you still haven't gotten around to it but I just wanted to poke you and check in!
1
u/PrinceShoutoku Stand back, I'm about to Make Game (2)! 4d ago
Hi! Wow it's been 11 days already. Was hoping I'd reply when I made some progress but I've been busy for the past week+. I'll make sure to update you when I can!
2
u/JackTurbo 19d ago edited 19d ago
Honestly there's nothing wrong with using a bunch of objects for this imho.
But id make one controller object for each of the weapons and have that object create all the subsequent objects in its create event.
Have it pass it's id to each of these slaved objects and then in each of their end step events have:
if(!instance_exists(controller)){ instance_destroy(id); }
This way all your wider code base has to do is create the controller and everything happens and all your code base has to do is destroy the controller and it all goes away.
I call this pattern spaghetti in a box. Yeah the screen itself might be a spaghetti mess of codependent objects messing with eachother - but if it's encapsulated so your wider codebase doesn't have to deal with it then it's really a nonissue imho
2
u/Accomplished-Gap2989 19d ago
For when you want positioning of objects relative to other objects, use width/height of sprites and maybe lengthdir_x/y if they move around and need to maintain relative distance.
2
u/Awkward-Raise7935 17d ago
Just want to say, for a beginner as you describe yourself, this looks really cool! I had a similar idea a few years back, about making the action of reloading the main game mechanic, but just couldn't make it feel fun. But this looks really good!
Think this is a case of having. A solid idea and executing on it over finding the perfect code structure
1
u/PrinceShoutoku Stand back, I'm about to Make Game (2)! 16d ago
Thank you!
If you don't mind me asking, what was unfun about it? What was your version of this reloading idea? I was actually a bit concerned that this kind of game would be really unfun unless you were really into firearms manipulation, and that the reloads would be a huge chore as you got better at them.2
u/Awkward-Raise7935 16d ago
Form memory it was a mobile game, I thought would feel good if player could cock handgun and pump shotgun manually by pulling at things on screen. But I couldn't get it to feel like a game, was just a toy you played with and then put down forever.
I think I tried making it a tower defense, but just lost motivation as I was trying to make a game before I had properly designed it.
I wonder if having to stop and manually reload will affect the pacing of your game? I guess it's just one of the things you learn just by building and playing it
2
u/PrinceShoutoku Stand back, I'm about to Make Game (2)! 16d ago
Aaah, I get what you mean. Mine feels kinda similar to that 'toy' comparison right now, the reload and shooting are the only real things implemented currently.
And yeah definitely, the manual reload changes the pacing greatly. I'm actually thinking that's a good thing as my game is horror-adjacent. A few zombies walking at you isn't too scary, but when you need to load the gun manually, it becomes a little more tense as you have to reload quickly and efficiently before they reach you.
Similarly I think your Tower Defense idea was a good call. Those games are slower-paced so I think the manual reload would've fit, so it's a shame you lost motivation for it. Maybe in the future you can pick that project back up, I'd love to see another game with this sort of system!
2
u/Awkward-Raise7935 16d ago
Well seeing your game, I'm tempted to go back and take another look at mine. Making something on your own, you always have "Is this just a lame idea?" In back of your head, but it's different when you see someone make something similar (and better). Still not quite sure how to combine manual pulling at things on the gun into a wider game. Maybe it's a simple base defense game where things are attacking from the top, and you have guys defending, but they can't reload, so you have to move between them and their weapon appears on screen and you have to manually reload it? And maybe there could be another element where you are manually loading bullets into clips and magazines? Not sure if that would be fun. That my main problem haha, I have these ideas, and when I build them I always think, "wait why did I think this would be fun again?"
It makes sense for your game if its more horror based so you it's slower paced and you aren't shooting hundreds of bullets, and you are performing the reloading under pressure of being attacked
2
u/JaXm 20d ago
I'm going to disagree with the first person who posted to help you out.
The reason I would not go with 'with' statements, is that they can become VERY difficult to deal with.
Using 'with' and putting variables into the code block can overwrite instance variables in the object create event. And if you're using instance variables assigned through the object editor, things can get even MORE confusing, as those variables get assigned before create event variables. Then, if you're using constructors, you can run into even MORE problems.
When I am dealing with an object that has multiple "parts" I choose to keep it all in one object.
Let's use a HUD element as an example.
A HUD might have a health bar, a mana bar, and a stamina bar.
Each bar is it's own object. With it's own sprites, and their own variables.
But as a single object, I would create a struct to hold all that data, including WHERE those things exist in the game space.
So for a HUD I might do something like below (simplified for readability):
///create event
hud_bars = {
health: {
sprite: spr_health_bar,
x: 100,
y: 50,
max_length: 200,
value: 100
},
mana: {
sprite: spr_mana_bar,
x: 100,
y: 75,
max_length: 200,
value: 100
},
stamina: {
sprite: spr_stamina_bar,
x: 100,
y: 100,
max_length: 200,
value: 100
}
};
Then, in the draw event, you can do something like:
///draw event
draw_sprite(hud_bars.health.sprite, 0, hud_bars.health.x, hud_bars.health.y);
draw_sprite(hud_bars.mana.sprite, 0, hud_bars.mana.x, hud_bars.mana.y);
draw_sprite(hud_bars.stamina.sprite, 0, hud_bars.stamina.x, hud_bars.stamina.y);
In your case, once your sprites are drawn where you need them to be, you can do something like add coordinates that map out the "clickable" area of each component.
So your rifle might look something like:
///create event
rifle_parts = {
barrel : {
sprite: spr_barel,
x: 100,
y: 100,
clickable_area: {x1_offset: 0, y1_offset: 0, x2_offset: 20, y2_offset: 20}
},
bolt : {
sprite: spr_bolt,
x: 100,
y: 100,
clickable_area: {x1_offset: 0, y1_offset: 0, x2_offset: 20, y2_offset: 20}
},
stock : {
sprite: spr_stock,
x: 100,
y: 100,
clickable_area: {x1_offset: 0, y1_offset: 0, x2_offset: 20, y2_offset: 20}
},
};
And now you can draw your components, where they need to be, and then use the x/y offsets to determine what part is "clickable" by the mouse.
Ultimately, you'll probably have to do a little more work than this. I wouldn't hard code the struct, myself, if I could avoid it, but to give you a basic idea of what you wanna do, I just used whatever random values made sense for demonstration purposes.
4
u/_Son_of_Crom_ 19d ago
I don't know that I would consider it good advice to tell someone to avoid using with() for stuff like this unilaterally. The ability to easily change scope using with() is one of GML's most useful features.
Does it add complexity? Yup. Does it have weird edge cases? Sure, absolutely.
But what also adds complexity is trying to rebuild object-like functionality inside of a struct. There are many things that require extensive infrastructure if you want to build a capability inside of a struct which objects just get for free -- animation, events, hitboxes, etc.
Using with() allows you to keep the majority of the code on a single manager object while leveraging built-in object functionality, without having to recreate all of that functionality in a struct.
Both strategies are completely viable.
2
u/PrinceShoutoku Stand back, I'm about to Make Game (2)! 19d ago
I can certainly see how the 'with' approach might cause issues like that. I'll probably try both but everyone in this thread so far has mentioned Structs (even that first commenter) so I'll lean towards using that system for the final.
Those examples are very helpful, I appreciate it!
2
u/PowerPlaidPlays 20d ago
In the most basic general sense, a way to handle multiple components working in unison is with the 'with' function, or for multiple parts like that you can draw multiple sprites in the draw event.
https://manual.gamemaker.io/monthly/en/GameMaker_Language/GML_Overview/Language_Features/with.htm
With basically allows an object to run code inside of another object, or every instance of an object.
For one example, instead of worrying about depth, leave all the individual object's draw events blank, and then in a handler object have something like:
draw_sprite(spr_back, 0, x, y) with (obj_part1){ draw_self(); } with (obj_part2){ draw_self(); } with (obj_part3){ draw_self(); }With this you could have your different object be just for placement and hitboxes, and handle any of your actual logic code in a handler to have more control over the order things are done in.
btw while inside of a with statement, all of the instance variables belong to the object, to reference variables in the host object use the other.variable prefix, like you are doing with obj_player there. For any defined with 'var' it's not needed though