Scenes were always intended to be an adapter. A modular piece that gets plugged into a simpler system.
At first, they were integrated into a monolith. Then, they were split out as we
narrowed into handleEvent. Then, they were integrated back into the engine
with the stack system. Finally, they split out again into an adapter.
That's where we are now.
Before:
const like = createLike(document.body);
like.start();
like.pushScene(blah);
After:
const like = createLike(document.body);
const sceneMan = new SceneManager(like);
like.start();
sceneMan.push(blah);
All of the like.* callbacks related to scene management have been split into
the manager in like/scene.
Also, prefab scenes now live in like/scene/prefab/*.
The scene lifecycle of constructor-load-destructor is tired.
Using resources shouldn't force us to choose between null-checking every single resource before use, versus assuming they're non-null and crashing out.
It shouldn't involve the boilerplate of doing deallocations that the garbage collector should be doing.
Therefore, LÏKE has decided to use a factory pattern for scenes.
What was once idiomatically:
class MyScene extends Scene {
someImage?: ImageHandle;
constructor(private path: string) {}
load(like) {
this.someImage = like.gfx.newImage(this.path);
}
draw(like) {
like.gfx.draw(this.someImage);
}
actionpressed(like, action) { ... }
};
is now:
const myScene = (path: string): Scene => (like: Like) => {
const someImage = like.gfx.newImage(path);
return {
draw() {
like.gfx.draw(someImage);
}
actionPressed(action) { ... }
}
}
It's also simpler when adopting scenes for the first time; when converting from a callback to a scene pattern, we no longer have to add like as the first argument of every callback. Instead, we can use this pattern:
// before...
like = createLike(document.body);
like.update = function (dt) {
foo();
}
// or
like.draw = () => { bar(); }
// after
like = createLike(document.body);
const createScene = (like) => {
const myScene = {}
myScene.update = function (dt) {
foo();
}
myScene.draw = () => { bar(); }
return myScene;
}
like.setScene(createScene)
For the vast majority of cases, this is as simple as s/^like\./myScene./ plus some wrapping.
Scene lifecycle functions have also been fleshed out in terms of sane functionality. See the fresh docs for details.
We are implementing this change without backwards compat. If you're crying out in pain right now, let me know and I'll consider writing a wrapper around old-style scenes.
like.quit / sceneInstance.quit callback added for cleanupsceneInstance.load called every time a scene enters the stack toplike/scene/prefab/fadeTransition added: easy fading between scenesscene.instance and scene.deinstancelike.input.setAction() no longer has a default value for the inputs parameter. Use an explicit empty array [] to remove an action.origin.r field of gfx transforms is now angle.setContext -- replaced with withRenderTarget.position arg to polygon drawing func.like.getScene(index) -- Get stack top or other index.like.setScene(scene) -- Now sets stack toplike.pushScene(scene, overlay: boolean) -- Put a scene on top. overlay for pause screens etc.angle, scale, origin) in their props table.like.canvas.hasFocuspolygon() has a translate prop to offset the entire shape.like.canvas.getContext()withTransform for lower-state transform abstraction.Document almost everything and put up a website.
like.gfx.setTarget to change what canvas it draws to.likelike/inputlike/audiolike/graphicslike/mathlike/timerlike/prefab-sceneslike.mouse.showCursor(boolean) integrated with like.mouse.setMode. Rationale: setVisible is irrelevant in capture mode.
Note that setting visible to false in setMode will be remembered when capture state is exited.like.gamepad.getGamepad -- Redundant with DOMlike.input.clear -- Why does this exist?like.mouse.setMode, replaces showCursor and allows setting mouse sensitivity in capture mode, as well as visibility and scroll blocking in non-captured mode.buttonMenuCenter mapping -- Relatively uncommon, no games will rely on this.gfx.circle angle property -- prefer transforms, we can rotate any shape...gamepad.isButtonDown to gamepad.isDowngamepad.isButtonJustPressed to gamepad.justPressedlike.gfx.print option limit renamed to width.like.gfx.print made alignment work differently than LOVE, more like browser canvas.like.mouse.setCapturedPos, allows emulated mouse to be teleported while in capture mode.Vec.map and Vec.map2 helpers.like.gamepad.isDown and like.gamepad.isDown now accept 'any' as the first argument, to check all gamepads.like.gamepad.justPressed now accepts a numeric argument for raw buttons.like.gamepad.setMapping, like.gamepad.getMapping: Set / get active button mappings.like.gamepad.loadMapping, like.gamepad.saveMapping: Put persistent mappings in localstorage.like.gamepad.enableAutoLoadMapping: Always load saved mappings on gamepad connection.like.gamepad.getSticks: Get an array of mapped sticks.like.gamepadconnected and like.gamepaddisconnected events.like.input.setAction now takes string or InputBinding arguments.like.input.appendToActionlike.input.getActionMappinglike.canvas.getDisplayPixelSize --gfx.circle center property actually work.gfx.circle filled arc actually fill like a pie slice kindalike.setScene was dispatching load before like had actually started running.Rect and type Rectangle now separate.
Named Mouse Buttons 1, 2, 3 are now 'left' | 'middle' | 'right'.
No More Physical Mapping Here lies Sam's attempt at making physical gamepad mappings in the browser. At first reading sdlgamecontrollerdb was promising, but the browser wasn't giving enough info for an SDL GUID -- it was ambiguous. Then, disaster strikes: Firefox and Chromium don't agree on where to map his DPad or analog sticks. tl;dr gamepad physical mapping support has been ended. Use actions bindings.. A prefab scene for binding controller inputs is in the works.
like.gamepadpressed(gamepad: number, name: string) event is now like.gamepadpressed(gamepad: number, buttonNum: number, name: string) as it was before.Updates to mouse moved callback
mousemoved(pos: Vector2, relative: boolean) => mousemoved(pos: Vector2, delta: Vector2)like.getMode is now like.canvas.getMode(): { size: Vector2, flags: CanvasModeFlags }
like.setMode is now like.canvas.setmode(size: Vector2 | 'native', flags: Partial<CanvasModeFlags> )
Resize event signature is now just resize(size: Vector2).
Module exports:
Rect, Rectangle, Vector2, and Vec2 now import from like/math/rect and like/math/vector2 etc.like/scene builtins are now in like/prefab-scenesRemoved Timer setSceneTime because it doesn't play nice with composed scenes.
LikeWithCallbacks is now just Like
Like is now LikeInternal
Like2DEvent is now just LikeEvent
Renamed Source to AudioSource
Renamed input.map and input.unmap to input.setAction(string, string[]?) -- which clears the action if nothing provided.
like.canvas module.like.canvas.getFullscreen(): boollike.canvas.setFullscreen(bool)like.callOwnHandlers(LikeEvent): This has been split out and exposed for the sake of
ease in writing custom LIKE systems.callSceneHandlers(LikeEvent): Similar, but for a scene to call its own events.sceneDispatch(Scene, like, LikeEvent): Used for passing events into sub-scenes (including root scene).like.focus and like.blur now have one argument 'tab' | 'canvas' for source.like.audio.getAllSourceslike.handleEvents and scene.handleEvents now work as expected -- they override the preexisting event handling.like/internal/[name] and cast [Name] to [NameInternal] to retrieve.mousepressed(x, y, button) → mousepressed(pos: Vector2, button: number)mousemoved(pos: Vector2, relative: boolean) - Mouse movement event
relative=false: absolute canvas coordinates [x, y]relative=true: relative movement [dx, dy] for FPS controlslockPointer(locked: boolean) / isPointerLocked() - Pointer lock APIshowCursor(visible: boolean) / isCursorVisible() - Cursor visibilityfocus / blur events - Canvas focus stategetCanvasSize() now returns render target size (pixel canvas in pixel mode)push(), pop(), translate(), rotate(), scale() for canvas state managementlike.setScene() method for switching scenes in the unified APIlike2d/callback and like2d/scene subpath exports no longer existlike2d import'like2d':
createLike() is now exported from main module (was 'like2d/callback')Scene type and StartupScene are exported from main module (was 'like2d/scene')like.setScene() instead of SceneRunnernewImage() moved from standalone export to like.gfx.newImage() - update import { newImage } to use like.gfx.newImage()createLike() now returns Like synchronously. Callbacks assigned as properties (like.load, like.update, like.draw). Callbacks receive no parameters - they close over like. Start loop with await like.start().like: Like as first parameter. Use like.gfx for drawing.newImage() for asset loading, bound graphics context accessed via like.gfxsetBackgroundColor(), setFont(), getFont()arc() - use circle() with arc optionShapeProps no longer includes color (now positional)Like interface includes gfx: BoundGraphicslike.draw = () => {...}routeEvents() exported from callback adapter for custom event handlingLikeInstance interfacelike parameter from callback adapter callbacks (now closed over)g parameter from scene adapter draw callback (use like.gfx)'like2d' directly{ type: 'fixed'|'native', ...} → { pixelResolution: Vector2|null, fullscreen: boolean }onStart callback, now takes setScene function directlysetFullscreen() into setMode() APIgetMode() to retrieve canvas configurationSceneRunner.toggleFullscreen()V2 → Vec2, R → RectEngine.start(update, draw) → Engine.start(onEvent) with Like2DEvent discriminated unionengine.onKey, engine.onMouse, engine.onGamepadkeypressed, mousepressed, etc.){type, args, timestamp} shapeProof of concept.