SLIP INTO RUBY - UNDER THE HOOD PART 22: SCENE IT BEFORE
In which the puns become unbearable.
Trihan
- 06/10/2021 04:09 AM
- 816 views
Hello, sports fans! Could it be that Trihan is keeping to a weekly schedule? Stranger things have happened!
We continue with the scene classes in this shiny new, super-exciting episode of
Today: Scene it before
Scene_MenuBase
This class performs basic processing for menu scenes, and inherits from Scene_Base.
In the overwrite to the start method, first we call the parent method. Then we call create_background and finally we set the instance variable @actor to the result of calling $game_party's menu_actor method (which gives us the actor chosen in the party status window prior to calling this scene, if there is one).
In the terminate method, first we call the parent method and then dispose_background, which will...well, dispose of the background, as we'll see.
In the method for creating the background, first we create an instance variable called @background_sprite and initialise it as a new instance of the Sprite class. Then we set its bitmap to the result of calling SceneManager's background_bitmap method, and set the sprite's color to 16R, 16G, 16B and 128 alpha. This gives us a partially-transparent darkened snapshot of the map.
Here we're just cleaning up after ourselves by disposing of the @background_sprite object. Failing to do this would result in a memory leak every time we open a menu scene.
In this method we're creating the help window for the scene. We set an instance variable called @help_window to a new instance of Window_Help and then set its viewport to @viewport, which was defined in Scene_Base.
This method switches to the next actor in the party; we set @actor to the result of calling $game_party's menu_actor_next method, and then call on_actor_change, which is an "event" method which will be called whenever the actor changes.
This is the same thing but for previous actor, using menu_actor_prev instead of menu_actor_next.
And here we have on_actor_change, which in the base class is empty. Classes which inherit from it will create their own implementations depending on what they need to do when the actor is changed.
That's all for the base menu scene! Not a lot going on, but it gets the basics sorted.
Scene_Menu
Here we have an important one. What's an RPG without a menu? This is the scene you go to for the list of commands you can choose from and an overview of the party's status, and as you may be able to guess it inherits from Scene_MenuBase.
In our overwrite of the start method, we first call the parent method as is the standard. Then in addition to that we're calling create_command_window, create_gold_window and create_status_window.
In this method we create the main menu command window, giving the player all the commands they'll be using during the game. Fairly simply, we set instance variable @command_window to a new instance of Window_MenuCommand and then set the handlers for all the commands it has. The commands themselves were defined by the window class so we don't need to worry about that, but unless we set handlers for them those options won't actually do anything in the scene.
I don't think I need to break this down line by line as we've already covered in the past how set_handler works. You give it a symbol for the command (which needs to match the symbol used in the add_command call in the window class), and then provide it with the method that will be called when the command is selected. Item, formation, save and game end have their own specified methods, while skill, equip and status all share command_personal, which will handle the scene branching from there.
This method creates the gold display window in the menu with which the player can see how poor they are. So we set instance variable @gold_window to a new instance of Window_Gold, then set its x coordinate to 0 and its y coordinate to the height of the game window minus the height of the gold window, which places it on the bottom left of the screen.
In this method we're creating the status window showing our party and their condition. Just one line of code needed: we set instance variable @status_window to a new instance of Window_MenuStatus, passing in the width of @command_window as the x coordinate and 0 as the y coordinate (this will cause it to take up the rest of the horizontal screen space and the entire height).
This is the method which is called when the player chooses the "Item" command, where we call the call method of SceneManager and pass in Scene_Item, which will take the player to the item scene.
This is the method called when the player chooses "Skill", "Equip" or "Status". We call @status_window's select_last method, which selects the most recent party member index the window was active on, then activate it, and set the handlers for the OK and cancel keys to on_personal_ok and on_personal_cancel respectively.
This method handles choosing the "Formation" command. As before, we call @status_window's select_last and activate methods, but then we set the handlers for OK and cancel to on_formation_ok and on_formation_cancel. This causes the status window to have different functionality depending on which command was chosen before it activated.
In the method for callin the save scene, we quite simply call SceneManager's call method and pass in Scene_Save.
And for the "Game End" command, it's the same but we pass in Scene_End instead.
This method is called when the player chooses "Skill", "Equip" or "Status"; we run a case statement on the current_symbol of @command_window: in each case we call SceneManager's call method, passing in Scene_Skill if the symbol is :skill, Scene_Equip if the symbol is :equip, and Scene_Status if it's :status.
This method is called if the player cancels while selecting a party member after choosing a "personal" menu command. We unselect @status_window and activate @command_window, which returns us to the command the player chose originally.
This method is called when the player hits ok on a party member after choosing "Formation". If the pending_index of @status_window is greater than or equal to 0, we call $game_party's swap_order method passing in @status_window's index and pending_index. Then we set pending_index to -1, and call redraw_item passing in @status_window's index. Otherwise, if the pending index is not greater than or equal to 0, we set pending_index to @status_window's index.
Either way, we activate @status_window.
So basically how this works is, the first time you hit ok on a party member, it sets the "pending index", indicating that we're designating a party member to swap. And if we've already designated a pending index, it swaps the pending party member with the one you selected and unsets the pending index for the next selection, and redraws the entry to show the new party order.
Last but not least, the method for hitting cancel while in formation mode. If the pending index is greater than or equal to 0, we set it to -1 and activate the status window. Otherwise, we unselect the status window and activate the command window.
And that's it for the menu!
Scene_ItemBase
This scene is the base for both the item scene and skill scene, which have quite a few common functions between them. It itself inherits from Scene_MenuBase.
Our start method is pretty simple: call the parent method and then call create_actor_window.
In this method, we create the window showing the actor details for choosing a target of an item or skill. First we set instance variable @actor_window to a new instance of Window_MenuActor and then set the ok and cancel handlers for it to on_actor_ok and on_actor_cancel.
This method gets the currently-selected item. For this, we return the result of calling @item_window's item method. The base class doesn't actually do anything to set @item_window, but as we'll see the child classes do.
This method gets the item or skill's user; to determine this, we return the result of calling the max_by method on $game_party's movable_members passing in a block using block variable "member" and returning the member's pha property.
Even if you remember some of the similar things we've seen, this might be a bit confusing, so let's break it down.
.pha is PHArmacology, which is an sp-parameter that determines how effective curative effects are on items or skills.
max_by iterates through an array running a block of code on each element and then returns the maximum value that was returned. So in the array this is running on, the return value will be the highest PHA.
movable_members, as you may remember, returns an array of all party members who are capable of acting (aren't dead or afflicted by a restriction state).
So when you put all this together, this method will return the highest PHA value of all party members who can take actions.
This has actually caused quite a bit of confusion in the community over the years with people who didn't realise that the user of an item in the menu isn't also the party member who was the target for it. It will always be the non-restricted member of your party with the highest pharmacology.
This method determines whether the cursor is in the left-hand column. And to do that, all we do is return whether the index of @item_window mod 2 is 0. It's pretty easy to see how this works: in a 2-column layout, the left-hand column indexes will be 0, 2, 4, 6, 8 etc. while the right-hand ones will be 1, 3, 5, 7, 9 etc. So taking the modulus 2 of the index will return 0 for a left-hand index and 1 for a right-hand one (for anyone who might have missed maths class that day or forgot the time we covered it, modulus (%) divides a value by another value and returns the remainder. There is never a remainder when you modulus an even number by 2, and always a remainder of 1 when you do it with an odd one, so it's a great mathematical process for determining index positions like this)
This method, as the name suggests, shows a subwindow, and takes that window as a parameter. First, we set a variable called width_remain to the width of the game window minus the width of the passed-in window. Then we set the window's x coordinate to width_remain if the cursor is in the left column or 0 if it's in the right (this prevents the subwindow from covering the item selected). Then we set the x coordinate and x origin of @viewport's rect to 0 if the cursor is in the left column or the window's width otherwise, and set the rect's width to width_remain.
The reason for this is that the subwindow is created before the other windows are, and technically is underneath them. Modifying the viewport's rect x and width reduces the display area so that nothing appears over the subwindow, and since the window can move depending on the cursor position, we need to move the viewport accordingly.
After moving the window and modifying the viewport as needed, we show and activate the window.
This method hides the subwindow, and again takes the subwindow as a parameter. We revert any changes to @viewport by setting its rect's x coordinate and x origin to 0, and the rect's width to the width of the game window. Then we hide and deactivate the window, and call activate_item_window.
The method called when the player hits ok on an actor. If the item is usable, we call use_item, and otherwise we call the play_buzzer method of the Sound module.
The cancel handler for the actor window. We call hide_sub_window passing in @actor_window as the argument.
This method confirms an item, and will either start the actor selection process or just use it if it doesn't target an ally.
We check whether the scene item is for a friendly battler. If so, we call show_sub_window passing in @actor_window and then call select_for_item on @actor_window passing in item (which will either select the target if the item is for a single actor or the entire party if it's for all members). Otherwise, we just call use_item and then activate_item_window.
This method...uh...activates the item window. That's the thing with self-explanatory code; doing a breakdown of it ends up being almost insulting. XD
So all we're doing here is calling refresh on @item_window and then activating it.
This method gets an array of all actors which are being targeted by the item. We check if the item is NOT for an ally; if so, we return an empty array. Otherwise, if the item is for all members, we return the result of calling $game_party's members method, which will give us an array of everyone in the party. And otherwise, we return the element of the members array at the index selected in @actor_window.
This method determines whether an item is usable. Here's we're just returning the result of calling the usable? method on the user, passing in item, AND item_effects_valid?, which will return true if the item has valid applicable effects, and false if not (as we'll see in a sec).
This method determines whether the item will have an applicable effect on the target(s); we run an any? loop through each element of item_target_actors, using block variable "target", and return the result of calling target's item_test method, passing in user and item. This will, for example, stop an HP recovery item from working if a target is already at full health.
This method is what actually uses the item. Again, we loop through item_target_actors but this time with an each loop, again using block variable "target". Then for as many times as the item's "repeats" property, we call item_apply on target, passing in user and item.
In this method, we use the item and perform a few other checks. First we call play_se_for_item (which won't be defined until child classes), then call use_item on the user, passing in item. We call use_item_to_actors to execute the item effects on the targets. Then we call check_common_event, then check_gameover and finally we call refresh on @actor_window.
This method checks whether a common event needs to execute (which will be the case if the item/skill effects include the "Common Event" option). We call SceneManager's goto method passing in Scene_Map if the common_event_reserved? method of $game_temp returns true.
And thus wraps up Scene_ItemBase! Now let's see its first child class.
Scene_Item
This scene processes game items like potions, HP ups, antidotes etc. As said above, it inherits from Scene_ItemBase.
In this overwrite to the start method, first we call the parent method, then create_help_window, then create_category_window, and finally create_item_window.
This method creates the category window. We set instance variable @category_window to a new instance of Window_ItemCategory, and set that window's viewport to @viewport. We set its help_window to @help_window and its y coordinate to the help window's height. Then we set the handlers for ok and cancel to on_category_ok and return_scene respectively.
This method creates the item window. We set a variable called wy to the y coordinate plus height of @categiry_window, and wh to the height of the game window minus wy. We set @item_window to a new instance of Window_ItemList passing in 0 as the x coordinate, wy as the y coordinate, the game window width as width and wh as the height. This gives us a window on the left of the screen underneath the category window, as wide as the screen and taking up the rest of the vertical space.
We set @item_window's viewport to @viewport and its help_window to @help_window as before, and set the ok and cancel handlers to on_item_ok and on_item_cancel.
Finally, we set @category_window's item_window to @item_window. This creates a link between the category and item windows so that the item window "knows" which category is selected and can update its item list accordingly.
This method is called when the player hits ok on a category; we activate @item_window and call its select_last method to select the last item that was used.
This method is called when the player hits ok on an item; we set $game_party's last_item_object to the selected item and then call determine_item.
This is the method called when the player hits cancel on an item; we call unselect on @item_window and then activate @category_window.
Here we see our first implementation of play_se_for_item. We just call the play_use_item method of the Sound module.
Last but not least, we overwrite use_item, first calling the parent method and then calling redraw_current_item on @item_window. This will update item quantities and usability as soon as they're used instead of having to wait until the player cancels.
That wraps it up for Scene_Item. Now let's look at the other side of the Scene_ItemBase inheritance coin.
Scene_Skill
And here it is. This is the scene that deals with your skills, your magic, your special abilities, and all of that good stuff. It inherits, surprising nobody, from Scene_ItemBase.
Our start method is very similar to the one from Scene_Item, but the windows are a bit different. Here we're calling the parent method, and then create_help_window, create_command_window, create_status_window and create_item_window.
First of our windows is the command window, created here. First we set variable wy to the height of @help_window. Then we set @command_window to a new instance of Window_SkillCommand, passing in an x coordinate of 0 and a y coordinate of wy. We set @command_window's viewport to @viewport, its help_window to @help_window, and its actor to @actor (to establish the links that will allow windows to update their contents based on the actor selected). Then we set the handlers for the "Skill" and "Cancel" commands, and for the page down and page up keys, to command_skill, return_scene, next_actor and prev_actor respectively.
Then we have the status window. We set y to the height of @help_window, and then set @status_window to a new instance of Window_SkillStatus passing in the @command_window's width for the x coordinate, and y for the y coordinate. This will place the status window to the right of the command window and under the help window. Then we set its viewport to @viewport and its actor to @actor.
Last of our windows in this scene is the item window, which will contain the actual list of skills to choose from based on the command selected.
First we set wx to 0, wy to @status_window's y minus its height, ww to the width of the game window and wh to the height of the game window minus wy. Then we set @item_window to a new instance of Window_SkillList, passing in wx, wy, ww and wh as the x, y, width and height. This will place the window on the far left, under the status window, taking up the full screen width and remaining height.
We set @item_window's actor to @actor, its viewport to @viewport and its help_window to @help_window. Then we set the handlers for ok and cancel to on_item_ok and on_item_cancel, and finally we set @command_window's skill_window to @item_window.
Other than a couple of coordinate and size differences, this is identical to the one from Scene_Item.
Unlike the item scene, in Scene_Skill for user we return @actor, or the actor whose skill list is currently being viewed.
The method for when a skill type command is selected is pretty simple: we activate @item_window and call select_last to select the last used skill.
In the method for hitting ok on an item (or skill in this case), we set the object of @actor's last_skill to item, and then call determine_item. This updates the skill that select_last will select, and sets up the skill selected for use.
The cancel handler is similarly uncomplicated. We just call @item_window's unselect method and then activate @command_window.
play_se_for_item is similar to Scene_Item's, but this time we're calling play_use_skill from the Sound module instead.
Our overwrite to use_item is similar too. First we call the parent method, as is tradition, and then we refresh @status_window and @item_window. This ensures that things like MP value and skill usability are always accurately displayed at the time of use.
Our actor change event method just sets the actor of @command_window, @status_window and @item_window to @actor, then activates @command_window. This ensures that all windows update their display to show the new actor and that the command window is active ready for selecting skill types.
And that's it! This feels like a good point to end on as I think doing Scene_Equip as well will make this run on just a bit too long. We've only got a couple of episodes worth of code left to break down, and then I'll need to figure out where the series is going from there.
Until next time!
We continue with the scene classes in this shiny new, super-exciting episode of

Today: Scene it before
Scene_MenuBase
This class performs basic processing for menu scenes, and inherits from Scene_Base.
def start super create_background @actor = $game_party.menu_actor end
In the overwrite to the start method, first we call the parent method. Then we call create_background and finally we set the instance variable @actor to the result of calling $game_party's menu_actor method (which gives us the actor chosen in the party status window prior to calling this scene, if there is one).
def terminate super dispose_background end
In the terminate method, first we call the parent method and then dispose_background, which will...well, dispose of the background, as we'll see.
def create_background @background_sprite = Sprite.new @background_sprite.bitmap = SceneManager.background_bitmap @background_sprite.color.set(16, 16, 16, 128) end
In the method for creating the background, first we create an instance variable called @background_sprite and initialise it as a new instance of the Sprite class. Then we set its bitmap to the result of calling SceneManager's background_bitmap method, and set the sprite's color to 16R, 16G, 16B and 128 alpha. This gives us a partially-transparent darkened snapshot of the map.
def dispose_background @background_sprite.dispose end
Here we're just cleaning up after ourselves by disposing of the @background_sprite object. Failing to do this would result in a memory leak every time we open a menu scene.
def create_help_window @help_window = Window_Help.new @help_window.viewport = @viewport end
In this method we're creating the help window for the scene. We set an instance variable called @help_window to a new instance of Window_Help and then set its viewport to @viewport, which was defined in Scene_Base.
def next_actor @actor = $game_party.menu_actor_next on_actor_change end
This method switches to the next actor in the party; we set @actor to the result of calling $game_party's menu_actor_next method, and then call on_actor_change, which is an "event" method which will be called whenever the actor changes.
def prev_actor @actor = $game_party.menu_actor_prev on_actor_change end
This is the same thing but for previous actor, using menu_actor_prev instead of menu_actor_next.
def on_actor_change end
And here we have on_actor_change, which in the base class is empty. Classes which inherit from it will create their own implementations depending on what they need to do when the actor is changed.
That's all for the base menu scene! Not a lot going on, but it gets the basics sorted.
Scene_Menu
Here we have an important one. What's an RPG without a menu? This is the scene you go to for the list of commands you can choose from and an overview of the party's status, and as you may be able to guess it inherits from Scene_MenuBase.
def start super create_command_window create_gold_window create_status_window end
In our overwrite of the start method, we first call the parent method as is the standard. Then in addition to that we're calling create_command_window, create_gold_window and create_status_window.
def create_command_window @command_window = Window_MenuCommand.new @command_window.set_handler(:item, method(:command_item)) @command_window.set_handler(:skill, method(:command_personal)) @command_window.set_handler(:equip, method(:command_personal)) @command_window.set_handler(:status, method(:command_personal)) @command_window.set_handler(:formation, method(:command_formation)) @command_window.set_handler(:save, method(:command_save)) @command_window.set_handler(:game_end, method(:command_game_end)) @command_window.set_handler(:cancel, method(:return_scene)) end
In this method we create the main menu command window, giving the player all the commands they'll be using during the game. Fairly simply, we set instance variable @command_window to a new instance of Window_MenuCommand and then set the handlers for all the commands it has. The commands themselves were defined by the window class so we don't need to worry about that, but unless we set handlers for them those options won't actually do anything in the scene.
I don't think I need to break this down line by line as we've already covered in the past how set_handler works. You give it a symbol for the command (which needs to match the symbol used in the add_command call in the window class), and then provide it with the method that will be called when the command is selected. Item, formation, save and game end have their own specified methods, while skill, equip and status all share command_personal, which will handle the scene branching from there.
def create_gold_window @gold_window = Window_Gold.new @gold_window.x = 0 @gold_window.y = Graphics.height - @gold_window.height end
This method creates the gold display window in the menu with which the player can see how poor they are. So we set instance variable @gold_window to a new instance of Window_Gold, then set its x coordinate to 0 and its y coordinate to the height of the game window minus the height of the gold window, which places it on the bottom left of the screen.
def create_status_window @status_window = Window_MenuStatus.new(@command_window.width, 0) end
In this method we're creating the status window showing our party and their condition. Just one line of code needed: we set instance variable @status_window to a new instance of Window_MenuStatus, passing in the width of @command_window as the x coordinate and 0 as the y coordinate (this will cause it to take up the rest of the horizontal screen space and the entire height).
def command_item SceneManager.call(Scene_Item) end
This is the method which is called when the player chooses the "Item" command, where we call the call method of SceneManager and pass in Scene_Item, which will take the player to the item scene.
def command_personal @status_window.select_last @status_window.activate @status_window.set_handler(:ok, method(:on_personal_ok)) @status_window.set_handler(:cancel, method(:on_personal_cancel)) end
This is the method called when the player chooses "Skill", "Equip" or "Status". We call @status_window's select_last method, which selects the most recent party member index the window was active on, then activate it, and set the handlers for the OK and cancel keys to on_personal_ok and on_personal_cancel respectively.
def command_formation @status_window.select_last @status_window.activate @status_window.set_handler(:ok, method(:on_formation_ok)) @status_window.set_handler(:cancel, method(:on_formation_cancel)) end
This method handles choosing the "Formation" command. As before, we call @status_window's select_last and activate methods, but then we set the handlers for OK and cancel to on_formation_ok and on_formation_cancel. This causes the status window to have different functionality depending on which command was chosen before it activated.
def command_save SceneManager.call(Scene_Save) end
In the method for callin the save scene, we quite simply call SceneManager's call method and pass in Scene_Save.
def command_game_end SceneManager.call(Scene_End) end
And for the "Game End" command, it's the same but we pass in Scene_End instead.
def on_personal_ok case @command_window.current_symbol when :skill SceneManager.call(Scene_Skill) when :equip SceneManager.call(Scene_Equip) when :status SceneManager.call(Scene_Status) end end
This method is called when the player chooses "Skill", "Equip" or "Status"; we run a case statement on the current_symbol of @command_window: in each case we call SceneManager's call method, passing in Scene_Skill if the symbol is :skill, Scene_Equip if the symbol is :equip, and Scene_Status if it's :status.
def on_personal_cancel @status_window.unselect @command_window.activate end
This method is called if the player cancels while selecting a party member after choosing a "personal" menu command. We unselect @status_window and activate @command_window, which returns us to the command the player chose originally.
def on_formation_ok if @status_window.pending_index >= 0 $game_party.swap_order(@status_window.index, @status_window.pending_index) @status_window.pending_index = -1 @status_window.redraw_item(@status_window.index) else @status_window.pending_index = @status_window.index end @status_window.activate end
This method is called when the player hits ok on a party member after choosing "Formation". If the pending_index of @status_window is greater than or equal to 0, we call $game_party's swap_order method passing in @status_window's index and pending_index. Then we set pending_index to -1, and call redraw_item passing in @status_window's index. Otherwise, if the pending index is not greater than or equal to 0, we set pending_index to @status_window's index.
Either way, we activate @status_window.
So basically how this works is, the first time you hit ok on a party member, it sets the "pending index", indicating that we're designating a party member to swap. And if we've already designated a pending index, it swaps the pending party member with the one you selected and unsets the pending index for the next selection, and redraws the entry to show the new party order.
def on_formation_cancel if @status_window.pending_index >= 0 @status_window.pending_index = -1 @status_window.activate else @status_window.unselect @command_window.activate end end
Last but not least, the method for hitting cancel while in formation mode. If the pending index is greater than or equal to 0, we set it to -1 and activate the status window. Otherwise, we unselect the status window and activate the command window.
And that's it for the menu!
Scene_ItemBase
This scene is the base for both the item scene and skill scene, which have quite a few common functions between them. It itself inherits from Scene_MenuBase.
def start super create_actor_window end
Our start method is pretty simple: call the parent method and then call create_actor_window.
def create_actor_window @actor_window = Window_MenuActor.new @actor_window.set_handler(:ok, method(:on_actor_ok)) @actor_window.set_handler(:cancel, method(:on_actor_cancel)) end
In this method, we create the window showing the actor details for choosing a target of an item or skill. First we set instance variable @actor_window to a new instance of Window_MenuActor and then set the ok and cancel handlers for it to on_actor_ok and on_actor_cancel.
def item @item_window.item end
This method gets the currently-selected item. For this, we return the result of calling @item_window's item method. The base class doesn't actually do anything to set @item_window, but as we'll see the child classes do.
def user $game_party.movable_members.max_by {|member| member.pha } end
This method gets the item or skill's user; to determine this, we return the result of calling the max_by method on $game_party's movable_members passing in a block using block variable "member" and returning the member's pha property.
Even if you remember some of the similar things we've seen, this might be a bit confusing, so let's break it down.
.pha is PHArmacology, which is an sp-parameter that determines how effective curative effects are on items or skills.
max_by iterates through an array running a block of code on each element and then returns the maximum value that was returned. So in the array this is running on, the return value will be the highest PHA.
movable_members, as you may remember, returns an array of all party members who are capable of acting (aren't dead or afflicted by a restriction state).
So when you put all this together, this method will return the highest PHA value of all party members who can take actions.
This has actually caused quite a bit of confusion in the community over the years with people who didn't realise that the user of an item in the menu isn't also the party member who was the target for it. It will always be the non-restricted member of your party with the highest pharmacology.
def cursor_left? @item_window.index % 2 == 0 end
This method determines whether the cursor is in the left-hand column. And to do that, all we do is return whether the index of @item_window mod 2 is 0. It's pretty easy to see how this works: in a 2-column layout, the left-hand column indexes will be 0, 2, 4, 6, 8 etc. while the right-hand ones will be 1, 3, 5, 7, 9 etc. So taking the modulus 2 of the index will return 0 for a left-hand index and 1 for a right-hand one (for anyone who might have missed maths class that day or forgot the time we covered it, modulus (%) divides a value by another value and returns the remainder. There is never a remainder when you modulus an even number by 2, and always a remainder of 1 when you do it with an odd one, so it's a great mathematical process for determining index positions like this)
def show_sub_window(window) width_remain = Graphics.width - window.width window.x = cursor_left? ? width_remain : 0 @viewport.rect.x = @viewport.ox = cursor_left? ? 0 : window.width @viewport.rect.width = width_remain window.show.activate end
This method, as the name suggests, shows a subwindow, and takes that window as a parameter. First, we set a variable called width_remain to the width of the game window minus the width of the passed-in window. Then we set the window's x coordinate to width_remain if the cursor is in the left column or 0 if it's in the right (this prevents the subwindow from covering the item selected). Then we set the x coordinate and x origin of @viewport's rect to 0 if the cursor is in the left column or the window's width otherwise, and set the rect's width to width_remain.
The reason for this is that the subwindow is created before the other windows are, and technically is underneath them. Modifying the viewport's rect x and width reduces the display area so that nothing appears over the subwindow, and since the window can move depending on the cursor position, we need to move the viewport accordingly.
After moving the window and modifying the viewport as needed, we show and activate the window.
def hide_sub_window(window) @viewport.rect.x = @viewport.ox = 0 @viewport.rect.width = Graphics.width window.hide.deactivate activate_item_window end
This method hides the subwindow, and again takes the subwindow as a parameter. We revert any changes to @viewport by setting its rect's x coordinate and x origin to 0, and the rect's width to the width of the game window. Then we hide and deactivate the window, and call activate_item_window.
def on_actor_ok if item_usable? use_item else Sound.play_buzzer end end
The method called when the player hits ok on an actor. If the item is usable, we call use_item, and otherwise we call the play_buzzer method of the Sound module.
def on_actor_cancel hide_sub_window(@actor_window) end
The cancel handler for the actor window. We call hide_sub_window passing in @actor_window as the argument.
def determine_item if item.for_friend? show_sub_window(@actor_window) @actor_window.select_for_item(item) else use_item activate_item_window end end
This method confirms an item, and will either start the actor selection process or just use it if it doesn't target an ally.
We check whether the scene item is for a friendly battler. If so, we call show_sub_window passing in @actor_window and then call select_for_item on @actor_window passing in item (which will either select the target if the item is for a single actor or the entire party if it's for all members). Otherwise, we just call use_item and then activate_item_window.
def activate_item_window @item_window.refresh @item_window.activate end
This method...uh...activates the item window. That's the thing with self-explanatory code; doing a breakdown of it ends up being almost insulting. XD
So all we're doing here is calling refresh on @item_window and then activating it.
def item_target_actors if !item.for_friend? [] elsif item.for_all? $game_party.members else [$game_party.members[@actor_window.index]] end end
This method gets an array of all actors which are being targeted by the item. We check if the item is NOT for an ally; if so, we return an empty array. Otherwise, if the item is for all members, we return the result of calling $game_party's members method, which will give us an array of everyone in the party. And otherwise, we return the element of the members array at the index selected in @actor_window.
def item_usable? user.usable?(item) && item_effects_valid? end
This method determines whether an item is usable. Here's we're just returning the result of calling the usable? method on the user, passing in item, AND item_effects_valid?, which will return true if the item has valid applicable effects, and false if not (as we'll see in a sec).
def item_effects_valid? item_target_actors.any? do |target| target.item_test(user, item) end end
This method determines whether the item will have an applicable effect on the target(s); we run an any? loop through each element of item_target_actors, using block variable "target", and return the result of calling target's item_test method, passing in user and item. This will, for example, stop an HP recovery item from working if a target is already at full health.
def use_item_to_actors item_target_actors.each do |target| item.repeats.times { target.item_apply(user, item) } end end
This method is what actually uses the item. Again, we loop through item_target_actors but this time with an each loop, again using block variable "target". Then for as many times as the item's "repeats" property, we call item_apply on target, passing in user and item.
def use_item play_se_for_item user.use_item(item) use_item_to_actors check_common_event check_gameover @actor_window.refresh end
In this method, we use the item and perform a few other checks. First we call play_se_for_item (which won't be defined until child classes), then call use_item on the user, passing in item. We call use_item_to_actors to execute the item effects on the targets. Then we call check_common_event, then check_gameover and finally we call refresh on @actor_window.
def check_common_event SceneManager.goto(Scene_Map) if $game_temp.common_event_reserved? end
This method checks whether a common event needs to execute (which will be the case if the item/skill effects include the "Common Event" option). We call SceneManager's goto method passing in Scene_Map if the common_event_reserved? method of $game_temp returns true.
And thus wraps up Scene_ItemBase! Now let's see its first child class.
Scene_Item
This scene processes game items like potions, HP ups, antidotes etc. As said above, it inherits from Scene_ItemBase.
def start super create_help_window create_category_window create_item_window end
In this overwrite to the start method, first we call the parent method, then create_help_window, then create_category_window, and finally create_item_window.
def create_category_window @category_window = Window_ItemCategory.new @category_window.viewport = @viewport @category_window.help_window = @help_window @category_window.y = @help_window.height @category_window.set_handler(:ok, method(:on_category_ok)) @category_window.set_handler(:cancel, method(:return_scene)) end
This method creates the category window. We set instance variable @category_window to a new instance of Window_ItemCategory, and set that window's viewport to @viewport. We set its help_window to @help_window and its y coordinate to the help window's height. Then we set the handlers for ok and cancel to on_category_ok and return_scene respectively.
def create_item_window wy = @category_window.y + @category_window.height wh = Graphics.height - wy @item_window = Window_ItemList.new(0, wy, Graphics.width, wh) @item_window.viewport = @viewport @item_window.help_window = @help_window @item_window.set_handler(:ok, method(:on_item_ok)) @item_window.set_handler(:cancel, method(:on_item_cancel)) @category_window.item_window = @item_window end
This method creates the item window. We set a variable called wy to the y coordinate plus height of @categiry_window, and wh to the height of the game window minus wy. We set @item_window to a new instance of Window_ItemList passing in 0 as the x coordinate, wy as the y coordinate, the game window width as width and wh as the height. This gives us a window on the left of the screen underneath the category window, as wide as the screen and taking up the rest of the vertical space.
We set @item_window's viewport to @viewport and its help_window to @help_window as before, and set the ok and cancel handlers to on_item_ok and on_item_cancel.
Finally, we set @category_window's item_window to @item_window. This creates a link between the category and item windows so that the item window "knows" which category is selected and can update its item list accordingly.
def on_category_ok @item_window.activate @item_window.select_last end
This method is called when the player hits ok on a category; we activate @item_window and call its select_last method to select the last item that was used.
def on_item_ok $game_party.last_item.object = item determine_item end
This method is called when the player hits ok on an item; we set $game_party's last_item_object to the selected item and then call determine_item.
def on_item_cancel @item_window.unselect @category_window.activate end
This is the method called when the player hits cancel on an item; we call unselect on @item_window and then activate @category_window.
def play_se_for_item Sound.play_use_item end
Here we see our first implementation of play_se_for_item. We just call the play_use_item method of the Sound module.
def use_item super @item_window.redraw_current_item end
Last but not least, we overwrite use_item, first calling the parent method and then calling redraw_current_item on @item_window. This will update item quantities and usability as soon as they're used instead of having to wait until the player cancels.
That wraps it up for Scene_Item. Now let's look at the other side of the Scene_ItemBase inheritance coin.
Scene_Skill
And here it is. This is the scene that deals with your skills, your magic, your special abilities, and all of that good stuff. It inherits, surprising nobody, from Scene_ItemBase.
def start super create_help_window create_command_window create_status_window create_item_window end
Our start method is very similar to the one from Scene_Item, but the windows are a bit different. Here we're calling the parent method, and then create_help_window, create_command_window, create_status_window and create_item_window.
def create_command_window wy = @help_window.height @command_window = Window_SkillCommand.new(0, wy) @command_window.viewport = @viewport @command_window.help_window = @help_window @command_window.actor = @actor @command_window.set_handler(:skill, method(:command_skill)) @command_window.set_handler(:cancel, method(:return_scene)) @command_window.set_handler(:pagedown, method(:next_actor)) @command_window.set_handler(:pageup, method(:prev_actor)) end
First of our windows is the command window, created here. First we set variable wy to the height of @help_window. Then we set @command_window to a new instance of Window_SkillCommand, passing in an x coordinate of 0 and a y coordinate of wy. We set @command_window's viewport to @viewport, its help_window to @help_window, and its actor to @actor (to establish the links that will allow windows to update their contents based on the actor selected). Then we set the handlers for the "Skill" and "Cancel" commands, and for the page down and page up keys, to command_skill, return_scene, next_actor and prev_actor respectively.
def create_status_window y = @help_window.height @status_window = Window_SkillStatus.new(@command_window.width, y) @status_window.viewport = @viewport @status_window.actor = @actor end
Then we have the status window. We set y to the height of @help_window, and then set @status_window to a new instance of Window_SkillStatus passing in the @command_window's width for the x coordinate, and y for the y coordinate. This will place the status window to the right of the command window and under the help window. Then we set its viewport to @viewport and its actor to @actor.
def create_item_window wx = 0 wy = @status_window.y + @status_window.height ww = Graphics.width wh = Graphics.height - wy @item_window = Window_SkillList.new(wx, wy, ww, wh) @item_window.actor = @actor @item_window.viewport = @viewport @item_window.help_window = @help_window @item_window.set_handler(:ok, method(:on_item_ok)) @item_window.set_handler(:cancel, method(:on_item_cancel)) @command_window.skill_window = @item_window end
Last of our windows in this scene is the item window, which will contain the actual list of skills to choose from based on the command selected.
First we set wx to 0, wy to @status_window's y minus its height, ww to the width of the game window and wh to the height of the game window minus wy. Then we set @item_window to a new instance of Window_SkillList, passing in wx, wy, ww and wh as the x, y, width and height. This will place the window on the far left, under the status window, taking up the full screen width and remaining height.
We set @item_window's actor to @actor, its viewport to @viewport and its help_window to @help_window. Then we set the handlers for ok and cancel to on_item_ok and on_item_cancel, and finally we set @command_window's skill_window to @item_window.
Other than a couple of coordinate and size differences, this is identical to the one from Scene_Item.
def user @actor end
Unlike the item scene, in Scene_Skill for user we return @actor, or the actor whose skill list is currently being viewed.
def command_skill @item_window.activate @item_window.select_last end
The method for when a skill type command is selected is pretty simple: we activate @item_window and call select_last to select the last used skill.
def on_item_ok @actor.last_skill.object = item determine_item end
In the method for hitting ok on an item (or skill in this case), we set the object of @actor's last_skill to item, and then call determine_item. This updates the skill that select_last will select, and sets up the skill selected for use.
def on_item_cancel @item_window.unselect @command_window.activate end
The cancel handler is similarly uncomplicated. We just call @item_window's unselect method and then activate @command_window.
def play_se_for_item
Sound.play_use_skill
end
play_se_for_item is similar to Scene_Item's, but this time we're calling play_use_skill from the Sound module instead.
def use_item super @status_window.refresh @item_window.refresh end
Our overwrite to use_item is similar too. First we call the parent method, as is tradition, and then we refresh @status_window and @item_window. This ensures that things like MP value and skill usability are always accurately displayed at the time of use.
def on_actor_change @command_window.actor = @actor @status_window.actor = @actor @item_window.actor = @actor @command_window.activate end
Our actor change event method just sets the actor of @command_window, @status_window and @item_window to @actor, then activates @command_window. This ensures that all windows update their display to show the new actor and that the command window is active ready for selecting skill types.
And that's it! This feels like a good point to end on as I think doing Scene_Equip as well will make this run on just a bit too long. We've only got a couple of episodes worth of code left to break down, and then I'll need to figure out where the series is going from there.
Until next time!