This is the sixth article in a series that takes the final mini-RPG from the “How to Make an RPG” project and adds a little more polish to one element.
In the previous polish phase we updated the game text to use crisp bitmap fonts. This time we’ll take a break from fonts and text, and add a new magic menu the player can browse outside of combat. The magic menu makes it easier to add another new feature; giving the player the ability use spells in the exploration mode.
Here’s the current menu and where we’ll add the “Magic” option.
Spell Mark Up
Before creating the magic menu, let’s look at how spells are defined:
["fire"] =
{
name = "Fire",
action = "element_spell",
element = "fire",
mp_cost = 8,
base_damage = {3, 5},
base_hit_chance = 1,
time_points = 10,
target =
{
selector = "WeakestEnemy",
switch_sides = true,
type = "One"
}
},
We’re going to add an extra field called “Description” to describe the spell. This description will be displayed in the magic menu.
["fire"] =
{
name = "Fire",
-- code omitted
description = "Damages an enemy with elemental fire!"
}
Here’s the description I’ve added for all the existing spells.
- Burn - Heavily damages all enemies with elemental fire!
- Ice - Damages an enemy with a freezing ice attack!
- Bolt - Damages an enemy with an electric bolt!
With the spell descriptions added, let’s move on to the menu itself.
Menus
The in-game menu sidebar has an option for “Item” but nothing for Magic
. When the player selects “Item” the item-menu is opened using the ItemMenuState
. We need to create
a new complementary “Magic” option and MagicMenuState
.
Now’s a good time to create an empty MagicMenuState.lua file and add references to the manifest and dependancies files.
Before writing the code for MagicMenuState
let’s hook it into the menu code. We’ll add it’s state transition, in InGameMenu.lua, as shown below.
function InGameMenuState:Create(stack, mapDef)
local this =
{
-- code omitted
}
this.mStateMachine = StateMachine:Create
{
["frontmenu"] =
function()
return FrontMenuState:Create(this)
end,
["items"] =
function()
return ItemMenuState:Create(this)
end,
-- This is the new entry for the magic menu!
["magic"] =
function()
return MagicMenuState:Create(this)
end,
Next let’s add the “Magic” text to the sidebar menu. Here’s the change in FrontMenuState
.
function FrontMenuState:Create(parent)
-- code omitted
local this
this =
{
-- code omitted
mSelections = Selection:Create
{
spacingY = 32,
data =
{
{ id = "items", text = "Items" },
{ id = "status", text = "Status" },
{ id = "equipment", text = "Equipment" },
{ id = "magic", text = "Magic"}, -- New Entry
{ id = "save", text = "Save" },
{ id = "load", text = "Load" }
},
With those changes the menu now looks like this:
We’ve successfully added the menu option but the alignment is a bit off. Let’s reduce the spacing, which is defined above, from 32
to 28
. Here’s the updated version that looks better:
Now if you click on the Magic
option two things will happen:
- You’ll be prompted to choose a party member
- After choosing one it will crash!
It crashes because we haven’t written the MagicMenuState
yet.
The prompt to choose a party member is more interesting. In our game, there’s only one character that can use magic. Therefore there’s an argument to skip the character selection and jump straight to the magic menu but then it’s not quite clear whose spells we’re browsing. We’ll keep the character selection but only allow the mage to be selected, this might be more complicated to write but I think it flows better.
Party Member to Magic Menu
The next change ensures that when the player selects a party member, they get redirected to the magic menu.
function FrontMenuState:OnPartyMemberChosen(actorIndex, actorSummary)
-- Need to move state according to menu selection
local indexToStateId =
{
[2] = "status",
[3] = "equip",
[4] = "magic" -- <-- we've added this option
}
When a party member is selected, the OnPartyMemberChosen
callback works out which state to transition to by getting the index of the option menu.
I don’t like this.
Updating this indexToStateId
table here is messy and easy to miss when adding a new menu option.
These polish articles are about polishing the user experience, not the code but let’s refactor this and make it better!
The selection menu entries are defined like this:
data =
{
{ id = "items", text = "Items" },
{ id = "status", text = "Status" },
{ id = "equipment", text = "Equipment" },
{ id = "magic", text = "Magic"},
{ id = "save", text = "Save" },
{ id = "load", text = "Load" }
},
Ok, this is where we should have the state id, not in a random table in the OnPartyMemberChosen
function. Let’s update it.
data =
{
{ id = "items", stateId = "items", text = "Items" },
{ id = "status", stateId = "status", text = "Status" },
{ id = "equipment", stateId = "equip", text = "Equipment" },
{ id = "magic", stateId = "magic", text = "Magic"},
{ id = "save", text = "Save" },
{ id = "load", text = "Load" }
},
Then we can change some of the transition code. For instance, take the OnMenuClick
function below:
function FrontMenuState:OnMenuClick(index, item)
if item.id == "items" then
return self.mStateMachine:Change("items")
end
This can be changed to the following:
function FrontMenuState:OnMenuClick(index, item)
if item.id == "items" then
return self.mStateMachine:Change(item.stateId)
end
A small change but a change for the better I’d say. Now let’s get back to OnPartyMemberChosen
and update that too. Here’s the original:
function FrontMenuState:OnPartyMemberChosen(actorIndex, actorSummary)
local indexToStateId =
{
[2] = "status",
[3] = "equip",
[4] = "magic"
-- more states can go here.
}
local actor = actorSummary.mActor
local index = self.mSelections:GetIndex()
local stateId = indexToStateId[index]
self.mStateMachine:Change(stateId, actor)
end
To this:
function FrontMenuState:OnPartyMemberChosen(actorIndex, actorSummary)
local item = self.mSelections:SelectedItem()
local actor = actorSummary.mActor
self.mStateMachine:Change(item.stateId, actor)
end
Ah, that feels a bit better!
Restricting the Party Member Selector for Magic
Right, when the player selects Magic
from the sidebar, they should only be able to choose the mage. The code that handles the selector is shown below:
function FrontMenuState:Update(dt)
if self.mInPartyMenu then
self.mPartyMenu:HandleInput()
Let’s create a new function called HandlePartyMenuInput
and replace the above code with a call to that.
function FrontMenuState:HandlePartyMenuInput()
local item = self.mSelections:SelectedItem()
if item.id == "magic" then
if Keyboard.JustPressed(KEY_SPACE) then
self.mPartyMenu:OnClick()
end
return
end
self.mPartyMenu:HandleInput()
end
function FrontMenuState:Update(dt)
if self.mInPartyMenu then
self:HandlePartyMenuInput()
If you run the code now and choose Magic
you’ll enter the party selector but won’t be able to move because we’ve taken over the controls. You’ll only be able to perform the selection task. (If you select the mage, it will crash - but don’t worry about that, we’ll get to it soon!)
The restricted selection works … but it only works by chance! The first character selected happens to be the mage. We need to ensure that the mage character is the one selected no matter the party order. (If we had more than one spell caster I’d just let you move through all the party members but you’d only be able to click on on spell casters).
When we select an item like magic
or status
, OnMenuClick
gets called. You can see the code below.
function FrontMenuState:OnMenuClick(index, item)
if item.id == "items" then
return self.mStateMachine:Change(item.stateId)
end
if item.id == "save" then
if self.mParent.mMapDef.can_save then
self.mStack:Pop()
Save:Save()
self.mStack:PushFit(gRenderer, 0, 0, "Saved!")
end
return
end
-- and so on
There’s some bad behaviour with the way this currently works. Try the following:
- Choose
Status
- Select the third party member
- Back out of the character select
- Chose
Magic
- The third party member is the still selected!!
The party member chooser should default to the first party member.
To do this we’ll use add a selection menu function called JumpToFirstItem
and call it as shown below:
function FrontMenuState:OnMenuClick(index, item)
if item.id == "items" then
return self.mStateMachine:Change(item.stateId)
end
-- code omitted
self.mPartyMenu:JumpToFirstItem() -- <- reset the selection cursor
self.mInPartyMenu = true
Let’s define JumpToFirstItem
in Selection.lua.
function Selection:JumpToFirstItem()
self.mFocusY = 1
self.mFocusX = 1
self.mDisplayStart = 1
end
When the players select Magic
the mage character should be selected by default. On opening the selector we’ll start with the top party member, check if they’re a mage and if not then we’ll move down through the list until we find one. If we don’t find a mage, we’ll reset the cursor to the first element and print out an error message. Here’s the code:
function FrontMenuState:OnMenuClick(index, item)
-- code omitted
self.mPartyMenu:JumpToFirstItem()
self.mInPartyMenu = true
self.mSelections:HideCursor()
self.mPartyMenu:ShowCursor()
self.mPrevTopBarText = self.mTopBarText
self.mTopBarText = "Choose a party member"
if item.id == "magic" then
local member = self.mPartyMenu:SelectedItem()
local memberCount = #self.mPartyMenu.mDataSource
-- Start at the top and scroll down
-- until you hit a mage character
while member.mActor.mId ~= "mage" and
self.mPartyMenu.mFocusY < memberCount do
self.mPartyMenu:MoveDown()
member = self.mPartyMenu:SelectedItem()
end
if(member.mActor.mId ~= "mage") then
print("Couldn't find mage!");
self.mPartyMenu:JumpToFirstItem()
end
end
end
That’s the selector working as we’d like! Next we need to open the magic menu itself.
Opening the Magic Menu
When writing the book I made a magic menu but it was cut when the book was getting too long. My original menu looked like this:
This menu is a good starting point but it needs tweaks:
- The top right panel was going to contain the magic types. We don’t have magic types, so we’ll leave it empty.
- Spell cost needs moving next to it’s name.
- Let’s join the second row of panels into one long panel. Keep the casters name and add their current mp.
- Activate the cursor so the player can automatically browse the spells
Thiis is a lot of UI code. Rather than write inline, you can check out the code commit over here.
Here’s the rearranged menu:
The player can now enter the magic menu from the in-game menu and browse the spells they’ve unlocked!
In the next polish article we’ll add a feature so the player can use certain items and spells on the world map.
Source
Here’s a github with an updated project here.