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

Merge branch 'optimize/card_move_speed' into 'main'

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

See merge request !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";
import { proxy, subscribe } from "valtio";
import { type NeosStore } from "../shared";
import { AnimationConfig, defaultAnimationConfig } from "./animation";
import { AudioConfig, defaultAudioConfig } from "./audio";
/** 将设置保存到本地 */
const NEO_SETTING_CONFIG = "__neo_setting_config__";
/** 设置项 */
type SettingStoreConfig = Pick<SettingStore, "audio">;
type SettingStoreConfig = Pick<SettingStore, "audio" | "animation">;
/** 默认设置 */
const defaultSettingConfig: SettingStoreConfig = {
audio: defaultAudioConfig,
animation: defaultAnimationConfig,
};
/** 获取默认设置 */
......@@ -21,7 +23,13 @@ function getDefaultSetting() {
if (!isSSR()) {
/** 获取默认设置 */
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;
}
......@@ -33,14 +41,23 @@ class SettingStore implements NeosStore {
/** 音频设置 */
audio: AudioConfig = defaultSetting.audio;
/** Animation Configuration */
animation: AnimationConfig = defaultSetting.animation;
/** 保存音频设置 */
saveAudioConfig(config: Partial<AudioConfig>): void {
Object.assign(this.audio, config);
}
/** save Animation Configuration */
saveAnimationConfig(config: Partial<AnimationConfig>): void {
Object.assign(this.animation, config);
}
reset(): void {
const defaultSetting = getDefaultSetting();
this.audio = defaultSetting.audio;
this.animation = defaultSetting.animation;
}
}
......@@ -52,7 +69,7 @@ subscribe(settingStore, () => {
if (!isSSR()) {
localStorage.setItem(
NEO_SETTING_CONFIG,
JSON.stringify(pick(settingStore, ["audio"])),
JSON.stringify(pick(settingStore, ["audio", "animation"])),
);
}
});
......@@ -3,7 +3,7 @@ import { isMe } from "@/stores";
import { matConfig } from "../../css";
import type { MoveFunc } from "./types";
import { asyncStart } from "./utils";
import { asyncStart, getDuration } from "./utils";
const {
BLOCK_WIDTH,
......@@ -45,5 +45,9 @@ export const moveToDeck: MoveFunc = async (props) => {
ry: isMe(controller) ? (zone === DECK ? 180 : 0) : 180,
zIndex: z,
height: DECK_CARD_HEIGHT,
config: {
duration: getDuration(),
},
});
};
......@@ -5,7 +5,7 @@ import { isMe } from "@/stores";
import { matConfig } from "../../css";
import type { MoveFunc } from "./types";
import { asyncStart } from "./utils";
import { asyncStart, getDuration } from "./utils";
const {
BLOCK_WIDTH,
......@@ -107,7 +107,7 @@ export const moveToGround: MoveFunc = async (props) => {
ry,
rz,
config: {
tension: 250,
duration: getDuration(),
clamp: true,
easing: easings.easeOutSine,
},
......@@ -121,7 +121,7 @@ export const moveToGround: MoveFunc = async (props) => {
zIndex: is_overlay ? 1 : 3,
config: {
easing: easings.easeInQuad,
duration: 200,
duration: 100,
clamp: true,
},
});
......
......@@ -3,7 +3,7 @@ import { cardStore, isMe } from "@/stores";
import { matConfig } from "../../css";
import type { MoveFunc } from "./types";
import { asyncStart } from "./utils";
import { asyncStart, getDuration } from "./utils";
const {
BLOCK_HEIGHT_M,
......@@ -55,5 +55,9 @@ export const moveToHand: MoveFunc = async (props) => {
height: HAND_CARD_HEIGHT,
zIndex: sequence,
// rx: -PLANE_ROTATE_X,
config: {
duration: getDuration(),
clamp: true,
},
});
};
......@@ -3,7 +3,7 @@ import { isMe } from "@/stores";
import { matConfig } from "../../css";
import type { MoveFunc } from "./types";
import { asyncStart } from "./utils";
import { asyncStart, getDuration } from "./utils";
const {
BLOCK_WIDTH,
......@@ -42,7 +42,8 @@ export const moveToOutside: MoveFunc = async (props) => {
subZ: 100,
zIndex: sequence,
config: {
tension: 140,
duration: getDuration(),
clamp: true,
},
});
api.set({ subZ: 0 });
......
import { type SpringConfig, type SpringRef } from "@react-spring/web";
import { settingStore } from "@/stores/settingStore";
export const asyncStart = <T extends {}>(api: SpringRef<T>) => {
return (p: Partial<T> & { config?: SpringConfig }) =>
new Promise((resolve) => {
......@@ -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 @@
"TurnOnMusic": "Música",
"TurnOnSoundEffects": "Efeitos sonoros",
"SwitchMusicAccordingToTheEnvironment": "Trocar música conforme o ambiente",
"LanguageSettings": "Idioma"
"LanguageSettings": "Idioma",
"AnimationSettings": "Animação",
"AnimationSpeed": "Velocidade da animação"
},
"DeckResults": {
"NoDeckGroupFound": "Não foi possível encontrar o baralho correspondente"
......
......@@ -199,7 +199,9 @@
"TurnOnMusic": "开启音乐",
"TurnOnSoundEffects": "开启音效",
"SwitchMusicAccordingToTheEnvironment": "根据环境切换音乐",
"LanguageSettings": "语言"
"LanguageSettings": "语言",
"AnimationSettings": "动画",
"AnimationSpeed": "动画速度"
},
"DeckResults": {
"NoDeckGroupFound": "找不到相应卡组"
......
......@@ -199,7 +199,9 @@
"TurnOnMusic": "Music",
"TurnOnSoundEffects": "Sound effects",
"SwitchMusicAccordingToTheEnvironment": "Switch music according to the environment",
"LanguageSettings": "Language"
"LanguageSettings": "Language",
"AnimationSettings": "Animation",
"AnimationSpeed": "Animation speed"
},
"DeckResults": {
"NoDeckGroupFound": "Cannot find the corresponding card deck"
......
......@@ -197,7 +197,9 @@
"TurnOnMusic": "Activer la musique",
"TurnOnSoundEffects": "Effets sonores",
"SwitchMusicAccordingToTheEnvironment": "Changer la musique en fonction de l'environnement",
"LanguageSettings": "Langue"
"LanguageSettings": "Langue",
"AnimationSettings": "Animation",
"AnimationSpeed": "Vitesse d'animation"
},
"DeckResults": {
"NoDeckGroupFound": "Impossible de trouver le jeu de cartes correspondant"
......
......@@ -197,7 +197,9 @@
"TurnOnMusic": "音楽をオンにする",
"TurnOnSoundEffects": "効果音をオンにする",
"SwitchMusicAccordingToTheEnvironment": "環境に応じて音楽を切り替える",
"LanguageSettings": "言語"
"LanguageSettings": "言語",
"AnimationSettings": "アニメーション",
"AnimationSpeed": "アニメーション速度"
},
"DeckResults": {
"NoDeckGroupFound": "対応するカードデッキが見つかりません"
......
......@@ -196,7 +196,9 @@
"AudioSettings": "Configurações de áudio",
"TurnOnMusic": "Música",
"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": {
"NoDeckGroupFound": "Não foi possível encontrar o baralho correspondente"
......
......@@ -197,7 +197,9 @@
"TurnOnMusic": "Activar música",
"TurnOnSoundEffects": "Efectos de sonido",
"SwitchMusicAccordingToTheEnvironment": "Cambiar la música según el entorno",
"LanguageSettings": "Idioma"
"LanguageSettings": "Idioma",
"AnimationSettings": "animación",
"AnimationSpeed": "Velocidad de animación"
},
"DeckResults": {
"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 zhCN from "antd/locale/zh_CN";
import React from "react";
......@@ -7,6 +11,7 @@ import { useTranslation } from "react-i18next";
import { I18NSelector } from "../I18N";
import { theme } from "../theme";
import { AnimationSetting } from "./Animation";
import { AudioSetting } from "./Audio";
/** 设置面板属性 */
......@@ -38,6 +43,15 @@ export const Setting = (props: SettingProps) => {
),
children: <I18NSelector />,
},
{
key: "animation",
label: (
<>
{i18n("AnimationSettings")} <PlayCircleOutlined />
</>
),
children: <AnimationSetting />,
},
];
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