46 Mastering Player Input: Keyboard, Mouse, and Gamepad
In our previous lessons, we explored the foundations of game development with Pyxel, including loading sprites and creating movement. Today, we’re diving into the critical topic of player input - the bridge between your players and the virtual worlds you create. We’ll explore how to capture and respond to keyboard presses, mouse movements, and even gamepad controls!
46.1 Why Input Matters: The Player’s Connection
Input is how players communicate their intentions to your game. Well-designed input systems create a feeling of responsiveness and control that’s essential for an enjoyable gaming experience. Think about it - even the most beautiful game with the most engaging story will fail if the controls feel clunky or unresponsive.
Let’s explore the three main types of input available in Pyxel:
46.2 Keyboard Input: The Classic Control Scheme
The keyboard is the most common input device for PC games, offering many keys for different actions. Pyxel provides two primary functions for detecting key presses:
46.2.1 btn()
vs btnp()
: Understanding the Difference
Pyxel offers two main functions for keyboard input:
pyxel.btn(key)
: ReturnsTrue
as long as the specified key is being held down- Perfect for continuous actions like movement
- Example: Moving a character while an arrow key is held
pyxel.btnp(key)
: ReturnsTrue
only on the first frame when a key is pressed- Perfect for one-time actions like jumping, shooting, or menu selection
- Example: Firing a weapon when the spacebar is pressed
Let’s see both in action:
import pyxel
class KeyboardDemo:
def __init__(self):
160, 120, title="Keyboard Input Demo")
pyxel.init(self.x = 80 # X position
self.y = 60 # Y position
self.color = 7 # White
self.update, self.draw)
pyxel.run(
def update(self):
# Continuous movement with btn()
if pyxel.btn(pyxel.KEY_LEFT):
self.x = max(self.x - 2, 0)
if pyxel.btn(pyxel.KEY_RIGHT):
self.x = min(self.x + 2, 160)
if pyxel.btn(pyxel.KEY_UP):
self.y = max(self.y - 2, 0)
if pyxel.btn(pyxel.KEY_DOWN):
self.y = min(self.y + 2, 120)
# One-time actions with btnp()
if pyxel.btnp(pyxel.KEY_SPACE):
# Change color when spacebar is pressed
self.color = (self.color + 1) % 16
# Quit the game
if pyxel.btnp(pyxel.KEY_Q):
pyxel.quit()
def draw(self):
1) # Clear screen with dark blue
pyxel.cls(
# Draw a square that moves with arrow keys
self.x - 4, self.y - 4, 8, 8, self.color)
pyxel.rect(
# Display instructions
5, 5, "Use arrow keys to move", 7)
pyxel.text(5, 15, "Press SPACE to change color", 7)
pyxel.text(5, 25, "Press Q to quit", 7)
pyxel.text(
# Show status
5, 100, f"Position: ({self.x}, {self.y})", 7)
pyxel.text(5, 110, f"Color: {self.color}", 7)
pyxel.text(
KeyboardDemo()
When you run this code, you’ll be able to move a square around the screen with the arrow keys (using btn()
for continuous movement) and change its color with the space bar (using btnp()
for a one-time action).
46.2.2 Key Constants: The Magic Words
Pyxel provides constants for all the keys you might want to use:
- Direction keys:
pyxel.KEY_UP
,pyxel.KEY_DOWN
,pyxel.KEY_LEFT
,pyxel.KEY_RIGHT
- Letter keys:
pyxel.KEY_A
throughpyxel.KEY_Z
- Number keys:
pyxel.KEY_0
throughpyxel.KEY_9
- Special keys:
pyxel.KEY_SPACE
,pyxel.KEY_RETURN
,pyxel.KEY_ESCAPE
, etc.
You can find the full list in the Pyxel documentation.
46.2.3 Advanced Keyboard Techniques
46.2.3.1 Detecting Multiple Keys
Pyxel can handle multiple key presses simultaneously. This allows for diagonal movement and combined actions:
# Diagonal movement
if pyxel.btn(pyxel.KEY_UP) and pyxel.btn(pyxel.KEY_RIGHT):
# Move diagonally up and right
self.y = max(self.y - 1, 0)
self.x = min(self.x + 1, 160)
46.2.3.2 Input Buffering
For games requiring precise timing, you might want to implement input buffering - accepting input slightly before an action is possible:
# Simple input buffer for a jump
if pyxel.btnp(pyxel.KEY_SPACE):
self.jump_buffer = 10 # Allow jump within 10 frames
# Later in the update
if self.jump_buffer > 0:
if self.on_ground: # If character is on ground
self.do_jump() # Execute the jump
self.jump_buffer = 0 # Reset buffer
else:
self.jump_buffer -= 1 # Decrease buffer timer
46.3 Mouse Input: Point and Click Adventures
Mouse input provides an intuitive way for players to interact with your game, especially for menus, strategy games, or point-and-click adventures.
46.3.1 Enabling Mouse Input
Before using the mouse, you need to enable it:
True) # Enable mouse pyxel.mouse(
46.3.2 Reading Mouse Position and Clicks
Pyxel makes it easy to get the mouse position and detect clicks:
# Get mouse position
= pyxel.mouse_x
mouse_x = pyxel.mouse_y
mouse_y
# Detect mouse clicks
= pyxel.btn(pyxel.MOUSE_BUTTON_LEFT)
left_click = pyxel.btn(pyxel.MOUSE_BUTTON_RIGHT)
right_click = pyxel.btn(pyxel.MOUSE_BUTTON_MIDDLE)
middle_click
# For single clicks (not held down)
= pyxel.btnp(pyxel.MOUSE_BUTTON_LEFT) left_click_once
Let’s create a simple drawing application to demonstrate mouse input:
import pyxel
class MouseDemo:
def __init__(self):
160, 120, title="Mouse Input Demo")
pyxel.init(True) # Enable mouse cursor
pyxel.mouse(
self.canvas_color = 1 # Dark blue
self.drawing_color = 7 # White
self.drawing = False
self.update, self.draw)
pyxel.run(
def update(self):
if pyxel.btnp(pyxel.KEY_Q):
pyxel.quit()
# Start drawing when left mouse button is pressed
if pyxel.btn(pyxel.MOUSE_BUTTON_LEFT):
self.drawing = True
# Draw a pixel at the mouse position
self.drawing_color)
pyxel.pset(pyxel.mouse_x, pyxel.mouse_y, else:
self.drawing = False
# Change drawing color with right mouse button
if pyxel.btnp(pyxel.MOUSE_BUTTON_RIGHT):
self.drawing_color = (self.drawing_color + 1) % 16
def draw(self):
# Canvas has already been modified in update with pyxel.pset
# Draw UI
0, 0, 160, 120, 5) # Border
pyxel.rectb(
# Display instructions
5, 5, "Left click to draw", 7)
pyxel.text(5, 15, "Right click to change color", 7)
pyxel.text(5, 25, "Press Q to quit", 7)
pyxel.text(
# Show current color
130, 5, 15, 15, self.drawing_color)
pyxel.rect(130, 5, 15, 15, 7)
pyxel.rectb(
# Show mouse coordinates
5, 105, f"Mouse: ({pyxel.mouse_x}, {pyxel.mouse_y})", 7)
pyxel.text(
MouseDemo()
This creates a simple drawing application where you can draw with the left mouse button and change colors with the right mouse button.
46.4 Gamepad Input: The Console Experience
For a truly authentic retro gaming experience, Pyxel supports gamepads, including classic SNES controllers through adapters. The input functions work just like keyboard input!
46.4.2 Gamepad Constants
Pyxel provides constants for standard gamepad buttons:
- D-Pad:
pyxel.GAMEPAD1_BUTTON_DPAD_UP
,pyxel.GAMEPAD1_BUTTON_DPAD_DOWN
, etc. - Action buttons:
pyxel.GAMEPAD1_BUTTON_A
,pyxel.GAMEPAD1_BUTTON_B
, etc. - Shoulder buttons:
pyxel.GAMEPAD1_BUTTON_SHOULDER_L
,pyxel.GAMEPAD1_BUTTON_SHOULDER_R
- Menu buttons:
pyxel.GAMEPAD1_BUTTON_START
,pyxel.GAMEPAD1_BUTTON_SELECT
Pyxel supports up to 8 controllers by changing the number in the constant (e.g., GAMEPAD2_BUTTON_A
for the second controller).
46.4.3 Two-Player Example
Let’s create a simple two-player movement demo using both keyboard and gamepad inputs:
import pyxel
class TwoPlayerDemo:
def __init__(self):
160, 120, title="Two-Player Demo")
pyxel.init(
# Player 1 (keyboard)
self.p1_x = 40
self.p1_y = 60
self.p1_color = 8 # Red
# Player 2 (gamepad)
self.p2_x = 120
self.p2_y = 60
self.p2_color = 12 # Blue
self.update, self.draw)
pyxel.run(
def update(self):
if pyxel.btnp(pyxel.KEY_Q):
pyxel.quit()
# Update Player 1 (keyboard)
#
# Note that `min` and `max` compare a series of numbers. Their use
# here prevents the player from moving beyond the screen edge
if pyxel.btn(pyxel.KEY_LEFT):
self.p1_x = max(self.p1_x - 2, 0)
if pyxel.btn(pyxel.KEY_RIGHT):
self.p1_x = min(self.p1_x + 2, 160)
if pyxel.btn(pyxel.KEY_UP):
self.p1_y = max(self.p1_y - 2, 0)
if pyxel.btn(pyxel.KEY_DOWN):
self.p1_y = min(self.p1_y + 2, 120)
# Update Player 2 (gamepad)
if pyxel.btn(pyxel.GAMEPAD1_BUTTON_DPAD_LEFT):
self.p2_x = max(self.p2_x - 2, 0)
if pyxel.btn(pyxel.GAMEPAD1_BUTTON_DPAD_RIGHT):
self.p2_x = min(self.p2_x + 2, 160)
if pyxel.btn(pyxel.GAMEPAD1_BUTTON_DPAD_UP):
self.p2_y = max(self.p2_y - 2, 0)
if pyxel.btn(pyxel.GAMEPAD1_BUTTON_DPAD_DOWN):
self.p2_y = min(self.p2_y + 2, 120)
def draw(self):
1) # Clear screen with dark blue
pyxel.cls(
# Draw Player 1
self.p1_x - 4, self.p1_y - 4, 8, 8, self.p1_color)
pyxel.rect(self.p1_x - 2, self.p1_y - 2, "1", 7)
pyxel.text(
# Draw Player 2
self.p2_x - 4, self.p2_y - 4, 8, 8, self.p2_color)
pyxel.rect(self.p2_x - 2, self.p2_y - 2, "2", 7)
pyxel.text(
# Display instructions
5, 5, "Player 1: Arrow Keys", 8)
pyxel.text(85, 5, "Player 2: Gamepad", 12)
pyxel.text(5, 105, "Press Q to quit", 7)
pyxel.text(
TwoPlayerDemo()
This example allows two players to control separate characters - one using the keyboard and the other using a gamepad.
This example allows the player to use either keyboard or gamepad interchangeably, providing flexibility in control options.
46.5 Practice Time: Your Input Control Quest
Now it’s your turn to create a Pyxel application using different types of input. Try these challenges:
Create a program that displays different shapes based on which key is pressed (e.g., ‘C’ for circle, ‘R’ for rectangle)
Make a simple menu system that can be navigated with either keyboard arrow keys or mouse clicks
Create a drawing application that uses different colors based on which mouse button is pressed
Here’s a starting point for your quest:
import pyxel
class MyInputDemo:
def __init__(self):
160, 120, title="My Input Demo")
pyxel.init(True) # Enable mouse
pyxel.mouse(
# Initialize your variables here
self.update, self.draw)
pyxel.run(
def update(self):
if pyxel.btnp(pyxel.KEY_Q):
pyxel.quit()
# Handle various input methods
# Your code here
def draw(self):
1) # Clear screen with dark blue
pyxel.cls(
# Draw your visuals based on input
# Your code here
# Display instructions
5, 5, "Your instructions here", 7)
pyxel.text(
# Create and start your demo
MyInputDemo()
46.6 Common Bugs to Watch Out For
As you experiment with different input methods in Pyxel, watch out for these common issues:
Forgetting to Enable Mouse: If your mouse input isn’t working, make sure you’ve called
pyxel.mouse(True)
to enable the mouse cursor.Using
btnp()
for Movement: Usingbtnp()
for movement will result in jerky, step-by-step motion. Usebtn()
for continuous actions like movement.Input Conflicts: When allowing multiple input methods, be careful about conflicting controls. For example, if the up arrow controls a character but also navigates a menu, you might need to track game states.
Missing Input Frames: Pyxel runs at a fixed frame rate. If your game logic is complex, you might miss some input frames. Consider using input buffers for critical actions.
Boundary Checking: Always include boundary checks when moving objects based on input to prevent them from moving off-screen.
Gamepad Connectivity: If gamepad input isn’t working, make sure your controller is properly connected and recognized by your operating system before starting Pyxel.
Key Repeat Rates: Operating system key repeat settings may affect how
btn()
behaves for held keys. This is usually not a problem but something to be aware of.
46.7 Conclusion and Resources for Further Exploration
You’ve now learned how to capture and respond to keyboard, mouse, and gamepad input in your Pyxel games. These skills form the foundation of player interaction, allowing you to create responsive and engaging game experiences.
To further enhance your input handling skills, check out these resources:
Pyxel GitHub Documentation - Official documentation for all Pyxel functions, including detailed input handling.
Game Feel: A Game Designer’s Guide to Virtual Sensation - An excellent book on creating responsive controls in games.
Input Buffering in Games - A deeper exploration of advanced input techniques.
UI Design for Game Developers - Great resource for designing interfaces that respond to player input.
In our next lesson, we’ll explore animations and flipping sprites to bring even more life to your games. Keep practicing with different input methods – responsive controls are the key to creating games that feel satisfying to play!