Hallways
Creating Hallways Levels
Creating a Hallways level is very straightforward: you need Blender, a text editor, and somewhere static to host a few files. A level is a folder of assets tied together by a JSON manifest.
At minimum, a level has:
- A render model, usually model.glb.
- A manifest JSON file.
- One manifest entry for each material used by the model.
Most levels will also have textures, a simplified collision mesh, portal meshes, and background music.
Blender
Build the visible space in Blender and export it as a .glb, usually named model.glb.
Only export the data Hallways needs:
- Positions.
- Material assignments.
- Diffuse UVs.
- Vertex colors, when you need them.
Do not embed texture image binaries in the .glb. Reference texture files from the manifest instead.
Collision
If your level has simple geometry, you can omit collider and Hallways will use model.glb for collision.
For detailed scenes, export a separate collision mesh such as collider.glb. This should be a simplified version of the level that keeps only the surfaces the player should collide with. It only needs positions.
Use a separate collider when visual detail would make movement snaggy, expensive, or too precise.
Manifest
The manifest tells Hallways which assets to load and how to interpret them. A level's canonical URL is the URL of its manifest.
{
"_version": "coco",
"model": "model.glb",
"collider": "collider.glb",
"spawn": [0, 1, 0],
"track": "track.ogg",
"materials": {
"Wall": {
"frames": ["wall.png"],
"animation_speed": 0.5,
"color": [255, 255, 255, 255],
"texture_addressing": "Linear"
}
},
"portals": {
"portal_a": {
"collider": "portal_a.glb",
"target": {
"href": "../other-level/level.json",
"name": "portal_b"
}
}
}
}
Manifest fields are strict. Unknown fields will fail to load.
Required Fields
| Field | Use |
|---|---|
| _version | Must be "coco". |
| model | Path to the visible .glb model. |
| materials | Map of glTF material names to Hallways material settings. Add one entry for each material used by the model. |
| portals | Map of portal names to portal definitions. It can be empty. |
Optional Fields
| Field | Use |
|---|---|
| collider | Path to a separate collision .glb. If omitted, model is used for collision. |
| spawn | Player spawn position as [x, y, z]. If omitted, the player starts at the origin. |
| track | Background music file. Use an Ogg container with Vorbis audio. |
Materials
Each key in materials must match a material name from the glTF model. For example, if a Blender material is named Wall, configure it under "Wall" in the manifest.
{
"frames": ["frame1.png", "frame2.png"],
"animation_speed": 1.0,
"color": [255, 255, 255, 255],
"texture_addressing": "Linear"
}
Material fields:
| Field | Use |
|---|---|
| frames | Texture frames for the material. Defaults to an empty list. |
| animation_speed | Animation speed for frame cycling. Defaults to 0.5. |
| color | RGBA tint as [r, g, b, a]. Defaults to white. |
| texture_addressing | Linear or Nearest. Defaults to Linear. |
The final diffuse color is texture color * material color * vertex color.
If no texture frames are specified, texture color is white. If material color is not specified, material color is white. If no vertex color is specified, vertex color is white.
The final diffuse alpha follows the same multiplication:
- Alpha 0.0 is discarded completely. Use this for cut-out holes and masks.
- Alpha 1.0 is rendered as opaque geometry.
- Alpha between 0.0 and 1.0 is rendered as transparent geometry.
Textures
Texture dimensions must match one of the supported square sizes.
| Size | Maximum textures per level |
|---|---|
| 2048x2048 | 1 |
| 1024x1024 | 4 |
| 512x512 | 8 |
| 256x256 | 32 |
| 128x128 | 64 |
| 64x64 | 256 |
Portals
Portals are separate .glb meshes referenced from the portals manifest map. Each portal entry has its own collider mesh:
{
"collider": "portal_a.glb",
"target": {
"href": "../other-level/level.json",
"name": "portal_b"
}
}target.href is a URL to another manifest. It can be absolute, such as https://example.com/level.json, or relative to the current level's manifest URL. target.name is the portal name in that destination manifest.
Portal rules:
- A level can contain up to 4 portals.
- The mesh must have at least 3 vertices.
- Portal geometry can be any coplanar polygon.
- Portals must be either wall-aligned or floor/ceiling-aligned.
- Wall portals only link to wall portals.
- Floor and ceiling portals only link to floor and ceiling portals.
- Linked floor and ceiling portals must have matching normals so gravity stays consistent and up/down does not invert.
- Linked portals should be the same size and shape. Hallways does not enforce this, but mismatched portals can cause collision issues.
- For floor and ceiling portals, Hallways uses the first indexed mesh vertex as an orientation anchor so it can rotate the player correctly when they pass through.
Hosting
Host the manifest and assets on any static file host. GitHub Pages is a good starting point.
Make sure the host serves the files directly and allows the browser to fetch them. Test by opening the manifest URL in a browser tab. If the JSON appears as text, Hallways should be able to request it too.
Keep paths relative where possible. This makes levels easier to move between hosts.