Handle mouse input
This guide builds on the keyboard guide: the scene has a navigable gift list. Here we add mouse handling -- scrolling, hovering, clicking rows, and the bottom ctrl buttons -- in its own file that reopens the scene class.
Principle
Mouse handling follows precise conventions in PSDK:
- It lives in a separate file (
004 Mouse.rb) that reopens the scene class. update_mouse(moved)and theMOUSE_BUTTON_ACTIONSconstant must be public -- the framework calls them from outside the class.- Processing is decomposed into four ordered steps: wheel, hover, click, ctrl buttons.
- Sub-components own their collision detection via
simple_mouse_in?; the scene routes events, it never checks sprite collisions directly.
The Mouse file
update_mouse orchestrates the four interaction types in order. Each sub-method returns true if it consumed the event, which stops further processing. Create scripts/20 MysteryGift/003 GamePlay/004 Mouse.rb:
module GamePlay
# Mouse input handling for the Mystery Gift scene
class MysteryGift < BaseCleanUpdate::FrameBalanced
# Mouse button action mapping for ctrl buttons [A, X, Y, B]
MOUSE_BUTTON_ACTIONS = [:action_a, nil, nil, :action_b]
# Handle mouse input each frame
# @param moved [Boolean] whether the mouse moved this frame
def update_mouse(moved)
return false unless @composition.done?
return false if update_mouse_wheel
return false if moved && update_mouse_hover
return false if update_mouse_click
return update_ctrl_button_mouse
end
# Handle mouse wheel scrolling
# @return [Boolean] true if the wheel was used
def update_mouse_wheel
delta = Mouse.wheel
return false if delta == 0
Mouse.wheel = 0
play_cursor_se if @composition.move_selection(delta > 0 ? -1 : 1)
return true
end
# Handle mouse hover over gift rows
# @return [Boolean] true if hover changed the selection
def update_mouse_hover
hovered_index = @composition.find_hovered_row_index
return false if hovered_index.nil?
return false if hovered_index == @composition.selected_index
play_cursor_se
@composition.select_row(hovered_index)
return true
end
# Handle mouse clicks on gift rows
# @return [Boolean] true if a click was handled
def update_mouse_click
return false unless Mouse.trigger?(:LEFT)
hovered_index = @composition.find_hovered_row_index
return false if hovered_index.nil?
play_cursor_se
@composition.select_row(hovered_index)
return true
end
# Handle ctrl button mouse interaction
# @return [Boolean]
def update_ctrl_button_mouse
update_mouse_ctrl_buttons(@base_ui.ctrl, MOUSE_BUTTON_ACTIONS)
return false
end
end
end
update_mouseandMOUSE_BUTTON_ACTIONSare public -- the framework callsupdate_mouse(Mouse.moved)every frame.@composition.done?blocks mouse input during animations, exactly like the keyboard.- Order matters: wheel first, then hover (only if the cursor moved), then click, and finally the ctrl buttons as a fallback.
Mouse.wheel = 0resets the value after reading. This reset is mandatory: without it, the value persists and the wheel keeps scrolling every frame.update_mouse_hoverandupdate_mouse_clickreuse@composition.find_hovered_row_indexand@composition.select_row-- the same selection methods the keyboard uses. Hover compares against@composition.selected_indexso it only fires when the selection actually changes.update_ctrl_button_mousedelegates toupdate_mouse_ctrl_buttons, a helper inherited fromGamePlay::Base. It handles hover highlight, press animation and triggers the mapped action on click.MOUSE_BUTTON_ACTIONSmaps positions[A, X, Y, B]to action methods;nilskips a position. The A entry (:action_a) becomes active once we show the A button and addaction_ain the i18n and dialogs guides.
Hit testing in the Composition
The scene asks the Composition "which row is hovered?"; the Composition asks each row. Add this method to scripts/20 MysteryGift/002 UI/999 Composition.rb:
# Find which row the mouse hovers
# @return [Integer, nil] the row index or nil
def find_hovered_row_index
return @rows.index { |row| row.visible && row.hovered? }
end
The scene reads the current selection through the Composition too, so expose it -- add attr_reader :selected_index near the top of the class:
class Composition < SpriteStack
# @return [Integer] current selected row index
attr_reader :selected_index
# ...
find_hovered_row_indexreturns the index of the first visible row whosehovered?is true, ornil. Each GiftRow owns its hit-testing viasimple_mouse_in?(added in the SpriteStack guide).- This three-layer delegation -- row owns
hovered?, Composition ownsfind_hovered_row_index, scene routes the event -- keeps each layer focused on its responsibility. The scene never touches a sprite's bounding box directly.
Try it
Claim a couple of codes, then open the scene:
$user_data[:mystery_gift].claim('POTION50')
$user_data[:mystery_gift].claim('MASTERBALL')
GamePlay.open_mystery_gift
Moving the mouse over a row highlights it, the wheel scrolls the selection, a left click selects a row, and clicking the bottom "Quit" button closes the scene.
Conclusion
update_mouseandMOUSE_BUTTON_ACTIONSmust be public -- the framework calls them from outside.- Decompose
update_mouseinto wheel, hover, click, ctrl buttons; each step returnstrueto stop the chain. - Always reset
Mouse.wheelto 0 after reading, or the wheel scrolls forever. - Mouse and keyboard share the same Composition methods (
select_row,move_selection,find_hovered_row_index). - Sub-components own their collision detection via
simple_mouse_in?; the scene queries the Composition, never sprites directly. MOUSE_BUTTON_ACTIONSmaps[A, X, Y, B]ctrl buttons to action methods, withnilto skip a position.