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: ( body: (
<> <>
<p>Команды для здоровья игрока:</p> <p>Команды для здоровья игрока:</p>
<table className="docTable"> <LangTabs
<tbody> js={<table className="docTable">
<tr><td><code>game.player.hp</code></td><td>текущее здоровье (можно читать)</td></tr> <tbody>
<tr><td><code>game.player.damage(n)</code></td><td>нанести урон</td></tr> <tr><td><code>game.player.hp</code></td><td>текущее здоровье</td></tr>
<tr><td><code>game.player.heal(n)</code></td><td>вылечить</td></tr> <tr><td><code>game.player.damage(n)</code></td><td>нанести урон</td></tr>
<tr><td><code>game.player.kill()</code></td><td>мгновенно убить</td></tr> <tr><td><code>game.player.heal(n)</code></td><td>вылечить</td></tr>
<tr><td><code>game.player.respawn()</code></td><td>воскресить на спавне</td></tr> <tr><td><code>game.player.kill()</code></td><td>мгновенно убить</td></tr>
<tr><td><code>game.player.setSpawn(точка)</code></td><td>новая точка возрождения</td></tr> <tr><td><code>game.player.respawn()</code></td><td>воскресить на спавне</td></tr>
</tbody> <tr><td><code>game.player.setSpawn(точка)</code></td><td>новая точка возрождения</td></tr>
</table> </tbody>
</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> <p><b>Пример 1 шипы наносят урон:</b></p>
<ScriptKind kind="object" on="шипы (конус)" /> <ScriptKind kind="object" on="шипы (конус)" />
<Code>{`game.self.onTouch(() => { <LangTabs
js={<Code>{`game.self.onTouch(() => {
game.player.damage(20); // отнять 20 здоровья game.player.damage(20); // отнять 20 здоровья
game.sound.play('hit'); 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> <p><b>Пример 2 аптечка лечит:</b></p>
<ScriptKind kind="object" on="аптечку" /> <ScriptKind kind="object" on="аптечку" />
<Code>{`game.self.onTouch(() => { <LangTabs
js={<Code>{`game.self.onTouch(() => {
game.player.heal(50); // добавить 50 здоровья game.player.heal(50); // добавить 50 здоровья
game.ui.showText('+50 HP', 1.5); game.ui.showText('+50 HP', 1.5);
game.self.delete(); // аптечка исчезает 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, импульсы, взрывы', title: 'F2. Физика: raycast, импульсы, взрывы',
body: ( body: (
<> <>
<p> <LangTabs
Отдел <code>game.physics</code> отвечает за «настоящую» js={<>
физику: <p>Отдел <code>game.physics</code> отвечает за «настоящую» физику:</p>
</p> <ul>
<ul> <li><code>raycast(откуда, куда, опции)</code> луч для стрельбы;</li>
<li> <li><code>applyImpulse(ref, сила)</code> толкнуть объект;</li>
<code>raycast(откуда, куда, опции)</code> пустить <li><code>explode(точка, радиус, опции)</code> взрыв.</li>
невидимый луч и узнать, во что он попал. Так делают </ul>
стрельбу; </>}
</li> lua={<>
<li> <p>В Lua для физики используется <code>workspace</code> и стандартный Roblox API:</p>
<code>applyImpulse(ref, сила)</code> толкнуть объект <ul>
(он должен быть не закреплён); <li><code>workspace:Raycast(origin, dir, params)</code> луч;</li>
</li> <li><code>part:ApplyImpulse(Vector3)</code> толкнуть Part;</li>
<li> <li><code>Instance.new("Explosion")</code> создать взрыв.</li>
<code>explode(точка, радиус, опции)</code> взрыв. </ul>
</li> </>}
</ul> />
<p><b>Пример стрельба лучом из камеры игрока:</b></p> <p><b>Пример стрельба лучом из камеры игрока:</b></p>
<ScriptKind kind="global" /> <ScriptKind kind="global" />
<Code>{`// При клике мышкой пускаем луч туда, куда смотрит игрок <LangTabs
game.onClick(() => { js={<Code>{`game.onClick(() => {
const p = game.player.position; const p = game.player.position;
const hit = game.physics.raycast( const hit = game.physics.raycast(
{ x: p.x, y: p.y + 1.5, z: p.z }, // откуда (от головы) { x: p.x, y: p.y + 1.5, z: p.z }, // откуда (от головы)
game.player.forward, // куда (взгляд) game.player.forward, // куда (взгляд)
{ maxDistance: 50 } // как далеко { maxDistance: 50 }
); );
if (hit.hit) { if (hit.hit) {
game.log('Попал в объект:', hit.ref); game.log('Попал в объект:', hit.ref);
game.sound.play('hit'); game.sound.play('hit');
} }
});`}</Code> });`}</Code>}
<p> lua={<Code>{`local UIS = game:GetService("UserInputService")
<code>hit.hit</code> попал ли луч во что-нибудь local Players = game:GetService("Players")
(да/нет). <code>hit.ref</code> адрес объекта, в который local player = Players.LocalPlayer
попали. local mouse = player:GetMouse()
</p>
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', id: 'attributes',
title: 'F3. Атрибуты объектов (setData / getData)', title: 'F3. Атрибуты объектов',
body: ( body: (
<> <>
<p> <p>
@ -2219,14 +2280,27 @@ game.onClick(() => {
или сколько монет стоит товар. или сколько монет стоит товар.
</p> </p>
<ScriptKind kind="object" on="товар в магазине" /> <ScriptKind kind="object" on="товар в магазине" />
<Code>{`// При старте игры запоминаем цену прямо на товаре <LangTabs
js={<Code>{`// При старте игры запоминаем цену прямо на товаре
game.scene.setData(game.self.ref, 'price', 50); game.scene.setData(game.self.ref, 'price', 50);
// Когда игрок кликает по товару читаем цену // Когда игрок кликает по товару читаем цену
game.self.onClick(() => { game.self.onClick(() => {
const price = game.scene.getData(game.self.ref, 'price'); const price = game.scene.getData(game.self.ref, 'price');
game.ui.showText('Этот товар стоит ' + price + ' монет', 2); 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> <p>
Чем атрибут лучше обычной переменной? Переменная одна Чем атрибут лучше обычной переменной? Переменная одна
на весь скрипт. А атрибут свой у каждого объекта. на весь скрипт. А атрибут свой у каждого объекта.
@ -2242,36 +2316,61 @@ game.self.onClick(() => {
body: ( body: (
<> <>
<p> <p>
<b>Тег</b> это «ярлык», который можно повесить сразу <b>Тег</b> это «ярлык» на объекте. Удобно ставить сразу на
на много объектов. Потом одной командой можно найти их все. много объектов и потом одной командой находить их все.
</p> </p>
<table className="docTable"> <LangTabs
<tbody> js={<table className="docTable">
<tr><td><code>tag(ref, 'звезда')</code></td><td>повесить тег</td></tr> <tbody>
<tr><td><code>untag(ref, 'звезда')</code></td><td>снять тег</td></tr> <tr><td><code>game.scene.tag(ref, 'звезда')</code></td><td>повесить тег</td></tr>
<tr><td><code>hasTag(ref, 'звезда')</code></td><td>есть ли тег</td></tr> <tr><td><code>game.scene.untag(ref, 'звезда')</code></td><td>снять тег</td></tr>
<tr><td><code>getTagged('звезда')</code></td><td>все объекты с тегом</td></tr> <tr><td><code>game.scene.hasTag(ref, 'звезда')</code></td><td>есть ли тег</td></tr>
</tbody> <tr><td><code>game.scene.getTagged('звезда')</code></td><td>все объекты с тегом</td></tr>
</table> </tbody>
</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> <p><b>Пример игра «собери все звёзды»:</b></p>
<ScriptKind kind="object" on="каждую звезду" /> <ScriptKind kind="object" on="каждую звезду" />
<Code>{`// Этот скрипт висит на звезде. <LangTabs
// При старте помечаем звезду тегом. js={<Code>{`// Этот скрипт висит на звезде.
game.scene.tag(game.self.ref, 'звезда'); game.scene.tag(game.self.ref, 'звезда');
// Когда игрок коснулся звезда собрана
game.self.onTouch(() => { game.self.onTouch(() => {
game.self.delete(); game.self.delete();
game.sound.play('coin'); game.sound.play('coin');
// сколько звёзд ещё осталось на сцене?
const left = game.scene.getTagged('звезда').length; const left = game.scene.getTagged('звезда').length;
if (left === 0) { if (left === 0) {
game.ui.showText('Все звёзды собраны! Победа!', 3); game.ui.showText('Все звёзды собраны! Победа!', 3);
} else { } else {
game.ui.showText('Осталось звёзд: ' + left, 1.5); 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> <Note>
Снятие тега убирает только ярлык. Цвет, размер и другие Снятие тега убирает только ярлык. Цвет, размер и другие
свойства объекта при этом не меняются. свойства объекта при этом не меняются.
@ -2281,16 +2380,16 @@ game.self.onTouch(() => {
}, },
{ {
id: 'proximity', id: 'proximity',
title: 'F5. ProximityPrompt — взаимодействие по клавише E', title: 'F5. Взаимодействие по клавише E (ProximityPrompt)',
body: ( body: (
<> <>
<p> <p>
Часто игра просит «подойди и нажми E»: открыть сундук, Часто игра просит «подойди и нажми E»: открыть сундук,
поговорить с торговцем, дёрнуть рычаг. Это делается поговорить с торговцем, дёрнуть рычаг.
командой <code>game.self.onInteract</code>:
</p> </p>
<ScriptKind kind="object" on="сундук" /> <ScriptKind kind="object" on="сундук" />
<Code>{`game.self.onInteract(() => { <LangTabs
js={<Code>{`game.self.onInteract(() => {
game.ui.showText('Сундук открыт!', 2); game.ui.showText('Сундук открыт!', 2);
game.scene.spawnParticles('sparks', game.scene.spawnParticles('sparks',
game.self.position, { duration: 1 }); game.self.position, { duration: 1 });
@ -2298,11 +2397,26 @@ game.self.onTouch(() => {
}, { }, {
text: 'Открыть сундук', // подсказка над объектом text: 'Открыть сундук', // подсказка над объектом
distance: 4 // на сколько метров подойти 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> <p>
Когда игрок подойдёт ближе чем на <code>distance</code> Когда игрок подойдёт на расстояние взаимодействия, над
метров, над объектом появится подсказка с текстом. объектом появится подсказка с текстом. Нажатие
Нажатие <kbd className="kbd">E</kbd> запустит функцию. <kbd className="kbd">E</kbd> запустит функцию.
</p> </p>
</> </>
), ),
@ -2318,36 +2432,62 @@ game.self.onTouch(() => {
Так показывают имена врагов, их HP, названия мест. Так показывают имена врагов, их HP, названия мест.
</p> </p>
<ScriptKind kind="global" /> <ScriptKind kind="global" />
<Code>{`// Допустим, npc — это адрес созданного NPC. <LangTabs
// Вешаем над ним табличку с именем. js={<Code>{`// npc — это адрес созданного NPC.
game.scene.setLabel(npc.ref, 'Торговец Боб', { game.scene.setLabel(npc.ref, 'Торговец Боб', {
color: '#ffffff', color: '#ffffff',
height: 2.5 // на 2.5 метра над объектом 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', id: 'pass-through',
title: 'F7. Проходимость объектов (passThrough)', title: 'F7. Проходимость объектов',
body: ( body: (
<> <>
<p> <p>
Иногда стена должна стать проходимой призрачная стена, Иногда стена должна стать проходимой призрачная стена,
секретный проход, исчезающий мост. Команда секретный проход, исчезающий мост.
<code> game.physics.passThrough(ref, true)</code> делает
объект «бесплотным»: видно его, но игрок проходит насквозь.
</p> </p>
<ScriptKind kind="object" on="секретную стену" /> <ScriptKind kind="object" on="секретную стену" />
<Code>{`// Когда игрок кликнет по стене — она пропустит сквозь себя <LangTabs
game.self.onClick(() => { js={<Code>{`game.self.onClick(() => {
game.physics.passThrough(game.self.ref, true); game.physics.passThrough(game.self.ref, true);
game.scene.setOpacity(game.self.ref, 0.3); // полупрозрачная game.scene.setOpacity(game.self.ref, 0.3); // полупрозрачная
game.ui.showText('Секретный проход открыт!', 2); 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> <Note>
Если сделать стену снова твёрдой, пока игрок стоит внутри Если сделать стену снова твёрдой, пока игрок стоит внутри
неё игра аккуратно вытолкнет его наружу, он не застрянет. неё игра аккуратно вытолкнет его наружу, он не застрянет.
@ -2362,39 +2502,53 @@ game.self.onClick(() => {
<> <>
<p> <p>
<b>Связи (constraints)</b> соединяют объекты, чтобы они <b>Связи (constraints)</b> соединяют объекты, чтобы они
двигались вместе или по правилам физики. Отдел двигались вместе или по правилам физики.
<code> game.constraints</code>:
</p> </p>
<ul> <ul>
<li> <li><b>Склейка (weld)</b> намертво приклеивает один объект к другому;</li>
<b>Склейка (weld)</b> намертво приклеивает один <li><b>Петля (hinge)</b> объект вращается вокруг оси, как дверь или качели;</li>
объект к другому; <li><b>Пружина (spring)</b> объект упруго колеблется, как батут.</li>
</li>
<li>
<b>Петля (hinge)</b> объект вращается вокруг оси,
как дверь на петлях или качели;
</li>
<li>
<b>Пружина (spring)</b> объект упруго колеблется,
как батут.
</li>
</ul> </ul>
<p><b>Пример качели на петле:</b></p> <p><b>Пример качели на петле:</b></p>
<ScriptKind kind="global" /> <ScriptKind kind="global" />
<Code>{`const swing = game.scene.findOne('Качели'); <LangTabs
js={<Code>{`const swing = game.scene.findOne('Качели');
// делаем качели на петле
const h = game.constraints.hinge(swing, { const h = game.constraints.hinge(swing, {
pivotX: 0, pivotZ: 0, // ось вращения pivotX: 0, pivotZ: 0,
angle: 30 // наклон на 30 градусов angle: 30
}); });
// раскачиваем в другую сторону каждую секунду // раскачиваем в другую сторону каждую секунду
let dir = -30; let dir = -30;
game.every(1, () => { game.every(1, () => {
h.setAngle(dir); h.setAngle(dir);
dir = -dir; // меняем знак: 30 -30 30 ... dir = -dir;
});`}</Code> });`}</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>}
/>
</> </>
), ),
}, },