Scripts:Radial Menus
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:
Documentation[edit]
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
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.
Script[edit]
#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 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 #---------------- 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)) #---------------- end # A SoM-style radial menu #===================================================================================== plotscript, radial menu, begin 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 ) # 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) ) #--------------------------------- wait(3) free slice(mCnt) break ) #--------------------------------------------------------------- # MENU SELECTION #--------------------------------------------------------------- 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 end
Sample Graphics[edit]
Like the script, the images below are in the public domain. Feel free to use them as you see fit.