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
Showing only changes of commit 76fba9cb35 - Show all commits

View File

@ -2134,29 +2134,64 @@ TweenService:Create(door, TweenInfo.new(1), goal):Play()`}</Code>}
body: (
<>
<p>Команды для здоровья игрока:</p>
<table className="docTable">
<LangTabs
js={<table className="docTable">
<tbody>
<tr><td><code>game.player.hp</code></td><td>текущее здоровье (можно читать)</td></tr>
<tr><td><code>game.player.hp</code></td><td>текущее здоровье</td></tr>
<tr><td><code>game.player.damage(n)</code></td><td>нанести урон</td></tr>
<tr><td><code>game.player.heal(n)</code></td><td>вылечить</td></tr>
<tr><td><code>game.player.kill()</code></td><td>мгновенно убить</td></tr>
<tr><td><code>game.player.respawn()</code></td><td>воскресить на спавне</td></tr>
<tr><td><code>game.player.setSpawn(точка)</code></td><td>новая точка возрождения</td></tr>
</tbody>
</table>
</table>}
lua={<table className="docTable">
<tbody>
<tr><td><code>humanoid.Health</code></td><td>текущее здоровье</td></tr>
<tr><td><code>humanoid:TakeDamage(n)</code></td><td>нанести урон</td></tr>
<tr><td><code>humanoid.Health = humanoid.Health + n</code></td><td>вылечить</td></tr>
<tr><td><code>humanoid.Health = 0</code></td><td>мгновенно убить</td></tr>
<tr><td><code>player:LoadCharacter()</code></td><td>воскресить</td></tr>
<tr><td><code>player.RespawnLocation = spawn</code></td><td>новая точка возрождения</td></tr>
</tbody>
</table>}
/>
<p><b>Пример 1 шипы наносят урон:</b></p>
<ScriptKind kind="object" on="шипы (конус)" />
<Code>{`game.self.onTouch(() => {
<LangTabs
js={<Code>{`game.self.onTouch(() => {
game.player.damage(20); // отнять 20 здоровья
game.sound.play('hit');
});`}</Code>
});`}</Code>}
lua={<Code>{`local part = script.Parent
part.Touched:Connect(function(hit)
local humanoid = hit.Parent:FindFirstChild("Humanoid")
if humanoid then
humanoid:TakeDamage(20) -- отнять 20 здоровья
end
end)`}</Code>}
/>
<p><b>Пример 2 аптечка лечит:</b></p>
<ScriptKind kind="object" on="аптечку" />
<Code>{`game.self.onTouch(() => {
<LangTabs
js={<Code>{`game.self.onTouch(() => {
game.player.heal(50); // добавить 50 здоровья
game.ui.showText('+50 HP', 1.5);
game.self.delete(); // аптечка исчезает
});`}</Code>
});`}</Code>}
lua={<Code>{`local part = script.Parent
part.Touched:Connect(function(hit)
local humanoid = hit.Parent:FindFirstChild("Humanoid")
if not humanoid then return end
-- добавить 50 здоровья, но не выше MaxHealth
humanoid.Health = math.min(humanoid.Health + 50, humanoid.MaxHealth)
print("+50 HP")
part:Destroy() -- аптечка исчезает
end)`}</Code>}
/>
</>
),
},
@ -2165,52 +2200,78 @@ TweenService:Create(door, TweenInfo.new(1), goal):Play()`}</Code>}
title: 'F2. Физика: raycast, импульсы, взрывы',
body: (
<>
<p>
Отдел <code>game.physics</code> отвечает за «настоящую»
физику:
</p>
<LangTabs
js={<>
<p>Отдел <code>game.physics</code> отвечает за «настоящую» физику:</p>
<ul>
<li>
<code>raycast(откуда, куда, опции)</code> пустить
невидимый луч и узнать, во что он попал. Так делают
стрельбу;
</li>
<li>
<code>applyImpulse(ref, сила)</code> толкнуть объект
(он должен быть не закреплён);
</li>
<li>
<code>explode(точка, радиус, опции)</code> взрыв.
</li>
<li><code>raycast(откуда, куда, опции)</code> луч для стрельбы;</li>
<li><code>applyImpulse(ref, сила)</code> толкнуть объект;</li>
<li><code>explode(точка, радиус, опции)</code> взрыв.</li>
</ul>
</>}
lua={<>
<p>В Lua для физики используется <code>workspace</code> и стандартный Roblox API:</p>
<ul>
<li><code>workspace:Raycast(origin, dir, params)</code> луч;</li>
<li><code>part:ApplyImpulse(Vector3)</code> толкнуть Part;</li>
<li><code>Instance.new("Explosion")</code> создать взрыв.</li>
</ul>
</>}
/>
<p><b>Пример стрельба лучом из камеры игрока:</b></p>
<ScriptKind kind="global" />
<Code>{`// При клике мышкой пускаем луч туда, куда смотрит игрок
game.onClick(() => {
<LangTabs
js={<Code>{`game.onClick(() => {
const p = game.player.position;
const hit = game.physics.raycast(
{ x: p.x, y: p.y + 1.5, z: p.z }, // откуда (от головы)
game.player.forward, // куда (взгляд)
{ maxDistance: 50 } // как далеко
{ maxDistance: 50 }
);
if (hit.hit) {
game.log('Попал в объект:', hit.ref);
game.sound.play('hit');
}
});`}</Code>
<p>
<code>hit.hit</code> попал ли луч во что-нибудь
(да/нет). <code>hit.ref</code> адрес объекта, в который
попали.
</p>
});`}</Code>}
lua={<Code>{`local UIS = game:GetService("UserInputService")
local Players = game:GetService("Players")
local player = Players.LocalPlayer
local mouse = player:GetMouse()
UIS.InputBegan:Connect(function(input)
if input.UserInputType ~= Enum.UserInputType.MouseButton1 then return end
local hrp = player.Character.HumanoidRootPart
local origin = hrp.Position + Vector3.new(0, 1.5, 0)
local direction = (mouse.Hit.Position - origin).Unit * 50
local raycastResult = workspace:Raycast(origin, direction)
if raycastResult then
print("Попал в объект:", raycastResult.Instance.Name)
end
end)`}</Code>}
/>
<LangTabs
js={<p>
<code>hit.hit</code> попал ли луч во что-нибудь.
<code> hit.ref</code> адрес объекта.
</p>}
lua={<p>
<code>raycastResult</code> равно <code>nil</code> если
луч ни во что не попал. Иначе у него есть поля
<code> .Instance</code> (что попало),
<code> .Position</code> (точка попадания),
<code> .Normal</code> (нормаль поверхности).
</p>}
/>
</>
),
},
{
id: 'attributes',
title: 'F3. Атрибуты объектов (setData / getData)',
title: 'F3. Атрибуты объектов',
body: (
<>
<p>
@ -2219,14 +2280,27 @@ game.onClick(() => {
или сколько монет стоит товар.
</p>
<ScriptKind kind="object" on="товар в магазине" />
<Code>{`// При старте игры запоминаем цену прямо на товаре
<LangTabs
js={<Code>{`// При старте игры запоминаем цену прямо на товаре
game.scene.setData(game.self.ref, 'price', 50);
// Когда игрок кликает по товару читаем цену
game.self.onClick(() => {
const price = game.scene.getData(game.self.ref, 'price');
game.ui.showText('Этот товар стоит ' + price + ' монет', 2);
});`}</Code>
});`}</Code>}
lua={<Code>{`local part = script.Parent
-- При старте игры запоминаем цену прямо на товаре
part:SetAttribute("Price", 50)
-- Когда игрок кликает читаем цену
local clickDetector = Instance.new("ClickDetector", part)
clickDetector.MouseClick:Connect(function(player)
local price = part:GetAttribute("Price")
print("Этот товар стоит " .. price .. " монет")
end)`}</Code>}
/>
<p>
Чем атрибут лучше обычной переменной? Переменная одна
на весь скрипт. А атрибут свой у каждого объекта.
@ -2242,36 +2316,61 @@ game.self.onClick(() => {
body: (
<>
<p>
<b>Тег</b> это «ярлык», который можно повесить сразу
на много объектов. Потом одной командой можно найти их все.
<b>Тег</b> это «ярлык» на объекте. Удобно ставить сразу на
много объектов и потом одной командой находить их все.
</p>
<table className="docTable">
<LangTabs
js={<table className="docTable">
<tbody>
<tr><td><code>tag(ref, 'звезда')</code></td><td>повесить тег</td></tr>
<tr><td><code>untag(ref, 'звезда')</code></td><td>снять тег</td></tr>
<tr><td><code>hasTag(ref, 'звезда')</code></td><td>есть ли тег</td></tr>
<tr><td><code>getTagged('звезда')</code></td><td>все объекты с тегом</td></tr>
<tr><td><code>game.scene.tag(ref, 'звезда')</code></td><td>повесить тег</td></tr>
<tr><td><code>game.scene.untag(ref, 'звезда')</code></td><td>снять тег</td></tr>
<tr><td><code>game.scene.hasTag(ref, 'звезда')</code></td><td>есть ли тег</td></tr>
<tr><td><code>game.scene.getTagged('звезда')</code></td><td>все объекты с тегом</td></tr>
</tbody>
</table>
</table>}
lua={<table className="docTable">
<tbody>
<tr><td><code>CollectionService:AddTag(part, "звезда")</code></td><td>повесить тег</td></tr>
<tr><td><code>CollectionService:RemoveTag(part, "звезда")</code></td><td>снять тег</td></tr>
<tr><td><code>CollectionService:HasTag(part, "звезда")</code></td><td>есть ли тег</td></tr>
<tr><td><code>CollectionService:GetTagged("звезда")</code></td><td>все объекты с тегом</td></tr>
</tbody>
</table>}
/>
<p><b>Пример игра «собери все звёзды»:</b></p>
<ScriptKind kind="object" on="каждую звезду" />
<Code>{`// Этот скрипт висит на звезде.
// При старте помечаем звезду тегом.
<LangTabs
js={<Code>{`// Этот скрипт висит на звезде.
game.scene.tag(game.self.ref, 'звезда');
// Когда игрок коснулся звезда собрана
game.self.onTouch(() => {
game.self.delete();
game.sound.play('coin');
// сколько звёзд ещё осталось на сцене?
const left = game.scene.getTagged('звезда').length;
if (left === 0) {
game.ui.showText('Все звёзды собраны! Победа!', 3);
} else {
game.ui.showText('Осталось звёзд: ' + left, 1.5);
}
});`}</Code>
});`}</Code>}
lua={<Code>{`local CS = game:GetService("CollectionService")
local part = script.Parent
-- Помечаем звезду тегом
CS:AddTag(part, "звезда")
part.Touched:Connect(function()
part:Destroy()
local left = #CS:GetTagged("звезда")
if left == 0 then
print("Все звёзды собраны! Победа!")
else
print("Осталось звёзд: " .. left)
end
end)`}</Code>}
/>
<Note>
Снятие тега убирает только ярлык. Цвет, размер и другие
свойства объекта при этом не меняются.
@ -2281,16 +2380,16 @@ game.self.onTouch(() => {
},
{
id: 'proximity',
title: 'F5. ProximityPrompt — взаимодействие по клавише E',
title: 'F5. Взаимодействие по клавише E (ProximityPrompt)',
body: (
<>
<p>
Часто игра просит «подойди и нажми E»: открыть сундук,
поговорить с торговцем, дёрнуть рычаг. Это делается
командой <code>game.self.onInteract</code>:
поговорить с торговцем, дёрнуть рычаг.
</p>
<ScriptKind kind="object" on="сундук" />
<Code>{`game.self.onInteract(() => {
<LangTabs
js={<Code>{`game.self.onInteract(() => {
game.ui.showText('Сундук открыт!', 2);
game.scene.spawnParticles('sparks',
game.self.position, { duration: 1 });
@ -2298,11 +2397,26 @@ game.self.onTouch(() => {
}, {
text: 'Открыть сундук', // подсказка над объектом
distance: 4 // на сколько метров подойти
});`}</Code>
});`}</Code>}
lua={<Code>{`local part = script.Parent
-- ProximityPrompt стандартный Roblox-способ
local prompt = Instance.new("ProximityPrompt")
prompt.ActionText = "Открыть"
prompt.ObjectText = "Сундук"
prompt.MaxActivationDistance = 4
prompt.KeyboardKeyCode = Enum.KeyCode.E
prompt.Parent = part
prompt.Triggered:Connect(function(player)
print("Сундук открыт!")
-- Можно создать эффект частиц или проиграть звук
end)`}</Code>}
/>
<p>
Когда игрок подойдёт ближе чем на <code>distance</code>
метров, над объектом появится подсказка с текстом.
Нажатие <kbd className="kbd">E</kbd> запустит функцию.
Когда игрок подойдёт на расстояние взаимодействия, над
объектом появится подсказка с текстом. Нажатие
<kbd className="kbd">E</kbd> запустит функцию.
</p>
</>
),
@ -2318,36 +2432,62 @@ game.self.onTouch(() => {
Так показывают имена врагов, их HP, названия мест.
</p>
<ScriptKind kind="global" />
<Code>{`// Допустим, npc — это адрес созданного NPC.
// Вешаем над ним табличку с именем.
<LangTabs
js={<Code>{`// npc — это адрес созданного NPC.
game.scene.setLabel(npc.ref, 'Торговец Боб', {
color: '#ffffff',
height: 2.5 // на 2.5 метра над объектом
});
// Позже можно убрать табличку
game.scene.clearLabel(npc.ref);`}</Code>
game.scene.clearLabel(npc.ref);`}</Code>}
lua={<Code>{`-- BillboardGui в Roblox — это GUI поверх Part
local part = workspace:WaitForChild("NPC")
local billboard = Instance.new("BillboardGui")
billboard.Size = UDim2.new(4, 0, 1, 0)
billboard.StudsOffset = Vector3.new(0, 2.5, 0) -- над объектом
billboard.Parent = part
local label = Instance.new("TextLabel")
label.BackgroundTransparency = 1
label.Size = UDim2.new(1, 0, 1, 0)
label.Text = "Торговец Боб"
label.TextColor3 = Color3.new(1, 1, 1)
label.TextScaled = true
label.Parent = billboard
-- Позже можно убрать табличку
-- billboard:Destroy()`}</Code>}
/>
</>
),
},
{
id: 'pass-through',
title: 'F7. Проходимость объектов (passThrough)',
title: 'F7. Проходимость объектов',
body: (
<>
<p>
Иногда стена должна стать проходимой призрачная стена,
секретный проход, исчезающий мост. Команда
<code> game.physics.passThrough(ref, true)</code> делает
объект «бесплотным»: видно его, но игрок проходит насквозь.
секретный проход, исчезающий мост.
</p>
<ScriptKind kind="object" on="секретную стену" />
<Code>{`// Когда игрок кликнет по стене — она пропустит сквозь себя
game.self.onClick(() => {
<LangTabs
js={<Code>{`game.self.onClick(() => {
game.physics.passThrough(game.self.ref, true);
game.scene.setOpacity(game.self.ref, 0.3); // полупрозрачная
game.ui.showText('Секретный проход открыт!', 2);
});`}</Code>
});`}</Code>}
lua={<Code>{`local part = script.Parent
local clickDetector = Instance.new("ClickDetector", part)
clickDetector.MouseClick:Connect(function(player)
part.CanCollide = false -- игрок проходит насквозь
part.Transparency = 0.7 -- полупрозрачная (0=видна, 1=невидима)
print("Секретный проход открыт!")
end)`}</Code>}
/>
<Note>
Если сделать стену снова твёрдой, пока игрок стоит внутри
неё игра аккуратно вытолкнет его наружу, он не застрянет.
@ -2362,39 +2502,53 @@ game.self.onClick(() => {
<>
<p>
<b>Связи (constraints)</b> соединяют объекты, чтобы они
двигались вместе или по правилам физики. Отдел
<code> game.constraints</code>:
двигались вместе или по правилам физики.
</p>
<ul>
<li>
<b>Склейка (weld)</b> намертво приклеивает один
объект к другому;
</li>
<li>
<b>Петля (hinge)</b> объект вращается вокруг оси,
как дверь на петлях или качели;
</li>
<li>
<b>Пружина (spring)</b> объект упруго колеблется,
как батут.
</li>
<li><b>Склейка (weld)</b> намертво приклеивает один объект к другому;</li>
<li><b>Петля (hinge)</b> объект вращается вокруг оси, как дверь или качели;</li>
<li><b>Пружина (spring)</b> объект упруго колеблется, как батут.</li>
</ul>
<p><b>Пример качели на петле:</b></p>
<ScriptKind kind="global" />
<Code>{`const swing = game.scene.findOne('Качели');
<LangTabs
js={<Code>{`const swing = game.scene.findOne('Качели');
// делаем качели на петле
const h = game.constraints.hinge(swing, {
pivotX: 0, pivotZ: 0, // ось вращения
angle: 30 // наклон на 30 градусов
pivotX: 0, pivotZ: 0,
angle: 30
});
// раскачиваем в другую сторону каждую секунду
let dir = -30;
game.every(1, () => {
h.setAngle(dir);
dir = -dir; // меняем знак: 30 -30 30 ...
});`}</Code>
dir = -dir;
});`}</Code>}
lua={<Code>{`-- В Roblox HingeConstraint — стандартный способ
local swing = workspace:WaitForChild("Качели")
local mount = workspace:WaitForChild("Опора") -- неподвижная точка
-- Attachment'ы (точки крепления)
local a0 = Instance.new("Attachment", mount)
local a1 = Instance.new("Attachment", swing)
local hinge = Instance.new("HingeConstraint")
hinge.Attachment0 = a0
hinge.Attachment1 = a1
hinge.ActuatorType = Enum.ActuatorType.Servo
hinge.ServoMaxTorque = 10000
hinge.AngularSpeed = 2
hinge.Parent = swing
-- Раскачиваем
local dir = 30
while true do
hinge.TargetAngle = dir
task.wait(1)
dir = -dir
end`}</Code>}
/>
</>
),
},