Scripts:Radial Menus

Jump to navigation Jump to search

BMR's Radial Menus[edit]

Difficulty Easy
Skills Knowledge of 3rd Party.hsi

This script creates radial menus similar to those that are used in Secret of Mana (and the sequels). It can be used with the built-in menus, or with custom ones. An example of it in action is below:

BMR Radial Menu.gif


The way this works is it grabs the images from a border sprite, then places them where appropriate. Before using this, you are going to need a few things, specifically:

  1. A set of icons to use (box edge graphic)
  2. A selection icon (small enemy graphic)
  3. Menus to call (can use the built-in ones)
  4. A declaration of the constants to be used

Below in the script, the section marked "Menu Constants" should ideally be placed alongside your other constants/globals declarations, if only to keep things nice and tidy. In this example script, I've used 8 different menu items, so I have 8 constants. You don't absolutely need to use constants like these, but it makes life much easier down the road. Take note, that these should be in the same order as they appear in your box edge sprite.

The constants that you'll need to change are menu:Selector, and icons:Menu. The first is the number of the small enemy sprite where your cursor is. The second, icons:Menu, is the number of the box edge sprite where you've got your icons.

You'll also need to run the environment initialize script. This is separate from the radial menu script, as UI Container is used by my other scripts as well, such as the dialogue menu.

Once you've got that, you can start editing the script. Probably the most important part that you'll edit is in the Variable Declaration section. Specifically, the value mNum. This specifies exactly how many menu items you've got. In the example script, it's set to 8. You can change the radius of the circle as well, but I've found that the current value of 4 suits me well enough.

Other than that, there's only one more thing you really need to tinker with, and that's the section marked Menu Selection. That section contains a Switch command, which will choose what to do based on which is the currently selected menu item. In the example, I've left some blank, as they will be used for custom functions later on (and are not relevant to the example). For things like inventory or spells, you can use the built-in functions, as I've done in the example.

And that's about it, you've got your radial menu up and running. At the end of the page, I've also included a sample menu selector and a set of icons that you can use for your menu. The icons are relatively general-purpose, so you can mix and match as you see fit. Note that they both share the same palette. While you don't have to do this, I find that it makes editing much simpler. For the selector, it's possible to go ahead and import it into Custom right away, but for the icons, you'll need to open up an image-editor and move around which icons you want to use.

One last important thing is that this uses Scripts:3rd_Party_HSI (for sine and cosine scripts), so be sure to include it.


#BMR's Radial Menus
# Version 1.1 - 20200620
# by BMR
# Feel free to use this anywhere, no credit is needed.

# Globals
global variable, begin
  200, UI Container

# Menu Constants
define constant, begin
  1, menu:Inventory
  2, menu:Status
  3, menu:Equip
  4, menu:Skills
  5, menu:Journal
  6, menu:Dialogue
  7, menu:SaveLoad
  8, menu:Options
  9, icons:Menu

  1, menu:Selector

# Environment Initializer (This needs to be called somewhere)
script, environment initialize, begin
  #Create and place the overall UI slice container
  UI Container := create container
  put slice screen(UI Container, 0, 0)
  set parent(UI Container, lookup slice(sl:map overlay))
  move slice above(UI Container, lookup slice(sl:map overlay))

# A SoM-style radial menu
plotscript, radial menu, begin

    mCnt, #Menu Overall Container
    mNum, #Number of menu items
    mRad, #Radial Menu radius in pixels
    mItm, #Menu Item
    mSel, #Menu Selector
    mMve, #Whether to rotate the menu
    MDir, #Menu Direction, 0 is None, -1 is Left, 1 is Right
    sCur, #Currently Selected
    aInt, #Angle Interval
    aCur, #Current Angle
    cPos, #Current Position
    cSel, #Currently Selected
    bseX, #Base X
    bseY, #Base Y
    tarX, #Target X
    tarY, #Target Y
    curX, #Current X
    curY, #Current Y
    ctr,  #A Counter
    actv #Whether Active
  # Initialize variables
  mNum := 8 #This is the number of menu items
  mRad := 4 #This is the radius of the circle
  bseX := hero pixel x + 8   #\Set where the center of
  bseY := hero pixel y -- 10 #/the radial menu will be
  aInt := 360 / mNum #Set the angle interval by dividing 360 by the number of items
  aCur := 0          #The first angle will always be 0, e.g. the item directly to the right of the screen
  mCnt := create container #Creates a container, makes freeing stuff up later easier
  actv := true  #The radial menu starts out Active
  mMve := false #Whether the menu moved or not, starts out False
  mDir := 0     #Starting direction of the menu, right now it's null
  sCur := 1 #Currently, the selected item is 1, or menu:Inventory by default
  suspend player #\This will suspend the player and the npcs
  suspend npcs   #/from moving, just to keep things neat
  set horiz anchor(mCnt, edge:center) # \
  put slice screen(mCnt, 0, 0)        #  \ This block will take the container slice
  set parent(mCnt, UI Container)      #  / and set it to the appropriate location
  put slice (mCnt, bseX, bseY)        # /
  # Create the items
  for(ctr, 1, mNum) do(
    mItm := load border sprite(icons:Menu) # \
    set sprite frame(mItm, ctr)            # |
    set horiz anchor(mItm, edge:center)    #  \ This block creates each menu item and
    set vert anchor(mItm, edge:center)     #  / then sets it to be a child of mCnt
                                           # |
    set parent(mItm, mCnt)                 # /
    tarX := ((bseX * 10000) + (mRad * 10000) * cosine(aCur)) / 10 000 000
    tarY := ((bseY * 10000) + (mRad * 10000) * sine(aCur)) / 10 000 000

    move slice to(mItm, tarX, tarY, 6) #Move the slice.  No, the menu items don't actually move in curves, they move in straight lines.  Looks good enough though.

    set slice extra(mItm, 0, aCur) # \ This block saves the angle of the menu item in the slice's extra
    aCur += aInt                   # / data.  It also increments the angle for the next item.

  # Create the selector
  mSel := load small enemy sprite(menu:Selector)  # \
  set horiz anchor(mSel, edge:center) # |
  set vert anchor(mSel, edge:center)  #  > This block creates the selector and puts it in hte proper place
                                      # |
  set parent(mSel, mCnt)              # /
  tarX := ((bseX * 10000) + (mRad * 10000) * cosine(aCur)) / 10 000 000 # \ Same
  tarY := ((bseY * 10000) + (mRad * 10000) * sine(aCur)) / 10 000 000   # / as above
  move slice to(mSel, tarX, tarY, 6)
  # This governs the movement (rotation) of the menu
  while (actv) do(
    wait for key #Sets a wait, that way keypresses aren't duplicated
    # Set data for rotating counter clock-wise
    if(key is pressed(left key) || key is pressed(up key)) then(
      mMve := true
      mDir := -1
      sCur += 1
      if(sCur > mNum) then(sCur := 1)
    # Set data for rotating clock-wise
    if(key is pressed(right key) || key is pressed(down key)) then(
      mMve := true
      mDir := 1
      sCur -= 1
      if(sCur <= 0) then(sCur := mNum)
    # Exit the menu
    if(key is pressed(cancel key)) then(
      # Move the menu items off screen
      mItm := first child(mCnt)
      while(mItm) do(
        move slice to(mItm, 0, 0, 3)
        mItm := next sibling(mItm)
      free slice(mCnt)
    if(key is pressed(use key)) then(
      switch(sCur) do(
        case(menu:Inventory) do(items menu)
        case(menu:Status)    do(status screen)
        case(menu:Equip)     do(equip menu)
        case(menu:Skills)    do(spells menu)
        case(menu:Journal)   do()
        case(menu:Dialogue)  do()
        case(menu:SaveLoad)  do()
        case(menu:Options)   do(main menu)
    # This section will rotate the menu
    if(mMve) then(
      mItm := first child(mCnt)
      # This does the actual rotating
      while(mItm) do(
        #This block only continues up to the second to last child, that's because the last child is the selector
        if(mItm == last child(mCnt)) then(break) 
        aCur := get slice extra(mItm, 0) #Retrieve the item's current angle
        aCur += aInt * mDir #Depending on whether rotating left or right, either add or subtract the angle interval
        tarX := ((bseX * 10000) + (mRad * 10000) * cosine(aCur)) / 10 000 000
        tarY := ((bseY * 10000) + (mRad * 10000) * sine(aCur)) / 10 000 000

        move slice to(mItm, tarX, tarY, 4)
        if(aCur > 360) then(aCur := aCur -- 360) # \ This keeps the angle between
        if(aCur < 0) then(aCur := aCur + 360)    # / 0 and 360, keeps things tidy
        set slice extra(mItm, 0, aCur) #Store the new angle in the extra data
        mItm := next sibling(mItm) #Gets the next menu item
      aCur := 0     #\
      mDir := 0     # >Reset the values
      mMve := false #/

  resume player # \ Resumes
  resume npcs   # / movement

Sample Graphics[edit]

Like the script, the images below are in the public domain. Feel free to use them as you see fit.

Menu Selector[edit]

BMR Radial menu selector.bmp

Menu Icons[edit]

BMR Radial menu icons.bmp