Commit 0541025d authored by Chunchi Che's avatar Chunchi Che

Merge branch 'optimize/card_move_speed' into 'main'

支持让玩家自动调整动画速度

See merge request mycard/Neos!389
parents 5f2cff6b 5bef96e6
export interface AnimationConfig {
// custom speed set by player
speed: number;
}
export const defaultAnimationConfig: AnimationConfig = {
speed: 0.7,
};
...@@ -3,17 +3,19 @@ import { pick } from "lodash-es"; ...@@ -3,17 +3,19 @@ import { pick } from "lodash-es";
import { proxy, subscribe } from "valtio"; import { proxy, subscribe } from "valtio";
import { type NeosStore } from "../shared"; import { type NeosStore } from "../shared";
import { AnimationConfig, defaultAnimationConfig } from "./animation";
import { AudioConfig, defaultAudioConfig } from "./audio"; import { AudioConfig, defaultAudioConfig } from "./audio";
/** 将设置保存到本地 */ /** 将设置保存到本地 */
const NEO_SETTING_CONFIG = "__neo_setting_config__"; const NEO_SETTING_CONFIG = "__neo_setting_config__";
/** 设置项 */ /** 设置项 */
type SettingStoreConfig = Pick<SettingStore, "audio">; type SettingStoreConfig = Pick<SettingStore, "audio" | "animation">;
/** 默认设置 */ /** 默认设置 */
const defaultSettingConfig: SettingStoreConfig = { const defaultSettingConfig: SettingStoreConfig = {
audio: defaultAudioConfig, audio: defaultAudioConfig,
animation: defaultAnimationConfig,
}; };
/** 获取默认设置 */ /** 获取默认设置 */
...@@ -21,7 +23,13 @@ function getDefaultSetting() { ...@@ -21,7 +23,13 @@ function getDefaultSetting() {
if (!isSSR()) { if (!isSSR()) {
/** 获取默认设置 */ /** 获取默认设置 */
const setting = localStorage.getItem(NEO_SETTING_CONFIG); const setting = localStorage.getItem(NEO_SETTING_CONFIG);
if (setting) return JSON.parse(setting) as SettingStoreConfig; if (setting) {
const config = JSON.parse(setting) as SettingStoreConfig;
if (config.audio === undefined) config.audio = defaultAudioConfig;
if (config.animation === undefined)
config.animation = defaultAnimationConfig;
return config;
}
} }
return defaultSettingConfig; return defaultSettingConfig;
} }
...@@ -33,14 +41,23 @@ class SettingStore implements NeosStore { ...@@ -33,14 +41,23 @@ class SettingStore implements NeosStore {
/** 音频设置 */ /** 音频设置 */
audio: AudioConfig = defaultSetting.audio; audio: AudioConfig = defaultSetting.audio;
/** Animation Configuration */
animation: AnimationConfig = defaultSetting.animation;
/** 保存音频设置 */ /** 保存音频设置 */
saveAudioConfig(config: Partial<AudioConfig>): void { saveAudioConfig(config: Partial<AudioConfig>): void {
Object.assign(this.audio, config); Object.assign(this.audio, config);
} }
/** save Animation Configuration */
saveAnimationConfig(config: Partial<AnimationConfig>): void {
Object.assign(this.animation, config);
}
reset(): void { reset(): void {
const defaultSetting = getDefaultSetting(); const defaultSetting = getDefaultSetting();
this.audio = defaultSetting.audio; this.audio = defaultSetting.audio;
this.animation = defaultSetting.animation;
} }
} }
...@@ -52,7 +69,7 @@ subscribe(settingStore, () => { ...@@ -52,7 +69,7 @@ subscribe(settingStore, () => {
if (!isSSR()) { if (!isSSR()) {
localStorage.setItem( localStorage.setItem(
NEO_SETTING_CONFIG, NEO_SETTING_CONFIG,
JSON.stringify(pick(settingStore, ["audio"])), JSON.stringify(pick(settingStore, ["audio", "animation"])),
); );
} }
}); });
...@@ -3,7 +3,7 @@ import { isMe } from "@/stores"; ...@@ -3,7 +3,7 @@ import { isMe } from "@/stores";
import { matConfig } from "../../css"; import { matConfig } from "../../css";
import type { MoveFunc } from "./types"; import type { MoveFunc } from "./types";
import { asyncStart } from "./utils"; import { asyncStart, getDuration } from "./utils";
const { const {
BLOCK_WIDTH, BLOCK_WIDTH,
...@@ -45,5 +45,9 @@ export const moveToDeck: MoveFunc = async (props) => { ...@@ -45,5 +45,9 @@ export const moveToDeck: MoveFunc = async (props) => {
ry: isMe(controller) ? (zone === DECK ? 180 : 0) : 180, ry: isMe(controller) ? (zone === DECK ? 180 : 0) : 180,
zIndex: z, zIndex: z,
height: DECK_CARD_HEIGHT, height: DECK_CARD_HEIGHT,
config: {
duration: getDuration(),
},
}); });
}; };
...@@ -5,7 +5,7 @@ import { isMe } from "@/stores"; ...@@ -5,7 +5,7 @@ import { isMe } from "@/stores";
import { matConfig } from "../../css"; import { matConfig } from "../../css";
import type { MoveFunc } from "./types"; import type { MoveFunc } from "./types";
import { asyncStart } from "./utils"; import { asyncStart, getDuration } from "./utils";
const { const {
BLOCK_WIDTH, BLOCK_WIDTH,
...@@ -107,7 +107,7 @@ export const moveToGround: MoveFunc = async (props) => { ...@@ -107,7 +107,7 @@ export const moveToGround: MoveFunc = async (props) => {
ry, ry,
rz, rz,
config: { config: {
tension: 250, duration: getDuration(),
clamp: true, clamp: true,
easing: easings.easeOutSine, easing: easings.easeOutSine,
}, },
...@@ -121,7 +121,7 @@ export const moveToGround: MoveFunc = async (props) => { ...@@ -121,7 +121,7 @@ export const moveToGround: MoveFunc = async (props) => {
zIndex: is_overlay ? 1 : 3, zIndex: is_overlay ? 1 : 3,
config: { config: {
easing: easings.easeInQuad, easing: easings.easeInQuad,
duration: 200, duration: 100,
clamp: true, clamp: true,
}, },
}); });
......
...@@ -3,7 +3,7 @@ import { cardStore, isMe } from "@/stores"; ...@@ -3,7 +3,7 @@ import { cardStore, isMe } from "@/stores";
import { matConfig } from "../../css"; import { matConfig } from "../../css";
import type { MoveFunc } from "./types"; import type { MoveFunc } from "./types";
import { asyncStart } from "./utils"; import { asyncStart, getDuration } from "./utils";
const { const {
BLOCK_HEIGHT_M, BLOCK_HEIGHT_M,
...@@ -55,5 +55,9 @@ export const moveToHand: MoveFunc = async (props) => { ...@@ -55,5 +55,9 @@ export const moveToHand: MoveFunc = async (props) => {
height: HAND_CARD_HEIGHT, height: HAND_CARD_HEIGHT,
zIndex: sequence, zIndex: sequence,
// rx: -PLANE_ROTATE_X, // rx: -PLANE_ROTATE_X,
config: {
duration: getDuration(),
clamp: true,
},
}); });
}; };
...@@ -3,7 +3,7 @@ import { isMe } from "@/stores"; ...@@ -3,7 +3,7 @@ import { isMe } from "@/stores";
import { matConfig } from "../../css"; import { matConfig } from "../../css";
import type { MoveFunc } from "./types"; import type { MoveFunc } from "./types";
import { asyncStart } from "./utils"; import { asyncStart, getDuration } from "./utils";
const { const {
BLOCK_WIDTH, BLOCK_WIDTH,
...@@ -42,7 +42,8 @@ export const moveToOutside: MoveFunc = async (props) => { ...@@ -42,7 +42,8 @@ export const moveToOutside: MoveFunc = async (props) => {
subZ: 100, subZ: 100,
zIndex: sequence, zIndex: sequence,
config: { config: {
tension: 140, duration: getDuration(),
clamp: true,
}, },
}); });
api.set({ subZ: 0 }); api.set({ subZ: 0 });
......
import { type SpringConfig, type SpringRef } from "@react-spring/web"; import { type SpringConfig, type SpringRef } from "@react-spring/web";
import { settingStore } from "@/stores/settingStore";
export const asyncStart = <T extends {}>(api: SpringRef<T>) => { export const asyncStart = <T extends {}>(api: SpringRef<T>) => {
return (p: Partial<T> & { config?: SpringConfig }) => return (p: Partial<T> & { config?: SpringConfig }) =>
new Promise((resolve) => { new Promise((resolve) => {
...@@ -9,3 +11,10 @@ export const asyncStart = <T extends {}>(api: SpringRef<T>) => { ...@@ -9,3 +11,10 @@ export const asyncStart = <T extends {}>(api: SpringRef<T>) => {
}); });
}); });
}; };
export function getDuration(): number {
const MAX_DURATION = 400;
const { speed } = settingStore.animation;
return MAX_DURATION - speed * 300;
}
...@@ -197,7 +197,9 @@ ...@@ -197,7 +197,9 @@
"TurnOnMusic": "Música", "TurnOnMusic": "Música",
"TurnOnSoundEffects": "Efeitos sonoros", "TurnOnSoundEffects": "Efeitos sonoros",
"SwitchMusicAccordingToTheEnvironment": "Trocar música conforme o ambiente", "SwitchMusicAccordingToTheEnvironment": "Trocar música conforme o ambiente",
"LanguageSettings": "Idioma" "LanguageSettings": "Idioma",
"AnimationSettings": "Animação",
"AnimationSpeed": "Velocidade da animação"
}, },
"DeckResults": { "DeckResults": {
"NoDeckGroupFound": "Não foi possível encontrar o baralho correspondente" "NoDeckGroupFound": "Não foi possível encontrar o baralho correspondente"
......
...@@ -199,7 +199,9 @@ ...@@ -199,7 +199,9 @@
"TurnOnMusic": "开启音乐", "TurnOnMusic": "开启音乐",
"TurnOnSoundEffects": "开启音效", "TurnOnSoundEffects": "开启音效",
"SwitchMusicAccordingToTheEnvironment": "根据环境切换音乐", "SwitchMusicAccordingToTheEnvironment": "根据环境切换音乐",
"LanguageSettings": "语言" "LanguageSettings": "语言",
"AnimationSettings": "动画",
"AnimationSpeed": "动画速度"
}, },
"DeckResults": { "DeckResults": {
"NoDeckGroupFound": "找不到相应卡组" "NoDeckGroupFound": "找不到相应卡组"
......
...@@ -199,7 +199,9 @@ ...@@ -199,7 +199,9 @@
"TurnOnMusic": "Music", "TurnOnMusic": "Music",
"TurnOnSoundEffects": "Sound effects", "TurnOnSoundEffects": "Sound effects",
"SwitchMusicAccordingToTheEnvironment": "Switch music according to the environment", "SwitchMusicAccordingToTheEnvironment": "Switch music according to the environment",
"LanguageSettings": "Language" "LanguageSettings": "Language",
"AnimationSettings": "Animation",
"AnimationSpeed": "Animation speed"
}, },
"DeckResults": { "DeckResults": {
"NoDeckGroupFound": "Cannot find the corresponding card deck" "NoDeckGroupFound": "Cannot find the corresponding card deck"
......
...@@ -197,7 +197,9 @@ ...@@ -197,7 +197,9 @@
"TurnOnMusic": "Activer la musique", "TurnOnMusic": "Activer la musique",
"TurnOnSoundEffects": "Effets sonores", "TurnOnSoundEffects": "Effets sonores",
"SwitchMusicAccordingToTheEnvironment": "Changer la musique en fonction de l'environnement", "SwitchMusicAccordingToTheEnvironment": "Changer la musique en fonction de l'environnement",
"LanguageSettings": "Langue" "LanguageSettings": "Langue",
"AnimationSettings": "Animation",
"AnimationSpeed": "Vitesse d'animation"
}, },
"DeckResults": { "DeckResults": {
"NoDeckGroupFound": "Impossible de trouver le jeu de cartes correspondant" "NoDeckGroupFound": "Impossible de trouver le jeu de cartes correspondant"
......
...@@ -197,7 +197,9 @@ ...@@ -197,7 +197,9 @@
"TurnOnMusic": "音楽をオンにする", "TurnOnMusic": "音楽をオンにする",
"TurnOnSoundEffects": "効果音をオンにする", "TurnOnSoundEffects": "効果音をオンにする",
"SwitchMusicAccordingToTheEnvironment": "環境に応じて音楽を切り替える", "SwitchMusicAccordingToTheEnvironment": "環境に応じて音楽を切り替える",
"LanguageSettings": "言語" "LanguageSettings": "言語",
"AnimationSettings": "アニメーション",
"AnimationSpeed": "アニメーション速度"
}, },
"DeckResults": { "DeckResults": {
"NoDeckGroupFound": "対応するカードデッキが見つかりません" "NoDeckGroupFound": "対応するカードデッキが見つかりません"
......
...@@ -196,7 +196,9 @@ ...@@ -196,7 +196,9 @@
"AudioSettings": "Configurações de áudio", "AudioSettings": "Configurações de áudio",
"TurnOnMusic": "Música", "TurnOnMusic": "Música",
"TurnOnSoundEffects": "Efeitos sonoros", "TurnOnSoundEffects": "Efeitos sonoros",
"SwitchMusicAccordingToTheEnvironment": "Trocar música conforme o ambiente" "SwitchMusicAccordingToTheEnvironment": "Trocar música conforme o ambiente",
"AnimationSettings": "animação",
"AnimationSpeed": "Velocidade da animação"
}, },
"DeckResults": { "DeckResults": {
"NoDeckGroupFound": "Não foi possível encontrar o baralho correspondente" "NoDeckGroupFound": "Não foi possível encontrar o baralho correspondente"
......
...@@ -197,7 +197,9 @@ ...@@ -197,7 +197,9 @@
"TurnOnMusic": "Activar música", "TurnOnMusic": "Activar música",
"TurnOnSoundEffects": "Efectos de sonido", "TurnOnSoundEffects": "Efectos de sonido",
"SwitchMusicAccordingToTheEnvironment": "Cambiar la música según el entorno", "SwitchMusicAccordingToTheEnvironment": "Cambiar la música según el entorno",
"LanguageSettings": "Idioma" "LanguageSettings": "Idioma",
"AnimationSettings": "animación",
"AnimationSpeed": "Velocidad de animación"
}, },
"DeckResults": { "DeckResults": {
"NoDeckGroupFound": "No se puede encontrar el mazo de cartas correspondiente" "NoDeckGroupFound": "No se puede encontrar el mazo de cartas correspondiente"
......
import { Form, Slider } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { useSnapshot } from "valtio";
import { settingStore } from "@/stores/settingStore";
export const AnimationSetting: React.FC = () => {
const { animation } = useSnapshot(settingStore);
const { t: i18n } = useTranslation("SystemSettings");
return (
<Form
initialValues={animation}
onValuesChange={(config) => settingStore.saveAnimationConfig(config)}
labelAlign="left"
>
<Form.Item label={i18n("AnimationSpeed")}>
<Form.Item name="speed" noStyle>
<Slider
style={{ width: 200 }}
min={0}
max={1}
step={0.01}
tooltip={{
formatter: (value) => ((value || 0) * 100).toFixed(0),
}}
/>
</Form.Item>
</Form.Item>
</Form>
);
};
import { AudioFilled, TranslationOutlined } from "@ant-design/icons"; import {
AudioFilled,
PlayCircleOutlined,
TranslationOutlined,
} from "@ant-design/icons";
import { ConfigProvider, Modal, Tabs, TabsProps } from "antd"; import { ConfigProvider, Modal, Tabs, TabsProps } from "antd";
import zhCN from "antd/locale/zh_CN"; import zhCN from "antd/locale/zh_CN";
import React from "react"; import React from "react";
...@@ -7,6 +11,7 @@ import { useTranslation } from "react-i18next"; ...@@ -7,6 +11,7 @@ import { useTranslation } from "react-i18next";
import { I18NSelector } from "../I18N"; import { I18NSelector } from "../I18N";
import { theme } from "../theme"; import { theme } from "../theme";
import { AnimationSetting } from "./Animation";
import { AudioSetting } from "./Audio"; import { AudioSetting } from "./Audio";
/** 设置面板属性 */ /** 设置面板属性 */
...@@ -38,6 +43,15 @@ export const Setting = (props: SettingProps) => { ...@@ -38,6 +43,15 @@ export const Setting = (props: SettingProps) => {
), ),
children: <I18NSelector />, children: <I18NSelector />,
}, },
{
key: "animation",
label: (
<>
{i18n("AnimationSettings")} <PlayCircleOutlined />
</>
),
children: <AnimationSetting />,
},
]; ];
return <Tabs defaultActiveKey={defaultKey} items={items} />; return <Tabs defaultActiveKey={defaultKey} items={items} />;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment