← All examples
Input
Input System
Polling InputState end-to-end
Step 3 of the input-system roadmap: a small InputState/InputSystem showcase.
Everything here is driven by polling InputState inside Update() — movement, an edge-triggered toggle, a mouse cursor, held vs. edge-triggered mouse buttons, and scroll — with OnSystemEvent left completely empty, proving the polling API is enough on its own for a real game loop.
Controls
- W A S D Move the box
- SPACE Toggle light/dark background (edge-triggered)
- Left Click (hold) Shown as held while down
- Right Click Drop a mark at the cursor (edge-triggered)
- Scroll Shown live

Run it yourself
build just this example
cmake --build --preset windows-debug --target input_demo
# binary lands under bin/ (e.g. bin/Debug/input_demo.exe on Windows)
See Installation for the full build setup.
Source
examples/input_demo/Game.hpp
#pragma once
#include <ASGE/ASGE.hpp>
#include <array>
/**
* @brief Step 3 of the input-system roadmap: a small InputState/InputSystem showcase.
*
* Everything here is driven by polling InputState in Update() -- WASD movement,
* a SPACE toggle (edge-triggered), a mouse cursor, left-click-held vs. right-click
* (edge-triggered) and scroll -- with OnSystemEvent left empty, proving the polling
* API is enough on its own for a real game loop.
*/
class InputDemoState final : public asge::game::state::IGameState<int>
{
static constexpr std::size_t kMaxMarks = 24;
asge::math::Float2 m_BoxPos{ 380.0f, 280.0f };
float m_BoxSize{ 48.0f };
bool m_LightBackground{ false };
// Render() only gets an IRenderer, not InputState, so the cursor's
// position/held-state are cached here in Update() and just drawn in Render().
asge::math::Float2 m_CursorPos{};
bool m_CursorHeld{ false };
std::array<asge::math::Float2, kMaxMarks> m_Marks{};
std::size_t m_MarkCount{ 0 };
std::size_t m_NextMarkSlot{ 0 };
void UpdatePlayerBox(float inDeltaTime, asge::input::InputState const& inInput);
void UpdateBackgroundToggle(asge::input::InputState const& inInput);
void UpdateBoxSizeFromScroll(asge::input::InputState const& inInput);
void UpdateCursor(asge::input::InputState const& inInput);
void UpdateMarks(asge::input::InputState const& inInput);
public:
[[nodiscard]] std::optional<asge::game::state::Transition<int>>
Update(float inDeltaTime, asge::input::InputState const& inInput) override;
void Render(asge::video::IRenderer& inRenderer) override;
void OnSystemEvent(asge::event::SystemEvent const& inSysEvent) override;
};
class InputDemoGame final : public asge::game::Game<int>
{
public:
explicit InputDemoGame(asge::video::IRenderer& inRenderer, asge::audio::AudioDevice& inAudioDev);
protected:
[[nodiscard]] std::unique_ptr<StateType> CreateState(int inId) override;
};
examples/input_demo/Game.cpp
#include "Game.hpp"
#include <algorithm>
using asge::input::Keycode;
using asge::input::MouseButton;
namespace
{
constexpr float kBoxSpeed = 250.0f; // pixels/second
constexpr float kMinBoxSize = 20.0f;
constexpr float kMaxBoxSize = 120.0f;
constexpr float kScrollToSize = 6.0f; // pixels of box size per scroll unit
constexpr int kCursorRadius = 12;
constexpr int kMarkRadius = 5;
constexpr float kWindowW = 800.0f;
constexpr float kWindowH = 600.0f;
}
void InputDemoState::UpdatePlayerBox(float inDeltaTime, asge::input::InputState const &inInput)
{
// Continuous, held-down movement -- exactly what IsKeyDown polling replaces
// the hand-rolled m_Up/m_Down/m_Left/m_Right + OnSystemEvent pattern for.
float const dx = (inInput.IsKeyDown(Keycode::D) ? kBoxSpeed : 0.0f)
- (inInput.IsKeyDown(Keycode::A) ? kBoxSpeed : 0.0f);
float const dy = (inInput.IsKeyDown(Keycode::S) ? kBoxSpeed : 0.0f)
- (inInput.IsKeyDown(Keycode::W) ? kBoxSpeed : 0.0f);
m_BoxPos += asge::math::Float2{ dx, dy } * inDeltaTime;
m_BoxPos = {
std::clamp( m_BoxPos.x(), 0.0f, kWindowW - m_BoxSize ),
std::clamp( m_BoxPos.y(), 0.0f, kWindowH - m_BoxSize )
};
}
void InputDemoState::UpdateBackgroundToggle(asge::input::InputState const &inInput)
{
// Edge-triggered: only flips once per press, unlike IsKeyDown above.
if ( inInput.IsKeyPressed(Keycode::SPACE) )
m_LightBackground = !m_LightBackground;
}
void InputDemoState::UpdateBoxSizeFromScroll(asge::input::InputState const &inInput)
{
m_BoxSize += inInput.GetScrollDelta().y() * kScrollToSize;
m_BoxSize = std::clamp( m_BoxSize, kMinBoxSize, kMaxBoxSize );
}
void InputDemoState::UpdateCursor(asge::input::InputState const &inInput)
{
m_CursorPos = inInput.GetMousePosition();
m_CursorHeld = inInput.IsMouseButtonDown(MouseButton::LEFT);
}
void InputDemoState::UpdateMarks(asge::input::InputState const &inInput)
{
// Right-click drops a mark at the cursor -- edge-triggered, same as the
// SPACE toggle above but for a mouse button instead of a key.
if ( !inInput.IsMouseButtonPressed(MouseButton::RIGHT) ) return;
m_Marks[m_NextMarkSlot] = inInput.GetMousePosition();
m_NextMarkSlot = (m_NextMarkSlot + 1) % kMaxMarks;
m_MarkCount = std::min( m_MarkCount + 1, kMaxMarks );
}
std::optional<asge::game::state::Transition<int>>
InputDemoState::Update(float inDeltaTime, asge::input::InputState const &inInput)
{
UpdatePlayerBox( inDeltaTime, inInput );
UpdateBackgroundToggle( inInput );
UpdateBoxSizeFromScroll( inInput );
UpdateCursor( inInput );
UpdateMarks( inInput );
return std::nullopt;
}
void InputDemoState::Render(asge::video::IRenderer &inRenderer)
{
inRenderer.Clear( m_LightBackground
? asge::graphics::RGBA_Color{ 225, 225, 230, 255 }
: asge::graphics::RGBA_Color{ 20, 20, 25, 255 } );
// Marks dropped by right-click.
for ( std::size_t i = 0; i < m_MarkCount; ++i )
{
inRenderer.DrawCircle(
asge::math::Int2{ static_cast<int>(m_Marks[i].x()), static_cast<int>(m_Marks[i].y()) },
kMarkRadius, { 240, 200, 60, 255 }, true );
}
// WASD-controlled box.
inRenderer.DrawRect(
asge::math::Rect{ m_BoxPos.x(), m_BoxPos.y(), m_BoxSize, m_BoxSize },
{ 220, 60, 60, 255 }, true );
// Mouse cursor: filled while left-click is held, outline otherwise. Both the
// position and the held-state come from InputState polling, cached in Update().
inRenderer.DrawCircle(
asge::math::Int2{ static_cast<int>(m_CursorPos.x()), static_cast<int>(m_CursorPos.y()) },
kCursorRadius, { 90, 170, 240, 255 }, m_CursorHeld );
}
void InputDemoState::OnSystemEvent([[maybe_unused]] asge::event::SystemEvent const &inSysEvent)
{
// Deliberately empty -- every reaction to input in this example comes from
// polling InputState in Update() instead.
}
InputDemoGame::InputDemoGame(asge::video::IRenderer& inRenderer, asge::audio::AudioDevice& inAudioDev)
: Game(inRenderer, inAudioDev)
{
SetInitialState(0);
}
std::unique_ptr<InputDemoGame::StateType> InputDemoGame::CreateState([[maybe_unused]] int inId)
{
return std::make_unique<InputDemoState>();
}