diff --git a/packages/stage/src/components/VRMModel.vue b/packages/stage/src/components/VRMModel.vue index fbf5136..c293e6a 100644 --- a/packages/stage/src/components/VRMModel.vue +++ b/packages/stage/src/components/VRMModel.vue @@ -2,7 +2,7 @@ import type { VRMCore } from '@pixiv/three-vrm-core' import { useLoop, useTresContext } from '@tresjs/core' import { AnimationMixer } from 'three' -import { clipFromVRMAnimation, loadVRMAnimation } from '~/composables/vrm/animation' +import { clipFromVRMAnimation, loadVRMAnimation, useBlink } from '~/composables/vrm/animation' import { loadVrm } from '~/composables/vrm/core' const props = defineProps<{ @@ -21,6 +21,7 @@ const vrm = ref() const vrmAnimationMixer = ref() const { scene } = useTresContext() const { onBeforeRender } = useLoop() +const blink = useBlink() watch(() => props.position, ([x, y, z]) => { if (vrm.value) { @@ -59,6 +60,7 @@ onMounted(async () => { onBeforeRender(({ delta }) => { vrmAnimationMixer.value?.update(delta) vrm.value?.update(delta) + blink.update(vrm.value, delta) }) vrm.value = _vrm diff --git a/packages/stage/src/composables/vrm/animation.ts b/packages/stage/src/composables/vrm/animation.ts index 74d8442..c0e340e 100644 --- a/packages/stage/src/composables/vrm/animation.ts +++ b/packages/stage/src/composables/vrm/animation.ts @@ -38,3 +38,51 @@ export async function clipFromVRMAnimation(vrm?: VRMCore, animation?: VRMAnimati // create animation clip return createVRMAnimationClip(animation, vrm) } + +export function useBlink() { + /** + * Eye blinking animation + */ + const isBlinking = ref(false) + const blinkProgress = ref(0) + const timeSinceLastBlink = ref(0) + const BLINK_DURATION = 0.2 // Duration of a single blink in seconds + const MIN_BLINK_INTERVAL = 1 // Minimum time between blinks + const MAX_BLINK_INTERVAL = 6 // Maximum time between blinks + const nextBlinkTime = ref(Math.random() * (MAX_BLINK_INTERVAL - MIN_BLINK_INTERVAL) + MIN_BLINK_INTERVAL) + + // Function to handle blinking animation + function update(vrm: VRMCore | undefined, delta: number) { + if (!vrm?.expressionManager) + return + + timeSinceLastBlink.value += delta + + // Check if it's time for next blink + if (!isBlinking.value && timeSinceLastBlink.value >= nextBlinkTime.value) { + isBlinking.value = true + blinkProgress.value = 0 + } + + // Handle blinking animation + if (isBlinking.value) { + blinkProgress.value += delta / BLINK_DURATION + + // Calculate blink value using sine curve for smooth animation + const blinkValue = Math.sin(Math.PI * blinkProgress.value) + + // Apply blink expression + vrm.expressionManager.setValue('blink', blinkValue) + + // Reset blink when animation is complete + if (blinkProgress.value >= 1) { + isBlinking.value = false + timeSinceLastBlink.value = 0 + vrm.expressionManager.setValue('blink', 0) // Reset blink value to 0 + nextBlinkTime.value = Math.random() * (MAX_BLINK_INTERVAL - MIN_BLINK_INTERVAL) + MIN_BLINK_INTERVAL + } + } + } + + return { update } +}