@luna-editor/engine 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Player.js CHANGED
@@ -17,6 +17,7 @@ import { EndScreen } from "./components/EndScreen";
17
17
  import { GameScreen } from "./components/GameScreen";
18
18
  import { OverlayUI } from "./components/OverlayUI";
19
19
  import { PluginComponentProvider } from "./components/PluginComponentProvider";
20
+ import { AudioProvider } from "./contexts/AudioContext";
20
21
  import { DataProvider } from "./contexts/DataContext";
21
22
  import { useBacklog } from "./hooks/useBacklog";
22
23
  import { usePlayerLogic } from "./hooks/usePlayerLogic";
@@ -26,8 +27,16 @@ import { usePreloadImages } from "./hooks/usePreloadImages";
26
27
  import { useTypewriter } from "./hooks/useTypewriter";
27
28
  import { PluginManager } from "./plugin/PluginManager";
28
29
  import { ComponentType } from "./sdk";
29
- export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd, onScenarioStart, onScenarioCancelled, className, autoplay = false, }) => {
30
+ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd, onScenarioStart, onScenarioCancelled, onSettingsChange, className, autoplay = false, }) => {
30
31
  var _a;
32
+ // デフォルト値とマージ
33
+ const mergedSettings = Object.assign({ textSpeed: 80, autoPlaySpeed: 3, bgmVolume: 1.0, seVolume: 1.0, voiceVolume: 1.0, effectVolume: 1.0, textSoundVolume: 1.0 }, settings);
34
+ // プラグインからの設定更新ハンドラ
35
+ const handleSettingsUpdate = useCallback((updatedSettings) => {
36
+ var _a, _b;
37
+ const newSettings = Object.assign(Object.assign({ aspectRatio: (_a = settings === null || settings === void 0 ? void 0 : settings.aspectRatio) !== null && _a !== void 0 ? _a : "16:9", bgObjectFit: (_b = settings === null || settings === void 0 ? void 0 : settings.bgObjectFit) !== null && _b !== void 0 ? _b : "cover" }, settings), updatedSettings);
38
+ onSettingsChange === null || onSettingsChange === void 0 ? void 0 : onSettingsChange(newSettings);
39
+ }, [settings, onSettingsChange]);
31
40
  const pluginManagerRef = useRef(new PluginManager());
32
41
  // グローバルUIAPIを初期化(プラグインコンポーネントから使用可能にする)
33
42
  useEffect(() => {
@@ -127,7 +136,7 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
127
136
  }
128
137
  }, [isFirstRenderComplete]);
129
138
  const { displayText, isTyping, skipTyping, startTyping } = useTypewriter({
130
- speed: 80,
139
+ speed: mergedSettings.textSpeed,
131
140
  });
132
141
  // 現在の表示可能なブロックを取得
133
142
  const currentBlock = displayableBlockIndices[state.currentBlockIndex] !== undefined
@@ -265,13 +274,13 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
265
274
  clearLogs: backlog.clearLogs,
266
275
  },
267
276
  settings: {
268
- aspectRatio: (_a = settings === null || settings === void 0 ? void 0 : settings.aspectRatio) !== null && _a !== void 0 ? _a : "16:9",
269
- bgObjectFit: (_b = settings === null || settings === void 0 ? void 0 : settings.bgObjectFit) !== null && _b !== void 0 ? _b : "contain",
270
- textSpeed: 5,
271
- autoPlaySpeed: 3,
272
- bgmVolume: 0.8,
273
- seVolume: 1.0,
274
- voiceVolume: 1.0,
277
+ aspectRatio: (_a = mergedSettings.aspectRatio) !== null && _a !== void 0 ? _a : "16:9",
278
+ bgObjectFit: (_b = mergedSettings.bgObjectFit) !== null && _b !== void 0 ? _b : "contain",
279
+ textSpeed: mergedSettings.textSpeed,
280
+ autoPlaySpeed: mergedSettings.autoPlaySpeed,
281
+ bgmVolume: mergedSettings.bgmVolume,
282
+ seVolume: mergedSettings.seVolume,
283
+ voiceVolume: mergedSettings.voiceVolume,
275
284
  skipMode: "unread",
276
285
  },
277
286
  pluginAssets: {
@@ -329,8 +338,14 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
329
338
  touchAction: "none",
330
339
  userSelect: "none",
331
340
  WebkitUserSelect: "none",
332
- }, children: _jsx("div", { className: "relative bg-white flex flex-col w-full overflow-hidden h-full", style: { aspectRatio: getAspectRatio() }, children: _jsxs(DataProvider, { data: dataContext, children: [_jsx("div", { className: "h-full", children: _jsx(GameScreen, { scenario: scenario, currentBlock: currentBlock, displayedCharacters: displayedCharacters }) }), _jsx(OverlayUI, { children: _jsxs("div", { className: "h-full w-full relative", children: [_jsx(PluginComponentProvider, { type: ComponentType.DialogueBox, pluginManager: pluginManagerRef.current, fallback: DialogueBox }), pluginManagerRef.current
333
- .getRegisteredComponents()
334
- .filter((type) => type !== ComponentType.DialogueBox) // DialogueBoxは既に上でレンダリング済み
335
- .map((componentType) => (_jsx(PluginComponentProvider, { type: componentType, pluginManager: pluginManagerRef.current }, componentType)))] }) })] }) }) })] }))] }));
341
+ }, children: _jsx("div", { className: "relative bg-white flex flex-col w-full overflow-hidden h-full", style: { aspectRatio: getAspectRatio() }, children: _jsx(DataProvider, { data: dataContext, onSettingsUpdate: handleSettingsUpdate, children: _jsxs(AudioProvider, { settings: {
342
+ bgmVolume: mergedSettings.bgmVolume,
343
+ seVolume: mergedSettings.seVolume,
344
+ voiceVolume: mergedSettings.voiceVolume,
345
+ effectVolume: mergedSettings.effectVolume,
346
+ textSoundVolume: mergedSettings.textSoundVolume,
347
+ }, children: [_jsx("div", { className: "h-full", children: _jsx(GameScreen, { scenario: scenario, currentBlock: currentBlock, displayedCharacters: displayedCharacters }) }), _jsx(OverlayUI, { children: _jsxs("div", { className: "h-full w-full relative", children: [_jsx(PluginComponentProvider, { type: ComponentType.DialogueBox, pluginManager: pluginManagerRef.current, fallback: DialogueBox }), pluginManagerRef.current
348
+ .getRegisteredComponents()
349
+ .filter((type) => type !== ComponentType.DialogueBox) // DialogueBoxは既に上でレンダリング済み
350
+ .map((componentType) => (_jsx(PluginComponentProvider, { type: componentType, pluginManager: pluginManagerRef.current }, componentType)))] }) })] }) }) }) })] }))] }));
336
351
  };
@@ -0,0 +1,13 @@
1
+ import { type ReactNode } from "react";
2
+ export interface AudioSettings {
3
+ bgmVolume: number;
4
+ seVolume: number;
5
+ voiceVolume: number;
6
+ effectVolume: number;
7
+ textSoundVolume: number;
8
+ }
9
+ export declare const AudioProvider: ({ children, settings, }: {
10
+ children: ReactNode;
11
+ settings: AudioSettings;
12
+ }) => import("react/jsx-runtime").JSX.Element;
13
+ export declare const useAudioSettings: () => AudioSettings;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext } from "react";
3
+ const AudioContext = createContext({
4
+ bgmVolume: 1.0,
5
+ seVolume: 1.0,
6
+ voiceVolume: 1.0,
7
+ effectVolume: 1.0,
8
+ textSoundVolume: 1.0,
9
+ });
10
+ export const AudioProvider = ({ children, settings, }) => {
11
+ return (_jsx(AudioContext.Provider, { value: settings, children: children }));
12
+ };
13
+ export const useAudioSettings = () => useContext(AudioContext);
@@ -1,5 +1,6 @@
1
1
  import { type ReactNode } from "react";
2
- import type { DataAPI, DataContext } from "../sdk";
2
+ import type { DataAPI, DataContext, PlayerSettingsData } from "../sdk";
3
+ type SettingsUpdater = (settings: Partial<PlayerSettingsData>) => void;
3
4
  /**
4
5
  * データプロバイダー
5
6
  * プラグインがシナリオ情報にリアクティブにアクセスするためのProvider
@@ -15,6 +16,7 @@ import type { DataAPI, DataContext } from "../sdk";
15
16
  */
16
17
  export declare const DataProvider: React.FC<{
17
18
  data: DataContext;
19
+ onSettingsUpdate?: SettingsUpdater;
18
20
  children: ReactNode;
19
21
  }>;
20
22
  /**
@@ -22,3 +24,4 @@ export declare const DataProvider: React.FC<{
22
24
  * プラグインから呼び出される
23
25
  */
24
26
  export declare function useDataAPI(): DataAPI;
27
+ export {};
@@ -2,6 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, } from "react";
3
3
  const DataContextInstance = createContext(null);
4
4
  const SubscribersContext = createContext(null);
5
+ const SettingsUpdaterContext = createContext(null);
5
6
  /**
6
7
  * データプロバイダー
7
8
  * プラグインがシナリオ情報にリアクティブにアクセスするためのProvider
@@ -15,7 +16,7 @@ const SubscribersContext = createContext(null);
15
16
  * </DataProvider>
16
17
  * ```
17
18
  */
18
- export const DataProvider = ({ data, children }) => {
19
+ export const DataProvider = ({ data, onSettingsUpdate, children }) => {
19
20
  const subscribers = useMemo(() => new Map(), // eslint-disable-line @typescript-eslint/no-explicit-any
20
21
  []);
21
22
  const previousDataRef = useRef(data);
@@ -48,7 +49,7 @@ export const DataProvider = ({ data, children }) => {
48
49
  }
49
50
  previousDataRef.current = data;
50
51
  }, [data, subscribers]);
51
- return (_jsx(SubscribersContext.Provider, { value: subscribers, children: _jsx(DataContextInstance.Provider, { value: data, children: children }) }));
52
+ return (_jsx(SubscribersContext.Provider, { value: subscribers, children: _jsx(SettingsUpdaterContext.Provider, { value: onSettingsUpdate !== null && onSettingsUpdate !== void 0 ? onSettingsUpdate : null, children: _jsx(DataContextInstance.Provider, { value: data, children: children }) }) }));
52
53
  };
53
54
  /**
54
55
  * DataAPI実装を提供するフック
@@ -57,6 +58,7 @@ export const DataProvider = ({ data, children }) => {
57
58
  export function useDataAPI() {
58
59
  const data = useContext(DataContextInstance);
59
60
  const subscribers = useContext(SubscribersContext);
61
+ const settingsUpdater = useContext(SettingsUpdaterContext);
60
62
  if (!data || !subscribers) {
61
63
  throw new Error("useDataAPI must be used within DataProvider");
62
64
  }
@@ -93,9 +95,18 @@ export function useDataAPI() {
93
95
  (_a = subscribers.get(subscriberKey)) === null || _a === void 0 ? void 0 : _a.delete(callback);
94
96
  };
95
97
  }, [subscribers]);
98
+ const updateSettings = useCallback((settings) => {
99
+ if (settingsUpdater) {
100
+ settingsUpdater(settings);
101
+ }
102
+ else {
103
+ console.warn("updateSettings called but no settings updater provided");
104
+ }
105
+ }, [settingsUpdater]);
96
106
  return useMemo(() => ({
97
107
  get,
98
108
  subscribe,
99
109
  watch,
100
- }), [get, subscribe, watch]);
110
+ updateSettings,
111
+ }), [get, subscribe, watch, updateSettings]);
101
112
  }
@@ -1,13 +1,16 @@
1
1
  import { useCallback, useRef } from "react";
2
+ import { useAudioSettings } from "../contexts/AudioContext";
2
3
  export const useVoice = () => {
3
4
  const audioRef = useRef(null);
5
+ const audioSettings = useAudioSettings();
4
6
  const playVoice = useCallback((voiceUrl) => {
5
7
  if (!audioRef.current) {
6
8
  audioRef.current = new Audio();
7
9
  }
8
10
  audioRef.current.src = voiceUrl;
11
+ audioRef.current.volume = audioSettings.voiceVolume;
9
12
  audioRef.current.play().catch(console.error);
10
- }, []);
13
+ }, [audioSettings.voiceVolume]);
11
14
  const stopVoice = useCallback(() => {
12
15
  if (audioRef.current) {
13
16
  audioRef.current.pause();
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export { useScreenSizeAtom } from "./atoms/screen-size";
2
2
  export { OverlayUI } from "./components/OverlayUI";
3
3
  export { aspectRatio, BasisHeight, BasisWidth } from "./constants/screen-size";
4
+ export type { AudioSettings } from "./contexts/AudioContext";
5
+ export { useAudioSettings } from "./contexts/AudioContext";
4
6
  export { useDataAPI } from "./contexts/DataContext";
5
7
  export { setGlobalUIAPI, usePluginAPI, useUIVisibility, } from "./hooks/usePluginAPI";
6
8
  export { useScreenScale, useScreenSize, useToPixel, } from "./hooks/useScreenSize";
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  export { useScreenSizeAtom } from "./atoms/screen-size";
3
3
  export { OverlayUI } from "./components/OverlayUI";
4
4
  export { aspectRatio, BasisHeight, BasisWidth } from "./constants/screen-size";
5
+ export { useAudioSettings } from "./contexts/AudioContext";
5
6
  export { useDataAPI } from "./contexts/DataContext";
6
7
  export { setGlobalUIAPI, usePluginAPI, useUIVisibility, } from "./hooks/usePluginAPI";
7
8
  export { useScreenScale, useScreenSize, useToPixel, } from "./hooks/useScreenSize";
@@ -621,6 +621,9 @@ export class PluginManager {
621
621
  watch: () => {
622
622
  throw new Error("DataAPI.watch() can only be called from within DataProvider context");
623
623
  },
624
+ updateSettings: () => {
625
+ throw new Error("DataAPI.updateSettings() can only be called from within DataProvider context");
626
+ },
624
627
  },
625
628
  };
626
629
  }
package/dist/sdk.d.ts CHANGED
@@ -48,7 +48,7 @@ export interface BacklogData {
48
48
  * プレイヤー設定データ
49
49
  */
50
50
  export interface PlayerSettingsData extends PlayerSettings {
51
- /** テキスト速度 (1-10) */
51
+ /** テキスト速度 (ミリ秒/文字) */
52
52
  textSpeed: number;
53
53
  /** 自動再生速度 (秒) */
54
54
  autoPlaySpeed: number;
@@ -168,6 +168,11 @@ export interface DataAPI {
168
168
  * @returns unsubscribe関数
169
169
  */
170
170
  watch<K extends keyof DataContext, P extends keyof DataContext[K]>(key: K, property: P, callback: DataSubscriber<DataContext[K][P]>): () => void;
171
+ /**
172
+ * プレイヤー設定を更新
173
+ * @param settings - 更新する設定値(部分更新可能)
174
+ */
175
+ updateSettings(settings: Partial<PlayerSettingsData>): void;
171
176
  }
172
177
  export interface PluginAPI {
173
178
  style: StyleAPI;
package/dist/types.d.ts CHANGED
@@ -153,6 +153,20 @@ export interface PublishedScenario {
153
153
  export interface PlayerSettings {
154
154
  aspectRatio: string;
155
155
  bgObjectFit: "contain" | "cover";
156
+ /** 文字送り速度 (ミリ秒/文字, デフォルト: 80) */
157
+ textSpeed?: number;
158
+ /** 自動再生速度 (秒, デフォルト: 3) */
159
+ autoPlaySpeed?: number;
160
+ /** BGM音量 (0-1, デフォルト: 1) */
161
+ bgmVolume?: number;
162
+ /** SE音量 (0-1, デフォルト: 1) */
163
+ seVolume?: number;
164
+ /** パートボイス音量 (0-1, デフォルト: 1) */
165
+ voiceVolume?: number;
166
+ /** エフェクト音音量 (0-1, デフォルト: 1) */
167
+ effectVolume?: number;
168
+ /** テキスト音音量 (0-1, デフォルト: 1) */
169
+ textSoundVolume?: number;
156
170
  }
157
171
  export interface PluginConfig {
158
172
  packageName: string;
@@ -167,6 +181,7 @@ export interface PlayerProps {
167
181
  onScenarioEnd?: () => void;
168
182
  onScenarioStart?: () => void;
169
183
  onScenarioCancelled?: () => void;
184
+ onSettingsChange?: (settings: PlayerSettings) => void;
170
185
  className?: string;
171
186
  autoplay?: boolean;
172
187
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luna-editor/engine",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Luna Editor scenario playback engine",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",