editor: add resize SFX triggered while dragging resize handles

Register a `resize` sound effect (three round-robin variations with
pitch/pan jitter and an 80ms gap) and emit it from the linear/radial
resize drag handler whenever the snapped+clamped value steps, so width /
length / depth / radius / height drags tick as the size changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Wassim SAMAD
2026-06-22 11:09:02 -04:00
co-authored by Claude Opus 4.8
parent b2f1a8432e
commit 9bfbcc9f01
6 changed files with 21 additions and 0 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -668,6 +668,11 @@ function LinearArrow({
? 1 ? 1
: -1 : -1
// Last value an emitted resize tick fired at — a new tick fires only
// when the (snapped + clamped) value actually changes, so the cue
// tracks real size steps instead of every sub-pixel pointer jitter.
let lastTickValue = initialValue
return { return {
overrideId, overrideId,
onBegin: () => { onBegin: () => {
@@ -695,6 +700,10 @@ function LinearArrow({
? snapScalar(rawNext, gridSnapStep) ? snapScalar(rawNext, gridSnapStep)
: rawNext : rawNext
const next = Math.min(maxBound, Math.max(minBound, snappedNext)) const next = Math.min(maxBound, Math.max(minBound, snappedNext))
if (next !== lastTickValue) {
lastTickValue = next
sfxEmitter.emit('sfx:resize')
}
const patch = descriptor.apply(initialNode as never, next, sceneApi) as Partial<AnyNode> const patch = descriptor.apply(initialNode as never, next, sceneApi) as Partial<AnyNode>
// Let the kind publish live guides for the edge being resized. // Let the kind publish live guides for the edge being resized.
onDrag?.({ ...(initialNode as object), ...patch } as AnyNode, sceneApi) onDrag?.({ ...(initialNode as object), ...patch } as AnyNode, sceneApi)
+2
View File
@@ -10,6 +10,7 @@ type SFXEvents = {
'sfx:item-pick': undefined 'sfx:item-pick': undefined
'sfx:item-place': undefined 'sfx:item-place': undefined
'sfx:item-rotate': undefined 'sfx:item-rotate': undefined
'sfx:resize': undefined
'sfx:structure-build-start': undefined 'sfx:structure-build-start': undefined
'sfx:structure-build': undefined 'sfx:structure-build': undefined
'sfx:structure-delete': undefined 'sfx:structure-delete': undefined
@@ -40,6 +41,7 @@ export function initSFXBus() {
sfxEmitter.on('sfx:item-pick', () => playSFX('itemPick')) sfxEmitter.on('sfx:item-pick', () => playSFX('itemPick'))
sfxEmitter.on('sfx:item-place', () => playSFX('itemPlace')) sfxEmitter.on('sfx:item-place', () => playSFX('itemPlace'))
sfxEmitter.on('sfx:item-rotate', () => playSFX('itemRotate')) sfxEmitter.on('sfx:item-rotate', () => playSFX('itemRotate'))
sfxEmitter.on('sfx:resize', () => playSFX('resize'))
sfxEmitter.on('sfx:structure-build-start', () => playSFX('structureBuildStart')) sfxEmitter.on('sfx:structure-build-start', () => playSFX('structureBuildStart'))
sfxEmitter.on('sfx:structure-build', () => playSFX('structureBuildEnd')) sfxEmitter.on('sfx:structure-build', () => playSFX('structureBuildEnd'))
sfxEmitter.on('sfx:structure-delete', () => playSFX('structureDelete')) sfxEmitter.on('sfx:structure-delete', () => playSFX('structureDelete'))
+10
View File
@@ -59,6 +59,16 @@ export const SFX: Record<string, SFXConfig> = {
volumeRange: [0.92, 1.0], volumeRange: [0.92, 1.0],
panJitter: 0.15, panJitter: 0.15,
}, },
// Ticks as a resize handle is dragged across snap steps. Fires in rapid
// succession, so it mirrors gridSnap: three variations cycled round-robin
// with pitch/pan jitter and a gap so the run reads as texture, not a tone.
resize: {
src: ['/audios/sfx/resize_0.mp3', '/audios/sfx/resize_1.mp3', '/audios/sfx/resize_2.mp3'],
rateRange: [0.98, 1.02],
volumeRange: [0.26, 0.34],
panJitter: 0.15,
minIntervalMs: 80,
},
// Fired when a structure draft begins (first click of a wall/slab/etc). // Fired when a structure draft begins (first click of a wall/slab/etc).
structureBuildStart: { structureBuildStart: {
src: '/audios/sfx/structure_build_start.mp3', src: '/audios/sfx/structure_build_start.mp3',