Skip to content

Refactor camera effects into extensible effect system#1400

Merged
obiot merged 4 commits intomasterfrom
feat/camera-effects-system
Apr 18, 2026
Merged

Refactor camera effects into extensible effect system#1400
obiot merged 4 commits intomasterfrom
feat/camera-effects-system

Conversation

@obiot
Copy link
Copy Markdown
Member

@obiot obiot commented Apr 18, 2026

Summary

Closes #1369

Replaces the hardcoded camera shake/fade state (_shake, _fadeIn, _fadeOut) with an extensible CameraEffect system. Fully backward compatible — shake(), fadeIn(), fadeOut() keep the same signatures.

New camera effects

  • CameraEffect — base class with update(dt), draw(renderer, w, h), destroy() lifecycle and isPersistent flag for effects that survive state changes
  • ShakeEffect — extracted shake logic with intensity, duration, axis, onComplete
  • FadeEffect — unified fade-in/out with direction: "in" | "out", supports non-opaque target alpha
  • MaskEffect — shape-based mask transitions using Ellipse or Polygon, with pooled shape rendering (zero per-frame allocations)

Camera2d API

  • addCameraEffect(effect) / getCameraEffect(EffectClass) / removeCameraEffect(effect)
  • shake(), fadeIn(), fadeOut() are now thin wrappers — same signatures, fully backward compatible
  • isPersistent effects survive camera.reset() (needed for state transitions)

Trigger

  • New transition setting: "fade" (default) or "mask" for shape-based level transitions
  • New color setting replaces legacy fade (backward compatible)
  • Both hide and reveal effects handled internally

State manager

  • state.transition() accepts "fade" or "mask" with optional shape parameter
  • state.change() uses switch/case for transition types
  • Pre-creates reveal effect to prevent flash between hide and reveal

Other fixes

  • Canvas: setMask(shape, true) uses evenodd clipping for proper inverted mask support
  • Ellipse: clone() now uses ellipsePool instead of new Ellipse()
  • TMXObjectFactory: override warning uses console.warn() instead of deprecation warning()

Examples

  • Platformer: camera shake on player hit
  • Platformer: LevelTrigger with star polygon mask transition

Test plan

  • 2547 tests pass (37 new: 15 FadeEffect/MaskEffect, 18 camera effects, 19 trigger, 1 ellipse clone)
  • Platformer: fade transition between states works (hide → reveal)
  • Platformer: star mask transition on level trigger (hide + reveal)
  • Platformer: camera shake on enemy hit
  • Multiple effects coexist (shake + fade simultaneously)
  • No visual regression without effects assigned

🤖 Generated with Claude Code

Replace hardcoded camera shake/fade state with an extensible CameraEffect
system. Effects are managed via addCameraEffect/getCameraEffect/removeCameraEffect.

Camera effects:
- CameraEffect base class with update/draw/destroy lifecycle and isPersistent flag
- ShakeEffect: extracted shake logic with intensity, duration, axis, onComplete
- FadeEffect: unified fade-in/out with direction "in"/"out", supports non-opaque alpha
- MaskEffect: shape-based mask transitions using Ellipse or Polygon with
  pooled shape (zero per-frame allocations), inverted clip masking

Camera2d:
- shake()/fadeIn()/fadeOut() are now backward-compatible wrappers
- cameraEffects array with auto-removal of completed effects
- isPersistent effects survive camera.reset() (for state transitions)

Trigger:
- Supports "fade" (default) and "mask" transition types via settings.transition
- settings.color replaces legacy settings.fade (backward compatible)
- Both hide and reveal effects handled internally via settings.onLoaded
- triggerEvent() uses FadeEffect/MaskEffect based on transition type

State manager:
- state.transition() accepts "fade" or "mask" with optional shape parameter
- state.change() uses switch/case for transition types with pre-created reveal
- _onSwitchComplete cleared after execution to prevent stale callbacks
- onComplete uses arrow function for correct this binding

Other fixes:
- Canvas: setMask(shape, true) uses evenodd clipping for proper inverted masks
- Ellipse: clone() now uses ellipsePool instead of new Ellipse()
- TMXObjectFactory: override warning uses console.warn() not deprecation warning()

Examples:
- Platformer: camera shake on player hit, LevelTrigger with star mask transition

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 18, 2026 09:32
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors melonJS camera shake/fade internals into an extensible CameraEffect system and updates state/trigger transitions to use effect instances (including a new mask-based transition), while keeping the existing shake(), fadeIn(), and fadeOut() APIs.

Changes:

  • Add CameraEffect base class plus built-in ShakeEffect, FadeEffect, and MaskEffect, and integrate an effect list into Camera2d.
  • Update state.change() and Trigger to drive hide→switch→reveal transitions via camera effects (fade or mask).
  • Fix Canvas inverted masking via clip("evenodd"), pool Ellipse.clone(), and update examples/tests/changelog.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
packages/melonjs/src/camera/effects/camera_effect.ts Introduces base lifecycle/type for camera effects.
packages/melonjs/src/camera/effects/shake_effect.ts Extracts shake behavior into an effect.
packages/melonjs/src/camera/effects/fade_effect.ts Unifies fade-in/out into a single effect.
packages/melonjs/src/camera/effects/mask_effect.ts Adds shape-based mask transition effect.
packages/melonjs/src/camera/camera2d.ts Adds effect list + add/get/remove; refactors shake/fade wrappers and FX drawing.
packages/melonjs/src/state/state.ts Adds "fade"/"mask" transitions driven by effects during state changes.
packages/melonjs/src/renderable/trigger.js Updates level Trigger to use FadeEffect/MaskEffect and adds new transition settings.
packages/melonjs/src/video/canvas/canvas_renderer.js Fixes inverted mask behavior using even-odd clip rule.
packages/melonjs/src/geometries/ellipse.ts Makes Ellipse.clone() use ellipsePool.
packages/melonjs/src/level/tiled/TMXObjectFactory.js Replaces deprecated warning helper with console.warn.
packages/melonjs/src/index.ts Re-exports new camera effect classes.
packages/melonjs/tests/camera.spec.js Adds tests for effect list behavior and built-in effects.
packages/melonjs/tests/trigger.spec.js Adds tests for Trigger transition settings and effect creation.
packages/melonjs/tests/ellipse.spec.ts Adds regression test for pooled ellipse cloning.
packages/melonjs/CHANGELOG.md Documents new effect system and related fixes.
packages/examples/src/examples/platformer/entities/player.ts Demonstrates camera shake on hit.
packages/examples/src/examples/platformer/entities/leveltrigger.ts Adds a star-mask Trigger subclass example.
packages/examples/src/examples/platformer/createGame.ts Registers custom Trigger + adjusts renderer const usage.
packages/examples/src/examples/platformer/assets/map/map1.tmx Adds a Trigger instance to showcase transition behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/melonjs/src/camera/camera2d.ts
Comment thread packages/melonjs/src/camera/effects/mask_effect.ts Outdated
Comment thread packages/melonjs/src/state/state.ts Outdated
Comment thread packages/melonjs/src/state/state.ts Outdated
Comment thread packages/melonjs/tests/camera.spec.js
Comment thread packages/melonjs/src/state/state.ts
obiot and others added 2 commits April 18, 2026 18:29
- MaskEffect: keep drawing on hide completion (prevents flash before state switch)
- state.ts: fix eslint-disable-line → eslint-disable-next-line placement
- state.ts: create reveal effects in _onSwitchComplete with post-switch viewport
  (fixes stale camera reference when viewport is reassigned during game.reset)
- Tests: deterministic Math.random stub for shake offset test

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 18, 2026 10:38
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/melonjs/src/renderable/trigger.js
Comment thread packages/melonjs/tests/camera.spec.js Outdated
Comment thread packages/melonjs/tests/camera.spec.js Outdated
- Trigger: onLoaded wrapper preserves levelId argument and this binding
- Tests: Math.random stub wrapped in try/finally for safe restoration
- Tests: renamed "reset should clear all effects" → "reset should clear
  non-persistent effects", added companion test for persistent effects

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@obiot obiot merged commit 4ec2918 into master Apr 18, 2026
6 checks passed
@obiot obiot deleted the feat/camera-effects-system branch April 18, 2026 10:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor camera effects into an extensible effect list with scene transitions

2 participants