Build your own easing curve and use it on keys and Lerp (#247)
The built-in eases cover the usual shapes, but a game often wants its own: a coin that bounces twice, a door that overshoots and settles. A new asset type holds such a curve, and keys and the Lerp node can pick it instead of a built-in ease. - New asset type "curve": points like in Vector art, x is t and y is the value. A new curve is the straight line from (0, 0) to (1, 1). - One pure evaluation in src/core/model/curve.ts serves the timeline preview, the running game and the Lerp node. - The curve always has one value per t. The end points stay at t 0 and 1, points cannot pass each other, and a handle that reaches past its neighbour is shortened along its direction. Loading a hand-edited file applies the same rules. - Curve tab: click on the line to insert a point that keeps the shape, click on free space to put one there, drag points and handles, double-click or Delete removes, Alt-click toggles round and sharp. Every step is one undo step. - Presets: all 30 built-in eases plus Linear, fitted with adaptive subdivision. Each stays within 0.4 % of the built-in ease. - Preview: a dot runs along the curve once a second, a second dot shows only the value. The Preview button stops it. - Animation keys get "Curve" under Easing with a curve picker. Lerp gets "Curve" under Ease, and its result may overshoot b. - A missing or unpicked curve runs linear. The inspector says so, the node shows it as a problem, and deleting a used curve warns with the number of keys and Lerp nodes. Tests: - ./check.sh: 180 files, 2612 tests, CHECK EXIT 0 - GM_STEPS=1-6,25,53: ALL PASSED, 309 checks - New e2e module 53 with 13 checks, all with the real mouse. Expected values come from its own Bezier evaluation. - Counter-checks bite: without Bezier evaluation 4 unit tests fail, without handle clamping 2. In the browser, a key that cannot find its curve, a click that sets no point and a handle drag that moves the point all turn checks red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
320d3e27e1
commit
393b8ec7f9
41 files changed
+1981
-67
No files matched your search
+11
-1
@@ -70,6 +70,7 @@ import type { EditorState } from './state';
|
||||
import { copySelection } from './clipboard';
|
||||
import { PixelArtEditor } from './pixelart/PixelArtEditor';
|
||||
import { VectorArtEditor } from './vectorart/VectorArtEditor';
|
||||
import { CurveEditor } from './curve/CurveEditor';
|
||||
import { TextureEditor } from './texture/TextureEditor';
|
||||
import { installFileImport } from './fileImport';
|
||||
import type { PluginManager } from './plugins/loader';
|
||||
@@ -88,6 +89,7 @@ export type CentreTab =
|
||||
| 'vectorart'
|
||||
| 'pixelart'
|
||||
| 'animation'
|
||||
| 'curve'
|
||||
| 'tile'
|
||||
| 'container'
|
||||
| 'list'
|
||||
@@ -134,6 +136,7 @@ const PANE_ORDER = [
|
||||
'vectorart',
|
||||
'pixelart',
|
||||
'animation',
|
||||
'curve',
|
||||
'tile',
|
||||
'container',
|
||||
'list',
|
||||
@@ -152,6 +155,7 @@ const TAB_LABELS: Record<CentreTab, string> = {
|
||||
vectorart: 'Vector art',
|
||||
pixelart: 'Pixel art',
|
||||
animation: 'Animation',
|
||||
curve: 'Curve',
|
||||
tile: 'Tile',
|
||||
container: 'Container',
|
||||
list: 'List',
|
||||
@@ -197,6 +201,7 @@ export class Editor implements PlayHost {
|
||||
readonly vectorArtEditor: VectorArtEditor;
|
||||
readonly pixelArtEditor: PixelArtEditor;
|
||||
readonly animationEditor: AnimationEditor;
|
||||
readonly curveEditor: CurveEditor;
|
||||
readonly tileEditor: TileEditor;
|
||||
readonly containerEditor: ContainerEditor;
|
||||
readonly listEditor: ListEditor;
|
||||
@@ -276,6 +281,7 @@ export class Editor implements PlayHost {
|
||||
this.vectorArtEditor = new VectorArtEditor(state);
|
||||
this.pixelArtEditor = new PixelArtEditor(state);
|
||||
this.animationEditor = new AnimationEditor(state);
|
||||
this.curveEditor = new CurveEditor(state);
|
||||
this.tileEditor = new TileEditor(state);
|
||||
this.containerEditor = new ContainerEditor(state);
|
||||
this.listEditor = new ListEditor(state);
|
||||
@@ -380,6 +386,7 @@ export class Editor implements PlayHost {
|
||||
vectorart: this.vectorArtEditor,
|
||||
pixelart: this.pixelArtEditor,
|
||||
animation: this.animationEditor,
|
||||
curve: this.curveEditor,
|
||||
tile: this.tileEditor,
|
||||
container: this.containerEditor,
|
||||
};
|
||||
@@ -814,6 +821,8 @@ export class Editor implements PlayHost {
|
||||
this.console.refresh();
|
||||
if (this.tab === 'animation') this.animationEditor.tick(step);
|
||||
if (this.tab === 'tile') this.tileEditor.tick(step);
|
||||
// The preview is the editor's own, so it runs on while the game is paused.
|
||||
if (this.tab === 'curve') this.curveEditor.tick(dt);
|
||||
// Play repaints the machine tab itself (see `PlaySession.tick`); an editor run has no session
|
||||
// to do it, and its states move just the same.
|
||||
if (this.tab === 'statemachine' && this.editorRun.isRunning) this.stateMachineEditor.canvas.redraw();
|
||||
@@ -837,7 +846,8 @@ export class Editor implements PlayHost {
|
||||
this.tab === 'pixelart' ||
|
||||
(this.tab === 'shader' && this.shaderEditor.animates) ||
|
||||
(this.tab === 'animation' && this.animationEditor.isPlaying) ||
|
||||
(this.tab === 'tile' && this.tileEditor.isPlaying)
|
||||
(this.tab === 'tile' && this.tileEditor.isPlaying) ||
|
||||
(this.tab === 'curve' && this.curveEditor.isPlaying)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user