Skip to content

Input

Whenever the player does something. bn::keypad is the only way to read the buttons in Butano, and there is no event or callback alternative: you ask every frame.

Every button has three questions you can ask, as functions like bn::keypad::a_pressed() or as bn::keypad::pressed(bn::keypad::key_type::A) when the button is a variable.

Function True when For how many frames
a_pressed() A was up last frame and is down now one
a_held() A is down every frame it stays down
a_released() A was down last frame and is up now one

The keypad is a snapshot, taken once per frame. bn::core::update() reads the buttons and compares them with the previous frame’s reading. Until the next update, every call gives the same answer, no matter how many times or from where you ask.

Pick the function by asking “how often should this happen?”

  • Once per press (toggle, menu choice, jump, cycling an option): pressed.
  • Every frame while the button is down (walking, charging, accelerating): held.
  • Once, when the player lets go (fire a charged shot, end a drag): released.

Hold A to charge: the screen turns bluer each frame. Let go to fire: it flashes, longer after a longer charge. Press B to change the flash color.

snippets/input/src/main.cpp
#include "bn_core.h"
#include "bn_keypad.h"
#include "bn_bg_palettes.h"
#include "bn_color.h"
int main()
{
bn::core::init();
constexpr bn::color shot_colors[] = { bn::color(31, 0, 0), bn::color(0, 31, 0), bn::color(31, 31, 31) };
int shot_color = 0;
int charge = 0;
int flash_frames = 0;
while(true)
{
// pressed: true on one frame only, so each press picks the next color once.
if(bn::keypad::b_pressed())
{
shot_color = (shot_color + 1) % 3;
}
// held: true on every frame while A is down, so the charge grows each frame.
if(bn::keypad::a_held() && charge < 31)
{
++charge;
}
// released: true on the frame A goes up. Fire, longer after a longer charge.
if(bn::keypad::a_released())
{
flash_frames = charge;
charge = 0;
}
if(flash_frames > 0)
{
bn::bg_palettes::set_transparent_color(shot_colors[shot_color]);
--flash_frames;
}
else
{
bn::bg_palettes::set_transparent_color(bn::color(0, 0, charge));
}
bn::core::update();
}
}

Arrows: D-pad · X: A · Z: B · Enter: Start · Backspace: Select · A/S: L/R

A toggle or a menu cursor goes crazy. You used held for something that should happen once. At 60 frames per second, a short tap is several frames long, so the toggle flips several times and lands on a random side, and the cursor flies to the end of the list. Use pressed. If you want a cursor to repeat while the button stays down, count the frames it has been held and step every N frames.

One press does two things. pressed is not consumed when you read it: it stays true for the whole frame. If A closes a dialog and your game code also checks A later in the same frame, that same press also makes the character jump. After handling a press, skip the rest of the input for that frame, for example by returning from the function or changing to a state that only reads input on the next frame.

Left and right at the same time. A real D-pad can’t press opposite directions together, but a keyboard in an emulator can. With two separate ifs, both run and the moves cancel out or fight. Use else if, or decide which direction wins.

The button never seems to change. You are waiting for it in a loop that doesn’t call bn::core::update(), so the snapshot is never refreshed. See the game loop.