DEV Community

Julio Fraga
Julio Fraga

Posted on

Going back to Gamemaker - my first functionalities on my point and click game

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();
        }
    }
];
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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 = []
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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)