Hello everyone! This is the first post I'm doing about the game I'm making with my wife.
If you don't know what I'm talking about but you're interested in game development and/or Gamemaker, please, read this other post I made where I explain this project.
So what are the things I've programmed so far, and how did I program them?
Here goes a quick summary:
- Menu with interactive buttons
- Clickable (collectable) items
- Inventory system
- Tips system
Here I go in details about each of them. Keep in mind this isn't a tutorial, just an overview on the logic/structure of the project. I think it can keep things short and sweet, enough to inspire and explain, not too much to be boring.
Menu
A menu with interactive buttons generated from a data list. Like this:
Create obj_menu_controller
menu_items = [
{
label: "Start",
callback: function() {
room_goto(TestRoom);
}
},
{
label: "Quit",
callback: function() {
game_end();
}
}
];
After that, we simply spawn the buttons, objects which each own has it's code to render a simple sprite with the text on top of it, and also changes sprites when mouse hovers it.
Draw GUI obj_button
if (hover) {
draw_sprite(spr_button_hover, 0, x, y);
} else {
draw_sprite(spr_button, 0, x, y);
}
// Text
draw_set_halign(fa_center);
draw_set_valign(fa_middle);
draw_set_color(c_black);
draw_set_font(fnt_menu_buttons);
draw_text(
x + width / 2,
y + height / 2,
label
);
draw_set_halign(fa_left);
draw_set_valign(fa_top);
Please don't judge my art style. I only made it to fill the gap. It's a temporary background (obviously) until my wife actually creates the final version
Clickable (collectable) items
Using a cookie as an example for now, I created clickable items that are added to your inventory via function call.
This one is a bit more elaborate. I started by making a parent object called obj_interactable. This one is intended to be one of the bases of the game. This will give the properties to children objects to be clickable, but each with custom behavior.
Create obj_interactable
action = function() {
show_debug_message($"You need to define the variable 'action_type' in the variable definition tab under your object so you can choose the behavior for this interactable. Please check the enum in obj_interactable. \n If you want a custom behavior, do like so: \n action = function()\{your_custom_behavior_here()\}");
}
enum action_types{
collect_action = 0,
}
if (instance_exists(obj_inventory)){
if (obj_id == "default_interactable_id"){
obj_id = $"default_interactable_id{length(obj_inventory.inventory_items) + 1}"
}
}
switch(action_type){
//collect_action
case 1:
action = function() {
collect(id, obj_id);
}
break;
default:
action = function() {
show_debug_message($"You need to define the variable 'action_type' in the variable definition tab under your object so you can choose the behavior for this interactable. Please check the enum in obj_interactable. \n If you want a custom behavior, do like so: \n action = function()\{your_custom_behavior_here()\}");
}
break;
}
As you can see, the behavior of the click changes based on the definition of the action_type variable.
That way, clickable things inherit all the properties that make something clickable (which aren't many currently, but can quickly escalate), while also being able to use different behaviors.
Left Pressed obj_interactable
action();
I also defined these variables via the UI, in the variable definitions tab.

I made it because I think it would be more obvious and easier to just create a lot of children objects and just tweak some values in the UI instead of changing the code directly. I think it also makes clear the responsibility of the parent object to deal with base properties and behaviors, and the clickable, as it's children, to choose what behavior it will have. That way, we can have unique objects but each with base behavior already ready to use.
Inventory system (which only adds items for now)
A simple UI that displays the items (each with it's own custom inventory sprite - different from the "in game" sprite) in a horizontal list.
Here, we have 2 objects. The first will be used as the "source of truth" for what the players are carrying in their inventory. We'll call it obj_inventory. For now, it only has the Create event, with this inside:
Create
global.inventory_items = []
The items show in the top corner, they slide down every time you pick something up and then they slide back up (outside of the screen).

For some weird reason my screen recorder doesn't show my mouse, but you can see when I click the cookies that they go to the top of the screen, as if they were in my inventory.
If you hover your mouse near to the top of the screen, it slides down again. If your mouse leaves that region it slides back up.
The second object is the obj_inventory_ui. That's what we use to draw the inventory.
Draw GUI obj_inventory_ui
if (instance_exists(obj_inventory)){
for (var i = 0; i < array_length(global.inventory_items); i++) {
draw_sprite(global.inventory_items[i].sprite, 0, xPos + i * 40, yPos);
}
}
Basically what we are doing here is, we are using draw_sprite() always in the same Y position and just varying the X position , always adding to the right of the previous item. This will probably need some improvement given that this will need to be a grid that breaks the line every time the width limit is reached, but since we are just testing functionalities for now, I think it's okay like that.
Adding items to the inventory
This one is pretty simple. I decided to go with a simple function inside a script, so you can call it from any object without the need to reference the obj_inventory object. I think it keeps it more clean that way, allowing the obj_inventory to manage it's own responsibilities.
src_inventory
function add_to_inventory(obj_id = ""){
show_debug_message($"Added ${obj_id} to inventory");
if (instance_exists(obj_inventory)){
array_push(global.inventory_items, to_inventory_item(obj_id));
on_inventoy_added();
}
}
function on_inventoy_added(){
if (instance_exists(obj_inventory)){
show_debug_message($"Inventory: {string_join(", ", global.inventory_items)}");
}
obj_inventory_ui.show_inventory_for(3);
}
As you can see I added a function called on_inventory_added(). It doesn't do much for now except show the inventory for a few seconds, but I want to improve it in the future and add a parameters that will take a list of "actions" that can be completely customized, so we can have a custom behavior for each item that gets added to the inventory.
At last, we have the obj_collectable. This one, is a child of the obj_interactable object.
It uses the collectable action_type, which is basically this:
src_collectable
function collect(instance, obj_id = ""){
show_debug_message($"Collected {obj_id}. Adding to inventory");
add_to_inventory(obj_id);
show_debug_message($"Destroying ${obj_id}");
with (instance) instance_destroy();
}
Very simple stuff. We just call the add_to_inventory() function and then destroy the instance, removing it from the screen (as you can see in the gif above).
The add_to_inventory() will then call the to_inventory_item() function, which will transform that obj_cookie, whose parent is obj_collectable, whose parent is obj_interactable, and will transfrom it in this format:
src_object_conversor
function to_inventory_item(obj_id) {
switch (obj_id) {
case "cookie":
return {
obj_id: "cookie",
name: "Cookie",
sprite: spr_cookie_inventory,
};
default:
return undefined;
}
}
Maybe I should rethink the name.
With this we can make sure that the objects inside the inventory list have an expected structure, avoiding errors with missing values and properties.
For now, we can only add to the inventory, so things can still change until I come up with removing/editing functionalities.
Tips system
You can add a tip via function call!
You can choose how many seconds it stays on the screen OR you can make it persistent and not disappear from screen.
When added, a tip will come from the left side of the screen, sliding, and stay there until the timer is over, so it slides back to the left disappearing.
If it's persistent, it only slides in and doesn't go back.
It works in a very simple way. We call and add_tip() function, that spawns an obj_tip with the time, and content desired
src_tip
function add_tip(content, is_permanent, time_active_seconds = 10){
clear_tip();
tip = instance_create_layer(0,0, "UI", obj_tip)
tip.setup(content, is_permanent, time_active_seconds);
}
And the obj_tip takes care of the rest, using a timer and the lerp() function to slide in and out of the screen
Step obj_tip
if (!is_permanent){
time_active_seconds --;
if (time_active_seconds > 0){
target_x = 0;
}
else {
target_x = -abs(rectangle_width);
}
}
start_x = lerp(start_x, target_x, 0.15);
And here is how we draw things
Draw GUI obj_tip
draw_set_alpha(0.5);
draw_set_color(c_black);
draw_rectangle(
start_x,
start_y,
start_x + rectangle_width,
start_y + rectangle_height,
false
);
draw_set_alpha(1);
draw_set_color(c_black);
draw_set_halign(fa_left);
draw_set_valign(fa_top);
draw_text_ext(
start_x ,
start_y,
content,
line_sep, // line spacing
rectangle_width // wrap width
);
// Reset
draw_set_halign(fa_left);
draw_set_valign(fa_top);
Final considerations
Apart from the menu functionality, all the rest is pretty dependent on one another. You can't make the collectables without thinking of the inventory system, and you can't just not add an UI for it, since it's a game after all. I think this is a pretty good start, setting the base of how this (very important) system will work. I should probably work in improving it soon.
Anyways, I hope that was fun to read and hope someone can take some knowledge from it. Leave your suggestions on how I could improve any of this. See you next time!




Top comments (0)