feat: 50 игр на Lua + импорт Roblox для всех + поддержка Lua в плеере #39

Merged
min merged 215 commits from feat/lua-50-games-bundle into main 2026-06-09 21:59:25 +00:00
2 changed files with 555 additions and 1 deletions
Showing only changes of commit a1faf237a1 - Show all commits

504
RUBLOX_LUA_API.md Normal file
View File

@ -0,0 +1,504 @@
# Lua API Рублокса (справочник для скриптеров)
Этот документ — полный список того, что работает в Lua-скриптах Рублокса.
API максимально приближен к Roblox, чтобы можно было переносить чужие
скрипты с минимальными правками.
> **Как переключить скрипт на Lua:** в шапке вкладки редактора кода кликни
> по переключателю **JS / Lua**. Подсветка синтаксиса и автодополнение
> автоматически переключатся.
---
## Содержание
1. [Базовые типы](#базовые-типы)
2. [DataModel: game, workspace, Players](#datamodel)
3. [Part — куб на сцене](#part)
4. [Создание и удаление](#создание-и-удаление)
5. [События: Touched, Heartbeat, RemoteEvent](#события)
6. [Таймеры: task.wait, task.delay](#таймеры)
7. [GUI: TextLabel, TextButton, Frame](#gui)
8. [Звук: Sound](#звук)
9. [Анимации: TweenService](#tweenservice)
10. [Игрок: Humanoid, LocalPlayer](#игрок)
11. [Чего пока нет](#чего-пока-нет)
---
## Базовые типы
### `Vector3`
```lua
local v = Vector3.new(1, 2, 3)
print(v.X, v.Y, v.Z) -- 1 2 3
print(v.Magnitude) -- 3.7416... (длина)
print(v.Unit) -- нормализованный
print(v:Dot(otherVec)) -- скалярное произведение
print(v:Cross(otherVec)) -- векторное произведение
local mid = v:Lerp(otherVec, 0.5) -- линейная интерполяция
-- Константы:
Vector3.zero -- (0,0,0)
Vector3.one -- (1,1,1)
Vector3.xAxis -- (1,0,0)
Vector3.yAxis, Vector3.zAxis
```
Поддержаны операторы: `+`, `-`, `*` (на число), `/`, унарный `-`.
### `Color3`
```lua
local c = Color3.new(0.5, 0.2, 0.8) -- 0..1 каждый
local c2 = Color3.fromRGB(255, 128, 0) -- 0..255
local c3 = Color3.fromHSV(0.1, 0.8, 1)
local c4 = Color3.fromHex("#FF8000")
local mid = c:Lerp(c2, 0.5)
print(c:ToHex()) -- "#7F33CC"
```
### `UDim2` / `UDim` / `Vector2`
Для GUI-координат:
```lua
local pos = UDim2.new(0.5, 0, 0.5, 0) -- центр экрана (scale/offset)
local pos2 = UDim2.fromScale(0.2, 0.1)
local pos3 = UDim2.fromOffset(100, 50) -- в пикселях
```
### `CFrame`
```lua
local cf = CFrame.new(0, 10, 0) -- позиция
local cf2 = CFrame.lookAt(eye, target) -- упрощённый
print(cf.Position) -- Vector3
```
### `Enum`
```lua
Enum.KeyCode.W
Enum.KeyCode.Space
Enum.Material.Plastic, Enum.Material.Neon, Enum.Material.Wood
Enum.UserInputType.MouseButton1
Enum.HumanoidStateType.Running
```
---
## DataModel
Виртуальное дерево, как в Roblox:
```lua
game -- корневой DataModel
game.Workspace -- = workspace (короче)
game.Players -- сервис игроков
game.Players.LocalPlayer -- локальный игрок
game.ReplicatedStorage -- хранилище общих ресурсов
game.StarterGui -- стартовое GUI
game.Lighting -- свет
```
Методы:
```lua
local svc = game:GetService("RunService")
local part = workspace:FindFirstChild("Coin")
local part2 = workspace:FindFirstChildOfClass("Part")
local all = workspace:GetChildren() -- массив всех детей
local descendants = workspace:GetDescendants()
local sib = workspace.Coin:FindFirstAncestorOfClass("Workspace")
print(workspace:IsA("Workspace")) -- true
```
---
## Part
`Part` — куб/сфера/цилиндр на сцене. **Это обёртка над примитивом Рублокса.**
Скрипт привязанный к кубу получает его через `script.Parent`:
```lua
-- script.Parent — Part к которому прицеплен скрипт
print(script.Parent.Name) -- "Part_1"
-- Чтение свойств
print(script.Parent.Position) -- Vector3
print(script.Parent.Size) -- Vector3
print(script.Parent.Color) -- Color3
print(script.Parent.Anchored) -- bool
print(script.Parent.CanCollide) -- bool
print(script.Parent.Transparency) -- 0..1
-- Запись (двигает куб в реальном времени!)
script.Parent.Position = Vector3.new(0, 10, 0)
script.Parent.Size = Vector3.new(5, 1, 5)
script.Parent.Color = Color3.fromRGB(255, 0, 0)
script.Parent.Anchored = false -- куб начнёт падать (физика)
script.Parent.Transparency = 0.5 -- полупрозрачный
script.Parent.CFrame = CFrame.new(0, 20, 0)
```
---
## Создание и удаление
### `Instance.new`
```lua
-- Создать Part на сцене
local p = Instance.new("Part")
p.Position = Vector3.new(0, 5, 0)
p.Size = Vector3.new(2, 2, 2)
p.Color = Color3.fromRGB(255, 100, 0)
p.Anchored = true
p.Parent = workspace
-- Удалить через 3 секунды
task.delay(3, function()
p:Destroy()
end)
```
Поддержанные классы:
- **Сцена:** `Part`, `WedgePart`, `MeshPart`
- **События:** `RemoteEvent`, `BindableEvent`
- **GUI:** `ScreenGui`, `Frame`, `TextLabel`, `TextButton`, `ImageLabel`,
`ImageButton`, `TextBox`, `ScrollingFrame`
- **Звук:** `Sound`
- **Прочее:** `Folder`, `Humanoid`, `Configuration`, любой `ClassName`
---
## События
### `script.Parent.Touched` — касание игрока
```lua
script.Parent.Touched:Connect(function(hit)
print("Игрок коснулся!", hit.Name)
local h = game.Players.LocalPlayer.Character:FindFirstChildOfClass("Humanoid")
if h then
h:TakeDamage(100) -- KillBrick
end
end)
```
### `RunService.Heartbeat` — каждый кадр
```lua
local RunService = game:GetService("RunService")
RunService.Heartbeat:Connect(function(dt)
-- dt — время с прошлого кадра (~0.016)
script.Parent.Position = script.Parent.Position + Vector3.new(0, 0.1, 0)
end)
```
### `BindableEvent` / `RemoteEvent` — общение между скриптами
```lua
-- Скрипт A создаёт событие в общем месте
local event = Instance.new("BindableEvent")
event.Name = "MyEvent"
event.Parent = game.ReplicatedStorage
-- Скрипт B подписывается
local event = game.ReplicatedStorage:WaitForChild("MyEvent")
event.Event:Connect(function(msg, num)
print("Получено:", msg, num)
end)
-- Скрипт A триггерит
event:Fire("привет", 42)
```
### `Humanoid.Died`
```lua
local h = game.Players.LocalPlayer.Character:FindFirstChildOfClass("Humanoid")
h.Died:Connect(function()
print("игрок умер")
end)
h.HealthChanged:Connect(function(newHp)
print("здоровье:", newHp)
end)
```
---
## Таймеры
### `task.wait(сек)` — приостановить скрипт
```lua
print("сейчас")
task.wait(1)
print("через секунду")
```
`task.wait` **не блокирует** другие скрипты — это yield через coroutines.
Можно использовать в `while true do ... task.wait(0.1) end` без проблем.
### `task.delay(сек, fn)` — выполнить через
```lua
task.delay(2, function()
print("через 2 секунды")
end)
```
### `task.spawn(fn)` — асинхронно
```lua
task.spawn(function()
print("параллельно с основным потоком")
end)
```
---
## GUI
### Базовая иерархия
```lua
-- ScreenGui — корень всех GUI
local sg = Instance.new("ScreenGui")
sg.Parent = game.Players.LocalPlayer.PlayerGui
-- TextLabel — статичный текст
local label = Instance.new("TextLabel")
label.Parent = sg
label.Text = "Привет!"
label.TextColor3 = Color3.fromRGB(255, 255, 0)
label.BackgroundColor3 = Color3.fromRGB(50, 30, 20)
label.Position = UDim2.new(0.4, 0, 0.1, 0) -- 40% от ширины, 10% от высоты
label.Size = UDim2.new(0.2, 0, 0.05, 0)
label.TextSize = 24
-- TextButton — кликабельная кнопка
local btn = Instance.new("TextButton")
btn.Parent = sg
btn.Text = "Нажми"
btn.Position = UDim2.new(0.4, 0, 0.5, 0)
btn.Size = UDim2.new(0.2, 0, 0.08, 0)
btn.MouseButton1Click:Connect(function()
print("Клик!")
label.Text = "Нажата!"
end)
```
### Свойства
| Свойство | Тип | Описание |
|----------------------|-----------|-----------------------------------|
| `Text` | string | Видимый текст |
| `TextColor3` | Color3 | Цвет текста |
| `TextSize` | number | Размер шрифта |
| `BackgroundColor3` | Color3 | Цвет фона |
| `BackgroundTransparency` | 0..1 | 0=сплошной, 1=прозрачный |
| `Position` | UDim2 | Позиция (scale=%, offset=px/10) |
| `Size` | UDim2 | Размер |
| `Visible` | bool | Виден или нет |
### События кнопок
```lua
btn.MouseButton1Click:Connect(fn) -- ЛКМ клик
btn.MouseEnter:Connect(fn) -- наведение
btn.MouseLeave:Connect(fn) -- увод
btn.Activated:Connect(fn) -- = MouseButton1Click
```
---
## Звук
```lua
local sound = Instance.new("Sound")
sound.SoundId = "coin" -- или "jump", "win", "lose", "hit", "click", "pickup"
sound.Volume = 1 -- 0..2
sound.PlaybackSpeed = 1 -- pitch
sound:Play()
```
Также Roblox-AssetID работает с эвристикой:
```lua
sound.SoundId = "rbxassetid://1234567890" -- автоподбор по имени переменной
```
Поддержанные звуки (процедурные, не из файлов):
- `jump` — прыжок
- `pickup` — подбор
- `coin` — звон монеты
- `win` — победа
- `lose` — поражение
- `click` — клик
- `hit` — удар
Зацикливание:
```lua
sound.Looped = true
sound:Play() -- играет до sound:Stop()
```
---
## TweenService
Плавная анимация свойств:
```lua
local TweenService = game:GetService("TweenService")
local part = script.Parent
local tween = TweenService:Create(
part,
{ Time = 2 }, -- длительность 2 сек
{ Position = Vector3.new(0, 20, 0),
Color = Color3.fromRGB(255, 0, 0) } -- цели
)
tween:Play()
tween.Completed:Connect(function()
print("Анимация завершилась!")
end)
```
Работает с `Position`, `Size`, `Color` (Vector3/Color3) и числовыми
свойствами (`Transparency`, `TextSize`, и т.д.).
---
## Игрок
### `game.Players.LocalPlayer`
```lua
local plr = game.Players.LocalPlayer
print(plr.Name, plr.UserId, plr.DisplayName)
print(plr.Character) -- Model
```
### `Humanoid`
```lua
local char = game.Players.LocalPlayer.Character
local h = char:FindFirstChildOfClass("Humanoid")
print(h.Health, h.MaxHealth)
print(h.WalkSpeed) -- скорость ходьбы
print(h.JumpPower) -- сила прыжка
h.Health = 0 -- мгновенная смерть → респавн
h:TakeDamage(50) -- урон с учётом invulnerability
h.Died:Connect(function()
print("Помер")
end)
h.HealthChanged:Connect(function(newHp)
if newHp < 30 then
print("Здоровье низкое!")
end
end)
```
### `HumanoidRootPart`
```lua
local hrp = char:FindFirstChild("HumanoidRootPart")
print(hrp.Position)
```
---
## Чего пока нет
Не работает (пока):
- **Скрипты не делятся на Server/LocalScript** — все скрипты client-side.
- **DataStoreService** — методы есть, но возвращают nil/no-op.
- **`workspace:Raycast`** / **`game.Lighting.ClockTime`** — заглушки.
- **`Players.PlayerAdded`** — никогда не фейерится (только один игрок).
- **3D-анимации (`Animation` instance + `AnimationController`)**
`LoadAnimation` возвращает заглушку.
- **`Sound` из файлов** — только встроенные процедурные.
- **`SurfaceGui` / `BillboardGui`** — нет, только `ScreenGui`.
- **`Model:MoveTo` / `:SetPrimaryPartCFrame`** — нет.
- **Networking (`RemoteFunction:InvokeServer`)** — RemoteEvent работает
только в пределах одного клиента.
Если что-то из этого критично — открой issue в репо.
---
## Пример: KillBrick + монета + GUI-счётчик
Положи 1 куб и 1 шарик на сцене. К каждому привяжи скрипт:
**На кубе (KillBrick):**
```lua
script.Parent.Color = Color3.fromRGB(200, 30, 30)
script.Parent.Touched:Connect(function()
local h = game.Players.LocalPlayer.Character:FindFirstChildOfClass("Humanoid")
if h then h:TakeDamage(100) end
end)
```
**На шарике (Coin):**
```lua
script.Parent.Color = Color3.fromRGB(255, 215, 0)
script.Parent.Touched:Connect(function()
-- Запускаем событие на ReplicatedStorage
local re = game.ReplicatedStorage:FindFirstChild("CoinPicked")
if not re then
re = Instance.new("BindableEvent")
re.Name = "CoinPicked"
re.Parent = game.ReplicatedStorage
end
re:Fire()
script.Parent:Destroy()
end)
```
**Глобальный скрипт (GUI):**
```lua
local sg = Instance.new("ScreenGui")
sg.Parent = game.Players.LocalPlayer.PlayerGui
local label = Instance.new("TextLabel")
label.Parent = sg
label.Text = "Монет: 0"
label.Position = UDim2.new(0.05, 0, 0.05, 0)
label.Size = UDim2.new(0.1, 0, 0.05, 0)
label.TextSize = 20
label.TextColor3 = Color3.fromRGB(255, 215, 0)
local count = 0
task.spawn(function()
while not game.ReplicatedStorage:FindFirstChild("CoinPicked") do
task.wait(0.1)
end
game.ReplicatedStorage.CoinPicked.Event:Connect(function()
count = count + 1
label.Text = "Монет: " .. count
local sound = Instance.new("Sound")
sound.SoundId = "coin"
sound:Play()
end)
end)
```
Получится: красный куб убивает, золотая монета даёт +1 к счётчику со
звуком.
---
**Версия документации:** Этап 7 (готово после реализации Этапов 1-6).
Если что-то описанное здесь не работает — это баг, репортуй.

View File

@ -610,7 +610,10 @@ export function registerRobloxShim(lua, opts) {
makeService('Lighting').Ambient = new RbxColor3(0.5, 0.5, 0.5); makeService('Lighting').Ambient = new RbxColor3(0.5, 0.5, 0.5);
makeService('Chat'); makeService('Chat');
makeService('SoundService'); const soundService = makeService('SoundService');
soundService.PlayLocalSound = function (sound) {
if (sound && typeof sound.Play === 'function') sound.Play();
};
makeService('PathfindingService'); makeService('PathfindingService');
makeService('CollectionService'); makeService('CollectionService');
makeService('MarketplaceService'); makeService('MarketplaceService');
@ -829,6 +832,53 @@ export function registerRobloxShim(lua, opts) {
inst.Health = 100; inst.MaxHealth = 100; inst.Health = 100; inst.MaxHealth = 100;
inst.Died = makeSignal(); inst.HealthChanged = makeSignal(); inst.Died = makeSignal(); inst.HealthChanged = makeSignal();
inst.TakeDamage = function (n) { this.Health = Math.max(0, this.Health - (n || 0)); }; inst.TakeDamage = function (n) { this.Health = Math.max(0, this.Health - (n || 0)); };
} else if (className === 'Sound') {
// Sound — процедурные звуки через _playSound.
// SoundId → имя процедурного звука (rbxassetid игнорится).
inst = newInstance('Sound', 'Sound');
inst.SoundId = '';
inst.Volume = 1;
inst.PlaybackSpeed = 1;
inst.Pitch = 1;
inst.Looped = false;
inst.IsPlaying = false;
inst.Played = makeSignal();
inst.Ended = makeSignal();
// Map SoundId/имя на встроенный звук (jump/pickup/win/lose/click/hit/coin).
const _mapSoundName = (idOrName) => {
if (!idOrName) return 'click';
const s = String(idOrName).toLowerCase();
// Прямые ключи имеют приоритет
if (['jump','pickup','win','lose','click','hit','coin'].indexOf(s) >= 0) return s;
// Эвристика по части строки (для Roblox AssetID)
if (s.includes('jump')) return 'jump';
if (s.includes('pickup') || s.includes('collect')) return 'pickup';
if (s.includes('win') || s.includes('victory')) return 'win';
if (s.includes('lose') || s.includes('death')) return 'lose';
if (s.includes('hit') || s.includes('damage')) return 'hit';
if (s.includes('coin') || s.includes('gem')) return 'coin';
return 'click';
};
inst.Play = function () {
const name = _mapSoundName(this.SoundId || this.Name);
const pitch = +this.PlaybackSpeed || +this.Pitch || 1;
const volume = +this.Volume || 1;
send('sound.play', { name, volume, pitch });
this.IsPlaying = true;
this.Played.Fire();
// Простая модель: считаем что звук длится 0.5с
SCHEDULER.sleeping.push({
wakeAt: SCHEDULER.now() + 500,
run: () => {
this.IsPlaying = false;
this.Ended.Fire();
if (this.Looped) this.Play();
},
});
};
inst.Stop = function () { this.IsPlaying = false; };
inst.Pause = function () { this.IsPlaying = false; };
inst.Resume = function () { if (!this.IsPlaying) this.Play(); };
} else if (className === 'ScreenGui') { } else if (className === 'ScreenGui') {
// ScreenGui — логический корень GUI. В Rublox overlay глобальный, // ScreenGui — логический корень GUI. В Rublox overlay глобальный,
// поэтому ScreenGui это просто контейнер-no-op (без gui.create). // поэтому ScreenGui это просто контейнер-no-op (без gui.create).