Releases: kaplayjs/kaplay
Release list
4000.0.0-alpha.27.1
[4000.0.0-alpha.27.1] - 2026-05-12
Added
- Added a
repack: falseoption toloadSpite()and a repack parameter to
loadSpriteAtlas(), for faster loading if you're packing stuff at build-time
(#1063) - @dragoncoder047
Changed
- (!) Updated "explicit
LoadSpriteOpt.frames" method of loading a sprite
with animations (via eitherloadSprite()orloadSpriteAtlas()) to use
pixel values for the coordinate space, which is more intuitive (#1061) -
@dragoncoder047 - Added padding around edges of spritesheet to prevent stretch if uv ends up out
of bounds (#1076) - @dragoncoder047
Fixed
- Updated texture packer refreshing to only refresh when a sprite is actually
requested to be drawn, to reduce the number of times the texture is synced to
the GPU, which is slow (#1058) - @dragoncoder047 - Fixed
loadBitmapFontFromSprite()so that the font drawn using it is drawn
with the correct scale, as one would expect (#1063) - @dragoncoder047 - Added
exists()which also checks whether the object is not undefined. This
should temporarily fix the problem with undefined objects during collision due
to iterators being invalidated by destroy. - @mflerackers - Fixed broadphase event duplication on scene switch, causing repeated
broadphase object registrations, which resulted in a performance drop
(#1074) - @imaginarny, @mflerackers - Fixed broadphase objects cleared on scene switch including those with
stay()
(#1077) - @imaginarny, @mflerackers
What's Changed
- fix: only sync texture when needed by @dragoncoder047 in #1058
- feat: give sprite frame explicitly in pixels by @dragoncoder047 in #1061
- chore(deps-dev): bump vite from 7.1.12 to 7.3.2 by @dependabot[bot] in #1065
- examples: update flappy and pause menu by @imaginarny in #1073
- fix: broadphase event duplication on scene switch by @imaginarny in #1074
- fix: broadphase objects with stay cleared on scene switch by @imaginarny in #1077
- feat: add back no repack option, also minor speedup of repacker by @dragoncoder047 in #1062
- fix: add padding around edges of spritesheet to prevent stretch if uv ends up out of bounds by @dragoncoder047 in #1076
- fix: changed font scaling to fix loadSpriteAsFont by @dragoncoder047 in #1063
- fix: refresh packer for direct referenced sprites resolving by @dragoncoder047 in #1089
Full Changelog: 4000.0.0-alpha.27...4000.0.0-alpha.27.1
4000.0.0-alpha.27
[4000.0.0-alpha.27] - 2026-03-19
Added
- Added
debug.warn()to log warning messages (#1028) - @lajbel - Added
debug.logMessage()to log a messages array with a wrapping style
(warn, error, info) (#1028) - @lajbel - Added per-sprite and per-font
filteroptions, which override the global
texFilterandfontFilteroptions (#1050) - @dragoncoder047 - Added global
fontFilteroption to control default font rasterization filter
(#1050, #1052) - @dragoncoder047 - 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 - Added Intl.Segmenter-based grapheme splitting for proper Indic language
support, via thelocaleoption inDrawTextOpt(#1013) - @shajidhasan - Added topMostOnlyActivate kaplay option. When true, only the topmost object
will receive clicks. This avoids problems in a UI where elements overlap -
@mflerackers - Added a
fill()component - @mflerackers - Added
lerpAngle()helper function to interpolate between clamped angles,
preventing 360掳 spins during transitions from 180 to -180 (#1054) -
@imaginarny - Added
piecewiseBezier()andpiecewiseCatmullRom()to evaluate curves with
multiple points - @mflerackers
Removed
- (!) The texture uv coordinates for primitives (solid- or gradient-filled
circles, rectangles, lines, and polygons) have now been changed to (1, 1)
instead of (0, 0), so shaders written for primitives will need to be updated.
(#1021) - @dragoncoder047
Changed
- (!) The global
onDraw()handler's no-tag form now always draws before
all game objects are drawn, regardless of whether it was attached after game
objects were added (#977) - @lajbel - (!) The sprite data format has been changed to allow individual frames to
be on different GPU textures. NowSpriteData.texdoesn't exist, and
SpriteData.framesis a list ofFrames instead of a list ofQuads. A
Framecontainstexandq(quad) properties that contain that data.
(#1021) - @dragoncoder047 - 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 - Updated all sprite and font loading to pack everything in the same texture to
allow it to all batch together, for speed and efficiency (#1021) -
@dragoncoder047 - Added spritesheet repacking, so spritesheet images that contain lots of blank
space don't waste texture memory (#1021) - @dragoncoder047
Fixed
- Fixed vertical alignment of text within a now-consistent bounding box across
browsers (#1053) - @imaginarny - Fixed tiled mode drawing of sprites ignoring opacity when it was 0 (#1020) -
@dragoncoder047 - Now, all global events handlers are available in scopes,
app.onXXXXand
scene.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 - Fixed the unexpected behavior of not preserving the aspect ratio in
drawSprite(#1026) - @benhuangbmj - Fixed
onClick()andonCollide()tag variants no longer working -
@mflerackers - Fixed
destroy()messing up enumeration duringupdate()and
fixedUpdate(), thanks to @imaginarny for helping to find the cause -
@mflerackers - Fixed canvas not resizing to fullscreen on
setFullscreen()(#1055) -
@imaginarny
New Contributors
- @JustKira made their first contribution in #996
- @shajidhasan made their first contribution in #1013
- @ProxyLoader made their first contribution in #1045
Full Changelog: 4000.0.0-alpha.26...4000.0.0-alpha.27
4000.0.0-alpha.26
[4000.0.0-alpha.26] - 2026-01-12
Added
- Added
AreaCompOpt.isSensor. Areas without body or is sensor will no longer
be eligible for collisions - @mflerackers - 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
- 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.timeScale
into 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
What's Changed
- feat: tween clone rgb/vec2 parameters by @lajbel in #982
- feat: Introduces sensors by @mflerackers in #979
- fix: return true for hasAnim() even if anim is just frame 0 by @dragoncoder047 in #985
- refactor: Convert hover handler to a system by @mflerackers in #986
- chore: build in typescript, improve contributing.md by @lajbel in #990
- fix: Should fix click by @mflerackers in #993
- feat: Added isVisuallyColliding to area by @mflerackers in #997
- feat: Added floodfill (used in various puzzle games and puzzle bobble like games) by @mflerackers in #998
- fix: input events now retain their paused state by @dragoncoder047 in #988
- fix: fix for loop syntax to use 'of' instead of 'in' by @benhuangbmj in #991
- fix(timer): use timeScale in loop/wait/tween by @Stanko in #1007
New Contributors
- @benhuangbmj made their first contribution in #991
- @Stanko made their first contribution in #1007
Full Changelog: 4000.0.0-alpha.25...4000.0.0-alpha.26
4000.0.0-alpha.25
[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
- 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
- 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
- The grid broadphase has been rewritten for performance - @mflerackers
Full Changelog: 4000.0.0-alpha.24...4000.0.0-alpha.25
4000.0.0-alpha.24
[4000.0.0-alpha.24] - 2025-12-12
Added
- Added the
maxTimeStepandfixedUpdateModeoptions, as well as
setFixedSpeed()for more granular control over fixed update and timing -
@dragoncoder047 - Added parameterized formatting tags like
"[color=red]Red text![/color]"in
CharTransformFuncfor 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
[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 totabandshift+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
objand
scene/app:onFixedUpdate()onUpdate()onDraw()
-
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
Full Changelog: 4000.0.0-alpha.22...4000.0.0-alpha.23
4000.0.0-alpha.22
[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:
scenegoonSceneLeavegetSceneName
-
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
Full Changelog: 4000.0.0-alpha.21...4000.0.0-alpha.22
4000.0.0-alpha.21
[4000.0.0-alpha.21] - 2025-08-07
Added
- Added Prefabs - @mflerackers, @lajbel, @amyspark-ng and other contributors.
- 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
- Renamed
KAPLAYOpt.tagsAsComponentstoKAPLAYOpt.tagComponentIds- @lajbel - Now moving mouse changes the value of
getLastInputDevice()- @amyspark-ng - Now
GameObjRaw.exists()work for nested objects
Fixed
- Fixed shader error messages - @dragoncoder047
New Contributors
- @FowluhhDev made their first contribution in #839
- @itzKiwiSky made their first contribution in #842
- @bobrosoft made their first contribution in #862
Full Changelog: 4000.0.0-alpha.20...4000.0.0-alpha.21
4000.0.0-alpha.20
[4000.0.0-alpha.20] - 2025-06-15
Added
- Now you can use the frames of a sprite in an atlas also as a font -
@dragoncoder047 - Improved various doc entries. - All 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 transform - @lajbel
- Fixed sprite scaling not working properly when letterbox - @mflerackers
- Fixed "add" event running twice in
addLevel()tiles - @lajbel - Fixed blend component having a wrong ID - @lajbel
Removed
loadPeditwas removed - @lajbel
Full Changelog: 4000.0.0-alpha.19...4000.0.0-alpha.20
3001.0.19
[3001.0.19] - 2025-06-15
- 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 transform - @lajbel
Full Changelog: 3001.0.18...3001.0.19