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:
How to Use
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:
- A set of icons to use (box edge graphic)
- A selection icon (small enemy graphic)
- Menus to call (can use the built-in ones)
- A declaration of the constants to be used
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 sl: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 important thing to note though, is that this doesn't include any sounds for when you rotate the menu. That's something I plan to add later on once I start working on the sound/music for my game, but in this script it's fairly easy to add in at any point.
Code: Select all
#BMR's Radial Menus
# Version 1.0 - 20170527
# by BMR
#
# Feel free to use this anywhere, no credit is needed.
#
#Globals
#===============================
global variable, begin
200, sl:UI Container
end
#===============================
#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
end
#===============================
#Environment Initializer (This needs to be called somewhere)
#=======================================
script, environment initialize, begin
#Create and place the overall UI slice container
#------------------------------------------------------------------------------------
sl:UI Container := create container(cntX, cntY)
put slice screen(sl:UI Container, 0, 0)
set parent(sl:UI Container, lookup slice(sl:map layer7))
move slice above(sl:UI Container, lookup slice(sl:map layer7))
#------------------------------------------------------------------------------------
end
#=======================================
#================================================================================================================================================================
#==============================================================This creates a SoM-style radial menu==============================================================
#================================================================================================================================================================
plotscript, radial menu, begin
#Declare the variables
#---------------------
variable(
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
)
#---------------------
#Generate the 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, sl: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)) / 10000000 # \Yeah, honestly I had no idea what I was doing here,
tarY := ((bseY * 10000) + (mRad * 10000) * sine(aCur)) / 10000000 # /and I just kept plugging numbers until it worked.
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)) / 10000000 # \ Yeah, same
tarY := ((bseY * 10000) + (mRad * 10000) * sine(aCur)) / 10000000 # / as above
move slice to(mSel, tarX, tarY, 6)
#--------------------------------------------------------
#--------------------------------------------------------
#-----------------------------------------------------------------------------------------------------------------------------
#This governs the movement (rotation) of the menu
#-----------------------------------------------------------------------------------------------------------------------------
while(actv == TRUE) do(
wait for key #Sets a wait, that way keypresses aren't duplicated
#Set data for rotating counter clock-wise
#---------------------------------------------------------------
if(key is pressed(key:left) || key is pressed(key:up)) then(
mMve := TRUE
mDir := -1
sCur += 1
if(sCur >> mNum) then(sCur := 1)
)
#---------------------------------------------------------------
#Set data for rotating clock-wise
#---------------------------------------------------------------
if(key is pressed(key:right) || key is pressed(key:down)) then(
mMve := TRUE
mDir := 1
sCur -= 1
if(sCur <= 0) then(sCur := mNum)
)
#---------------------------------------------------------------
#Exit the menu
#---------------------------------------------------------------
if(key is pressed(key:ESC)) 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)
)
#---------------------------------
#---------------------------------
wait(3)
free slice(mCnt)
break
)
#---------------------------------------------------------------
#---------------------------------------------------------------
#MENU SELECTION
# Note, constants are defined in a different file.
#---------------------------------------------------------------
if(key is pressed(key:ENTER)) 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 == TRUE) then(
mItm := first child(mCnt)
#---------------------------------
#This does the actual rotating
#---------------------------------
while(mItm) do(
if(mItm <> last child(mCnt)) then( #This block only continues up to the second to last child, that's because the last child is the selector
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)) / 10000000 # \ More math.
tarY := ((bseY * 10000) + (mRad * 10000) * sine(aCur)) / 10000000 # / Ugh.
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 sibling
)
else(
break
)
)
#---------------------------------
#---------------------------------
aCur := 0 #\
mDir := 0 # >Reset the values
mMve := FALSE #/
)
#----------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------
)
#-----------------------------------------------------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------------------------------------------------
resume player # \ Resumes
resume npcs # / movement
end
#================================================================================================================================================================
#================================================================================================================================================================