feat(stage-*): support max fps

This commit is contained in:
RainbowBird
2026-02-01 22:20:10 +08:00
parent 77f8aad97e
commit cfa375f34a
8 changed files with 50 additions and 2 deletions
@@ -74,6 +74,11 @@ live2d:
title: Disable model mouse tracking
button-disable:
title: Disable
fps:
title: FPS
description: Limit rendering frame rate
options:
unlimited: No Limit
microphone: Microphone
models: Model
pages:
@@ -26,6 +26,7 @@ withDefaults(defineProps<{
live2dAutoBlinkEnabled?: boolean
live2dForceAutoBlinkEnabled?: boolean
live2dShadowEnabled?: boolean
live2dMaxFps?: number
}>(), {
paused: false,
focusAt: () => ({ x: 0, y: 0 }),
@@ -37,6 +38,7 @@ withDefaults(defineProps<{
live2dAutoBlinkEnabled: true,
live2dForceAutoBlinkEnabled: false,
live2dShadowEnabled: true,
live2dMaxFps: 0,
})
const componentState = defineModel<'pending' | 'loading' | 'mounted'>('state', { default: 'pending' })
@@ -70,6 +72,7 @@ defineExpose({
:width="width"
:height="height"
:resolution="2"
:max-fps="live2dMaxFps"
max-h="100dvh"
>
<Live2DModel
@@ -9,8 +9,10 @@ const props = withDefaults(defineProps<{
width: number
height: number
resolution?: number
maxFps?: number
}>(), {
resolution: 2,
maxFps: 0,
})
const componentState = defineModel<'pending' | 'loading' | 'mounted'>('state', { default: 'pending' })
@@ -20,6 +22,13 @@ const isPixiCanvasReady = ref(false)
const pixiApp = ref<Application>()
const pixiAppCanvas = ref<HTMLCanvasElement>()
function resolveMaxFps(limit?: number) {
if (!limit || limit <= 0)
return 0
return Math.max(1, Math.round(limit))
}
function installRenderGuard(app: Application) {
const guardedRender = () => {
try {
@@ -33,6 +42,7 @@ function installRenderGuard(app: Application) {
app.ticker.remove(app.render, app)
app.ticker.add(guardedRender)
app.ticker.maxFPS = resolveMaxFps(props.maxFps)
}
async function initLive2DPixiStage(parent: HTMLDivElement) {
@@ -82,6 +92,10 @@ function handleResize() {
}
watch([() => props.width, () => props.height, () => props.resolution], handleResize)
watch(() => props.maxFps, (limit) => {
if (pixiApp.value)
pixiApp.value.ticker.maxFPS = resolveMaxFps(limit)
})
onMounted(async () => containerRef.value && await initLive2DPixiStage(containerRef.value))
onUnmounted(() => pixiApp.value?.destroy())
@@ -42,6 +42,7 @@ const {
live2dAutoBlinkEnabled,
live2dForceAutoBlinkEnabled,
live2dShadowEnabled,
live2dMaxFps,
} = storeToRefs(settingsStore)
watch(selectedModel, async () => {
@@ -110,6 +111,7 @@ watch(selectedModel, async () => {
:live2d-auto-blink-enabled="live2dAutoBlinkEnabled"
:live2d-force-auto-blink-enabled="live2dForceAutoBlinkEnabled"
:live2d-shadow-enabled="live2dShadowEnabled"
:live2d-max-fps="live2dMaxFps"
/>
</div>
</template>
@@ -1,8 +1,8 @@
<script setup lang="ts">
import { defaultModelParameters, useLive2d } from '@proj-airi/stage-ui-live2d'
import { Button, Checkbox, FieldRange } from '@proj-airi/ui'
import { Button, Checkbox, FieldRange, SelectTab } from '@proj-airi/ui'
import { storeToRefs } from 'pinia'
import { onMounted, onUnmounted, ref, watch } from 'vue'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useSettings } from '../../../../stores/settings'
@@ -25,6 +25,7 @@ const {
live2dAutoBlinkEnabled,
live2dForceAutoBlinkEnabled,
live2dShadowEnabled,
live2dMaxFps,
} = storeToRefs(settings)
const live2d = useLive2d()
@@ -39,6 +40,11 @@ const selectedRuntimeMotion = ref<string>('')
const selectedRuntimeMotionName = ref<string>('')
const runtimeMotions = ref<Array<{ name: string, fullPath: string, displayPath: string, group: string, index: number }>>([])
const showMotionSelector = ref(false)
const fpsOptions = computed(() => [
{ value: 0, label: t('settings.live2d.fps.options.unlimited') },
{ value: 60, label: '60' },
{ value: 30, label: '30' },
])
// Get available runtime motions from the model
onMounted(() => {
@@ -334,6 +340,18 @@ onUnmounted(() => {
</div>
</div>
<div :class="['mt-4', 'flex', 'items-center', 'justify-between']">
<div :class="['flex', 'flex-col', 'gap-1']">
<span :class="['text-sm', 'text-neutral-600', 'dark:text-neutral-400']">
{{ t('settings.live2d.fps.title') }}
</span>
<span :class="['text-xs', 'text-neutral-500', 'dark:text-neutral-400']">
{{ t('settings.live2d.fps.description') }}
</span>
</div>
<SelectTab v-model="live2dMaxFps" :options="fpsOptions" size="sm" :class="['w-48', 'shrink-0']" />
</div>
<div mt-4 flex items-center justify-between>
<span text-sm text-neutral-600 dark:text-neutral-400>Auto Blink</span>
<Checkbox v-model="live2dAutoBlinkEnabled" />
@@ -64,6 +64,7 @@ const {
live2dAutoBlinkEnabled,
live2dForceAutoBlinkEnabled,
live2dShadowEnabled,
live2dMaxFps,
} = storeToRefs(settingsStore)
const { mouthOpenSize } = storeToRefs(useSpeakingStore())
const { audioContext } = useAudioContext()
@@ -546,6 +547,7 @@ defineExpose({
:live2d-auto-blink-enabled="live2dAutoBlinkEnabled"
:live2d-force-auto-blink-enabled="live2dForceAutoBlinkEnabled"
:live2d-shadow-enabled="live2dShadowEnabled"
:live2d-max-fps="live2dMaxFps"
/>
<ThreeScene
v-if="stageModelRenderer === 'vrm' && showStage"
@@ -65,6 +65,7 @@ export const useSettings = defineStore('settings', () => {
live2dAutoBlinkEnabled: live2dRefs.live2dAutoBlinkEnabled,
live2dForceAutoBlinkEnabled: live2dRefs.live2dForceAutoBlinkEnabled,
live2dShadowEnabled: live2dRefs.live2dShadowEnabled,
live2dMaxFps: live2dRefs.live2dMaxFps,
// Theme settings
themeColorsHue: themeRefs.themeColorsHue,
@@ -7,6 +7,7 @@ export const useSettingsLive2d = defineStore('settings-live2d', () => {
const live2dAutoBlinkEnabled = useLocalStorageManualReset<boolean>('settings/live2d/auto-blink-enabled', true)
const live2dForceAutoBlinkEnabled = useLocalStorageManualReset<boolean>('settings/live2d/force-auto-blink-enabled', false)
const live2dShadowEnabled = useLocalStorageManualReset<boolean>('settings/live2d/shadow-enabled', true)
const live2dMaxFps = useLocalStorageManualReset<number>('settings/live2d/max-fps', 0)
function resetState() {
live2dDisableFocus.reset()
@@ -14,6 +15,7 @@ export const useSettingsLive2d = defineStore('settings-live2d', () => {
live2dAutoBlinkEnabled.reset()
live2dForceAutoBlinkEnabled.reset()
live2dShadowEnabled.reset()
live2dMaxFps.reset()
}
return {
@@ -22,6 +24,7 @@ export const useSettingsLive2d = defineStore('settings-live2d', () => {
live2dAutoBlinkEnabled,
live2dForceAutoBlinkEnabled,
live2dShadowEnabled,
live2dMaxFps,
resetState,
}
})