feat(stage-*): support max fps
This commit is contained in:
@@ -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,
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user