Skip to content

Multi-file Camera Scripts

So far we've treated a camera as a single .luau file. As a camera grows, it's often nicer to split it across several files - inputs in one place, math helpers in another, and so on. To do this, a camera can instead be a folder containing a package.json and as many .luau files as you like.

The game treats any folder in Behaviors that contains a package.json as a single camera. Everything else works exactly as before: the camera still shows up in the Cameras panel, still has a config, and still defines the same tick, onGui, and friends.

The built-in Free Camera is a good example of this layout:

Behaviors
└───aa.freecam.luau
    ├───package.json
    ├───main.luau
    ├───inputs.luau
    ├───util.luau
    └───emotes.luau

Note

The folder name is up to you, but using the same author.cameraname convention as single-file scripts helps avoid collisions.

The folder itself behaves just like a single script file would - drop it into your user camera folder (C:\Users\[USERNAME]\Documents\Another-Axiom\A2\Cameras\Behaviors) and the game will pick it up and hot-reload it on save.

The package.json

The package.json tells the game how to load your camera and carries some descriptive metadata about it. Here is the Free Camera's:

package.json
{
  "name": "anotheraxiom.freecam",
  "displayName": "Free Camera",
  "version": "0.0.1",
  "description": "A floating camera that can be manually controlled with keyboard and gamepad.",
  "main": "main.luau",
  "keywords": ["freecam", "controller", "keyboard"],
  "author": "Another Axiom",
  "defaultKeybind": "X"
}
Field Required Description
main Yes The entry-point file, relative to the package folder. This is the file the game loads and where it looks for tick, onGui, etc. The package won't load without it.
name Yes A unique identifier for the camera. Use a reverse-domain-style id like anotheraxiom.freecam to avoid colliding with other authors' cameras.
displayName No The human-friendly name shown in the Cameras panel.
description No A short summary of what the camera does.
version No A version string for your own tracking. Prefer semantic versioning (e.g., "1.0.0").
keywords No A list of tags describing the camera.
author No Who made the camera.
defaultKeybind No A key to bind the camera to by default (e.g. "X"). Leave it as "" for no default.
url No A link to the camera's homepage or source.

Splitting code across files

Inside the package, your entry file can pull in other files with require. Each required file is its own module: it builds up a table (or any other value) and returns it, and require hands that value back to the caller.

require takes a path relative to the file doing the requiring. Sibling files are referenced by name, and you can organize deeper layouts with subfolders.

--!strict

local Inputs = require("inputs")
local Util = require("util")
local Emotes = require("emotes")

function tick(dt: number)
  -- use the modules just like any other table
  Util.setSmoothing(true, Inputs)
end
--!strict

local Util = {}

function Util.setSmoothing(smoothing: boolean, Inputs)
  -- ...
end

return Util
--!strict

return {
  lookSmoothness = 0.7,
  rotEuler = Vec3.new(0, 0, 0),
}

The same globals you use in a single-file script (camera, config, Vec3, Gui, and so on) are available in every file of the package - you don't need to pass them around or re-import them.

Warning

require() acts like a copy of the module. Any changes to variables made in an imported script will not be reflected in a different script that requires the same module.

Subfolders

Paths are relative to the file calling require, so a file inside lib/ would reach a sibling with require("helpers"), not require("lib/helpers").

author.mycamera
├───package.json
├───main.luau
└───lib
    ├───math.luau
    └───shapes.luau
main.luau
local Math = require("lib/math")
local Shapes = require("lib/shapes")

Converting an existing script

Turning the single-file myname.mycamera.luau from Getting Started with Scripting into a package is straightforward:

  1. Make a new folder named myname.mycamera.luau in your Behaviors folder.
  2. Move your existing script into it and rename it main.luau.
  3. Add a package.json next to it:

    package.json
    {
      "name": "myname.mycamera",
      "displayName": "My Camera",
      "main": "main.luau"
    }
    
  4. Save. The camera will reload, and you can now start pulling helper code out of main.luau into sibling files as it grows.

Experiment

Move a self-contained piece of your camera - say a helper function or a table of constants - into its own file, return it, and require it back into main.luau.

What's next?

  • Review the Configuration page to see how config files pair with your camera.
  • Examine the built-in cameras for real multi-file examples - the aa.freecam package is a good place to start.