@luna-editor/engine 0.5.12 → 0.5.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Player.js CHANGED
@@ -16,6 +16,7 @@ import { BackgroundLayer } from "./components/BackgroundLayer";
16
16
  import { ClickWaitIndicator } from "./components/ClickWaitIndicator";
17
17
  import { ConversationBranchBox } from "./components/ConversationBranchBox";
18
18
  import { DialogueBox } from "./components/DialogueBox";
19
+ import { EndScreen } from "./components/EndScreen";
19
20
  import { FullscreenTextBox } from "./components/FullscreenTextBox";
20
21
  import { GameScreen } from "./components/GameScreen";
21
22
  import { OverlayUI } from "./components/OverlayUI";
@@ -40,7 +41,7 @@ import { BranchNavigator } from "./utils/branchNavigator";
40
41
  import { VariableManager } from "./utils/variableManager";
41
42
  const EMPTY_PLUGINS = [];
42
43
  const EMPTY_SOUNDS = [];
43
- export const Player = ({ scenario: scenarioProp, settings, plugins = EMPTY_PLUGINS, sounds = EMPTY_SOUNDS, onEnd, onScenarioEnd, onScenarioStart, onScenarioCancelled, onSettingsChange, className, autoplay = false, preventDefaultScroll = true, screenSize: screenSizeProp, disableKeyboardNavigation = false, }) => {
44
+ export const Player = ({ scenario: scenarioProp, settings, plugins = EMPTY_PLUGINS, sounds = EMPTY_SOUNDS, onEnd, onScenarioEnd, onScenarioStart, onScenarioCancelled, onSettingsChange, className, autoplay = false, preventDefaultScroll = true, screenSize: screenSizeProp, disableKeyboardNavigation = false, hideEndScreen = false, }) => {
44
45
  var _a, _b, _c, _d;
45
46
  // scenario.blocks が存在しない場合は空の配列を使用
46
47
  const scenario = useMemo(() => {
@@ -456,7 +457,7 @@ export const Player = ({ scenario: scenarioProp, settings, plugins = EMPTY_PLUGI
456
457
  return blocks;
457
458
  }, [scenario.blocks, displayableBlockIndices, state.currentBlockIndex]);
458
459
  // サウンド再生フック
459
- useSoundPlayer({
460
+ const { stopAll: stopAllSounds } = useSoundPlayer({
460
461
  soundBlocks: soundBlocksToProcess,
461
462
  isFirstRenderComplete,
462
463
  muteAudio: mergedSettings.muteAudio,
@@ -624,6 +625,8 @@ export const Player = ({ scenario: scenarioProp, settings, plugins = EMPTY_PLUGI
624
625
  scenarioName: scenario.name || "scenario",
625
626
  });
626
627
  }
628
+ // リスタート時にすべての音声を停止
629
+ stopAllSounds();
627
630
  setIsFirstRenderComplete(false);
628
631
  setHasStarted(false); // これにより次の useEffect で onScenarioStart が呼ばれる
629
632
  setCurrentBranchBlock(null);
@@ -643,6 +646,7 @@ export const Player = ({ scenario: scenarioProp, settings, plugins = EMPTY_PLUGI
643
646
  pluginManager,
644
647
  scenario.id,
645
648
  scenario.name,
649
+ stopAllSounds,
646
650
  ]);
647
651
  // プラグインからシナリオを中断するためのコールバック
648
652
  const cancelScenario = useCallback(() => {
@@ -760,6 +764,8 @@ export const Player = ({ scenario: scenarioProp, settings, plugins = EMPTY_PLUGI
760
764
  }
761
765
  else {
762
766
  setState((prev) => (Object.assign(Object.assign({}, prev), { isEnded: true, isPlaying: false })));
767
+ // シナリオ終了時にすべての音声を停止
768
+ stopAllSounds();
763
769
  // プラグインのonScenarioEndフックを呼び出し
764
770
  pluginManager.callHook("onScenarioEnd", {
765
771
  scenarioId: scenario.id,
@@ -779,6 +785,7 @@ export const Player = ({ scenario: scenarioProp, settings, plugins = EMPTY_PLUGI
779
785
  pluginManager,
780
786
  scenario.id,
781
787
  scenario.name,
788
+ stopAllSounds,
782
789
  ]);
783
790
  // プラグインからAUTO再生を制御するためのコールバック
784
791
  const toggleAutoPlay = useCallback(() => {
@@ -967,7 +974,7 @@ export const Player = ({ scenario: scenarioProp, settings, plugins = EMPTY_PLUGI
967
974
  return width / height;
968
975
  };
969
976
  // 条件付きレンダリングを JSX で処理(フックの後、early return なし)
970
- 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" }) })), currentBlock && !state.isEnded && (_jsxs(_Fragment, { children: [(!imagesLoaded ||
977
+ 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 && !hideEndScreen && (_jsx(EndScreen, { scenarioName: scenario.name, onRestart: restart, className: className })), currentBlock && !state.isEnded && (_jsxs(_Fragment, { children: [(!imagesLoaded ||
971
978
  !fontsLoaded ||
972
979
  !isFirstRenderComplete ||
973
980
  !pluginsLoaded) && (_jsx("div", { className: clsx("luna-player fixed inset-0 bg-black overflow-hidden flex items-center justify-center z-50", className), style: {
@@ -0,0 +1,5 @@
1
+ import type { DisplayedCharacter } from "../types";
2
+ export declare const useImagePreloader: (
3
+ displayedCharacters: DisplayedCharacter[],
4
+ singleImageUrl?: string
5
+ ) => boolean;
@@ -0,0 +1,53 @@
1
+ import { useEffect, useState } from "react";
2
+ export var useImagePreloader = function (displayedCharacters, singleImageUrl) {
3
+ var _a = useState(true),
4
+ isLoading = _a[0],
5
+ setIsLoading = _a[1];
6
+ useEffect(
7
+ function () {
8
+ var imageUrls = [];
9
+ // 複数キャラクター表示の場合
10
+ if (displayedCharacters.length > 0) {
11
+ displayedCharacters.forEach(function (char) {
12
+ if (char.entityState.imageUrl) {
13
+ imageUrls.push(char.entityState.imageUrl);
14
+ }
15
+ });
16
+ } else if (singleImageUrl) {
17
+ // 単一キャラクター表示の場合
18
+ imageUrls.push(singleImageUrl);
19
+ }
20
+ // 画像がない場合は即座に完了
21
+ if (imageUrls.length === 0) {
22
+ setIsLoading(false);
23
+ return;
24
+ }
25
+ setIsLoading(true);
26
+ // すべての画像を並列でプリロード
27
+ var loadPromises = imageUrls.map(function (url) {
28
+ return new Promise(function (resolve, reject) {
29
+ var img = new Image();
30
+ img.onload = function () {
31
+ return resolve();
32
+ };
33
+ img.onerror = function () {
34
+ return reject(new Error("Failed to load image: ".concat(url)));
35
+ };
36
+ img.src = url;
37
+ });
38
+ });
39
+ // すべての画像の読み込みを待つ
40
+ Promise.all(loadPromises)
41
+ .then(function () {
42
+ setIsLoading(false);
43
+ })
44
+ .catch(function (error) {
45
+ console.error("Image preload error:", error);
46
+ // エラーが発生しても続行
47
+ setIsLoading(false);
48
+ });
49
+ },
50
+ [displayedCharacters, singleImageUrl]
51
+ );
52
+ return isLoading;
53
+ };
@@ -0,0 +1,41 @@
1
+ /**
2
+ * プラグインAPIからReactインスタンスを設定
3
+ */
4
+ export declare function setReactRuntime(react: any): void;
5
+ /**
6
+ * JSX Transform用のjsx関数
7
+ */
8
+ export declare function jsx(type: any, props: any, key?: any): any;
9
+ /**
10
+ * JSX Transform用のjsxs関数(複数子要素用)
11
+ */
12
+ export declare function jsxs(type: any, props: any, key?: any): any;
13
+ /**
14
+ * Fragment用
15
+ */
16
+ export declare function Fragment(props: {
17
+ children?: any;
18
+ }): any;
19
+ /**
20
+ * Reactフックと関数のプロキシ
21
+ */
22
+ export declare const useState: (...args: any[]) => any;
23
+ export declare const useEffect: (...args: any[]) => any;
24
+ export declare const useCallback: (...args: any[]) => any;
25
+ export declare const useMemo: (...args: any[]) => any;
26
+ export declare const useRef: (...args: any[]) => any;
27
+ export declare const useContext: (...args: any[]) => any;
28
+ export declare const useReducer: (...args: any[]) => any;
29
+ export declare const createElement: (...args: any[]) => any;
30
+ declare const _default: {
31
+ createElement: (...args: any[]) => any;
32
+ Fragment: typeof Fragment;
33
+ useState: (...args: any[]) => any;
34
+ useEffect: (...args: any[]) => any;
35
+ useCallback: (...args: any[]) => any;
36
+ useMemo: (...args: any[]) => any;
37
+ useRef: (...args: any[]) => any;
38
+ useContext: (...args: any[]) => any;
39
+ useReducer: (...args: any[]) => any;
40
+ };
41
+ export default _default;
@@ -0,0 +1,99 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* eslint-disable import/no-anonymous-default-export */
3
+ let runtimeReact = null;
4
+ /**
5
+ * プラグインAPIからReactインスタンスを設定
6
+ */
7
+ export function setReactRuntime(react) {
8
+ runtimeReact = react;
9
+ }
10
+ /**
11
+ * JSX Transform用のjsx関数
12
+ */
13
+ export function jsx(type, props, key) {
14
+ if (!runtimeReact) {
15
+ throw new Error("React runtime not initialized. Make sure plugin is loaded properly.");
16
+ }
17
+ return runtimeReact.createElement(type, key ? Object.assign(Object.assign({}, props), { key }) : props);
18
+ }
19
+ /**
20
+ * JSX Transform用のjsxs関数(複数子要素用)
21
+ */
22
+ export function jsxs(type, props, key) {
23
+ if (!runtimeReact) {
24
+ throw new Error("React runtime not initialized. Make sure plugin is loaded properly.");
25
+ }
26
+ return runtimeReact.createElement(type, key ? Object.assign(Object.assign({}, props), { key }) : props);
27
+ }
28
+ /**
29
+ * Fragment用
30
+ */
31
+ export function Fragment(props) {
32
+ if (!runtimeReact) {
33
+ throw new Error("React runtime not initialized. Make sure plugin is loaded properly.");
34
+ }
35
+ return runtimeReact.createElement(runtimeReact.Fragment, null, props.children);
36
+ }
37
+ /**
38
+ * Reactフックと関数のプロキシ
39
+ */
40
+ export const useState = (...args) => {
41
+ if (!runtimeReact) {
42
+ throw new Error("React runtime not initialized. Make sure plugin is loaded properly.");
43
+ }
44
+ return runtimeReact.useState(...args);
45
+ };
46
+ export const useEffect = (...args) => {
47
+ if (!runtimeReact) {
48
+ throw new Error("React runtime not initialized. Make sure plugin is loaded properly.");
49
+ }
50
+ return runtimeReact.useEffect(...args);
51
+ };
52
+ export const useCallback = (...args) => {
53
+ if (!runtimeReact) {
54
+ throw new Error("React runtime not initialized. Make sure plugin is loaded properly.");
55
+ }
56
+ return runtimeReact.useCallback(...args);
57
+ };
58
+ export const useMemo = (...args) => {
59
+ if (!runtimeReact) {
60
+ throw new Error("React runtime not initialized. Make sure plugin is loaded properly.");
61
+ }
62
+ return runtimeReact.useMemo(...args);
63
+ };
64
+ export const useRef = (...args) => {
65
+ if (!runtimeReact) {
66
+ throw new Error("React runtime not initialized. Make sure plugin is loaded properly.");
67
+ }
68
+ return runtimeReact.useRef(...args);
69
+ };
70
+ export const useContext = (...args) => {
71
+ if (!runtimeReact) {
72
+ throw new Error("React runtime not initialized. Make sure plugin is loaded properly.");
73
+ }
74
+ return runtimeReact.useContext(...args);
75
+ };
76
+ export const useReducer = (...args) => {
77
+ if (!runtimeReact) {
78
+ throw new Error("React runtime not initialized. Make sure plugin is loaded properly.");
79
+ }
80
+ return runtimeReact.useReducer(...args);
81
+ };
82
+ export const createElement = (...args) => {
83
+ if (!runtimeReact) {
84
+ throw new Error("React runtime not initialized. Make sure plugin is loaded properly.");
85
+ }
86
+ return runtimeReact.createElement(...args);
87
+ };
88
+ // デフォルトエクスポート(互換性用)
89
+ export default {
90
+ createElement,
91
+ Fragment,
92
+ useState,
93
+ useEffect,
94
+ useCallback,
95
+ useMemo,
96
+ useRef,
97
+ useContext,
98
+ useReducer,
99
+ };
package/dist/types.d.ts CHANGED
@@ -374,6 +374,8 @@ export interface PlayerProps {
374
374
  };
375
375
  /** キーボードナビゲーションを無効化するかどうか(デフォルト: false) */
376
376
  disableKeyboardNavigation?: boolean;
377
+ /** 終了画面(リスタートボタン等)を非表示にするかどうか(デフォルト: false) */
378
+ hideEndScreen?: boolean;
377
379
  }
378
380
  export type VariableValue = string | number | boolean;
379
381
  export interface PlayerState {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luna-editor/engine",
3
- "version": "0.5.12",
3
+ "version": "0.5.13",
4
4
  "description": "Luna Editor scenario playback engine",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",