Adding A Project Menu To The Lumberyard Editor

In this post I’ll be showing how we at Third Kind Games have extended the Lumberyard Editor to provide a custom menu for project specific actions and configuration setup. Firstly I’ll show you how you can trivially register and layout menu items with the system from your game code and gems. Finally I’ll provide the instructions for how to integrate the system in to the engine.

 

Character Select Menu

 


 

Using the project menu

Adding items

Adding a menu item is done via a call to the AzToolsFramework::ProjectMenuRequests bus during initialisation and the code below shows all you need to do to add an item to the menu.

#include <AzToolsFramework/ToolsMessaging/ProjectMenuBus.h>

bool EditorGame::Init(ISystem* system, IGameToEditorInterface* gameToEditorInterface)
{
  // After initialisation has completed, register the custom menu options
  EBUS_EVENT(AzToolsFramework::ProjectMenuRequests, RegisterItem, 
      // Name of the item (the '&' indicates the next character is the keyboard shortcut)
      "&Test",
      // Parent menu for the index, s_noParent indicates root menu
      AzToolsFramework::ProjectMenuBus::s_noParent, 
      // The function to call when the item is selected
      AzToolsFramework::MenuItemSelectedCB(&EditorGame::MenuItemSelected, this));
} 

void EditorGame::MenuItemSelected(AZ::u32 id, char const* name) 
{ 
    AZ_TracePrintf("EditorGame", "Menu Item Selected"); 
}

Menu Item Example

 

Adding sub-menus

Adding sub-menus is a similar process to adding items to the menu, again a single bus call is used to create the menu and the resulting id is passed as the parent menu to the following register item calls.

AZ::u32 subMenu;

EBUS_EVENT_RESULT(subMenu, 
    AzToolsFramework::ProjectMenuRequests, RegisterMenu, 
    // The menu name (the '&' works in the same way as for item names)
    "&Menu Name", 
    // The parent menu for this menu
    AzToolsFramework::ProjectMenuBus::s_noParent);

EBUS_EVENT(AzToolsFramework::ProjectMenuRequests, RegisterItem, 
    "&Test",
    // Note that the parent menu is now the id returned from RegisterMenu
    subMenu,
    AzToolsFramework::MenuItemSelectedCB(&EditorGame::MenuItemSelected, this));

Sub-Menu Example

 

If you attempt to add a sub-menu that already exists (same name and parent) then the existing menu will be returned, this allows you to add items to the same menu from different parts of your code without having to pass any ids around. This is best exhibited with Gems where we first register a Gems sub-menu and then create a sub-menu below for each Gem.

To add items from a Gem, add the following code in to the GemModule class;

void GemAction(AZ::u32 id, char const* name)
{
    AZ_TracePrintf("ProjectMenu", "Gem action selected %u '%s'", id, name);
}

void OnSystemEvent(ESystemEvent event, UINT_PTR wparam, UINT_PTR lparam) override
{
   switch (event)
   {
       case ESYSTEM_EVENT_GAME_POST_INIT:
       {
           AZ::u32 gemsMenu;
           EBUS_EVENT_RESULT(gemsMenu, 
               AzToolsFramework::ProjectMenuRequests, RegisterMenu, 
               "&Gems", 
               AzToolsFramework::ProjectMenuBus::s_noParent);
                   
           AZ::u32 gemMenu;
           EBUS_EVENT_RESULT(gemMenu, 
               AzToolsFramework::ProjectMenuRequests, RegisterMenu, 
               "Gem Name", 
               gemsMenu);

           EBUS_EVENT(AzToolsFramework::ProjectMenuRequests, RegisterItem, 
               "Gem Item", 
               gemMenu, 
               AzToolsFramework::MenuItemSelectedCB(&ExampleModule::GemAction, this));
           break;
       }
   }
}

Note that in the example shown we have the results from two different gems that have registered items and sub-menus with the exact same code above (apart from the names)

Multiple Gems Example

 

Selectable Items

The last option for adding items to the menu is to allow them to be in a selected state, this is indicated by a tick to the side of the item and is useful for indicating a currently selected configuration setting. For this example I’m going to create a basic character select option which stores the name of the currently selected character in EditorGame.

void EditorGame::SelectCharacter(AZ::u32 id, char const* name)
{
    m_character = name;
    AZ_TracePrintf("ProjectMenu", "Selected character '%s'", name);
}

// Return true if the item is selected, false if it isn't 
bool EditorGame::UpdateIsSelectedCharacter(AZ::u32 id, char const* name)
{
    return m_character == name;
}

bool EditorGame::Init(ISystem* system, IGameToEditorInterface* gameToEditorInterface)
{
    // After initialisation has completed, register the custom menu options
    
    AZ::u32 characterMenu;
    EBUS_EVENT_RESULT(characterMenu, 
        AzToolsFramework::ProjectMenuRequests, RegisterMenu, 
        "Select &Character", 
        AzToolsFramework::ProjectMenuBus::s_noParent);

    // Note the IsSelected callback is also required to indicate the selected state of the item
    auto characterSelectCallback = AzToolsFramework::MenuItemSelectedCB(&EditorGame::SelectCharacter, this);
    auto characterIsSelectedCallback = AzToolsFramework::MenuItemIsSelectedCB(&EditorGame::UpdateIsSelectedCharacter, this);
 
    // Call RegisterSelectableItem instead of RegisterItem
    EBUS_EVENT(AzToolsFramework::ProjectMenuRequests, RegisterSelectableItem, 
        "&Alice", 
        characterMenu, 
        characterSelectCallback, 
        // isSelected callback is used to check the status of the menu item
        characterIsSelectedCallback);
 
    EBUS_EVENT(AzToolsFramework::ProjectMenuRequests, RegisterSelectableItem, 
        "&Bob", 
        characterMenu, 
        characterSelectCallback, 
        characterIsSelectedCallback);

    EBUS_EVENT(AzToolsFramework::ProjectMenuRequests, RegisterSelectableItem, 
        "&Dave", 
        characterMenu, 
        characterSelectCallback, 
        characterIsSelectedCallback);
 
    m_character = "Alice";
}

With this change we now have a simple configuration method for setting up the game in the editor for testing, it could be easily used to set up character choice, initial inventory loadout, spawn locations etc.

Select Character Example

 

Final note on usage

The menu system is dependent on the AzToolsFramework and so any module that uses the system will need to add this dependency to it’s use list in the wscript file.

 


 

Integrating the system

First download the system files from here, these are the main files for the system and will need to be placed in the editor source tree appropriately;

  • Copy “ProjectMenuBus.h” to “dev\Code\Framework\AzToolsFramework\AzToolsFramework\ToolsMessaging\”
  • Add “ToolsMessaging/ProjectMenuBus.h” to “dev\Code\Framework\AzToolsFramework\AzToolsFramework\aztoolsframework.waf_files” (under ToolsMessaging)
  • Copy “EditorProjectMenu.h/.cpp” to “dev\Code\Sandbox\Editor\Core\”
  • Add “Core/EditorProjectMenu.cpp” and “Core/EditorProjectMenu.h” to “dev\Code\Sandbox\Editor\editor.waf_files” (under Core)

 

We also need to make some code changes to existing Lumberyard files, I’m unable to share the full files here so instead I’m going to detail the required changes. Line numbers used here are referencing Lumberyard 1.9.0.1 (other versions may differ), where the location of the change needs to be specific I’ve also described the required location.

 

dev\Code\Sandbox\Editor\resource.h

Line 3970 (just before FIRST_QT_ACTION is defined), Add:

#define PROJECT_MENU_ACTION_COUNT                    1024
#define ID_PROJECT_MENU_FIRST                        45000
#define ID_PROJECT_MENU_LAST                         (ID_PROJECT_MENU_FIRST + PROJECT_MENU_ACTION_COUNT - 1)

 

dev\Code\Sandbox\Editor\CryEdit.h

Line 354 (within the AFX_MSG group), Add:

afx_msg void OnProjectMenuSelected(UINT id);

 

dev\Code\Sandbox\Editor\CryEdit.cpp

Add the include:

#include "Core/EditorProjectMenu.h"

At line 861 (just before END_MESSAGE_MAP), Add:

ON_COMMAND_RANGE(ID_PROJECT_MENU_FIRST, ID_PROJECT_MENU_LAST, OnProjectMenuSelected)

Add the function:

void CCryEditApp::OnProjectMenuSelected(UINT id)
{
  EditorProjectMenu::Get().ActionSelected(id);
}

 

dev\Code\Sandbox\Editor\Core\LevelEditorMenuHandler.cpp

Add the include:

#include "Core/EditorProjectMenu.h"

At line 66 (at the end of LevelEditorMenuHandler::Initialize()), Add:

auto projectMenu = EditorProjectMenu::Get().CreateMenu(m_actionManager, m_topLevelMenus);
if (projectMenu)
{
  m_topLevelMenus << projectMenu;
}

 

And that’s it, you’re ready to start using the Project Menu to customise your editor!

 


 

License

The source files included in this post are provided under the Unlicense meaning that you are free to use/alter them as you see fit, please refer to http://unlicense.org for further details.

Leave a Reply