From 711c590b0cd636251b8ab7dad9fe8639cff85358 Mon Sep 17 00:00:00 2001 From: Patrick Bozic Date: Mon, 5 Sep 2022 18:31:54 +0800 Subject: [PATCH 1/6] Refactor animations-baker.js --- animations-baker.js | 192 +++++++++++++++++++++++++------------------- 1 file changed, 108 insertions(+), 84 deletions(-) diff --git a/animations-baker.js b/animations-baker.js index de2675fb55..f12f63f2cc 100644 --- a/animations-baker.js +++ b/animations-baker.js @@ -540,104 +540,128 @@ const {CharsetEncoder} = require('three/examples/js/libs/mmdparser.js'); 'sword_topdown_slash.fbx', 'sword_topdown_slash_step.fbx', ]; + const animationComboIndices = comboAnimationNames.map(comboAnimationName => { const animation = animations.find(a => a.name === comboAnimationName); const {tracks, object} = animation; // console.log('got interpolants', object, animation.name); - const bones = []; - object.traverse(o => { - if (o.isBone) { - const bone = o; - bone.initialPosition = bone.position.clone(); - bone.initialQuaternion = bone.quaternion.clone(); - bones.push(bone); + + const validateAndRemoveScaleTracks = () => { + const epsilon = 0.2; + const allOnesEpsilon = arr => arr.every(v => Math.abs(1 - v) < epsilon); + const tracksToRemove = []; + + for (const track of tracks) { + if (/\.scale$/.test(track.name)) { + if (allOnesEpsilon(track.values)) { + const index = tracks.indexOf(track); + tracksToRemove.push(index); + } else { + throw new Error(`This track has invalid values. All scale transforms must be set to 1. Aborting.\n + Animation: ${animation.name}, Track: ${track.name}, values: \n + ${track.values}`); + } + } } - }); - // console.log('got bones', bones.map(b => b.name)); - const rootBone = object; // not really a bone - const leftHandBone = bones.find(b => b.name === 'mixamorigLeftHand'); - const rightHandBone = bones.find(b => b.name === 'mixamorigRightHand'); - const epsilon = 0.2; - const allOnesEpsilon = arr => arr.every(v => Math.abs(1 - v) < epsilon); - - const bonePositionInterpolants = {}; - const boneQuaternionInterpolants = {}; - const tracksToRemove = []; - for (const track of tracks) { - if (/\.position$/.test(track.name)) { - const boneName = track.name.replace(/\.position$/, ''); - // const bone = bones.find(b => b.name === boneName); - const boneInterpolant = new THREE.LinearInterpolant(track.times, track.values, track.getValueSize()); - bonePositionInterpolants[boneName] = boneInterpolant; - } else if (/\.quaternion$/.test(track.name)) { - const boneName = track.name.replace(/\.quaternion$/, ''); - // const bone = bones.find(b => b.name === boneName); - const boneInterpolant = new THREE.QuaternionLinearInterpolant(track.times, track.values, track.getValueSize()); - boneQuaternionInterpolants[boneName] = boneInterpolant; - } else if (/\.scale$/.test(track.name)) { - if (allOnesEpsilon(track.values)) { - const index = tracks.indexOf(track); - tracksToRemove.push(index); + // remove scale transform tracks as they won't be used; + let i = tracksToRemove.length; + while (i--) { + tracks.splice(tracksToRemove[i], 1); + } + }; + validateAndRemoveScaleTracks(); + + const getBoneInterpolants = () => { + const bonePositionInterpolants = {}; + const boneQuaternionInterpolants = {}; + + for (const track of tracks) { + if (/\.position$/.test(track.name)) { + const boneName = track.name.replace(/\.position$/, ''); + // const bone = bones.find(b => b.name === boneName); + const boneInterpolant = new THREE.LinearInterpolant(track.times, track.values, track.getValueSize()); + bonePositionInterpolants[boneName] = boneInterpolant; + } else if (/\.quaternion$/.test(track.name)) { + const boneName = track.name.replace(/\.quaternion$/, ''); + // const bone = bones.find(b => b.name === boneName); + const boneInterpolant = new THREE.QuaternionLinearInterpolant(track.times, track.values, track.getValueSize()); + boneQuaternionInterpolants[boneName] = boneInterpolant; } else { - throw new Error(`This track has invalid values. All scale transforms must be set to 1. Aborting.\n Animation: ${animation.name}, Track: ${track.name}, values: \n ${track.values}`); + console.warn('unknown track name', animation.name, track); } - } else { - console.warn('unknown track name', animation.name, track); } - } - // remove scale transform tracks as they won't be used; - let i = tracksToRemove.length; - while (i--) { - tracks.splice(tracksToRemove[i], 1); - } - - const walkBufferSize = 256; - const leftHandDeltas = new Float32Array(walkBufferSize); - const rightHandDeltas = new Float32Array(walkBufferSize); + return {bonePositionInterpolants, boneQuaternionInterpolants}; + }; - let lastRightHandPos = new THREE.Vector3(); - let lastLeftHandPos = new THREE.Vector3(); + const extractBones = root => { + const bones = []; + root.traverse(o => { + if (o.isBone) { + const bone = o; + bone.initialPosition = bone.position.clone(); + bone.initialQuaternion = bone.quaternion.clone(); + bones.push(bone); + } + }); + return bones; + }; + const walkBufferSize = 256; let maxRightDeltaIndex = 0; - let maxLeftDeltaIndex = 0; + let maxLeftDeltaIndex = 0; - for (let i = 0; i < walkBufferSize; i++) { - const f = i / (walkBufferSize - 1); - for (const bone of bones) { - const positionInterpolant = bonePositionInterpolants[bone.name]; - const quaternionInterpolant = boneQuaternionInterpolants[bone.name]; - if (positionInterpolant) { - const pv = positionInterpolant.evaluate(f * animation.duration); - bone.position.fromArray(pv); - } else { - bone.position.copy(bone.initialPosition); - } - if (quaternionInterpolant) { - const qv = quaternionInterpolant.evaluate(f * animation.duration); - bone.quaternion.fromArray(qv); - } else { - bone.quaternion.copy(bone.initialQuaternion); + const leftHandDeltas = new Float32Array(walkBufferSize); + const rightHandDeltas = new Float32Array(walkBufferSize); + + const bake = () => { + const rootBone = object; // not really a bone + const bones = extractBones(rootBone); + const leftHandBone = bones.find(b => b.name === 'mixamorigLeftHand'); + const rightHandBone = bones.find(b => b.name === 'mixamorigRightHand'); + + const {bonePositionInterpolants, boneQuaternionInterpolants} = getBoneInterpolants(); + + let lastRightHandPos = new THREE.Vector3(); + let lastLeftHandPos = new THREE.Vector3(); + + for (let i = 0; i < walkBufferSize; i++) { + const f = i / (walkBufferSize - 1); + for (const bone of bones) { + const positionInterpolant = bonePositionInterpolants[bone.name]; + const quaternionInterpolant = boneQuaternionInterpolants[bone.name]; + if (positionInterpolant) { + const pv = positionInterpolant.evaluate(f * animation.duration); + bone.position.fromArray(pv); + } else { + bone.position.copy(bone.initialPosition); + } + if (quaternionInterpolant) { + const qv = quaternionInterpolant.evaluate(f * animation.duration); + bone.quaternion.fromArray(qv); + } else { + bone.quaternion.copy(bone.initialQuaternion); + } } + rootBone.updateMatrixWorld(true); + + const fbxScale = 100; + const leftHand = new THREE.Vector3().setFromMatrixPosition(leftHandBone.matrixWorld).divideScalar(fbxScale); + const rightHand = new THREE.Vector3().setFromMatrixPosition(rightHandBone.matrixWorld).divideScalar(fbxScale); + + const leftHandDelta = i === 0 ? 0 : lastLeftHandPos.distanceTo(leftHand); + const rightHandDelta = i === 0 ? 0 : lastRightHandPos.distanceTo(rightHand); + + leftHandDeltas[i] = leftHandDelta * 1000; + rightHandDeltas[i] = rightHandDelta * 1000; + + maxRightDeltaIndex = rightHandDeltas[i] > rightHandDeltas[maxRightDeltaIndex] ? i : maxRightDeltaIndex; + maxLeftDeltaIndex = leftHandDeltas[i] > leftHandDeltas[maxRightDeltaIndex] ? i : maxLeftDeltaIndex; + + lastLeftHandPos.set(leftHand.x, leftHand.y, leftHand.z); + lastRightHandPos.set(rightHand.x, rightHand.y, rightHand.z); } - rootBone.updateMatrixWorld(true); - const fbxScale = 100; - const leftHand = new THREE.Vector3().setFromMatrixPosition(leftHandBone.matrixWorld).divideScalar(fbxScale); - const rightHand = new THREE.Vector3().setFromMatrixPosition(rightHandBone.matrixWorld).divideScalar(fbxScale); - - const leftHandDelta = i === 0 ? 0 : lastLeftHandPos.distanceTo(leftHand); - const rightHandDelta = i === 0 ? 0 : lastRightHandPos.distanceTo(rightHand); - - leftHandDeltas[i] = leftHandDelta * 1000; - rightHandDeltas[i] = rightHandDelta * 1000; - - maxRightDeltaIndex = rightHandDeltas[i] > rightHandDeltas[maxRightDeltaIndex] ? i : maxRightDeltaIndex; - maxLeftDeltaIndex = leftHandDeltas[i] > leftHandDeltas[maxRightDeltaIndex] ? i : maxLeftDeltaIndex; - - - lastLeftHandPos.set(leftHand.x, leftHand.y, leftHand.z); - lastRightHandPos.set(rightHand.x, rightHand.y, rightHand.z); - } - + }; + bake(); // console.log('got', leftHandDeltas, rightHandDeltas, maxLeftDeltaIndex, maxRightDeltaIndex); From 2aed12a1b268b4b3bbf4e8a6696ef8990d2e3740 Mon Sep 17 00:00:00 2001 From: Patrick Bozic Date: Tue, 6 Sep 2022 22:34:40 +0800 Subject: [PATCH 2/6] Refactor character-sfx.js --- character-sfx.js | 812 ++++++++++++++++++++++++----------------------- 1 file changed, 411 insertions(+), 401 deletions(-) diff --git a/character-sfx.js b/character-sfx.js index 20301bfc6a..d878a86a08 100644 --- a/character-sfx.js +++ b/character-sfx.js @@ -72,411 +72,21 @@ const _getActionFrameIndex = (f, frameTimes) => { return i; }; -class CharacterSfx extends EventTarget{ +class Sfx extends EventTarget { constructor(player) { super(); this.player = player; - - this.lastJumpState = false; - this.lastStepped = [false, false]; - this.lastWalkTime = 0; - - - - this.lastSwordComboName = null; - this.swordComboStartTime = 0; - - this.lastEatFrameIndex = -1; - this.lastDrinkFrameIndex = -1; - - this.narutoRunStartTime = 0; - this.narutoRunFinishTime = 0; - this.narutoRunTrailSoundStartTime = 0; - this.narutoRunTurnSoundStartTime = 0; - this.currentQ = new THREE.Quaternion(); - this.preQ = new THREE.Quaternion(); - this.arr = [0, 0, 0, 0]; - - this.startRunningTime = 0; - this.willGasp = false; - - this.oldNarutoRunSound = null; - this.lastEmote = null; - - this.currentStep = null; - this.currentSwimmingHand = null; - this.setSwimmingHand = true; - - this.lastLandState = false; - + this.oldGrunt = null; + this.soundFiles = sounds.getSoundFiles(); + this.actions = []; } - update(timestamp, timeDiffS) { - if (!this.player.avatar) { - return; - } - - const timeSeconds = timestamp/1000; - const currentSpeed = localVector.set(this.player.avatar.velocity.x, 0, this.player.avatar.velocity.z).length(); - - const idleWalkFactor = Math.min(Math.max((currentSpeed - idleFactorSpeed) / (walkFactorSpeed - idleFactorSpeed), 0), 1); - const walkRunFactor = Math.min(Math.max((currentSpeed - walkFactorSpeed) / (runFactorSpeed - walkFactorSpeed), 0), 1); - const crouchFactor = Math.min(Math.max(1 - (this.player.avatar.crouchTime / crouchMaxTime), 0), 1); - - const soundFiles = sounds.getSoundFiles(); - // const soundFileAudioBuffer = sounds.getSoundFileAudioBuffer(); - - // jump - const _handleJump = () => { - if (this.player.avatar.jumpState && !this.lastJumpState) { - sounds.playSoundName('jump'); - - // play jump grunt - if(this.player.hasAction('jump') && this.player.getAction('jump').trigger === 'jump'){ - this.playGrunt('jump'); - } - } /*else if (this.lastJumpState && !this.player.avatar.jumpState) { - sounds.playSoundName('land'); - }*/ - if(this.player.avatar.landState && !this.lastLandState){ - sounds.playSoundName('land'); - } - this.lastLandState = this.player.avatar.landState; - this.lastJumpState = this.player.avatar.jumpState; - }; - _handleJump(); - - // step - const _handleStep = () => { - if (idleWalkFactor > 0.7 && !this.player.avatar.jumpState && !this.player.avatar.fallLoopState && !this.player.avatar.flyState && !this.player.hasAction('swim')) { - const isRunning = walkRunFactor > 0.5; - const isCrouching = crouchFactor > 0.5; - const isNarutoRun = this.player.avatar.narutoRunState; - const walkRunAnimationName = (() => { - if (isNarutoRun) { - return 'naruto run.fbx'; - } else { - const animationAngles = isCrouching ? - Avatar.getClosest2AnimationAngles('crouch', this.player.avatar.getAngle()) - : - (isRunning ? - Avatar.getClosest2AnimationAngles('run', this.player.avatar.getAngle()) - : - Avatar.getClosest2AnimationAngles('walk', this.player.avatar.getAngle()) - ); - return animationAngles[0].name; - } - })(); - const localSoundFiles = (() => { - if (isNarutoRun) { - return soundFiles.narutoRun; - } else if (isCrouching) { - return soundFiles.walk; - } else if (isRunning) { - return soundFiles.run; - } else { - return soundFiles.walk; - } - })(); - const animations = Avatar.getAnimations(); - const animation = animations.find(a => a.name === walkRunAnimationName); - const animationStepIndices = Avatar.getAnimationStepIndices(); - const animationIndices = animationStepIndices.find(i => i.name === walkRunAnimationName); - const {leftStepIndices, rightStepIndices} = animationIndices; - - const offset = offsets[walkRunAnimationName] ?? 0; // ?? window.lol; - const _getStepIndex = timeSeconds => { - const timeMultiplier = walkRunAnimationName === 'naruto run.fbx' ? narutoRunTimeFactor : 1; - const walkTime = (timeSeconds * timeMultiplier + offset) % animation.duration; - const walkFactor = walkTime / animation.duration; - const stepIndex = Math.floor(mod(walkFactor, 1) * leftStepIndices.length); - return stepIndex; - }; - - const startIndex = _getStepIndex(this.lastWalkTime); - const endIndex = _getStepIndex(timeSeconds); - for (let i = startIndex;; i++) { - i = i % leftStepIndices.length; - if (i !== endIndex) { - if (leftStepIndices[i] && !this.lastStepped[0] && !this.player.avatar.narutoRunState && timeSeconds-this.narutoRunFinishTime>0.5) { - const candidateAudios = localSoundFiles//.filter(a => a.paused); - if (candidateAudios.length > 0) { - /* for (const a of candidateAudios) { - !a.paused && a.pause(); - } */ - this.currentStep = 'left'; - const audioSpec = candidateAudios[Math.floor(Math.random() * candidateAudios.length)]; - sounds.playSound(audioSpec); - } - } - this.lastStepped[0] = leftStepIndices[i]; - - if (rightStepIndices[i] && !this.lastStepped[1] && !this.player.avatar.narutoRunState && timeSeconds-this.narutoRunFinishTime>0.5) { - const candidateAudios = localSoundFiles// .filter(a => a.paused); - if (candidateAudios.length > 0) { - /* for (const a of candidateAudios) { - !a.paused && a.pause(); - } */ - this.currentStep = 'right'; - const audioSpec = candidateAudios[Math.floor(Math.random() * candidateAudios.length)]; - sounds.playSound(audioSpec); - } - } - this.lastStepped[1] = rightStepIndices[i]; - } else { - break; - } - } - - this.lastWalkTime = timeSeconds; - } - - }; - - if (!this.player.hasAction('sit')) { - _handleStep(); - } - - const _handleSwim = () => { - if(this.player.hasAction('swim')){ - // const candidateAudios = soundFiles.water; - // console.log(candidateAudios); - if(this.player.getAction('swim').animationType === 'breaststroke'){ - if(this.setSwimmingHand && this.player.actionInterpolants.movements.get() % breaststrokeDuration <= breaststrokeOffset){ - this.setSwimmingHand = false; - this.currentSwimmingHand = null; - } - else if(!this.setSwimmingHand && this.player.actionInterpolants.movements.get() % breaststrokeDuration > breaststrokeOffset){ - let regex = new RegExp('^water/swim[0-9]*.wav$'); - const candidateAudios = soundFiles.water.filter(f => regex.test(f.name)); - const audioSpec = candidateAudios[Math.floor(Math.random() * candidateAudios.length)]; - if(this.player.getAction('swim').onSurface) - sounds.playSound(audioSpec); - - this.setSwimmingHand = true; - this.currentSwimmingHand = 'right'; - } - - } - else if(this.player.getAction('swim').animationType === 'freestyle'){ - let regex = new RegExp('^water/swim_fast[0-9]*.wav$'); - const candidateAudios = soundFiles.water.filter(f => regex.test(f.name)); - const audioSpec = candidateAudios[Math.floor(Math.random() * candidateAudios.length)]; - - if(this.setSwimmingHand && this.player.actionInterpolants.movements.get() % freestyleDuration <= freestyleOffset){ - // console.log('left hand') - if(this.player.getAction('swim').onSurface) - sounds.playSound(audioSpec); - this.currentSwimmingHand = 'left'; - this.setSwimmingHand = false; - } - else if(!this.setSwimmingHand && this.player.actionInterpolants.movements.get() % freestyleDuration > freestyleOffset){ - // console.log('right hand') - if(this.player.getAction('swim').onSurface) - sounds.playSound(audioSpec); - this.currentSwimmingHand = 'right'; - this.setSwimmingHand = true; - } - } - } - } - - _handleSwim(); - - - const _handleNarutoRun = () => { - - this.currentQ.copy(this.player.quaternion); - - let temp=this.currentQ.angleTo(this.preQ); - for(let i=0;i<4;i++){ - let temp2=this.arr[i]; - this.arr[i]=temp; - temp=temp2; - } - - - - if(this.player.avatar.narutoRunState){ - if(this.narutoRunStartTime===0){ - this.narutoRunStartTime=timeSeconds; - sounds.playSound(soundFiles.sonicBoom[0]); - this.playGrunt('narutoRun'); - } - else { - if(this.arr.reduce((a,b)=>a+b) >= Math.PI/3){ - - this.arr.fill(0) - if(timeSeconds - this.narutoRunTurnSoundStartTime>soundFiles.sonicBoom[3].duration-0.9 || this.narutoRunTurnSoundStartTime==0){ - sounds.playSound(soundFiles.sonicBoom[3]); - this.narutoRunTurnSoundStartTime = timeSeconds; - } - - } - - if(timeSeconds - this.narutoRunTrailSoundStartTime>soundFiles.sonicBoom[2].duration-0.2 || this.narutoRunTrailSoundStartTime==0){ - - const localSound = sounds.playSound(soundFiles.sonicBoom[2]); - this.oldNarutoRunSound = localSound; - localSound.addEventListener('ended', () => { - if (this.oldNarutoRunSound === localSound) { - this.oldNarutoRunSound = null; - } - }); - - this.narutoRunTrailSoundStartTime = timeSeconds; - } - } - - // if naruto run play more than 2 sec, set willGasp - if(timeSeconds - this.narutoRunStartTime > 2){ - this.willGasp = true; - } - - } - if(!this.player.avatar.narutoRunState && this.narutoRunStartTime!=0 ){ - this.narutoRunStartTime=0; - this.narutoRunFinishTime=timeSeconds; - this.narutoRunTrailSoundStartTime=0; - this.narutoRunTurnSoundStartTime=0; - sounds.playSound(soundFiles.sonicBoom[1]); - if (this.oldNarutoRunSound) { - !this.oldNarutoRunSound.paused && this.oldNarutoRunSound.stop(); - this.oldNarutoRunSound = null; - } - } - this.preQ.x=this.currentQ.x; - this.preQ.y=this.currentQ.y; - this.preQ.z=this.currentQ.z; - this.preQ.w=this.currentQ.w; - - }; - _handleNarutoRun(); - - // combo - const dispatchComboSoundEvent = (soundIndex) =>{ - this.dispatchEvent(new MessageEvent('meleewhoosh', { - data: { - index: soundIndex - }, - })); - } - const _handleCombo = () => { - if (this.player.hasAction('use') && this.player.getAction('use').behavior === 'sword') { - const comboAnimationName = this.player.getAction('use').animationCombo ? - aimAnimations[this.player.getAction('use').animationCombo[this.player.avatar.useAnimationIndex]] - : null; - if (comboAnimationName) { - if (comboAnimationName !== this.lastSwordComboName) { - this.swordComboStartTime = timeSeconds; - this.alreadyPlayComboSound = false; - } - const animations = Avatar.getAnimations(); - const animation = animations.find(a => a.name === comboAnimationName); - const animationComboIndices = Avatar.getanimationComboIndices(); - const animationIndices = animationComboIndices.find(i => i.name === comboAnimationName); - const handDeltas = this.player.getAction('use').boneAttachment === 'leftHand' ? animationIndices.rightHandDeltas : animationIndices.leftHandDeltas; - const maxDeltaIndex = this.player.getAction('use').boneAttachment === 'leftHand' ? animationIndices.maxRightDeltaIndex : animationIndices.maxLeftDeltaIndex; - - const ratio = (timeSeconds - this.swordComboStartTime) / animation.duration; - if (ratio <= 1 && !this.alreadyPlayComboSound) { - const index = Math.floor(ratio * handDeltas.length); - if (index > maxDeltaIndex) { - this.alreadyPlayComboSound = true; - this.playGrunt('attack'); - const soundIndex = this.player.avatar.useAnimationIndex; - dispatchComboSoundEvent(soundIndex); - } - } - } - this.lastSwordComboName = comboAnimationName; - } - - }; - - _handleCombo(); - - const _handleGasp = () =>{ - const isRunning = currentSpeed > 0.5; - if(isRunning){ - if(this.startRunningTime === 0) - this.startRunningTime = timeSeconds; - } - else{ - if(this.startRunningTime !== 0 && this.willGasp && !this.player.avatar.narutoRunState){ - this.playGrunt('gasp'); - } - this.willGasp = false; - this.startRunningTime = 0; - } - - if(timeSeconds - this.startRunningTime > 5 && this.startRunningTime !== 0){ - this.willGasp = true; - } - - - } - _handleGasp(); - - const _handleFood = () => { - const useAction = this.player.getAction('use'); - if (useAction) { - const _handleEat = () => { - const v = this.player.actionInterpolants.use.get(); - const eatFrameIndex = _getActionFrameIndex(v, eatFrameIndices); - - // console.log('chomp', v, eatFrameIndex, this.lastEatFrameIndex); - if (eatFrameIndex !== 0 && eatFrameIndex !== this.lastEatFrameIndex) { - sounds.playSoundName('chomp'); - // control mouth movement - this.player.characterBehavior.setMouthMoving(0.04,0.04,0.1,0.02); - } - - this.lastEatFrameIndex = eatFrameIndex; - }; - const _handleDrink = () => { - // console.log('drink action', useAction); - - const v = this.player.actionInterpolants.use.get(); - const drinkFrameIndex = _getActionFrameIndex(v, drinkFrameIndices); - - // console.log('gulp', v, drinkFrameIndex, this.lastDrinkFrameIndex); - if (drinkFrameIndex !== 0 && drinkFrameIndex !== this.lastDrinkFrameIndex) { - sounds.playSoundName('gulp'); - // control mouth movement - this.player.characterBehavior.setMouthMoving(0.1,0.1,0.1,0.1); - } - - this.lastDrinkFrameIndex = drinkFrameIndex; - }; - - // console.log('got use action', useAction); - switch (useAction.behavior) { - case 'eat': { - _handleEat(); - break; - } - case 'drink': { - _handleDrink(); - break; - } - default: { - break; - } - } - } - }; - _handleFood(); - - // emote - const _handleEmote = () => { - if(this.player.avatar.emoteAnimation && this.lastEmote !== this.player.avatar.emoteAnimation){ - this.playEmote(this.player.avatar.emoteAnimation); - } - this.lastEmote = this.player.avatar.emoteAnimation; - }; - _handleEmote(); + updateActions() { + const actions = this.player.getActionsArray(); + this.actions = Object.fromEntries( + actions.map(action => [action.type, action]) + ); } - playGrunt(type, index){ + playGrunt(type, index) { if (this.player.voicePack) { // ensure voice pack loaded let voiceFiles, offset, duration; switch (type) { @@ -547,7 +157,7 @@ class CharacterSfx extends EventTarget{ audioBufferSourceNode.start(0, offset, duration); } } - playEmote(type, index){ + playEmote(type, index) { if (this.player.voicePack) { // ensure voice pack loaded let voiceFiles, offset, duration; switch (type) { @@ -634,6 +244,406 @@ class CharacterSfx extends EventTarget{ audioBufferSourceNode.start(0, offset, duration); } } +} + +class JumpSfx extends Sfx { + constructor(player) { + super(player); + this.lastLandState = false; + this.lastJumpState = false; + } + update(timestamp, timeDiffS, actions) { + if (this.player.avatar.jumpState && !this.lastJumpState) { + sounds.playSoundName('jump'); + + // play jump grunt + if(actions.jump?.trigger === 'jump') { + this.playGrunt('jump'); + } + } + if(this.player.avatar.landState && !this.lastLandState){ + sounds.playSoundName('land'); + } + this.lastLandState = this.player.avatar.landState; + this.lastJumpState = this.player.avatar.jumpState; + } +} + +class StepSfx extends Sfx { + constructor(player) { + super(player); + this.lastWalkTime = 0; + this.lastStepped = [false, false]; + this.narutoRunFinishTime = 0; + this.currentStep = null; + } + update(timestamp, timeDiffS, actions) { + const timeSeconds = timestamp/1000; + const currentSpeed = localVector.set(this.player.avatar.velocity.x, 0, this.player.avatar.velocity.z).length(); + const idleWalkFactor = Math.min(Math.max((currentSpeed - idleFactorSpeed) / (walkFactorSpeed - idleFactorSpeed), 0), 1); + const walkRunFactor = Math.min(Math.max((currentSpeed - walkFactorSpeed) / (runFactorSpeed - walkFactorSpeed), 0), 1); + const crouchFactor = Math.min(Math.max(1 - (this.player.avatar.crouchTime / crouchMaxTime), 0), 1); + + if (idleWalkFactor > 0.7 + && !this.player.avatar.jumpState + && !this.player.avatar.fallLoopState + && !this.player.avatar.flyState + && !actions.swim + && !actions.sit) { + + const isRunning = walkRunFactor > 0.5; + const isCrouching = crouchFactor > 0.5; + const isNarutoRun = this.player.avatar.narutoRunState; + const walkRunAnimationName = (() => { + if (isNarutoRun) { + return 'naruto run.fbx'; + } else { + const animationAngles = isCrouching ? + Avatar.getClosest2AnimationAngles('crouch', this.player.avatar.getAngle()) + : + (isRunning ? + Avatar.getClosest2AnimationAngles('run', this.player.avatar.getAngle()) + : + Avatar.getClosest2AnimationAngles('walk', this.player.avatar.getAngle()) + ); + return animationAngles[0].name; + } + })(); + const localSoundFiles = (() => { + if (isNarutoRun) { + return this.soundFiles.narutoRun; + } else if (isCrouching) { + return this.soundFiles.walk; + } else if (isRunning) { + return this.soundFiles.run; + } else { + return this.soundFiles.walk; + } + })(); + const animations = Avatar.getAnimations(); + const animation = animations.find(a => a.name === walkRunAnimationName); + const animationStepIndices = Avatar.getAnimationStepIndices(); + const animationIndices = animationStepIndices.find(i => i.name === walkRunAnimationName); + const {leftStepIndices, rightStepIndices} = animationIndices; + + const offset = offsets[walkRunAnimationName] ?? 0; // ?? window.lol; // check + const _getStepIndex = timeSeconds => { + const timeMultiplier = walkRunAnimationName === 'naruto run.fbx' ? narutoRunTimeFactor : 1; + const walkTime = (timeSeconds * timeMultiplier + offset) % animation.duration; + const walkFactor = walkTime / animation.duration; + const stepIndex = Math.floor(mod(walkFactor, 1) * leftStepIndices.length); + return stepIndex; + }; + + const startIndex = _getStepIndex(this.lastWalkTime); + const endIndex = _getStepIndex(timeSeconds); + for (let i = startIndex;; i++) { + i = i % leftStepIndices.length; + if (i !== endIndex) { + if (leftStepIndices[i] && !this.lastStepped[0] && !this.player.avatar.narutoRunState && timeSeconds-this.narutoRunFinishTime>0.5) { + const candidateAudios = localSoundFiles//.filter(a => a.paused); + if (candidateAudios.length > 0) { + this.currentStep = 'left'; + const audioSpec = candidateAudios[Math.floor(Math.random() * candidateAudios.length)]; + sounds.playSound(audioSpec); + } + } + this.lastStepped[0] = leftStepIndices[i]; + + if (rightStepIndices[i] && !this.lastStepped[1] && !this.player.avatar.narutoRunState && timeSeconds-this.narutoRunFinishTime>0.5) { + const candidateAudios = localSoundFiles// .filter(a => a.paused); + if (candidateAudios.length > 0) { + this.currentStep = 'right'; + const audioSpec = candidateAudios[Math.floor(Math.random() * candidateAudios.length)]; + sounds.playSound(audioSpec); + } + } + this.lastStepped[1] = rightStepIndices[i]; + } else { + break; + } + } + this.lastWalkTime = timeSeconds; + } + } +} + +class SwimSfx extends Sfx { + constructor(player) { + super(player); + this.currentSwimmingHand = null; + this.setSwimmingHand = true; + } + update(timestamp, timeDiffS, actions) { + if(actions.swim){ + if(actions.swim.animationType === 'breaststroke'){ + if(this.setSwimmingHand && this.player.actionInterpolants.movements.get() % breaststrokeDuration <= breaststrokeOffset){ // check + this.setSwimmingHand = false; + this.currentSwimmingHand = null; + } + else if(!this.setSwimmingHand && this.player.actionInterpolants.movements.get() % breaststrokeDuration > breaststrokeOffset){ + let regex = new RegExp('^water/swim[0-9]*.wav$'); + const candidateAudios = this.soundFiles.water.filter(f => regex.test(f.name)); + const audioSpec = candidateAudios[Math.floor(Math.random() * candidateAudios.length)]; + if(actions.swim.onSurface) + sounds.playSound(audioSpec); + + this.setSwimmingHand = true; + this.currentSwimmingHand = 'right'; + } + } + else if(actions.swim.animationType === 'freestyle'){ + let regex = new RegExp('^water/swim_fast[0-9]*.wav$'); + const candidateAudios = this.soundFiles.water.filter(f => regex.test(f.name)); + const audioSpec = candidateAudios[Math.floor(Math.random() * candidateAudios.length)]; + + if(this.setSwimmingHand && this.player.actionInterpolants.movements.get() % freestyleDuration <= freestyleOffset){ + // console.log('left hand') + if(actions.swim.onSurface) + sounds.playSound(audioSpec); + this.currentSwimmingHand = 'left'; + this.setSwimmingHand = false; + } + else if(!this.setSwimmingHand && this.player.actionInterpolants.movements.get() % freestyleDuration > freestyleOffset){ + // console.log('right hand') + if(actions.swim.onSurface) + sounds.playSound(audioSpec); + this.currentSwimmingHand = 'right'; + this.setSwimmingHand = true; + } + } + } + } +} + +class NarutoRunSfx extends Sfx { + constructor(player) { + super(player); + this.narutoRunStartTime = 0; + this.narutoRunFinishTime = 0; + this.narutoRunTrailSoundStartTime = 0; + this.narutoRunTurnSoundStartTime = 0; + this.oldNarutoRunSound = null; + this.currentQ = new THREE.Quaternion(); + this.preQ = new THREE.Quaternion(); + this.arr = [0, 0, 0, 0]; + this.willGasp = false; + } + update(timestamp, timeDiffS, actions) { + const timeSeconds = timestamp/1000; + this.currentQ.copy(this.player.quaternion); + let temp = this.currentQ.angleTo(this.preQ); + for(let i = 0; i < 4; i++) { + let temp2 = this.arr[i]; + this.arr[i] = temp; + temp = temp2; + } + if(this.player.avatar.narutoRunState) { + if(this.narutoRunStartTime===0) { + this.narutoRunStartTime=timeSeconds; + sounds.playSound(this.soundFiles.sonicBoom[0]); + this.playGrunt('narutoRun'); // check shared + } + else { + if(this.arr.reduce((a, b) => a + b) >= Math.PI / 3) { + this.arr.fill(0) + if(timeSeconds - this.narutoRunTurnSoundStartTime > this.soundFiles.sonicBoom[3].duration - 0.9 || this.narutoRunTurnSoundStartTime == 0) { + sounds.playSound(this.soundFiles.sonicBoom[3]); + this.narutoRunTurnSoundStartTime = timeSeconds; + } + } + if(timeSeconds - this.narutoRunTrailSoundStartTime > this.soundFiles.sonicBoom[2].duration - 0.2 || this.narutoRunTrailSoundStartTime==0) { + const localSound = sounds.playSound(this.soundFiles.sonicBoom[2]); + this.oldNarutoRunSound = localSound; + localSound.addEventListener('ended', () => { + if (this.oldNarutoRunSound === localSound) { + this.oldNarutoRunSound = null; + } + }); + this.narutoRunTrailSoundStartTime = timeSeconds; + } + } + // if naruto run play more than 2 sec, set willGasp + if(timeSeconds - this.narutoRunStartTime > 2){ + this.willGasp = true; + } + } + if(!this.player.avatar.narutoRunState && this.narutoRunStartTime!=0 ) { + this.narutoRunStartTime = 0; + this.narutoRunFinishTime = timeSeconds; + this.narutoRunTrailSoundStartTime = 0; + this.narutoRunTurnSoundStartTime = 0; + sounds.playSound(this.soundFiles.sonicBoom[1]); + if (this.oldNarutoRunSound) { + !this.oldNarutoRunSound.paused && this.oldNarutoRunSound.stop(); + this.oldNarutoRunSound = null; + } + } + this.preQ.x = this.currentQ.x; + this.preQ.y = this.currentQ.y; + this.preQ.z = this.currentQ.z; + this.preQ.w = this.currentQ.w; // check q.copy? + } +} + +class ComboSfx extends Sfx { + constructor(player) { + super(player); + this.lastSwordComboName = null; + this.swordComboStartTime = 0; + this.alreadyPlayComboSound = false; + } + update(timestamp, timeDiffS, actions) { + const timeSeconds = timestamp/1000; + if (actions.use?.behavior === 'sword') { + const comboAnimationName = actions.use.animationCombo ? // todo + aimAnimations[actions.use.animationCombo[this.player.avatar.useAnimationIndex]] + : null; + if (comboAnimationName) { + if (comboAnimationName !== this.lastSwordComboName) { + this.swordComboStartTime = timeSeconds; + this.alreadyPlayComboSound = false; + } + const animations = Avatar.getAnimations(); + const animation = animations.find(a => a.name === comboAnimationName); + const animationComboIndices = Avatar.getanimationComboIndices(); + const animationIndices = animationComboIndices.find(i => i.name === comboAnimationName); + const handDeltas = actions.use.boneAttachment === 'leftHand' ? animationIndices.rightHandDeltas : animationIndices.leftHandDeltas; + const maxDeltaIndex = actions.use.boneAttachment === 'leftHand' ? animationIndices.maxRightDeltaIndex : animationIndices.maxLeftDeltaIndex; + + const ratio = (timeSeconds - this.swordComboStartTime) / animation.duration; + if (ratio <= 1 && !this.alreadyPlayComboSound) { + const index = Math.floor(ratio * handDeltas.length); + if (index > maxDeltaIndex) { + this.alreadyPlayComboSound = true; + this.playGrunt('attack'); + const soundIndex = this.player.avatar.useAnimationIndex; + this.dispatchEvent(new MessageEvent('meleewhoosh', { + data: { + index: soundIndex + }, + })); + } + } + } + this.lastSwordComboName = comboAnimationName; + } + } +} + +class GaspSfx extends Sfx { + constructor(player) { + super(player); + this.startRunningTime = 0; + this.willGasp = false; + } + update(timestamp, timeDiffS, actions) { + const timeSeconds = timestamp/1000; + const currentSpeed = localVector.set(this.player.avatar.velocity.x, 0, this.player.avatar.velocity.z).length(); + const isRunning = currentSpeed > 0.5; + if(isRunning){ + if(this.startRunningTime === 0) + this.startRunningTime = timeSeconds; + } + else{ + if(this.startRunningTime !== 0 && this.willGasp && !this.player.avatar.narutoRunState){ + this.playGrunt('gasp'); + } + this.willGasp = false; + this.startRunningTime = 0; + } + + if(timeSeconds - this.startRunningTime > 5 && this.startRunningTime !== 0){ + this.willGasp = true; + } + } +} + +class FoodSfx extends Sfx { + constructor(player) { + super(player); + this.lastEatFrameIndex = -1; + this.lastDrinkFrameIndex = -1; + } + update(timestamp, timeDiffS, actions) { + const useAction = this.player.getAction('use'); + if (useAction) { + const _handleEat = () => { + const v = this.player.actionInterpolants.use.get(); + const eatFrameIndex = _getActionFrameIndex(v, eatFrameIndices); + if (eatFrameIndex !== 0 && eatFrameIndex !== this.lastEatFrameIndex) { + sounds.playSoundName('chomp'); + this.player.characterBehavior.setMouthMoving(0.04, 0.04, 0.1, 0.02); + } + this.lastEatFrameIndex = eatFrameIndex; + }; + const _handleDrink = () => { + const v = this.player.actionInterpolants.use.get(); + const drinkFrameIndex = _getActionFrameIndex(v, drinkFrameIndices); + if (drinkFrameIndex !== 0 && drinkFrameIndex !== this.lastDrinkFrameIndex) { + sounds.playSoundName('gulp'); + this.player.characterBehavior.setMouthMoving(0.1,0.1,0.1,0.1); + } + this.lastDrinkFrameIndex = drinkFrameIndex; + }; + switch (useAction.behavior) { + case 'eat': { + _handleEat(); + break; + } + case 'drink': { + _handleDrink(); + break; + } + default: { + break; + } + } + } + } +} + +class EmoteSfx extends Sfx { + constructor(player) { + super(player); + this.lastEmote = null; + } + update(timestamp, timeDiffS, actions) { + if(this.player.avatar.emoteAnimation && this.lastEmote !== this.player.avatar.emoteAnimation){ + this.playEmote(this.player.avatar.emoteAnimation); + } + this.lastEmote = this.player.avatar.emoteAnimation; + } +} + + +class CharacterSfx extends Sfx { + constructor(player) { + super(player); + + this.jumpSfx = new JumpSfx(this.player); + this.stepSfx = new StepSfx(this.player); + this.swimSfx = new SwimSfx(this.player); + this.narutoRunSfx = new NarutoRunSfx(this.player); + this.comboSfx = new ComboSfx(this.player); + this.gaspSfx = new GaspSfx(this.player); + this.foodSfx = new FoodSfx(this.player); + this.emoteSfx = new EmoteSfx(this.player); + } + update(timestamp, timeDiffS) { + if (!this.player.avatar) { + return; + } + super.updateActions(); + this.jumpSfx.update(timestamp, timeDiffS, this.actions); + this.stepSfx.update(timestamp, timeDiffS, this.actions); + this.swimSfx.update(timestamp, timeDiffS, this.actions); + this.narutoRunSfx.update(timestamp, timeDiffS, this.actions); + this.comboSfx.update(timestamp, timeDiffS, this.actions); + this.gaspSfx.update(timestamp, timeDiffS, this.actions); + this.foodSfx.update(timestamp, timeDiffS, this.actions); + this.emoteSfx.update(timestamp, timeDiffS, this.actions); + } destroy() { this.cleanup && this.cleanup(); } From bb70dec668a72250c58f6e88528754f2c4c8fd15 Mon Sep 17 00:00:00 2001 From: Patrick Bozic Date: Wed, 7 Sep 2022 15:25:46 +0800 Subject: [PATCH 3/6] Fix actions, precompute voicefiles --- character-controller.js | 1 + character-sfx.js | 329 +++++++++++++++++++--------------------- 2 files changed, 155 insertions(+), 175 deletions(-) diff --git a/character-controller.js b/character-controller.js index 2f1b7b0048..88afedbf15 100644 --- a/character-controller.js +++ b/character-controller.js @@ -256,6 +256,7 @@ class PlayerBase extends THREE.Object3D { indexUrl, }); this.updateVoicer(); + this.dispatchEvent({type: 'voicepackloaded'}); } setVoiceEndpoint(voiceId) { if (!voiceId) throw new Error('voice Id is null') diff --git a/character-sfx.js b/character-sfx.js index d878a86a08..a1d7a3084f 100644 --- a/character-sfx.js +++ b/character-sfx.js @@ -77,48 +77,50 @@ class Sfx extends EventTarget { super(); this.player = player; this.oldGrunt = null; + this.voiceFiles = null; this.soundFiles = sounds.getSoundFiles(); - this.actions = []; + this.player.addEventListener('voicepackloaded', this.setVoiceFiles.bind(this)); } - updateActions() { - const actions = this.player.getActionsArray(); - this.actions = Object.fromEntries( - actions.map(action => [action.type, action]) - ); + cleanup() { + this.player.removeEventListener('voicepackloaded', this.setVoiceFiles.bind(this)); } - playGrunt(type, index) { - if (this.player.voicePack) { // ensure voice pack loaded - let voiceFiles, offset, duration; - switch (type) { - case 'hurt': { - voiceFiles = this.player.voicePack.actionVoices.filter(f => /hurt/i.test(f.name)); - break; - } - case 'scream': { - voiceFiles = this.player.voicePack.actionVoices.filter(f => /scream/i.test(f.name)); - break; - } - case 'attack': { - voiceFiles = this.player.voicePack.actionVoices.filter(f => /attack/i.test(f.name)); - break; - } - case 'angry': { - voiceFiles = this.player.voicePack.actionVoices.filter(f => /angry/i.test(f.name)); - break; - } - case 'gasp': { - voiceFiles = this.player.voicePack.actionVoices.filter(f => /gasp/i.test(f.name)); - break; - } - case 'jump': { - voiceFiles = this.player.voicePack.actionVoices.filter(f => /jump/i.test(f.name)); - break; - } - case 'narutoRun': { - voiceFiles = this.player.voicePack.actionVoices.filter(f => /nr/i.test(f.name)); - break; - } + setVoiceFiles() { + const actionVoices = this.player.voicePack.actionVoices; + const emoteVoices = this.player.voicePack.emoteVoices; + this.voiceFiles = { + actionVoices: { + hurt: actionVoices.filter(f => /hurt/i.test(f.name)), + scream: actionVoices.filter(f => /scream/i.test(f.name)), + attack: actionVoices.filter(f => /attack/i.test(f.name)), + angry: actionVoices.filter(f => /angry/i.test(f.name)), + gasp: actionVoices.filter(f => /gasp/i.test(f.name)), + jump: actionVoices.filter(f => /jump/i.test(f.name)), + narutoRun: actionVoices.filter(f => /nr/i.test(f.name)) + }, + emoteVoices: { + alertSoft: emoteVoices.filter(f => /alert/i.test(f.name)), + alert: emoteVoices.filter(f => /alert/i.test(f.name)), + angrySoft: emoteVoices.filter(f => /angry/i.test(f.name)), + angry: emoteVoices.filter(f => /angry/i.test(f.name)), + embarrassedSoft: emoteVoices.filter(f => /emba/i.test(f.name)), + embarrassed: emoteVoices.filter(f => /emba/i.test(f.name)), + headNodSoft: emoteVoices.filter(f => /nod/i.test(f.name)), + headNod: emoteVoices.filter(f => /nod/i.test(f.name)), + headShakeSoft: emoteVoices.filter(f => /shake/i.test(f.name)), + headShake: emoteVoices.filter(f => /shake/i.test(f.name)), + sadSoft: emoteVoices.filter(f => /sad/i.test(f.name)), + sad: emoteVoices.filter(f => /sad/i.test(f.name)), + surpriseSoft: emoteVoices.filter(f => /surprise/i.test(f.name)), + surprise: emoteVoices.filter(f => /surprise/i.test(f.name)), + victorySoft: emoteVoices.filter(f => /victory/i.test(f.name)), + victory: emoteVoices.filter(f => /victory/i.test(f.name)) } + }; + } + playGrunt(type, index) { + if (this.voiceFiles) { + const voiceFiles = this.voiceFiles.actionVoices[type]; + let offset, duration; if (index === undefined) { let voice = selectVoice(voiceFiles); @@ -158,54 +160,9 @@ class Sfx extends EventTarget { } } playEmote(type, index) { - if (this.player.voicePack) { // ensure voice pack loaded - let voiceFiles, offset, duration; - switch (type) { - case 'alertSoft': - case 'alert': { - voiceFiles = this.player.voicePack.emoteVoices.filter(f => /alert/i.test(f.name)); - break; - } - case 'angrySoft': - case 'angry': { - voiceFiles = this.player.voicePack.emoteVoices.filter(f => /angry/i.test(f.name)); - break; - } - case 'embarrassedSoft': - case 'embarrassed': { - voiceFiles = this.player.voicePack.emoteVoices.filter(f => /emba/i.test(f.name)); - break; - } - case 'headNodSoft': - case 'headNod': { - voiceFiles = this.player.voicePack.emoteVoices.filter(f => /nod/i.test(f.name)); - break; - } - case 'headShakeSoft': - case 'headShake': { - voiceFiles = this.player.voicePack.emoteVoices.filter(f => /shake/i.test(f.name)); - break; - } - case 'sadSoft': - case 'sad': { - voiceFiles = this.player.voicePack.emoteVoices.filter(f => /sad/i.test(f.name)); - break; - } - case 'surpriseSoft': - case 'surprise': { - voiceFiles = this.player.voicePack.emoteVoices.filter(f => /surprise/i.test(f.name)); - break; - } - case 'victorySoft': - case 'victory': { - voiceFiles = this.player.voicePack.emoteVoices.filter(f => /victory/i.test(f.name)); - break; - } - default: { - voiceFiles = this.player.voicePack.emoteVoices; - break; - } - } + if (this.voiceFiles) { // ensure voice pack loaded + const voiceFiles = this.voiceFiles.emoteVoices[type]; + let offset, duration; if (index === undefined) { let voice = selectVoice(voiceFiles); @@ -252,12 +209,12 @@ class JumpSfx extends Sfx { this.lastLandState = false; this.lastJumpState = false; } - update(timestamp, timeDiffS, actions) { + update(timestamp, timeDiffS) { if (this.player.avatar.jumpState && !this.lastJumpState) { sounds.playSoundName('jump'); - // play jump grunt - if(actions.jump?.trigger === 'jump') { + const jumpAction = this.player.getAction('jump'); + if(jumpAction?.trigger === 'jump') { this.playGrunt('jump'); } } @@ -277,35 +234,42 @@ class StepSfx extends Sfx { this.narutoRunFinishTime = 0; this.currentStep = null; } - update(timestamp, timeDiffS, actions) { + update(timestamp, timeDiffS) { const timeSeconds = timestamp/1000; const currentSpeed = localVector.set(this.player.avatar.velocity.x, 0, this.player.avatar.velocity.z).length(); const idleWalkFactor = Math.min(Math.max((currentSpeed - idleFactorSpeed) / (walkFactorSpeed - idleFactorSpeed), 0), 1); const walkRunFactor = Math.min(Math.max((currentSpeed - walkFactorSpeed) / (runFactorSpeed - walkFactorSpeed), 0), 1); const crouchFactor = Math.min(Math.max(1 - (this.player.avatar.crouchTime / crouchMaxTime), 0), 1); - if (idleWalkFactor > 0.7 - && !this.player.avatar.jumpState - && !this.player.avatar.fallLoopState - && !this.player.avatar.flyState - && !actions.swim - && !actions.sit) { - + if ( + idleWalkFactor > 0.7 && + !this.player.avatar.jumpState && + !this.player.avatar.fallLoopState && + !this.player.avatar.flyState && + !this.player.hasAction("swim") && + !this.player.hasAction("sit") + ) { const isRunning = walkRunFactor > 0.5; const isCrouching = crouchFactor > 0.5; const isNarutoRun = this.player.avatar.narutoRunState; const walkRunAnimationName = (() => { if (isNarutoRun) { - return 'naruto run.fbx'; + return "naruto run.fbx"; } else { - const animationAngles = isCrouching ? - Avatar.getClosest2AnimationAngles('crouch', this.player.avatar.getAngle()) - : - (isRunning ? - Avatar.getClosest2AnimationAngles('run', this.player.avatar.getAngle()) - : - Avatar.getClosest2AnimationAngles('walk', this.player.avatar.getAngle()) - ); + const animationAngles = isCrouching + ? Avatar.getClosest2AnimationAngles( + "crouch", + this.player.avatar.getAngle() + ) + : isRunning + ? Avatar.getClosest2AnimationAngles( + "run", + this.player.avatar.getAngle() + ) + : Avatar.getClosest2AnimationAngles( + "walk", + this.player.avatar.getAngle() + ); return animationAngles[0].name; } })(); @@ -321,40 +285,62 @@ class StepSfx extends Sfx { } })(); const animations = Avatar.getAnimations(); - const animation = animations.find(a => a.name === walkRunAnimationName); + const animation = animations.find((a) => a.name === walkRunAnimationName); const animationStepIndices = Avatar.getAnimationStepIndices(); - const animationIndices = animationStepIndices.find(i => i.name === walkRunAnimationName); - const {leftStepIndices, rightStepIndices} = animationIndices; + const animationIndices = animationStepIndices.find( + (i) => i.name === walkRunAnimationName + ); + const { leftStepIndices, rightStepIndices } = animationIndices; const offset = offsets[walkRunAnimationName] ?? 0; // ?? window.lol; // check - const _getStepIndex = timeSeconds => { - const timeMultiplier = walkRunAnimationName === 'naruto run.fbx' ? narutoRunTimeFactor : 1; - const walkTime = (timeSeconds * timeMultiplier + offset) % animation.duration; + const _getStepIndex = (timeSeconds) => { + const timeMultiplier = + walkRunAnimationName === "naruto run.fbx" ? narutoRunTimeFactor : 1; + const walkTime = + (timeSeconds * timeMultiplier + offset) % animation.duration; const walkFactor = walkTime / animation.duration; - const stepIndex = Math.floor(mod(walkFactor, 1) * leftStepIndices.length); + const stepIndex = Math.floor( + mod(walkFactor, 1) * leftStepIndices.length + ); return stepIndex; }; const startIndex = _getStepIndex(this.lastWalkTime); const endIndex = _getStepIndex(timeSeconds); - for (let i = startIndex;; i++) { + for (let i = startIndex; ; i++) { i = i % leftStepIndices.length; if (i !== endIndex) { - if (leftStepIndices[i] && !this.lastStepped[0] && !this.player.avatar.narutoRunState && timeSeconds-this.narutoRunFinishTime>0.5) { - const candidateAudios = localSoundFiles//.filter(a => a.paused); + if ( + leftStepIndices[i] && + !this.lastStepped[0] && + !this.player.avatar.narutoRunState && + timeSeconds - this.narutoRunFinishTime > 0.5 + ) { + const candidateAudios = localSoundFiles; //.filter(a => a.paused); if (candidateAudios.length > 0) { - this.currentStep = 'left'; - const audioSpec = candidateAudios[Math.floor(Math.random() * candidateAudios.length)]; + this.currentStep = "left"; + const audioSpec = + candidateAudios[ + Math.floor(Math.random() * candidateAudios.length) + ]; sounds.playSound(audioSpec); } } this.lastStepped[0] = leftStepIndices[i]; - if (rightStepIndices[i] && !this.lastStepped[1] && !this.player.avatar.narutoRunState && timeSeconds-this.narutoRunFinishTime>0.5) { - const candidateAudios = localSoundFiles// .filter(a => a.paused); + if ( + rightStepIndices[i] && + !this.lastStepped[1] && + !this.player.avatar.narutoRunState && + timeSeconds - this.narutoRunFinishTime > 0.5 + ) { + const candidateAudios = localSoundFiles; // .filter(a => a.paused); if (candidateAudios.length > 0) { - this.currentStep = 'right'; - const audioSpec = candidateAudios[Math.floor(Math.random() * candidateAudios.length)]; + this.currentStep = "right"; + const audioSpec = + candidateAudios[ + Math.floor(Math.random() * candidateAudios.length) + ]; sounds.playSound(audioSpec); } } @@ -374,44 +360,38 @@ class SwimSfx extends Sfx { this.currentSwimmingHand = null; this.setSwimmingHand = true; } - update(timestamp, timeDiffS, actions) { - if(actions.swim){ - if(actions.swim.animationType === 'breaststroke'){ - if(this.setSwimmingHand && this.player.actionInterpolants.movements.get() % breaststrokeDuration <= breaststrokeOffset){ // check - this.setSwimmingHand = false; - this.currentSwimmingHand = null; - } - else if(!this.setSwimmingHand && this.player.actionInterpolants.movements.get() % breaststrokeDuration > breaststrokeOffset){ - let regex = new RegExp('^water/swim[0-9]*.wav$'); - const candidateAudios = this.soundFiles.water.filter(f => regex.test(f.name)); - const audioSpec = candidateAudios[Math.floor(Math.random() * candidateAudios.length)]; - if(actions.swim.onSurface) - sounds.playSound(audioSpec); - - this.setSwimmingHand = true; - this.currentSwimmingHand = 'right'; - } - } - else if(actions.swim.animationType === 'freestyle'){ - let regex = new RegExp('^water/swim_fast[0-9]*.wav$'); - const candidateAudios = this.soundFiles.water.filter(f => regex.test(f.name)); - const audioSpec = candidateAudios[Math.floor(Math.random() * candidateAudios.length)]; - - if(this.setSwimmingHand && this.player.actionInterpolants.movements.get() % freestyleDuration <= freestyleOffset){ - // console.log('left hand') - if(actions.swim.onSurface) - sounds.playSound(audioSpec); - this.currentSwimmingHand = 'left'; + update(timestamp, timeDiffS) { + const swimAction = this.player.getAction('swim'); + if(swimAction?.animationType === 'breaststroke') { + if(this.setSwimmingHand && this.player.actionInterpolants.movements.get() % breaststrokeDuration <= breaststrokeOffset){ // check this.setSwimmingHand = false; + this.currentSwimmingHand = null; } - else if(!this.setSwimmingHand && this.player.actionInterpolants.movements.get() % freestyleDuration > freestyleOffset){ - // console.log('right hand') - if(actions.swim.onSurface) + else if(!this.setSwimmingHand && this.player.actionInterpolants.movements.get() % breaststrokeDuration > breaststrokeOffset){ + let regex = new RegExp('^water/swim[0-9]*.wav$'); + const candidateAudios = this.soundFiles.water.filter(f => regex.test(f.name)); + const audioSpec = candidateAudios[Math.floor(Math.random() * candidateAudios.length)]; + if(swimAction.onSurface) sounds.playSound(audioSpec); - this.currentSwimmingHand = 'right'; + this.setSwimmingHand = true; + this.currentSwimmingHand = 'right'; } - } + } else if(swimAction?.animationType === 'freestyle') { + let regex = new RegExp('^water/swim_fast[0-9]*.wav$'); + const candidateAudios = this.soundFiles.water.filter(f => regex.test(f.name)); + const audioSpec = candidateAudios[Math.floor(Math.random() * candidateAudios.length)]; + + if(this.setSwimmingHand && this.player.actionInterpolants.movements.get() % freestyleDuration <= freestyleOffset){ + if(swimAction.onSurface) sounds.playSound(audioSpec); + this.currentSwimmingHand = 'left'; + this.setSwimmingHand = false; + } + else if(!this.setSwimmingHand && this.player.actionInterpolants.movements.get() % freestyleDuration > freestyleOffset){ + if(swimAction.onSurface) sounds.playSound(audioSpec); + this.currentSwimmingHand = 'right'; + this.setSwimmingHand = true; + } } } } @@ -429,7 +409,7 @@ class NarutoRunSfx extends Sfx { this.arr = [0, 0, 0, 0]; this.willGasp = false; } - update(timestamp, timeDiffS, actions) { + update(timestamp, timeDiffS) { const timeSeconds = timestamp/1000; this.currentQ.copy(this.player.quaternion); let temp = this.currentQ.angleTo(this.preQ); @@ -493,12 +473,11 @@ class ComboSfx extends Sfx { this.swordComboStartTime = 0; this.alreadyPlayComboSound = false; } - update(timestamp, timeDiffS, actions) { + update(timestamp, timeDiffS) { const timeSeconds = timestamp/1000; - if (actions.use?.behavior === 'sword') { - const comboAnimationName = actions.use.animationCombo ? // todo - aimAnimations[actions.use.animationCombo[this.player.avatar.useAnimationIndex]] - : null; + const useAction = this.player.getAction('use'); + if (useAction?.behavior === 'sword' && useAction?.animationCombo) { + const comboAnimationName = aimAnimations[useAction.animationCombo[useAction.index]]; if (comboAnimationName) { if (comboAnimationName !== this.lastSwordComboName) { this.swordComboStartTime = timeSeconds; @@ -508,8 +487,8 @@ class ComboSfx extends Sfx { const animation = animations.find(a => a.name === comboAnimationName); const animationComboIndices = Avatar.getanimationComboIndices(); const animationIndices = animationComboIndices.find(i => i.name === comboAnimationName); - const handDeltas = actions.use.boneAttachment === 'leftHand' ? animationIndices.rightHandDeltas : animationIndices.leftHandDeltas; - const maxDeltaIndex = actions.use.boneAttachment === 'leftHand' ? animationIndices.maxRightDeltaIndex : animationIndices.maxLeftDeltaIndex; + const handDeltas = useAction.boneAttachment === 'leftHand' ? animationIndices.rightHandDeltas : animationIndices.leftHandDeltas; + const maxDeltaIndex = useAction.boneAttachment === 'leftHand' ? animationIndices.maxRightDeltaIndex : animationIndices.maxLeftDeltaIndex; const ratio = (timeSeconds - this.swordComboStartTime) / animation.duration; if (ratio <= 1 && !this.alreadyPlayComboSound) { @@ -537,7 +516,7 @@ class GaspSfx extends Sfx { this.startRunningTime = 0; this.willGasp = false; } - update(timestamp, timeDiffS, actions) { + update(timestamp, timeDiffS) { const timeSeconds = timestamp/1000; const currentSpeed = localVector.set(this.player.avatar.velocity.x, 0, this.player.avatar.velocity.z).length(); const isRunning = currentSpeed > 0.5; @@ -565,7 +544,7 @@ class FoodSfx extends Sfx { this.lastEatFrameIndex = -1; this.lastDrinkFrameIndex = -1; } - update(timestamp, timeDiffS, actions) { + update(timestamp, timeDiffS) { const useAction = this.player.getAction('use'); if (useAction) { const _handleEat = () => { @@ -608,7 +587,7 @@ class EmoteSfx extends Sfx { super(player); this.lastEmote = null; } - update(timestamp, timeDiffS, actions) { + update(timestamp, timeDiffS) { if(this.player.avatar.emoteAnimation && this.lastEmote !== this.player.avatar.emoteAnimation){ this.playEmote(this.player.avatar.emoteAnimation); } @@ -634,16 +613,16 @@ class CharacterSfx extends Sfx { if (!this.player.avatar) { return; } - super.updateActions(); - this.jumpSfx.update(timestamp, timeDiffS, this.actions); - this.stepSfx.update(timestamp, timeDiffS, this.actions); - this.swimSfx.update(timestamp, timeDiffS, this.actions); - this.narutoRunSfx.update(timestamp, timeDiffS, this.actions); - this.comboSfx.update(timestamp, timeDiffS, this.actions); - this.gaspSfx.update(timestamp, timeDiffS, this.actions); - this.foodSfx.update(timestamp, timeDiffS, this.actions); - this.emoteSfx.update(timestamp, timeDiffS, this.actions); + this.jumpSfx.update(timestamp, timeDiffS); + this.stepSfx.update(timestamp, timeDiffS); + this.swimSfx.update(timestamp, timeDiffS); + this.narutoRunSfx.update(timestamp, timeDiffS); + this.comboSfx.update(timestamp, timeDiffS); + this.gaspSfx.update(timestamp, timeDiffS); + this.foodSfx.update(timestamp, timeDiffS); + this.emoteSfx.update(timestamp, timeDiffS); } + destroy() { this.cleanup && this.cleanup(); } From 557143d1cbd22797897e53dcabb26b94184cd5a2 Mon Sep 17 00:00:00 2001 From: Patrick Bozic Date: Thu, 8 Sep 2022 18:42:24 +0800 Subject: [PATCH 4/6] Play sword combo sounds (silsword) via ComboSfx --- character-sfx.js | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/character-sfx.js b/character-sfx.js index a1d7a3084f..2175e82718 100644 --- a/character-sfx.js +++ b/character-sfx.js @@ -246,28 +246,28 @@ class StepSfx extends Sfx { !this.player.avatar.jumpState && !this.player.avatar.fallLoopState && !this.player.avatar.flyState && - !this.player.hasAction("swim") && - !this.player.hasAction("sit") + !this.player.hasAction('swim') && + !this.player.hasAction('sit') ) { const isRunning = walkRunFactor > 0.5; const isCrouching = crouchFactor > 0.5; const isNarutoRun = this.player.avatar.narutoRunState; const walkRunAnimationName = (() => { if (isNarutoRun) { - return "naruto run.fbx"; + return 'naruto run.fbx'; } else { const animationAngles = isCrouching ? Avatar.getClosest2AnimationAngles( - "crouch", + 'crouch', this.player.avatar.getAngle() ) : isRunning ? Avatar.getClosest2AnimationAngles( - "run", + 'run', this.player.avatar.getAngle() ) : Avatar.getClosest2AnimationAngles( - "walk", + 'walk', this.player.avatar.getAngle() ); return animationAngles[0].name; @@ -295,7 +295,7 @@ class StepSfx extends Sfx { const offset = offsets[walkRunAnimationName] ?? 0; // ?? window.lol; // check const _getStepIndex = (timeSeconds) => { const timeMultiplier = - walkRunAnimationName === "naruto run.fbx" ? narutoRunTimeFactor : 1; + walkRunAnimationName === 'naruto run.fbx' ? narutoRunTimeFactor : 1; const walkTime = (timeSeconds * timeMultiplier + offset) % animation.duration; const walkFactor = walkTime / animation.duration; @@ -318,7 +318,7 @@ class StepSfx extends Sfx { ) { const candidateAudios = localSoundFiles; //.filter(a => a.paused); if (candidateAudios.length > 0) { - this.currentStep = "left"; + this.currentStep = 'left'; const audioSpec = candidateAudios[ Math.floor(Math.random() * candidateAudios.length) @@ -336,7 +336,7 @@ class StepSfx extends Sfx { ) { const candidateAudios = localSoundFiles; // .filter(a => a.paused); if (candidateAudios.length > 0) { - this.currentStep = "right"; + this.currentStep = 'right'; const audioSpec = candidateAudios[ Math.floor(Math.random() * candidateAudios.length) @@ -496,12 +496,11 @@ class ComboSfx extends Sfx { if (index > maxDeltaIndex) { this.alreadyPlayComboSound = true; this.playGrunt('attack'); - const soundIndex = this.player.avatar.useAnimationIndex; - this.dispatchEvent(new MessageEvent('meleewhoosh', { - data: { - index: soundIndex - }, - })); + const index = useAction.index; + const soundRegex = new RegExp(`^combat/sword_slash${index}-[0-9]*.wav$`); + const soundCandidate = this.soundFiles.combat.filter(f => soundRegex.test(f.name)); + const audioSpec = soundCandidate[Math.floor(Math.random() * soundCandidate.length)]; + sounds.playSound(audioSpec); } } } @@ -595,7 +594,6 @@ class EmoteSfx extends Sfx { } } - class CharacterSfx extends Sfx { constructor(player) { super(player); From 1418c3d7467aa9cfd6938ab2e3f5353190d641b1 Mon Sep 17 00:00:00 2001 From: Patrick Bozic Date: Thu, 8 Sep 2022 20:45:35 +0800 Subject: [PATCH 5/6] move food frameIndices to avatars/constants.js --- avatars/constants.js | 4 +++- character-sfx.js | 4 ++-- constants.js | 3 --- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/avatars/constants.js b/avatars/constants.js index f90b237474..9c89eb40e3 100644 --- a/avatars/constants.js +++ b/avatars/constants.js @@ -1,4 +1,6 @@ export const idleFactorSpeed = 0; export const walkFactorSpeed = 0.25; export const runFactorSpeed = 0.7; -export const narutoRunTimeFactor = 2; \ No newline at end of file +export const narutoRunTimeFactor = 2; +export const eatFrameIndices = [500, 800, 1100]; +export const drinkFrameIndices = [400, 700, 1000]; \ No newline at end of file diff --git a/character-sfx.js b/character-sfx.js index 2175e82718..4b4fefa8d9 100644 --- a/character-sfx.js +++ b/character-sfx.js @@ -8,11 +8,11 @@ import { walkFactorSpeed, runFactorSpeed, narutoRunTimeFactor, + eatFrameIndices, + drinkFrameIndices, } from './avatars/constants.js'; import { crouchMaxTime, - eatFrameIndices, - drinkFrameIndices, } from './constants.js'; import { mod, diff --git a/constants.js b/constants.js index 6a2e0c86b7..9ebb82d9e9 100644 --- a/constants.js +++ b/constants.js @@ -133,9 +133,6 @@ export const avatarInterpolationFrameRate = 60; export const avatarInterpolationTimeDelay = 1000/(avatarInterpolationFrameRate * 0.5); export const avatarInterpolationNumFrames = 4; -export const eatFrameIndices = [500, 800, 1100]; -export const drinkFrameIndices = [400, 700, 1000]; - export const defaultMaxId = 8192; export const defaultMusicVolume = 0.35; From eeb078ff045d35dc02663936085767abadcb28ef Mon Sep 17 00:00:00 2001 From: Patrick Bozic Date: Thu, 8 Sep 2022 20:58:35 +0800 Subject: [PATCH 6/6] remove metaverse_components/use.js --- metaverse-components.js | 4 ++-- metaverse_components/use.js | 45 ------------------------------------- 2 files changed, 2 insertions(+), 47 deletions(-) delete mode 100644 metaverse_components/use.js diff --git a/metaverse-components.js b/metaverse-components.js index 4c5f054ad8..ebe17a985b 100644 --- a/metaverse-components.js +++ b/metaverse-components.js @@ -3,7 +3,7 @@ import wear from './metaverse_components/wear.js'; import pet from './metaverse_components/pet.js'; import drop from './metaverse_components/drop.js'; // import mob from './metaverse_components/mob.js'; -import use from './metaverse_components/use.js'; +// import use from './metaverse_components/use.js'; const componentTemplates = { wear, @@ -11,7 +11,7 @@ const componentTemplates = { pet, drop, // mob, - use, + // use, }; export { componentTemplates, diff --git a/metaverse_components/use.js b/metaverse_components/use.js deleted file mode 100644 index e0e46f39c7..0000000000 --- a/metaverse_components/use.js +++ /dev/null @@ -1,45 +0,0 @@ -import * as THREE from 'three'; -import metaversefile from 'metaversefile'; - - - -export default (app, component) => { - const {useUse} = metaversefile; - const sounds = metaversefile.useSound(); - const soundFiles = sounds.getSoundFiles(); - - let using = false; - let player = null; - - - - useUse((e) => { - using = e.use; - }); - const meleewhoosh = (e) =>{ - if(using){ - const index = e.data.index; - const soundRegex = new RegExp(`^combat/sword_slash${index}-[0-9]*.wav$`); - const soundCandidate = soundFiles.combat.filter(f => soundRegex.test(f.name)); - const audioSpec = soundCandidate[Math.floor(Math.random() * soundCandidate.length)]; - sounds.playSound(audioSpec); - } - } - const _unwear = () => { - if (component.behavior === 'sword') { - player.characterSfx.removeEventListener('meleewhoosh', meleewhoosh); - } - player = null; - }; - app.addEventListener('wearupdate', e => { - if (e.wear) { - player = e.player; - if (component.behavior === 'sword') { - player.characterSfx.addEventListener('meleewhoosh', meleewhoosh); - } - } else { - _unwear(); - } - }); - return app; -}; \ No newline at end of file