Coding conventions¶
These describe what the existing code in Assets/Scripts actually does. Match it.
Namespaces¶
| Namespace | Contents |
|---|---|
HoseBoy |
everything written for this game — Camera, Hose, UI |
Monologue.* |
the vendored dialogue framework |
HelloMarioFramework |
the vendored controller kit |
BuildingBlocks.DataTypes |
InspectableDictionary |
New game code goes in HoseBoy. A handful of files in Assets/Scripts/Utils sit in the global
namespace (CinemachineCameraManager, DontDestroyOnLoad and friends); that is a wart, not a
pattern to follow.
Naming¶
PascalCasefor types, methods, properties and events.camelCasefor private fields and locals. Nom_or_prefix on new code — the hose and camera scripts use barecamelCasefor privates, and that is the house style. (Monologueusesm_and_; leave it alone but do not copy it.)- Serialised private fields are still
camelCase; Unity's inspector already prettifies them. - Constants and static readonly shader IDs:
PascalCasewith anIdsuffix —private static readonly int FillId = Shader.PropertyToID("_Fill");
Component style¶
Private fields with [SerializeField], not public fields. Expose read-only state as
properties:
Group the inspector with [Header] and explain with [Tooltip]. Every serialised field
that a designer will touch gets a tooltip saying what the number means in units, not what the
field is called again:
[SerializeField, Tooltip("Recoil acceleration at full pressure, in metres per second squared.")]
private float acceleration = 22f;
Resolve references in Awake with a sensible fallback, so a component dropped on an object
mostly works before anyone opens the inspector:
Null-guard every serialised reference at the point of use. Components in this project are routinely half-wired; nothing should throw because a field is empty.
Use [RequireComponent] when a component genuinely cannot work without another, and
implement Reset() to set sane defaults (CameraZone sets its collider to trigger).
Execution order is explicit¶
Anything whose correctness depends on running before or after something else declares it:
[DefaultExecutionOrder(200)] // After Player.FixedUpdate writes velocity
public class HoseRecoil : MonoBehaviour
The attribute needs a comment saying what it is ordered against. The full ladder is in Hose system; add new entries there.
Comments¶
The existing code comments why, not what, and it is worth preserving:
// The canvas batches by material, so this gets its own copy to write into
instance = new Material(image.material);
Every non-obvious component has an XML <summary> explaining what it is for in one or two
sentences, and a <remarks> block when the execution order or a workaround needs justifying.
Write those for new components. Do not write comments that restate the line below them.
Flag known problems in place with // FIXME: or // TODO: and an explanation. Several already
exist and are documented on the dialogue system
page.
Performance habits already in the codebase¶
- Allocate once.
HoseWaterStreambuilds a fixed index buffer up front and collapses unused rings to zero radius rather than resizing the mesh per frame. MaterialPropertyBlockover material instances for renderers, so batching survives. The one exception isFluidMeterUI, which must instance its material because the canvas batches by material.- Cache
GetComponentresults.HoseWaterJetcaches itsIWaterTargetlookup per impact collider. - Cache
Shader.PropertyToIDinstatic readonly intfields. - No
GameObject.Find, noSendMessage, no per-frame LINQ.
Cross-system communication¶
Static C# events, not singleton method calls, where the two systems should not know about each
other. DialogueManager raises OnDialogueStartEvent; PlayerCameraDirector subscribes in
OnEnable and unsubscribes in OnDisable. Neither holds a reference to the other.
Always unsubscribe in OnDisable. Static events outlive scene loads and a missed
unsubscribe is a leak plus a null-target exception later.
Singletons exist (PlayerCameraDirector.Instance, DialogueManager.Instance,
Player.singleton) but are used sparingly, for genuinely single-instance directors.
Extension points over special cases¶
Prefer an interface anything can implement to a type check. IWaterTarget is the model: the jet
knows nothing about beetles, inlets or buttons, only that something on the impact collider might
want to hear about being hit.
Likewise, prefer data over code. FluidProfile turns "the hose now sprays lava" into a
ScriptableObject swap rather than a branch in the jet.
Formatting¶
Four-space indent, Allman braces, var only when the type is obvious from the right-hand side.
Match the file you are in.
What not to do¶
- Do not edit
Assets/HelloMarioFrameworkorAssets/Pluginsin place. Extend alongside. - Do not edit generated
.csprojfiles orhoseboy.slnx. - Do not add public fields to reach across components; add a property or an event.
- Do not use legacy
Input.*. The one place that does is a known problem. - Do not use 2D physics callbacks. This is a 3D project;
TriggerDialogueis the cautionary example. - Do not detect the player with
CompareTag("Player")— the player object is untagged. UseGetComponentInParent<HelloMarioFramework.Player>().