"What we've got here is failure to communicate."
Cool Hand Luke (1967)
Working with GameMaker — or around it.
One of the biggest drawbacks of GameMaker is the TOTAL lack of anything that resembles a modern UI for your game. There are no buttons, scroll bars, checkboxes, you name it, not even an input box. While creating your own buttons is a no-brainer, the lack of a working input control is really hurting. Sure, there's 3rd party code addressing that, alas, my game, my rules, and one of these rules is: NO 3rd party code.
Luckily, I don't have to deal with that yet and worry about it … later.
The second drawback is: no nesting.
Back in the flash days, you created a MovieClip, dropped your buttons onto it, and when you moved the MC around the buttons stayed where you placed them (relative to the MC's position). Great.
Not so much in GM (well, as far as I know), you're very much on your own again. Top that with the lack of event-bubbling, and you can kiss your easy UI ambitions goodbye.
Now that this is out of the way, let's focus on the thing I'm currently working on:
The Action Menu

The "action menu", opens if you click on an item and the current active character is near enough.
As RGR is controlled by a mouse, I needed some kind of menu when you want to interact with anything in the game world beyond moving around. But the simple idea of the "action menu" opened a cornucopia of challenges and choices.
What should happen:
- "Player clicks on item" ("case_002" here)
- if the character is not standing next to it, walk to the item, no menu yet.
- if the character is close enough, open menu.
- "Open menu"
- get actions, based on character and item [dummy for now, to be supplied]
- render menu
- handle actions and find a way to exit the menu
- "Perform action" [to be supplied]

9-Slice, the perfect tool for the "action menu" background. Easy.
The menu's background and buttons
GM's sprite editor has a convenient panel for editing 9-slices, and they are easy to use if you just drop them into a layer. If you like to use it as the background of an object that you create at runtime, you need to plot it yourself (again, as far as I know). I needed to do that anyway, as the little tip should be plotted centred on top of it. So in the "Draw" event I simply:
draw_sprite_stretched(sprite_index, -1, x, y, m_width, m_height);
draw_sprite(spr_ActionBubble_Tip, -1, x + m_width / 2, y - sprite_get_height(spr_ActionBubble_Tip));
After that, we just add the action buttons. Just. Did I mention: "no nesting"? That leaves two options:
- plot the buttons during the "Draw" event and write my own code to handle the mouse events, or:
- create a button base object and add them to the room, above the menu
I decided against using the draw event variant, as the menu doesn't move while being open and, TBH, it would have been a head fuck.
The next thing I wanted to add, was some kind of overall hit area, which hides them menu if you leave it. The screenshot shows it as a white rectangle. I assumed that the collision mask would still work for a 9-slice, and it would be enough to just expand it outwards for a few pixels to do the magic. I just would need to check if the mouse is inside the collision mask, using:
m_hovering = position_meeting(mouse_x, mouse_y, id);
Well, that doesn't work, like, at all. I ended up trying to get the bounding box of the menu's background and for that, I had to rummage through the docs again.
m_hovering = point_in_rectangle(mouse_x, mouse_y, m_Rect.Left(), m_Rect.Top(), m_Rect.Right(), m_Rect.Bottom()); // position_meeting is NOT working with 9-slices
This finally did the trick. I just had to fill m_Rect
(my own rectangle class) with the help of the sprite_get_bbox_*
methods.
Speaking of sprite_get_bbox_*
, these little buggers hide the fact that it is surprisingly hard to get the absolute pixel positions for (top, left, right and bottom) for a sprite which's origin isn't at 0,0. The sprite_get_bbox_*
methods return their values relative to the origin (of course they do). In order to get the REAL pixel values, you need to take that into account. Simply adding the object's x
and y
to it doesn't work (or it does, you just don't get the result you wanted).
// getting real sprite coords is a pain in the ass, HALFTILE/TILESIZE to create a nice border
m_Rect.Set(
min(x, real(sprite_get_bbox_left(Item.sprite_index)) - sprite_get_xoffset(Item.sprite_index) + Item.x) - HALFTILE,
min(y, real(sprite_get_bbox_top(Item.sprite_index)) - sprite_get_yoffset(Item.sprite_index) + Item.y) - HALFTILE,
max(m_width, real(sprite_get_bbox_right(Item.sprite_index) - sprite_get_bbox_left(Item.sprite_index))) + TILESIZE,
max(m_height, real(sprite_get_bbox_bottom(Item.sprite_index) - sprite_get_bbox_top(Item.sprite_index))) + TILESIZE
);
This makes sure the bounding box always covers the item, but if the menu is wider, it uses the menu's width instead.
Hey, this works!
One thing left was to disable the item's and characters' mouse events while the menu is open. I also need to decide how to explain what each icon does, either in the form of tooltips or something else, but that's for later.

I didn't mention it, but of course, there's also code that makes a character always walk to the front of an item, if possible.
Laters …