Skip to content
Open
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
3594b58
feat: particle support limit velocity over lifetime
hhhhkrx Mar 16, 2026
80803df
feat: add Transform Feedback simulation for LimitVelocityOverLifetime
GuoLei1990 Mar 16, 2026
2193eb7
refactor: clean up TransformFeedbackPrimitive and Simulator
GuoLei1990 Mar 16, 2026
d023c18
fix: align TF shader velocity system with correct module interactions
GuoLei1990 Mar 17, 2026
d1b3bc2
refactor: simplify instance count assignment and streamline condition…
GuoLei1990 Mar 17, 2026
ba3cf33
fix: resolve GL conflict when modules dynamically enable during TF mode
GuoLei1990 Mar 17, 2026
e19d3d5
style: format ParticleGenerator
GuoLei1990 Mar 17, 2026
fcc73e5
refactor: use MeshTopology enum and improve TransformFeedback API
GuoLei1990 Mar 17, 2026
2024ae1
refactor: clean architecture for TransformFeedbackPrimitive
GuoLei1990 Mar 17, 2026
b913778
fix: unbind TF buffer after TF object unbind to clear global binding …
GuoLei1990 Mar 17, 2026
86d670f
refactor: polish TransformFeedbackPrimitive API and naming
GuoLei1990 Mar 17, 2026
33cc9dc
refactor: clean up LimitVelocityOverLifetimeModule
GuoLei1990 Mar 17, 2026
b5ab642
fix: rename u_DragConstant to renderer_LVLDragConstant for naming con…
GuoLei1990 Mar 17, 2026
30141c4
refactor: pass VertexBufferBinding to platform layer instead of raw b…
GuoLei1990 Mar 17, 2026
e1e8bcd
fix: clear stale TF buffer binding before bindBufferRange
GuoLei1990 Mar 17, 2026
efd53e1
fix: ensure VAO A/B always correspond to binding A/B
GuoLei1990 Mar 17, 2026
fc66911
fix: guard limit velocity module enablement on WebGL1
hhhhkrx Mar 17, 2026
c2d491b
fix: use full VOL instantaneous value instead of delta
GuoLei1990 Mar 17, 2026
639e893
fix: correct TF render path velocity split for stretched billboard
hhhhkrx Mar 17, 2026
86caf6a
refactor: move buffer layout to ParticleBufferUtils, rename TF to Fee…
GuoLei1990 Mar 17, 2026
98fc9f6
refactor: extract TransformFeedbackSimulator as reusable base
GuoLei1990 Mar 17, 2026
41755f2
fix: store TF position in simulation space instead of baked world coords
GuoLei1990 Mar 17, 2026
7bbe5a9
fix: persist world-space FOL into base velocity like gravity
hhhhkrx Mar 17, 2026
5795450
refactor: rename _useTFMode to _useTransformFeedback, clean up comments
GuoLei1990 Mar 17, 2026
784f01a
perf: cache simulator reference in writeParticleData
GuoLei1990 Mar 17, 2026
c9c418f
fix: project VOL into LVL target space for dampen and drag
hhhhkrx Mar 17, 2026
ef83408
refactor: remove dead drag code from non-TF render path
GuoLei1990 Mar 17, 2026
4cc87be
perf: reuse cached invWorldRotation in position integration
GuoLei1990 Mar 17, 2026
486e497
refactor: clean up TF naming in ParticleGenerator
GuoLei1990 Mar 17, 2026
dc1f4e1
refactor: rename volVelocity to instantVOLVelocity for clarity
hhhhkrx Mar 17, 2026
fdaf404
refactor: move TF pass after _updateShaderData to avoid duplicate uni…
GuoLei1990 Mar 17, 2026
5c9d35f
fix: guard drag curve min with RENDERER_LVL_DRAG_IS_RANDOM_TWO macro
hhhhkrx Mar 17, 2026
c382b20
refactor: simplify ParticleGenerator feedback code
GuoLei1990 Mar 17, 2026
14e406a
refactor: rename abbreviated variables in LVL shader for clarity
hhhhkrx Mar 17, 2026
554e5d8
refactor: simplify _addFeedbackParticle with Vector3 API
GuoLei1990 Mar 17, 2026
7a207d8
refactor: merge feedback/non-feedback instance buffer upload paths
GuoLei1990 Mar 17, 2026
993c6ce
style: format ParticleGenerator
GuoLei1990 Mar 17, 2026
63fd990
refactor: rename particle_transform_feedback_update to particle_feedb…
GuoLei1990 Mar 17, 2026
24d4600
fix: make e2e case deterministic with fixed random seed
GuoLei1990 Mar 17, 2026
0802398
test: update e2e screenshots for particle cases
GuoLei1990 Mar 17, 2026
dc064eb
fix: move a_Random2 declaration after FOL include to fix missing attr…
GuoLei1990 Mar 17, 2026
ee67e54
fix: use CPU-side macros for a_Random2 conditional declaration
GuoLei1990 Mar 17, 2026
429db08
perf: reuse instance VertexBufferBinding instead of creating per frame
GuoLei1990 Mar 17, 2026
cadf867
fix: use a_Random2.w instead of a_Random0.x for drag random factor
hhhhkrx Mar 18, 2026
cc1ab6b
fix: fix ray and plane when ray origin is on the plane and parallel (…
singlecoder Mar 16, 2026
3455cd6
chore: release v2.0.0-alpha.15
cptbtptpbcptdtptp Mar 17, 2026
7d9f2df
Fix compont props clone bug (#2926)
cptbtptpbcptdtptp Mar 18, 2026
47d64aa
fix: preserve feedback buffer data on resize via GPU buffer copy
GuoLei1990 Mar 18, 2026
0ffa4cf
fix: e2e texture
hhhhkrx Mar 18, 2026
1cb0c20
fix: integrate TF simulator into engine shader pool for device restore
GuoLei1990 Mar 18, 2026
b9a816e
fix: feedback buffer ring-buffer-aware resize with copy/destroy API
GuoLei1990 Mar 18, 2026
2d192a7
fix: unbind COPY_READ/WRITE_BUFFER after copyBufferSubData
GuoLei1990 Mar 18, 2026
30af3da
fix: retire all particles on device restore instead of restoring GPU …
GuoLei1990 Mar 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions e2e/case/particleRenderer-limitVelocity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/**
* @title Particle Limit Velocity Over Lifetime
* @category Particle
*/
import {
AssetType,
BlendMode,
Burst,
Camera,
Color,
Engine,
Entity,
SphereShape,
ParticleCompositeCurve,
ParticleCurveMode,
ParticleGradientMode,
ParticleMaterial,
ParticleRenderer,
ParticleSimulationSpace,
PostProcess,
BloomEffect,
TonemappingEffect,
Texture2D,
WebGLEngine
} from "@galacean/engine";
import { initScreenshot, updateForE2E } from "./.mockForE2E";

WebGLEngine.create({
canvas: "canvas"
}).then((engine) => {
engine.canvas.resizeByClientSize();

const scene = engine.sceneManager.activeScene;
const rootEntity = scene.createRootEntity();
scene.background.solidColor = new Color(0, 0, 0, 1);

const cameraEntity = rootEntity.createChild("camera");
cameraEntity.transform.setPosition(2, 1.43, 30);
const camera = cameraEntity.addComponent(Camera);
camera.fieldOfView = 60;
camera.enableHDR = true;
camera.enablePostProcess = true;

// Post process
const postProcess = rootEntity.addComponent(PostProcess);
const bloom = postProcess.addEffect(BloomEffect);
bloom.intensity.value = 1;
bloom.threshold.value = 0.8;
postProcess.addEffect(TonemappingEffect);

engine.resourceManager
.load({
url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*JPsCSK5LtYkAAAAAAAAAAAAADil6AQ/original",
type: AssetType.Texture2D
})
.then((texture) => {
createParticle(engine, rootEntity, <Texture2D>texture);

updateForE2E(engine, 30);
initScreenshot(engine, camera);
});
});

function createParticle(engine: Engine, rootEntity: Entity, texture: Texture2D): void {
const particleEntity = new Entity(engine, "LimitVelocity");
particleEntity.transform.setPosition(2.006557, 1.43, 12.35);

const particleRenderer = particleEntity.addComponent(ParticleRenderer);
const generator = particleRenderer.generator;
generator.useAutoRandomSeed = false;

const material = new ParticleMaterial(engine);
material.baseColor = new Color(0.2, 0.6, 1.0, 1.0);
material.blendMode = BlendMode.Additive;
material.baseTexture = texture;
particleRenderer.setMaterial(material);

const { main, emission, limitVelocityOverLifetime, colorOverLifetime, velocityOverLifetime } = generator;

// Main
main.duration = 2;
main.isLoop = true;
main.startDelay.constant = 0;
main.startLifetime.constantMin = 0.6;
main.startLifetime.constantMax = 1;
main.startLifetime.mode = ParticleCurveMode.TwoConstants;
main.startSpeed.constantMin = 20;
main.startSpeed.constantMax = 40;
main.startSpeed.mode = ParticleCurveMode.TwoConstants;
main.startSize.constantMin = 0.05;
main.startSize.constantMax = 0.15;
main.startSize.mode = ParticleCurveMode.TwoConstants;
main.startColor.constantMin.set(280 / 255, 670 / 255, 2550 / 255, 1);
main.startColor.constantMax.set(1130 / 255, 740 / 255, 2550 / 255, 1);
main.startColor.mode = ParticleGradientMode.TwoConstants;
main.gravityModifier.constant = 0;
main.simulationSpace = ParticleSimulationSpace.Local;
main.maxParticles = 100;

// Emission
emission.rateOverTime.constant = 0;
emission.addBurst(new Burst(0, new ParticleCompositeCurve(10, 30)));
const sphereShape = new SphereShape();
sphereShape.radius = 0.8;
emission.shape = sphereShape;

// Color over lifetime
colorOverLifetime.enabled = true;
colorOverLifetime.color.mode = ParticleGradientMode.Gradient;
const gradient = colorOverLifetime.color.gradient;
gradient.alphaKeys[0].alpha = 0;
gradient.alphaKeys[1].alpha = 0;
gradient.addAlphaKey(0.2, 1.0);
gradient.addAlphaKey(0.8, 1.0);

velocityOverLifetime.enabled = true;
velocityOverLifetime.velocityX.constant = 1;
velocityOverLifetime.velocityY.constant = 20;
velocityOverLifetime.velocityZ.constant = 1;

// Limit velocity over lifetime
limitVelocityOverLifetime.enabled = true;
limitVelocityOverLifetime.separateAxes = true;
Comment on lines +122 to +123
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify module behavior: enabling is gated by WebGL2.
module_file="$(fd 'LimitVelocityOverLifetimeModule.ts$' | head -n1)"
case_file="$(fd 'particleRenderer-limitVelocity.ts$' | head -n1)"

echo "Module file: ${module_file}"
echo "Case file: ${case_file}"

rg -n -C3 'override set enabled|isWebGL2|_setTransformFeedback' "$module_file"
rg -n -C3 'limitVelocityOverLifetime\.enabled|isWebGL2' "$case_file"

# Optional: inspect similar E2E patterns for capability gating.
rg -n --type=ts -C2 'isWebGL2|TransformFeedback|limitVelocityOverLifetime' e2e/case

Repository: galacean/engine

Length of output: 2685


Fail fast when WebGL2 is unavailable so this test actually exercises limit-velocity.

The module's enabled setter silently returns without enabling on WebGL1 (it checks isWebGL2 and returns early). Add an explicit WebGL2 capability guard before line 122 to ensure the test fails or skips if WebGL2 is not available, rather than silently passing without covering the intended transform-feedback behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/case/particleRenderer-limitVelocity.ts` around lines 122 - 123, Before
enabling limitVelocityOverLifetime, add an explicit WebGL2 capability guard that
checks the renderer/context for WebGL2 (e.g. renderer.capabilities.isWebGL2 or
gl instanceof WebGL2RenderingContext) and fail or skip the test if WebGL2 is not
available; specifically, insert the guard just before the lines that set
limitVelocityOverLifetime.enabled and .separateAxes so the test does not
silently no-op on WebGL1—use a clear early exit such as throwing an
Error("WebGL2 required for limit-velocity test") or invoking the test runner's
skip mechanism.

limitVelocityOverLifetime.limitX = new ParticleCompositeCurve(1);
limitVelocityOverLifetime.limitY = new ParticleCompositeCurve(1);
limitVelocityOverLifetime.limitZ = new ParticleCompositeCurve(0);
limitVelocityOverLifetime.space = ParticleSimulationSpace.World;
limitVelocityOverLifetime.dampen = 0.25;
limitVelocityOverLifetime.drag = new ParticleCompositeCurve(0.0);
limitVelocityOverLifetime.multiplyDragByParticleSize = true;
limitVelocityOverLifetime.multiplyDragByParticleVelocity = true;

rootEntity.addChild(particleEntity);
}
8 changes: 7 additions & 1 deletion e2e/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,12 @@ export const E2E_CONFIG = {
threshold: 0,
diffPercentage: 0.1630209
},
limitVelocityOverLifetime: {
category: "Particle",
caseFileName: "particleRenderer-limitVelocity",
threshold: 0,
diffPercentage: 0.0364
},
textureSheetAnimation: {
category: "Particle",
caseFileName: "particleRenderer-textureSheetAnimation",
Expand All @@ -345,7 +351,7 @@ export const E2E_CONFIG = {
category: "Particle",
caseFileName: "particleRenderer-shape-mesh",
threshold: 0,
diffPercentage: 0.0162
diffPercentage: 0.01698
},
particleEmissive: {
category: "Particle",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions e2e/fixtures/originImage/Particle_particleRenderer-force.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion e2e/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@galacean/engine-e2e",
"private": true,
"version": "2.0.0-alpha.14",
"version": "2.0.0-alpha.15",
"license": "MIT",
"scripts": {
"case": "vite serve .dev --config .dev/vite.config.js",
Expand Down
2 changes: 1 addition & 1 deletion examples/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@galacean/engine-examples",
"version": "2.0.0-alpha.14",
"version": "2.0.0-alpha.15",
"private": true,
"license": "MIT",
"main": "dist/main.js",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@galacean/engine-root",
"version": "2.0.0-alpha.14",
"version": "2.0.0-alpha.15",
"packageManager": "pnpm@9.3.0",
"private": true,
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@galacean/engine-core",
"version": "2.0.0-alpha.14",
"version": "2.0.0-alpha.15",
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org"
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/2d/sprite/SpriteMask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,8 @@ export class SpriteMask extends Renderer implements ISpriteRenderer {
/**
* @internal
*/
override _cloneTo(target: SpriteMask, srcRoot: Entity, targetRoot: Entity): void {
super._cloneTo(target, srcRoot, targetRoot);
override _cloneTo(target: SpriteMask): void {
super._cloneTo(target);
target.sprite = this._sprite;
}

Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/2d/sprite/SpriteRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,8 @@ export class SpriteRenderer extends Renderer implements ISpriteRenderer {
/**
* @internal
*/
override _cloneTo(target: SpriteRenderer, srcRoot: Entity, targetRoot: Entity): void {
super._cloneTo(target, srcRoot, targetRoot);
override _cloneTo(target: SpriteRenderer): void {
super._cloneTo(target);
target.sprite = this._sprite;
target.drawMode = this._drawMode;
}
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/2d/text/TextRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,8 +333,8 @@ export class TextRenderer extends Renderer implements ITextRenderer {
/**
* @internal
*/
override _cloneTo(target: TextRenderer, srcRoot: Entity, targetRoot: Entity): void {
super._cloneTo(target, srcRoot, targetRoot);
override _cloneTo(target: TextRenderer): void {
super._cloneTo(target);
target.font = this._font;
target._subFont = this._subFont;
}
Expand Down Expand Up @@ -458,8 +458,8 @@ export class TextRenderer extends Renderer implements ITextRenderer {

// prettier-ignore
const e0 = e[0], e1 = e[1], e2 = e[2],
e4 = e[4], e5 = e[5], e6 = e[6],
e12 = e[12], e13 = e[13], e14 = e[14];
e4 = e[4], e5 = e[5], e6 = e[6],
e12 = e[12], e13 = e[13], e14 = e[14];

const up = TextRenderer._tempVec31.set(e4, e5, e6);
const right = TextRenderer._tempVec30.set(e0, e1, e2);
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/Camera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -829,7 +829,7 @@ export class Camera extends Component {
/**
* @internal
*/
_cloneTo(target: Camera, srcRoot: Entity, targetRoot: Entity): void {
_cloneTo(target: Camera): void {
this._renderTarget?._addReferCount(1);
}

Expand Down
9 changes: 8 additions & 1 deletion packages/core/src/Component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { IReferable } from "./asset/IReferable";
import { EngineObject } from "./base";
import { assignmentClone, ignoreClone } from "./clone/CloneManager";
import { CloneUtils } from "./clone/CloneUtils";
import { Entity } from "./Entity";
import { ActiveChangeFlag } from "./enums/ActiveChangeFlag";
import { Scene } from "./Scene";
Expand All @@ -10,7 +11,6 @@ import { Scene } from "./Scene";
*/
export class Component extends EngineObject {
/** @internal */
@ignoreClone
_entity: Entity;

/** @internal */
Expand Down Expand Up @@ -154,6 +154,13 @@ export class Component extends EngineObject {
}
}

/**
* @internal
*/
_remap<T extends Component>(srcRoot: Entity, targetRoot: Entity): T {
return CloneUtils.remapComponent(srcRoot, targetRoot, this) as unknown as T;
}

protected _addResourceReferCount(resource: IReferable, count: number): void {
this._entity._isTemplate || resource._addReferCount(count);
}
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/Entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Transform } from "./Transform";
import { UpdateFlagManager } from "./UpdateFlagManager";
import { ReferResource } from "./asset/ReferResource";
import { EngineObject } from "./base";
import { CloneUtils } from "./clone/CloneUtils";
import { ComponentCloner } from "./clone/ComponentCloner";
import { ActiveChangeFlag } from "./enums/ActiveChangeFlag";
import { EntityModifyFlags } from "./enums/EntityModifyFlags";
Expand Down Expand Up @@ -431,6 +432,13 @@ export class Entity extends EngineObject {
return this._updateFlagManager.createFlag(BoolUpdateFlag);
}

/**
* @internal
*/
_remap(srcRoot: Entity, targetRoot: Entity): Entity {
return CloneUtils.remapEntity(srcRoot, targetRoot, this);
}

/**
* @internal
*/
Expand Down
6 changes: 2 additions & 4 deletions packages/core/src/Renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { RenderContext } from "./RenderPipeline/RenderContext";
import { SubRenderElement } from "./RenderPipeline/SubRenderElement";
import { Transform, TransformModifyFlags } from "./Transform";
import { assignmentClone, deepClone, ignoreClone } from "./clone/CloneManager";
import { IComponentCustomClone } from "./clone/ComponentCloner";
import { SpriteMaskLayer } from "./enums/SpriteMaskLayer";
import { Material } from "./material";
import { ShaderMacro, ShaderProperty } from "./shader";
Expand All @@ -21,7 +20,7 @@ import { ShaderDataGroup } from "./shader/enums/ShaderDataGroup";
* @decorator `@dependentComponents(Transform, DependentMode.CheckOnly)`
*/
@dependentComponents(Transform, DependentMode.CheckOnly)
export class Renderer extends Component implements IComponentCustomClone {
export class Renderer extends Component {
private static _tempVector0 = new Vector3();

private static _receiveShadowMacro = ShaderMacro.getByName("RENDERER_IS_RECEIVE_SHADOWS");
Expand Down Expand Up @@ -66,7 +65,6 @@ export class Renderer extends Component implements IComponentCustomClone {
protected _rendererLayer: Vector4 = new Vector4();
@ignoreClone
protected _bounds: BoundingBox = new BoundingBox();
@ignoreClone
protected _transformEntity: Entity;

@deepClone
Expand Down Expand Up @@ -351,7 +349,7 @@ export class Renderer extends Component implements IComponentCustomClone {
/**
* @internal
*/
_cloneTo(target: Renderer, srcRoot: Entity, targetRoot: Entity): void {
_cloneTo(target: Renderer): void {
const materials = this._materials;
for (let i = 0, n = materials.length; i < n; i++) {
target._setMaterial(i, materials[i]);
Expand Down
3 changes: 1 addition & 2 deletions packages/core/src/Transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ export class Transform extends Component {

@ignoreClone
protected _isParentDirty: boolean = true;
@ignoreClone
private _parentTransformCache: Transform = null;
@ignoreClone
private _dirtyFlag: number = TransformModifyFlags.LqLmWmWpWeWqWsWus;
Expand Down Expand Up @@ -581,7 +580,7 @@ export class Transform extends Component {
/**
* @internal
*/
_cloneTo(target: Transform, srcRoot: Entity, targetRoot: Entity): void {
_cloneTo(target: Transform): void {
const { _position: position, _rotation: rotation, _scale: scale } = target;

// @ts-ignore
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/animation/Animator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ export class Animator extends Component {
/**
* @internal
*/
_cloneTo(target: Animator, srcRoot: Entity, targetRoot: Entity): void {
_cloneTo(target: Animator): void {
const animatorController = target._animatorController;
if (animatorController) {
target._addResourceReferCount(animatorController, 1);
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/audio/AudioSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ export class AudioSource extends Component {
/**
* @internal
*/
_cloneTo(target: AudioSource, srcRoot: Entity, targetRoot: Entity): void {
_cloneTo(target: AudioSource): void {
target._clip?._addReferCount(1);
target._gainNode.gain.setValueAtTime(target._volume, AudioManager.getContext().currentTime);
}
Expand Down
Loading
Loading