Warning: Constant WP_CRON_LOCK_TIMEOUT already defined in /home2/jemston4/public_html/wp-config.php on line 91

Warning: Constant AUTOSAVE_INTERVAL already defined in /home2/jemston4/public_html/wp-config.php on line 92

Warning: Constant WP_POST_REVISIONS already defined in /home2/jemston4/public_html/wp-config.php on line 93

Warning: Constant EMPTY_TRASH_DAYS already defined in /home2/jemston4/public_html/wp-config.php on line 94
Creating Context Menus – JEMstone Games

Creating Context Menus

This is going to be a quick and ugly tutorial on creating instance-specific menus, the type you might see in a city-builder when you right click on object. /u/The_Capptianm requested a tutorial on something like this. This is just for demonstration purposes, so we’re going to have a basic system that will create the menu as soon as the left mouse button is pressed. Here’s a video of the end product and a video version of this tutorial:

Step 1: Create Sprites

We’re going to create 3 Sprites, one for our people, one for our buildings, and one for our menu buttons. I’m going to make mine monochromatic so we can use draw_color_ext to allow our objects to differentiate themselves with different colors.

sBuilding

This is going to be a simple sprite used as a building. This one will be 64×64.

sPerson

Another simple sprite used to represent a person, this will be 32×32.

sButton

This sprite will be 64×18 and will use 3 pixels on each side as a shadow to make the button look 3D. We will draw text over it.

Step 2: Setting up the Room

This part doesn’t matter that much how you set it up. I left it with the default width and height and gave it a light green background. We’re going to have 3 Layers, from top to bottom should be: “GUI”, “Instances”, “Background”.

Step 3: Creating Objects

Planning Inheritance

The easiest way to accomplish menu systems is to set up a robust inheritance system. This allows you to program behavior for a group of objects and eliminates a lot of tedium. We want to have a group of buildings for our town and a group of people to live in our town. In this example we’re just going to use a small sample to show how it’s done. For our buildings we’ll want a House and a City Hall. For our civilians we’ll want a generic Person and an Alien. Both types of buildings will share certain characteristics and both civilians will share certain characteristics. All game objects will also share the behavior that when clicked will create a menu. To this end we will create a hierarchy that looks something like this:

Basic Plan for Game Object hierarchy

The Game Object

In my games I like to have a master Game object that handles a lot of the functionality of the game itself. This also creates a centralized hub to control settings and initialize global variables. We are going to add a Create Event to our Game object:

// Game_Create
randomize();
global.selected = noone;
enum Options {
    Move,
    Delete,
    Change,
    Tax
};
optionString = [
    "Move",
    "Delete",
    "Change",
    "Tax 'Em!"
];

We’re going to use a global variable global.selected to track the id of the currently selected object. This prevents us from accidentally trying to access the menu of multiple objects at a time.

Each object in our game that will have a menu will have an array named options that stores which menu options are available to it. Our Game object will store the “library” for these menu options and we’ll store this in an enum named Options. Right now, our available options are Move, Delete, Change, and Tax.

We’ll also need a way to display these options to the user, so we’ll create an array that stores the strings explaining what each option is. We’re going to call this array optionString and we’ll be able to access it from other objects by using Game.optionString[].

Place this object in your room.

The gameObjectParent object

The next thing we want is a “Master Parent” object for all interactive objects in our game. The two properties every object will have in this game is a color and an array of options. The color will be used, as explained above, to draw the sprite as a specific color instead of the generic white that we created originally. To store these properties, we’ll use myColor and options. In our Create Event put:

// gameObjectParent_Create
myColor = c_white;
options = [];

We also want a way to convey to the user which object is currently selected, so in the Draw event we’ll put this code:

// gameObjectParent_Draw
if( global.selected == id){
    var xOffset = sprite_width * 0.1;
    var yOffset = sprite_height * 0.1;
    draw_sprite_ext( sprite_index, image_index, x - xOffset, y - yOffset, 1.2, 1.2, 0, c_black, 1);
}
draw_sprite_ext( sprite_index, image_index, x, y, 1, 1, 0, myColor, 1);

This will check if this object is currently selected. If it is, it draws a black, slightly larger version of the sprite behind our current sprite which will create a ghetto outline effect.

The last thing we want to do with this object is make it so that when it’s clicked, it sets global.selected to itself and then create a menu. We don’t have a menu object yet, so for now we’ll set the Mouse Left Pressed Event to this:

// gameObjectParent_LeftPressed
// TODO: Delete any menus that exist
global.selected = id;
// TODO: Create a menu

Building Parent Object

Next up we’re going to create oBuilding which is a generic building in our game. Our goal with this is to simply define behaviors which are unique from its parent object (gameObjectParent) and will be universal with all buildings in our game. To set this object up, we’re going to create a folder called Buildings and in this folder we will create oBuilding. Set its sprite to sBuilding and its parent to gameObjectParent. In the create event we will call event_inherited(); and then add an additional line of code defining the options that all buildings will share. Our code should look like this:

// oBuilding_Create
event_inherited();
options = [Options.Move, Options.Delete];

This means that all buildings will be allowed to be moved and deleted by default. In this example we will be redefining options in each child object, but you could also just append new options to the end of the array for children objects if you’d prefer.

oHouse and oCityHall

Next, we are going to create 2 specific types of buildings. These are oHouse for a simple house and oCityHall for all of our town’s important work to be done. oHouse will only be different from its parent object in that it will always be blue. To achieve this, we create oHouse in our Buildings folder, set its Sprite to sBuilding and its parent object to oBuilding. In our Create Event we will put:

// oHouse_Create
event_inherited();
myColor = c_blue;

Because we don’t define options in this object, it will allow options to be defined by its parent object. This means that oHouse.options is equal to [Options.Move, Options.Delete] because that’s what is defined in oBuilding. oCityHall will be unique in that its menu will enable the player to collect taxes and it will be yellow to make it stand out. Its code:

// oCityHall_Create
event_inherited();
myColor = c_yellow;
options = [Options.Move, Options.Delete, Options.Tax];

At this point if you want to test the functionality of global.selected you can place some of these buildings in your room’s Instances Layer and run the game. You’ll notice as you select buildings they will show that they are selected by drawing a black border. We haven’t programmed the menu functionality yet, though, so even though the options variable is set, you won’t be able to see it in game.

oPerson and oAlien

In our town we’ll have many generic people. We’ll also have a couple of aliens trying to blend in. We’re going to create a folder named People and in that folder we will create oPerson with sprite sPerson and parent gameObjectParent. People are diverse so we’ll have the color of our person be completely random and we’ll also decide which options should be available to our citizens. Place this in the Create Event:

// oPerson_Create
event_inherited();
myColor = make_color_rgb( irandom(255), irandom(255), irandom(255));
options = [Options.Move, Options.Delete, Options.Change];

Our alien will behave exactly the same as our person, but aliens aren’t that diverse naturally so we’ll create oAlien in our People folder with sprite sPerson and parent oPerson and in it’s Create Event we’ll make sure our aliens always start out green:

// oAlien_Create
event_inherited();
myColor = c_green;

Step 4: Creating a Menu System

oMenu and Finish gameObjectParent

Now we have the basic building blocks to our game, but we have no menu system yet. There are many ways to go about this, but our method is going to be to create a menu object that then creates a button object for each of the options available. First, we’ll define oMenu. In the Create Event we will set the following settings and variables:

// oMenu_Create
options = [];
initialized = false;

padding = 4;
w = 64 + padding * 2;
h = padding * 2;

What we want is to be able to have our game objects create oMenu and define the options. We set options to a blank array just so that it’s declared and won’t cause any undefined sort of behavior. We’ll switch initialized to true after options is set by the calling object and the buttons for those options have been created. padding stores the number of pixels we want between the edge of the menu and the side of the button. w and h will store the width and height of our menu. w is set to 64 because that’s the width of our sButton Sprite and we add padding * 2 because we want that padding on both sides of the button.

Now that we know how we want the menu object to work, let’s go back to our gameObjectParent object and finish defining its behavior for the Mouse Left Pressed Event:

// gameObjectParent_LeftPressed
if( instance_exists( oMenu)){
    instance_destroy( oMenu);
}
global.selected = id;
var myMenu = instance_create_layer( x + sprite_width, y, "Instances", oMenu);
myMenu.options = options;

First, we destroy any menus that already exist, then we set which object is currently selected, then we create an oMenu object and set its options to the options we have stored in our local options variable. Now we need to finish programming the behavior of oMenu. In oMenu‘s Step Event we want it to check if it’s already been initialized and if it hasn’t, create the buttons for the player to interact with.

// oMenu_Step
if( !initialized){
    initialized = true;
    h += 20 * array_length_1d( options);
    for( var i = 0; i < array_length_1d( options); ++i){
        var myButton = instance_create_layer( x + padding, y + padding + 20 * i, "GUI", oButton);
        myButton.option = options[i];
    }
}

First, we check if this menu has already been initialized and if it hasn’t, we set the height to now include as many buttons as the selected objects has options. The 20 is used here because our sButton‘s height is 18 and we want a 2 pixel margin at the bottom of the button. Finally, we loop through each option and create a button for it on the GUI Layer in our room. We’ll define how the button behaves in a moment. Remember, we don’t need to program any interaction with the menu because the interaction is either handled by the game objects (by selecting a game object) or by the buttons (by clicking a button).

The next behavior we’ll want to program for the menu is the Draw Event. This is going to be a fairly straightforward box that’s drawn around the buttons.

// oMenu_Draw
draw_set_color( c_black);
draw_rectangle( x, y, x + w, y + h, false);

The last thing we want to take into account is that when the menu is destroyed, we want to make sure it takes the buttons with it and that it no longer indicates that anything is selected. To this end we will create a Destroy Event and add the following code:

instance_destroy( oButton);
global.selected = noone;

If you want to test the game’s behavior to this point, create an object named oButton and then launch your game. You should see that when you select an object, a black box is now drawn beside it and will be a different size depending on how many options are available to the object you selected. We haven’t created buttons yet, so that won’t be working but we will soon!

oButton

Create oButton if you haven’t already and give it the sButton sprite. We don’t really need a Create Event but I like to include one anyway just to help prevent undefined errors and to make troubleshooting easier, so our Create Event will just have one line of code:

// oButton_Create
option = -1;

Next, we’ll draw the button so that we can finally see what we’re working with. We’ll also want to draw text on the button to tell the user what the button does, so create a font to be used on the button and name it fMenu. I defined my font as Size 10 Arial. In the Draw Event of your oButton put this code:

// oButton_Draw
draw_self();
draw_set_font( fMenu);
draw_set_color( c_black);
draw_text( x + 3, y + 1, Game.optionString[option]);

You might be a bit confused by the part that reads Game.optionString[option]. If you recall, we created an array in our Game object named optionString. The strings stored in that array correspond to the options defined in the Option enum, so if we created a button with option = Option.Move; then Game.optionString[option] would equal “Move”. Go ahead and execute the game from here and you should now see your menus drawn next to your selected object complete with different buttons for each of the options! A nice bit of polish you can add is to change the draw_self() part of oButton‘s Draw Event to be:

if(
    median( x, x + sprite_width, mouse_x) == mouse_x
    && median( y, y + sprite_height, mouse_y) == mouse_y
){
    draw_sprite_ext( sprite_index, image_index, x, y, 1, 1, 0, c_yellow, 1);
}else{
    draw_self();
}

This makes it so the button is drawn highlighted if the mouse is over the button and if it’s not the button is just drawn regular.

The very last thing we need to do for this tutorial is to define behavior when the buttons are pressed and it’s SUPER easy. All we need to do is create a Mouse Left Pressed Event in your oButton. In that event we’ll write:

// oButton_LeftPressed
switch( option){
    case Options.Move:
        (global.selected).x = irandom( room_width - 64);
        (global.selected).y = irandom( room_height - 64);
        instance_destroy(oMenu);
        break;
    case Options.Delete:
        instance_destroy(global.selected);
        instance_destroy(oMenu);
        break;
    case Options.Change:
        (global.selected).myColor = make_color_rgb( irandom( 255), irandom( 255), irandom( 255));
        break;
    case Options.Tax:
        show_message( "Noooo! Why, m'lord?!?!?!?!");
        break;
}

We’re using a switch statement to determine which option this button represents. If it’s a “Move” button, we move the object to a random location in the room. If it’s a “Delete” button, we destroy the object. If it’s a “Change” button (only available to People and Aliens), the object will change color. If it’s a “Tax ‘Em!” button, the people will cry out in protest.


And that’s all there is to it! If you have any questions please feel free to ask. If there’s something you think I need to elaborate on more let me know or if there’s something I messed up on please point it out.

Leave a comment

Your email address will not be published. Required fields are marked *