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

FieldUse
_versionMust be "coco".
modelPath to the visible .glb model.
materialsMap of glTF material names to Hallways material settings. Add one entry for each material used by the model.
portalsMap of portal names to portal definitions. It can be empty.

Optional Fields

FieldUse
colliderPath to a separate collision .glb. If omitted, model is used for collision.
spawnPlayer spawn position as [x, y, z]. If omitted, the player starts at the origin.
trackBackground 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:

FieldUse
framesTexture frames for the material. Defaults to an empty list.
animation_speedAnimation speed for frame cycling. Defaults to 0.5.
colorRGBA tint as [r, g, b, a]. Defaults to white.
texture_addressingLinear 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.

SizeMaximum textures per level
2048x20481
1024x10244
512x5128
256x25632
128x12864
64x64256

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.