@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.
- package/dist/Player.d.ts +1 -1
- package/dist/Player.js +527 -85
- 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 +14 -0
- package/dist/contexts/AudioContext.js +14 -0
- package/dist/contexts/DataContext.d.ts +4 -1
- package/dist/contexts/DataContext.js +82 -13
- 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 +7 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.js +3 -1
- package/dist/plugin/PluginManager.d.ts +66 -2
- package/dist/plugin/PluginManager.js +352 -79
- package/dist/sdk.d.ts +184 -22
- package/dist/sdk.js +27 -2
- package/dist/types.d.ts +303 -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,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
|
-
|
|
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]);
|
|
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 =
|
|
35
|
-
setGlobalUIAPI(uiAPI,
|
|
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 = [
|
|
57
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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
|
-
|
|
302
|
+
pluginManager.registerStyleElement("speakerName", speakerNameElement);
|
|
115
303
|
}
|
|
116
304
|
if (dialogueElement) {
|
|
117
|
-
|
|
118
|
-
|
|
305
|
+
pluginManager.registerStyleElement("dialogueBox", dialogueElement);
|
|
306
|
+
pluginManager.registerStyleElement("scenarioBlockContent", dialogueElement);
|
|
119
307
|
}
|
|
120
308
|
if (characterSpriteElement) {
|
|
121
|
-
|
|
309
|
+
pluginManager.registerStyleElement("characterSprite", characterSpriteElement);
|
|
122
310
|
}
|
|
123
311
|
if (gameScreenElement) {
|
|
124
|
-
|
|
125
|
-
|
|
312
|
+
pluginManager.registerStyleElement("gameScreen", gameScreenElement);
|
|
313
|
+
pluginManager.registerStyleElement("background", gameScreenElement);
|
|
126
314
|
}
|
|
127
315
|
}
|
|
128
|
-
}, [isFirstRenderComplete]);
|
|
129
|
-
|
|
130
|
-
|
|
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 =
|
|
134
|
-
|
|
135
|
-
|
|
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 = (
|
|
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
|
-
|
|
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,
|
|
150
|
-
//
|
|
478
|
+
}, [currentBlock, loadBranch]);
|
|
479
|
+
// プラグインフックを別のuseEffectで実行(branchStateが更新されてから)
|
|
151
480
|
useEffect(() => {
|
|
152
|
-
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) {
|
|
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
|
-
|
|
167
|
-
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({
|
|
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:
|
|
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
|
-
|
|
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
|
|
643
|
+
const _handlePrevious = useCallback(() => {
|
|
233
644
|
if (state.currentBlockIndex > 0) {
|
|
234
645
|
handlePreviousInternal();
|
|
235
646
|
}
|
|
236
647
|
}, [state.currentBlockIndex, handlePreviousInternal]);
|
|
237
|
-
//
|
|
238
|
-
const
|
|
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 =
|
|
269
|
-
bgObjectFit: (_b =
|
|
270
|
-
textSpeed:
|
|
271
|
-
autoPlaySpeed:
|
|
272
|
-
bgmVolume:
|
|
273
|
-
seVolume:
|
|
274
|
-
voiceVolume:
|
|
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
|
|
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
|
-
|
|
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 ||
|
|
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 ||
|
|
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:
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
};
|