C# · Language Design · Compiler Tooling

Loki

A custom dialogue and interaction language designed to keep branching narrative logic portable across game engines, programming languages, and platforms.

Loki banner

The Goal

Loki began as an experiment in separating game dialogue and interaction logic from any one engine. Rather than hard-code conversations directly into Unity, Unreal, or another runtime, I designed a small language and compiler that could turn human-readable .loki scripts into portable compiled data.

  • Keep narrative scripts independent from a specific game engine or programming language.
  • Support branching dialogue, variables, calls into game code, and cross-script references.
  • Catch syntax problems before scripts reach the game runtime.
  • Provide a command-line workflow for creating, building, and organizing Loki projects.

Language and Tokenization

The compiler starts by tokenizing each .loki source file. The tokenizer recognizes dialogue text, comments, global and local variables, class definitions, class calls, using statements, and reference calls. It also keeps line and token positions so errors can be reported against the original script.

  • Custom tokenizer written in C# with explicit parsing states.
  • Handles bracketed code without breaking dialogue into incorrect tokens.
  • Separates punctuation, delays, variables, definitions, and reference calls.
  • Supports both line comments and block comments.
  • Associates tokens with source line and token numbers for diagnostics.

Compiler Pipeline

A build verifies the project configuration, gathers the project's .loki files, skips any files marked as ignored, then runs each source through the tokenizer, scanner, and JSON serializer. The compiled project is written as a single .lid file inside bin/output.

  • Tokenizer converts source text into structured tokens.
  • Scanner validates and organizes those tokens.
  • JsonSerializer generates engine-agnostic compiled data.
  • The compiler combines individual script results into one project-level output file.
  • Build timing and logging make the compiler easier to debug and profile.

Command-Line Workflow

I built Loki as a CLI tool so it could fit into a normal source-controlled game pipeline instead of requiring a custom editor. The command system includes project creation, project builds, file ignoring, imports, testing, and uninstall support.

  • loki new <project name> creates a new Loki project configuration.
  • loki build compiles the current project, with an optional path argument.
  • loki ignore records source files the compiler should skip.
  • Project configuration is stored separately from source dialogue.
  • The tool was structured so additional packaging/import workflows could be added later.

Engine Integration

The language was designed around the idea that dialogue often needs to do more than display text. Loki scripts can represent references back into game code, allowing narrative content to trigger audio, visual, UI, or other game-specific functionality without making the language itself dependent on Unity or Unreal.

  • Reference-call syntax provides a bridge between compiled dialogue and host-engine functionality.
  • Global and local variables support stateful dialogue.
  • Cross-script organization keeps large narrative projects from becoming one monolithic file.
  • Compiled output is data rather than an engine-specific asset type.

What I Learned

  • Language design requires thinking about authoring ergonomics as much as compiler implementation.
  • Useful error messages depend on preserving source location information throughout the pipeline.
  • Separating authored source from compiled runtime data makes a tool easier to integrate across engines.
  • CLI tooling, project configuration, serialization, and logging are as important as the parser itself.