03-11-2009, 06:39 PM
Selectable Window Fundamentals
by RPG Advocate
saved from Phylomortis.Com
by RPG Advocate
saved from Phylomortis.Com
Introduction
This tutorial describes the basics of manipulating selectable windows, such as transitioning from one window to another, seperation of input handling, and strategies for easily creating your own selectable windows.
Here we go
If you intend to use RGSS to create custom systems, some portion of your design time will probably be spent set up selectable windows that allow the player to interact with your system's components. This tutorial will teach you how to set up these windows. For this tutorial, I will be using code samples from my battle debugger script. If you would like to see the entire script in a new window, please [ click here ].
The first thing you should know about is how to transition from one window to the next (or previous), when the user selects a choice from the window or cancels. Take a look at code sample 1. The snippet of code shown makes it so that during a test play or battle test, the debug command window, monster data window, and turn count window appear. The numbers next to the windows in the pictures correspond to the comment numbers in the code sample. The first thing you should notice is that for windows 1 and 2, the selectable windows, the values @windowname.active and @windowname.visible are being modified. Let's take those two in turn. If you want a window (selectable or not) to disappear from the screen in response to a selection, then the statement @windowname.visible = false should be used. Conversely, if you want a window to appear, use the statement @windowname.visible = true. In the case of Code Sample 1, I've made the action command window (Window #1) disappear and the main debug command window (Window #2) appear. Additionally, I made the action command window inactive and the main debug command window active. What does "active", mean? In the context of RGSS, for a window to be "active" means that its cursor will move when the user presses the arrow keys. Note that this does not mean that when the user presses the decision key, a command will be selected. This is because of the division of input processing, explained later. A window should never be invisible and active, and in most cases, only one window should be active at a time (there are exceptions, but these are for advanced users), so please check your code carefully. Otherwise, the bug might be hard to track. Now, contrast that with windows #3, and #4, the non-selectable information windows. "Active" has no meaning for an information window, so I merely make the monster data and turn count windows visible.
Code Sample 1
CODE
if Input.trigger?(Input::X) && ($BTEST || $DEBUG)
$game_system.se_play($data_system.decision_se)
@debugging = true
@actor_command_window.active = false # 1
@actor_command_window.visible = false
@battledebug_window.active = true # 2
@battledebug_window.visible = true
@battledebug_window.index = 0
@monsterdata_window.refresh
@count_window.refresh
@count_window.visible = true # 3
@monsterdata_window.visible = true # 4
return
end
$game_system.se_play($data_system.decision_se)
@debugging = true
@actor_command_window.active = false # 1
@actor_command_window.visible = false
@battledebug_window.active = true # 2
@battledebug_window.visible = true
@battledebug_window.index = 0
@monsterdata_window.refresh
@count_window.refresh
@count_window.visible = true # 3
@monsterdata_window.visible = true # 4
return
end
Now, let's see what happens when the user cancels the window shown in the second screenshot. Take a look at Code Sample 2. Other than some cleanup from the debugger itself, you're probably not entirely surprised to find that the true/false values shown in Code Sample 1 are just reversed in Code Sample 2. When taken together, these two code samples provide the basic framework for moving between selectable windows.
Code Sample 2
CODE
if Input.trigger?(Input::B)
@debugging = false
@sv_index = -1
$game_system.se_play($data_system.cancel_se)
@battledebug_window.active = false
@battledebug_window.visible = false
@monsterdata_window.visible = false
@count_window.visible = false
@actor_command_window.active = true
@actor_command_window.visible = true
phase3_setup_command_window
end
@debugging = false
@sv_index = -1
$game_system.se_play($data_system.cancel_se)
@battledebug_window.active = false
@battledebug_window.visible = false
@monsterdata_window.visible = false
@count_window.visible = false
@actor_command_window.active = true
@actor_command_window.visible = true
phase3_setup_command_window
end
Now that the basic framework has been established, let's look at how the windows differentiate between selections. That is, how the window knows what to do when the user selects "Change Character HP" from the window above, as opposed to "Change Turn Number" or some other command. The code highlighted in red in Code Sample 3 shows the method for handling decisions. In your update_windowname method, you should have a "if Input.trigger?(INPUT::C)" clause, with the a case statement that checks the current "index" of the cursor. The index of the cursor is the item number that is currently selected (see image below Code Sample 3 for an example). Take note from the image that the index starts at 0. Notice that each "when" clause within the case statement matches one of the possible values of @battledebug_window.index. Let's take a look at the "when 0" case as an example. Index 0 corresponds to "Change Character HP" so the code within that clause will execute. In this case, the decision sound effect is played, the battle debug window is made invisible and inactive, and the monsterdata and turn count windows are made invisible in preparation for calling the debug_actor_select method, which handles the arrow cursor for characters within the battle debugger.
Code Sample 3
CODE
def update_battledebug
if @battledebug_window.active
@selected_entity = -1
end
if Input.trigger?(Input::B)
@debugging = false
@sv_index = -1
$game_system.se_play($data_system.cancel_se)
@battledebug_window.active = false
@battledebug_window.visible = false
@monsterdata_window.visible = false
@count_window.visible = false
@actor_command_window.active = true
@actor_command_window.visible = true
phase3_setup_command_window
end
if Input.trigger?(Input::C)
case @battledebug_window.index
when 0
$game_system.se_play($data_system.decision_se)
@battledebug_window.active = false
@battledebug_window.visible = false
@monsterdata_window.visible = false
@count_window.visible = false
debug_actor_select
when 1
$game_system.se_play($data_system.decision_se)
@battledebug_window.active = false
@battledebug_window.visible = false
@monsterdata_window.visible = false
@count_window.visible = false
debug_actor_select
when 2
$game_system.se_play($data_system.decision_se)
@battledebug_window.active = false
@battledebug_window.visible = false
@monsterdata_window.visible = false
@count_window.visible = false
debug_actor_select
when 3
$game_system.se_play($data_system.decision_se)
@battledebug_window.active = false
@battledebug_window.visible = false
@monsterdata_window.visible = false
@count_window.visible = false
debug_enemy_select
when 4
$game_system.se_play($data_system.decision_se)
@battledebug_window.active = false
@battledebug_window.visible = false
@monsterdata_window.visible = false
@count_window.visible = false
debug_enemy_select
when 5
$game_system.se_play($data_system.decision_se)
@battledebug_window.active = false
@battledebug_window.visible = false
@monsterdata_window.visible = false
@count_window.visible = false
debug_enemy_select
when 6
$game_system.se_play($data_system.decision_se)
for enemy in $game_troop.enemies
enemy.hp = 0
enemy.immortal = false
@battledebug_window.visible = false
@battledebug_window.active = false
@monsterdata_window.visible = false
@count_window.visible = false
start_phase5
end
when 7
$game_system.se_play($data_system.decision_se)
for enemy in $game_troop.enemies
if enemy.dead? && enemy.hidden == false
enemy.hp += 1
enemy.damage = -1
enemy.damage_pop = true
end
end
@monsterdata_window.refresh
when 8
$game_system.se_play($data_system.decision_se)
@battledebug_window.active = false
@left_window.visible = true
@right_window.visible = true
@left_window.active = true
when 9
$game_system.se_play($data_system.decision_se)
@battledebug_window.active = false
@battledebug_window.visible = false
@monsterdata_window.visible = false
@count_window.visible = false
debug_enemy_select
when 10
$game_system.se_play($data_system.decision_se)
for enemy in $game_troop.enemies
enemy.hidden = false
end
@monsterdata_window.refresh
when 11
$game_system.se_play($data_system.decision_se)
@battledebug_window.active = false
@itemset_window.index = 0
@itemset_window.active = true
@itemset_window.visible = true
when 12
$game_system.se_play($data_system.decision_se)
@battledebug_window.active = false
@turn_window.number = $game_temp.battle_turn + 1
@turn_window.active = true
@turn_window.visible = true
when 13
$game_system.se_play($data_system.decision_se)
@battledebug_window.active = false
@end_window.active = true
@end_window.visible = true
end
end
end
if @battledebug_window.active
@selected_entity = -1
end
if Input.trigger?(Input::B)
@debugging = false
@sv_index = -1
$game_system.se_play($data_system.cancel_se)
@battledebug_window.active = false
@battledebug_window.visible = false
@monsterdata_window.visible = false
@count_window.visible = false
@actor_command_window.active = true
@actor_command_window.visible = true
phase3_setup_command_window
end
if Input.trigger?(Input::C)
case @battledebug_window.index
when 0
$game_system.se_play($data_system.decision_se)
@battledebug_window.active = false
@battledebug_window.visible = false
@monsterdata_window.visible = false
@count_window.visible = false
debug_actor_select
when 1
$game_system.se_play($data_system.decision_se)
@battledebug_window.active = false
@battledebug_window.visible = false
@monsterdata_window.visible = false
@count_window.visible = false
debug_actor_select
when 2
$game_system.se_play($data_system.decision_se)
@battledebug_window.active = false
@battledebug_window.visible = false
@monsterdata_window.visible = false
@count_window.visible = false
debug_actor_select
when 3
$game_system.se_play($data_system.decision_se)
@battledebug_window.active = false
@battledebug_window.visible = false
@monsterdata_window.visible = false
@count_window.visible = false
debug_enemy_select
when 4
$game_system.se_play($data_system.decision_se)
@battledebug_window.active = false
@battledebug_window.visible = false
@monsterdata_window.visible = false
@count_window.visible = false
debug_enemy_select
when 5
$game_system.se_play($data_system.decision_se)
@battledebug_window.active = false
@battledebug_window.visible = false
@monsterdata_window.visible = false
@count_window.visible = false
debug_enemy_select
when 6
$game_system.se_play($data_system.decision_se)
for enemy in $game_troop.enemies
enemy.hp = 0
enemy.immortal = false
@battledebug_window.visible = false
@battledebug_window.active = false
@monsterdata_window.visible = false
@count_window.visible = false
start_phase5
end
when 7
$game_system.se_play($data_system.decision_se)
for enemy in $game_troop.enemies
if enemy.dead? && enemy.hidden == false
enemy.hp += 1
enemy.damage = -1
enemy.damage_pop = true
end
end
@monsterdata_window.refresh
when 8
$game_system.se_play($data_system.decision_se)
@battledebug_window.active = false
@left_window.visible = true
@right_window.visible = true
@left_window.active = true
when 9
$game_system.se_play($data_system.decision_se)
@battledebug_window.active = false
@battledebug_window.visible = false
@monsterdata_window.visible = false
@count_window.visible = false
debug_enemy_select
when 10
$game_system.se_play($data_system.decision_se)
for enemy in $game_troop.enemies
enemy.hidden = false
end
@monsterdata_window.refresh
when 11
$game_system.se_play($data_system.decision_se)
@battledebug_window.active = false
@itemset_window.index = 0
@itemset_window.active = true
@itemset_window.visible = true
when 12
$game_system.se_play($data_system.decision_se)
@battledebug_window.active = false
@turn_window.number = $game_temp.battle_turn + 1
@turn_window.active = true
@turn_window.visible = true
when 13
$game_system.se_play($data_system.decision_se)
@battledebug_window.active = false
@end_window.active = true
@end_window.visible = true
end
end
end
You can see from the code in Code Sample 3 that the update_battledebug method watches for the user to press the "B" or "C" keys, but you'll notice that it doesn't watch for the user to press the arrow keys and move the cursor in response. This is because of RGSS's seperation of input handling responsibilities. Handling of arrow key input is done within the Window_Selectable class, since the effects of pressing an arrow are fairly common across all selectable windows. The effects of pressing keys other than the arrows are left up to the scripter, as shown above. Simple setting windowname.active to true will make the window respond to input from the arrows, but in order to make the window respond to other key inputs you've set up, you need to use the update method within the scene within which the window is contained to check and see if the window is active each frame. If it is, you call your update_windowname method. An example of checking to see if the battle debug window is active is shown in Code Sample 4. The code that will invoke the code in Code Sample 3 is shown in red.
Code Sample 4
CODE
def update_phase3
if @enemy_arrow != nil && @debugging
update_debug_enemy_select
elsif @actor_arrow != nil && @debugging
update_debug_actor_select
elsif @enemy_arrow != nil
update_phase3_enemy_select
elsif @actor_arrow != nil
update_phase3_actor_select
elsif @skill_window != nil
update_phase3_skill_select
elsif @item_window != nil
update_phase3_item_select
elsif @actor_command_window.active
update_phase3_basic_command
elsif @battledebug_window.active
update_battledebug
elsif @end_window.active
update_end
elsif @turn_window.active
update_turn
elsif @itemset_window.active
update_itemset
elsif @enemyhp_window.active
update_enemyhp
elsif @value_window.active
update_value
elsif @left_window.active
update_left
elsif @right_window.active
update_right
elsif @var_window.active
update_var
end
end
if @enemy_arrow != nil && @debugging
update_debug_enemy_select
elsif @actor_arrow != nil && @debugging
update_debug_actor_select
elsif @enemy_arrow != nil
update_phase3_enemy_select
elsif @actor_arrow != nil
update_phase3_actor_select
elsif @skill_window != nil
update_phase3_skill_select
elsif @item_window != nil
update_phase3_item_select
elsif @actor_command_window.active
update_phase3_basic_command
elsif @battledebug_window.active
update_battledebug
elsif @end_window.active
update_end
elsif @turn_window.active
update_turn
elsif @itemset_window.active
update_itemset
elsif @enemyhp_window.active
update_enemyhp
elsif @value_window.active
update_value
elsif @left_window.active
update_left
elsif @right_window.active
update_right
elsif @var_window.active
update_var
end
end
Now that you know how selectable windows work, you might be wondering how to best take advantage of the variety of selectable windows. If you intend to use a vertical window with one column, like the main debug command window shown in the screenshots above, the built-in class Window_Command makes it easy to create one. For horizontal windows with one row and multiple columns, your best bet is to use a template like the one shown in Code Sample 5. You need to change each item shown in red as listed below:
x: x position of the window.
y: y position of the window.
width: width of the window.
value: the number of commands in the window.
commnads: the commands in the window.
space: the amount of space in pixels between each command.
Code Sample 5
CODE
class Window_Horizontal < Window_Selectable
# ------------------------------------
def initialize
super(x, y, width, 64)
self.contents = Bitmap.new(width - 32, height - 32)
@item_max = value
@column_max = value
@commands = ["command1", "command2", "comannd3" ... "commandN"]
refresh
self.index = 0
end
# ------------------------------------
def refresh
self.contents.clear
for i in 0...@item_max
draw_item(i)
end
end
# ------------------------------------
def draw_item(index)
x = 4 + index * space
self.contents.draw_text(x, 0, 128, 32, @commands[index])
end
end
# ------------------------------------
def initialize
super(x, y, width, 64)
self.contents = Bitmap.new(width - 32, height - 32)
@item_max = value
@column_max = value
@commands = ["command1", "command2", "comannd3" ... "commandN"]
refresh
self.index = 0
end
# ------------------------------------
def refresh
self.contents.clear
for i in 0...@item_max
draw_item(i)
end
end
# ------------------------------------
def draw_item(index)
x = 4 + index * space
self.contents.draw_text(x, 0, 128, 32, @commands[index])
end
end
If you want a window with two or more rows and columns, and that selections contained in that window are all of a similar type, then a window patterned after Window_Item or Window_Skill is probably what you're looking for. If the selections do very different things when selected, then you will likely need to create a custom window class from scratch. That may seem daunting at first, but once you master the concepts presented in this tutorial, you should have a fairly good idea how to go about it.