6.9 KiB
Adding content — quick checklist
Rough working notes. Verify against the scripts if anything looks off; file references are the source of truth.
1. Adding a new weapon
The pipeline is def-driven: AmmoDef (what it fires) → WeaponDef (the gun)
→ weapon scene (the visible mount). Scenes are shared — mark12.tscn is used
by both mark12.tres and bofors.tres. Only build a new scene for a new look.
- AmmoDef — new
.tresinresources/ammo/(script:scripts/resource_definitions/ammo_def.gd). Fields:projectile_scene(usuallyshell.tscn),damage,muzzle_speed,gravity_scale,lifetime,display_name. - WeaponDef — new
.tresinresources/weapons/(script:scripts/resource_definitions/weapon_def.gd). Fields:weapon_scene(required —WeaponSlot.can_mount()rejects a def without one; reusemark12.tscnunless you made a new scene),ammo,fire_interval,muzzle_speed_scale(barrel-length multiplier on ammo speed),size/type(slot gating), traverse/elevation speeds and elevation limits. - Weapon scene (only if it needs a new look) — structure per
mark12.tscn/scripts/weapon.gd:- Root
Node3Dwithscripts/weapon.gd. %Yaw(Node3D, unique name) — rotates horizontally. Optional: omit it and the weapon is a fixed mount (no aiming, fires along the muzzle).%Pitch(Node3D, unique name) under Yaw — barrel elevation. Also optional (yaw-only mount if omitted).%Muzzle(Marker3D, unique name) at the barrel tip, -Z pointing out of the barrel. Required —weapon.gdhard-references it.%Hitbox(AnimatableBody3D, unique name) under Yaw, with a CollisionShape3D. Sync To Physics must be UNTICKED — with it on the collider does not follow the moving boat. Collision layer = subsystems (7) only, collision mask = empty. Projectiles get a collision exception with the shooter's hitboxes at fire time.- Leave the root's
defexport EMPTY. The mounting WeaponSlot assigns it (weapon_slot.gdmount()). A scene referencing its own def is infinite recursion (def → scene → def …).
- Root
- Put it on a boat — add an entry to the Boat's
loadoutdictionary (scripts/boat.gd): key = the WeaponSlot node's exact name (StringName), value = the WeaponDef.Boat._ready()mounts everything. The slot'sallowed_sizes/allowed_typesmust contain the def'ssize/typeormount()warns and refuses.
New projectile scenes: root must use scripts/projectile.gd (or replicate
it) — it self-registers into shift_with_origin and calls
reset_physics_interpolation() on launch. Copy shell.tscn's physics setup:
layer 4 (projectiles), mask = terrain+player+enemies, continuous_cd on,
contact_monitor on with max_contacts_reported ≥ 1.
2. Setting up a boat scene
Template: boat.tscn.
- Root:
RigidBody3Dwithscripts/boat.gd(extendsscripts/floating_body.gd).- Group:
shift_with_origin(set in the scene). Theboatsgroup is joined automatically inBoat._ready()— don't add it by hand. - Collision layer: player (2) or enemies (3).
- Set
mass— all handling forces scale with it, so handling stays the same across masses; mass mostly matters for collisions.
- Group:
- Children:
MeshInstance3D+CollisionShape3D. Never scale physics nodes — set sizes on the mesh/shape resources themselves. - Buoyancy tuning (exports from
floating_body.gd):buoyancy(multiple of gravity when fully submerged; >1 floats),full_force_depth,water_drag,water_angular_drag,probe_damping.- Probe placement:
probe_extents(half-width, height, half-length) +probe_grid(columns × rows). A grid axis of 1 is valid (centerline). - Any Marker3D direct children override the generated grid and become the probes — also means: don't park unrelated Marker3Ds directly under the boat root.
- Handling exports on
boat.gd:engine_power,reverse_ratio,throttle_response,rudder_strength,keel_grip. - Weapon mounts: child
Node3Ds withscripts/weapon_slot.gd, positioned and rotated as the mount points (e.g. stern mount rotated 180°). Setallowed_sizes/allowed_typesper slot (defaults: MEDIUM, BALLISTIC). loadoutdict on the root: slot node name → WeaponDef (see section 1). Keys must match the node names exactly.weapon_groups: group id → array of slot names. Group 0 is auto-built in_ready()(all slots) — any editor value for it is ignored.
3. Other things to keep in mind
- Tabs, never spaces, for all GDScript.
@export= static configuration only. Runtime-controlled references are plainvarwith a# Controlled by PlayerControllerstyle comment; internals use_prefix.- Collision layers: 1 terrain, 2 player, 3 enemies, 4 projectiles,
5 pickups, 6 water, 7 subsystems (see
project.godot[layer_names]). Water is query-only — surface/contact logic goes throughOcean.get_wave_height(), never a collider (that's howfloating_body.gddoes buoyancy). - Wave math is dual-implemented:
shaders/ocean.gdshader(rendering) andscripts/ocean.gd(physics). Any wave change goes in BOTH. Neverset_shader_parameteron the water material from gameplay code — use Ocean's properties. - Origin shifting is active. Free-moving objects (projectiles, debris,
anything not parented to a shifted node) must join
shift_with_origin— preferadd_to_group()in_ready(). Never cache world positions across frames; for true world-space positions useWorldManager.true_position(). - Physics interpolation is ON. Every deliberate teleport needs a
reset_physics_interpolation()chaser or it renders as a smear. - Boats never read
Input. Controllers write intents:desired_throttle,steer_input,fire_intents,aim_point. - Inspector-stored-value trap: once a value is edited in a scene it's
stored in the
.tscnand overrides any later change to the script's default. Changed a default and it "doesn't work"? Check the scene file. - WeaponSlots are static scene structure — never added/removed at
runtime. The build-once caches in
Boat._ready()depend on this. - Scripts aren't
@tool, so export setters don't run in the editor — never trust an exported value to have been validated at edit time.
Notes / discrepancies found while verifying (2026-07): %Muzzle is a hard
requirement of weapon.gd (plain @onready %Muzzle), unlike %Yaw /
%Pitch / %Hitbox which use get_node_or_null. A weapon scene isn't truly
optional for a def — can_mount() requires weapon_scene != null, so "make
a new scene" is what's optional; the def always points at one. CLAUDE.md's
layer table lists only 1–5; project.godot also names 6 water and
7 subsystems (the Hitbox layer value 64 in mark12.tscn = layer 7).