A behavior object carries a "Run in editor" switch next to Enabled. When it is on, the behavior ticks outside of Play and works on the saved model directly - Unity semantics: what it moves is a real change, autosaved and not undoable. Input stays with the editor, so On key never fires while typing. The editor run never bakes placed containers into the model, since that would silently drop their instanceEdits. syncToModel reports whether it wrote anything, and the dirty flag is only raised on change - otherwise a behavior moving something every frame would trigger ~60 saves a second.
93 lines
6.0 KiB
Markdown
93 lines
6.0 KiB
Markdown
ein toggle bei einem behavior object in einer scene (passender name fehlt mir noch):
|
||
- wenn der toggle aktiviert ist, soll das behavior außerhalb des play modes trotzdem laufen (sprich immer)
|
||
- das behavior ist natürlich nicht aktiv wenn die scene nicht geladen ist wo sich das behavior befindet (sprich genauso wie im play mode, nur ohne play mode)
|
||
|
||
---
|
||
|
||
**Umgesetzt (2026-09-09).** Der Schalter heisst **`Run in editor`** und steht im Inspector eines
|
||
Behavior-Objekts direkt unter `Enabled`. Ist er an, laeuft das Behavior weiter, waehrend das
|
||
Projekt *nicht* spielt (`src/editor/EditorRun.ts`, vom Editor neben der Play-Session getickt).
|
||
|
||
## Die eine Entscheidung, die der Schalter erzwingt
|
||
|
||
Play arbeitet auf einer **Wegwerf-Kopie** — deshalb ist Spielen fuer das gespeicherte Projekt
|
||
gefahrlos. Ausserhalb von Play gibt es diese Kopie nicht, und damit stand die Frage im Raum, was
|
||
ein laufendes Behavior ueberhaupt anfassen darf. Entschieden wurde (Rueckfrage an den User) fuer
|
||
die **Unity-Semantik** (`ExecuteInEditMode`):
|
||
|
||
> Was das Behavior bewegt oder schreibt, ist eine **echte Aenderung am gespeicherten Modell**.
|
||
> Sie wird autosaved, die Panels zeigen sie — und **Undo holt sie nicht zurueck**.
|
||
|
||
Das macht den Schalter maechtig (ein Behavior kann Inhalt bauen oder anordnen) und ist genau der
|
||
Grund, warum er **standardmaessig aus** ist. Der E2E-Check haelt beide Haelften fest: der Server
|
||
liefert die verschobene Position aus, und nach zwei Undos ist der Schalter weg, die Bewegung nicht.
|
||
|
||
## Was der Editor-Lauf bewusst *nicht* bekommt
|
||
|
||
| | |
|
||
|---|---|
|
||
| **Eingaben** | Tastatur und Maus bleiben beim Editor, sonst wuerde Tippen in einem Textfeld Behaviors ausloesen. `On key …`, `On click` und `Is key down` feuern hier nie; `On start`, `On update`, `On timer` und Kollisionen schon. |
|
||
| **Container-Inhalt** | `syncFromModel` schreibt den Inhalt platzierter Container als normale Objekte aus (*baking*) und loescht dabei deren `instanceEdits` — im Editor waere das eine stille, irreversible Aenderung. Der Lauf instanziiert deshalb mit `bakeContainers: false`. **Preis:** ein Behavior *innerhalb* eines platzierten Containers laeuft ausserhalb von Play nicht. |
|
||
| **ein zweites Leben** | Nur die Behaviors der **geladenen** Szene laufen, und nur im Edit-Modus. Play stoppt den Editor-Lauf — dort laeuft ohnehin jedes Behavior. Eine Exception im Graphen oder ein `Stop game`-Node beendet den Lauf und schreibt den Grund in die Notice-Zeile, statt ihn 60× pro Sekunde zu wiederholen. |
|
||
|
||
Der zweite Punkt der Notiz faellt damit von selbst heraus: die Welt wird aus der aktiven Szene
|
||
gebaut, ein Behavior einer anderen Szene hat gar keine Entity. Beim Szenenwechsel wird der Lauf
|
||
neu aufgebaut oder verworfen.
|
||
|
||
## Kein Save-Sturm
|
||
|
||
Ein Behavior, das jeden Frame etwas verschiebt, macht das Modell jeden Frame schmutzig — 60×
|
||
pro Sekunde speichern waere die naheliegende Katastrophe. Zwei Dinge verhindern das:
|
||
|
||
- `syncToModel()` meldet jetzt, **ob** ueberhaupt geschrieben wurde (`{structure, values}` statt
|
||
eines blossen `boolean`). Ein Behavior, das nur liest oder wartet, macht gar nichts schmutzig.
|
||
- `setSaveStatus('dirty')` emittiert nur bei **Wechsel**, der Autosave-Timer wird also nicht jeden
|
||
Frame neu gestellt. Gemessen: **2 Speicherungen in 2 Sekunden**, nicht ~120.
|
||
|
||
## Ein Darstellungsfehler, den erst der Screenshot gezeigt hat
|
||
|
||
`schemaForAsset` liefert fuer ein Behavior `COMMON_PROPS + Typschema + Variablen`. Der
|
||
Object-Inspector zeigte alles ausser `enabled` unter der Ueberschrift **VARIABLES** — der neue
|
||
Schalter landete also dort, wo nur steht, was das Behavior *deklariert*, und die Notiz „The
|
||
behavior declares no variables." verschwand fuer immer. Beides ist repariert: die Typschema-Zeilen
|
||
eines Behaviors stehen jetzt bei `Enabled`, die Sektion darunter fuehrt wieder nur die Variablen.
|
||
Aufgefallen ist es nur, weil ich mir den Screenshot angesehen habe — die Checks waren zufrieden.
|
||
|
||
## Ein Messgeraet statt eines geratenen Offsets
|
||
|
||
Der erste Anlauf klickte die Checkbox ueber `locate('Enabled').x + 55`. Das trifft nur Zeilen,
|
||
deren Label in die 64-px-Spalte passt — `Run in editor` ist breiter und schiebt seine Checkbox
|
||
11 px nach rechts, also ging der Klick daneben und fuenf Checks fielen um. Neu ist deshalb
|
||
`__gm.inspectorField(label)`: es liefert die Bildschirmmitte des **Controls** einer Inspector-
|
||
Zeile, gefunden ueber sein Label. Damit ist jeder kuenftige Inspector-Check unabhaengig von der
|
||
Labelbreite.
|
||
|
||
## Belegt
|
||
|
||
Acht Unit-Tests (`tests/editor/editor-run.test.ts`, plus einer in `container.test.ts` fuer das
|
||
Nicht-Backen). Gegengeprueft: dreht man den Filter auf „alle Behaviors" und die Szenen-Bedingung
|
||
weg, fallen **vier von sieben** um; mit `bakeContainers: true` faellt der Container-Test.
|
||
Dazu zehn E2E-Checks in Modul 17:
|
||
|
||
```
|
||
PASS a placed behavior does not run outside play mode until it is asked to
|
||
– {"running":false,"time":0,"behaviors":[]} x=0
|
||
PASS the inspector offers the switch on a behavior object, with Enabled and above the variables
|
||
– {"enabledBox":{"x":1070,"y":221},"runBox":{"x":1081,"y":253},"variables":{…,"y":509.5}}
|
||
PASS switching it on is an ordinary override on the object – {"runInEditor":true}
|
||
PASS the behavior runs although Play was never pressed
|
||
– {"running":true,"time":0.46,"behaviors":["er-logic"]} mode=edit
|
||
PASS the owner drifts on, and the moved object is the stored one – x 48.33 → 108.32
|
||
PASS what the behavior moved is saved, not just shown – stored x=308.31 (started at 0)
|
||
PASS saving stays debounced while it runs, instead of once per frame – 2 saves in 2 s
|
||
PASS taking the switch back stops the run and the object stands still – x=329.98
|
||
PASS undo takes back the switch, never the movement it caused – overrides={} x=351.65, started at 0
|
||
PASS the step leaves nothing of its own behind
|
||
```
|
||
|
||
Der Schritt baut sich seine eigene Kiste samt Behavior (`On update → Move(owner)`) statt den
|
||
Mover der Fixture zu nehmen — der laeuft ueber die Pfeiltasten, und die sieht der Editor-Lauf
|
||
absichtlich nie. Am Ende raeumt er alles wieder weg.
|
||
|
||
Screenshot `36-editor-run`.
|