@luna-editor/engine 0.1.0 → 0.3.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.
Files changed (65) hide show
  1. package/dist/Player.d.ts +1 -1
  2. package/dist/Player.js +527 -85
  3. package/dist/api/conversationBranch.d.ts +4 -0
  4. package/dist/api/conversationBranch.js +83 -0
  5. package/dist/components/BackgroundLayer.d.ts +19 -0
  6. package/dist/components/BackgroundLayer.js +218 -0
  7. package/dist/components/ClickWaitIndicator.d.ts +10 -0
  8. package/dist/components/ClickWaitIndicator.js +31 -0
  9. package/dist/components/ConversationBranchBox.d.ts +2 -0
  10. package/dist/components/ConversationBranchBox.js +29 -0
  11. package/dist/components/DialogueBox.js +16 -1
  12. package/dist/components/FontSettingsPanel.d.ts +10 -0
  13. package/dist/components/FontSettingsPanel.js +30 -0
  14. package/dist/components/FullscreenTextBox.d.ts +6 -0
  15. package/dist/components/FullscreenTextBox.js +70 -0
  16. package/dist/components/GameScreen.d.ts +1 -0
  17. package/dist/components/GameScreen.js +363 -81
  18. package/dist/components/PluginComponentProvider.d.ts +2 -2
  19. package/dist/components/PluginComponentProvider.js +3 -3
  20. package/dist/components/TimeWaitIndicator.d.ts +15 -0
  21. package/dist/components/TimeWaitIndicator.js +17 -0
  22. package/dist/contexts/AudioContext.d.ts +14 -0
  23. package/dist/contexts/AudioContext.js +14 -0
  24. package/dist/contexts/DataContext.d.ts +4 -1
  25. package/dist/contexts/DataContext.js +82 -13
  26. package/dist/hooks/useBacklog.js +3 -0
  27. package/dist/hooks/useConversationBranch.d.ts +16 -0
  28. package/dist/hooks/useConversationBranch.js +125 -0
  29. package/dist/hooks/useFontLoader.d.ts +23 -0
  30. package/dist/hooks/useFontLoader.js +153 -0
  31. package/dist/hooks/useFullscreenText.d.ts +17 -0
  32. package/dist/hooks/useFullscreenText.js +120 -0
  33. package/dist/hooks/usePlayerLogic.d.ts +10 -3
  34. package/dist/hooks/usePlayerLogic.js +115 -18
  35. package/dist/hooks/usePluginEvents.d.ts +4 -1
  36. package/dist/hooks/usePluginEvents.js +16 -11
  37. package/dist/hooks/usePreloadImages.js +27 -7
  38. package/dist/hooks/useSoundPlayer.d.ts +15 -0
  39. package/dist/hooks/useSoundPlayer.js +209 -0
  40. package/dist/hooks/useTypewriter.d.ts +6 -2
  41. package/dist/hooks/useTypewriter.js +42 -6
  42. package/dist/hooks/useVoice.js +7 -1
  43. package/dist/index.d.ts +6 -3
  44. package/dist/index.js +3 -1
  45. package/dist/plugin/PluginManager.d.ts +66 -2
  46. package/dist/plugin/PluginManager.js +352 -79
  47. package/dist/sdk.d.ts +184 -22
  48. package/dist/sdk.js +27 -2
  49. package/dist/types.d.ts +303 -4
  50. package/dist/utils/branchBlockConverter.d.ts +2 -0
  51. package/dist/utils/branchBlockConverter.js +21 -0
  52. package/dist/utils/branchNavigator.d.ts +14 -0
  53. package/dist/utils/branchNavigator.js +55 -0
  54. package/dist/utils/facePositionCalculator.js +0 -1
  55. package/dist/utils/variableManager.d.ts +18 -0
  56. package/dist/utils/variableManager.js +159 -0
  57. package/package.json +1 -1
  58. package/dist/components/ConversationLogUI.d.ts +0 -2
  59. package/dist/components/ConversationLogUI.js +0 -115
  60. package/dist/hooks/useConversationLog.d.ts +0 -14
  61. package/dist/hooks/useConversationLog.js +0 -82
  62. package/dist/hooks/useUIVisibility.d.ts +0 -9
  63. package/dist/hooks/useUIVisibility.js +0 -19
  64. package/dist/plugin/luna-react.d.ts +0 -41
  65. package/dist/plugin/luna-react.js +0 -99
package/dist/Player.js CHANGED
@@ -10,33 +10,75 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
12
12
  import { clsx } from "clsx";
13
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
13
+ import React, { useCallback, useEffect, useMemo, useRef, useState, } from "react";
14
14
  import { useScreenSizeAtom } from "./atoms/screen-size";
15
+ import { BackgroundLayer } from "./components/BackgroundLayer";
16
+ import { ClickWaitIndicator } from "./components/ClickWaitIndicator";
17
+ import { ConversationBranchBox } from "./components/ConversationBranchBox";
15
18
  import { DialogueBox } from "./components/DialogueBox";
16
19
  import { EndScreen } from "./components/EndScreen";
20
+ import { FullscreenTextBox } from "./components/FullscreenTextBox";
17
21
  import { GameScreen } from "./components/GameScreen";
18
22
  import { OverlayUI } from "./components/OverlayUI";
19
23
  import { PluginComponentProvider } from "./components/PluginComponentProvider";
24
+ import { TimeWaitIndicator } from "./components/TimeWaitIndicator";
25
+ import { AudioProvider } from "./contexts/AudioContext";
20
26
  import { DataProvider } from "./contexts/DataContext";
21
27
  import { useBacklog } from "./hooks/useBacklog";
28
+ import { useConversationBranch } from "./hooks/useConversationBranch";
29
+ import { useFontLoader } from "./hooks/useFontLoader";
22
30
  import { usePlayerLogic } from "./hooks/usePlayerLogic";
23
31
  import { setGlobalUIAPI } from "./hooks/usePluginAPI";
24
32
  import { usePluginEvents } from "./hooks/usePluginEvents";
25
33
  import { usePreloadImages } from "./hooks/usePreloadImages";
34
+ import { useSoundPlayer } from "./hooks/useSoundPlayer";
26
35
  import { useTypewriter } from "./hooks/useTypewriter";
27
36
  import { PluginManager } from "./plugin/PluginManager";
28
37
  import { ComponentType } from "./sdk";
29
- export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd, onScenarioStart, onScenarioCancelled, className, autoplay = false, }) => {
30
- var _a;
31
- const pluginManagerRef = useRef(new PluginManager());
38
+ import { convertBranchBlockToScenarioBlock } from "./utils/branchBlockConverter";
39
+ import { BranchNavigator } from "./utils/branchNavigator";
40
+ import { VariableManager } from "./utils/variableManager";
41
+ export const Player = ({ scenario: scenarioProp, settings, plugins = [], sounds = [], onEnd, onScenarioEnd, onScenarioStart, onScenarioCancelled, onSettingsChange, className, autoplay = false, preventDefaultScroll = true, screenSize: screenSizeProp, disableKeyboardNavigation = false, }) => {
42
+ var _a, _b, _c, _d, _e;
43
+ // scenario.blocks が存在しない場合は空の配列を使用
44
+ const scenario = useMemo(() => {
45
+ var _a;
46
+ return (Object.assign(Object.assign({}, scenarioProp), { blocks: (_a = scenarioProp.blocks) !== null && _a !== void 0 ? _a : [] }));
47
+ }, [scenarioProp]);
48
+ // デフォルト値とマージ
49
+ const mergedSettings = Object.assign({ textSpeed: 80, autoPlaySpeed: 3, bgmVolume: 0.5, seVolume: 1.0, voiceVolume: 1.0, effectVolume: 1.0, textSoundVolume: 1.0, defaultBackgroundFadeDuration: 0 }, settings);
50
+ // プラグインからの設定更新ハンドラ
51
+ const handleSettingsUpdate = useCallback((updatedSettings) => {
52
+ var _a, _b;
53
+ 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);
54
+ onSettingsChange === null || onSettingsChange === void 0 ? void 0 : onSettingsChange(newSettings);
55
+ }, [settings, onSettingsChange]);
56
+ // 遅延初期化でPluginManagerを作成(毎レンダリングでnew PluginManager()が評価されるのを防ぐ)
57
+ const pluginManagerRef = useRef(undefined);
58
+ if (!pluginManagerRef.current) {
59
+ pluginManagerRef.current = new PluginManager();
60
+ }
61
+ // 以降の参照用(undefinedにならないことを保証)
62
+ const pluginManager = pluginManagerRef.current;
63
+ const handleChoiceSelectionRef = useRef(null);
32
64
  // グローバルUIAPIを初期化(プラグインコンポーネントから使用可能にする)
33
65
  useEffect(() => {
34
- const uiAPI = pluginManagerRef.current.getUIAPI();
35
- setGlobalUIAPI(uiAPI, pluginManagerRef.current);
36
- }, []);
66
+ const uiAPI = pluginManager.getUIAPI();
67
+ setGlobalUIAPI(uiAPI, pluginManager);
68
+ }, [pluginManager]);
69
+ // プラグインのミュート状態を設定
70
+ useEffect(() => {
71
+ var _a;
72
+ pluginManager.setMuteAudio((_a = mergedSettings.muteAudio) !== null && _a !== void 0 ? _a : false);
73
+ }, [pluginManager, mergedSettings.muteAudio]);
37
74
  // 画面サイズの初期化
38
75
  const [, setScreenSize] = useScreenSizeAtom();
39
76
  useEffect(() => {
77
+ // screenSizeが明示的に指定されている場合はそれを使用(プレビュー用)
78
+ if (screenSizeProp) {
79
+ setScreenSize(screenSizeProp);
80
+ return;
81
+ }
40
82
  // クライアントサイドでのみ実行
41
83
  if (typeof window === "undefined")
42
84
  return;
@@ -50,47 +92,199 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
50
92
  };
51
93
  window.addEventListener("resize", handleResize);
52
94
  return () => window.removeEventListener("resize", handleResize);
53
- }, [setScreenSize]);
95
+ }, [setScreenSize, screenSizeProp]);
54
96
  // 表示可能なブロックのインデックスを事前計算
55
97
  const displayableBlockIndices = useMemo(() => {
56
- const supportedBlockTypes = ["dialogue", "narration"];
57
- return scenario.blocks
98
+ const supportedBlockTypes = [
99
+ "dialogue",
100
+ "narration",
101
+ "conversation_branch",
102
+ "fullscreen_text",
103
+ "click_wait",
104
+ "time_wait",
105
+ ];
106
+ const indices = scenario.blocks
58
107
  .map((block, index) => ({ block, index }))
59
108
  .filter(({ block }) => supportedBlockTypes.includes(block.blockType))
60
109
  .map(({ index }) => index);
110
+ return indices;
61
111
  }, [scenario.blocks]);
112
+ // 変数マネージャーの初期化
113
+ const variableManagerRef = useRef(scenario.variables && scenario.variables.length > 0
114
+ ? new VariableManager(scenario.variables)
115
+ : null);
116
+ // メディアタイプを判定するヘルパー関数
117
+ const getMediaType = useCallback((url) => {
118
+ var _a, _b;
119
+ const extension = (_b = (_a = url.split(".").pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : "";
120
+ if (["mp4", "webm", "ogg", "mov", "avi", "mkv"].includes(extension)) {
121
+ return "video";
122
+ }
123
+ if (extension === "gif") {
124
+ return "gif";
125
+ }
126
+ return "image";
127
+ }, []);
128
+ // 現在の背景状態を計算(character_entranceパターンに倣う)
129
+ // background_groupの場合は複数の背景を配列で返す
130
+ const calculateCurrentBackground = useCallback((upToBlockIndex) => {
131
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
132
+ let currentBackgroundBlock = null;
133
+ let previousBackgroundBlock = null;
134
+ let backgroundGroupBlock = null;
135
+ // 現在のブロックまでを逆順で走査して、最後のbackground_changeまたはbackground_groupを見つける
136
+ // background_changeが先に見つかった場合、background_groupは無視する(背景がリセットされる)
137
+ for (let i = upToBlockIndex; i >= 0; i--) {
138
+ const block = scenario.blocks[i];
139
+ // background_groupの場合
140
+ if (block.blockType === "background_group" &&
141
+ block.backgroundGroupItems &&
142
+ block.backgroundGroupItems.length > 0) {
143
+ // すでにbackground_changeが見つかっている場合は無視
144
+ // (background_changeがbackground_groupより後にあるので、背景はリセットされている)
145
+ if (currentBackgroundBlock) {
146
+ break;
147
+ }
148
+ backgroundGroupBlock = block;
149
+ break;
150
+ }
151
+ // imageUrlが存在し、かつ空文字列でないことを確認
152
+ const imageUrl = (_a = block.backgroundState) === null || _a === void 0 ? void 0 : _a.imageUrl;
153
+ if (block.blockType === "background_change" &&
154
+ imageUrl &&
155
+ imageUrl.trim() !== "") {
156
+ if (!currentBackgroundBlock) {
157
+ currentBackgroundBlock = block;
158
+ }
159
+ else if (!previousBackgroundBlock) {
160
+ previousBackgroundBlock = block;
161
+ break; // 2つ見つかったら終了
162
+ }
163
+ }
164
+ }
165
+ // background_groupが見つかった場合(background_changeより後にない場合のみ)
166
+ if (backgroundGroupBlock) {
167
+ return ((_c = (_b = backgroundGroupBlock.backgroundGroupItems) === null || _b === void 0 ? void 0 : _b.filter((item) => !!item.state.imageUrl && item.state.imageUrl.trim() !== "").map((item) => {
168
+ var _a, _b;
169
+ return ({
170
+ objectId: item.objectId,
171
+ stateId: item.stateId,
172
+ objectName: item.object.name,
173
+ stateName: item.state.name,
174
+ imageUrl: item.state.imageUrl,
175
+ webmUrl: (_a = item.state.webmUrl) !== null && _a !== void 0 ? _a : null,
176
+ objectFit: item.objectFit,
177
+ loop: item.loop,
178
+ mediaType: getMediaType(item.state.imageUrl),
179
+ opacity: item.opacity,
180
+ layer: (_b = item.layer) !== null && _b !== void 0 ? _b : "background",
181
+ });
182
+ })) !== null && _c !== void 0 ? _c : []);
183
+ }
184
+ if (!currentBackgroundBlock) {
185
+ return [];
186
+ }
187
+ const imageUrl = (_e = (_d = currentBackgroundBlock.backgroundState) === null || _d === void 0 ? void 0 : _d.imageUrl) !== null && _e !== void 0 ? _e : "";
188
+ // 前の背景ブロックのfadeDurationを使用(前の背景→現在の背景へのフェード時間)
189
+ // ブロックに設定がない場合はデフォルト値を使用
190
+ const previousOptions = previousBackgroundBlock === null || previousBackgroundBlock === void 0 ? void 0 : previousBackgroundBlock.options;
191
+ const blockFadeDuration = typeof (previousOptions === null || previousOptions === void 0 ? void 0 : previousOptions.fadeDuration) === "number"
192
+ ? previousOptions.fadeDuration
193
+ : 0;
194
+ const fadeDuration = blockFadeDuration > 0
195
+ ? blockFadeDuration
196
+ : mergedSettings.defaultBackgroundFadeDuration;
197
+ return [
198
+ {
199
+ objectId: (_f = currentBackgroundBlock.backgroundObjectId) !== null && _f !== void 0 ? _f : "",
200
+ stateId: (_g = currentBackgroundBlock.backgroundStateId) !== null && _g !== void 0 ? _g : "",
201
+ objectName: (_j = (_h = currentBackgroundBlock.backgroundObject) === null || _h === void 0 ? void 0 : _h.name) !== null && _j !== void 0 ? _j : "",
202
+ stateName: (_l = (_k = currentBackgroundBlock.backgroundState) === null || _k === void 0 ? void 0 : _k.name) !== null && _l !== void 0 ? _l : "",
203
+ imageUrl: imageUrl,
204
+ webmUrl: (_o = (_m = currentBackgroundBlock.backgroundState) === null || _m === void 0 ? void 0 : _m.webmUrl) !== null && _o !== void 0 ? _o : null,
205
+ objectFit: (_p = currentBackgroundBlock.backgroundObjectFit) !== null && _p !== void 0 ? _p : "cover",
206
+ loop: (_q = currentBackgroundBlock.backgroundLoop) !== null && _q !== void 0 ? _q : true,
207
+ mediaType: getMediaType(imageUrl),
208
+ fadeDuration,
209
+ opacity: 1.0,
210
+ layer: "background",
211
+ },
212
+ ];
213
+ }, [
214
+ scenario.blocks,
215
+ getMediaType,
216
+ mergedSettings.defaultBackgroundFadeDuration,
217
+ ]);
62
218
  const [state, setState] = useState({
63
219
  currentBlockIndex: 0,
64
220
  isPlaying: autoplay,
65
221
  isEnded: false,
222
+ variables: (_a = variableManagerRef.current) === null || _a === void 0 ? void 0 : _a.getVariablesMap(),
66
223
  });
224
+ const [currentBranchBlock, setCurrentBranchBlock] = useState(null);
225
+ // 遷移の種類を追跡(クリック、自動、時間待ちなど)
226
+ const [transitionSource, setTransitionSource] = useState("click");
227
+ // シナリオIDを追跡して、変更時に状態をリセット(プレビュー用)
228
+ const previousScenarioIdRef = useRef(scenario.id);
229
+ // シナリオが変更されたときに状態をリセット(keyを変更せずに対応)
230
+ useEffect(() => {
231
+ var _a;
232
+ if (previousScenarioIdRef.current !== scenario.id) {
233
+ previousScenarioIdRef.current = scenario.id;
234
+ // 状態をリセット
235
+ setState({
236
+ currentBlockIndex: 0,
237
+ isPlaying: autoplay,
238
+ isEnded: false,
239
+ variables: (_a = variableManagerRef.current) === null || _a === void 0 ? void 0 : _a.getVariablesMap(),
240
+ });
241
+ setCurrentBranchBlock(null);
242
+ }
243
+ }, [scenario.id, autoplay]);
67
244
  // 画像を事前読み込み
68
245
  const imagesLoaded = usePreloadImages(scenario);
246
+ // フォントを読み込み
247
+ const { isLoaded: fontsLoaded } = useFontLoader(scenario.fonts);
69
248
  // プラグインの読み込み状態
70
249
  const [pluginsLoaded, setPluginsLoaded] = useState(false);
250
+ // 読み込み済みプラグインのパッケージ名を追跡
251
+ const loadedPluginNamesRef = useRef(new Set());
71
252
  // プラグインの読み込み
253
+ // biome-ignore lint/correctness/useExhaustiveDependencies: pluginManagerは安定した参照なので依存配列から除外しても安全
72
254
  useEffect(() => {
255
+ let isCancelled = false;
73
256
  const loadPlugins = () => __awaiter(void 0, void 0, void 0, function* () {
74
- console.log("Loading plugins:", plugins);
75
- setPluginsLoaded(false);
76
- for (const plugin of plugins) {
77
- console.log("Loading plugin:", plugin.packageName);
78
- yield pluginManagerRef.current.loadPlugin(plugin.packageName, plugin.bundleUrl, plugin.config);
257
+ // 作品のサウンドデータをPluginManagerに設定
258
+ if (sounds.length > 0) {
259
+ pluginManager.setWorkSounds(sounds);
260
+ }
261
+ // 未読み込みのプラグインのみを抽出
262
+ const newPlugins = plugins.filter((plugin) => !loadedPluginNamesRef.current.has(plugin.packageName));
263
+ // 新しいプラグインがある場合のみローディング状態にする
264
+ if (newPlugins.length > 0) {
265
+ setPluginsLoaded(false);
266
+ for (const plugin of newPlugins) {
267
+ if (isCancelled)
268
+ return;
269
+ yield pluginManager.loadPlugin(plugin.packageName, plugin.bundleUrl, plugin.config);
270
+ loadedPluginNamesRef.current.add(plugin.packageName);
271
+ }
272
+ if (isCancelled)
273
+ return;
274
+ pluginManager.callHook("onInit");
275
+ pluginManager.showUI(ComponentType.DialogueBox);
276
+ // 全てのアセットプリロードが完了するまで待機
277
+ yield pluginManager.waitForPreloads();
278
+ }
279
+ if (!isCancelled) {
280
+ setPluginsLoaded(true);
79
281
  }
80
- // プラグイン初期化フック - すべてのプラグインの読み込み完了後に実行
81
- console.log("Calling onInit hook");
82
- pluginManagerRef.current.callHook("onInit");
83
- // DialogueBoxの初期状態を表示に設定
84
- pluginManagerRef.current.showUI(ComponentType.DialogueBox);
85
- setPluginsLoaded(true);
86
- console.log("All plugins loaded successfully");
87
282
  });
88
283
  loadPlugins();
89
284
  return () => {
90
- const manager = pluginManagerRef.current;
91
- manager.cleanup();
285
+ isCancelled = true;
92
286
  };
93
- }, [plugins]);
287
+ }, [plugins, sounds]);
94
288
  // 初回レンダリング完了フラグ
95
289
  const [isFirstRenderComplete, setIsFirstRenderComplete] = useState(false);
96
290
  // シナリオ開始コールバック(後でcurrentBlockが定義された後に実行)
@@ -104,58 +298,222 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
104
298
  // data-character-id属性を持つ最初のキャラクター要素を取得
105
299
  const characterSpriteElement = document.querySelector("[data-character-id]");
106
300
  const gameScreenElement = document.querySelector(".h-screen.w-full");
107
- console.log("🔍 [StyleAPI] Element registration:", {
108
- speakerNameElement: !!speakerNameElement,
109
- dialogueElement: !!dialogueElement,
110
- characterSpriteElement: !!characterSpriteElement,
111
- gameScreenElement: !!gameScreenElement,
112
- });
113
301
  if (speakerNameElement) {
114
- pluginManagerRef.current.registerStyleElement("speakerName", speakerNameElement);
302
+ pluginManager.registerStyleElement("speakerName", speakerNameElement);
115
303
  }
116
304
  if (dialogueElement) {
117
- pluginManagerRef.current.registerStyleElement("dialogueBox", dialogueElement);
118
- pluginManagerRef.current.registerStyleElement("scenarioBlockContent", dialogueElement);
305
+ pluginManager.registerStyleElement("dialogueBox", dialogueElement);
306
+ pluginManager.registerStyleElement("scenarioBlockContent", dialogueElement);
119
307
  }
120
308
  if (characterSpriteElement) {
121
- pluginManagerRef.current.registerStyleElement("characterSprite", characterSpriteElement);
309
+ pluginManager.registerStyleElement("characterSprite", characterSpriteElement);
122
310
  }
123
311
  if (gameScreenElement) {
124
- pluginManagerRef.current.registerStyleElement("gameScreen", gameScreenElement);
125
- pluginManagerRef.current.registerStyleElement("background", gameScreenElement);
312
+ pluginManager.registerStyleElement("gameScreen", gameScreenElement);
313
+ pluginManager.registerStyleElement("background", gameScreenElement);
126
314
  }
127
315
  }
128
- }, [isFirstRenderComplete]);
129
- const { displayText, isTyping, skipTyping, startTyping } = useTypewriter({
130
- speed: 80,
316
+ }, [pluginManager, isFirstRenderComplete]);
317
+ // Fullscreen text要素の登録(ブロックタイプがfullscreen_textの時のみ)
318
+ useEffect(() => {
319
+ if (!isFirstRenderComplete)
320
+ return;
321
+ // 少し遅延させてDOMが更新されるのを待つ
322
+ const timeoutId = setTimeout(() => {
323
+ const fullscreenOverlay = document.querySelector("[data-fullscreen-text-element]");
324
+ const fullscreenContainer = document.querySelector("[data-fullscreen-text-container]");
325
+ if (fullscreenOverlay) {
326
+ pluginManager.registerStyleElement("fullscreenTextOverlay", fullscreenOverlay);
327
+ }
328
+ if (fullscreenContainer) {
329
+ pluginManager.registerStyleElement("fullscreenTextContainer", fullscreenContainer);
330
+ }
331
+ }, 100);
332
+ return () => clearTimeout(timeoutId);
333
+ }, [pluginManager, isFirstRenderComplete]);
334
+ const { displayText, isTyping, skipTyping, startTyping, resetAccumulated } = useTypewriter({
335
+ speed: mergedSettings.textSpeed,
131
336
  });
132
- // 現在の表示可能なブロックを取得
133
- const currentBlock = displayableBlockIndices[state.currentBlockIndex] !== undefined
134
- ? scenario.blocks[displayableBlockIndices[state.currentBlockIndex]]
135
- : undefined;
337
+ // 現在の表示可能なブロックを取得(分岐ブロックが優先)
338
+ const currentBlock = currentBranchBlock ||
339
+ (displayableBlockIndices[state.currentBlockIndex] !== undefined
340
+ ? scenario.blocks[displayableBlockIndices[state.currentBlockIndex]]
341
+ : undefined);
342
+ // 前のブロックを取得(フェード処理用)
343
+ const previousBlock = useMemo(() => {
344
+ if (state.currentBlockIndex <= 0)
345
+ return null;
346
+ const prevIdx = displayableBlockIndices[state.currentBlockIndex - 1];
347
+ return prevIdx !== undefined ? scenario.blocks[prevIdx] : null;
348
+ }, [scenario.blocks, displayableBlockIndices, state.currentBlockIndex]);
136
349
  // usePlayerLogicに渡す実際のブロックインデックス
137
- const actualBlockIndex = (_a = displayableBlockIndices[state.currentBlockIndex]) !== null && _a !== void 0 ? _a : 0;
350
+ const actualBlockIndex = (_b = displayableBlockIndices[state.currentBlockIndex]) !== null && _b !== void 0 ? _b : 0;
138
351
  // バックログ機能
139
352
  const backlog = useBacklog({
140
353
  scenario,
141
354
  currentBlockIndex: actualBlockIndex,
142
355
  currentBlock: currentBlock || null,
143
356
  });
357
+ // サウンド再生機能
358
+ // actualBlockIndexの前後にあるサウンドブロックを処理するため、
359
+ // 表示ブロックに到達する直前のサウンドブロックを取得
360
+ const soundBlocksToProcess = useMemo(() => {
361
+ const blocks = [];
362
+ const currentDisplayableIdx = displayableBlockIndices[state.currentBlockIndex];
363
+ if (currentDisplayableIdx === undefined)
364
+ return blocks;
365
+ // 前の表示可能ブロックのインデックスを取得
366
+ const prevDisplayableIdx = state.currentBlockIndex > 0
367
+ ? displayableBlockIndices[state.currentBlockIndex - 1]
368
+ : -1;
369
+ // 前の表示ブロックから現在の表示ブロックまでの間にあるサウンドブロックを収集
370
+ for (let i = prevDisplayableIdx + 1; i < currentDisplayableIdx; i++) {
371
+ const block = scenario.blocks[i];
372
+ if (block.blockType === "bgm_play" ||
373
+ block.blockType === "se_play" ||
374
+ block.blockType === "bgm_stop") {
375
+ blocks.push(block);
376
+ }
377
+ }
378
+ return blocks;
379
+ }, [scenario.blocks, displayableBlockIndices, state.currentBlockIndex]);
380
+ // サウンド再生フック
381
+ useSoundPlayer({
382
+ soundBlocks: soundBlocksToProcess,
383
+ isFirstRenderComplete,
384
+ muteAudio: mergedSettings.muteAudio,
385
+ });
386
+ // 会話分岐機能
387
+ const branchNavigatorRef = useRef(new BranchNavigator());
388
+ const conversationBranch = useConversationBranch({
389
+ apiBaseUrl: typeof window !== "undefined" ? window.location.origin : "",
390
+ variableManager: variableManagerRef.current,
391
+ scenario,
392
+ });
393
+ // conversationBranchから安定した参照を抽出
394
+ const { loadBranch, selectChoice, getSelectedChoiceFullData, branchState } = conversationBranch;
395
+ // 選択肢選択処理
396
+ const handleChoiceSelection = useCallback((choiceId) => {
397
+ selectChoice(choiceId);
398
+ const selectedChoice = getSelectedChoiceFullData(choiceId);
399
+ if (selectedChoice) {
400
+ branchNavigatorRef.current.startBranchNavigation(selectedChoice);
401
+ pluginManager.callHook("onChoiceSelected", {
402
+ choiceId,
403
+ choiceText: selectedChoice.choiceText,
404
+ });
405
+ const firstBranchBlock = branchNavigatorRef.current.nextBranchBlock();
406
+ if (firstBranchBlock) {
407
+ setCurrentBranchBlock(convertBranchBlockToScenarioBlock(firstBranchBlock));
408
+ }
409
+ }
410
+ else {
411
+ console.error("[ConversationBranch] No choice data found for:", choiceId);
412
+ }
413
+ }, [pluginManager, selectChoice, getSelectedChoiceFullData]);
414
+ // handleChoiceSelectionRefを更新
415
+ handleChoiceSelectionRef.current = handleChoiceSelection;
416
+ // window.playerAPIとPluginManagerのコールバックを設定
417
+ useEffect(() => {
418
+ if (typeof window !== "undefined") {
419
+ window.playerAPI = {
420
+ selectChoice: (choiceId) => {
421
+ var _a;
422
+ (_a = handleChoiceSelectionRef.current) === null || _a === void 0 ? void 0 : _a.call(handleChoiceSelectionRef, choiceId);
423
+ },
424
+ };
425
+ }
426
+ pluginManager.setSelectChoiceCallback((choiceId) => {
427
+ var _a;
428
+ (_a = handleChoiceSelectionRef.current) === null || _a === void 0 ? void 0 : _a.call(handleChoiceSelectionRef, choiceId);
429
+ });
430
+ pluginManager.setGetBranchStateCallback(() => {
431
+ return branchState;
432
+ });
433
+ return () => {
434
+ if (typeof window !== "undefined") {
435
+ delete window.playerAPI;
436
+ }
437
+ };
438
+ }, [pluginManager, branchState]);
144
439
  // ダイアログ表示とアクションノード実行のuseEffectを分離
145
440
  useEffect(() => {
146
441
  if (currentBlock) {
147
- startTyping(currentBlock.content || "");
442
+ // fullscreen_text は独自のタイプライターを使うので、displayTextをクリアしてスキップ
443
+ if (currentBlock.blockType === "fullscreen_text") {
444
+ startTyping("");
445
+ return;
446
+ }
447
+ let content = currentBlock.content || "";
448
+ if (variableManagerRef.current &&
449
+ (currentBlock.blockType === "dialogue" ||
450
+ currentBlock.blockType === "narration")) {
451
+ content = variableManagerRef.current.interpolateText(content);
452
+ }
453
+ // 前のブロックのcontinueModeをチェック
454
+ const previousOptions = previousBlock === null || previousBlock === void 0 ? void 0 : previousBlock.options;
455
+ let continueMode = false;
456
+ if (previousOptions === null || previousOptions === void 0 ? void 0 : previousOptions.continueMode) {
457
+ const mode = previousOptions.continueMode;
458
+ if (mode === "continue" || mode === "continueWithNewline") {
459
+ continueMode = mode;
460
+ }
461
+ }
462
+ const isContinuableBlock = currentBlock.blockType === "dialogue" ||
463
+ currentBlock.blockType === "narration";
464
+ if (continueMode && isContinuableBlock) {
465
+ startTyping(content, continueMode);
466
+ }
467
+ else {
468
+ resetAccumulated();
469
+ startTyping(content, false);
470
+ }
471
+ }
472
+ }, [currentBlock, previousBlock, startTyping, resetAccumulated]);
473
+ // 分岐ブロック自動ロード処理
474
+ useEffect(() => {
475
+ if (currentBlock && currentBlock.blockType === "conversation_branch") {
476
+ loadBranch(currentBlock.id);
148
477
  }
149
- }, [currentBlock, startTyping]);
150
- // 画像読み込み完了後、GameScreenがマウントされてから表示する
478
+ }, [currentBlock, loadBranch]);
479
+ // プラグインフックを別のuseEffectで実行(branchStateが更新されてから)
151
480
  useEffect(() => {
152
- if (imagesLoaded && currentBlock && !isFirstRenderComplete) {
481
+ if (currentBlock &&
482
+ currentBlock.blockType === "conversation_branch" &&
483
+ branchState.isActive) {
484
+ pluginManager.callHook("onBranchStart", {
485
+ branchBlockId: currentBlock.id,
486
+ choices: branchState.currentChoices,
487
+ });
488
+ // 変数分岐の場合は自動で選択された分岐に進む
489
+ if (branchState.branchType === "variable") {
490
+ if (branchState.selectedChoiceId) {
491
+ handleChoiceSelection(branchState.selectedChoiceId);
492
+ }
493
+ else if (branchState.errorState === "NO_MATCHING_CONDITION") {
494
+ console.error("変数分岐: いずれの条件にも一致しませんでした。シナリオが停止します。");
495
+ }
496
+ }
497
+ }
498
+ }, [
499
+ pluginManager,
500
+ currentBlock,
501
+ branchState.isActive,
502
+ branchState.currentChoices,
503
+ branchState.branchType,
504
+ branchState.selectedChoiceId,
505
+ branchState.errorState,
506
+ handleChoiceSelection,
507
+ ]);
508
+ // 画像とフォント読み込み完了後、GameScreenがマウントされてから表示する
509
+ useEffect(() => {
510
+ if (imagesLoaded && fontsLoaded && currentBlock && !isFirstRenderComplete) {
153
511
  // requestAnimationFrameを使用してレンダリング完了を待つ
154
512
  requestAnimationFrame(() => {
155
513
  setIsFirstRenderComplete(true);
156
514
  });
157
515
  }
158
- }, [imagesLoaded, currentBlock, isFirstRenderComplete]);
516
+ }, [imagesLoaded, fontsLoaded, currentBlock, isFirstRenderComplete]);
159
517
  // シナリオ開始コールバック(currentBlock定義後)
160
518
  useEffect(() => {
161
519
  if (isFirstRenderComplete && !hasStarted && currentBlock) {
@@ -163,8 +521,30 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
163
521
  onScenarioStart === null || onScenarioStart === void 0 ? void 0 : onScenarioStart();
164
522
  }
165
523
  }, [isFirstRenderComplete, hasStarted, currentBlock, onScenarioStart]);
166
- const { handleNext: handleNextInternal, handlePrevious: handlePreviousInternal, togglePlay, // eslint-disable-line @typescript-eslint/no-unused-vars
167
- restart: restartInternal, displayedCharacters, } = usePlayerLogic({
524
+ // restartをusePlayerLogicの前に定義(分岐状態もリセット + キャンセルコールバック)
525
+ const restart = useCallback(() => {
526
+ // リスタート時はシナリオがキャンセルされたとみなす
527
+ if (hasStarted && !state.isEnded) {
528
+ onScenarioCancelled === null || onScenarioCancelled === void 0 ? void 0 : onScenarioCancelled();
529
+ }
530
+ setIsFirstRenderComplete(false);
531
+ setHasStarted(false);
532
+ setCurrentBranchBlock(null);
533
+ branchNavigatorRef.current.reset();
534
+ resetAccumulated(); // 蓄積テキストをクリア
535
+ setState({
536
+ currentBlockIndex: 0,
537
+ isPlaying: autoplay,
538
+ isEnded: false,
539
+ });
540
+ }, [
541
+ autoplay,
542
+ hasStarted,
543
+ state.isEnded,
544
+ onScenarioCancelled,
545
+ resetAccumulated,
546
+ ]);
547
+ const { handleNext: handleNextInternal, handlePrevious: handlePreviousInternal, togglePlay: _togglePlay, restart: _restartInternal, displayedCharacters, } = usePlayerLogic({
168
548
  state: Object.assign(Object.assign({}, state), { currentBlockIndex: actualBlockIndex }),
169
549
  setState: (newState) => {
170
550
  if (typeof newState === "function") {
@@ -190,13 +570,18 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
190
570
  onEnd,
191
571
  onScenarioEnd,
192
572
  autoplay,
573
+ branchState: branchState,
574
+ branchNavigator: branchNavigatorRef.current,
575
+ customRestart: restart,
576
+ variableManager: variableManagerRef.current,
577
+ disableKeyboardNavigation,
193
578
  });
194
579
  // プラグインイベント処理
195
580
  const realBlockIndex = currentBlock
196
581
  ? scenario.blocks.indexOf(currentBlock)
197
582
  : 0;
198
583
  usePluginEvents({
199
- pluginManager: pluginManagerRef.current,
584
+ pluginManager: pluginManager,
200
585
  currentBlock,
201
586
  displayedCharacters,
202
587
  blockIndex: state.currentBlockIndex,
@@ -204,6 +589,7 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
204
589
  allBlocks: scenario.blocks,
205
590
  realBlockIndex,
206
591
  pluginsLoaded,
592
+ transitionSource,
207
593
  });
208
594
  // 初期履歴構築(シナリオ開始時)
209
595
  useEffect(() => {
@@ -212,8 +598,31 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
212
598
  }
213
599
  }, [isFirstRenderComplete, actualBlockIndex, backlog]);
214
600
  // ハンドラーをラップして表示可能インデックスで動作するように
215
- const handleNext = useCallback(() => {
216
- if (state.currentBlockIndex < displayableBlockIndices.length - 1) {
601
+ const handleNext = useCallback((source = "click") => {
602
+ // time_waitブロックはクリックで遷移できないようにする
603
+ if (source === "click" && (currentBlock === null || currentBlock === void 0 ? void 0 : currentBlock.blockType) === "time_wait") {
604
+ return;
605
+ }
606
+ // 遷移元を設定
607
+ setTransitionSource(source);
608
+ if (currentBranchBlock &&
609
+ branchNavigatorRef.current.hasMoreBranchBlocks()) {
610
+ const nextBranchBlock = branchNavigatorRef.current.nextBranchBlock();
611
+ if (nextBranchBlock) {
612
+ setCurrentBranchBlock(convertBranchBlockToScenarioBlock(nextBranchBlock));
613
+ }
614
+ else {
615
+ setCurrentBranchBlock(null);
616
+ branchNavigatorRef.current.reset();
617
+ handleNextInternal();
618
+ }
619
+ }
620
+ else if (currentBranchBlock) {
621
+ setCurrentBranchBlock(null);
622
+ branchNavigatorRef.current.reset();
623
+ handleNextInternal();
624
+ }
625
+ else if (state.currentBlockIndex < displayableBlockIndices.length - 1) {
217
626
  handleNextInternal();
218
627
  }
219
628
  else {
@@ -222,6 +631,8 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
222
631
  onScenarioEnd === null || onScenarioEnd === void 0 ? void 0 : onScenarioEnd();
223
632
  }
224
633
  }, [
634
+ currentBlock === null || currentBlock === void 0 ? void 0 : currentBlock.blockType,
635
+ currentBranchBlock,
225
636
  state.currentBlockIndex,
226
637
  displayableBlockIndices.length,
227
638
  handleNextInternal,
@@ -229,24 +640,16 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
229
640
  onScenarioEnd,
230
641
  ]);
231
642
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
232
- const handlePrevious = useCallback(() => {
643
+ const _handlePrevious = useCallback(() => {
233
644
  if (state.currentBlockIndex > 0) {
234
645
  handlePreviousInternal();
235
646
  }
236
647
  }, [state.currentBlockIndex, handlePreviousInternal]);
237
- // restartをラップして、firstRenderCompleteもリセット
238
- const restart = useCallback(() => {
239
- // リスタート時はシナリオがキャンセルされたとみなす
240
- if (hasStarted && !state.isEnded) {
241
- onScenarioCancelled === null || onScenarioCancelled === void 0 ? void 0 : onScenarioCancelled();
242
- }
243
- setIsFirstRenderComplete(false);
244
- setHasStarted(false);
245
- restartInternal();
246
- }, [hasStarted, state.isEnded, onScenarioCancelled, restartInternal]);
648
+ // 現在の背景を計算
649
+ const currentBackground = useMemo(() => calculateCurrentBackground(actualBlockIndex), [calculateCurrentBackground, actualBlockIndex]);
247
650
  // DataContext の構築 - displayedCharactersが必要なため usePlayerLogic の後に配置
248
651
  const dataContext = useMemo(() => {
249
- var _a, _b;
652
+ var _a, _b, _c;
250
653
  return ({
251
654
  playback: {
252
655
  currentBlockIndex: actualBlockIndex,
@@ -254,7 +657,9 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
254
657
  scenarioId: scenario.id,
255
658
  scenarioName: scenario.name,
256
659
  currentBlock: currentBlock || null,
257
- displayText,
660
+ displayText: displayText.includes("\n")
661
+ ? displayText.split("\n").map((line, index, array) => (_jsxs(React.Fragment, { children: [line, index < array.length - 1 && _jsx("br", {})] }, index)))
662
+ : displayText,
258
663
  isTyping,
259
664
  displayedCharacters,
260
665
  },
@@ -265,22 +670,32 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
265
670
  clearLogs: backlog.clearLogs,
266
671
  },
267
672
  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,
673
+ aspectRatio: (_a = mergedSettings.aspectRatio) !== null && _a !== void 0 ? _a : "16:9",
674
+ bgObjectFit: (_b = mergedSettings.bgObjectFit) !== null && _b !== void 0 ? _b : "contain",
675
+ textSpeed: mergedSettings.textSpeed,
676
+ autoPlaySpeed: mergedSettings.autoPlaySpeed,
677
+ bgmVolume: mergedSettings.bgmVolume,
678
+ seVolume: mergedSettings.seVolume,
679
+ voiceVolume: mergedSettings.voiceVolume,
275
680
  skipMode: "unread",
276
681
  },
277
682
  pluginAssets: {
278
683
  getAssetUrl: (pluginName, filename) => {
279
- return pluginManagerRef.current.getPluginAssetUrl(pluginName, filename);
684
+ return pluginManager.getPluginAssetUrl(pluginName, filename);
280
685
  },
281
686
  },
687
+ branchData: conversationBranch.branchState,
688
+ branchHistory: [],
689
+ background: currentBackground.length > 0 ? currentBackground[0] : null,
690
+ backgrounds: currentBackground,
691
+ fonts: {
692
+ fonts: (_c = scenario.fonts) !== null && _c !== void 0 ? _c : [],
693
+ selectedFontFamily: mergedSettings.selectedFontFamily,
694
+ isLoaded: fontsLoaded,
695
+ },
282
696
  });
283
697
  }, [
698
+ pluginManager,
284
699
  actualBlockIndex,
285
700
  scenario,
286
701
  currentBlock,
@@ -288,10 +703,22 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
288
703
  isTyping,
289
704
  displayedCharacters,
290
705
  backlog,
291
- settings,
706
+ conversationBranch.branchState,
707
+ mergedSettings.aspectRatio,
708
+ mergedSettings.autoPlaySpeed,
709
+ mergedSettings.bgObjectFit,
710
+ mergedSettings.bgmVolume,
711
+ mergedSettings.seVolume,
712
+ mergedSettings.textSpeed,
713
+ mergedSettings.voiceVolume,
714
+ mergedSettings.selectedFontFamily,
715
+ currentBackground,
716
+ fontsLoaded,
292
717
  ]);
293
- // マウスホイールとタッチジェスチャーを無効化
718
+ // マウスホイールとタッチジェスチャーを無効化(preventDefaultScrollがtrueの場合のみ)
294
719
  useEffect(() => {
720
+ if (!preventDefaultScroll)
721
+ return;
295
722
  const handleWheel = (e) => {
296
723
  e.preventDefault();
297
724
  };
@@ -312,7 +739,7 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
312
739
  document.removeEventListener("touchmove", handleTouchMove);
313
740
  document.removeEventListener("gesturestart", handleGestureStart);
314
741
  };
315
- }, []);
742
+ }, [preventDefaultScroll]);
316
743
  // アスペクト比を計算
317
744
  const getAspectRatio = () => {
318
745
  if (!(settings === null || settings === void 0 ? void 0 : settings.aspectRatio))
@@ -321,16 +748,31 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
321
748
  return `${width}/${height}`;
322
749
  };
323
750
  // 条件付きレンダリングを JSX で処理(フックの後、early return なし)
324
- return (_jsxs(_Fragment, { children: [!currentBlock && !state.isEnded && (_jsx("div", { className: clsx("flex items-center justify-center p-8", className), children: _jsx("p", { className: "text-gray-500", children: "\u30B7\u30CA\u30EA\u30AA\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093" }) })), state.isEnded && (_jsx(EndScreen, { scenarioName: scenario.name, onRestart: restart, className: className })), currentBlock && !state.isEnded && (_jsxs(_Fragment, { children: [(!imagesLoaded || !isFirstRenderComplete) && (_jsx("div", { className: clsx("luna-player fixed inset-0 bg-black overflow-hidden flex items-center justify-center z-50", className), style: {
751
+ return (_jsxs(_Fragment, { children: [!currentBlock && !state.isEnded && (_jsx("div", { className: clsx("flex items-center justify-center p-8", className), children: _jsx("p", { className: "text-gray-500", children: "\u30B7\u30CA\u30EA\u30AA\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093" }) })), state.isEnded && (_jsx(EndScreen, { scenarioName: scenario.name, onRestart: restart, className: className })), currentBlock && !state.isEnded && (_jsxs(_Fragment, { children: [(!imagesLoaded ||
752
+ !fontsLoaded ||
753
+ !isFirstRenderComplete ||
754
+ !pluginsLoaded) && (_jsx("div", { className: clsx("luna-player fixed inset-0 bg-black overflow-hidden flex items-center justify-center z-50", className), style: {
325
755
  touchAction: "none",
326
756
  userSelect: "none",
327
757
  WebkitUserSelect: "none",
328
- } })), _jsx("div", { className: clsx("luna-player fixed inset-0 bg-black overflow-hidden flex items-center justify-center", className, (!imagesLoaded || !isFirstRenderComplete) && "opacity-0"), onClick: handleNext, style: {
758
+ } })), _jsx("div", { className: clsx("luna-player fixed inset-0 bg-black overflow-hidden flex items-center justify-center", className, (!imagesLoaded ||
759
+ !fontsLoaded ||
760
+ !isFirstRenderComplete ||
761
+ !pluginsLoaded) &&
762
+ "opacity-0"), onClick: () => handleNext("click"), style: {
329
763
  touchAction: "none",
330
764
  userSelect: "none",
331
765
  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)))] }) })] }) }) })] }))] }));
766
+ }, 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: {
767
+ bgmVolume: mergedSettings.bgmVolume,
768
+ seVolume: mergedSettings.seVolume,
769
+ voiceVolume: mergedSettings.voiceVolume,
770
+ effectVolume: mergedSettings.effectVolume,
771
+ textSoundVolume: mergedSettings.textSoundVolume,
772
+ muteAudio: (_c = mergedSettings.muteAudio) !== null && _c !== void 0 ? _c : false,
773
+ }, children: [_jsxs("div", { className: "h-full relative", children: [_jsx(PluginComponentProvider, { type: ComponentType.Background, pluginManager: pluginManager, fallback: BackgroundLayer }, "background"), _jsx(GameScreen, { scenario: scenario, currentBlock: currentBlock, previousBlock: previousBlock, displayedCharacters: displayedCharacters }, "game-screen")] }), _jsx(OverlayUI, { children: _jsxs("div", { className: "h-full w-full relative", children: [_jsx(PluginComponentProvider, { type: ComponentType.DialogueBox, pluginManager: pluginManager, fallback: DialogueBox }, "dialogue-box"), (currentBlock === null || currentBlock === void 0 ? void 0 : currentBlock.blockType) === "conversation_branch" && (_jsx(PluginComponentProvider, { type: ComponentType.ConversationBranch, pluginManager: pluginManager, fallback: ConversationBranchBox }, "conversation-branch")), (currentBlock === null || currentBlock === void 0 ? void 0 : currentBlock.blockType) === "fullscreen_text" && (_jsx(FullscreenTextBox, { onComplete: () => handleNext("click") }, "fullscreen-text")), (currentBlock === null || currentBlock === void 0 ? void 0 : currentBlock.blockType) === "click_wait" && (_jsx(ClickWaitIndicator, {}, "click-wait")), (currentBlock === null || currentBlock === void 0 ? void 0 : currentBlock.blockType) === "time_wait" && (_jsx(TimeWaitIndicator, { duration: (_e = (_d = currentBlock.options) === null || _d === void 0 ? void 0 : _d.duration) !== null && _e !== void 0 ? _e : 1, onComplete: () => handleNext("time_wait") }, "time-wait")), pluginManager
774
+ .getRegisteredComponents()
775
+ .filter((type) => type !== ComponentType.DialogueBox &&
776
+ type !== ComponentType.ConversationBranch)
777
+ .map((componentType) => (_jsx(PluginComponentProvider, { type: componentType, pluginManager: pluginManager }, componentType)))] }) })] }) }) }) })] }))] }));
336
778
  };