Input Bindings & Project Settings Guide
How to handle inputs in your s&box game.
## Project Settings Overview
All project-level configuration lives in the `ProjectSettings/` folder at the root of your s&box project. These are JSON files that s&box reads automatically — you never load them manually in code.
| File | Purpose |
|------|---------|
| `Input.config` | Defines all input actions and their default key/gamepad bindings |
| `Collision.config` | Collision layer definitions and which layers collide/trigger/ignore |
These files have metadata fields (`__guid`, `__schema`, `__type`, `__version`) that s&box uses internally. Don't remove them.
---
## Input System: How It Works
### The Pipeline
```
Input.config (defines actions)
↓
s&box engine reads config on startup
↓
Your code calls Input.Pressed("ActionName"), Input.Down("ActionName"), etc.
↓
Engine maps the action name → bound key → returns true/false
```
You define **actions** (logical names like "Attack1", "Jump"), bind them to **keys** in the config, then reference only the **action name** in code. Players can rebind keys in-game through s&box's built-in settings, and your code never changes.
### Input.config Structure
Located at: `ProjectSettings/Input.config`
```json
{
"Actions": [
{
"Name": "Forward",
"GroupName": "Movement",
"Title": null,
"KeyboardCode": "W",
"GamepadCode": "None"
},
{
"Name": "Attack1",
"GroupName": "Actions",
"Title": "Primary Attack",
"KeyboardCode": "mouse1",
"GamepadCode": "RightTrigger"
}
],
"__guid": "...",
"__schema": "configdata",
"__type": "InputSettings",
"__version": 1
}
```
#### Fields
| Field | Description |
|-------|-------------|
| `Name` | The action identifier you use in code. **Case-sensitive.** `Input.Pressed("Attack1")` matches `"Name": "Attack1"` |
| `GroupName` | Category for organization in the settings UI: `Movement`, `Actions`, `Inventory`, `Other` |
| `Title` | Human-readable label shown in settings. If `null`, the `Name` is used |
| `KeyboardCode` | Default keyboard key: `W`, `mouse1`, `space`, `shift`, `alt`, `ctrl`, `tab`, `enter`, `e`, `1`–`9`, etc. |
| `GamepadCode` | Gamepad binding: `A`, `B`, `X`, `Y`, `RightTrigger`, `LeftTrigger`, `DpadNorth/South/East/West`, `LeftJoystickButton`, `SwitchLeftBumper`, `SwitchLeftMenu`, `None`, etc. |
### Editing the Config
**Option A: Edit the JSON directly.** Open `ProjectSettings/Input.config` in any text editor. Add/remove/modify action entries in the `Actions` array. s&box picks up changes on next compile/run.
**Option B: Use the s&box editor.** Go to **Project → Input** in the editor menu. This gives you a visual editor for the same file.
Both produce identical results — the editor just writes to the same JSON file.
---
## Using Input in Code
### Core API Methods
```csharp
// Was the key JUST pressed this frame? (fires once)
if ( Input.Pressed( "Attack1" ) ) { /* cast spell */ }
// Is the key currently held down? (fires every frame while held)
if ( Input.Down( "Use" ) ) { /* hold-E progress */ }
// Was the key JUST released this frame? (fires once)
if ( Input.Released( "Attack2" ) ) { /* cancel charge */ }
```
### Analog Input (Not in Input.config)
These are built-in and always available:
```csharp
// Movement vector from WASD (automatically uses Forward/Backward/Left/Right actions)
Vector3 move = Input.AnalogMove; // x = forward/back, y = left/right
// Mouse look delta
Angles look = Input.AnalogLook; // pitch + yaw
// Mouse wheel
float scroll = Input.MouseWheel.y; // positive = up, negative = down
// Raw mouse position (screen space)
Vector2 mousePos = Mouse.Position;
```
### Direct Keyboard Access (Bypasses Input.config)
For keys that aren't input actions (menus, debug, shortcuts):
```csharp
// Check a raw key by name — NOT an action, the actual key
if ( Sandbox.Input.Keyboard.Pressed( "Escape" ) ) { /* close menu */ }
if ( Sandbox.Input.Keyboard.Pressed( "M" ) ) { /* toggle music */ }
```
Use this sparingly. Prefer actions in Input.config so players can rebind.
---
## Best Practices
### 1. Always Use Actions, Not Raw Keys
**Bad:**
```csharp
if ( Sandbox.Input.Keyboard.Pressed( "E" ) ) { /* interact */ }
```
**Good:**
```csharp
if ( Input.Pressed( "Use" ) ) { /* interact */ }
```
Actions let players rebind. Raw keys don't. Only use `Sandbox.Input.Keyboard` for keys that genuinely shouldn't be rebindable (Escape for closing menus).
### 2. Wrap Direct Keyboard Calls in Try-Catch
Direct keyboard access can throw if the input system isn't ready:
```csharp
public static bool EscPressed()
{
try { return Sandbox.Input.Keyboard.Pressed( "Escape" ); }
catch { return false; }
}
```
### 3. Block Input During UI/Chat
When a text input or menu is open, game input should be ignored. You'll need to implement this yourself — track whether a menu or chat is open with a bool, then check it before processing input:
```csharp
// Example — your own flags, not built-in
private bool _isChatOpen;
private bool _isMenuOpen;
protected override void OnUpdate()
{
if ( _isChatOpen || _isMenuOpen ) return;
// Now safe to process game input
if ( Input.Pressed( "Attack1" ) ) { ... }
}
```
### 4. Input Runs Client-Side Only
All `Input.*` calls return meaningful values only on the local client. On proxies (`IsProxy == true`), input is meaningless. Guard gameplay input behind:
```csharp
protected override void OnUpdate()
{
// Visuals before this — they run for everyone
UpdateAnimations();
if ( IsProxy ) return;
// Gameplay input after — only runs for the local player
if ( Input.Pressed( "Attack1" ) ) CastSpell();
}
```
### 5. Use GroupName for Organization
Group related actions so the settings UI is clean:
| GroupName | For |
|-----------|-----|
| `Movement` | WASD, jump, sprint, crouch |
| `Actions` | Attack, interact, reload |
| `Inventory` | Slot keys, scroll prev/next |
| `Other` | Chat, voice, scoreboard, menus |
### 6. Set Meaningful Titles
If your action name is cryptic, set `Title` for the settings UI:
```json
{
"Name": "Score",
"Title": "Scoreboard",
"KeyboardCode": "tab"
}
```
Players see "Scoreboard" in settings instead of "Score".
---
## Adding a New Input Action
### Step 1: Add to Input.config
Open `ProjectSettings/Input.config` and add an entry:
```json
{
"Name": "Sprint",
"GroupName": "Movement",
"Title": "Sprint",
"KeyboardCode": "shift",
"GamepadCode": "LeftJoystickButton"
}
```
### Step 2: Use in Code
```csharp
if ( Input.Down( "Sprint" ) )
{
_moveSpeed = SprintSpeed;
}
```
That's it. No registration, no initialization, no enum. The string name is the link between config and code.
---
## Available KeyboardCode Values
### Letters & Numbers
`A`–`Z`, `0`–`9`
### Mouse
`mouse1` (left), `mouse2` (right), `mouse3` (middle), `mouse4`, `mouse5`
### Modifiers
`shift`, `ctrl`, `alt`, `capslock`
### Special Keys
`space`, `tab`, `enter`, `escape`, `backspace`, `delete`, `insert`
### Arrow Keys
`uparrow`, `downarrow`, `leftarrow`, `rightarrow`
### Function Keys
`f1`–`f12`
### Punctuation
`semicolon`, `comma`, `period`, `slash`, `backslash`, `minus`, `equal`, `leftbracket`, `rightbracket`, `backquote`
---
## Available GamepadCode Values
| Code | Button |
|------|--------|
| `A`, `B`, `X`, `Y` | Face buttons |
| `RightTrigger`, `LeftTrigger` | Triggers (analog) |
| `SwitchRightBumper`, `SwitchLeftBumper` | Bumpers/shoulders |
| `DpadNorth`, `DpadSouth`, `DpadEast`, `DpadWest` | D-pad |
| `LeftJoystickButton`, `RightJoystickButton` | Stick clicks |
| `SwitchLeftMenu`, `SwitchRightMenu` | Start/Select/Menu |
| `None` | No gamepad binding |
---
## Collision.config
Located at: `ProjectSettings/Collision.config`
Defines collision layers and how they interact:
```json
{
"Version": 2,
"Defaults": {
"solid": "Collide",
"world": "Collide",
"trigger": "Trigger",
"ladder": "Ignore",
"water": "Trigger"
},
"Pairs": [
{ "a": "solid", "b": "solid", "r": "Collide" },
{ "a": "trigger", "b": "solid", "r": "Trigger" },
{ "a": "trigger", "b": "playerclip", "r": "Ignore" },
{ "a": "playerclip", "b": "solid", "r": "Collide" }
]
}
```
- **Defaults**: Default interaction for each layer (Collide, Trigger, Ignore)
- **Pairs**: Override rules between specific layer pairs
- **Interaction types**: `Collide` (physics collision), `Trigger` (overlap events, no physics), `Ignore` (pass through)
Tags on GameObjects (like `"solid"`, `"trigger"`, `"playerclip"`) map to these layers. Used in traces too:
```csharp
Scene.Trace.Ray( from, to ).WithTag( "solid" ).Run();
Scene.Trace.Ray( from, to ).WithoutTags( "trigger", "water" ).Run();
```
---
## Default s&box Input Actions
These are the actions that come with a new s&box project's `Input.config`:
| Action | Key | Gamepad | Typical Use |
|--------|-----|---------|-------------|
| Forward/Back/Left/Right | WASD | — | Movement |
| Jump | Space | A | Jumping |
| Run | Shift | L-Stick Click | Sprinting |
| Walk | Alt | — | Slow movement / free camera |
| Duck | Ctrl | B | Crouching |
| Attack1 | Mouse1 | R-Trigger | Primary action (shoot, interact) |
| Attack2 | Mouse2 | L-Trigger | Secondary action (aim, alt-fire) |
| Use | E | Y | Interact with objects |
| Reload | R | X | Reload weapon |
| Slot 0–9 | 0–9 | DPad (1-3) | Quick-select inventory slots |
| SlotPrev / SlotNext | Mouse4/5 | Bumpers | Cycle slots |
| View | C | R-Stick Click | Toggle camera view |
| Voice | V | — | Push-to-talk |
| Drop | G | — | Drop item |
| Flashlight | F | DPad-N | Toggle flashlight |
| Score | Tab | L-Menu | Scoreboard |
| Menu | Q | R-Menu | Open menu |
| Chat | Enter | — | Open text chat |
| Inventory | I | — | Open inventory |
You can remove, rename, or add to these freely — they're just defaults to get you started.