Skip to content

Basic Tutorial

offyerrocker edited this page Jun 28, 2020 · 10 revisions

Basic Tutorial

(Updated as of v1.0) This page contains information that will tell you how to implement RadialMouseMenu into your mod.

(It will help to have some Lua knowledge.)

Usage can basically be summarized into the following steps:

  1. Setting up your menu information
  2. Creating your RadialMouseMenu object
  3. Receiving input from your RadialMouseMenu object

Step 1: Setting up your RadialMouseMenu information

The first thing you need to know is how information is set up in RadialMouseMenu. The options you can include when setting up your menu object are as follows:

a. name (String: Unique identifier for this menu object. Mandatory.)

b. radius (Number: Size of the circle. Optional; defaults to 300.)

c. items (Table: Choices in your menu. See [Items].)

d. center_text* (HUD Text object data- this text appears in the middle of the radial and reflects the choice you are currently looking at. Optional; if not specified, looks like the example.)

e. bg* (HUD Bitmap object data- this image is the background of the radial menu which renders behind all other parts of the menu. Optional; if not specified, looks like the example.)

f. selector* (HUD Bitmap object data- this image is the arc that appears around the choice you are currently looking at. Optional; if not specified, looks like the example.)

g. arrow* (HUD Bitmap object data- this image is a little arrow that shows which angle around the circle your mouse is pointing at. Optional; if not specified, looks like the example.)

h. x (Number: horizontal menu position on screen. Optional; counts from center of screen)

i. y (Number: vertical menu position on screen. Optional; counts from center of screen)

*HUD object data follows the Panel data type; however, what you will be entering for these elements, if anything, will only need to be a table. The RadialMouseMenu class will handle the Panel creation for you, so you would only need to enter some settings. Again, these things are optional, and only need to be changed if you want to really customize the textures, icons, and appearances of this menu. Please see the example for a more thorough description.

The most important part of your menu object will be the choices in it, obviously. Each of these choices will be referred to as an "item." Almost everything else (except for name) can be ignored if you are okay with using my default appearances.

Breakdown of an Item

An item contains several subparts:

a. text (String: The string that will display when you mouse over this item in the menu)

b. stay_open (Boolean: When this item is clicked: if stay_open is true, the menu does not close. Otherwise, if stay_open is false or not defined, the menu closes automatically when this item is clicked, allowing the user to click multiple items.)

c. callback (Function: When this item is clicked, the function callback is run. Optional; you may also use the Hooks method if you prefer! See Step 3.)

d. icon (HUD Panel bitmap data: The preview icon for this item. If this icon's data is not specified, it will be invisible.)

e. body (HUD Panel bitmap data: The "slice" image for this item. If this body's data is not specified, it will be invisible.)

f. text_panel (HUD Panel text data: The display text panel for this item, which will show the string text as defined in part a. If this text_panel's data is not specified, it will use default appearance values.)

g. show_text (Boolean: whether or not to show the display text panel (part f.) or not. If show_text is false or not specified, the text (part a.) will only show when you mouse over the item in the menu.)

Here is an example of making two items, which will then be passed along when creating the menu object:

	my_items = {
		{
			text = "Need Meds!", --the display text of this item, as it will appear in the menu
			icon = { --this is data that will be converted into a bitmap object
				texture = tweak_data.hud_icons.equipment_doctor_bag.texture,--this is the image path for your icon
				texture_rect = tweak_data.hud_icons.equipment_doctor_bag.texture_rect,--texture rects are only necessary if you are using an icon atlas type texture
				layer = 3,
				w = 16,
				h = 16,
				alpha = 0.7,
				color = Color(1,0.5,0)
			},
			stay_open = true, --the menu will not close when you click on this
                        callback = callback(VoiceCommandsMod,VoiceCommandsMod,"say_line","g80x_plu") --this will use VoiceCommandsMod's voice-chat function to play the "I need a medbag!" voiceline
		},
		{ --the same, but for an ammo bag
			text = "Need Ammo!",
			icon = {
				texture = tweak_data.hud_icons.equipment_ammo_bag.texture, 
				texture_rect = tweak_data.hud_icons.equipment_ammo_bag.texture_rect,
				layer = 3,
				w = 16,
				h = 16,
				alpha = 0.7,
				color = Color.yellow
			},
			stay_open = true,
                        callback = callback(VoiceCommandsMod,VoiceCommandsMod,"say_line","g81x_plu")
		}
	}

Step 2: Creating your RadialMouseMenu object

You have just learned the components that RadialMouseMenu can accept. All you need to do now is assemble them in a certain format, and execute the code to create it.

Here is an example:

	params = {
		name = "VoiceCommandsMenu",
		radius = 200,
		items = my_items --we'll use the items we created in Step 1 here
	}

        my_radial_menu = RadialMouseMenu:new(params)

However: RadialMouseMenu depends on managers.gui_data to be instantiated in order to actually create your menu object. (This happens naturally during game loading; you do not need to instantiate managers.gui_data yourself- you only need to wait.) As a result, if you attempt to create a new menu object with RadialMouseMenu:new() before the game is fully loaded, your menu will be queued for creation. This is the reasoning behind the second argument to new().

The example in the codeblock above assumes that your menu creation will always take place after the game is done loading, such that your menu object's creation will never even need to be queued. Here is an example of an implementation of creating your menu object where your menu object's creation from new() may take place before game loading is complete:

        function MyModGlobal:SetMyRadialMenu(menu) --"setter" function
                my_radial_menu = menu
        end

	params = {
		name = "VoiceCommandsMenu",
		radius = 200,
		items = my_items --we'll use the items we created in Step 1 here
	}

        my_radial_menu = RadialMouseMenu:new(params,callback(MyModGlobal,MyModGlobal,"SetMyRadialMenu"))

In this second example, my_radial_menu is set twice - once in the callback setter function, and once directly using the result of RadialMouseMenu:new() as the new value of my_radial_menu. In the event that managers.gui_data is not instantiated at the time that RadialMouseMenu:new() is called, new() will first return nil, and the second argument will be executed (if it is a function type object) upon the game's eventual loading. For this reason, in this case, your keybind to activate your radial menu should include a sanity check to ensure that your radial menu exists (is not nil) before attempting to call any methods on it such as Toggle(), or else you may encounter a crash.

You may decide to use either of the implementation styles shown in the above two examples; choose wisely according to your own mod's implementation and load order.


Step 3: Receiving input from your RadialMouseMenu object

RadialMouseMenu is set up to return input in two different, non-exclusive ways:

a. Callbacks. (See Step 2, part b.) When your item is clicked, (if you have a callback function specified) this function will run. If you already have a function callback set up, or if these items have no numerical link (that is, their order in the circle is not related to their function at all) then you may want to use this callback method- however, this is only a minor case of code efficiency.

Here is an example function matches the callback that was created in Step 2.

--this function plays the voiceline that is passed to it as an argument
function VoiceCommandsMod:say_line(id)
    if Utils:IsInHeist() and Utils:IsInCustody() == false and Utils:IsInGameState() then 
	managers.player:local_player():sound():say(id,true,true)
    end
end

b. Hooks. When your item is clicked, it will call the BLT Hook with id "radialmenu_selected_" .. name, where name is the name that you assigned to your menu in Step 1, part a. You can use BLT's Hooks system to assign code to run when this Hook is called.

Here is an example Hook function.

Hooks:Add("radialmenu_selected_VoiceCommandsMenu","VCMenu_Selected",function(num)
    if num == 1 then 
        log("Aaaagh, I clicked item one! I need a medicbag!")
    elseif num == 2 then 
        log("I clicked item two! Anyone got some ammo?")
    end
end)

In this case, the number index of your item will be passed as an argument, so you may prefer to use this method if your items have a numerical link of some sort. You may also prefer to use this method if you do not have a class or table for your mod set up, or your mod is a small one.

However, like I said before, this is only a minor case of code efficiency, and in the end you can do whichever you like; determining which method is most efficient for your means is at your discretion.