← All documentation

Building with Defold

Defold produces the smallest HTML5 builds of the three supported engines — an empty project is around 1MB — which leaves the most room for assets inside the 10MB budget. The trade-off is that Lua and JavaScript have to talk to each other — and that your game's rules are not Lua at all.

Read the trust model first. It explains why the rules below exist.


Where Defold fits

A game has two halves, and Defold is only ever the second:

Half Language Runs
The rules JavaScript (ES2015) On our server
The bundle Lua, in Defold In the player's browser

Our server runs rules in JavaScript — Nakama's runtime — so a Defold game's rules are a small JavaScript module, not Lua: for a pooled game a ServerGame (see determinism), for a head-to-head game a match handler (server rules). The deal, the spawns, every decision and the score live there. Your Defold project draws what the server sends and sends what the player does. It is never given the seed.

That changes two things that used to need care in Defold:

  • math.random may be used for anything in the Lua — particles, idle animation. Nothing the Lua does decides anything.
  • update(self, dt) may drive everything you draw. The game's clock is the server's; Defold only shows it. There is no simulation to put in fixed_update.

The one thing the Lua must not do is draw a board the server has not sent: animate from the last frame to the new one, never ahead of it.


Talking to the platform

Defold reaches JavaScript through html5.run(). The pattern is a small JS shim that owns the message protocol, with Lua polling it.

Add to your index.html template (Defold lets you customise this in game.project → html5 → htmlfile):

<script>
  window.C8 = { inbox: [] };

  window.addEventListener("message", function (event) {
    if (event.source !== parent) return;
    window.C8.inbox.push(event.data);
  });

  window.c8_poll = function () {
    var message = window.C8.inbox.shift();
    return message ? JSON.stringify(message) : "";
  };

  window.c8_send = function (json) {
    parent.postMessage(JSON.parse(json), "*");
  };

  // Say you are listening. The host holds the first frame until you do.
  parent.postMessage({ type: "ready", protocolVersion: 1 }, "*");
</script>

In Lua:

local function poll()
    local raw = html5.run("window.c8_poll()")
    if raw == "" then return nil end
    return json.decode(raw)
end

local function send(message)
    html5.run("window.c8_send(" .. json.encode(json.encode(message)) .. ")")
end

function update(self, dt)
    local message = poll()
    while message do
        if message.type == "match-state" then
            -- A frame: { tick, tickRate, over, state } — `state` is your rules' view.
            self.frame = message.view
            redraw(self, self.frame.state)
        elseif message.type == "match-complete" then
            show_final(self, message.score)     -- the server's count
        end
        message = poll()
    end
    animate_sprites(self, dt)
end

-- On a tap: send what the player did. The next frame shows what became of it.
local function on_tap(self, index)
    send({ type = "intent", action = "tap", payload = { value = index } })
end

There is no completion to report and no score to send: the server ends the run and counts it.


Building

Project → Bundle → HTML5 Application. Ship the output plus a manifest.json.

Strip unused engine components in game.project → app_manifest — Defold 1.12 and later excludes unused code from HTML5 builds automatically, which is worth a few hundred kilobytes.

Size

An empty Defold HTML5 project is roughly 1MB, and builds are WebAssembly-only from 1.13. That leaves about 9MB for assets. In practice audio is what overruns the budget: compress to Ogg at a modest bitrate before reducing texture quality.

Common mistakes

Symptom Cause
Certification rejects the rules Math.random() in the JavaScript rules
The game never becomes ready The JS shim is missing from your custom index.html
html5.run returns nothing Defold escapes strings — json.encode twice, as above
A tap shows, then jumps back The Lua drew the move before the server's frame
Physics decides something Defold's physics runs in the browser; anything that decides belongs in the rules