Nova64 now has full support for video game controllers (gamepads) with automatic detection and mapping to the standard button layout.
✅ Automatic Detection: Gamepads are automatically detected when connected
✅ Standard Mapping: Uses the standard gamepad button layout (Xbox/PlayStation compatible)
✅ Analog Sticks: Full support for both left and right analog sticks with deadzone
✅ Button States: Both held (btn) and pressed (btnp) states supported
✅ Dual Input: Keyboard and gamepad work simultaneously - use either or both!
btn(0) - D-pad Up / Arrow Up
btn(1) - D-pad Left / Arrow Left
btn(2) - D-pad Right / Arrow Right
btn(3) - D-pad Down / Arrow Down
btn(4) - X button / Z key
btn(5) - Y button / X key
btn(6) - LB button / C key
btn(7) - RB button / V key
btn(8) - LT button / A key
btn(9) - RT button / S key
btn(12) - Start / Enter
btn(13) - Select / Space
Access analog stick values directly:
// Left analog stick
const leftX = leftStickX(); // -1.0 to 1.0 (left to right)
const leftY = leftStickY(); // -1.0 to 1.0 (up to down)
// Right analog stick
const rightX = rightStickX(); // -1.0 to 1.0 (left to right)
const rightY = rightStickY(); // -1.0 to 1.0 (up to down)
// Or use the gamepadAxis function
const lx = gamepadAxis('leftX');
const ly = gamepadAxis('leftY');
const rx = gamepadAxis('rightX');
const ry = gamepadAxis('rightY');
Note: A deadzone of 0.15 is applied automatically to prevent stick drift.
// Check if button is currently held down
if (btn(0)) {
// D-pad up is being held (keyboard or gamepad)
}
// Check if button was just pressed this frame
if (btnp(4)) {
// X button was just pressed (not held)
shoot();
}
// Left stick for movement
export function update(dt) {
const moveX = leftStickX();
const moveY = leftStickY();
if (Math.abs(moveX) > 0 || Math.abs(moveY) > 0) {
player.x += moveX * speed * dt;
player.y += moveY * speed * dt;
}
}
// Right stick for camera control
export function update(dt) {
const camX = rightStickX();
const camY = rightStickY();
camera.rotation += camX * sensitivity * dt;
camera.pitch += camY * sensitivity * dt;
}
// Check if a gamepad is connected
if (gamepadConnected()) {
print('Gamepad Ready!', 10, 10, rgba8(0, 255, 0, 255));
}
export function update(dt) {
// Movement with D-pad or left stick
if (btn(0) || leftStickY() < -0.3) {
player.y -= speed * dt; // Move up
}
if (btn(3) || leftStickY() > 0.3) {
player.y += speed * dt; // Move down
}
if (btn(1) || leftStickX() < -0.3) {
player.x -= speed * dt; // Move left
}
if (btn(2) || leftStickX() > 0.3) {
player.x += speed * dt; // Move right
}
// Action buttons
if (btnp(4)) {
jump();
}
if (btnp(5)) {
attack();
}
}
export function update(dt) {
// Use left stick for steering
const steer = leftStickX();
car.angle += steer * turnSpeed * dt;
// RT for accelerate, LT for brake
if (btn(9)) {
// RT
car.speed += acceleration * dt;
}
if (btn(8)) {
// LT
car.speed -= braking * dt;
}
// A button for boost
if (btnp(4)) {
activateBoost();
}
}
let menuIndex = 0;
const menuItems = ['Start Game', 'Options', 'Quit'];
export function update(dt) {
// Navigate menu with D-pad or left stick
if (btnp(3) || leftStickY() > 0.7) {
menuIndex = (menuIndex + 1) % menuItems.length;
}
if (btnp(0) || leftStickY() < -0.7) {
menuIndex = (menuIndex - 1 + menuItems.length) % menuItems.length;
}
// Select with A button or Start
if (btnp(4) || btnp(12)) {
selectMenuItem(menuIndex);
}
}
export function update(dt) {
// Left stick for player movement
const moveX = leftStickX();
const moveZ = leftStickY();
player.x += moveX * speed * dt;
player.z += moveZ * speed * dt;
// Right stick for camera rotation
const camX = rightStickX();
const camY = rightStickY();
camera.yaw += camX * sensitivity * dt;
camera.pitch += camY * sensitivity * dt;
}
Always support both keyboard and gamepad for maximum accessibility:
// Good - supports both
const leftPressed = btn(1) || leftStickX() < -0.3;
const rightPressed = btn(2) || leftStickX() > 0.3;
Use btnp() for discrete actions like jumping, shooting, or menu selection:
if (btnp(4)) {
jump(); // Only triggers once per press
}
Use btn() for continuous actions like movement or aiming:
if (btn(9)) {
accelerate(); // Continuously accelerates while held
}
The default deadzone is 0.15. For analog movement, you may want to apply additional thresholds:
const moveX = leftStickX();
const moveY = leftStickY();
// Apply custom threshold
const threshold = 0.2;
if (Math.abs(moveX) < threshold) moveX = 0;
if (Math.abs(moveY) < threshold) moveY = 0;
Display appropriate button prompts based on input method:
export function draw() {
if (gamepadConnected()) {
print('Press A to Jump', 10, 10, rgba8(255, 255, 255, 255));
} else {
print('Press Z to Jump', 10, 10, rgba8(255, 255, 255, 255));
}
}
Different gamepads may have slightly different button mappings. Test with:
console.log(btn(0), btn(1), btn(2))If you experience stick drift:
To add gamepad support to existing games:
No changes needed! All existing btn() and btnp() calls now work with gamepads automatically
Optional: Add analog stick support for smoother movement:
// Before (keyboard only)
if (btn(1)) player.x -= speed * dt;
if (btn(2)) player.x += speed * dt;
// After (keyboard + analog stick)
const moveX = leftStickX();
if (btn(1) || moveX < -0.3) player.x -= speed * dt;
if (btn(2) || moveX > 0.3) player.x += speed * dt;
// Even better (smooth analog movement)
player.x += leftStickX() * speed * dt;
if (gamepadConnected()) {
print('🎮', 600, 10, rgba8(0, 255, 0, 255));
}
Planned features:
Gamepad support in Nova64 makes your games more accessible and enjoyable. Players can use whatever input method they prefer, and analog sticks provide smoother, more precise control for 3D games.
For questions or issues, please refer to the Nova64 documentation or submit an issue on GitHub.