Changelog
All notable changes to this project will be documented in this file.
The format is (mostly) based on Keep a Changelog, and this project adheres to Semantic Versioning.
[unreleased]
Added
- Added
tileModeoption to 9-slice sprites with four tiling strategies:'none'(stretch all),'edges'(tile edges only),'center'(tile center only), and'all'(tile both edges and center) (#996) - @JustKira - Added a
calculate()method to the internal FPS counters, so advanced users can access them to create their own FPS monitor (#1010) - @dragoncoder047
Changed
- Updated the texture packer to use a new packing algorithm which may get more sprites onto the same texture, improving graphics batching performance (#1011) - @dragoncoder047
Fixed
- Fixed tiled mode drawing of sprites ignoring opacity when it was 0 (#1020) - @dragoncoder047
- Now, all global events handlers are avaible in scopes,
app.onXXXXandscene.onXXXX()(#977) - @lajbel - Fixed input events attached to paused ancestors not being paused (#1009) - @amyspark-ng, @dragoncoder047
- Fixed type
UniformValueunion not includingTexture, a valid option (#1018) - @dragoncoder047 - Text component no longer hangs if the requested width is too narrow for a single character - @dragoncoder047
- Fixed input events attached to paused ancestors not being paused (#1009) - @amyspark-ng, @dragoncoder047
- Fixed type
UniformValueunion not includingTexture, a valid option (#1018) - @dragoncoder047 - Fixed event crash when using
onLoador other events that doesn’t return an EventController, and then usinggo()(#1024) - @lajbel, credits to @dragoncoder047
[4000.0.0-alpha.26] - 2026-01-12
Added
- Added
floodFill()for puzzle games - @mflerackers - Added
AreaComp.isVisuallyCollidingto test collisions in screen space. This can be used for fixed objects which do not necessarily collide in world space. Note that this involves additional processing as it tests outside the collision system, which works in world space - @mflerackers - Added
buildConnectivityMap()- @mflerackers - Added
buildConvexHull()- @mflerackers
Changed
- (!) Added
AreaCompOpt.isSensor. Areas without body or is sensor will no longer be eligible for collisions - @mflerackers - Both worldPos and screenPos are properties now - @mflerackers
Fixed
- Fixed
tween()not cloning the passed vectors/colors - @lajbel - Fixed
timer()related events (tween/loop/wait) not takingdebug.timeScaleinto account - @Stanko - Fixed the vibration effect on bodies introduced in alpha.25 thanks to @lajbel’s debugging skills - @mflerackers
- Fixed
SpriteComp.hasAnim()returning false erroneously when the animation named was just constant frame 0 - @dragoncoder047 - Fixed
levelComp.serialize()use for…of in the place of the for…in when looping through the tile object keys - @benhuangbmj - Fixed input events attached to a game object having the event’s paused value reset when the object is paused or unpaused - @dragoncoder047
- Hidden objects are processed again in transform - @mflerackers
- When the parent is changed, the transform is invalidated - @mflerackers
- Fixed click and hover for
fixed()objects - @mflerackers - Object toWorld/fromWorld/toScreen/fromScreen work more logical now - @mflerackers
- Sticky platforms work again - @mflerackers
Removed
- (!)
onClick(() => {})was removed, useonMousePress()instead.onClick("tag", () => {});stays the same,
[4000.0.0-alpha.25] - 2025-12-23
Added
- Added the
fakeMouseMoveevent inFakeMouseComp, it will triggers when you move the object - @lajbel - Global
retrieve()method to get the objects with area within a certain rectangle - @mflerackers
Changed
- (!) You can no longer change the position of an object by doing obj.pos.x += 1. You need to assign a new Vec2 or use moveBy instead - @mflerackers
- Transforms are now only recalculated when needed. Thus static objects no longer increase computation in the transform phase - @mflerackers
- Areas are now only recalculated when the area settings or (optional) renderArea has changed - @mflerackers
- World (transformed) areas are now only recalculated when the area or transform has changed - @mflerackers
- World bounding boxes are now only recalculated when the world area has changed - @mflerackers
- Broad stage collision detection spatial structures are now only updated when an object’s world bounding box has changed - @mflerackers
- The grid broadphase has been rewritten for performance - @mflerackers
- Global
retrieve()method to get the objects with area within a certain rectangle - @mflerackers
[4000.0.0-alpha.24] - 2025-12-12
Added
- Added the
maxTimeStepandfixedUpdateModeoptions, as well assetFixedSpeed()for more granular control over fixed update and timing - @dragoncoder047 - Added parameterized formatting tags like
"[color=red]Red text![/color]"inCharTransformFuncfor more powerful text formatting options - @dragoncoder047 - Added
createRegularPolygon()andcreateStarPolygon()to create 2D regular polytopes - @mflerackers - Added
createCogPolygon()to create 2D regular cogs - @mflerackers - Added
getSpriteOutline()that takes a sprite asset and returns a polygon showing the outline - @milosilo-dev - Added Quadtree for collision detection (only for fixed size screen for now, needs expansion) - @mflerackers
- Added vertical sweep and prune - @mflerackers
- Added configuration to choose broad phase algorithm - @mflerackers
Fixed
- Fixed the
fakeMouse()component not giving the right position when the camera transform was not the identity matrix - @dragoncoder047 - Fixed tall fonts being cropped - @anthonygood
- Fixed the sprite animation
onEnd()callback being called before the animation actually stopped, so if the onEnd callback started a new animation, the new animation was instantly stopped - @dragoncoder047 - Now
playMusic()actually uses the requested volume and playback rate given in the options - @dragoncoder047
[4000.0.0-alpha.23] - 2025-11-05
Added
-
Added
getGamepadAnalogButton()to read the analog value of buttons like the triggers - @dragoncoder047isGamepadButtonDown("rtrigger"); // -> true/false, 0/1 getGamepadAnalogButton("rtrigger"); // -> analog value between 0 (not pressed) and 1 (fully pressed) -
Added chorded button bindings using the Buttons API, so you can bind different actions to
tabandshift+tab, and handle them like normal. Also works with gamepads and mouse! - @dragoncoder047kaplay({ buttons: { forward: { keyboard: "tab", gamepad: "south", }, backward: { keyboard: "shift+tab", gamepad: "rshoulder+south", }, }, }); -
Added
skewto text formatting, so now italics is possible - @dragoncoder047 -
Added lifetime scopes, a way to define the lifetime of an event handler using a specific scope,
scene,appor a game object - @lajbel, @dragoncoder047app.onUpdate(() => { // runs until it is cancelled }); scene("game", () => { const obj = add([]); obj.onUpdate(() => { // runs until obj is destroyed }); scene.onUpdate(() => { // or just onUpdate(() => { // runs until scene is changed }); });All the available handlers in the scopes are
GameEventHandlersones:onKeyDown()onKeyPress()onKeyPressRepeat()onKeyRelease()onCharInput()onMouseDown()onMousePress()onMouseRelease()onMouseMove()onScroll()onTouchStart()onTouchMove()onTouchEnd()onGamepadConnect()onGamepadDisconnect()onGamepadButtonDown()onGamepadButtonPress()onGamepadButtonRelease()onGamepadStick()onButtonDown()onButtonPress()onButtonRelease()onTabHide()onTabShow()
And this game object handlers may differ when using it with
objandscene/app: -
Added
appscope for app event handlers - @lajbelapp.onUpdate(() => { // runs until it is cancelled }); -
Added
KAPLAYOpt.defaultLifetimeScopefor setting the default lifetime scope used for event handlers - @lajbelkaplay({ defaultLifetimeScope: "app", // default is "scene" }); onKeyPress("space", () => { // runs until is cancelled }); -
Added
skewto text formatting, so now italics is possible - @dragoncoder047
Changed
-
(!) Renamed
onShow()toonTabShow()andonHide()toonTabHide()- @lajbel -
In addition to being the
scene()function, nowsceneis also a scope for scene event handlers - @lajbelscene("game", () => { scene.onUpdate(() => { // or just onUpdate(() => { // runs until scene is changed }); });
Fixed
- Now
pushScene()andpopScene()give the arguments to the scene in the same way thatgo()does rather than passing them all to the first argument as an array - @dragoncoder047 - Fixed a flicker due to the fadeIn not setting opacity until the next frame - @mflerackers
- Fixed FPS cap not working correctly - @mflerackers, @dragoncoder047
[4000.0.0-alpha.22] - 2025-10-9
Added
-
Added
KAPLAYOpt.types,kaplayTypes()andOptto config specific TypeScript Advanced Features (TAF) - @lajbelkaplay({ types: kaplayTypes< // Opt<> is optional but recommended to get autocomplete Opt<{ scenes: {}; // define scenes and arguments strictScenes: true; // you can only use defined scenes }> >(), }); -
Added
TypesOpt.scenesto type scenes and parameters - @lajbelconst k = kaplay({ types: kaplayTypes< Opt<{ scenes: { game: [gamemode: "normal" | "hard"]; gameOver: [score: number, highScore: number]; }; }> >(), }); // If you trigger autocomplete it shows "game" or "gameOver" k.scene("game", (gamemode) => { // gamemode is now type "normal" | "hard" // @ts-expect-error Argument of type 'string' is not assignable // to parameter of type 'number'. k.go("gameOver", "10", 10); // });The methods that support this are:
-
Added
TypesOpt.strictScenesto make usable scenes just the ones defined - @lajbelconst k = kaplay({ types: kaplayTypes< Opt<{ scenes: { game: [gamemode: "normal" | "hard"]; gameOver: [score: number, highScore: number]; }; strictScenes: true; }> >(), }); // @ts-expect-error Argument of type '"hi"' is not assignable to // parameter of type '"game" | "gameOver"'. k.scene("hi", () => {}); -
Added named animations - @mflerackers
By giving a name to an animation, you can define more than one animation
const anim = obj.animation.get("idle"); anim.animate("pos", [0, 5, 0], { relative: true }); -
Added
screenshotToBlob()to get a screenshot as aBlob- @dragoncoder047 -
Added
getButtons()to get the input binding buttons definition - @lajbel -
Added
RuleSystem,DecisionTreeandStateMachinefor enemy AI - @mflerackers -
Added constraint components for distance, translation, rotation, scale and transform constraints - @mflerackers
-
Added inverse kinematics constraint components using FABRIK and CCD, the latter one can use bone constraints to constrain the angle - @mflerackers
-
Added skew to Mat23, transformation stack, RenderProps, GameObjRaw as well as a component - @mflerackers
-
Added texture uniforms, in order to access more than one texture at a time in shaders - @mflerackers
Fixed
- Now error screen should be instantly shown - @lajbel
Changed
- Now, you can use
color(c)with a hexadecimal literal number (ex: 0x00ff00) - @lajbel// blue frog add([sprite("bean"), color(0x0000ff)]); - (!)
KAPLAYCtxdoesn’t use generics anymore. Now,KAPLAYCtxTuses them - @lajbel - Now,
kaplaywill returnKAPLAYCtxorKAPLAYCtxTdepending if it’s using Advanced TypeScript Features or not - @lajbel loadShader()now also checks for link errors as well as compile errors and reports them rather than just silently trying to use a borked shader - @dragoncoder047- The debug
record()function now records with sound enabled like it should - @dragoncoder047 - Now
KAPLAYOpt.spriteAtlasPaddingis set to2by default - @lajbel - Transformation and drawing is split now, so the transform can be modified before drawing - @mflerackers
[4000.0.0-alpha.21] - 2025-08-07
Added
-
Added
GameObjRaw.serialize()for serializing the game object and its components. - @mflerackers, @lajbelconst bean = add([sprite("prefab")]); const beanSerialized = bean.serialize(); -
Added
createPrefab()for serializing an object and register it (or not) as a prefab from a Game Object. - @mflerackers, @lajbelconst beanObj = add([sprite("bean")]); // Serialize game object and register it as a prefab asset createPrefab("bean", beanObj); addPrefab("bean"); // Just get serialized data const serializedBean = createPrefab(beanObj); addPrefab(beanObj); -
Added
addPrefab()for creating an object previously serialized - @mflerackers, @lajbelloadPrefab("bean", "/bean.kaprefab"); addPrefab("bean"); -
Added new scene methods
pushScene()andpopScene(), for stack behaviour in scenes - @itzKiwiSky -
Added
throwError()for throwing custom errors to the blue screen, even errors KAPLAY can’t handle. - @lajbel -
Added
insertionSort()- @dragoncoder047 -
Added a mapping for PS5 (DualSense) gamepads, so now you can bind actions to the touchpad press (only works in Chrome for some reason) - @dragoncoder047
Changed
- Now
GameObjRaw.exists()work for nested objects - Now moving mouse changes the value of
getLastInputDevice()- @amyspark-ng - (!) Renamed
KAPLAYOpt.tagsAsComponentstoKAPLAYOpt.tagComponentIds- @lajbel
Fixed
- Fixed shader error messages - @dragoncoder047
- Fixed compatibility issues when calculating font height with missing TextMetrics props - @imaginarny
[4000.0.0-alpha.20] - 2025-06-15
Added
- Added
loadSpriteFromFont()for loading a bitmap font from a loaded sprite. - @dragoncoder047
Changed
- Improved various doc entries. - Many contributors
Fixed
- Fixed
AreaComp#onClick()attaching events to app, instead of object, so event wasn’t being paused withobj.paused- @lajbel - Fixed all touch events having a bad transformation - @lajbel
- Fixed sprite scaling not working properly with
KAPLAYOpt.letterbox- @mflerackers - Fixed “add” event running twice in
addLevel()tiles - @lajbel - Fixed blend component having a wrong ID - @lajbel
Removed
- (!)
loadPedit()was removed - @lajbel
[4000.0.0-alpha.19] - 2025-05-16
This version changelog covers versions 4000.0.0-alpha.0 through 4000.0.0-alpha.19, as we didn’t have a concise changelog strategy before.
Added
-
Added
fakeMouse()to create a fake mouse cursor - @lajbelconst myCursor = add([fakeMouse(), sprite("kat"), pos(100, 100)]); myCursor.press(); // trigger onClick events if the mouse is over myCursor.release(); myCursor.moveBy(vec2(100, 200)); // move as your wish -
Added
system()to replace internal events or create new - @mflerackerssystem("collision", () => { // system code }, [SystemPhase.AfterFixedUpdate, SystemPhase.AfterUpdate]), -
Added
ellipse()component - @mflerackers -
Added circle and (rotated) ellipse collision shapes - @mflerackers
-
Added
clipLineToRect()- @mflerackers -
Added
obj.setParent()to change the parent of a game object - @mflerackers -
Added restitution and friction to physics - @mflerackers
-
All game objects have methods
onTag()andonUntag()for watching tag changes - @mflerackers -
Added
SystemPhaseenum to identify different lifecycle events in the game loop that systems can hook into - @mflerackers -
Added Blend mode is selectable to change how sprites are composited on top of each other - @mflerackers
-
Added Picture API to cache drawing of selected objects - @mflerackers
-
Added
drawCanvas()- @mflerackers -
Added
video()component to embed a video file into the game - @mflerackers -
Added
level()component and parent argument toaddLevel()- @KeSuave -
Allow the
text()component to change the font and apply shaders per-character - @dragoncoder047 -
Allow characters in text to be scaled and have the text flow around it with
stretchInPlace: false- @dragoncoder047 -
Expose the formatted text parsing functions to allow manipulation of formatted text - @dragoncoder047
-
Now you can use the frames of a sprite in an atlas also as a font - @dragoncoder047
-
More errors raised during object creation are caught and cause the blue crash screen - @lajbel
-
The blue crash screen will no longer fail to draw if the error message contains brackets - @dragoncoder047
-
Now you can use the global option
inspectOnlyActive: falseto prevent paused objects from showing in the debug inspect view, this is useful if you are swapping out objects for different views - @dragoncoder047 -
The
OffScreenCompnow has an optionoffscreenDistanceto change the distance at which an object is considered off-screen - @dragoncoder047 -
Now you can cherry-pick specific frames of a sprite sheet by using the
frameslist, instead of being limited to consecutive framesstartandend- @dragoncoder047 -
wave()can now go back and forth between any value that is able to be used withlerp()- @dragoncoder047, @mflerackers -
The
TextInputComphas more events:focus,blur,input, andchange, to better interact with the text input state - @dragoncoder047 -
Areas no longer struggle with parents whose transform inst’t up-to-date - @mflerackers
-
Exported step and smoothstep - @mflerackers
-
Small circles and arcs use now less points than larger ones - @mflerackers
-
Added pushMatrix, storeMatrix and loadIdentity to the stack functions - @mflerackers
-
Typed
StateComp- @amyspark-ng -
Added bias to line drawing, which controls the offset from the center of the line - @mflerackers
-
Added
SpriteAnimPlayOpt.preventRestartto allowSpriteComp.play()to be called from anonUpdate()and not reset the animation to frame 0 - @dragoncoder047
Changed
- (!) - Now
z()is global instead of relative - @mflerackers - (!) Layers now work globally, no longer only between siblings - @mflerackers
- (!): Changed default behavior to
kaplay({ tagsAsComponents: false }) - The physics engine creates less garbage - @mflerackers
- Tag-based events are slightly faster - @dragoncoder047
- Moved camera to the shader - @mflerackers
- Replaced the Separating Axis Theorem (SAT) collision detection module with the
Gilbert–Johnson–Keerthi
(
GJK) algorithm, which is faster - @mflerackers - Now if you pass a nullish value to
.use()it throws an error - Improved TypeScript in game objects - @amyspark-ng, @lajbel, @KeSuave
- Added/updated JSDoc comments to some members - @ErikGXDev, @dragoncoder047
- The
textInputcomponent’sisFocusedproperty is now a one-hot lockout, setting it to true (focused) will clear focus from all the other text inputs - @dragoncoder047 - Changed the API of
HealthComp- @amyspark-ng - CapsLock now affects
TextInputComp- @amyspark-ng
Fixed
GameObjRaw.exists()now correctly returns false if the parent was destroyed but obj wasn’t - @dragoncoder047Vec2.dot()now actually does the Correct Calculation™ - @andrenanninga- Fixed
debug.timeScalenot affectingdt()scale - @lajbel - Fixed
wait()’sTimerComp.onEnd()being waiting for twice the duration - @dragoncoder047 - Fixed non-focused
TextInputCompbackspace - @KeSuave - Fixed 9slice sprites behaving wrong when using
Anchor- @mflerackers - Fixed rendering glitches with outlines on circles - @mflerackers
- Fixed
setCursorLocked(true)throwing error if the browser is using the old non-Promise-based API return value - @imaginarny - Fixed
PatrolCompnot going to last waypoint - @nojaf - Fixed various TypeScript types - @amyspark-ng, @lajbel, @KeSuave
Removed
- (!)
make()was sent to doom - @lajbel