Gw Temp

Menu

Tutorial - 'CMSs in Gamemaker' by Kuro

An item about Gamemaker posted on

Blurb

Another great tutorial by Kuro, this time on making CMSs in Gamemaker!

Body

CMSs in Game Maker

NOTE: This tutorial was written in Game Maker 5 GML. Yes, I know GM6 is out, but I like GM5, and yes, I am afraid of change. Now shut up about it and read.
- Kuro -


Everybody needs to have a menu in their game, right? Today, you can learn how to make one in Game Maker. The benefits of using this kind of menu system? Well, for one, its customizable to a degree. Two, you can make as many menus as you want without changing too much base code. And best of all, you don't have to worry about X and Y coordinates. All of the visual placement is taken care of when you create them menu! So let's hop right in.




There are two parts to creating a Menu: The visual aspect and the operational aspect. The Visual part is easy to take care of, while the Operational part will take a bit more effort. To start with the visual, create a Script called 'InitializeMenus' or something to that effect. Inside, we'll be creating an array that will hold our Menu information.



// -- Script: InitializeMenus()
// constant variables 'menu_...' defined in 'constants' tab under Game Options (They're just index values, starting at 0-menu_text and going to 4-menu_fsize)

global.title_menu[0, menu_text] = "Start Game";
global.title_menu[0, menu_hspace] = 0;
global.title_menu[0, menu_vspace] = 0;
global.title_menu[0, menu_font] = "Arial";
global.title_menu[0, menu_fsize] = 12;

global.title_menu[1, menu_text] = "Continue Game";
global.title_menu[1, menu_hspace] = 0;
global.title_menu[1, menu_vspace] = 0;
global.title_menu[1, menu_font] = "Arial";
global.title_menu[1, menu_fsize] = 12;

global.title_menu[2, menu_text] = "Exit Game";
global.title_menu[2, menu_hspace] = 0;
global.title_menu[2, menu_vspace] = 0;
global.title_menu[2, menu_font] = "Arial";
global.title_menu[2, menu_fsize] = 12;

// -- End InitializeMenus()


That wasn't hard at all. Notice how I've defined the array as title_menu, instead of just making one big array that can contain all of the menus in the game. That's to keep the Menu System scalable, or reusuable for the layman.


Now comes the operational part. This can be a bit more difficult, but don't let it intimidate you. Now I've thought and thought about ways I could do this, and I've come to the conclusion that having separate objects that act as 'Cursors' for each menu, while not efficient, will suffice. First off, create a new sprite that you want to use for your cursor. After you create/import it, a decision must be made. Will you:

A-) Have your cursor always sit on the left side of your menu text or...
B-) Have your cursor always sit on the right side of your menu text or...
C-) Your cursor is a hover cursor (Think Rm2k) and is transparent to a degree. Now I haven't found a good way to stretch the sprite yet, so I suggest sticking with a square sprite (16x16, 32x32, etc.) Either way, the stretch is going to look bad, so I'm only putting this method in for reference. I suggest using A, because its basically standard.

For A, make sure your sprite is right-aligned. That means the right edge of the sprite needs to touch the right edge of the sprite box. After you do that, change the sprite's X anchor (offset) to the sprite width. That will make sure the sprite will have some space between it and the menu text.

For B, make sure your sprite is left-aligned. That means the left edge of the sprite needs to touch the left edge of the sprite box. After you do that, change the sprite's X anchor (offset) to -1. That will make sure the sprite will have some space between it and the menu text.

For C, keep the x and y anchor values at 0. You'll also need to zero-trim the box (Crop). You can experiment with the width and height values until you find something you like. I'm guessing that a higher sized box early on (64,128) will yield a better stretch, but don't make it too big, because if your Y value is too high and is stretched lower, its going to look bad as well.


For all of them, (except C), you need to set the Y offset to 1/2 the sprite height - 1, i.e. the sprite is 22 px high, so the Y offset should be 10. This is of course, assuming the sprite is vertically centered. If not, you should just play around with the Y offset value till you find something that looks like the halfway point of the sprite. Symmetry is the key here.


Heh, suck it Trebek.


Now after you finish that up. Its time to start creating your 'Cursors.'

Create an object called... whatever you want, its your cursor. You'll need three events, Create, Step, and Draw. (I tend to use these a lot if you haven't noticed already)
And select your sprite as well. That's pretty important.

In the Create Event:
self.pos = 0;
self.enabled = false;


The Draw event is tricky. We'll be drawing everything in here and depending on the cursor type you're using, (A, B, or C) you'll have to use a different formula so everything will place correctly. Just copy/paste the code that corresponds to your cursor type and I'll explain how it works afterwards.


For A:

if(self.enabled){
// -- Draw the cursor first
draw_sprite(self.sprite_index,self.image_single,self.x + global.title_menu[self.pos, menu_hspace], (self.y + global.title_menu[self.pos, menu_vspace]) + (global.title_menu[self.pos, menu_fsize] * self.pos));

// -- Next, draw the menu itself
for(i=0;i // -- NOTE: I didn't put inside the title_menu array a place for font colors. Check the end of the tutorial on why I did this.
font_size = global.title_menu[i, menu_fsize];
font_name = global.title_menu[i, menu_font];

draw_text(self.x + global.title_menu[i, menu_hspace], (self.y + global.title_menu[i, menu_vspace]) + (global.title_menu[i, menu_fsize] * i), global.title_menu[i, menu_text]);
}
}

For B:

if(self.enabled){
// -- Draw the cursor first
draw_sprite(self.sprite_index,self.image_single,self.x + global.title_menu[self.pos, menu_hspace] + string_width(global.title_menu[self.pos, menu_text]), (self.y + global.title_menu[self.pos, menu_vspace]) + (global.title_menu[self.pos, menu_fsize] * self.pos));

// -- Next, draw the menu itself
for(i=0;i // -- NOTE: I didn't put inside the title_menu array a place for font colors. Check the end of the tutorial on why I did this.
font_size = global.title_menu[i, menu_fsize];
font_name = global.title_menu[i, menu_font];

draw_text(self.x + global.title_menu[i, menu_hspace], (self.y + global.title_menu[i, menu_vspace]) + (global.title_menu[i, menu_fsize] * i), global.title_menu[i, menu_text]);
}
}

And For C:

if(self.enabled){
// -- Draw the Menu First. We want the hover cursor displayed over the text this time.
for(i=0;i // -- NOTE: I didn't put inside the title_menu array a place for font colors. Check the end of the tutorial on why I did this.
font_size = global.title_menu[i, menu_fsize];
font_name = global.title_menu[i, menu_font];

draw_text(self.x + global.title_menu[i, menu_hspace], (self.y + global.title_menu[i, menu_vspace]) + (global.title_menu[i, menu_fsize] * i), global.title_menu[i, menu_text]);
}

// -- Draw the cursor second this time.
draw_sprite_stretched(self.sprite_index,self.image_single,self.x + global.title_menu[self.pos, menu_hspace], self.y + global.title_menu[self.pos, menu_vspace] + (global.title_menu[self.pos, menu_fsize] * self.pos), string_width(global.title_menu[self.pos, menu_text]), string_height(global.title_menu[self.pos, menu_text]));
}

(I tested each of the blocks of code to make sure they work and on my machine, they did. If they don't work for you, email me and I'll help you out.)

If you've read some of that, you'll notice the for-loop has a new variable not referenced yet. I'm talking about max_pos. Something that Game Maker (or rather, GML) doesn't have is a way to find the size of an array (at least in GM5). Its important to know exactly how many menu items you'll have so you iterate through them a necessary amount of times. Unfortunately, I haven't figured out a secure way to do this without defining the number of menu items when the menu is created. So that's what we'll do, but for now, let's check the whole Input-Output thing.


Now there are two parts to a 'Cursor,' Input and Output.

- Input is just what it is: getting user response through the use of a peripheral device, such as a keyboard or mouse.
EXAMPLE: Inside the step event of the cursor, we obtain the input from the keyboard through GML. If you don't want to use GML, you can also use the keyboard events and D&D.

io_handle();
if(self.enabled){
if(keyboard_check(vk_up)){
self.pos -= 1;
io_clear();
// TODO: Play a cursor sound in here using sound_play()
}else if(keyboard_check(vk_down)){
self.pos += 1;
io_clear();
// TODO: Play a cursor sound in here using sound_play()
}

if(self.pos < 0){
self.pos = self.max_pos - 1;
}else if(self.pos == self.max_pos){
self.pos = 0;
}
}

The io_handle() and io_clear() functions are built-in and basically there to 'simulate' a keyPRESS. The keyboard won't actually do anything when you hold the key down, at least until it starts repeating (you know when you hold a key for too longggggggggggggggggggggggggggggggggg. Like that.)


- Output is taking those inputs and doing something with them.
EXAMPLE: We'll check to see if 'Enter' was pressed, and if so, we'll see where the cursor was so we can act accordingly.

if(keyboard_check(vk_enter)){
io_clear();
// TODO: Play a sound here using sound_play()
switch(self.pos){
case 0:
// -- Start a new game
sleep(800);
room_goto(room_new_game);
break;
case 1:
// -- Load a saved game
sleep(800);
room_goto(room_load_game);
break;
case 2:
// -- Exit the program
sleep(800);
game_end();
break;
}
}

Simple enough. Now you can do different things inside the 'case' statements. If you're making a status menu, you may not want to move to a new room, but maybe use an item or equip a weapon. Well, do all of that inside the cases.

Now only two more things to really discuss and a few minor points as well. First, CreateMenu(). CreateMenu() takes in a few inputs to work. In my own 'version' of my menu system, I have six (two extra for horizontal and vertical centering, but not to be discussed today), but you should have four. The first two are the X and Y coordinates of your menu. These need to be the top-left corner of where you want your menu. The third argument is the cursor object you want created. My object was called 'title_cursor' so that's what I'll be specifying in the script call. You know when you've typed the name correctly because it will turn pinkish in color. The last argument needs to be how many menu items in total there are. Now here's the script:

// -- Script: CreateMenu()
iid = instance_create(argument0,argument1,argument2);

iid.max_pos = argument3;
iid.enabled = true;
// -- End CreateMenu();

That's it. Now you can see where max_pos and the enabled variables come from. If I didn't mention it earlier, 'enabled' is there to make sure everything is ready inside the menu. That's why its called last. It may be hard to imagine, but there's a high possibility of something screwing up in the menu because something wasn't loaded properly between these two calls:

iid = instance_create(argument0,argument1,argument2);
// -- Error right here!
iid.enabled = true;

I kid you not. Computers are fast man, and since each event in an object is listening every frame after its creation, the game will definetly let you know if something wasn't loaded. Now its not likely with this system that something won't be 'loaded' but its a good method to keep your game error-free.

Now whenever you want to make a menu, just call CreateMenu() with the parameters you want and Koolah Limpah! You should have a fully-functioning menu.

Man oh man, we're at the end and I still have two things I need to say. On the whole font_color thing: You can add to your global arrays an entry for font_color if you wish, there's absolutely no reason you shouldn't, unless you don't want to use TrueType fonts. If you want to use Bitmap Fonts instead, there wouldn't be a reason for a color entry. Now if you don't know what a Bitmap Font is, don't worry about it, I'm not going to go over that here. But it wouldn't be too hard to change the code around to make it work if you already knew how to use them.

Next, I didn't talk about how to make multi-layered menus. Its going to be a necessity if you're making an RPG, but due to time restraints, the length of THIS tutorial, and the fact that I'm lazy, it'll have to wait for another day.


Another tutorial in the bag. Next up is the second half of Rpg Basics III, and after that, Side-View Battle Systems. One.

Kuro