@nice2dev/game-engine 1.3.15 → 1.3.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (434) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cjs/accessibility/Accessibility.js +152 -165
  3. package/dist/cjs/ai/AI.js +292 -294
  4. package/dist/cjs/ai/BehaviorTree.js +1029 -1113
  5. package/dist/cjs/ai/StateMachine.js +694 -756
  6. package/dist/cjs/animation/Animation.js +232 -241
  7. package/dist/cjs/audio/AudioAdvanced.js +221 -236
  8. package/dist/cjs/audio/AudioBridge.js +441 -435
  9. package/dist/cjs/audio/AudioManager.js +242 -259
  10. package/dist/cjs/build-cloud/BuildOrchestration.js +1059 -1098
  11. package/dist/cjs/build-cloud/GodotBuildCloud.js +641 -670
  12. package/dist/cjs/build-cloud/UnityBuildCloud.js +563 -610
  13. package/dist/cjs/build-cloud/UnrealBuildCloud.js +610 -635
  14. package/dist/cjs/core/EventBus.js +51 -56
  15. package/dist/cjs/core/GameClock.js +62 -66
  16. package/dist/cjs/core/GameConfig.js +72 -79
  17. package/dist/cjs/core/GameLoop.js +80 -89
  18. package/dist/cjs/core/ServiceLocator.js +61 -65
  19. package/dist/cjs/core/Validation.js +72 -82
  20. package/dist/cjs/core/math.js +108 -112
  21. package/dist/cjs/devtools/DevTools.js +542 -563
  22. package/dist/cjs/devtools/DeveloperExperience.js +521 -559
  23. package/dist/cjs/devtools/GameplayAnalytics.js +636 -646
  24. package/dist/cjs/dialogue/DialogueSystem.js +984 -986
  25. package/dist/cjs/docs/DocGenerator.js +339 -364
  26. package/dist/cjs/ecs/World.js +355 -371
  27. package/dist/cjs/editor/AchievementEditor.js +580 -636
  28. package/dist/cjs/editor/AdvancedEditor.js +184 -191
  29. package/dist/cjs/editor/AnimationRetargeting.js +696 -741
  30. package/dist/cjs/editor/AssetBrowser.js +1072 -1160
  31. package/dist/cjs/editor/AssetManager.js +187 -192
  32. package/dist/cjs/editor/ConsolePro.js +971 -1061
  33. package/dist/cjs/editor/CraftingEditor.js +680 -730
  34. package/dist/cjs/editor/DebugTools.js +187 -185
  35. package/dist/cjs/editor/DialogueSystem.js +844 -901
  36. package/dist/cjs/editor/LeaderboardSystem.js +429 -483
  37. package/dist/cjs/editor/NiceGameEditor.js +1903 -1201
  38. package/dist/cjs/editor/PrefabSystem.js +772 -838
  39. package/dist/cjs/editor/ProfilerPanel.js +649 -700
  40. package/dist/cjs/editor/QuestEditor.js +792 -839
  41. package/dist/cjs/editor/ReplaySystem.js +585 -634
  42. package/dist/cjs/editor/SceneEditor.js +176 -200
  43. package/dist/cjs/editor/ScreenshotMode.js +621 -688
  44. package/dist/cjs/editor/ShaderGraph.js +1556 -1582
  45. package/dist/cjs/editor/TimelineEditor.js +715 -778
  46. package/dist/cjs/editor/UndoRedoPro.js +830 -904
  47. package/dist/cjs/editor/VRARMode.js +582 -645
  48. package/dist/cjs/engine/NiceGameEngine.js +141 -164
  49. package/dist/cjs/engine/SaveSystem.js +441 -495
  50. package/dist/cjs/enterprise/Enterprise.js +254 -261
  51. package/dist/cjs/export/BevyExporter.js +927 -967
  52. package/dist/cjs/export/CocosExporter.js +644 -673
  53. package/dist/cjs/export/Construct3Exporter.js +783 -794
  54. package/dist/cjs/export/DefoldExporter.js +1028 -1069
  55. package/dist/cjs/export/GDevelopExporter.js +674 -708
  56. package/dist/cjs/export/GameMakerExporter.js +791 -805
  57. package/dist/cjs/export/GodotExporter.js +1003 -1045
  58. package/dist/cjs/export/MinecraftBedrockExporter.js +573 -578
  59. package/dist/cjs/export/MonoGameExporter.js +1265 -1293
  60. package/dist/cjs/export/Pico8Exporter.js +766 -812
  61. package/dist/cjs/export/PlatformExporter.js +188 -206
  62. package/dist/cjs/export/RPGMakerExporter.js +860 -879
  63. package/dist/cjs/export/RobloxExporter.js +883 -919
  64. package/dist/cjs/export/Solar2DExporter.js +1218 -1256
  65. package/dist/cjs/export/ThreeJSExporter.js +1006 -1073
  66. package/dist/cjs/export/Tic80Exporter.js +1210 -1267
  67. package/dist/cjs/export/UnityExporter.js +1116 -1131
  68. package/dist/cjs/export/UnrealExporter.js +889 -919
  69. package/dist/cjs/export/WebExporter.js +931 -970
  70. package/dist/cjs/export/index.js +42 -65
  71. package/dist/cjs/godot-integration/GodotAssetLibrary.js +659 -692
  72. package/dist/cjs/godot-integration/GodotImport.js +929 -1027
  73. package/dist/cjs/godot-integration/GodotLiveLink.js +915 -945
  74. package/dist/cjs/godot-integration/components/GodotProjectBrowser.js +911 -611
  75. package/dist/cjs/i18n/I18n.js +860 -900
  76. package/dist/cjs/i18n/useTranslation.js +84 -89
  77. package/dist/cjs/import/AsepriteImporter.js +658 -716
  78. package/dist/cjs/import/DragonBonesImporter.js +437 -461
  79. package/dist/cjs/import/GameMakerImporter.js +511 -521
  80. package/dist/cjs/import/GodotSceneImporter.js +788 -822
  81. package/dist/cjs/import/LDtkImporter.js +436 -454
  82. package/dist/cjs/import/Live2DImporter.js +497 -518
  83. package/dist/cjs/import/NdgFormat.js +424 -457
  84. package/dist/cjs/import/OgmoImporter.js +466 -493
  85. package/dist/cjs/import/RPGMakerImporter.js +451 -477
  86. package/dist/cjs/import/SceneImporter.js +378 -423
  87. package/dist/cjs/import/SpineImporter.js +511 -546
  88. package/dist/cjs/import/SpriterImporter.js +590 -613
  89. package/dist/cjs/import/TiledMapImporter.js +811 -846
  90. package/dist/cjs/import/UnitySceneImporter.js +687 -703
  91. package/dist/cjs/import/index.js +206 -251
  92. package/dist/cjs/index.js +275 -278
  93. package/dist/cjs/input/GamepadNavigation.js +185 -205
  94. package/dist/cjs/input/InputManager.js +397 -421
  95. package/dist/cjs/input/useGamepads.js +70 -81
  96. package/dist/cjs/integration/IconSprite.js +129 -256
  97. package/dist/cjs/inventory/InventorySystem.js +920 -995
  98. package/dist/cjs/kids/KidMode.js +997 -1022
  99. package/dist/cjs/kids/KidTools.js +570 -594
  100. package/dist/cjs/monetization/Monetization.js +170 -177
  101. package/dist/cjs/multiplayer/LocalMultiplayer.js +287 -287
  102. package/dist/cjs/multiplayer/MiniGameTypes.js +49 -59
  103. package/dist/cjs/native-engine/AnimationSystem.js +731 -806
  104. package/dist/cjs/native-engine/AssetPipeline.js +454 -515
  105. package/dist/cjs/native-engine/AudioEngine.js +763 -804
  106. package/dist/cjs/native-engine/InputSystem.js +1075 -1145
  107. package/dist/cjs/native-engine/NetworkingSystem.js +733 -793
  108. package/dist/cjs/native-engine/PhysicsEngine.js +583 -607
  109. package/dist/cjs/native-engine/RenderingEngine.js +302 -350
  110. package/dist/cjs/native-engine/SceneGraph.js +769 -808
  111. package/dist/cjs/network/MultiplayerTransport.js +97 -132
  112. package/dist/cjs/network/Networking.js +549 -564
  113. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/AbortController.js +21 -29
  114. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/AccessTokenHttpClient.js +40 -48
  115. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/DefaultHttpClient.js +35 -43
  116. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/DefaultReconnectPolicy.js +10 -15
  117. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/Errors.js +103 -135
  118. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/FetchHttpClient.js +131 -158
  119. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/HandshakeProtocol.js +39 -53
  120. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/HeaderNames.js +5 -8
  121. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/HttpClient.js +40 -48
  122. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/HttpConnection.js +494 -572
  123. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/HubConnection.js +826 -953
  124. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/HubConnectionBuilder.js +125 -146
  125. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/IHubProtocol.js +13 -23
  126. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/ILogger.js +11 -25
  127. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/ITransport.js +13 -24
  128. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/JsonHubProtocol.js +108 -121
  129. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/Loggers.js +10 -14
  130. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/LongPollingTransport.js +150 -185
  131. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/MessageBuffer.js +158 -196
  132. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/ServerSentEventsTransport.js +90 -105
  133. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/Subject.js +30 -35
  134. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/TextMessageFormat.js +16 -21
  135. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/Utils.js +215 -248
  136. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/WebSocketTransport.js +135 -157
  137. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/XhrHttpClient.js +76 -85
  138. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/index.js +24 -27
  139. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/pkg-version.js +3 -4
  140. package/dist/cjs/pathfinding/Pathfinding.js +245 -263
  141. package/dist/cjs/performance/AssetStreaming.js +561 -635
  142. package/dist/cjs/performance/BenchmarkSuite.js +283 -311
  143. package/dist/cjs/performance/DeltaCompression.js +467 -526
  144. package/dist/cjs/performance/DeterministicPhysics.js +450 -498
  145. package/dist/cjs/performance/ECSOptimization.js +644 -695
  146. package/dist/cjs/performance/GameSaveSystem.js +509 -556
  147. package/dist/cjs/performance/HotReload.js +447 -500
  148. package/dist/cjs/performance/InputRecording.js +511 -561
  149. package/dist/cjs/performance/InterestManagement.js +453 -500
  150. package/dist/cjs/performance/LocalizationRuntime.js +376 -423
  151. package/dist/cjs/performance/MemoryManagement.js +410 -470
  152. package/dist/cjs/performance/ModSupport.js +445 -496
  153. package/dist/cjs/performance/MultiThreadedPhysics.js +400 -443
  154. package/dist/cjs/performance/NetworkPrediction.js +424 -482
  155. package/dist/cjs/performance/ObjectPooling.js +569 -617
  156. package/dist/cjs/performance/Performance.js +174 -190
  157. package/dist/cjs/performance/PerformanceAdvanced.js +614 -637
  158. package/dist/cjs/performance/WebGPURenderer.js +656 -683
  159. package/dist/cjs/physics/PhysicsAdvanced.js +345 -379
  160. package/dist/cjs/physics/PhysicsEngine2D.js +391 -427
  161. package/dist/cjs/plugins/PluginSDK.js +432 -466
  162. package/dist/cjs/plugins/PluginTestKit.js +307 -330
  163. package/dist/cjs/procedural/Procedural.js +243 -254
  164. package/dist/cjs/procedural/WorldBuilding.js +297 -314
  165. package/dist/cjs/quest/QuestSystem.js +975 -932
  166. package/dist/cjs/release/MobileExport.js +157 -163
  167. package/dist/cjs/release/Release.js +225 -238
  168. package/dist/cjs/rendering/Camera2D.js +121 -124
  169. package/dist/cjs/rendering/Renderer2D.js +321 -335
  170. package/dist/cjs/rendering/WebGPURenderPipeline.js +632 -651
  171. package/dist/cjs/runtime3d/Runtime3D.js +359 -378
  172. package/dist/cjs/runtime3d/SceneEditor3D.js +252 -263
  173. package/dist/cjs/scene/SceneManager.js +177 -193
  174. package/dist/cjs/scripting/GraphToAST.js +574 -547
  175. package/dist/cjs/scripting/LanguageExporter.js +254 -279
  176. package/dist/cjs/scripting/NodeGraph.js +546 -570
  177. package/dist/cjs/scripting/ScriptAST.js +31 -39
  178. package/dist/cjs/scripting/VisualScripting2.js +1016 -1065
  179. package/dist/cjs/scripting/exporters/CSharpExporter.js +464 -496
  180. package/dist/cjs/scripting/exporters/GDScriptExporter.js +390 -442
  181. package/dist/cjs/scripting/exporters/LuaExporter.js +406 -447
  182. package/dist/cjs/scripting/exporters/PythonExporter.js +484 -549
  183. package/dist/cjs/scripting/exporters/RustExporter.js +459 -512
  184. package/dist/cjs/scripting/exporters/TypeScriptExporter.js +505 -556
  185. package/dist/cjs/social/Social.js +72 -80
  186. package/dist/cjs/systems/LightingSystem.js +844 -931
  187. package/dist/cjs/systems/ParticleSystem2.js +1447 -1451
  188. package/dist/cjs/systems/PhysicsDebugSystem.js +1070 -1141
  189. package/dist/cjs/systems/TerrainSystem.js +985 -1098
  190. package/dist/cjs/templates/ActionTemplates.js +296 -309
  191. package/dist/cjs/templates/FightingTemplate.js +417 -463
  192. package/dist/cjs/templates/MetroidvaniaTemplate.js +353 -405
  193. package/dist/cjs/templates/PartyTemplates.js +190 -202
  194. package/dist/cjs/templates/PuzzleTemplates.js +245 -261
  195. package/dist/cjs/templates/RPGTemplates.js +160 -178
  196. package/dist/cjs/templates/SportsTemplates.js +237 -250
  197. package/dist/cjs/templates/StrategyTemplates.js +143 -153
  198. package/dist/cjs/templates/SurvivalTemplate.js +336 -380
  199. package/dist/cjs/templates/WaveDefense.js +499 -526
  200. package/dist/cjs/tilemap/Tilemap.js +207 -214
  201. package/dist/cjs/tutorials/TutorialBranching.js +396 -417
  202. package/dist/cjs/tutorials/TutorialEngine.js +888 -931
  203. package/dist/cjs/tutorials/TutorialOverlay.js +602 -640
  204. package/dist/cjs/tutorials/content/Tutorials_T05_T08.js +787 -809
  205. package/dist/cjs/tutorials/content/Tutorials_T09_T11.js +736 -756
  206. package/dist/cjs/tutorials/content/Tutorials_T12_T15.js +754 -778
  207. package/dist/cjs/ui/GameUI.js +295 -312
  208. package/dist/cjs/unity-integration/UnityAssetStore.js +700 -741
  209. package/dist/cjs/unity-integration/UnityImport.js +1066 -1176
  210. package/dist/cjs/unity-integration/UnityLiveLink.js +934 -981
  211. package/dist/cjs/unity-integration/components/UnityAssetStoreBrowser.js +1233 -757
  212. package/dist/cjs/unity-integration/components/UnityProjectBrowser.js +1240 -768
  213. package/dist/cjs/unreal-integration/UnrealImport.js +391 -412
  214. package/dist/cjs/unreal-integration/UnrealLiveLink.js +1102 -1137
  215. package/dist/cjs/unreal-integration/UnrealMarketplace.js +436 -473
  216. package/dist/cjs/unreal-integration/components/UnrealProjectBrowser.js +1150 -767
  217. package/dist/cjs/xr/ARVR.js +167 -175
  218. package/dist/esm/accessibility/Accessibility.js +160 -164
  219. package/dist/esm/ai/AI.js +310 -293
  220. package/dist/esm/ai/BehaviorTree.js +1057 -1112
  221. package/dist/esm/ai/StateMachine.js +709 -755
  222. package/dist/esm/animation/Animation.js +237 -240
  223. package/dist/esm/audio/AudioAdvanced.js +239 -235
  224. package/dist/esm/audio/AudioBridge.js +448 -434
  225. package/dist/esm/audio/AudioManager.js +243 -258
  226. package/dist/esm/build-cloud/BuildOrchestration.js +1071 -1097
  227. package/dist/esm/build-cloud/GodotBuildCloud.js +647 -669
  228. package/dist/esm/build-cloud/UnityBuildCloud.js +571 -609
  229. package/dist/esm/build-cloud/UnrealBuildCloud.js +615 -634
  230. package/dist/esm/core/EventBus.js +52 -55
  231. package/dist/esm/core/GameClock.js +63 -65
  232. package/dist/esm/core/GameConfig.js +77 -78
  233. package/dist/esm/core/GameLoop.js +81 -88
  234. package/dist/esm/core/ServiceLocator.js +63 -64
  235. package/dist/esm/core/Validation.js +82 -81
  236. package/dist/esm/core/math.js +109 -111
  237. package/dist/esm/devtools/DevTools.js +546 -562
  238. package/dist/esm/devtools/DeveloperExperience.js +535 -558
  239. package/dist/esm/devtools/GameplayAnalytics.js +647 -645
  240. package/dist/esm/dialogue/DialogueSystem.js +998 -985
  241. package/dist/esm/docs/DocGenerator.js +342 -363
  242. package/dist/esm/ecs/World.js +358 -370
  243. package/dist/esm/editor/AchievementEditor.js +584 -635
  244. package/dist/esm/editor/AdvancedEditor.js +191 -190
  245. package/dist/esm/editor/AnimationRetargeting.js +700 -740
  246. package/dist/esm/editor/AssetBrowser.js +1078 -1159
  247. package/dist/esm/editor/AssetManager.js +188 -191
  248. package/dist/esm/editor/ConsolePro.js +977 -1060
  249. package/dist/esm/editor/CraftingEditor.js +684 -729
  250. package/dist/esm/editor/DebugTools.js +194 -184
  251. package/dist/esm/editor/DialogueSystem.js +849 -900
  252. package/dist/esm/editor/LeaderboardSystem.js +433 -482
  253. package/dist/esm/editor/NiceGameEditor.js +1908 -1200
  254. package/dist/esm/editor/PrefabSystem.js +777 -837
  255. package/dist/esm/editor/ProfilerPanel.js +654 -699
  256. package/dist/esm/editor/QuestEditor.js +795 -838
  257. package/dist/esm/editor/ReplaySystem.js +591 -633
  258. package/dist/esm/editor/SceneEditor.js +189 -199
  259. package/dist/esm/editor/ScreenshotMode.js +628 -687
  260. package/dist/esm/editor/ShaderGraph.js +1565 -1581
  261. package/dist/esm/editor/TimelineEditor.js +733 -777
  262. package/dist/esm/editor/UndoRedoPro.js +835 -903
  263. package/dist/esm/editor/VRARMode.js +586 -644
  264. package/dist/esm/engine/NiceGameEngine.js +142 -163
  265. package/dist/esm/engine/SaveSystem.js +447 -494
  266. package/dist/esm/enterprise/Enterprise.js +265 -260
  267. package/dist/esm/export/BevyExporter.js +929 -966
  268. package/dist/esm/export/CocosExporter.js +646 -672
  269. package/dist/esm/export/Construct3Exporter.js +785 -793
  270. package/dist/esm/export/DefoldExporter.js +1030 -1068
  271. package/dist/esm/export/GDevelopExporter.js +676 -707
  272. package/dist/esm/export/GameMakerExporter.js +793 -804
  273. package/dist/esm/export/GodotExporter.js +1004 -1044
  274. package/dist/esm/export/MinecraftBedrockExporter.js +575 -577
  275. package/dist/esm/export/MonoGameExporter.js +1267 -1292
  276. package/dist/esm/export/Pico8Exporter.js +768 -811
  277. package/dist/esm/export/PlatformExporter.js +193 -205
  278. package/dist/esm/export/RPGMakerExporter.js +862 -878
  279. package/dist/esm/export/RobloxExporter.js +885 -918
  280. package/dist/esm/export/Solar2DExporter.js +1220 -1255
  281. package/dist/esm/export/ThreeJSExporter.js +1007 -1072
  282. package/dist/esm/export/Tic80Exporter.js +1212 -1266
  283. package/dist/esm/export/UnityExporter.js +1117 -1130
  284. package/dist/esm/export/UnrealExporter.js +891 -918
  285. package/dist/esm/export/WebExporter.js +933 -969
  286. package/dist/esm/export/index.js +86 -71
  287. package/dist/esm/godot-integration/GodotAssetLibrary.js +663 -691
  288. package/dist/esm/godot-integration/GodotImport.js +935 -1026
  289. package/dist/esm/godot-integration/GodotLiveLink.js +918 -944
  290. package/dist/esm/godot-integration/components/GodotProjectBrowser.js +912 -610
  291. package/dist/esm/i18n/I18n.js +871 -899
  292. package/dist/esm/i18n/useTranslation.js +88 -88
  293. package/dist/esm/import/AsepriteImporter.js +637 -715
  294. package/dist/esm/import/DragonBonesImporter.js +417 -460
  295. package/dist/esm/import/GameMakerImporter.js +491 -520
  296. package/dist/esm/import/GodotSceneImporter.js +767 -821
  297. package/dist/esm/import/LDtkImporter.js +415 -453
  298. package/dist/esm/import/Live2DImporter.js +477 -517
  299. package/dist/esm/import/NdgFormat.js +410 -456
  300. package/dist/esm/import/OgmoImporter.js +446 -492
  301. package/dist/esm/import/RPGMakerImporter.js +431 -476
  302. package/dist/esm/import/SceneImporter.js +385 -422
  303. package/dist/esm/import/SpineImporter.js +491 -545
  304. package/dist/esm/import/SpriterImporter.js +570 -612
  305. package/dist/esm/import/TiledMapImporter.js +790 -845
  306. package/dist/esm/import/UnitySceneImporter.js +666 -702
  307. package/dist/esm/import/index.js +247 -258
  308. package/dist/esm/index.js +1332 -188
  309. package/dist/esm/input/GamepadNavigation.js +187 -204
  310. package/dist/esm/input/InputManager.js +401 -420
  311. package/dist/esm/input/useGamepads.js +72 -80
  312. package/dist/esm/integration/IconSprite.js +143 -255
  313. package/dist/esm/inventory/InventorySystem.js +925 -994
  314. package/dist/esm/kids/KidMode.js +1003 -1021
  315. package/dist/esm/kids/KidTools.js +573 -593
  316. package/dist/esm/monetization/Monetization.js +180 -176
  317. package/dist/esm/multiplayer/LocalMultiplayer.js +290 -286
  318. package/dist/esm/multiplayer/MiniGameTypes.js +58 -58
  319. package/dist/esm/native-engine/AnimationSystem.js +735 -805
  320. package/dist/esm/native-engine/AssetPipeline.js +456 -514
  321. package/dist/esm/native-engine/AudioEngine.js +765 -803
  322. package/dist/esm/native-engine/InputSystem.js +1079 -1144
  323. package/dist/esm/native-engine/NetworkingSystem.js +740 -792
  324. package/dist/esm/native-engine/PhysicsEngine.js +585 -606
  325. package/dist/esm/native-engine/RenderingEngine.js +304 -349
  326. package/dist/esm/native-engine/SceneGraph.js +773 -807
  327. package/dist/esm/network/MultiplayerTransport.js +99 -131
  328. package/dist/esm/network/Networking.js +556 -563
  329. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/AbortController.js +22 -28
  330. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/AccessTokenHttpClient.js +41 -47
  331. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/DefaultHttpClient.js +36 -42
  332. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/DefaultReconnectPolicy.js +11 -14
  333. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/Errors.js +111 -134
  334. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/FetchHttpClient.js +132 -157
  335. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/HandshakeProtocol.js +40 -52
  336. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/HeaderNames.js +6 -7
  337. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/HttpClient.js +42 -47
  338. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/HttpConnection.js +496 -571
  339. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/HubConnection.js +828 -952
  340. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/HubConnectionBuilder.js +126 -145
  341. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/IHubProtocol.js +14 -23
  342. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/ILogger.js +12 -25
  343. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/ITransport.js +15 -24
  344. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/JsonHubProtocol.js +109 -120
  345. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/Loggers.js +11 -13
  346. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/LongPollingTransport.js +151 -184
  347. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/MessageBuffer.js +159 -195
  348. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/ServerSentEventsTransport.js +91 -104
  349. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/Subject.js +31 -34
  350. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/TextMessageFormat.js +17 -20
  351. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/Utils.js +230 -248
  352. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/WebSocketTransport.js +136 -156
  353. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/XhrHttpClient.js +77 -84
  354. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/index.js +31 -12
  355. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/pkg-version.js +4 -3
  356. package/dist/esm/pathfinding/Pathfinding.js +249 -262
  357. package/dist/esm/performance/AssetStreaming.js +567 -634
  358. package/dist/esm/performance/BenchmarkSuite.js +285 -310
  359. package/dist/esm/performance/DeltaCompression.js +478 -525
  360. package/dist/esm/performance/DeterministicPhysics.js +479 -497
  361. package/dist/esm/performance/ECSOptimization.js +653 -694
  362. package/dist/esm/performance/GameSaveSystem.js +515 -555
  363. package/dist/esm/performance/HotReload.js +454 -499
  364. package/dist/esm/performance/InputRecording.js +520 -560
  365. package/dist/esm/performance/InterestManagement.js +460 -499
  366. package/dist/esm/performance/LocalizationRuntime.js +382 -422
  367. package/dist/esm/performance/MemoryManagement.js +415 -469
  368. package/dist/esm/performance/ModSupport.js +455 -495
  369. package/dist/esm/performance/MultiThreadedPhysics.js +408 -442
  370. package/dist/esm/performance/NetworkPrediction.js +436 -481
  371. package/dist/esm/performance/ObjectPooling.js +582 -616
  372. package/dist/esm/performance/Performance.js +182 -189
  373. package/dist/esm/performance/PerformanceAdvanced.js +621 -636
  374. package/dist/esm/performance/WebGPURenderer.js +660 -682
  375. package/dist/esm/physics/PhysicsAdvanced.js +359 -378
  376. package/dist/esm/physics/PhysicsEngine2D.js +393 -426
  377. package/dist/esm/plugins/PluginSDK.js +435 -465
  378. package/dist/esm/plugins/PluginTestKit.js +315 -329
  379. package/dist/esm/procedural/Procedural.js +248 -253
  380. package/dist/esm/procedural/WorldBuilding.js +316 -313
  381. package/dist/esm/quest/QuestSystem.js +982 -931
  382. package/dist/esm/release/MobileExport.js +163 -162
  383. package/dist/esm/release/Release.js +234 -237
  384. package/dist/esm/rendering/Camera2D.js +122 -123
  385. package/dist/esm/rendering/Renderer2D.js +323 -334
  386. package/dist/esm/rendering/WebGPURenderPipeline.js +647 -650
  387. package/dist/esm/runtime3d/Runtime3D.js +368 -377
  388. package/dist/esm/runtime3d/SceneEditor3D.js +268 -262
  389. package/dist/esm/scene/SceneManager.js +178 -192
  390. package/dist/esm/scripting/GraphToAST.js +576 -546
  391. package/dist/esm/scripting/LanguageExporter.js +263 -278
  392. package/dist/esm/scripting/NodeGraph.js +550 -569
  393. package/dist/esm/scripting/ScriptAST.js +45 -38
  394. package/dist/esm/scripting/VisualScripting2.js +1025 -1064
  395. package/dist/esm/scripting/exporters/CSharpExporter.js +465 -495
  396. package/dist/esm/scripting/exporters/GDScriptExporter.js +391 -441
  397. package/dist/esm/scripting/exporters/LuaExporter.js +407 -446
  398. package/dist/esm/scripting/exporters/PythonExporter.js +485 -548
  399. package/dist/esm/scripting/exporters/RustExporter.js +460 -511
  400. package/dist/esm/scripting/exporters/TypeScriptExporter.js +506 -555
  401. package/dist/esm/social/Social.js +77 -79
  402. package/dist/esm/systems/LightingSystem.js +855 -930
  403. package/dist/esm/systems/ParticleSystem2.js +1453 -1450
  404. package/dist/esm/systems/PhysicsDebugSystem.js +1078 -1140
  405. package/dist/esm/systems/TerrainSystem.js +996 -1097
  406. package/dist/esm/templates/ActionTemplates.js +307 -308
  407. package/dist/esm/templates/FightingTemplate.js +439 -462
  408. package/dist/esm/templates/MetroidvaniaTemplate.js +391 -404
  409. package/dist/esm/templates/PartyTemplates.js +206 -201
  410. package/dist/esm/templates/PuzzleTemplates.js +260 -260
  411. package/dist/esm/templates/RPGTemplates.js +172 -177
  412. package/dist/esm/templates/SportsTemplates.js +251 -249
  413. package/dist/esm/templates/StrategyTemplates.js +158 -152
  414. package/dist/esm/templates/SurvivalTemplate.js +367 -379
  415. package/dist/esm/templates/WaveDefense.js +501 -525
  416. package/dist/esm/tilemap/Tilemap.js +209 -213
  417. package/dist/esm/tutorials/TutorialBranching.js +399 -416
  418. package/dist/esm/tutorials/TutorialEngine.js +893 -930
  419. package/dist/esm/tutorials/TutorialOverlay.js +603 -639
  420. package/dist/esm/tutorials/content/Tutorials_T05_T08.js +792 -808
  421. package/dist/esm/tutorials/content/Tutorials_T09_T11.js +740 -755
  422. package/dist/esm/tutorials/content/Tutorials_T12_T15.js +759 -777
  423. package/dist/esm/ui/GameUI.js +296 -311
  424. package/dist/esm/unity-integration/UnityAssetStore.js +704 -740
  425. package/dist/esm/unity-integration/UnityImport.js +1071 -1175
  426. package/dist/esm/unity-integration/UnityLiveLink.js +938 -980
  427. package/dist/esm/unity-integration/components/UnityAssetStoreBrowser.js +1234 -756
  428. package/dist/esm/unity-integration/components/UnityProjectBrowser.js +1241 -767
  429. package/dist/esm/unreal-integration/UnrealImport.js +395 -411
  430. package/dist/esm/unreal-integration/UnrealLiveLink.js +1105 -1136
  431. package/dist/esm/unreal-integration/UnrealMarketplace.js +442 -472
  432. package/dist/esm/unreal-integration/components/UnrealProjectBrowser.js +1151 -766
  433. package/dist/esm/xr/ARVR.js +181 -174
  434. package/package.json +7 -12
@@ -1,1289 +1,1991 @@
1
- 'use strict';
2
-
3
- var jsxRuntime = require('react/jsx-runtime');
4
- var ui = require('@nice2dev/ui');
5
- var React = require('react');
6
- var EventBus = require('../core/EventBus.js');
7
- var World = require('../ecs/World.js');
8
- var useTranslation = require('../i18n/useTranslation.js');
9
- var SceneManager = require('../scene/SceneManager.js');
10
-
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const ui = require("@nice2dev/ui");
5
+ const React = require("react");
6
+ const EventBus = require("../core/EventBus.js");
7
+ const World = require("../ecs/World.js");
8
+ const useTranslation = require("../i18n/useTranslation.js");
9
+ const SceneManager = require("../scene/SceneManager.js");
11
10
  class UndoStack {
12
- constructor(maxSize = 100) {
13
- this.stack = [];
14
- this.pointer = -1;
15
- this.events = new EventBus.EventBus();
16
- this.maxSize = maxSize;
11
+ constructor(maxSize = 100) {
12
+ this.stack = [];
13
+ this.pointer = -1;
14
+ this.events = new EventBus.EventBus();
15
+ this.maxSize = maxSize;
16
+ }
17
+ push(entry) {
18
+ this.stack.splice(this.pointer + 1);
19
+ this.stack.push(entry);
20
+ if (this.stack.length > this.maxSize) {
21
+ this.stack.shift();
17
22
  }
18
- push(entry) {
19
- // Remove everything after current pointer
20
- this.stack.splice(this.pointer + 1);
21
- this.stack.push(entry);
22
- if (this.stack.length > this.maxSize) {
23
- this.stack.shift();
24
- }
25
- this.pointer = this.stack.length - 1;
26
- this.events.emit('change', null);
27
- }
28
- undo() {
29
- if (this.pointer < 0) {
30
- return null;
31
- }
32
- const entry = this.stack[this.pointer];
33
- entry.undo();
34
- this.pointer--;
35
- this.events.emit('change', null);
36
- return entry.description;
37
- }
38
- redo() {
39
- if (this.pointer >= this.stack.length - 1) {
40
- return null;
41
- }
42
- this.pointer++;
43
- const entry = this.stack[this.pointer];
44
- entry.redo();
45
- this.events.emit('change', null);
46
- return entry.description;
47
- }
48
- get canUndo() {
49
- return this.pointer >= 0;
50
- }
51
- get canRedo() {
52
- return this.pointer < this.stack.length - 1;
23
+ this.pointer = this.stack.length - 1;
24
+ this.events.emit("change", null);
25
+ }
26
+ undo() {
27
+ if (this.pointer < 0) {
28
+ return null;
53
29
  }
54
- clear() {
55
- this.stack.length = 0;
56
- this.pointer = -1;
57
- this.events.emit('change', null);
30
+ const entry = this.stack[this.pointer];
31
+ entry.undo();
32
+ this.pointer--;
33
+ this.events.emit("change", null);
34
+ return entry.description;
35
+ }
36
+ redo() {
37
+ if (this.pointer >= this.stack.length - 1) {
38
+ return null;
58
39
  }
40
+ this.pointer++;
41
+ const entry = this.stack[this.pointer];
42
+ entry.redo();
43
+ this.events.emit("change", null);
44
+ return entry.description;
45
+ }
46
+ get canUndo() {
47
+ return this.pointer >= 0;
48
+ }
49
+ get canRedo() {
50
+ return this.pointer < this.stack.length - 1;
51
+ }
52
+ clear() {
53
+ this.stack.length = 0;
54
+ this.pointer = -1;
55
+ this.events.emit("change", null);
56
+ }
59
57
  }
60
58
  const DEFAULT_PANELS = [
61
- { id: 'hierarchy', title: 'Hierarchy', visible: true, position: 'left', width: 250 },
62
- { id: 'inspector', title: 'Inspector', visible: true, position: 'right', width: 300 },
63
- { id: 'assets', title: 'Assets', visible: true, position: 'bottom', height: 200 },
64
- { id: 'console', title: 'Console', visible: true, position: 'bottom', height: 150 },
59
+ { id: "hierarchy", title: "Hierarchy", visible: true, position: "left", width: 250 },
60
+ { id: "inspector", title: "Inspector", visible: true, position: "right", width: 300 },
61
+ { id: "assets", title: "Assets", visible: true, position: "bottom", height: 200 },
62
+ { id: "console", title: "Console", visible: true, position: "bottom", height: 150 }
65
63
  ];
66
64
  const DEFAULT_STATE = {
67
- mode: 'edit',
68
- tool: 'select',
69
- selectedEntities: [],
70
- clipboard: [],
71
- gridSize: 16,
72
- snapToGrid: true,
73
- showGrid: true,
74
- showRulers: true,
75
- zoom: 1,
76
- panOffset: { x: 0, y: 0 },
77
- panels: DEFAULT_PANELS,
78
- currentSceneId: null,
79
- projectPath: null,
80
- dirty: false,
65
+ mode: "edit",
66
+ tool: "select",
67
+ selectedEntities: [],
68
+ clipboard: [],
69
+ gridSize: 16,
70
+ snapToGrid: true,
71
+ showGrid: true,
72
+ showRulers: true,
73
+ zoom: 1,
74
+ panOffset: { x: 0, y: 0 },
75
+ panels: DEFAULT_PANELS,
76
+ currentSceneId: null,
77
+ projectPath: null,
78
+ dirty: false
81
79
  };
82
80
  function editorReducer(state, action) {
83
- switch (action.type) {
84
- case 'SET_MODE':
85
- return { ...state, mode: action.mode };
86
- case 'SET_TOOL':
87
- return { ...state, tool: action.tool };
88
- case 'SELECT':
89
- return { ...state, selectedEntities: action.entities };
90
- case 'ADD_SELECTION':
91
- return {
92
- ...state,
93
- selectedEntities: [...new Set([...state.selectedEntities, ...action.entities])],
94
- };
95
- case 'DESELECT_ALL':
96
- return { ...state, selectedEntities: [] };
97
- case 'SET_CLIPBOARD':
98
- return { ...state, clipboard: action.entities };
99
- case 'SET_GRID_SIZE':
100
- return { ...state, gridSize: action.size };
101
- case 'TOGGLE_SNAP':
102
- return { ...state, snapToGrid: !state.snapToGrid };
103
- case 'TOGGLE_GRID':
104
- return { ...state, showGrid: !state.showGrid };
105
- case 'SET_ZOOM':
106
- return { ...state, zoom: Math.max(0.1, Math.min(10, action.zoom)) };
107
- case 'SET_PAN':
108
- return { ...state, panOffset: action.offset };
109
- case 'TOGGLE_PANEL':
110
- return {
111
- ...state,
112
- panels: state.panels.map((p) => p.id === action.panelId ? { ...p, visible: !p.visible } : p),
113
- };
114
- case 'SET_SCENE':
115
- return { ...state, currentSceneId: action.sceneId };
116
- case 'MARK_DIRTY':
117
- return { ...state, dirty: true };
118
- case 'MARK_CLEAN':
119
- return { ...state, dirty: false };
120
- default:
121
- return state;
122
- }
81
+ switch (action.type) {
82
+ case "SET_MODE":
83
+ return { ...state, mode: action.mode };
84
+ case "SET_TOOL":
85
+ return { ...state, tool: action.tool };
86
+ case "SELECT":
87
+ return { ...state, selectedEntities: action.entities };
88
+ case "ADD_SELECTION":
89
+ return {
90
+ ...state,
91
+ selectedEntities: [.../* @__PURE__ */ new Set([...state.selectedEntities, ...action.entities])]
92
+ };
93
+ case "DESELECT_ALL":
94
+ return { ...state, selectedEntities: [] };
95
+ case "SET_CLIPBOARD":
96
+ return { ...state, clipboard: action.entities };
97
+ case "SET_GRID_SIZE":
98
+ return { ...state, gridSize: action.size };
99
+ case "TOGGLE_SNAP":
100
+ return { ...state, snapToGrid: !state.snapToGrid };
101
+ case "TOGGLE_GRID":
102
+ return { ...state, showGrid: !state.showGrid };
103
+ case "SET_ZOOM":
104
+ return { ...state, zoom: Math.max(0.1, Math.min(10, action.zoom)) };
105
+ case "SET_PAN":
106
+ return { ...state, panOffset: action.offset };
107
+ case "TOGGLE_PANEL":
108
+ return {
109
+ ...state,
110
+ panels: state.panels.map(
111
+ (p) => p.id === action.panelId ? { ...p, visible: !p.visible } : p
112
+ )
113
+ };
114
+ case "SET_SCENE":
115
+ return { ...state, currentSceneId: action.sceneId };
116
+ case "MARK_DIRTY":
117
+ return { ...state, dirty: true };
118
+ case "MARK_CLEAN":
119
+ return { ...state, dirty: false };
120
+ default:
121
+ return state;
122
+ }
123
123
  }
124
124
  const EditorContext = /* @__PURE__ */ React.createContext(null);
125
125
  function useEditor() {
126
- const ctx = React.useContext(EditorContext);
127
- if (!ctx) {
128
- throw new Error('useEditor must be used within NiceGameEditor');
129
- }
130
- return ctx;
126
+ const ctx = React.useContext(EditorContext);
127
+ if (!ctx) {
128
+ throw new Error("useEditor must be used within NiceGameEditor");
129
+ }
130
+ return ctx;
131
131
  }
132
132
  function useKeyboardShortcuts(shortcuts) {
133
- React.useEffect(() => {
134
- const handler = (e) => {
135
- for (const s of shortcuts) {
136
- if (e.key.toLowerCase() === s.key.toLowerCase() &&
137
- !!e.ctrlKey === !!s.ctrl &&
138
- !!e.shiftKey === !!s.shift &&
139
- !!e.altKey === !!s.alt) {
140
- e.preventDefault();
141
- s.action();
142
- return;
143
- }
144
- }
145
- };
146
- window.addEventListener('keydown', handler);
147
- return () => window.removeEventListener('keydown', handler);
148
- }, [shortcuts]);
133
+ React.useEffect(() => {
134
+ const handler = (e) => {
135
+ for (const s of shortcuts) {
136
+ if (e.key.toLowerCase() === s.key.toLowerCase() && !!e.ctrlKey === !!s.ctrl && !!e.shiftKey === !!s.shift && !!e.altKey === !!s.alt) {
137
+ e.preventDefault();
138
+ s.action();
139
+ return;
140
+ }
141
+ }
142
+ };
143
+ window.addEventListener("keydown", handler);
144
+ return () => window.removeEventListener("keydown", handler);
145
+ }, [shortcuts]);
149
146
  }
150
- /* ── .ndg Project Format ──────────────────────────────────────── */
151
147
  function serializeProject(project) {
152
- return JSON.stringify(project, null, 2);
148
+ return JSON.stringify(project, null, 2);
153
149
  }
154
150
  function deserializeProject(json) {
155
- return JSON.parse(json);
151
+ return JSON.parse(json);
156
152
  }
157
- /** Built-in guided tour (EN inline + i18n keys → PL and other locales). */
158
153
  const GAME_EDITOR_TOUR = [
159
- {
160
- target: '.nice-game-editor',
161
- titleKey: 'tutorial.gameEditor.intro.title',
162
- title: 'Game editor',
163
- contentKey: 'tutorial.gameEditor.intro.content',
164
- content: 'Build your scene in the viewport using the hierarchy and asset panels, tweak entities in the inspector, and save the project.',
165
- placement: 'auto',
166
- },
154
+ {
155
+ target: ".nice-game-editor",
156
+ titleKey: "tutorial.gameEditor.intro.title",
157
+ title: "Game editor",
158
+ contentKey: "tutorial.gameEditor.intro.content",
159
+ content: "Build your scene in the viewport using the hierarchy and asset panels, tweak entities in the inspector, and save the project.",
160
+ placement: "auto"
161
+ }
167
162
  ];
168
- const NiceGameEditor = ({ project, onSave, className, style, tutorial, }) => {
169
- const { t: tr } = useTranslation.useTranslation('engine');
170
- const [state, dispatch] = React.useReducer(editorReducer, DEFAULT_STATE);
171
- const worldRef = React.useRef(new World.World());
172
- const undoRef = React.useRef(new UndoStack());
173
- const sceneManagerRef = React.useRef(new SceneManager.SceneManager());
174
- const eventsRef = React.useRef(new EventBus.EventBus());
175
- const world = worldRef.current;
176
- const undoStack = undoRef.current;
177
- const sceneManager = sceneManagerRef.current;
178
- const events = eventsRef.current;
179
- const [worldVersion, setWorldVersion] = React.useState(0);
180
- const [consoleLogs, setConsoleLogs] = React.useState([]);
181
- const logIdRef = React.useRef(0);
182
- const refreshWorld = React.useCallback(() => setWorldVersion((v) => v + 1), []);
183
- const addLog = React.useCallback((level, message) => {
184
- setConsoleLogs((prev) => [
185
- ...prev.slice(-199),
186
- { id: logIdRef.current++, level, message, timestamp: Date.now() },
187
- ]);
188
- }, []);
189
- const createNewEntity = React.useCallback((name, components = ['Transform']) => {
190
- const entity = world.createEntity(name);
191
- for (const comp of components) {
192
- world.addComponent(entity.id, comp);
193
- }
194
- dispatch({ type: 'SELECT', entities: [entity.id] });
195
- dispatch({ type: 'MARK_DIRTY' });
196
- refreshWorld();
197
- addLog('info', `Created entity "${name}" (#${entity.id})`);
198
- return entity.id;
199
- }, [world, refreshWorld, addLog]);
200
- // Keyboard shortcuts. The list is built once; Save and Delete reach the handlers of the latest
201
- // render through a ref (they are declared below and change with the selection and the props) —
202
- // until 1.2.2 they called the first render's, so Delete never saw a selection.
203
- const shortcutActionsRef = React.useRef({ save: () => { }, deleteSelected: () => { } });
204
- const shortcuts = React.useMemo(() => [
205
- { key: 'z', ctrl: true, action: () => undoStack.undo(), description: 'Undo' },
206
- { key: 'z', ctrl: true, shift: true, action: () => undoStack.redo(), description: 'Redo' },
207
- { key: 'y', ctrl: true, action: () => undoStack.redo(), description: 'Redo' },
208
- {
209
- key: 's',
210
- ctrl: true,
211
- action: () => shortcutActionsRef.current.save(),
212
- description: 'Save',
213
- },
214
- {
215
- key: 'v',
216
- action: () => dispatch({ type: 'SET_TOOL', tool: 'select' }),
217
- description: 'Select tool',
218
- },
219
- {
220
- key: 'w',
221
- action: () => dispatch({ type: 'SET_TOOL', tool: 'move' }),
222
- description: 'Move tool',
223
- },
224
- {
225
- key: 'e',
226
- action: () => dispatch({ type: 'SET_TOOL', tool: 'rotate' }),
227
- description: 'Rotate tool',
228
- },
229
- {
230
- key: 'r',
231
- action: () => dispatch({ type: 'SET_TOOL', tool: 'scale' }),
232
- description: 'Scale tool',
233
- },
234
- {
235
- key: 'g',
236
- ctrl: true,
237
- action: () => dispatch({ type: 'TOGGLE_GRID' }),
238
- description: 'Toggle grid',
239
- },
240
- {
241
- key: 'Delete',
242
- action: () => shortcutActionsRef.current.deleteSelected(),
243
- description: 'Delete selected',
244
- },
245
- {
246
- key: 'Escape',
247
- action: () => dispatch({ type: 'DESELECT_ALL' }),
248
- description: 'Deselect all',
163
+ const NiceGameEditor = ({
164
+ project,
165
+ onSave,
166
+ className,
167
+ style,
168
+ tutorial
169
+ }) => {
170
+ const { t: tr } = useTranslation.useTranslation("engine");
171
+ const [state, dispatch] = React.useReducer(editorReducer, DEFAULT_STATE);
172
+ const worldRef = React.useRef(new World.World());
173
+ const undoRef = React.useRef(new UndoStack());
174
+ const sceneManagerRef = React.useRef(new SceneManager.SceneManager());
175
+ const eventsRef = React.useRef(new EventBus.EventBus());
176
+ const world = worldRef.current;
177
+ const undoStack = undoRef.current;
178
+ const sceneManager = sceneManagerRef.current;
179
+ const events = eventsRef.current;
180
+ const [worldVersion, setWorldVersion] = React.useState(0);
181
+ const [consoleLogs, setConsoleLogs] = React.useState([]);
182
+ const logIdRef = React.useRef(0);
183
+ const refreshWorld = React.useCallback(() => setWorldVersion((v) => v + 1), []);
184
+ const addLog = React.useCallback((level, message) => {
185
+ setConsoleLogs((prev) => [
186
+ ...prev.slice(-199),
187
+ { id: logIdRef.current++, level, message, timestamp: Date.now() }
188
+ ]);
189
+ }, []);
190
+ const createNewEntity = React.useCallback(
191
+ (name, components = ["Transform"]) => {
192
+ const entity = world.createEntity(name);
193
+ for (const comp of components) {
194
+ world.addComponent(entity.id, comp);
195
+ }
196
+ dispatch({ type: "SELECT", entities: [entity.id] });
197
+ dispatch({ type: "MARK_DIRTY" });
198
+ refreshWorld();
199
+ addLog("info", `Created entity "${name}" (#${entity.id})`);
200
+ return entity.id;
201
+ },
202
+ [world, refreshWorld, addLog]
203
+ );
204
+ const shortcutActionsRef = React.useRef({ save: () => {
205
+ }, deleteSelected: () => {
206
+ } });
207
+ const shortcuts = React.useMemo(
208
+ () => [
209
+ { key: "z", ctrl: true, action: () => undoStack.undo(), description: "Undo" },
210
+ { key: "z", ctrl: true, shift: true, action: () => undoStack.redo(), description: "Redo" },
211
+ { key: "y", ctrl: true, action: () => undoStack.redo(), description: "Redo" },
212
+ {
213
+ key: "s",
214
+ ctrl: true,
215
+ action: () => shortcutActionsRef.current.save(),
216
+ description: "Save"
217
+ },
218
+ {
219
+ key: "v",
220
+ action: () => dispatch({ type: "SET_TOOL", tool: "select" }),
221
+ description: "Select tool"
222
+ },
223
+ {
224
+ key: "w",
225
+ action: () => dispatch({ type: "SET_TOOL", tool: "move" }),
226
+ description: "Move tool"
227
+ },
228
+ {
229
+ key: "e",
230
+ action: () => dispatch({ type: "SET_TOOL", tool: "rotate" }),
231
+ description: "Rotate tool"
232
+ },
233
+ {
234
+ key: "r",
235
+ action: () => dispatch({ type: "SET_TOOL", tool: "scale" }),
236
+ description: "Scale tool"
237
+ },
238
+ {
239
+ key: "g",
240
+ ctrl: true,
241
+ action: () => dispatch({ type: "TOGGLE_GRID" }),
242
+ description: "Toggle grid"
243
+ },
244
+ {
245
+ key: "Delete",
246
+ action: () => shortcutActionsRef.current.deleteSelected(),
247
+ description: "Delete selected"
248
+ },
249
+ {
250
+ key: "Escape",
251
+ action: () => dispatch({ type: "DESELECT_ALL" }),
252
+ description: "Deselect all"
253
+ }
254
+ ],
255
+ [undoStack]
256
+ );
257
+ useKeyboardShortcuts(shortcuts);
258
+ const handleSave = React.useCallback(() => {
259
+ if (!onSave) {
260
+ return;
261
+ }
262
+ const sceneData = sceneManager.serializeScene(
263
+ world,
264
+ state.currentSceneId ?? "main",
265
+ "Main Scene"
266
+ );
267
+ const proj = project ?? {
268
+ formatVersion: 1,
269
+ name: "Untitled",
270
+ version: "0.1.0",
271
+ author: "",
272
+ description: "",
273
+ settings: {
274
+ width: 1280,
275
+ height: 720,
276
+ targetFps: 60,
277
+ maxPlayers: 4,
278
+ physics: {
279
+ gravity: { x: 0, y: 980 },
280
+ fixedTimestep: 1 / 60,
281
+ velocityIterations: 8,
282
+ positionIterations: 3
249
283
  },
250
- ], [undoStack]);
251
- useKeyboardShortcuts(shortcuts);
252
- const handleSave = React.useCallback(() => {
253
- if (!onSave) {
254
- return;
284
+ audio: { masterVolume: 1, musicVolume: 0.7, sfxVolume: 1, voiceVolume: 1 },
285
+ rendering: {
286
+ pixelPerfect: false,
287
+ antiAlias: true,
288
+ backgroundColor: { r: 0.1, g: 0.1, b: 0.15, a: 1 }
255
289
  }
256
- const sceneData = sceneManager.serializeScene(world, state.currentSceneId ?? 'main', 'Main Scene');
257
- const proj = project ?? {
258
- formatVersion: 1,
259
- name: 'Untitled',
260
- version: '0.1.0',
261
- author: '',
262
- description: '',
263
- settings: {
264
- width: 1280,
265
- height: 720,
266
- targetFps: 60,
267
- maxPlayers: 4,
268
- physics: {
269
- gravity: { x: 0, y: 980 },
270
- fixedTimestep: 1 / 60,
271
- velocityIterations: 8,
272
- positionIterations: 3,
273
- },
274
- audio: { masterVolume: 1, musicVolume: 0.7, sfxVolume: 1, voiceVolume: 1 },
275
- rendering: {
276
- pixelPerfect: false,
277
- antiAlias: true,
278
- backgroundColor: { r: 0.1, g: 0.1, b: 0.15, a: 1 },
279
- },
280
- },
281
- scenes: [sceneData],
282
- startSceneId: sceneData.id,
283
- assets: { assets: [] },
284
- };
285
- onSave(proj);
286
- dispatch({ type: 'MARK_CLEAN' });
287
- }, [world, sceneManager, state.currentSceneId, project, onSave]);
288
- const deleteSelected = React.useCallback(() => {
289
- const toDelete = [...state.selectedEntities];
290
- if (toDelete.length === 0) {
291
- return;
292
- }
293
- // Store for undo
294
- const snapshots = toDelete.map((id) => ({
295
- entity: world.getEntity(id),
296
- id,
297
- }));
290
+ },
291
+ scenes: [sceneData],
292
+ startSceneId: sceneData.id,
293
+ assets: { assets: [] }
294
+ };
295
+ onSave(proj);
296
+ dispatch({ type: "MARK_CLEAN" });
297
+ }, [world, sceneManager, state.currentSceneId, project, onSave]);
298
+ const deleteSelected = React.useCallback(() => {
299
+ const toDelete = [...state.selectedEntities];
300
+ if (toDelete.length === 0) {
301
+ return;
302
+ }
303
+ const snapshots = toDelete.map((id) => ({
304
+ entity: world.getEntity(id),
305
+ id
306
+ }));
307
+ for (const id of toDelete) {
308
+ world.destroyEntity(id);
309
+ }
310
+ dispatch({ type: "DESELECT_ALL" });
311
+ dispatch({ type: "MARK_DIRTY" });
312
+ refreshWorld();
313
+ addLog("info", `Deleted ${toDelete.length} entity(ies)`);
314
+ undoStack.push({
315
+ description: `Delete ${toDelete.length} entity(ies)`,
316
+ undo: () => {
317
+ events.emit("editor:undo:delete", snapshots);
318
+ },
319
+ redo: () => {
298
320
  for (const id of toDelete) {
299
- world.destroyEntity(id);
321
+ world.destroyEntity(id);
300
322
  }
301
- dispatch({ type: 'DESELECT_ALL' });
302
- dispatch({ type: 'MARK_DIRTY' });
303
- refreshWorld();
304
- addLog('info', `Deleted ${toDelete.length} entity(ies)`);
305
- undoStack.push({
306
- description: `Delete ${toDelete.length} entity(ies)`,
307
- undo: () => {
308
- // Recreating entities from snapshots would be more complex in real impl
309
- events.emit('editor:undo:delete', snapshots);
310
- },
311
- redo: () => {
312
- for (const id of toDelete) {
313
- world.destroyEntity(id);
314
- }
315
- },
316
- });
317
- }, [state.selectedEntities, world, undoStack, refreshWorld, addLog, events]);
318
- shortcutActionsRef.current = { save: handleSave, deleteSelected };
319
- const ctxValue = React.useMemo(() => ({
320
- state,
321
- dispatch,
322
- world,
323
- undoStack,
324
- sceneManager,
325
- events,
326
- worldVersion,
327
- refreshWorld,
328
- createNewEntity,
329
- consoleLogs,
330
- addLog,
331
- }), [
332
- state,
333
- world,
334
- undoStack,
335
- sceneManager,
336
- events,
337
- worldVersion,
338
- refreshWorld,
339
- createNewEntity,
340
- consoleLogs,
341
- addLog,
342
- ]);
343
- return (jsxRuntime.jsx(EditorContext.Provider, { value: ctxValue, children: jsxRuntime.jsxs("div", { className: `nice-game-editor ${className ?? ''}`, style: {
344
- display: 'flex',
345
- flexDirection: 'column',
346
- width: '100%',
347
- height: '100%',
348
- overflow: 'hidden',
349
- backgroundColor: '#1e1e2e',
350
- color: '#cdd6f4',
351
- fontFamily: 'monospace',
352
- fontSize: 13,
353
- position: 'relative',
354
- ...style,
355
- }, children: [jsxRuntime.jsx("span", { style: { position: 'absolute', top: 6, right: 6, zIndex: 30 }, children: jsxRuntime.jsx(ui.NiceTutorialButton, { steps: ui.resolveTutorial(tutorial, GAME_EDITOR_TOUR) }) }), jsxRuntime.jsx(EditorToolbar, {}), jsxRuntime.jsxs("div", { style: { display: 'flex', flex: 1, overflow: 'hidden' }, children: [state.panels.find((p) => p.id === 'hierarchy')?.visible && (jsxRuntime.jsx(EditorPanelContainer, { title: tr('editor.panels.hierarchy'), width: 250, position: "left", children: jsxRuntime.jsx(HierarchyPanel, {}) })), jsxRuntime.jsx("div", { style: { flex: 1, position: 'relative', overflow: 'hidden' }, children: jsxRuntime.jsx(EditorViewport, {}) }), state.panels.find((p) => p.id === 'inspector')?.visible && (jsxRuntime.jsx(EditorPanelContainer, { title: tr('editor.panels.inspector'), width: 300, position: "right", children: jsxRuntime.jsx(InspectorPanel, {}) }))] }), jsxRuntime.jsxs("div", { style: { display: 'flex', borderTop: '1px solid #45475a' }, children: [state.panels.find((p) => p.id === 'assets')?.visible && (jsxRuntime.jsx(EditorPanelContainer, { title: tr('editor.panels.assets'), height: 180, position: "bottom", children: jsxRuntime.jsx(AssetPanel, {}) })), state.panels.find((p) => p.id === 'console')?.visible && (jsxRuntime.jsx(EditorPanelContainer, { title: tr('editor.panels.console'), height: 180, position: "bottom", children: jsxRuntime.jsx(ConsolePanel, {}) }))] })] }) }));
323
+ }
324
+ });
325
+ }, [state.selectedEntities, world, undoStack, refreshWorld, addLog, events]);
326
+ shortcutActionsRef.current = { save: handleSave, deleteSelected };
327
+ const ctxValue = React.useMemo(
328
+ () => ({
329
+ state,
330
+ dispatch,
331
+ world,
332
+ undoStack,
333
+ sceneManager,
334
+ events,
335
+ worldVersion,
336
+ refreshWorld,
337
+ createNewEntity,
338
+ consoleLogs,
339
+ addLog
340
+ }),
341
+ [
342
+ state,
343
+ world,
344
+ undoStack,
345
+ sceneManager,
346
+ events,
347
+ worldVersion,
348
+ refreshWorld,
349
+ createNewEntity,
350
+ consoleLogs,
351
+ addLog
352
+ ]
353
+ );
354
+ return /* @__PURE__ */ jsxRuntime.jsx(EditorContext.Provider, { value: ctxValue, children: /* @__PURE__ */ jsxRuntime.jsxs(
355
+ "div",
356
+ {
357
+ className: `nice-game-editor ${className ?? ""}`,
358
+ style: {
359
+ display: "flex",
360
+ flexDirection: "column",
361
+ width: "100%",
362
+ height: "100%",
363
+ overflow: "hidden",
364
+ backgroundColor: "#1e1e2e",
365
+ color: "#cdd6f4",
366
+ fontFamily: "monospace",
367
+ fontSize: 13,
368
+ position: "relative",
369
+ ...style
370
+ },
371
+ children: [
372
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { position: "absolute", top: 6, right: 6, zIndex: 30 }, children: /* @__PURE__ */ jsxRuntime.jsx(ui.NiceTutorialButton, { steps: ui.resolveTutorial(tutorial, GAME_EDITOR_TOUR) }) }),
373
+ /* @__PURE__ */ jsxRuntime.jsx(EditorToolbar, {}),
374
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flex: 1, overflow: "hidden" }, children: [
375
+ state.panels.find((p) => p.id === "hierarchy")?.visible && /* @__PURE__ */ jsxRuntime.jsx(EditorPanelContainer, { title: tr("editor.panels.hierarchy"), width: 250, position: "left", children: /* @__PURE__ */ jsxRuntime.jsx(HierarchyPanel, {}) }),
376
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, position: "relative", overflow: "hidden" }, children: /* @__PURE__ */ jsxRuntime.jsx(EditorViewport, {}) }),
377
+ state.panels.find((p) => p.id === "inspector")?.visible && /* @__PURE__ */ jsxRuntime.jsx(
378
+ EditorPanelContainer,
379
+ {
380
+ title: tr("editor.panels.inspector"),
381
+ width: 300,
382
+ position: "right",
383
+ children: /* @__PURE__ */ jsxRuntime.jsx(InspectorPanel, {})
384
+ }
385
+ )
386
+ ] }),
387
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", borderTop: "1px solid #45475a" }, children: [
388
+ state.panels.find((p) => p.id === "assets")?.visible && /* @__PURE__ */ jsxRuntime.jsx(EditorPanelContainer, { title: tr("editor.panels.assets"), height: 180, position: "bottom", children: /* @__PURE__ */ jsxRuntime.jsx(AssetPanel, {}) }),
389
+ state.panels.find((p) => p.id === "console")?.visible && /* @__PURE__ */ jsxRuntime.jsx(
390
+ EditorPanelContainer,
391
+ {
392
+ title: tr("editor.panels.console"),
393
+ height: 180,
394
+ position: "bottom",
395
+ children: /* @__PURE__ */ jsxRuntime.jsx(ConsolePanel, {})
396
+ }
397
+ )
398
+ ] })
399
+ ]
400
+ }
401
+ ) });
356
402
  };
357
- /* ── Helpers ───────────────────────────────────────────────────── */
358
403
  function getEntityBounds(entityId, world) {
359
- const shape = world.getComponent(entityId, 'Shape');
360
- if (shape) {
361
- if (shape.shape === 'circle') {
362
- return { x: -shape.radius, y: -shape.radius, w: shape.radius * 2, h: shape.radius * 2 };
363
- }
364
- return { x: -shape.width / 2, y: -shape.height / 2, w: shape.width, h: shape.height };
404
+ const shape = world.getComponent(entityId, "Shape");
405
+ if (shape) {
406
+ if (shape.shape === "circle") {
407
+ return { x: -shape.radius, y: -shape.radius, w: shape.radius * 2, h: shape.radius * 2 };
365
408
  }
366
- const sprite = world.getComponent(entityId, 'Sprite');
367
- if (sprite) {
368
- return {
369
- x: -sprite.width * sprite.anchor.x,
370
- y: -sprite.height * sprite.anchor.y,
371
- w: sprite.width,
372
- h: sprite.height,
373
- };
374
- }
375
- const text = world.getComponent(entityId, 'Text');
376
- if (text) {
377
- return { x: -30, y: -text.fontSize / 2, w: 60, h: text.fontSize };
378
- }
379
- return { x: -8, y: -8, w: 16, h: 16 };
409
+ return { x: -shape.width / 2, y: -shape.height / 2, w: shape.width, h: shape.height };
410
+ }
411
+ const sprite = world.getComponent(entityId, "Sprite");
412
+ if (sprite) {
413
+ return {
414
+ x: -sprite.width * sprite.anchor.x,
415
+ y: -sprite.height * sprite.anchor.y,
416
+ w: sprite.width,
417
+ h: sprite.height
418
+ };
419
+ }
420
+ const text = world.getComponent(entityId, "Text");
421
+ if (text) {
422
+ return { x: -30, y: -text.fontSize / 2, w: 60, h: text.fontSize };
423
+ }
424
+ return { x: -8, y: -8, w: 16, h: 16 };
380
425
  }
381
426
  function getEntityWorldBounds(entityId, world) {
382
- const transform = world.getComponent(entityId, 'Transform');
383
- if (!transform) {
384
- return null;
385
- }
386
- const b = getEntityBounds(entityId, world);
387
- return { x: transform.position.x + b.x, y: transform.position.y + b.y, w: b.w, h: b.h };
427
+ const transform = world.getComponent(entityId, "Transform");
428
+ if (!transform) {
429
+ return null;
430
+ }
431
+ const b = getEntityBounds(entityId, world);
432
+ return { x: transform.position.x + b.x, y: transform.position.y + b.y, w: b.w, h: b.h };
388
433
  }
389
434
  function getCursorForTool(tool) {
390
- switch (tool) {
391
- case 'move':
392
- return 'move';
393
- case 'rotate':
394
- return 'crosshair';
395
- case 'scale':
396
- return 'nwse-resize';
397
- case 'pan':
398
- return 'grab';
399
- default:
400
- return 'default';
401
- }
435
+ switch (tool) {
436
+ case "move":
437
+ return "move";
438
+ case "rotate":
439
+ return "crosshair";
440
+ case "scale":
441
+ return "nwse-resize";
442
+ case "pan":
443
+ return "grab";
444
+ default:
445
+ return "default";
446
+ }
402
447
  }
403
448
  function colorToCSS(c) {
404
- return `rgba(${Math.round(c.r * 255)},${Math.round(c.g * 255)},${Math.round(c.b * 255)},${c.a})`;
449
+ return `rgba(${Math.round(c.r * 255)},${Math.round(c.g * 255)},${Math.round(c.b * 255)},${c.a})`;
405
450
  }
406
451
  function cssToColor(hex) {
407
- const m = hex.match(/^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i);
408
- if (!m) {
409
- return { r: 1, g: 1, b: 1, a: 1 };
410
- }
411
- return {
412
- r: parseInt(m[1], 16) / 255,
413
- g: parseInt(m[2], 16) / 255,
414
- b: parseInt(m[3], 16) / 255,
415
- a: 1,
416
- };
452
+ const m = hex.match(/^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i);
453
+ if (!m) {
454
+ return { r: 1, g: 1, b: 1, a: 1 };
455
+ }
456
+ return {
457
+ r: parseInt(m[1], 16) / 255,
458
+ g: parseInt(m[2], 16) / 255,
459
+ b: parseInt(m[3], 16) / 255,
460
+ a: 1
461
+ };
417
462
  }
418
463
  function colorToHex(c) {
419
- const r = Math.round(c.r * 255)
420
- .toString(16)
421
- .padStart(2, '0');
422
- const g = Math.round(c.g * 255)
423
- .toString(16)
424
- .padStart(2, '0');
425
- const b = Math.round(c.b * 255)
426
- .toString(16)
427
- .padStart(2, '0');
428
- return `#${r}${g}${b}`;
464
+ const r = Math.round(c.r * 255).toString(16).padStart(2, "0");
465
+ const g = Math.round(c.g * 255).toString(16).padStart(2, "0");
466
+ const b = Math.round(c.b * 255).toString(16).padStart(2, "0");
467
+ return `#${r}${g}${b}`;
429
468
  }
430
- /** `label` is also the name given to a created entity, so it stays English (A3);
431
- * `key` is what the button shows. */
432
469
  const ENTITY_PRESETS = [
433
- { key: 'empty', label: 'Empty', icon: 'circle', components: ['Transform'] },
434
- { key: 'sprite', label: 'Sprite', icon: 'image', components: ['Transform', 'Sprite'] },
435
- { key: 'shape', label: 'Shape', icon: 'square', components: ['Transform', 'Shape'] },
436
- { key: 'text', label: 'Text', icon: 'type', components: ['Transform', 'Text'] },
437
- {
438
- key: 'physicsBody',
439
- label: 'Physics Body',
440
- icon: 'zap',
441
- components: ['Transform', 'Shape', 'Collider2D', 'RigidBody2D'],
442
- },
443
- { key: 'camera', label: 'Camera', icon: 'camera', components: ['Transform', 'CameraTarget'] },
470
+ { key: "empty", label: "Empty", icon: "circle", components: ["Transform"] },
471
+ { key: "sprite", label: "Sprite", icon: "image", components: ["Transform", "Sprite"] },
472
+ { key: "shape", label: "Shape", icon: "square", components: ["Transform", "Shape"] },
473
+ { key: "text", label: "Text", icon: "type", components: ["Transform", "Text"] },
474
+ {
475
+ key: "physicsBody",
476
+ label: "Physics Body",
477
+ icon: "zap",
478
+ components: ["Transform", "Shape", "Collider2D", "RigidBody2D"]
479
+ },
480
+ { key: "camera", label: "Camera", icon: "camera", components: ["Transform", "CameraTarget"] }
444
481
  ];
445
- /* ── Sub-components ───────────────────────────────────────────── */
446
482
  const EditorToolbar = () => {
447
- const { t: tr } = useTranslation.useTranslation('engine');
448
- const { state, dispatch, createNewEntity, undoStack } = useEditor();
449
- const [showAddMenu, setShowAddMenu] = React.useState(false);
450
- const tools = ['select', 'move', 'rotate', 'scale', 'pan'];
451
- const toolIcons = {
452
- select: 'cursor',
453
- move: 'move',
454
- rotate: 'refresh',
455
- scale: 'expand',
456
- pan: 'grip',
457
- };
458
- const btnStyle = (active) => ({
459
- background: active ? '#585b70' : 'transparent',
460
- border: 'none',
461
- color: '#cdd6f4',
462
- padding: '4px 8px',
463
- borderRadius: 4,
464
- cursor: 'pointer',
465
- fontSize: 14,
466
- });
467
- return (jsxRuntime.jsxs("div", { style: {
468
- display: 'flex',
469
- alignItems: 'center',
470
- gap: 4,
471
- padding: '4px 8px',
472
- borderBottom: '1px solid #45475a',
473
- backgroundColor: '#181825',
474
- }, children: [tools.map((t) => (jsxRuntime.jsx("button", { onClick: () => dispatch({ type: 'SET_TOOL', tool: t }), style: btnStyle(state.tool === t), title: `${tr(`editor.tools.${t}`)} (${t[0].toUpperCase()})`, children: jsxRuntime.jsx(ui.NiceIcon, { name: toolIcons[t], size: 14 }) }, t))), jsxRuntime.jsx("div", { style: { width: 1, height: 20, background: '#45475a', margin: '0 4px' } }), jsxRuntime.jsxs("div", { style: { position: 'relative' }, children: [jsxRuntime.jsx("button", { onClick: () => setShowAddMenu(!showAddMenu), style: {
475
- ...btnStyle(false),
476
- fontSize: 12,
477
- padding: '4px 10px',
478
- background: '#2f9e44',
479
- color: '#fff',
480
- }, children: "+ Entity" }), showAddMenu && (jsxRuntime.jsx("div", { style: {
481
- position: 'absolute',
482
- top: '100%',
483
- left: 0,
484
- zIndex: 100,
485
- background: '#1e1e2e',
486
- border: '1px solid #45475a',
487
- borderRadius: 4,
488
- padding: 4,
489
- minWidth: 150,
490
- marginTop: 2,
491
- }, children: ENTITY_PRESETS.map((p) => (jsxRuntime.jsxs("button", { onClick: () => {
492
- createNewEntity(p.label, p.components);
493
- setShowAddMenu(false);
494
- }, style: {
495
- display: 'block',
496
- width: '100%',
497
- textAlign: 'left',
498
- background: 'transparent',
499
- border: 'none',
500
- color: '#cdd6f4',
501
- padding: '4px 8px',
502
- cursor: 'pointer',
503
- borderRadius: 2,
504
- fontSize: 12,
505
- }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: p.icon, size: 12 }), " ", tr(`editor.entity.${p.key}`)] }, p.label))) }))] }), jsxRuntime.jsx("div", { style: { width: 1, height: 20, background: '#45475a', margin: '0 4px' } }), jsxRuntime.jsx("button", { onClick: () => dispatch({ type: 'SET_MODE', mode: state.mode === 'play' ? 'edit' : 'play' }), style: btnStyle(state.mode === 'play'), title: tr('editor.view.playStop'), children: jsxRuntime.jsx(ui.NiceIcon, { name: state.mode === 'play' ? 'stop' : 'play', size: 14 }) }), jsxRuntime.jsx("button", { onClick: () => dispatch({ type: 'SET_MODE', mode: state.mode === 'pause' ? 'play' : 'pause' }), style: btnStyle(state.mode === 'pause'), title: tr('editor.actions.pause'), disabled: state.mode === 'edit', children: jsxRuntime.jsx(ui.NiceIcon, { name: "pause", size: 14 }) }), jsxRuntime.jsx("div", { style: { width: 1, height: 20, background: '#45475a', margin: '0 4px' } }), jsxRuntime.jsx("button", { onClick: () => undoStack.undo(), style: btnStyle(false), title: `${tr('editor.actions.undo')} (Ctrl+Z)`, disabled: !undoStack.canUndo, children: jsxRuntime.jsx(ui.NiceIcon, { name: "undo", size: 14 }) }), jsxRuntime.jsx("button", { onClick: () => undoStack.redo(), style: btnStyle(false), title: `${tr('editor.actions.redo')} (Ctrl+Y)`, disabled: !undoStack.canRedo, children: jsxRuntime.jsx(ui.NiceIcon, { name: "redo", size: 14 }) }), jsxRuntime.jsx("div", { style: { width: 1, height: 20, background: '#45475a', margin: '0 4px' } }), jsxRuntime.jsx("button", { onClick: () => dispatch({ type: 'SET_ZOOM', zoom: state.zoom * 0.8 }), style: btnStyle(false), title: tr('editor.view.zoomOut'), children: "\u2212" }), jsxRuntime.jsxs("span", { style: { fontSize: 11, minWidth: 40, textAlign: 'center' }, children: [(state.zoom * 100) | 0, "%"] }), jsxRuntime.jsx("button", { onClick: () => dispatch({ type: 'SET_ZOOM', zoom: state.zoom * 1.25 }), style: btnStyle(false), title: tr('editor.view.zoomIn'), children: "+" }), jsxRuntime.jsx("button", { onClick: () => {
506
- dispatch({ type: 'SET_ZOOM', zoom: 1 });
507
- dispatch({ type: 'SET_PAN', offset: { x: 0, y: 0 } });
508
- }, style: btnStyle(false), title: tr('editor.view.resetView'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "home", size: 14 }) }), jsxRuntime.jsx("div", { style: { width: 1, height: 20, background: '#45475a', margin: '0 4px' } }), jsxRuntime.jsx("button", { onClick: () => dispatch({ type: 'TOGGLE_GRID' }), style: btnStyle(state.showGrid), title: `${tr('editor.view.toggleGrid')} (Ctrl+G)`, children: jsxRuntime.jsx(ui.NiceIcon, { name: "grid", size: 14 }) }), jsxRuntime.jsx("button", { onClick: () => dispatch({ type: 'TOGGLE_SNAP' }), style: btnStyle(state.snapToGrid), title: tr('editor.view.toggleSnap'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "target", size: 14 }) }), jsxRuntime.jsx("div", { style: { flex: 1 } }), jsxRuntime.jsxs("span", { style: { fontSize: 11, opacity: 0.6 }, children: [state.mode.toUpperCase(), " | ", state.tool, " | ", state.dirty ? 'Unsaved' : 'Saved'] })] }));
483
+ const { t: tr } = useTranslation.useTranslation("engine");
484
+ const { state, dispatch, createNewEntity, undoStack } = useEditor();
485
+ const [showAddMenu, setShowAddMenu] = React.useState(false);
486
+ const tools = ["select", "move", "rotate", "scale", "pan"];
487
+ const toolIcons = {
488
+ select: "cursor",
489
+ move: "move",
490
+ rotate: "refresh",
491
+ scale: "expand",
492
+ pan: "grip"
493
+ };
494
+ const btnStyle = (active) => ({
495
+ background: active ? "#585b70" : "transparent",
496
+ border: "none",
497
+ color: "#cdd6f4",
498
+ padding: "4px 8px",
499
+ borderRadius: 4,
500
+ cursor: "pointer",
501
+ fontSize: 14
502
+ });
503
+ return /* @__PURE__ */ jsxRuntime.jsxs(
504
+ "div",
505
+ {
506
+ style: {
507
+ display: "flex",
508
+ alignItems: "center",
509
+ gap: 4,
510
+ padding: "4px 8px",
511
+ borderBottom: "1px solid #45475a",
512
+ backgroundColor: "#181825"
513
+ },
514
+ children: [
515
+ tools.map((t) => /* @__PURE__ */ jsxRuntime.jsx(
516
+ "button",
517
+ {
518
+ onClick: () => dispatch({ type: "SET_TOOL", tool: t }),
519
+ style: btnStyle(state.tool === t),
520
+ title: `${tr(`editor.tools.${t}`)} (${t[0].toUpperCase()})`,
521
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.NiceIcon, { name: toolIcons[t], size: 14 })
522
+ },
523
+ t
524
+ )),
525
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: 1, height: 20, background: "#45475a", margin: "0 4px" } }),
526
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
527
+ /* @__PURE__ */ jsxRuntime.jsx(
528
+ "button",
529
+ {
530
+ onClick: () => setShowAddMenu(!showAddMenu),
531
+ style: {
532
+ ...btnStyle(false),
533
+ fontSize: 12,
534
+ padding: "4px 10px",
535
+ background: "#2f9e44",
536
+ color: "#fff"
537
+ },
538
+ children: "+ Entity"
539
+ }
540
+ ),
541
+ showAddMenu && /* @__PURE__ */ jsxRuntime.jsx(
542
+ "div",
543
+ {
544
+ style: {
545
+ position: "absolute",
546
+ top: "100%",
547
+ left: 0,
548
+ zIndex: 100,
549
+ background: "#1e1e2e",
550
+ border: "1px solid #45475a",
551
+ borderRadius: 4,
552
+ padding: 4,
553
+ minWidth: 150,
554
+ marginTop: 2
555
+ },
556
+ children: ENTITY_PRESETS.map((p) => /* @__PURE__ */ jsxRuntime.jsxs(
557
+ "button",
558
+ {
559
+ onClick: () => {
560
+ createNewEntity(p.label, p.components);
561
+ setShowAddMenu(false);
562
+ },
563
+ style: {
564
+ display: "block",
565
+ width: "100%",
566
+ textAlign: "left",
567
+ background: "transparent",
568
+ border: "none",
569
+ color: "#cdd6f4",
570
+ padding: "4px 8px",
571
+ cursor: "pointer",
572
+ borderRadius: 2,
573
+ fontSize: 12
574
+ },
575
+ children: [
576
+ /* @__PURE__ */ jsxRuntime.jsx(ui.NiceIcon, { name: p.icon, size: 12 }),
577
+ " ",
578
+ tr(`editor.entity.${p.key}`)
579
+ ]
580
+ },
581
+ p.label
582
+ ))
583
+ }
584
+ )
585
+ ] }),
586
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: 1, height: 20, background: "#45475a", margin: "0 4px" } }),
587
+ /* @__PURE__ */ jsxRuntime.jsx(
588
+ "button",
589
+ {
590
+ onClick: () => dispatch({ type: "SET_MODE", mode: state.mode === "play" ? "edit" : "play" }),
591
+ style: btnStyle(state.mode === "play"),
592
+ title: tr("editor.view.playStop"),
593
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.NiceIcon, { name: state.mode === "play" ? "stop" : "play", size: 14 })
594
+ }
595
+ ),
596
+ /* @__PURE__ */ jsxRuntime.jsx(
597
+ "button",
598
+ {
599
+ onClick: () => dispatch({ type: "SET_MODE", mode: state.mode === "pause" ? "play" : "pause" }),
600
+ style: btnStyle(state.mode === "pause"),
601
+ title: tr("editor.actions.pause"),
602
+ disabled: state.mode === "edit",
603
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.NiceIcon, { name: "pause", size: 14 })
604
+ }
605
+ ),
606
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: 1, height: 20, background: "#45475a", margin: "0 4px" } }),
607
+ /* @__PURE__ */ jsxRuntime.jsx(
608
+ "button",
609
+ {
610
+ onClick: () => undoStack.undo(),
611
+ style: btnStyle(false),
612
+ title: `${tr("editor.actions.undo")} (Ctrl+Z)`,
613
+ disabled: !undoStack.canUndo,
614
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.NiceIcon, { name: "undo", size: 14 })
615
+ }
616
+ ),
617
+ /* @__PURE__ */ jsxRuntime.jsx(
618
+ "button",
619
+ {
620
+ onClick: () => undoStack.redo(),
621
+ style: btnStyle(false),
622
+ title: `${tr("editor.actions.redo")} (Ctrl+Y)`,
623
+ disabled: !undoStack.canRedo,
624
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.NiceIcon, { name: "redo", size: 14 })
625
+ }
626
+ ),
627
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: 1, height: 20, background: "#45475a", margin: "0 4px" } }),
628
+ /* @__PURE__ */ jsxRuntime.jsx(
629
+ "button",
630
+ {
631
+ onClick: () => dispatch({ type: "SET_ZOOM", zoom: state.zoom * 0.8 }),
632
+ style: btnStyle(false),
633
+ title: tr("editor.view.zoomOut"),
634
+ children: "−"
635
+ }
636
+ ),
637
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontSize: 11, minWidth: 40, textAlign: "center" }, children: [
638
+ state.zoom * 100 | 0,
639
+ "%"
640
+ ] }),
641
+ /* @__PURE__ */ jsxRuntime.jsx(
642
+ "button",
643
+ {
644
+ onClick: () => dispatch({ type: "SET_ZOOM", zoom: state.zoom * 1.25 }),
645
+ style: btnStyle(false),
646
+ title: tr("editor.view.zoomIn"),
647
+ children: "+"
648
+ }
649
+ ),
650
+ /* @__PURE__ */ jsxRuntime.jsx(
651
+ "button",
652
+ {
653
+ onClick: () => {
654
+ dispatch({ type: "SET_ZOOM", zoom: 1 });
655
+ dispatch({ type: "SET_PAN", offset: { x: 0, y: 0 } });
656
+ },
657
+ style: btnStyle(false),
658
+ title: tr("editor.view.resetView"),
659
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.NiceIcon, { name: "home", size: 14 })
660
+ }
661
+ ),
662
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: 1, height: 20, background: "#45475a", margin: "0 4px" } }),
663
+ /* @__PURE__ */ jsxRuntime.jsx(
664
+ "button",
665
+ {
666
+ onClick: () => dispatch({ type: "TOGGLE_GRID" }),
667
+ style: btnStyle(state.showGrid),
668
+ title: `${tr("editor.view.toggleGrid")} (Ctrl+G)`,
669
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.NiceIcon, { name: "grid", size: 14 })
670
+ }
671
+ ),
672
+ /* @__PURE__ */ jsxRuntime.jsx(
673
+ "button",
674
+ {
675
+ onClick: () => dispatch({ type: "TOGGLE_SNAP" }),
676
+ style: btnStyle(state.snapToGrid),
677
+ title: tr("editor.view.toggleSnap"),
678
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.NiceIcon, { name: "target", size: 14 })
679
+ }
680
+ ),
681
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1 } }),
682
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontSize: 11, opacity: 0.6 }, children: [
683
+ state.mode.toUpperCase(),
684
+ " | ",
685
+ state.tool,
686
+ " | ",
687
+ state.dirty ? "Unsaved" : "Saved"
688
+ ] })
689
+ ]
690
+ }
691
+ );
509
692
  };
510
693
  const EditorPanelContainer = ({ title, width, height, position, children }) => {
511
- const isHorizontal = position === 'bottom';
512
- return (jsxRuntime.jsxs("div", { style: {
513
- width: isHorizontal ? '100%' : width,
514
- height: isHorizontal ? height : undefined,
515
- flex: isHorizontal ? undefined : undefined,
516
- borderLeft: position === 'right' ? '1px solid #45475a' : undefined,
517
- borderRight: position === 'left' ? '1px solid #45475a' : undefined,
518
- display: 'flex',
519
- flexDirection: 'column',
520
- overflow: 'hidden',
521
- backgroundColor: '#1e1e2e',
522
- }, children: [jsxRuntime.jsx("div", { style: {
523
- padding: '4px 8px',
524
- fontSize: 11,
525
- fontWeight: 'bold',
526
- backgroundColor: '#181825',
527
- borderBottom: '1px solid #313244',
528
- textTransform: 'uppercase',
529
- letterSpacing: 1,
530
- }, children: title }), jsxRuntime.jsx("div", { style: { flex: 1, overflow: 'auto', padding: 4 }, children: children })] }));
694
+ const isHorizontal = position === "bottom";
695
+ return /* @__PURE__ */ jsxRuntime.jsxs(
696
+ "div",
697
+ {
698
+ style: {
699
+ width: isHorizontal ? "100%" : width,
700
+ height: isHorizontal ? height : void 0,
701
+ flex: isHorizontal ? void 0 : void 0,
702
+ borderLeft: position === "right" ? "1px solid #45475a" : void 0,
703
+ borderRight: position === "left" ? "1px solid #45475a" : void 0,
704
+ display: "flex",
705
+ flexDirection: "column",
706
+ overflow: "hidden",
707
+ backgroundColor: "#1e1e2e"
708
+ },
709
+ children: [
710
+ /* @__PURE__ */ jsxRuntime.jsx(
711
+ "div",
712
+ {
713
+ style: {
714
+ padding: "4px 8px",
715
+ fontSize: 11,
716
+ fontWeight: "bold",
717
+ backgroundColor: "#181825",
718
+ borderBottom: "1px solid #313244",
719
+ textTransform: "uppercase",
720
+ letterSpacing: 1
721
+ },
722
+ children: title
723
+ }
724
+ ),
725
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, overflow: "auto", padding: 4 }, children })
726
+ ]
727
+ }
728
+ );
531
729
  };
532
730
  const HierarchyPanel = () => {
533
- const { t: tr } = useTranslation.useTranslation('engine');
534
- const { world, state, dispatch, createNewEntity, refreshWorld, addLog } = useEditor();
535
- const entities = world.allEntities().filter((e) => e.parent == null);
536
- const getIcon = (id) => {
537
- if (world.hasComponent(id, 'CameraTarget')) {
538
- return 'camera';
539
- }
540
- if (world.hasComponent(id, 'Sprite')) {
541
- return 'image';
542
- }
543
- if (world.hasComponent(id, 'Text')) {
544
- return 'type';
545
- }
546
- if (world.hasComponent(id, 'Shape')) {
547
- return 'square';
548
- }
549
- if (world.hasComponent(id, 'ParticleEmitter')) {
550
- return 'star';
551
- }
552
- if (world.hasComponent(id, 'AudioSource')) {
553
- return 'volume';
731
+ const { t: tr } = useTranslation.useTranslation("engine");
732
+ const { world, state, dispatch, createNewEntity, refreshWorld, addLog } = useEditor();
733
+ const entities = world.allEntities().filter((e) => e.parent == null);
734
+ const getIcon = (id) => {
735
+ if (world.hasComponent(id, "CameraTarget")) {
736
+ return "camera";
737
+ }
738
+ if (world.hasComponent(id, "Sprite")) {
739
+ return "image";
740
+ }
741
+ if (world.hasComponent(id, "Text")) {
742
+ return "type";
743
+ }
744
+ if (world.hasComponent(id, "Shape")) {
745
+ return "square";
746
+ }
747
+ if (world.hasComponent(id, "ParticleEmitter")) {
748
+ return "star";
749
+ }
750
+ if (world.hasComponent(id, "AudioSource")) {
751
+ return "volume";
752
+ }
753
+ return "circle";
754
+ };
755
+ const renderEntity = (e, depth) => {
756
+ const isSelected = state.selectedEntities.includes(e.id);
757
+ const children = e.children.map((cid) => world.getEntity(cid)).filter(Boolean);
758
+ return /* @__PURE__ */ jsxRuntime.jsxs(React.Fragment, { children: [
759
+ /* @__PURE__ */ jsxRuntime.jsxs(
760
+ "div",
761
+ {
762
+ role: "button",
763
+ tabIndex: 0,
764
+ onKeyDown: ui.clickOnActivationKey,
765
+ onClick: () => dispatch({ type: "SELECT", entities: [e.id] }),
766
+ style: {
767
+ padding: "2px 6px",
768
+ paddingLeft: 6 + depth * 16,
769
+ cursor: "pointer",
770
+ backgroundColor: isSelected ? "#45475a" : "transparent",
771
+ borderRadius: 2,
772
+ display: "flex",
773
+ alignItems: "center",
774
+ gap: 4,
775
+ fontSize: 12
776
+ },
777
+ children: [
778
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { width: 14, display: "inline-flex", justifyContent: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx(ui.NiceIcon, { name: getIcon(e.id), size: 12 }) }),
779
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { flex: 1 }, children: e.name }),
780
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { opacity: 0.3, fontSize: 10 }, children: [
781
+ "#",
782
+ e.id
783
+ ] })
784
+ ]
554
785
  }
555
- return 'circle';
556
- };
557
- const renderEntity = (e, depth) => {
558
- const isSelected = state.selectedEntities.includes(e.id);
559
- const children = e.children.map((cid) => world.getEntity(cid)).filter(Boolean);
560
- return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsxs("div", { role: "button", tabIndex: 0, onKeyDown: ui.clickOnActivationKey, onClick: () => dispatch({ type: 'SELECT', entities: [e.id] }), style: {
561
- padding: '2px 6px',
562
- paddingLeft: 6 + depth * 16,
563
- cursor: 'pointer',
564
- backgroundColor: isSelected ? '#45475a' : 'transparent',
565
- borderRadius: 2,
566
- display: 'flex',
567
- alignItems: 'center',
568
- gap: 4,
569
- fontSize: 12,
570
- }, children: [jsxRuntime.jsx("span", { style: { width: 14, display: 'inline-flex', justifyContent: 'center' }, children: jsxRuntime.jsx(ui.NiceIcon, { name: getIcon(e.id), size: 12 }) }), jsxRuntime.jsx("span", { style: { flex: 1 }, children: e.name }), jsxRuntime.jsxs("span", { style: { opacity: 0.3, fontSize: 10 }, children: ["#", e.id] })] }), children.map((c) => renderEntity(c, depth + 1))] }, e.id));
571
- };
572
- return (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsxs("div", { style: {
573
- padding: '2px 6px',
574
- fontSize: 10,
575
- display: 'flex',
576
- gap: 4,
577
- borderBottom: '1px solid #313244',
578
- marginBottom: 4,
579
- }, children: [jsxRuntime.jsx("button", { onClick: () => createNewEntity('Entity'), style: {
580
- background: 'transparent',
581
- border: 'none',
582
- color: '#a6adc8',
583
- cursor: 'pointer',
584
- fontSize: 10,
585
- }, title: tr('editor.assets.addEntity'), children: "+" }), jsxRuntime.jsx("button", { onClick: () => {
586
- if (state.selectedEntities.length > 0) {
587
- const id = state.selectedEntities[0];
588
- const child = world.createEntity('Child');
589
- world.addComponent(child.id, 'Transform');
590
- world.setParent(child.id, id);
591
- dispatch({ type: 'SELECT', entities: [child.id] });
592
- dispatch({ type: 'MARK_DIRTY' });
593
- refreshWorld();
594
- addLog('info', `Created child entity (#${child.id})`);
595
- }
596
- }, style: {
597
- background: 'transparent',
598
- border: 'none',
599
- color: '#a6adc8',
600
- cursor: 'pointer',
601
- fontSize: 10,
602
- }, title: tr('editor.assets.addChild'), disabled: state.selectedEntities.length === 0, children: jsxRuntime.jsx(ui.NiceIcon, { name: "plus", size: 12 }) }), jsxRuntime.jsx("span", { style: { flex: 1 } }), jsxRuntime.jsxs("span", { style: { color: '#6c7086' }, children: [world.entityCount, " entities"] })] }), entities.map((e) => renderEntity(e, 0))] }));
786
+ ),
787
+ children.map((c) => renderEntity(c, depth + 1))
788
+ ] }, e.id);
789
+ };
790
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
791
+ /* @__PURE__ */ jsxRuntime.jsxs(
792
+ "div",
793
+ {
794
+ style: {
795
+ padding: "2px 6px",
796
+ fontSize: 10,
797
+ display: "flex",
798
+ gap: 4,
799
+ borderBottom: "1px solid #313244",
800
+ marginBottom: 4
801
+ },
802
+ children: [
803
+ /* @__PURE__ */ jsxRuntime.jsx(
804
+ "button",
805
+ {
806
+ onClick: () => createNewEntity("Entity"),
807
+ style: {
808
+ background: "transparent",
809
+ border: "none",
810
+ color: "#a6adc8",
811
+ cursor: "pointer",
812
+ fontSize: 10
813
+ },
814
+ title: tr("editor.assets.addEntity"),
815
+ children: "+"
816
+ }
817
+ ),
818
+ /* @__PURE__ */ jsxRuntime.jsx(
819
+ "button",
820
+ {
821
+ onClick: () => {
822
+ if (state.selectedEntities.length > 0) {
823
+ const id = state.selectedEntities[0];
824
+ const child = world.createEntity("Child");
825
+ world.addComponent(child.id, "Transform");
826
+ world.setParent(child.id, id);
827
+ dispatch({ type: "SELECT", entities: [child.id] });
828
+ dispatch({ type: "MARK_DIRTY" });
829
+ refreshWorld();
830
+ addLog("info", `Created child entity (#${child.id})`);
831
+ }
832
+ },
833
+ style: {
834
+ background: "transparent",
835
+ border: "none",
836
+ color: "#a6adc8",
837
+ cursor: "pointer",
838
+ fontSize: 10
839
+ },
840
+ title: tr("editor.assets.addChild"),
841
+ disabled: state.selectedEntities.length === 0,
842
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.NiceIcon, { name: "plus", size: 12 })
843
+ }
844
+ ),
845
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { flex: 1 } }),
846
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { color: "#6c7086" }, children: [
847
+ world.entityCount,
848
+ " entities"
849
+ ] })
850
+ ]
851
+ }
852
+ ),
853
+ entities.map((e) => renderEntity(e, 0))
854
+ ] });
603
855
  };
604
- /* ── Inspector sub-editors ─────────────────────────────────────── */
605
856
  const fieldStyle = {
606
- width: '100%',
607
- background: '#313244',
608
- color: '#cdd6f4',
609
- border: '1px solid #45475a',
610
- borderRadius: 3,
611
- padding: '2px 4px',
612
- fontSize: 11,
857
+ width: "100%",
858
+ background: "#313244",
859
+ color: "#cdd6f4",
860
+ border: "1px solid #45475a",
861
+ borderRadius: 3,
862
+ padding: "2px 4px",
863
+ fontSize: 11
613
864
  };
614
- const NumField = ({ label, value, onChange, step = 1 }) => (jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4, marginBottom: 2 }, children: [jsxRuntime.jsx("span", { style: { fontSize: 10, minWidth: 50, color: '#a6adc8' }, children: label }), jsxRuntime.jsx("input", { type: "number", value: Math.round(value * 1000) / 1000, step: step, onChange: (e) => onChange(Number(e.target.value)), style: { ...fieldStyle, flex: 1 } })] }));
615
- const ColField = ({ label, color, onChange }) => (jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4, marginBottom: 2 }, children: [jsxRuntime.jsx("span", { style: { fontSize: 10, minWidth: 50, color: '#a6adc8' }, children: label }), jsxRuntime.jsx("input", { type: "color", value: colorToHex(color), onChange: (e) => onChange(cssToColor(e.target.value)), style: { width: 28, height: 20, border: 'none', padding: 0, cursor: 'pointer' } }), jsxRuntime.jsx("span", { style: { fontSize: 9, color: '#6c7086' }, children: colorToHex(color) })] }));
616
- const TransformEditor = ({ entityId }) => {
617
- const { world, refreshWorld, dispatch } = useEditor();
618
- const t = world.getComponent(entityId, 'Transform');
619
- if (!t) {
620
- return null;
865
+ const NumField = ({ label, value, onChange, step = 1 }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4, marginBottom: 2 }, children: [
866
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10, minWidth: 50, color: "#a6adc8" }, children: label }),
867
+ /* @__PURE__ */ jsxRuntime.jsx(
868
+ "input",
869
+ {
870
+ type: "number",
871
+ value: Math.round(value * 1e3) / 1e3,
872
+ step,
873
+ onChange: (e) => onChange(Number(e.target.value)),
874
+ style: { ...fieldStyle, flex: 1 }
621
875
  }
622
- const update = (fn) => {
623
- fn();
624
- dispatch({ type: 'MARK_DIRTY' });
625
- refreshWorld();
626
- };
627
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(NumField, { label: "Pos X", value: t.position.x, onChange: (v) => update(() => {
628
- t.position.x = v;
629
- }) }), jsxRuntime.jsx(NumField, { label: "Pos Y", value: t.position.y, onChange: (v) => update(() => {
630
- t.position.y = v;
631
- }) }), jsxRuntime.jsx(NumField, { label: "Rotation", value: t.rotation * (180 / Math.PI), step: 5, onChange: (v) => update(() => {
632
- t.rotation = v * (Math.PI / 180);
633
- }) }), jsxRuntime.jsx(NumField, { label: "Scale X", value: t.scale.x, step: 0.1, onChange: (v) => update(() => {
634
- t.scale.x = v;
635
- }) }), jsxRuntime.jsx(NumField, { label: "Scale Y", value: t.scale.y, step: 0.1, onChange: (v) => update(() => {
636
- t.scale.y = v;
637
- }) })] }));
876
+ )
877
+ ] });
878
+ const ColField = ({ label, color, onChange }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4, marginBottom: 2 }, children: [
879
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10, minWidth: 50, color: "#a6adc8" }, children: label }),
880
+ /* @__PURE__ */ jsxRuntime.jsx(
881
+ "input",
882
+ {
883
+ type: "color",
884
+ value: colorToHex(color),
885
+ onChange: (e) => onChange(cssToColor(e.target.value)),
886
+ style: { width: 28, height: 20, border: "none", padding: 0, cursor: "pointer" }
887
+ }
888
+ ),
889
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 9, color: "#6c7086" }, children: colorToHex(color) })
890
+ ] });
891
+ const TransformEditor = ({ entityId }) => {
892
+ const { world, refreshWorld, dispatch } = useEditor();
893
+ const t = world.getComponent(entityId, "Transform");
894
+ if (!t) {
895
+ return null;
896
+ }
897
+ const update = (fn) => {
898
+ fn();
899
+ dispatch({ type: "MARK_DIRTY" });
900
+ refreshWorld();
901
+ };
902
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
903
+ /* @__PURE__ */ jsxRuntime.jsx(
904
+ NumField,
905
+ {
906
+ label: "Pos X",
907
+ value: t.position.x,
908
+ onChange: (v) => update(() => {
909
+ t.position.x = v;
910
+ })
911
+ }
912
+ ),
913
+ /* @__PURE__ */ jsxRuntime.jsx(
914
+ NumField,
915
+ {
916
+ label: "Pos Y",
917
+ value: t.position.y,
918
+ onChange: (v) => update(() => {
919
+ t.position.y = v;
920
+ })
921
+ }
922
+ ),
923
+ /* @__PURE__ */ jsxRuntime.jsx(
924
+ NumField,
925
+ {
926
+ label: "Rotation",
927
+ value: t.rotation * (180 / Math.PI),
928
+ step: 5,
929
+ onChange: (v) => update(() => {
930
+ t.rotation = v * (Math.PI / 180);
931
+ })
932
+ }
933
+ ),
934
+ /* @__PURE__ */ jsxRuntime.jsx(
935
+ NumField,
936
+ {
937
+ label: "Scale X",
938
+ value: t.scale.x,
939
+ step: 0.1,
940
+ onChange: (v) => update(() => {
941
+ t.scale.x = v;
942
+ })
943
+ }
944
+ ),
945
+ /* @__PURE__ */ jsxRuntime.jsx(
946
+ NumField,
947
+ {
948
+ label: "Scale Y",
949
+ value: t.scale.y,
950
+ step: 0.1,
951
+ onChange: (v) => update(() => {
952
+ t.scale.y = v;
953
+ })
954
+ }
955
+ )
956
+ ] });
638
957
  };
639
958
  const ShapeEditor = ({ entityId }) => {
640
- const { t: tr } = useTranslation.useTranslation('engine');
641
- const { world, refreshWorld, dispatch } = useEditor();
642
- const s = world.getComponent(entityId, 'Shape');
643
- if (!s) {
644
- return null;
645
- }
646
- const update = (fn) => {
647
- fn();
648
- dispatch({ type: 'MARK_DIRTY' });
649
- refreshWorld();
650
- };
651
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4, marginBottom: 2 }, children: [jsxRuntime.jsx("span", { style: { fontSize: 10, minWidth: 50, color: '#a6adc8' }, children: tr('editor.fields.shape') }), jsxRuntime.jsxs("select", { value: s.shape, onChange: (e) => update(() => {
652
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- mutates discriminated-union variant; consumer narrows by `s.shape`
653
- s.shape = e.target.value;
654
- }), style: { ...fieldStyle, flex: 1 }, children: [jsxRuntime.jsx("option", { value: "rect", children: tr('editor.shape.rect') }), jsxRuntime.jsx("option", { value: "circle", children: tr('editor.shape.circle') }), jsxRuntime.jsx("option", { value: "polygon", children: tr('editor.shape.polygon') }), jsxRuntime.jsx("option", { value: "line", children: tr('editor.shape.line') })] })] }), s.shape !== 'circle' && (jsxRuntime.jsx(NumField, { label: "Width", value: s.width, onChange: (v) => update(() => {
655
- s.width = v;
656
- }) })), s.shape !== 'circle' && (jsxRuntime.jsx(NumField, { label: "Height", value: s.height, onChange: (v) => update(() => {
657
- s.height = v;
658
- }) })), s.shape === 'circle' && (jsxRuntime.jsx(NumField, { label: "Radius", value: s.radius, onChange: (v) => update(() => {
659
- s.radius = v;
660
- }) })), jsxRuntime.jsx(ColField, { label: "Fill", color: s.fillColor, onChange: (c) => update(() => {
661
- Object.assign(s.fillColor, c);
662
- }) }), jsxRuntime.jsx(ColField, { label: "Stroke", color: s.strokeColor, onChange: (c) => update(() => {
663
- Object.assign(s.strokeColor, c);
664
- }) }), jsxRuntime.jsx(NumField, { label: "Stroke W", value: s.strokeWidth, onChange: (v) => update(() => {
665
- s.strokeWidth = v;
666
- }) })] }));
959
+ const { t: tr } = useTranslation.useTranslation("engine");
960
+ const { world, refreshWorld, dispatch } = useEditor();
961
+ const s = world.getComponent(entityId, "Shape");
962
+ if (!s) {
963
+ return null;
964
+ }
965
+ const update = (fn) => {
966
+ fn();
967
+ dispatch({ type: "MARK_DIRTY" });
968
+ refreshWorld();
969
+ };
970
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
971
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4, marginBottom: 2 }, children: [
972
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10, minWidth: 50, color: "#a6adc8" }, children: tr("editor.fields.shape") }),
973
+ /* @__PURE__ */ jsxRuntime.jsxs(
974
+ "select",
975
+ {
976
+ value: s.shape,
977
+ onChange: (e) => update(() => {
978
+ s.shape = e.target.value;
979
+ }),
980
+ style: { ...fieldStyle, flex: 1 },
981
+ children: [
982
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "rect", children: tr("editor.shape.rect") }),
983
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "circle", children: tr("editor.shape.circle") }),
984
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "polygon", children: tr("editor.shape.polygon") }),
985
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "line", children: tr("editor.shape.line") })
986
+ ]
987
+ }
988
+ )
989
+ ] }),
990
+ s.shape !== "circle" && /* @__PURE__ */ jsxRuntime.jsx(
991
+ NumField,
992
+ {
993
+ label: "Width",
994
+ value: s.width,
995
+ onChange: (v) => update(() => {
996
+ s.width = v;
997
+ })
998
+ }
999
+ ),
1000
+ s.shape !== "circle" && /* @__PURE__ */ jsxRuntime.jsx(
1001
+ NumField,
1002
+ {
1003
+ label: "Height",
1004
+ value: s.height,
1005
+ onChange: (v) => update(() => {
1006
+ s.height = v;
1007
+ })
1008
+ }
1009
+ ),
1010
+ s.shape === "circle" && /* @__PURE__ */ jsxRuntime.jsx(
1011
+ NumField,
1012
+ {
1013
+ label: "Radius",
1014
+ value: s.radius,
1015
+ onChange: (v) => update(() => {
1016
+ s.radius = v;
1017
+ })
1018
+ }
1019
+ ),
1020
+ /* @__PURE__ */ jsxRuntime.jsx(
1021
+ ColField,
1022
+ {
1023
+ label: "Fill",
1024
+ color: s.fillColor,
1025
+ onChange: (c) => update(() => {
1026
+ Object.assign(s.fillColor, c);
1027
+ })
1028
+ }
1029
+ ),
1030
+ /* @__PURE__ */ jsxRuntime.jsx(
1031
+ ColField,
1032
+ {
1033
+ label: "Stroke",
1034
+ color: s.strokeColor,
1035
+ onChange: (c) => update(() => {
1036
+ Object.assign(s.strokeColor, c);
1037
+ })
1038
+ }
1039
+ ),
1040
+ /* @__PURE__ */ jsxRuntime.jsx(
1041
+ NumField,
1042
+ {
1043
+ label: "Stroke W",
1044
+ value: s.strokeWidth,
1045
+ onChange: (v) => update(() => {
1046
+ s.strokeWidth = v;
1047
+ })
1048
+ }
1049
+ )
1050
+ ] });
667
1051
  };
668
1052
  const SpriteEditor = ({ entityId }) => {
669
- const { t: tr } = useTranslation.useTranslation('engine');
670
- const { world, refreshWorld, dispatch } = useEditor();
671
- const s = world.getComponent(entityId, 'Sprite');
672
- if (!s) {
673
- return null;
674
- }
675
- const update = (fn) => {
676
- fn();
677
- dispatch({ type: 'MARK_DIRTY' });
678
- refreshWorld();
679
- };
680
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(NumField, { label: "Width", value: s.width, onChange: (v) => update(() => {
681
- s.width = v;
682
- }) }), jsxRuntime.jsx(NumField, { label: "Height", value: s.height, onChange: (v) => update(() => {
683
- s.height = v;
684
- }) }), jsxRuntime.jsx(ColField, { label: "Tint", color: s.tint, onChange: (c) => update(() => {
685
- Object.assign(s.tint, c);
686
- }) }), jsxRuntime.jsx(NumField, { label: "Opacity", value: s.opacity, step: 0.1, onChange: (v) => update(() => {
687
- s.opacity = Math.max(0, Math.min(1, v));
688
- }) }), jsxRuntime.jsx(NumField, { label: "Layer", value: s.layer, step: 1, onChange: (v) => update(() => {
689
- s.layer = v;
690
- }) }), jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4, marginBottom: 2 }, children: [jsxRuntime.jsx("span", { style: { fontSize: 10, minWidth: 50, color: '#a6adc8' }, children: tr('editor.fields.visible') }), jsxRuntime.jsx("input", { type: "checkbox", checked: s.visible, onChange: (e) => update(() => {
691
- s.visible = e.target.checked;
692
- }) })] })] }));
1053
+ const { t: tr } = useTranslation.useTranslation("engine");
1054
+ const { world, refreshWorld, dispatch } = useEditor();
1055
+ const s = world.getComponent(entityId, "Sprite");
1056
+ if (!s) {
1057
+ return null;
1058
+ }
1059
+ const update = (fn) => {
1060
+ fn();
1061
+ dispatch({ type: "MARK_DIRTY" });
1062
+ refreshWorld();
1063
+ };
1064
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1065
+ /* @__PURE__ */ jsxRuntime.jsx(
1066
+ NumField,
1067
+ {
1068
+ label: "Width",
1069
+ value: s.width,
1070
+ onChange: (v) => update(() => {
1071
+ s.width = v;
1072
+ })
1073
+ }
1074
+ ),
1075
+ /* @__PURE__ */ jsxRuntime.jsx(
1076
+ NumField,
1077
+ {
1078
+ label: "Height",
1079
+ value: s.height,
1080
+ onChange: (v) => update(() => {
1081
+ s.height = v;
1082
+ })
1083
+ }
1084
+ ),
1085
+ /* @__PURE__ */ jsxRuntime.jsx(
1086
+ ColField,
1087
+ {
1088
+ label: "Tint",
1089
+ color: s.tint,
1090
+ onChange: (c) => update(() => {
1091
+ Object.assign(s.tint, c);
1092
+ })
1093
+ }
1094
+ ),
1095
+ /* @__PURE__ */ jsxRuntime.jsx(
1096
+ NumField,
1097
+ {
1098
+ label: "Opacity",
1099
+ value: s.opacity,
1100
+ step: 0.1,
1101
+ onChange: (v) => update(() => {
1102
+ s.opacity = Math.max(0, Math.min(1, v));
1103
+ })
1104
+ }
1105
+ ),
1106
+ /* @__PURE__ */ jsxRuntime.jsx(
1107
+ NumField,
1108
+ {
1109
+ label: "Layer",
1110
+ value: s.layer,
1111
+ step: 1,
1112
+ onChange: (v) => update(() => {
1113
+ s.layer = v;
1114
+ })
1115
+ }
1116
+ ),
1117
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4, marginBottom: 2 }, children: [
1118
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10, minWidth: 50, color: "#a6adc8" }, children: tr("editor.fields.visible") }),
1119
+ /* @__PURE__ */ jsxRuntime.jsx(
1120
+ "input",
1121
+ {
1122
+ type: "checkbox",
1123
+ checked: s.visible,
1124
+ onChange: (e) => update(() => {
1125
+ s.visible = e.target.checked;
1126
+ })
1127
+ }
1128
+ )
1129
+ ] })
1130
+ ] });
693
1131
  };
694
1132
  const TextEditor = ({ entityId }) => {
695
- const { t: tr } = useTranslation.useTranslation('engine');
696
- const { world, refreshWorld, dispatch } = useEditor();
697
- const t = world.getComponent(entityId, 'Text');
698
- if (!t) {
699
- return null;
700
- }
701
- const update = (fn) => {
702
- fn();
703
- dispatch({ type: 'MARK_DIRTY' });
704
- refreshWorld();
705
- };
706
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { style: { marginBottom: 2 }, children: [jsxRuntime.jsx("span", { style: { fontSize: 10, color: '#a6adc8' }, children: tr('editor.fields.text') }), jsxRuntime.jsx("input", { type: "text", value: t.text, onChange: (e) => update(() => {
707
- t.text = e.target.value;
708
- }), style: { ...fieldStyle, width: '100%' } })] }), jsxRuntime.jsx(NumField, { label: "Font Size", value: t.fontSize, onChange: (v) => update(() => {
709
- t.fontSize = v;
710
- }) }), jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4, marginBottom: 2 }, children: [jsxRuntime.jsx("span", { style: { fontSize: 10, minWidth: 50, color: '#a6adc8' }, children: tr('editor.fields.align') }), jsxRuntime.jsxs("select", { value: t.align, onChange: (e) => update(() => {
711
- t.align = e.target.value;
712
- }), style: { ...fieldStyle, flex: 1 }, children: [jsxRuntime.jsx("option", { value: "left", children: tr('editor.align.left') }), jsxRuntime.jsx("option", { value: "center", children: tr('editor.align.center') }), jsxRuntime.jsx("option", { value: "right", children: tr('editor.align.right') })] })] }), jsxRuntime.jsx(ColField, { label: "Color", color: t.color, onChange: (c) => update(() => {
713
- Object.assign(t.color, c);
714
- }) })] }));
1133
+ const { t: tr } = useTranslation.useTranslation("engine");
1134
+ const { world, refreshWorld, dispatch } = useEditor();
1135
+ const t = world.getComponent(entityId, "Text");
1136
+ if (!t) {
1137
+ return null;
1138
+ }
1139
+ const update = (fn) => {
1140
+ fn();
1141
+ dispatch({ type: "MARK_DIRTY" });
1142
+ refreshWorld();
1143
+ };
1144
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1145
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 2 }, children: [
1146
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10, color: "#a6adc8" }, children: tr("editor.fields.text") }),
1147
+ /* @__PURE__ */ jsxRuntime.jsx(
1148
+ "input",
1149
+ {
1150
+ type: "text",
1151
+ value: t.text,
1152
+ onChange: (e) => update(() => {
1153
+ t.text = e.target.value;
1154
+ }),
1155
+ style: { ...fieldStyle, width: "100%" }
1156
+ }
1157
+ )
1158
+ ] }),
1159
+ /* @__PURE__ */ jsxRuntime.jsx(
1160
+ NumField,
1161
+ {
1162
+ label: "Font Size",
1163
+ value: t.fontSize,
1164
+ onChange: (v) => update(() => {
1165
+ t.fontSize = v;
1166
+ })
1167
+ }
1168
+ ),
1169
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4, marginBottom: 2 }, children: [
1170
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10, minWidth: 50, color: "#a6adc8" }, children: tr("editor.fields.align") }),
1171
+ /* @__PURE__ */ jsxRuntime.jsxs(
1172
+ "select",
1173
+ {
1174
+ value: t.align,
1175
+ onChange: (e) => update(() => {
1176
+ t.align = e.target.value;
1177
+ }),
1178
+ style: { ...fieldStyle, flex: 1 },
1179
+ children: [
1180
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "left", children: tr("editor.align.left") }),
1181
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "center", children: tr("editor.align.center") }),
1182
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "right", children: tr("editor.align.right") })
1183
+ ]
1184
+ }
1185
+ )
1186
+ ] }),
1187
+ /* @__PURE__ */ jsxRuntime.jsx(
1188
+ ColField,
1189
+ {
1190
+ label: "Color",
1191
+ color: t.color,
1192
+ onChange: (c) => update(() => {
1193
+ Object.assign(t.color, c);
1194
+ })
1195
+ }
1196
+ )
1197
+ ] });
715
1198
  };
716
1199
  const RigidBodyEditor = ({ entityId }) => {
717
- const { t: tr } = useTranslation.useTranslation('engine');
718
- const { world, refreshWorld, dispatch } = useEditor();
719
- const rb = world.getComponent(entityId, 'RigidBody2D');
720
- if (!rb) {
721
- return null;
722
- }
723
- const update = (fn) => {
724
- fn();
725
- dispatch({ type: 'MARK_DIRTY' });
726
- refreshWorld();
727
- };
728
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4, marginBottom: 2 }, children: [jsxRuntime.jsx("span", { style: { fontSize: 10, minWidth: 50, color: '#a6adc8' }, children: tr('editor.fields.body') }), jsxRuntime.jsxs("select", { value: rb.bodyType, onChange: (e) => update(() => {
729
- rb.bodyType = e.target.value;
730
- }), style: { ...fieldStyle, flex: 1 }, children: [jsxRuntime.jsx("option", { value: "dynamic", children: tr('editor.body.dynamic') }), jsxRuntime.jsx("option", { value: "static", children: tr('editor.body.static') }), jsxRuntime.jsx("option", { value: "kinematic", children: tr('editor.body.kinematic') })] })] }), jsxRuntime.jsx(NumField, { label: "Mass", value: rb.mass, step: 0.1, onChange: (v) => update(() => {
731
- rb.mass = v;
732
- }) }), jsxRuntime.jsx(NumField, { label: "Drag", value: rb.drag, step: 0.01, onChange: (v) => update(() => {
733
- rb.drag = v;
734
- }) }), jsxRuntime.jsx(NumField, { label: "Bounce", value: rb.restitution, step: 0.05, onChange: (v) => update(() => {
735
- rb.restitution = v;
736
- }) }), jsxRuntime.jsx(NumField, { label: "Friction", value: rb.friction, step: 0.05, onChange: (v) => update(() => {
737
- rb.friction = v;
738
- }) }), jsxRuntime.jsx(NumField, { label: "Gravity", value: rb.gravityScale, step: 0.1, onChange: (v) => update(() => {
739
- rb.gravityScale = v;
740
- }) })] }));
1200
+ const { t: tr } = useTranslation.useTranslation("engine");
1201
+ const { world, refreshWorld, dispatch } = useEditor();
1202
+ const rb = world.getComponent(entityId, "RigidBody2D");
1203
+ if (!rb) {
1204
+ return null;
1205
+ }
1206
+ const update = (fn) => {
1207
+ fn();
1208
+ dispatch({ type: "MARK_DIRTY" });
1209
+ refreshWorld();
1210
+ };
1211
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1212
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4, marginBottom: 2 }, children: [
1213
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10, minWidth: 50, color: "#a6adc8" }, children: tr("editor.fields.body") }),
1214
+ /* @__PURE__ */ jsxRuntime.jsxs(
1215
+ "select",
1216
+ {
1217
+ value: rb.bodyType,
1218
+ onChange: (e) => update(() => {
1219
+ rb.bodyType = e.target.value;
1220
+ }),
1221
+ style: { ...fieldStyle, flex: 1 },
1222
+ children: [
1223
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "dynamic", children: tr("editor.body.dynamic") }),
1224
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "static", children: tr("editor.body.static") }),
1225
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "kinematic", children: tr("editor.body.kinematic") })
1226
+ ]
1227
+ }
1228
+ )
1229
+ ] }),
1230
+ /* @__PURE__ */ jsxRuntime.jsx(
1231
+ NumField,
1232
+ {
1233
+ label: "Mass",
1234
+ value: rb.mass,
1235
+ step: 0.1,
1236
+ onChange: (v) => update(() => {
1237
+ rb.mass = v;
1238
+ })
1239
+ }
1240
+ ),
1241
+ /* @__PURE__ */ jsxRuntime.jsx(
1242
+ NumField,
1243
+ {
1244
+ label: "Drag",
1245
+ value: rb.drag,
1246
+ step: 0.01,
1247
+ onChange: (v) => update(() => {
1248
+ rb.drag = v;
1249
+ })
1250
+ }
1251
+ ),
1252
+ /* @__PURE__ */ jsxRuntime.jsx(
1253
+ NumField,
1254
+ {
1255
+ label: "Bounce",
1256
+ value: rb.restitution,
1257
+ step: 0.05,
1258
+ onChange: (v) => update(() => {
1259
+ rb.restitution = v;
1260
+ })
1261
+ }
1262
+ ),
1263
+ /* @__PURE__ */ jsxRuntime.jsx(
1264
+ NumField,
1265
+ {
1266
+ label: "Friction",
1267
+ value: rb.friction,
1268
+ step: 0.05,
1269
+ onChange: (v) => update(() => {
1270
+ rb.friction = v;
1271
+ })
1272
+ }
1273
+ ),
1274
+ /* @__PURE__ */ jsxRuntime.jsx(
1275
+ NumField,
1276
+ {
1277
+ label: "Gravity",
1278
+ value: rb.gravityScale,
1279
+ step: 0.1,
1280
+ onChange: (v) => update(() => {
1281
+ rb.gravityScale = v;
1282
+ })
1283
+ }
1284
+ )
1285
+ ] });
741
1286
  };
742
1287
  const ADDABLE_COMPONENTS = [
743
- 'Transform',
744
- 'Sprite',
745
- 'Shape',
746
- 'Collider2D',
747
- 'RigidBody2D',
748
- 'Text',
749
- 'Animator',
750
- 'Script',
751
- 'AudioSource',
752
- 'ParticleEmitter',
753
- 'CameraTarget',
1288
+ "Transform",
1289
+ "Sprite",
1290
+ "Shape",
1291
+ "Collider2D",
1292
+ "RigidBody2D",
1293
+ "Text",
1294
+ "Animator",
1295
+ "Script",
1296
+ "AudioSource",
1297
+ "ParticleEmitter",
1298
+ "CameraTarget"
754
1299
  ];
755
1300
  const InspectorPanel = () => {
756
- const { t: tr } = useTranslation.useTranslation('engine');
757
- const { state, world, dispatch, refreshWorld, addLog } = useEditor();
758
- const [showAddComp, setShowAddComp] = React.useState(false);
759
- if (state.selectedEntities.length === 0) {
760
- return jsxRuntime.jsx("div", { style: { opacity: 0.5, padding: 8 }, children: tr('editor.messages.noSelection') });
761
- }
762
- const entityId = state.selectedEntities[0];
763
- const entity = world.getEntity(entityId);
764
- if (!entity) {
765
- return null;
766
- }
767
- const compTypes = ADDABLE_COMPONENTS;
768
- const presentComponents = compTypes.filter((type) => world.hasComponent(entityId, type));
769
- const componentEditors = {
770
- Transform: TransformEditor,
771
- Shape: ShapeEditor,
772
- Sprite: SpriteEditor,
773
- Text: TextEditor,
774
- RigidBody2D: RigidBodyEditor,
775
- };
776
- return (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsxs("div", { style: { padding: '4px 0', marginBottom: 4, borderBottom: '1px solid #313244' }, children: [jsxRuntime.jsx("input", { type: "text", value: entity.name, onChange: (e) => {
777
- entity.name = e.target.value;
778
- dispatch({ type: 'MARK_DIRTY' });
779
- refreshWorld();
780
- }, style: { ...fieldStyle, fontWeight: 'bold', fontSize: 13 } }), jsxRuntime.jsxs("span", { style: { fontSize: 9, color: '#6c7086' }, children: ["ID: ", entityId] })] }), presentComponents.map((type) => {
781
- const Editor = componentEditors[type];
782
- return (jsxRuntime.jsxs("div", { style: { marginBottom: 8, borderTop: '1px solid #313244', paddingTop: 4 }, children: [jsxRuntime.jsxs("div", { style: {
783
- display: 'flex',
784
- alignItems: 'center',
785
- justifyContent: 'space-between',
786
- marginBottom: 4,
787
- }, children: [jsxRuntime.jsx("span", { style: {
788
- fontSize: 11,
789
- fontWeight: 'bold',
790
- textTransform: 'uppercase',
791
- color: '#89b4fa',
792
- }, children: type }), type !== 'Transform' && (jsxRuntime.jsx("button", { onClick: () => {
793
- world.removeComponent(entityId, type);
794
- dispatch({ type: 'MARK_DIRTY' });
795
- refreshWorld();
796
- addLog('info', `Removed ${type} from #${entityId}`);
797
- }, style: {
798
- background: 'transparent',
799
- border: 'none',
800
- color: '#f38ba8',
801
- cursor: 'pointer',
802
- fontSize: 10,
803
- }, title: tr('editor.assets.removeComponent', { type }), children: jsxRuntime.jsx(ui.NiceIcon, { name: "close", size: 10 }) }))] }), Editor ? (jsxRuntime.jsx(Editor, { entityId: entityId })) : (jsxRuntime.jsx("pre", { style: { fontSize: 10, opacity: 0.6, whiteSpace: 'pre-wrap', margin: 0 }, children: JSON.stringify(world.getComponent(entityId, type), null, 2) }))] }, type));
804
- }), jsxRuntime.jsxs("div", { style: { marginTop: 8, position: 'relative' }, children: [jsxRuntime.jsx("button", { onClick: () => setShowAddComp(!showAddComp), style: {
805
- width: '100%',
806
- padding: '4px 8px',
807
- background: '#313244',
808
- border: '1px solid #45475a',
809
- color: '#cdd6f4',
810
- borderRadius: 4,
811
- cursor: 'pointer',
812
- fontSize: 11,
813
- }, children: "+ Add Component" }), showAddComp && (jsxRuntime.jsx("div", { style: {
814
- position: 'absolute',
815
- bottom: '100%',
816
- left: 0,
817
- right: 0,
818
- background: '#1e1e2e',
819
- border: '1px solid #45475a',
820
- borderRadius: 4,
821
- padding: 4,
822
- marginBottom: 2,
823
- maxHeight: 200,
824
- overflow: 'auto',
825
- zIndex: 10,
826
- }, children: compTypes
827
- .filter((t) => !world.hasComponent(entityId, t))
828
- .map((type) => (jsxRuntime.jsx("button", { onClick: () => {
829
- world.addComponent(entityId, type);
830
- dispatch({ type: 'MARK_DIRTY' });
831
- refreshWorld();
832
- addLog('info', `Added ${type} to #${entityId}`);
833
- setShowAddComp(false);
834
- }, style: {
835
- display: 'block',
836
- width: '100%',
837
- textAlign: 'left',
838
- background: 'transparent',
839
- border: 'none',
840
- color: '#cdd6f4',
841
- padding: '3px 6px',
842
- cursor: 'pointer',
843
- borderRadius: 2,
844
- fontSize: 11,
845
- }, children: type }, type))) }))] })] }));
1301
+ const { t: tr } = useTranslation.useTranslation("engine");
1302
+ const { state, world, dispatch, refreshWorld, addLog } = useEditor();
1303
+ const [showAddComp, setShowAddComp] = React.useState(false);
1304
+ if (state.selectedEntities.length === 0) {
1305
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { opacity: 0.5, padding: 8 }, children: tr("editor.messages.noSelection") });
1306
+ }
1307
+ const entityId = state.selectedEntities[0];
1308
+ const entity = world.getEntity(entityId);
1309
+ if (!entity) {
1310
+ return null;
1311
+ }
1312
+ const compTypes = ADDABLE_COMPONENTS;
1313
+ const presentComponents = compTypes.filter((type) => world.hasComponent(entityId, type));
1314
+ const componentEditors = {
1315
+ Transform: TransformEditor,
1316
+ Shape: ShapeEditor,
1317
+ Sprite: SpriteEditor,
1318
+ Text: TextEditor,
1319
+ RigidBody2D: RigidBodyEditor
1320
+ };
1321
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1322
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "4px 0", marginBottom: 4, borderBottom: "1px solid #313244" }, children: [
1323
+ /* @__PURE__ */ jsxRuntime.jsx(
1324
+ "input",
1325
+ {
1326
+ type: "text",
1327
+ value: entity.name,
1328
+ onChange: (e) => {
1329
+ entity.name = e.target.value;
1330
+ dispatch({ type: "MARK_DIRTY" });
1331
+ refreshWorld();
1332
+ },
1333
+ style: { ...fieldStyle, fontWeight: "bold", fontSize: 13 }
1334
+ }
1335
+ ),
1336
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontSize: 9, color: "#6c7086" }, children: [
1337
+ "ID: ",
1338
+ entityId
1339
+ ] })
1340
+ ] }),
1341
+ presentComponents.map((type) => {
1342
+ const Editor = componentEditors[type];
1343
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1344
+ "div",
1345
+ {
1346
+ style: { marginBottom: 8, borderTop: "1px solid #313244", paddingTop: 4 },
1347
+ children: [
1348
+ /* @__PURE__ */ jsxRuntime.jsxs(
1349
+ "div",
1350
+ {
1351
+ style: {
1352
+ display: "flex",
1353
+ alignItems: "center",
1354
+ justifyContent: "space-between",
1355
+ marginBottom: 4
1356
+ },
1357
+ children: [
1358
+ /* @__PURE__ */ jsxRuntime.jsx(
1359
+ "span",
1360
+ {
1361
+ style: {
1362
+ fontSize: 11,
1363
+ fontWeight: "bold",
1364
+ textTransform: "uppercase",
1365
+ color: "#89b4fa"
1366
+ },
1367
+ children: type
1368
+ }
1369
+ ),
1370
+ type !== "Transform" && /* @__PURE__ */ jsxRuntime.jsx(
1371
+ "button",
1372
+ {
1373
+ onClick: () => {
1374
+ world.removeComponent(entityId, type);
1375
+ dispatch({ type: "MARK_DIRTY" });
1376
+ refreshWorld();
1377
+ addLog("info", `Removed ${type} from #${entityId}`);
1378
+ },
1379
+ style: {
1380
+ background: "transparent",
1381
+ border: "none",
1382
+ color: "#f38ba8",
1383
+ cursor: "pointer",
1384
+ fontSize: 10
1385
+ },
1386
+ title: tr("editor.assets.removeComponent", { type }),
1387
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.NiceIcon, { name: "close", size: 10 })
1388
+ }
1389
+ )
1390
+ ]
1391
+ }
1392
+ ),
1393
+ Editor ? /* @__PURE__ */ jsxRuntime.jsx(Editor, { entityId }) : /* @__PURE__ */ jsxRuntime.jsx("pre", { style: { fontSize: 10, opacity: 0.6, whiteSpace: "pre-wrap", margin: 0 }, children: JSON.stringify(world.getComponent(entityId, type), null, 2) })
1394
+ ]
1395
+ },
1396
+ type
1397
+ );
1398
+ }),
1399
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: 8, position: "relative" }, children: [
1400
+ /* @__PURE__ */ jsxRuntime.jsx(
1401
+ "button",
1402
+ {
1403
+ onClick: () => setShowAddComp(!showAddComp),
1404
+ style: {
1405
+ width: "100%",
1406
+ padding: "4px 8px",
1407
+ background: "#313244",
1408
+ border: "1px solid #45475a",
1409
+ color: "#cdd6f4",
1410
+ borderRadius: 4,
1411
+ cursor: "pointer",
1412
+ fontSize: 11
1413
+ },
1414
+ children: "+ Add Component"
1415
+ }
1416
+ ),
1417
+ showAddComp && /* @__PURE__ */ jsxRuntime.jsx(
1418
+ "div",
1419
+ {
1420
+ style: {
1421
+ position: "absolute",
1422
+ bottom: "100%",
1423
+ left: 0,
1424
+ right: 0,
1425
+ background: "#1e1e2e",
1426
+ border: "1px solid #45475a",
1427
+ borderRadius: 4,
1428
+ padding: 4,
1429
+ marginBottom: 2,
1430
+ maxHeight: 200,
1431
+ overflow: "auto",
1432
+ zIndex: 10
1433
+ },
1434
+ children: compTypes.filter((t) => !world.hasComponent(entityId, t)).map((type) => /* @__PURE__ */ jsxRuntime.jsx(
1435
+ "button",
1436
+ {
1437
+ onClick: () => {
1438
+ world.addComponent(entityId, type);
1439
+ dispatch({ type: "MARK_DIRTY" });
1440
+ refreshWorld();
1441
+ addLog("info", `Added ${type} to #${entityId}`);
1442
+ setShowAddComp(false);
1443
+ },
1444
+ style: {
1445
+ display: "block",
1446
+ width: "100%",
1447
+ textAlign: "left",
1448
+ background: "transparent",
1449
+ border: "none",
1450
+ color: "#cdd6f4",
1451
+ padding: "3px 6px",
1452
+ cursor: "pointer",
1453
+ borderRadius: 2,
1454
+ fontSize: 11
1455
+ },
1456
+ children: type
1457
+ },
1458
+ type
1459
+ ))
1460
+ }
1461
+ )
1462
+ ] })
1463
+ ] });
846
1464
  };
847
1465
  const AssetPanel = () => {
848
- const { t: tr } = useTranslation.useTranslation('engine');
849
- const [tab, setTab] = React.useState('all');
850
- const tabBtnStyle = (active) => ({
851
- padding: '2px 8px',
852
- border: 'none',
853
- borderRadius: '3px 3px 0 0',
854
- cursor: 'pointer',
855
- fontSize: 10,
856
- background: active ? '#313244' : 'transparent',
857
- color: active ? '#cdd6f4' : '#6c7086',
858
- });
859
- const assets = [
860
- { name: 'player.png', type: 'sprites', icon: 'image' },
861
- { name: 'enemy.png', type: 'sprites', icon: 'image' },
862
- { name: 'tileset.png', type: 'sprites', icon: 'image' },
863
- { name: 'background.jpg', type: 'sprites', icon: 'image' },
864
- { name: 'jump.wav', type: 'audio', icon: 'volume' },
865
- { name: 'music.mp3', type: 'audio', icon: 'music' },
866
- { name: 'coin.wav', type: 'audio', icon: 'volume' },
867
- { name: 'playerController.ts', type: 'scripts', icon: 'code' },
868
- { name: 'enemyAI.ts', type: 'scripts', icon: 'code' },
869
- ];
870
- const filtered = tab === 'all' ? assets : assets.filter((a) => a.type === tab);
871
- return (jsxRuntime.jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%' }, children: [jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 2, padding: '2px 4px', borderBottom: '1px solid #313244' }, children: [['all', 'sprites', 'audio', 'scripts'].map((t) => (jsxRuntime.jsx("button", { onClick: () => setTab(t), style: tabBtnStyle(tab === t), children: t.charAt(0).toUpperCase() + t.slice(1) }, t))), jsxRuntime.jsx("span", { style: { flex: 1 } }), jsxRuntime.jsx("button", { style: {
872
- background: 'transparent',
873
- border: 'none',
874
- color: '#a6adc8',
875
- cursor: 'pointer',
876
- fontSize: 10,
877
- }, title: tr('editor.assets.import'), children: "+ Import" })] }), jsxRuntime.jsx("div", { style: {
878
- flex: 1,
879
- overflow: 'auto',
880
- display: 'grid',
881
- gridTemplateColumns: 'repeat(auto-fill, 80px)',
882
- gap: 4,
883
- padding: 4,
884
- }, children: filtered.map((a) => (jsxRuntime.jsxs("div", { draggable: true, style: {
885
- display: 'flex',
886
- flexDirection: 'column',
887
- alignItems: 'center',
888
- gap: 2,
889
- padding: 4,
890
- background: '#313244',
891
- borderRadius: 4,
892
- cursor: 'grab',
893
- fontSize: 9,
894
- textAlign: 'center',
895
- }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: a.icon, size: 20 }), jsxRuntime.jsx("span", { style: { wordBreak: 'break-all' }, children: a.name })] }, a.name))) })] }));
1466
+ const { t: tr } = useTranslation.useTranslation("engine");
1467
+ const [tab, setTab] = React.useState("all");
1468
+ const tabBtnStyle = (active) => ({
1469
+ padding: "2px 8px",
1470
+ border: "none",
1471
+ borderRadius: "3px 3px 0 0",
1472
+ cursor: "pointer",
1473
+ fontSize: 10,
1474
+ background: active ? "#313244" : "transparent",
1475
+ color: active ? "#cdd6f4" : "#6c7086"
1476
+ });
1477
+ const assets = [
1478
+ { name: "player.png", type: "sprites", icon: "image" },
1479
+ { name: "enemy.png", type: "sprites", icon: "image" },
1480
+ { name: "tileset.png", type: "sprites", icon: "image" },
1481
+ { name: "background.jpg", type: "sprites", icon: "image" },
1482
+ { name: "jump.wav", type: "audio", icon: "volume" },
1483
+ { name: "music.mp3", type: "audio", icon: "music" },
1484
+ { name: "coin.wav", type: "audio", icon: "volume" },
1485
+ { name: "playerController.ts", type: "scripts", icon: "code" },
1486
+ { name: "enemyAI.ts", type: "scripts", icon: "code" }
1487
+ ];
1488
+ const filtered = tab === "all" ? assets : assets.filter((a) => a.type === tab);
1489
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", height: "100%" }, children: [
1490
+ /* @__PURE__ */ jsxRuntime.jsxs(
1491
+ "div",
1492
+ {
1493
+ style: { display: "flex", gap: 2, padding: "2px 4px", borderBottom: "1px solid #313244" },
1494
+ children: [
1495
+ ["all", "sprites", "audio", "scripts"].map((t) => /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setTab(t), style: tabBtnStyle(tab === t), children: t.charAt(0).toUpperCase() + t.slice(1) }, t)),
1496
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { flex: 1 } }),
1497
+ /* @__PURE__ */ jsxRuntime.jsx(
1498
+ "button",
1499
+ {
1500
+ style: {
1501
+ background: "transparent",
1502
+ border: "none",
1503
+ color: "#a6adc8",
1504
+ cursor: "pointer",
1505
+ fontSize: 10
1506
+ },
1507
+ title: tr("editor.assets.import"),
1508
+ children: "+ Import"
1509
+ }
1510
+ )
1511
+ ]
1512
+ }
1513
+ ),
1514
+ /* @__PURE__ */ jsxRuntime.jsx(
1515
+ "div",
1516
+ {
1517
+ style: {
1518
+ flex: 1,
1519
+ overflow: "auto",
1520
+ display: "grid",
1521
+ gridTemplateColumns: "repeat(auto-fill, 80px)",
1522
+ gap: 4,
1523
+ padding: 4
1524
+ },
1525
+ children: filtered.map((a) => /* @__PURE__ */ jsxRuntime.jsxs(
1526
+ "div",
1527
+ {
1528
+ draggable: true,
1529
+ style: {
1530
+ display: "flex",
1531
+ flexDirection: "column",
1532
+ alignItems: "center",
1533
+ gap: 2,
1534
+ padding: 4,
1535
+ background: "#313244",
1536
+ borderRadius: 4,
1537
+ cursor: "grab",
1538
+ fontSize: 9,
1539
+ textAlign: "center"
1540
+ },
1541
+ children: [
1542
+ /* @__PURE__ */ jsxRuntime.jsx(ui.NiceIcon, { name: a.icon, size: 20 }),
1543
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { wordBreak: "break-all" }, children: a.name })
1544
+ ]
1545
+ },
1546
+ a.name
1547
+ ))
1548
+ }
1549
+ )
1550
+ ] });
896
1551
  };
897
1552
  const ConsolePanel = () => {
898
- const { t: tr } = useTranslation.useTranslation('engine');
899
- const { consoleLogs } = useEditor();
900
- const [filter, setFilter] = React.useState('all');
901
- const scrollRef = React.useRef(null);
902
- React.useEffect(() => {
903
- if (scrollRef.current) {
904
- scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
905
- }
906
- }, [consoleLogs.length]);
907
- const filtered = filter === 'all' ? consoleLogs : consoleLogs.filter((l) => l.level === filter);
908
- const levelColors = { info: '#89b4fa', warn: '#f9e2af', error: '#f38ba8' };
909
- const filterBtn = (f, label) => (jsxRuntime.jsx("button", { onClick: () => setFilter(f), style: {
910
- padding: '1px 6px',
911
- border: 'none',
912
- borderRadius: 2,
913
- cursor: 'pointer',
914
- fontSize: 9,
915
- background: filter === f ? '#313244' : 'transparent',
916
- color: filter === f ? '#cdd6f4' : '#6c7086',
917
- }, children: label }));
918
- return (jsxRuntime.jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', fontFamily: 'monospace' }, children: [jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 2, padding: '2px 4px', borderBottom: '1px solid #313244' }, children: [filterBtn('all', 'All'), " ", filterBtn('info', 'Info'), " ", filterBtn('warn', 'Warn'), ' ', filterBtn('error', 'Error'), jsxRuntime.jsx("span", { style: { flex: 1 } }), jsxRuntime.jsxs("span", { style: { fontSize: 9, color: '#6c7086' }, children: [filtered.length, " entries"] })] }), jsxRuntime.jsx("div", { ref: scrollRef, style: { flex: 1, overflow: 'auto', fontSize: 10 }, children: filtered.length === 0 ? (jsxRuntime.jsx("div", { style: { opacity: 0.4, padding: 8 }, children: tr('editor.messages.consoleReady') })) : (filtered.map((log) => (jsxRuntime.jsxs("div", { style: {
919
- padding: '1px 4px',
920
- borderBottom: '1px solid #181825',
921
- display: 'flex',
922
- gap: 6,
923
- }, children: [jsxRuntime.jsx("span", { style: { color: '#6c7086', minWidth: 55 }, children: new Date(log.timestamp).toLocaleTimeString([], { hour12: false }) }), jsxRuntime.jsxs("span", { style: { color: levelColors[log.level], fontWeight: 'bold', minWidth: 35 }, children: ["[", log.level.toUpperCase(), "]"] }), jsxRuntime.jsx("span", { children: log.message })] }, log.id)))) })] }));
1553
+ const { t: tr } = useTranslation.useTranslation("engine");
1554
+ const { consoleLogs } = useEditor();
1555
+ const [filter, setFilter] = React.useState("all");
1556
+ const scrollRef = React.useRef(null);
1557
+ React.useEffect(() => {
1558
+ if (scrollRef.current) {
1559
+ scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
1560
+ }
1561
+ }, [consoleLogs.length]);
1562
+ const filtered = filter === "all" ? consoleLogs : consoleLogs.filter((l) => l.level === filter);
1563
+ const levelColors = { info: "#89b4fa", warn: "#f9e2af", error: "#f38ba8" };
1564
+ const filterBtn = (f, label) => /* @__PURE__ */ jsxRuntime.jsx(
1565
+ "button",
1566
+ {
1567
+ onClick: () => setFilter(f),
1568
+ style: {
1569
+ padding: "1px 6px",
1570
+ border: "none",
1571
+ borderRadius: 2,
1572
+ cursor: "pointer",
1573
+ fontSize: 9,
1574
+ background: filter === f ? "#313244" : "transparent",
1575
+ color: filter === f ? "#cdd6f4" : "#6c7086"
1576
+ },
1577
+ children: label
1578
+ }
1579
+ );
1580
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1581
+ "div",
1582
+ {
1583
+ style: { display: "flex", flexDirection: "column", height: "100%", fontFamily: "monospace" },
1584
+ children: [
1585
+ /* @__PURE__ */ jsxRuntime.jsxs(
1586
+ "div",
1587
+ {
1588
+ style: { display: "flex", gap: 2, padding: "2px 4px", borderBottom: "1px solid #313244" },
1589
+ children: [
1590
+ filterBtn("all", "All"),
1591
+ " ",
1592
+ filterBtn("info", "Info"),
1593
+ " ",
1594
+ filterBtn("warn", "Warn"),
1595
+ " ",
1596
+ filterBtn("error", "Error"),
1597
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { flex: 1 } }),
1598
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontSize: 9, color: "#6c7086" }, children: [
1599
+ filtered.length,
1600
+ " entries"
1601
+ ] })
1602
+ ]
1603
+ }
1604
+ ),
1605
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: scrollRef, style: { flex: 1, overflow: "auto", fontSize: 10 }, children: filtered.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { opacity: 0.4, padding: 8 }, children: tr("editor.messages.consoleReady") }) : filtered.map((log) => /* @__PURE__ */ jsxRuntime.jsxs(
1606
+ "div",
1607
+ {
1608
+ style: {
1609
+ padding: "1px 4px",
1610
+ borderBottom: "1px solid #181825",
1611
+ display: "flex",
1612
+ gap: 6
1613
+ },
1614
+ children: [
1615
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#6c7086", minWidth: 55 }, children: new Date(log.timestamp).toLocaleTimeString([], { hour12: false }) }),
1616
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { color: levelColors[log.level], fontWeight: "bold", minWidth: 35 }, children: [
1617
+ "[",
1618
+ log.level.toUpperCase(),
1619
+ "]"
1620
+ ] }),
1621
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: log.message })
1622
+ ]
1623
+ },
1624
+ log.id
1625
+ )) })
1626
+ ]
1627
+ }
1628
+ );
924
1629
  };
925
1630
  const EditorViewport = () => {
926
- const canvasRef = React.useRef(null);
927
- const { state, dispatch, world, worldVersion, refreshWorld, undoStack } = useEditor();
928
- const dragging = React.useRef(false);
929
- const dragStart = React.useRef({ x: 0, y: 0 });
930
- const dragEntityStart = React.useRef({ x: 0, y: 0 });
931
- const panning = React.useRef(false);
932
- const panStart = React.useRef({ x: 0, y: 0, px: 0, py: 0 });
933
- // Draw loop
934
- React.useEffect(() => {
935
- const canvas = canvasRef.current;
936
- if (!canvas) {
937
- return;
938
- }
939
- const ctx = canvas.getContext('2d');
940
- if (!ctx) {
941
- return;
942
- }
943
- const { width, height } = canvas.getBoundingClientRect();
944
- canvas.width = width;
945
- canvas.height = height;
946
- ctx.fillStyle = '#11111b';
947
- ctx.fillRect(0, 0, width, height);
948
- ctx.save();
949
- ctx.translate(width / 2 + state.panOffset.x, height / 2 + state.panOffset.y);
950
- ctx.scale(state.zoom, state.zoom);
951
- // Grid
952
- if (state.showGrid) {
953
- const gs = state.gridSize;
954
- const halfW = width / 2 / state.zoom;
955
- const halfH = height / 2 / state.zoom;
956
- const offX = -state.panOffset.x / state.zoom;
957
- const offY = -state.panOffset.y / state.zoom;
958
- const startX = Math.floor((offX - halfW) / gs) * gs;
959
- const endX = Math.ceil((offX + halfW) / gs) * gs;
960
- const startY = Math.floor((offY - halfH) / gs) * gs;
961
- const endY = Math.ceil((offY + halfH) / gs) * gs;
962
- ctx.strokeStyle = 'rgba(69, 71, 90, 0.3)';
963
- ctx.lineWidth = 0.5 / state.zoom;
964
- for (let x = startX; x <= endX; x += gs) {
965
- ctx.beginPath();
966
- ctx.moveTo(x, startY);
967
- ctx.lineTo(x, endY);
968
- ctx.stroke();
969
- }
970
- for (let y = startY; y <= endY; y += gs) {
971
- ctx.beginPath();
972
- ctx.moveTo(startX, y);
973
- ctx.lineTo(endX, y);
974
- ctx.stroke();
1631
+ const canvasRef = React.useRef(null);
1632
+ const { state, dispatch, world, worldVersion, refreshWorld, undoStack } = useEditor();
1633
+ const dragging = React.useRef(false);
1634
+ const dragStart = React.useRef({ x: 0, y: 0 });
1635
+ const dragEntityStart = React.useRef({ x: 0, y: 0 });
1636
+ const panning = React.useRef(false);
1637
+ const panStart = React.useRef({ x: 0, y: 0, px: 0, py: 0 });
1638
+ React.useEffect(() => {
1639
+ const canvas = canvasRef.current;
1640
+ if (!canvas) {
1641
+ return;
1642
+ }
1643
+ const ctx = canvas.getContext("2d");
1644
+ if (!ctx) {
1645
+ return;
1646
+ }
1647
+ const { width, height } = canvas.getBoundingClientRect();
1648
+ canvas.width = width;
1649
+ canvas.height = height;
1650
+ ctx.fillStyle = "#11111b";
1651
+ ctx.fillRect(0, 0, width, height);
1652
+ ctx.save();
1653
+ ctx.translate(width / 2 + state.panOffset.x, height / 2 + state.panOffset.y);
1654
+ ctx.scale(state.zoom, state.zoom);
1655
+ if (state.showGrid) {
1656
+ const gs = state.gridSize;
1657
+ const halfW = width / 2 / state.zoom;
1658
+ const halfH = height / 2 / state.zoom;
1659
+ const offX = -state.panOffset.x / state.zoom;
1660
+ const offY = -state.panOffset.y / state.zoom;
1661
+ const startX = Math.floor((offX - halfW) / gs) * gs;
1662
+ const endX = Math.ceil((offX + halfW) / gs) * gs;
1663
+ const startY = Math.floor((offY - halfH) / gs) * gs;
1664
+ const endY = Math.ceil((offY + halfH) / gs) * gs;
1665
+ ctx.strokeStyle = "rgba(69, 71, 90, 0.3)";
1666
+ ctx.lineWidth = 0.5 / state.zoom;
1667
+ for (let x = startX; x <= endX; x += gs) {
1668
+ ctx.beginPath();
1669
+ ctx.moveTo(x, startY);
1670
+ ctx.lineTo(x, endY);
1671
+ ctx.stroke();
1672
+ }
1673
+ for (let y = startY; y <= endY; y += gs) {
1674
+ ctx.beginPath();
1675
+ ctx.moveTo(startX, y);
1676
+ ctx.lineTo(endX, y);
1677
+ ctx.stroke();
1678
+ }
1679
+ ctx.strokeStyle = "rgba(205, 214, 244, 0.15)";
1680
+ ctx.lineWidth = 1 / state.zoom;
1681
+ ctx.beginPath();
1682
+ ctx.moveTo(0, startY);
1683
+ ctx.lineTo(0, endY);
1684
+ ctx.stroke();
1685
+ ctx.beginPath();
1686
+ ctx.moveTo(startX, 0);
1687
+ ctx.lineTo(endX, 0);
1688
+ ctx.stroke();
1689
+ }
1690
+ const entities = world.allEntities();
1691
+ for (const entity of entities) {
1692
+ if (!entity.enabled) {
1693
+ continue;
1694
+ }
1695
+ const transform = world.getComponent(entity.id, "Transform");
1696
+ if (!transform) {
1697
+ continue;
1698
+ }
1699
+ ctx.save();
1700
+ ctx.translate(transform.position.x, transform.position.y);
1701
+ ctx.rotate(transform.rotation);
1702
+ ctx.scale(transform.scale.x, transform.scale.y);
1703
+ const isSelected = state.selectedEntities.includes(entity.id);
1704
+ const shape = world.getComponent(entity.id, "Shape");
1705
+ if (shape && shape.visible) {
1706
+ ctx.fillStyle = colorToCSS(shape.fillColor);
1707
+ ctx.strokeStyle = colorToCSS(shape.strokeColor);
1708
+ ctx.lineWidth = shape.strokeWidth;
1709
+ switch (shape.shape) {
1710
+ case "rect":
1711
+ ctx.fillRect(-shape.width / 2, -shape.height / 2, shape.width, shape.height);
1712
+ if (shape.strokeWidth > 0) {
1713
+ ctx.strokeRect(-shape.width / 2, -shape.height / 2, shape.width, shape.height);
975
1714
  }
976
- // Origin axes
977
- ctx.strokeStyle = 'rgba(205, 214, 244, 0.15)';
978
- ctx.lineWidth = 1 / state.zoom;
1715
+ break;
1716
+ case "circle":
979
1717
  ctx.beginPath();
980
- ctx.moveTo(0, startY);
981
- ctx.lineTo(0, endY);
982
- ctx.stroke();
1718
+ ctx.arc(0, 0, shape.radius, 0, Math.PI * 2);
1719
+ ctx.fill();
1720
+ if (shape.strokeWidth > 0) {
1721
+ ctx.stroke();
1722
+ }
1723
+ break;
1724
+ case "line":
983
1725
  ctx.beginPath();
984
- ctx.moveTo(startX, 0);
985
- ctx.lineTo(endX, 0);
1726
+ ctx.moveTo(shape.lineStart.x, shape.lineStart.y);
1727
+ ctx.lineTo(shape.lineEnd.x, shape.lineEnd.y);
986
1728
  ctx.stroke();
987
- }
988
- // Render entities
989
- const entities = world.allEntities();
990
- for (const entity of entities) {
991
- if (!entity.enabled) {
992
- continue;
993
- }
994
- const transform = world.getComponent(entity.id, 'Transform');
995
- if (!transform) {
996
- continue;
997
- }
998
- ctx.save();
999
- ctx.translate(transform.position.x, transform.position.y);
1000
- ctx.rotate(transform.rotation);
1001
- ctx.scale(transform.scale.x, transform.scale.y);
1002
- const isSelected = state.selectedEntities.includes(entity.id);
1003
- // Shape
1004
- const shape = world.getComponent(entity.id, 'Shape');
1005
- if (shape && shape.visible) {
1006
- ctx.fillStyle = colorToCSS(shape.fillColor);
1007
- ctx.strokeStyle = colorToCSS(shape.strokeColor);
1008
- ctx.lineWidth = shape.strokeWidth;
1009
- switch (shape.shape) {
1010
- case 'rect':
1011
- ctx.fillRect(-shape.width / 2, -shape.height / 2, shape.width, shape.height);
1012
- if (shape.strokeWidth > 0) {
1013
- ctx.strokeRect(-shape.width / 2, -shape.height / 2, shape.width, shape.height);
1014
- }
1015
- break;
1016
- case 'circle':
1017
- ctx.beginPath();
1018
- ctx.arc(0, 0, shape.radius, 0, Math.PI * 2);
1019
- ctx.fill();
1020
- if (shape.strokeWidth > 0) {
1021
- ctx.stroke();
1022
- }
1023
- break;
1024
- case 'line':
1025
- ctx.beginPath();
1026
- ctx.moveTo(shape.lineStart.x, shape.lineStart.y);
1027
- ctx.lineTo(shape.lineEnd.x, shape.lineEnd.y);
1028
- ctx.stroke();
1029
- break;
1030
- case 'polygon':
1031
- if (shape.points.length > 2) {
1032
- ctx.beginPath();
1033
- ctx.moveTo(shape.points[0].x, shape.points[0].y);
1034
- for (let i = 1; i < shape.points.length; i++) {
1035
- ctx.lineTo(shape.points[i].x, shape.points[i].y);
1036
- }
1037
- ctx.closePath();
1038
- ctx.fill();
1039
- if (shape.strokeWidth > 0) {
1040
- ctx.stroke();
1041
- }
1042
- }
1043
- break;
1044
- }
1045
- }
1046
- // Sprite
1047
- const sprite = world.getComponent(entity.id, 'Sprite');
1048
- if (sprite && sprite.visible) {
1049
- ctx.globalAlpha = sprite.opacity;
1050
- ctx.fillStyle = colorToCSS(sprite.tint);
1051
- const ax = -sprite.width * sprite.anchor.x;
1052
- const ay = -sprite.height * sprite.anchor.y;
1053
- ctx.fillRect(ax, ay, sprite.width, sprite.height);
1054
- // Sprite border
1055
- ctx.strokeStyle = 'rgba(255,255,255,0.2)';
1056
- ctx.lineWidth = 1 / state.zoom;
1057
- ctx.strokeRect(ax, ay, sprite.width, sprite.height);
1058
- // Sprite label
1059
- ctx.fillStyle = 'rgba(255,255,255,0.5)';
1060
- ctx.font = `${9 / state.zoom}px monospace`;
1061
- ctx.textAlign = 'center';
1062
- ctx.textBaseline = 'middle';
1063
- ctx.fillText(sprite.assetId || 'sprite', 0, 0);
1064
- ctx.globalAlpha = 1;
1065
- }
1066
- // Text
1067
- const text = world.getComponent(entity.id, 'Text');
1068
- if (text && text.visible && text.text) {
1069
- ctx.font = `${text.fontSize}px ${text.fontFamily}`;
1070
- ctx.fillStyle = colorToCSS(text.color);
1071
- ctx.textAlign = text.align;
1072
- ctx.textBaseline = text.baseline;
1073
- ctx.fillText(text.text, 0, 0);
1074
- }
1075
- // Particle emitter indicator
1076
- const particles = world.getComponent(entity.id, 'ParticleEmitter');
1077
- if (particles) {
1078
- ctx.strokeStyle = '#f9e2af';
1079
- ctx.lineWidth = 1 / state.zoom;
1080
- ctx.setLineDash([3 / state.zoom, 3 / state.zoom]);
1081
- ctx.beginPath();
1082
- ctx.arc(0, 0, 12, 0, Math.PI * 2);
1729
+ break;
1730
+ case "polygon":
1731
+ if (shape.points.length > 2) {
1732
+ ctx.beginPath();
1733
+ ctx.moveTo(shape.points[0].x, shape.points[0].y);
1734
+ for (let i = 1; i < shape.points.length; i++) {
1735
+ ctx.lineTo(shape.points[i].x, shape.points[i].y);
1736
+ }
1737
+ ctx.closePath();
1738
+ ctx.fill();
1739
+ if (shape.strokeWidth > 0) {
1083
1740
  ctx.stroke();
1084
- ctx.setLineDash([]);
1085
- ctx.fillStyle = '#f9e2af';
1086
- ctx.font = `${8 / state.zoom}px monospace`;
1087
- ctx.textAlign = 'center';
1088
- // Sparkle marker drawn as a cross (no glyph fonts on the canvas)
1089
- const spark = 4 / state.zoom;
1090
- ctx.fillRect(-spark / 4, -spark, spark / 2, spark * 2);
1091
- ctx.fillRect(-spark, -spark / 4, spark * 2, spark / 2);
1092
- }
1093
- // Camera indicator
1094
- const cam = world.getComponent(entity.id, 'CameraTarget');
1095
- if (cam) {
1096
- ctx.strokeStyle = '#a6e3a1';
1097
- ctx.lineWidth = 1.5 / state.zoom;
1098
- const hw = 60, hh = 34;
1099
- ctx.strokeRect(-hw, -hh, hw * 2, hh * 2);
1100
- ctx.font = `${8 / state.zoom}px monospace`;
1101
- ctx.fillStyle = '#a6e3a1';
1102
- ctx.textAlign = 'center';
1103
- ctx.fillText('Camera', 0, -hh - 4 / state.zoom);
1104
- }
1105
- // Selection highlight
1106
- if (isSelected) {
1107
- const b = getEntityBounds(entity.id, world);
1108
- ctx.strokeStyle = '#89b4fa';
1109
- ctx.lineWidth = 2 / state.zoom;
1110
- ctx.setLineDash([4 / state.zoom, 4 / state.zoom]);
1111
- ctx.strokeRect(b.x - 2, b.y - 2, b.w + 4, b.h + 4);
1112
- ctx.setLineDash([]);
1113
- // Corner handles
1114
- const hs = 4 / state.zoom;
1115
- ctx.fillStyle = '#89b4fa';
1116
- ctx.fillRect(b.x - hs, b.y - hs, hs * 2, hs * 2);
1117
- ctx.fillRect(b.x + b.w - hs, b.y - hs, hs * 2, hs * 2);
1118
- ctx.fillRect(b.x - hs, b.y + b.h - hs, hs * 2, hs * 2);
1119
- ctx.fillRect(b.x + b.w - hs, b.y + b.h - hs, hs * 2, hs * 2);
1741
+ }
1120
1742
  }
1121
- // Entity name label (only if no visible shape/sprite/text OR selected)
1122
- if (isSelected || (!shape?.visible && !sprite?.visible && !text?.visible)) {
1123
- ctx.font = `${10 / state.zoom}px monospace`;
1124
- ctx.fillStyle = isSelected ? '#89b4fa' : 'rgba(205, 214, 244, 0.4)';
1125
- ctx.textAlign = 'center';
1126
- ctx.textBaseline = 'bottom';
1127
- const b = getEntityBounds(entity.id, world);
1128
- ctx.fillText(entity.name, 0, b.y - 4 / state.zoom);
1129
- // Draw small dot for invisible/empty entities
1130
- if (!shape?.visible && !sprite?.visible && !text?.visible) {
1131
- ctx.fillStyle = isSelected ? '#89b4fa' : 'rgba(205, 214, 244, 0.3)';
1132
- ctx.beginPath();
1133
- ctx.arc(0, 0, 3 / state.zoom, 0, Math.PI * 2);
1134
- ctx.fill();
1135
- }
1136
- }
1137
- ctx.restore();
1138
- }
1139
- ctx.restore();
1140
- // HUD
1141
- ctx.fillStyle = '#6c7086';
1142
- ctx.font = '10px monospace';
1143
- ctx.textAlign = 'left';
1144
- ctx.textBaseline = 'top';
1145
- ctx.fillText(`Entities: ${world.entityCount} | Grid: ${state.gridSize}px`, 8, 8);
1146
- }, [state, worldVersion, world]);
1147
- // Mouse handlers
1148
- const screenToWorld = React.useCallback((e) => {
1149
- const canvas = canvasRef.current;
1150
- const rect = canvas.getBoundingClientRect();
1151
- const sx = e.clientX - rect.left;
1152
- const sy = e.clientY - rect.top;
1153
- return {
1154
- x: (sx - canvas.width / 2 - state.panOffset.x) / state.zoom,
1155
- y: (sy - canvas.height / 2 - state.panOffset.y) / state.zoom,
1156
- };
1157
- }, [state.zoom, state.panOffset]);
1158
- const handlePointerDown = React.useCallback((e) => {
1159
- // Middle click or pan tool = pan
1160
- if (e.button === 1 || (e.button === 0 && state.tool === 'pan')) {
1161
- panning.current = true;
1162
- panStart.current = {
1163
- x: e.clientX,
1164
- y: e.clientY,
1165
- px: state.panOffset.x,
1166
- py: state.panOffset.y,
1167
- };
1168
- e.target.setPointerCapture(e.pointerId);
1169
- return;
1170
- }
1171
- if (e.button !== 0) {
1172
- return;
1743
+ break;
1173
1744
  }
1174
- const { x: wx, y: wy } = screenToWorld(e);
1175
- // Hit test (reverse order for top-most)
1176
- const entities = world.allEntities();
1177
- let hitId = null;
1178
- for (let i = entities.length - 1; i >= 0; i--) {
1179
- const ent = entities[i];
1180
- if (!ent.enabled) {
1181
- continue;
1182
- }
1183
- const bounds = getEntityWorldBounds(ent.id, world);
1184
- if (bounds &&
1185
- wx >= bounds.x &&
1186
- wx <= bounds.x + bounds.w &&
1187
- wy >= bounds.y &&
1188
- wy <= bounds.y + bounds.h) {
1189
- hitId = ent.id;
1190
- break;
1191
- }
1745
+ }
1746
+ const sprite = world.getComponent(entity.id, "Sprite");
1747
+ if (sprite && sprite.visible) {
1748
+ ctx.globalAlpha = sprite.opacity;
1749
+ ctx.fillStyle = colorToCSS(sprite.tint);
1750
+ const ax = -sprite.width * sprite.anchor.x;
1751
+ const ay = -sprite.height * sprite.anchor.y;
1752
+ ctx.fillRect(ax, ay, sprite.width, sprite.height);
1753
+ ctx.strokeStyle = "rgba(255,255,255,0.2)";
1754
+ ctx.lineWidth = 1 / state.zoom;
1755
+ ctx.strokeRect(ax, ay, sprite.width, sprite.height);
1756
+ ctx.fillStyle = "rgba(255,255,255,0.5)";
1757
+ ctx.font = `${9 / state.zoom}px monospace`;
1758
+ ctx.textAlign = "center";
1759
+ ctx.textBaseline = "middle";
1760
+ ctx.fillText(sprite.assetId || "sprite", 0, 0);
1761
+ ctx.globalAlpha = 1;
1762
+ }
1763
+ const text = world.getComponent(entity.id, "Text");
1764
+ if (text && text.visible && text.text) {
1765
+ ctx.font = `${text.fontSize}px ${text.fontFamily}`;
1766
+ ctx.fillStyle = colorToCSS(text.color);
1767
+ ctx.textAlign = text.align;
1768
+ ctx.textBaseline = text.baseline;
1769
+ ctx.fillText(text.text, 0, 0);
1770
+ }
1771
+ const particles = world.getComponent(entity.id, "ParticleEmitter");
1772
+ if (particles) {
1773
+ ctx.strokeStyle = "#f9e2af";
1774
+ ctx.lineWidth = 1 / state.zoom;
1775
+ ctx.setLineDash([3 / state.zoom, 3 / state.zoom]);
1776
+ ctx.beginPath();
1777
+ ctx.arc(0, 0, 12, 0, Math.PI * 2);
1778
+ ctx.stroke();
1779
+ ctx.setLineDash([]);
1780
+ ctx.fillStyle = "#f9e2af";
1781
+ ctx.font = `${8 / state.zoom}px monospace`;
1782
+ ctx.textAlign = "center";
1783
+ const spark = 4 / state.zoom;
1784
+ ctx.fillRect(-spark / 4, -spark, spark / 2, spark * 2);
1785
+ ctx.fillRect(-spark, -spark / 4, spark * 2, spark / 2);
1786
+ }
1787
+ const cam = world.getComponent(entity.id, "CameraTarget");
1788
+ if (cam) {
1789
+ ctx.strokeStyle = "#a6e3a1";
1790
+ ctx.lineWidth = 1.5 / state.zoom;
1791
+ const hw = 60, hh = 34;
1792
+ ctx.strokeRect(-hw, -hh, hw * 2, hh * 2);
1793
+ ctx.font = `${8 / state.zoom}px monospace`;
1794
+ ctx.fillStyle = "#a6e3a1";
1795
+ ctx.textAlign = "center";
1796
+ ctx.fillText("Camera", 0, -hh - 4 / state.zoom);
1797
+ }
1798
+ if (isSelected) {
1799
+ const b = getEntityBounds(entity.id, world);
1800
+ ctx.strokeStyle = "#89b4fa";
1801
+ ctx.lineWidth = 2 / state.zoom;
1802
+ ctx.setLineDash([4 / state.zoom, 4 / state.zoom]);
1803
+ ctx.strokeRect(b.x - 2, b.y - 2, b.w + 4, b.h + 4);
1804
+ ctx.setLineDash([]);
1805
+ const hs = 4 / state.zoom;
1806
+ ctx.fillStyle = "#89b4fa";
1807
+ ctx.fillRect(b.x - hs, b.y - hs, hs * 2, hs * 2);
1808
+ ctx.fillRect(b.x + b.w - hs, b.y - hs, hs * 2, hs * 2);
1809
+ ctx.fillRect(b.x - hs, b.y + b.h - hs, hs * 2, hs * 2);
1810
+ ctx.fillRect(b.x + b.w - hs, b.y + b.h - hs, hs * 2, hs * 2);
1811
+ }
1812
+ if (isSelected || !shape?.visible && !sprite?.visible && !text?.visible) {
1813
+ ctx.font = `${10 / state.zoom}px monospace`;
1814
+ ctx.fillStyle = isSelected ? "#89b4fa" : "rgba(205, 214, 244, 0.4)";
1815
+ ctx.textAlign = "center";
1816
+ ctx.textBaseline = "bottom";
1817
+ const b = getEntityBounds(entity.id, world);
1818
+ ctx.fillText(entity.name, 0, b.y - 4 / state.zoom);
1819
+ if (!shape?.visible && !sprite?.visible && !text?.visible) {
1820
+ ctx.fillStyle = isSelected ? "#89b4fa" : "rgba(205, 214, 244, 0.3)";
1821
+ ctx.beginPath();
1822
+ ctx.arc(0, 0, 3 / state.zoom, 0, Math.PI * 2);
1823
+ ctx.fill();
1192
1824
  }
1193
- if (hitId !== null) {
1194
- dispatch({ type: 'SELECT', entities: [hitId] });
1195
- if (state.tool === 'select' || state.tool === 'move') {
1196
- dragging.current = true;
1197
- dragStart.current = { x: wx, y: wy };
1198
- const t = world.getComponent(hitId, 'Transform');
1199
- if (t) {
1200
- dragEntityStart.current = { x: t.position.x, y: t.position.y };
1201
- }
1202
- }
1825
+ }
1826
+ ctx.restore();
1827
+ }
1828
+ ctx.restore();
1829
+ ctx.fillStyle = "#6c7086";
1830
+ ctx.font = "10px monospace";
1831
+ ctx.textAlign = "left";
1832
+ ctx.textBaseline = "top";
1833
+ ctx.fillText(`Entities: ${world.entityCount} | Grid: ${state.gridSize}px`, 8, 8);
1834
+ }, [state, worldVersion, world]);
1835
+ const screenToWorld = React.useCallback(
1836
+ (e) => {
1837
+ const canvas = canvasRef.current;
1838
+ const rect = canvas.getBoundingClientRect();
1839
+ const sx = e.clientX - rect.left;
1840
+ const sy = e.clientY - rect.top;
1841
+ return {
1842
+ x: (sx - canvas.width / 2 - state.panOffset.x) / state.zoom,
1843
+ y: (sy - canvas.height / 2 - state.panOffset.y) / state.zoom
1844
+ };
1845
+ },
1846
+ [state.zoom, state.panOffset]
1847
+ );
1848
+ const handlePointerDown = React.useCallback(
1849
+ (e) => {
1850
+ if (e.button === 1 || e.button === 0 && state.tool === "pan") {
1851
+ panning.current = true;
1852
+ panStart.current = {
1853
+ x: e.clientX,
1854
+ y: e.clientY,
1855
+ px: state.panOffset.x,
1856
+ py: state.panOffset.y
1857
+ };
1858
+ e.target.setPointerCapture(e.pointerId);
1859
+ return;
1860
+ }
1861
+ if (e.button !== 0) {
1862
+ return;
1863
+ }
1864
+ const { x: wx, y: wy } = screenToWorld(e);
1865
+ const entities = world.allEntities();
1866
+ let hitId = null;
1867
+ for (let i = entities.length - 1; i >= 0; i--) {
1868
+ const ent = entities[i];
1869
+ if (!ent.enabled) {
1870
+ continue;
1203
1871
  }
1204
- else {
1205
- dispatch({ type: 'DESELECT_ALL' });
1872
+ const bounds = getEntityWorldBounds(ent.id, world);
1873
+ if (bounds && wx >= bounds.x && wx <= bounds.x + bounds.w && wy >= bounds.y && wy <= bounds.y + bounds.h) {
1874
+ hitId = ent.id;
1875
+ break;
1206
1876
  }
1207
- e.target.setPointerCapture(e.pointerId);
1208
- }, [state.tool, state.panOffset, screenToWorld, world, dispatch]);
1209
- const handlePointerMove = React.useCallback((e) => {
1210
- if (panning.current) {
1211
- const dx = e.clientX - panStart.current.x;
1212
- const dy = e.clientY - panStart.current.y;
1213
- dispatch({
1214
- type: 'SET_PAN',
1215
- offset: { x: panStart.current.px + dx, y: panStart.current.py + dy },
1216
- });
1217
- return;
1877
+ }
1878
+ if (hitId !== null) {
1879
+ dispatch({ type: "SELECT", entities: [hitId] });
1880
+ if (state.tool === "select" || state.tool === "move") {
1881
+ dragging.current = true;
1882
+ dragStart.current = { x: wx, y: wy };
1883
+ const t = world.getComponent(hitId, "Transform");
1884
+ if (t) {
1885
+ dragEntityStart.current = { x: t.position.x, y: t.position.y };
1886
+ }
1218
1887
  }
1219
- if (dragging.current && state.selectedEntities.length > 0) {
1220
- const { x: wx, y: wy } = screenToWorld(e);
1221
- const dx = wx - dragStart.current.x;
1222
- const dy = wy - dragStart.current.y;
1223
- const entityId = state.selectedEntities[0];
1224
- const t = world.getComponent(entityId, 'Transform');
1225
- if (t) {
1226
- let nx = dragEntityStart.current.x + dx;
1227
- let ny = dragEntityStart.current.y + dy;
1228
- if (state.snapToGrid) {
1229
- nx = Math.round(nx / state.gridSize) * state.gridSize;
1230
- ny = Math.round(ny / state.gridSize) * state.gridSize;
1231
- }
1232
- t.position.x = nx;
1233
- t.position.y = ny;
1234
- dispatch({ type: 'MARK_DIRTY' });
1235
- refreshWorld();
1236
- }
1888
+ } else {
1889
+ dispatch({ type: "DESELECT_ALL" });
1890
+ }
1891
+ e.target.setPointerCapture(e.pointerId);
1892
+ },
1893
+ [state.tool, state.panOffset, screenToWorld, world, dispatch]
1894
+ );
1895
+ const handlePointerMove = React.useCallback(
1896
+ (e) => {
1897
+ if (panning.current) {
1898
+ const dx = e.clientX - panStart.current.x;
1899
+ const dy = e.clientY - panStart.current.y;
1900
+ dispatch({
1901
+ type: "SET_PAN",
1902
+ offset: { x: panStart.current.px + dx, y: panStart.current.py + dy }
1903
+ });
1904
+ return;
1905
+ }
1906
+ if (dragging.current && state.selectedEntities.length > 0) {
1907
+ const { x: wx, y: wy } = screenToWorld(e);
1908
+ const dx = wx - dragStart.current.x;
1909
+ const dy = wy - dragStart.current.y;
1910
+ const entityId = state.selectedEntities[0];
1911
+ const t = world.getComponent(entityId, "Transform");
1912
+ if (t) {
1913
+ let nx = dragEntityStart.current.x + dx;
1914
+ let ny = dragEntityStart.current.y + dy;
1915
+ if (state.snapToGrid) {
1916
+ nx = Math.round(nx / state.gridSize) * state.gridSize;
1917
+ ny = Math.round(ny / state.gridSize) * state.gridSize;
1918
+ }
1919
+ t.position.x = nx;
1920
+ t.position.y = ny;
1921
+ dispatch({ type: "MARK_DIRTY" });
1922
+ refreshWorld();
1237
1923
  }
1238
- }, [
1239
- state.selectedEntities,
1240
- state.snapToGrid,
1241
- state.gridSize,
1242
- screenToWorld,
1243
- world,
1244
- dispatch,
1245
- refreshWorld,
1246
- ]);
1247
- const handlePointerUp = React.useCallback(() => {
1248
- if (dragging.current && state.selectedEntities.length > 0) {
1249
- const entityId = state.selectedEntities[0];
1250
- const t = world.getComponent(entityId, 'Transform');
1251
- if (t) {
1252
- const finalPos = { x: t.position.x, y: t.position.y };
1253
- const startPos = { ...dragEntityStart.current };
1254
- if (finalPos.x !== startPos.x || finalPos.y !== startPos.y) {
1255
- undoStack.push({
1256
- description: 'Move entity',
1257
- undo: () => {
1258
- t.position.x = startPos.x;
1259
- t.position.y = startPos.y;
1260
- refreshWorld();
1261
- },
1262
- redo: () => {
1263
- t.position.x = finalPos.x;
1264
- t.position.y = finalPos.y;
1265
- refreshWorld();
1266
- },
1267
- });
1268
- }
1924
+ }
1925
+ },
1926
+ [
1927
+ state.selectedEntities,
1928
+ state.snapToGrid,
1929
+ state.gridSize,
1930
+ screenToWorld,
1931
+ world,
1932
+ dispatch,
1933
+ refreshWorld
1934
+ ]
1935
+ );
1936
+ const handlePointerUp = React.useCallback(() => {
1937
+ if (dragging.current && state.selectedEntities.length > 0) {
1938
+ const entityId = state.selectedEntities[0];
1939
+ const t = world.getComponent(entityId, "Transform");
1940
+ if (t) {
1941
+ const finalPos = { x: t.position.x, y: t.position.y };
1942
+ const startPos = { ...dragEntityStart.current };
1943
+ if (finalPos.x !== startPos.x || finalPos.y !== startPos.y) {
1944
+ undoStack.push({
1945
+ description: "Move entity",
1946
+ undo: () => {
1947
+ t.position.x = startPos.x;
1948
+ t.position.y = startPos.y;
1949
+ refreshWorld();
1950
+ },
1951
+ redo: () => {
1952
+ t.position.x = finalPos.x;
1953
+ t.position.y = finalPos.y;
1954
+ refreshWorld();
1269
1955
  }
1956
+ });
1270
1957
  }
1271
- dragging.current = false;
1272
- panning.current = false;
1273
- }, [state.selectedEntities, world, undoStack, refreshWorld]);
1274
- const handleWheel = React.useCallback((e) => {
1275
- e.preventDefault();
1276
- const factor = e.deltaY > 0 ? 0.9 : 1.1;
1277
- dispatch({ type: 'SET_ZOOM', zoom: state.zoom * factor });
1278
- }, [state.zoom, dispatch]);
1279
- return (jsxRuntime.jsx("canvas", { ref: canvasRef, style: {
1280
- width: '100%',
1281
- height: '100%',
1282
- display: 'block',
1283
- cursor: getCursorForTool(state.tool),
1284
- }, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, onWheel: handleWheel, onContextMenu: (e) => e.preventDefault() }));
1958
+ }
1959
+ }
1960
+ dragging.current = false;
1961
+ panning.current = false;
1962
+ }, [state.selectedEntities, world, undoStack, refreshWorld]);
1963
+ const handleWheel = React.useCallback(
1964
+ (e) => {
1965
+ e.preventDefault();
1966
+ const factor = e.deltaY > 0 ? 0.9 : 1.1;
1967
+ dispatch({ type: "SET_ZOOM", zoom: state.zoom * factor });
1968
+ },
1969
+ [state.zoom, dispatch]
1970
+ );
1971
+ return /* @__PURE__ */ jsxRuntime.jsx(
1972
+ "canvas",
1973
+ {
1974
+ ref: canvasRef,
1975
+ style: {
1976
+ width: "100%",
1977
+ height: "100%",
1978
+ display: "block",
1979
+ cursor: getCursorForTool(state.tool)
1980
+ },
1981
+ onPointerDown: handlePointerDown,
1982
+ onPointerMove: handlePointerMove,
1983
+ onPointerUp: handlePointerUp,
1984
+ onWheel: handleWheel,
1985
+ onContextMenu: (e) => e.preventDefault()
1986
+ }
1987
+ );
1285
1988
  };
1286
-
1287
1989
  exports.NiceGameEditor = NiceGameEditor;
1288
1990
  exports.UndoStack = UndoStack;
1289
1991
  exports.deserializeProject = deserializeProject;