Repository navigation
Soft lights, sortOffset, onFrame, tintOverLife, track regions and fixes - #1764
Merged
Merged
Conversation
Engine additions and fixes from the isometric dungeon example:
- Light2d#softness fades the edge of a light's hole in the stage's
ambient darkness: one quad through an internal AmbientLightEffect on
WebGL and WebGPU, an offscreen erase on Canvas
- Renderable#sortOffset, and autoSort keeping an "x" or "y" sorted
container in order as its children move
- setCurrentAnimation(name, { onFrame }) on Sprite and Sprite3d
- ParticleEmitterSettings.tintOverLife
- audio.playTrack(name, { volume, sprite }) (TrackOptions), and a sprite
region declared looping loops through audio.play
- effects that sample around a pixel keep to the sprite's frame on an
atlas or a tileset (ME_frame, fed per quad by the quad batchers)
- a zoomed camera draws its floating renderables, and a ColorLayer
fills its view
- stage lighting: the Canvas mask under a scrolled, zoomed or offset
camera, the first column and row at a fractional camera position, and
the whole view of a zoomed-out camera
- the skills cover all of the above
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SeWCkETjW35ZbXDRswV1hx
# Conflicts: # packages/melonjs/CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Engine additions and fixes from the isometric dungeon example (coming in its own PR). Each new API was reviewed on its own before it was built. This branch starts from master and does not depend on #1763; both add to the 20.11 changelog, so whichever merges second needs a small rebase there.
Added
Light2d#softness(0 to 1) fades the edge of a light's hole in the stage's ambient darkness, from a hard rim (0, the default, unchanged) to a darkness that thins straight from the centre (1), along the same falloff as the glow. When any light in view is soft,Stage#drawLightingdraws the darkness in one pass through a new internalRenderer#drawAmbientLight. On WebGL and WebGPU that is one quad through an internalAmbientLightEffect, which cuts up to 32 lights in view; overlapping lights multiply. On Canvas the darkness is erased from a reused offscreen canvas. Hard and soft lights mix in one scene, and a stage whose lights are all hard keeps the oldfillRectpath.Renderable#sortOffsetlines a renderable up as if it stood that many pixels lower or higher when its container sorts on"y"or"x", without moving it. It takes finite numbers only (TypeErrorotherwise), re-sorts the parent when it changes, and is cleared when a pooled renderable is reset. Equal positions keep insertion order, which the tilemaps skill now spells out (the child added last is drawn behind).setCurrentAnimation(name, { onFrame })calls back with each frame index as playback reaches it, from frame 0, on aSpriteor aSprite3d.ParticleEmitterSettings.tintOverLifeblends a particle's tint from the first color at birth to the last at the end of its life. It sets the color only (the fade keeps the opacity). The blend is taken in 32 steps, sampled once inreset(), because the Canvas renderer keeps a tinted copy of a texture per color for as long as the texture lives: a continuous blend would grow that cache without bound.audio.playTrack(name, { volume, sprite })plays one region of a clip as the looping track (TrackOptionsis exported). A region of the loop's exact length keeps an MP3 loop gapless in Safari, which plays the padding an MP3 encoder adds to the end of the file.Changed
autoSortkeeps an"x"or"y"sorted container in order as its children move. At the end of eachContainer#updateit checks the order in one comparison pass and re-sorts (and marks the frame dirty) only when something is out of place. A walking character passes in front of and behind the scenery with nosort()call. A frame where nothing moved costs one pass and causes no redraw (about 0.065 ms for 956 children). SettingautoSortto false keeps the order you set, and a"z"sort is unchanged.Fixed
ME_frame,ME_frame_half, through an internalShaderEffect#_setFrameRect), and these effects sample through aframeTexel()helper: transparent outside the frame, and kept half a texel inside it so filtering does not reach over the edge. A side of the frame on the texture's own edge stays unbounded, as do whole-texture passes (camera effects, offscreen passes, meshes), so those sample as before. Custom shaders are untouched unless they declare the uniforms.ColorLayerfills its whole view.Container#drawskipped floating children on any camera that was not at zoom 1, so a zoomed main camera lost its HUD; that check now also lets through the stage's"default"camera when it is drawn at the screen origin, so a minimap still skips them.ColorLayernow scales its clip by the zoom.translate()truncates withoutsubPixel, so the darkness now reaches a whole-pixel margin past the view (ceil(zoom) / zoomworld units).camera.widthworld units, notcamera.width / zoom.[start, duration, true]) loops when played withaudio.play(name, { sprite }). Its loop flag was overridden to false unlessloop: truewas passed too.Also
softness, soft and hard lights together, and symptom rows), effects and shaders (effects keep to the frame on an atlas), audio (playTrackregions), math (Noise#getNoise1dfor a flicker or a wobble), particles (tintOverLife), renderables and tilemaps (sortOffset,autoSort, tie order), sprites and animation (onFrame).tmxlayer-data.spec.jsno longer points at the isometricRpg example, which the dungeon example replaces.Verification
light-softness(38),particle-tint-over-life(27),effect-frame-bounds(29),sort-offset(19),camera-zoom-floating(18),container-autosort(10), plusonFramecases inframeAnimationandanimation(9) and audio cases inaudio-backend(6).onFrame7/7,sortOffset12/12,autoSort7/7,tintOverLife10/10;pnpm lintandpnpm docpass with no new doc warnings.🤖 Generated with Claude Code
https://claude.ai/code/session_01SeWCkETjW35ZbXDRswV1hx