That applies to:
- your gameplay
Behaviourclasses - built-in scene components such as
Transform,Camera,MeshRenderer,ModelRenderer,CubeRenderer,SphereRenderer,CylinderRenderer,PlaneRenderer,SpriteRenderer,SpriteAnimation,Rigidbody2D,BoxCollider2D,CircleCollider2D,RectangleRenderer, andAudioSource - built-in PHP UI wrappers such as
Canvas,UIElement,RectTransform,Text,Image, andButton
That means the same attribute workflow can shape both your own Behaviours and the engine wrapper surface you expose to designers.
Basic Serialized Fields
Public properties are serialized by default:
<?php
declare(strict_types=1);
namespace Game\Scripts;
use Lenga\Engine\Core\Behaviour;
final class PlayerConfig extends Behaviour
{
public float $moveSpeed = 6.0;
public bool $canDash = true;
public string $startState = 'Idle';
}
Those show up as normal Inspector fields.
You can also expose a non-public field explicitly:
use Lenga\Engine\Attributes\SerializeField;
final class EnemyConfig extends Behaviour
{
#[SerializeField]
private float $aggressionRadius = 8.0;
}
And you can hide a field without removing it from the class:
use Lenga\Engine\Attributes\HideInInspector;
#[HideInInspector]
public string $debugState = '';
Pure List Arrays Render as Dropdowns
When a serialized array is a pure scalar list, the Inspector shows it as a select input instead of raw JSON.
Supported list values:
- strings
- integers
- floats
- booleans
Example:
final class DifficultySelector extends Behaviour
{
public array $difficultyOptions = ['Easy', 'Medium', 'Hard'];
}
In the Inspector, that renders as a dropdown.
One important note: the property still stays an array internally. Lenga treats the first item as the current selection, so choosing a different option moves that value to the front of the list instead of converting the property into a string.
That keeps PHP type hydration sound for array-typed fields.
Enum Fields Render as Dropdowns Too
Typed PHP enum fields also render as dropdowns in the Inspector.
That applies to:
- project-local enums in your own classes
- built-in engine enums such as
ForceMode2D,GamepadButton, andCanvasRenderMode
Example:
<?php
declare(strict_types=1);
namespace Game\Scripts;
use Lenga\Engine\Core\Behaviour;
enum Difficulty: string
{
case Easy = 'easy';
case Medium = 'medium';
case Hard = 'hard';
}
final class MatchRules extends Behaviour
{
public Difficulty $difficulty = Difficulty::Medium;
}
In the Inspector, that renders as a dropdown with Easy, Medium, and Hard.
Lenga stores the selected enum value as a simple scalar or case name in scene data, then hydrates it back into the correct PHP enum case before your Behaviour lifecycle runs. That means you still work with a real Difficulty value in code instead of a loose string.
Helpful Inspector Attributes
Lenga supports these Inspector-focused attributes:
HeaderHideInInspectorMinRangeSerializeFieldSerializeReferenceSpaceTextAreaTooltip
Class-level attributes such as RequireComponent also participate in the component workflow. If a Behaviour declares required native components, Lenga auto-adds them before the Behaviour lifecycle runs.
That means the full workflow is:
- Add
#[RequireComponent(...)]above yourBehaviourclass. - Attach that behaviour to a GameObject in the editor, or add it from code.
- Lenga checks the same GameObject for the required components.
- If any required component is missing, Lenga adds it before
awake()andstart()run.
The editor also uses the same metadata when the Behaviour is added from the component menu or when the Behaviour class is assigned directly from the Inspector.
SerializeReference for Nested Data
Use SerializeReference when a field should hold a nested object instead of a primitive value.
Example:
<?php
declare(strict_types=1);
namespace Game\Scripts;
use Lenga\Engine\Attributes\Header;
use Lenga\Engine\Attributes\Range;
use Lenga\Engine\Attributes\SerializeReference;
use Lenga\Engine\Core\Behaviour;
final class CameraFollowSettings
{
#[Range(0.01, 2.0)]
public float $smoothTime = 0.2;
#[Header('Look Ahead')]
public float $distance = 2.5;
public float $verticalOffset = 1.0;
}
final class CameraFollow extends Behaviour
{
#[SerializeReference]
public CameraFollowSettings $settings;
}
In the Inspector, settings now renders as a nested object editor instead of disappearing behind raw JSON.
Behavior:
- nested scalar fields render normally
- nested enum fields still render as dropdowns
- nested
GameObjectand component references still use scene pickers - nullable references can be created or cleared from the Inspector
- compatible concrete classes can now be picked from the Inspector when the declared type has known subclasses or known implementing classes
- non-null references fall back to their declared concrete type when no scene value has been saved yet
- stored data hydrates back into real PHP objects before lifecycle methods run
Limitations to Keep in Mind:
- this is still an early managed-reference slice rather than the finished reference workflow
- the type picker now understands class inheritance, direct interface implementations, and interface-to-interface inheritance when those relationships can be discovered from project PHP metadata
- unsupported nested complex values may still require raw JSON editing
Example:
<?php
declare(strict_types=1);
namespace Game\Scripts;
use Lenga\Engine\Attributes\Header;
use Lenga\Engine\Attributes\Range;
use Lenga\Engine\Attributes\Space;
use Lenga\Engine\Attributes\TextArea;
use Lenga\Engine\Attributes\Tooltip;
use Lenga\Engine\Core\Behaviour;
final class DialoguePrompt extends Behaviour
{
#[Header('Presentation')]
#[Tooltip('How quickly the prompt fades in.')]
#[Range(0.0, 2.0)]
public float $fadeSeconds = 0.35;
#[Space(10)]
#[Header('Copy')]
#[TextArea(3, 6)]
public string $message = 'Press Interact to continue.';
}
That will typically give you:
- a header before the field group
- a tooltip on hover
- a slider for
fadeSeconds - a multiline text area for
message
Attributes Also Work on Built-In Wrappers
If you add attributes to a built-in PHP wrapper property, the Inspector now respects them there as well.
For example:
use Lenga\Engine\Attributes\Range;
final class AudioSource extends Component
{
#[Range(0, 1)]
public float $volume { /* ... */ }
#[Range(-3, 3)]
public float $pitch { /* ... */ }
}
That will render the built-in Audio Source Inspector rows as sliders instead of plain drag fields.
The same metadata flow is also used for visibility, spacing, tooltips, and other supported field attributes on wrapper-backed component rows and built-in UI inspector panels such as Canvas, Text, and Button. Camera field-of-view sliders, cylinder slice limits, mesh/model path fields, audio volume, and sprite animation values can all follow the same authored metadata path.
A Few Practical Tips
- Use
Rangewhen you want designers to stay within a known authored band. - Use
Minwhen the value can grow freely but should never go below zero or another floor. - Use
Tooltipfor intent, not for repeating the label. - Use pure scalar lists when the field is really a fixed option set.
- Use
SerializeReferencefor authored nested data objects instead of pushing them into loose arrays or raw JSON. - Keep highly irregular nested structures for raw JSON until they have a cleaner authored shape.