Web Export Guide

Web Export Guide

Grid Building supports web export for all three demo types: top-down, platformer, and isometric. This guide covers what you need to know to get your project running in a browser.

Requirements

  • Godot 4.4+ with Web export template installed
  • GDScript builds only (C# / Mono does not support web export in Godot 4.x)
  • export_filter="all_resources" or explicit inclusion of rules and template directories

How Resource Loading Differs on Web

Resource deserialization in exported builds (including web) can behave differently from the editor. Fields that are initialized in _init() may still be null if they are not explicitly saved in the .tres file, because:

  1. Deserialization order@export variables are set after _init() runs (godot#70575).
  2. Typed array fragilityArray[ExtResource("...")]([...]) syntax in .tres files can deserialize as empty in exported builds (godot#97782, godot#72489).
  3. Missing subresources — nested resources not explicitly serialized in the .tres remain at engine defaults (null for Objects).

What this means for placement rules

CollisionsCheckRule has a messages field of type CollisionRuleSettings. In the editor, _init() creates this automatically:

1
2
3
func _init():
    if messages == null:
        messages = CollisionRuleSettings.new()

In exported builds, messages may still be null if the .tres does not explicitly serialize it. The rule now includes _ensure_messages() which lazy-loads a default CollisionRuleSettings whenever messages is accessed:

1
2
3
4
5
func _ensure_messages() -> void:
    if messages != null:
        return
    messages = CollisionRuleSettings.new()
    print("[CollisionsCheckRule] Lazy-loaded default CollisionRuleSettings ...")

This safeguard runs in validate_placement() and get_editor_issues(), so exports do not crash on null messages regardless of why it is null.

Best Practices for Web-Compatible Resources

Save base rules as standalone .tres files

Base placement_rules in GBSettings should reference external .tres files, not embedded SubResource(...) entries:

Good — external rule resource:

1
2
3
4
5
[ext_resource type="Resource" uid="uid://xxx" path="res://rules/my_collision_rule.tres" id="1_rule"]

[resource]
script = ExtResource("settings_script")
placement_rules = [ExtResource("1_rule")]

Avoid — embedded subresource:

1
2
3
4
5
6
[sub_resource type="Resource" id="Resource_abc"]
script = ExtResource("collisions_script")

[resource]
script = ExtResource("settings_script")
placement_rules = [SubResource("Resource_abc")]

Use plain arrays for placement_rules

Avoid the typed-array wrapper syntax in .tres files. It can silently break in exported builds.

Good:

1
placement_rules = [ExtResource("1_rule"), ExtResource("2_rule")]

Avoid:

1
placement_rules = Array[ExtResource("script")]([ExtResource("1_rule"), ExtResource("2_rule")])

See godot#97782 for the upstream bug report.

Externalize GBSettings from GBConfig

Avoid embedding GBSettings as a subresource inside GBConfig. Save it as its own .tres file:

1
2
3
4
5
[ext_resource type="Resource" path="res://config/my_settings.tres" id="1_settings"]

[resource]
script = ExtResource("config_script")
settings = ExtResource("1_settings")

Serialize nested resources explicitly

Save important nested resources directly in the .tres file rather than relying on _init() to create them:

  • CollisionsCheckRule.messages
  • CollisionsCheckRule.fail_visual_settings
  • Tile rule fail_visual_settings

Verify indicator scenes serialize collision flags

RuleCheckIndicator scenes should explicitly set:

1
2
3
[node name="RuleCheckIndicator" type="ShapeCast2D"]
collide_with_bodies = true
collide_with_areas = true

These defaults are safe in the editor but should be written explicitly so exported builds do not depend on editor-only defaults.

Collision Masks by Demo Type

Different demos use different physics layers. Do not copy collision masks between demos blindly:

DemoRule collision_maskRule apply_to_objects_mask
Top-down1 or project-specific1
Platformer1 or project-specific1
Isometric25602561

Verification

Automated tests

Run the web export compatibility test suite:

1
godot --headless --path . -s addons/gdUnit4/bin/GdUnitCmdTool.gd runtest -a res://addons/grid_building/test/placement/web_export

This validates:

  • .tres-loaded rules lazy-load messages on first use
  • validate_placement() does not crash when messages is null
  • Demo config chains load rules correctly

Pre-export checklist

  • Base rules in GBSettings are external .tres files
  • GBConfig.settings references an external GBSettings .tres
  • Placeable packed_scene references a real .tscn file
  • Collision rules serialize messages and fail_visual_settings
  • Indicator scenes serialize collide_with_bodies and collide_with_areas
  • placement_rules uses plain array syntax (no Array[ExtResource(...)](...))
  • Collision masks match your project’s physics layer setup
  • Export filter includes res://templates/ and any custom rules directories

Troubleshooting

Placement indicators do not appear

  1. Check browser console for Failed to load resource errors
  2. Verify placement_rules is non-empty by adding a temporary print in GBCompositionContainer.get_placement_rules()
  3. Ensure base rules are external .tres files, not embedded subresources
  4. Check that placement_rules does not use typed-array syntax (Array[ExtResource])

Placement always succeeds (no red tiles)

  1. Verify CollisionsCheckRule.collision_mask overlaps your target objects’ collision_layer
  2. Check that indicator scenes have collide_with_bodies = true
  3. For isometric projects, verify masks are 2560 / 2561, not top-down defaults

Rules load but validation is wrong

  1. Check messages is serialized in the rule .tres or that _ensure_messages() is being triggered
  2. Verify IndicatorFactory receives a valid logger (it falls back to a default GBLogger automatically)

References


Last updated: 2026-05-02