@luna-editor/engine 0.2.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.
- package/dist/Player.d.ts +1 -1
- package/dist/Player.js +504 -77
- package/dist/api/conversationBranch.d.ts +4 -0
- package/dist/api/conversationBranch.js +83 -0
- package/dist/components/BackgroundLayer.d.ts +19 -0
- package/dist/components/BackgroundLayer.js +218 -0
- package/dist/components/ClickWaitIndicator.d.ts +10 -0
- package/dist/components/ClickWaitIndicator.js +31 -0
- package/dist/components/ConversationBranchBox.d.ts +2 -0
- package/dist/components/ConversationBranchBox.js +29 -0
- package/dist/components/DialogueBox.js +16 -1
- package/dist/components/FontSettingsPanel.d.ts +10 -0
- package/dist/components/FontSettingsPanel.js +30 -0
- package/dist/components/FullscreenTextBox.d.ts +6 -0
- package/dist/components/FullscreenTextBox.js +70 -0
- package/dist/components/GameScreen.d.ts +1 -0
- package/dist/components/GameScreen.js +363 -81
- package/dist/components/PluginComponentProvider.d.ts +2 -2
- package/dist/components/PluginComponentProvider.js +3 -3
- package/dist/components/TimeWaitIndicator.d.ts +15 -0
- package/dist/components/TimeWaitIndicator.js +17 -0
- package/dist/contexts/AudioContext.d.ts +1 -0
- package/dist/contexts/AudioContext.js +1 -0
- package/dist/contexts/DataContext.js +69 -11
- package/dist/hooks/useBacklog.js +3 -0
- package/dist/hooks/useConversationBranch.d.ts +16 -0
- package/dist/hooks/useConversationBranch.js +125 -0
- package/dist/hooks/useFontLoader.d.ts +23 -0
- package/dist/hooks/useFontLoader.js +153 -0
- package/dist/hooks/useFullscreenText.d.ts +17 -0
- package/dist/hooks/useFullscreenText.js +120 -0
- package/dist/hooks/usePlayerLogic.d.ts +10 -3
- package/dist/hooks/usePlayerLogic.js +115 -18
- package/dist/hooks/usePluginEvents.d.ts +4 -1
- package/dist/hooks/usePluginEvents.js +16 -11
- package/dist/hooks/usePreloadImages.js +27 -7
- package/dist/hooks/useSoundPlayer.d.ts +15 -0
- package/dist/hooks/useSoundPlayer.js +209 -0
- package/dist/hooks/useTypewriter.d.ts +6 -2
- package/dist/hooks/useTypewriter.js +42 -6
- package/dist/hooks/useVoice.js +4 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -1
- package/dist/plugin/PluginManager.d.ts +66 -2
- package/dist/plugin/PluginManager.js +349 -79
- package/dist/sdk.d.ts +178 -21
- package/dist/sdk.js +27 -2
- package/dist/types.d.ts +288 -4
- package/dist/utils/branchBlockConverter.d.ts +2 -0
- package/dist/utils/branchBlockConverter.js +21 -0
- package/dist/utils/branchNavigator.d.ts +14 -0
- package/dist/utils/branchNavigator.js +55 -0
- package/dist/utils/facePositionCalculator.js +0 -1
- package/dist/utils/variableManager.d.ts +18 -0
- package/dist/utils/variableManager.js +159 -0
- package/package.json +1 -1
- package/dist/components/ConversationLogUI.d.ts +0 -2
- package/dist/components/ConversationLogUI.js +0 -115
- package/dist/hooks/useConversationLog.d.ts +0 -14
- package/dist/hooks/useConversationLog.js +0 -82
- package/dist/hooks/useUIVisibility.d.ts +0 -9
- package/dist/hooks/useUIVisibility.js +0 -19
- package/dist/plugin/luna-react.d.ts +0 -41
- package/dist/plugin/luna-react.js +0 -99
package/dist/Player.js
CHANGED
|
@@ -10,42 +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";
|
|
20
25
|
import { AudioProvider } from "./contexts/AudioContext";
|
|
21
26
|
import { DataProvider } from "./contexts/DataContext";
|
|
22
27
|
import { useBacklog } from "./hooks/useBacklog";
|
|
28
|
+
import { useConversationBranch } from "./hooks/useConversationBranch";
|
|
29
|
+
import { useFontLoader } from "./hooks/useFontLoader";
|
|
23
30
|
import { usePlayerLogic } from "./hooks/usePlayerLogic";
|
|
24
31
|
import { setGlobalUIAPI } from "./hooks/usePluginAPI";
|
|
25
32
|
import { usePluginEvents } from "./hooks/usePluginEvents";
|
|
26
33
|
import { usePreloadImages } from "./hooks/usePreloadImages";
|
|
34
|
+
import { useSoundPlayer } from "./hooks/useSoundPlayer";
|
|
27
35
|
import { useTypewriter } from "./hooks/useTypewriter";
|
|
28
36
|
import { PluginManager } from "./plugin/PluginManager";
|
|
29
37
|
import { ComponentType } from "./sdk";
|
|
30
|
-
|
|
31
|
-
|
|
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]);
|
|
32
48
|
// デフォルト値とマージ
|
|
33
|
-
const mergedSettings = Object.assign({ textSpeed: 80, autoPlaySpeed: 3, bgmVolume:
|
|
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);
|
|
34
50
|
// プラグインからの設定更新ハンドラ
|
|
35
51
|
const handleSettingsUpdate = useCallback((updatedSettings) => {
|
|
36
52
|
var _a, _b;
|
|
37
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);
|
|
38
54
|
onSettingsChange === null || onSettingsChange === void 0 ? void 0 : onSettingsChange(newSettings);
|
|
39
55
|
}, [settings, onSettingsChange]);
|
|
40
|
-
|
|
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);
|
|
41
64
|
// グローバルUIAPIを初期化(プラグインコンポーネントから使用可能にする)
|
|
42
65
|
useEffect(() => {
|
|
43
|
-
const uiAPI =
|
|
44
|
-
setGlobalUIAPI(uiAPI,
|
|
45
|
-
}, []);
|
|
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]);
|
|
46
74
|
// 画面サイズの初期化
|
|
47
75
|
const [, setScreenSize] = useScreenSizeAtom();
|
|
48
76
|
useEffect(() => {
|
|
77
|
+
// screenSizeが明示的に指定されている場合はそれを使用(プレビュー用)
|
|
78
|
+
if (screenSizeProp) {
|
|
79
|
+
setScreenSize(screenSizeProp);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
49
82
|
// クライアントサイドでのみ実行
|
|
50
83
|
if (typeof window === "undefined")
|
|
51
84
|
return;
|
|
@@ -59,47 +92,199 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
|
|
|
59
92
|
};
|
|
60
93
|
window.addEventListener("resize", handleResize);
|
|
61
94
|
return () => window.removeEventListener("resize", handleResize);
|
|
62
|
-
}, [setScreenSize]);
|
|
95
|
+
}, [setScreenSize, screenSizeProp]);
|
|
63
96
|
// 表示可能なブロックのインデックスを事前計算
|
|
64
97
|
const displayableBlockIndices = useMemo(() => {
|
|
65
|
-
const supportedBlockTypes = [
|
|
66
|
-
|
|
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
|
|
67
107
|
.map((block, index) => ({ block, index }))
|
|
68
108
|
.filter(({ block }) => supportedBlockTypes.includes(block.blockType))
|
|
69
109
|
.map(({ index }) => index);
|
|
110
|
+
return indices;
|
|
70
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
|
+
]);
|
|
71
218
|
const [state, setState] = useState({
|
|
72
219
|
currentBlockIndex: 0,
|
|
73
220
|
isPlaying: autoplay,
|
|
74
221
|
isEnded: false,
|
|
222
|
+
variables: (_a = variableManagerRef.current) === null || _a === void 0 ? void 0 : _a.getVariablesMap(),
|
|
75
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]);
|
|
76
244
|
// 画像を事前読み込み
|
|
77
245
|
const imagesLoaded = usePreloadImages(scenario);
|
|
246
|
+
// フォントを読み込み
|
|
247
|
+
const { isLoaded: fontsLoaded } = useFontLoader(scenario.fonts);
|
|
78
248
|
// プラグインの読み込み状態
|
|
79
249
|
const [pluginsLoaded, setPluginsLoaded] = useState(false);
|
|
250
|
+
// 読み込み済みプラグインのパッケージ名を追跡
|
|
251
|
+
const loadedPluginNamesRef = useRef(new Set());
|
|
80
252
|
// プラグインの読み込み
|
|
253
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: pluginManagerは安定した参照なので依存配列から除外しても安全
|
|
81
254
|
useEffect(() => {
|
|
255
|
+
let isCancelled = false;
|
|
82
256
|
const loadPlugins = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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);
|
|
88
281
|
}
|
|
89
|
-
// プラグイン初期化フック - すべてのプラグインの読み込み完了後に実行
|
|
90
|
-
console.log("Calling onInit hook");
|
|
91
|
-
pluginManagerRef.current.callHook("onInit");
|
|
92
|
-
// DialogueBoxの初期状態を表示に設定
|
|
93
|
-
pluginManagerRef.current.showUI(ComponentType.DialogueBox);
|
|
94
|
-
setPluginsLoaded(true);
|
|
95
|
-
console.log("All plugins loaded successfully");
|
|
96
282
|
});
|
|
97
283
|
loadPlugins();
|
|
98
284
|
return () => {
|
|
99
|
-
|
|
100
|
-
manager.cleanup();
|
|
285
|
+
isCancelled = true;
|
|
101
286
|
};
|
|
102
|
-
}, [plugins]);
|
|
287
|
+
}, [plugins, sounds]);
|
|
103
288
|
// 初回レンダリング完了フラグ
|
|
104
289
|
const [isFirstRenderComplete, setIsFirstRenderComplete] = useState(false);
|
|
105
290
|
// シナリオ開始コールバック(後でcurrentBlockが定義された後に実行)
|
|
@@ -113,58 +298,222 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
|
|
|
113
298
|
// data-character-id属性を持つ最初のキャラクター要素を取得
|
|
114
299
|
const characterSpriteElement = document.querySelector("[data-character-id]");
|
|
115
300
|
const gameScreenElement = document.querySelector(".h-screen.w-full");
|
|
116
|
-
console.log("🔍 [StyleAPI] Element registration:", {
|
|
117
|
-
speakerNameElement: !!speakerNameElement,
|
|
118
|
-
dialogueElement: !!dialogueElement,
|
|
119
|
-
characterSpriteElement: !!characterSpriteElement,
|
|
120
|
-
gameScreenElement: !!gameScreenElement,
|
|
121
|
-
});
|
|
122
301
|
if (speakerNameElement) {
|
|
123
|
-
|
|
302
|
+
pluginManager.registerStyleElement("speakerName", speakerNameElement);
|
|
124
303
|
}
|
|
125
304
|
if (dialogueElement) {
|
|
126
|
-
|
|
127
|
-
|
|
305
|
+
pluginManager.registerStyleElement("dialogueBox", dialogueElement);
|
|
306
|
+
pluginManager.registerStyleElement("scenarioBlockContent", dialogueElement);
|
|
128
307
|
}
|
|
129
308
|
if (characterSpriteElement) {
|
|
130
|
-
|
|
309
|
+
pluginManager.registerStyleElement("characterSprite", characterSpriteElement);
|
|
131
310
|
}
|
|
132
311
|
if (gameScreenElement) {
|
|
133
|
-
|
|
134
|
-
|
|
312
|
+
pluginManager.registerStyleElement("gameScreen", gameScreenElement);
|
|
313
|
+
pluginManager.registerStyleElement("background", gameScreenElement);
|
|
135
314
|
}
|
|
136
315
|
}
|
|
137
|
-
}, [isFirstRenderComplete]);
|
|
138
|
-
|
|
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({
|
|
139
335
|
speed: mergedSettings.textSpeed,
|
|
140
336
|
});
|
|
141
|
-
//
|
|
142
|
-
const currentBlock =
|
|
143
|
-
|
|
144
|
-
|
|
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]);
|
|
145
349
|
// usePlayerLogicに渡す実際のブロックインデックス
|
|
146
|
-
const actualBlockIndex = (
|
|
350
|
+
const actualBlockIndex = (_b = displayableBlockIndices[state.currentBlockIndex]) !== null && _b !== void 0 ? _b : 0;
|
|
147
351
|
// バックログ機能
|
|
148
352
|
const backlog = useBacklog({
|
|
149
353
|
scenario,
|
|
150
354
|
currentBlockIndex: actualBlockIndex,
|
|
151
355
|
currentBlock: currentBlock || null,
|
|
152
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]);
|
|
153
439
|
// ダイアログ表示とアクションノード実行のuseEffectを分離
|
|
154
440
|
useEffect(() => {
|
|
155
441
|
if (currentBlock) {
|
|
156
|
-
|
|
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);
|
|
157
477
|
}
|
|
158
|
-
}, [currentBlock,
|
|
159
|
-
//
|
|
478
|
+
}, [currentBlock, loadBranch]);
|
|
479
|
+
// プラグインフックを別のuseEffectで実行(branchStateが更新されてから)
|
|
160
480
|
useEffect(() => {
|
|
161
|
-
if (
|
|
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) {
|
|
162
511
|
// requestAnimationFrameを使用してレンダリング完了を待つ
|
|
163
512
|
requestAnimationFrame(() => {
|
|
164
513
|
setIsFirstRenderComplete(true);
|
|
165
514
|
});
|
|
166
515
|
}
|
|
167
|
-
}, [imagesLoaded, currentBlock, isFirstRenderComplete]);
|
|
516
|
+
}, [imagesLoaded, fontsLoaded, currentBlock, isFirstRenderComplete]);
|
|
168
517
|
// シナリオ開始コールバック(currentBlock定義後)
|
|
169
518
|
useEffect(() => {
|
|
170
519
|
if (isFirstRenderComplete && !hasStarted && currentBlock) {
|
|
@@ -172,8 +521,30 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
|
|
|
172
521
|
onScenarioStart === null || onScenarioStart === void 0 ? void 0 : onScenarioStart();
|
|
173
522
|
}
|
|
174
523
|
}, [isFirstRenderComplete, hasStarted, currentBlock, onScenarioStart]);
|
|
175
|
-
|
|
176
|
-
restart
|
|
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({
|
|
177
548
|
state: Object.assign(Object.assign({}, state), { currentBlockIndex: actualBlockIndex }),
|
|
178
549
|
setState: (newState) => {
|
|
179
550
|
if (typeof newState === "function") {
|
|
@@ -199,13 +570,18 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
|
|
|
199
570
|
onEnd,
|
|
200
571
|
onScenarioEnd,
|
|
201
572
|
autoplay,
|
|
573
|
+
branchState: branchState,
|
|
574
|
+
branchNavigator: branchNavigatorRef.current,
|
|
575
|
+
customRestart: restart,
|
|
576
|
+
variableManager: variableManagerRef.current,
|
|
577
|
+
disableKeyboardNavigation,
|
|
202
578
|
});
|
|
203
579
|
// プラグインイベント処理
|
|
204
580
|
const realBlockIndex = currentBlock
|
|
205
581
|
? scenario.blocks.indexOf(currentBlock)
|
|
206
582
|
: 0;
|
|
207
583
|
usePluginEvents({
|
|
208
|
-
pluginManager:
|
|
584
|
+
pluginManager: pluginManager,
|
|
209
585
|
currentBlock,
|
|
210
586
|
displayedCharacters,
|
|
211
587
|
blockIndex: state.currentBlockIndex,
|
|
@@ -213,6 +589,7 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
|
|
|
213
589
|
allBlocks: scenario.blocks,
|
|
214
590
|
realBlockIndex,
|
|
215
591
|
pluginsLoaded,
|
|
592
|
+
transitionSource,
|
|
216
593
|
});
|
|
217
594
|
// 初期履歴構築(シナリオ開始時)
|
|
218
595
|
useEffect(() => {
|
|
@@ -221,8 +598,31 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
|
|
|
221
598
|
}
|
|
222
599
|
}, [isFirstRenderComplete, actualBlockIndex, backlog]);
|
|
223
600
|
// ハンドラーをラップして表示可能インデックスで動作するように
|
|
224
|
-
const handleNext = useCallback(() => {
|
|
225
|
-
|
|
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) {
|
|
226
626
|
handleNextInternal();
|
|
227
627
|
}
|
|
228
628
|
else {
|
|
@@ -231,6 +631,8 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
|
|
|
231
631
|
onScenarioEnd === null || onScenarioEnd === void 0 ? void 0 : onScenarioEnd();
|
|
232
632
|
}
|
|
233
633
|
}, [
|
|
634
|
+
currentBlock === null || currentBlock === void 0 ? void 0 : currentBlock.blockType,
|
|
635
|
+
currentBranchBlock,
|
|
234
636
|
state.currentBlockIndex,
|
|
235
637
|
displayableBlockIndices.length,
|
|
236
638
|
handleNextInternal,
|
|
@@ -238,24 +640,16 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
|
|
|
238
640
|
onScenarioEnd,
|
|
239
641
|
]);
|
|
240
642
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
241
|
-
const
|
|
643
|
+
const _handlePrevious = useCallback(() => {
|
|
242
644
|
if (state.currentBlockIndex > 0) {
|
|
243
645
|
handlePreviousInternal();
|
|
244
646
|
}
|
|
245
647
|
}, [state.currentBlockIndex, handlePreviousInternal]);
|
|
246
|
-
//
|
|
247
|
-
const
|
|
248
|
-
// リスタート時はシナリオがキャンセルされたとみなす
|
|
249
|
-
if (hasStarted && !state.isEnded) {
|
|
250
|
-
onScenarioCancelled === null || onScenarioCancelled === void 0 ? void 0 : onScenarioCancelled();
|
|
251
|
-
}
|
|
252
|
-
setIsFirstRenderComplete(false);
|
|
253
|
-
setHasStarted(false);
|
|
254
|
-
restartInternal();
|
|
255
|
-
}, [hasStarted, state.isEnded, onScenarioCancelled, restartInternal]);
|
|
648
|
+
// 現在の背景を計算
|
|
649
|
+
const currentBackground = useMemo(() => calculateCurrentBackground(actualBlockIndex), [calculateCurrentBackground, actualBlockIndex]);
|
|
256
650
|
// DataContext の構築 - displayedCharactersが必要なため usePlayerLogic の後に配置
|
|
257
651
|
const dataContext = useMemo(() => {
|
|
258
|
-
var _a, _b;
|
|
652
|
+
var _a, _b, _c;
|
|
259
653
|
return ({
|
|
260
654
|
playback: {
|
|
261
655
|
currentBlockIndex: actualBlockIndex,
|
|
@@ -263,7 +657,9 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
|
|
|
263
657
|
scenarioId: scenario.id,
|
|
264
658
|
scenarioName: scenario.name,
|
|
265
659
|
currentBlock: currentBlock || null,
|
|
266
|
-
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,
|
|
267
663
|
isTyping,
|
|
268
664
|
displayedCharacters,
|
|
269
665
|
},
|
|
@@ -285,11 +681,21 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
|
|
|
285
681
|
},
|
|
286
682
|
pluginAssets: {
|
|
287
683
|
getAssetUrl: (pluginName, filename) => {
|
|
288
|
-
return
|
|
684
|
+
return pluginManager.getPluginAssetUrl(pluginName, filename);
|
|
289
685
|
},
|
|
290
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
|
+
},
|
|
291
696
|
});
|
|
292
697
|
}, [
|
|
698
|
+
pluginManager,
|
|
293
699
|
actualBlockIndex,
|
|
294
700
|
scenario,
|
|
295
701
|
currentBlock,
|
|
@@ -297,10 +703,22 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
|
|
|
297
703
|
isTyping,
|
|
298
704
|
displayedCharacters,
|
|
299
705
|
backlog,
|
|
300
|
-
|
|
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,
|
|
301
717
|
]);
|
|
302
|
-
//
|
|
718
|
+
// マウスホイールとタッチジェスチャーを無効化(preventDefaultScrollがtrueの場合のみ)
|
|
303
719
|
useEffect(() => {
|
|
720
|
+
if (!preventDefaultScroll)
|
|
721
|
+
return;
|
|
304
722
|
const handleWheel = (e) => {
|
|
305
723
|
e.preventDefault();
|
|
306
724
|
};
|
|
@@ -321,7 +739,7 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
|
|
|
321
739
|
document.removeEventListener("touchmove", handleTouchMove);
|
|
322
740
|
document.removeEventListener("gesturestart", handleGestureStart);
|
|
323
741
|
};
|
|
324
|
-
}, []);
|
|
742
|
+
}, [preventDefaultScroll]);
|
|
325
743
|
// アスペクト比を計算
|
|
326
744
|
const getAspectRatio = () => {
|
|
327
745
|
if (!(settings === null || settings === void 0 ? void 0 : settings.aspectRatio))
|
|
@@ -330,11 +748,18 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
|
|
|
330
748
|
return `${width}/${height}`;
|
|
331
749
|
};
|
|
332
750
|
// 条件付きレンダリングを JSX で処理(フックの後、early return なし)
|
|
333
|
-
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 ||
|
|
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: {
|
|
334
755
|
touchAction: "none",
|
|
335
756
|
userSelect: "none",
|
|
336
757
|
WebkitUserSelect: "none",
|
|
337
|
-
} })), _jsx("div", { className: clsx("luna-player fixed inset-0 bg-black overflow-hidden flex items-center justify-center", className, (!imagesLoaded ||
|
|
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: {
|
|
338
763
|
touchAction: "none",
|
|
339
764
|
userSelect: "none",
|
|
340
765
|
WebkitUserSelect: "none",
|
|
@@ -344,8 +769,10 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
|
|
|
344
769
|
voiceVolume: mergedSettings.voiceVolume,
|
|
345
770
|
effectVolume: mergedSettings.effectVolume,
|
|
346
771
|
textSoundVolume: mergedSettings.textSoundVolume,
|
|
347
|
-
|
|
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
|
|
348
774
|
.getRegisteredComponents()
|
|
349
|
-
.filter((type) => type !== ComponentType.DialogueBox
|
|
350
|
-
|
|
775
|
+
.filter((type) => type !== ComponentType.DialogueBox &&
|
|
776
|
+
type !== ComponentType.ConversationBranch)
|
|
777
|
+
.map((componentType) => (_jsx(PluginComponentProvider, { type: componentType, pluginManager: pluginManager }, componentType)))] }) })] }) }) }) })] }))] }));
|
|
351
778
|
};
|