diff --git a/rbxl-importer/src/converter.py b/rbxl-importer/src/converter.py index 8d23375..9b4b158 100644 --- a/rbxl-importer/src/converter.py +++ b/rbxl-importer/src/converter.py @@ -725,22 +725,28 @@ class Converter: return None # top-level в ScreenGui = parentId=None в Rublox return f'rbx_gui_{parent_ref}' - def _udim2_to_rublox(self, udim2, default_scale=None) -> Tuple[float, float]: - """Roblox UDim2(scale, offset) → pixel x/y для Rublox GUI. - Берём только offset (scale*screen игнорим: точную viewport-ширину не знаем - в момент импорта). Если offset=0 а scale≠0 — used 1280×720 как референс. + def _udim2_to_rublox(self, udim2, axis: str = 'x') -> int: + """Roblox UDim2(scale, offset) → pixel размер для Rublox GUI. + Reference viewport: 1280×720 (стандарт Roblox при импорте). + axis='x' → используется ширина 1280, 'y' → высота 720. """ if udim2 is None: + return 0 + ref = 1280 if axis == 'x' else 720 + if isinstance(udim2, dict): + scale = udim2.get('scale', 0) or 0 + offset = udim2.get('offset', 0) or 0 + return int(offset + scale * ref) + return 0 + + def _udim2_pair(self, udim2) -> Tuple[int, int]: + """UDim2 = {x: UDim, y: UDim} → (px, py).""" + if not isinstance(udim2, dict): return (0, 0) - # Формат: UDim2 = {x: UDim(scale, offset), y: UDim(scale, offset)} - x_obj = udim2.get('x') if isinstance(udim2, dict) else None - y_obj = udim2.get('y') if isinstance(udim2, dict) else None - def resolve(u): - if not u: return 0 - scale = u.get('scale', 0) if isinstance(u, dict) else 0 - offset = u.get('offset', 0) if isinstance(u, dict) else 0 - return int(offset + scale * 1280) - return (resolve(x_obj), resolve(y_obj)) + return ( + self._udim2_to_rublox(udim2.get('x'), 'x'), + self._udim2_to_rublox(udim2.get('y'), 'y'), + ) def _color3_to_hex(self, c3) -> str: if c3 is None: @@ -770,12 +776,19 @@ class Converter: else: r_type = 'frame' - pos_x, pos_y = self._udim2_to_rublox(props.get('Position')) - size_x, size_y = self._udim2_to_rublox(props.get('Size')) + pos_x, pos_y = self._udim2_pair(props.get('Position')) + size_x, size_y = self._udim2_pair(props.get('Size')) # Size может быть 0×0 (если только scale) — дефолтим в 100×30 if size_x <= 0: size_x = 100 if size_y <= 0: size_y = 30 + # Фильтр Roblox CDN URL'ов: rbxasset://, rbxassetid://, rbxhttp:// — + # браузер их не поймёт, даём пустую строку. В будущем asset_downloader + # может подменить на cached_url. + raw_image = str(props.get('Image', '') or '') + if raw_image.startswith(('rbxasset://', 'rbxassetid://', 'rbxhttp://', 'rbxthumb://')): + raw_image = '' + element = { 'id': f'rbx_gui_{inst.referent}', 'type': r_type, @@ -797,7 +810,7 @@ class Converter: 'textSize': int(props.get('TextSize', 14) or 14), 'textAlign': 'center', 'fontWeight': 700 if cls in ('TextButton',) else 500, - 'imageUrl': str(props.get('Image', '') or ''), + 'imageUrl': raw_image, 'imageAsset': None, 'zIndex': int(props.get('ZIndex', 1) or 1), 'origin': 'roblox-' + cls.lower(), diff --git a/src/editor/engine/rbxl-lua-integration.js b/src/editor/engine/rbxl-lua-integration.js index d8258ac..4c07fba 100644 --- a/src/editor/engine/rbxl-lua-integration.js +++ b/src/editor/engine/rbxl-lua-integration.js @@ -13,7 +13,7 @@ import { RobloxLuaSharedSandbox } from './RobloxLuaSharedSandbox.js'; /** * Распаковывает Lua-исходник из поля code упакованного rbxl-importer'ом. - * Формат: `// @roblox-lua\n// {meta json}\n/* lua_source:\n<код>\n*/` + * Формат: "// @roblox-lua\\n// {meta json}\\n/[*] lua_source:\\n<код>\\n[*]/" */ export function unpackRobloxLuaCode(code) { const openTag = '/* lua_source:\n'; @@ -153,8 +153,3 @@ export function handleLuaCommand(_scriptId, cmd, payload, runtime) { } } -/* ──────── Legacy single-script API (для обратной совместимости) ──────── */ -// Старая логика per-script Worker оставлена в RobloxLuaSandbox.js + RobloxLuaWorker.js, -// но GameRuntime теперь использует startRobloxLuaShared. Эти экспорты не удалены -// чтобы тесты в player/tests/ продолжали работать. -export { startRobloxLuaShared as startRobloxLuaScript };