feat: 50 игр на Lua + импорт Roblox для всех + поддержка Lua в плеере #39
@ -3835,15 +3835,13 @@ game.onTick(() => {
|
||||
},
|
||||
{
|
||||
id: 'recipes-touch',
|
||||
title: 'S2. Касание объекта (onTouch)',
|
||||
title: 'S2. Касание объекта',
|
||||
body: (
|
||||
<>
|
||||
<p>
|
||||
Самое частое событие — <b>игрок коснулся объекта</b>. Вешаем
|
||||
скрипт на объект и подписываемся через <code>game.self.onTouch</code>.
|
||||
</p>
|
||||
<p>Самое частое событие — <b>игрок коснулся объекта</b>.</p>
|
||||
<ScriptKind kind="object" on="любой объект (плита, зона, предмет)" />
|
||||
<Code>{`// Игрок наступил на объект — показать надпись и звук
|
||||
<LangTabs
|
||||
js={<Code>{`// Игрок наступил на объект — показать надпись и звук
|
||||
game.self.onTouch(() => {
|
||||
game.ui.showText('Ты коснулся плиты!', 2);
|
||||
game.sound.play('click');
|
||||
@ -3852,14 +3850,36 @@ game.self.onTouch(() => {
|
||||
// Когда игрок ушёл с объекта
|
||||
game.self.onUntouch(() => {
|
||||
game.ui.showText('Отошёл', 1);
|
||||
});`}</Code>
|
||||
<p>
|
||||
Можно подписаться и на <b>чужой</b> объект из глобального
|
||||
скрипта — найди его по имени:
|
||||
</p>
|
||||
});`}</Code>}
|
||||
lua={<Code>{`local part = script.Parent
|
||||
|
||||
-- Игрок наступил
|
||||
part.Touched:Connect(function(hit)
|
||||
local player = game:GetService("Players"):GetPlayerFromCharacter(hit.Parent)
|
||||
if player then
|
||||
print("Ты коснулся плиты!")
|
||||
end
|
||||
end)
|
||||
|
||||
-- Игрок ушёл
|
||||
part.TouchEnded:Connect(function(hit)
|
||||
local player = game:GetService("Players"):GetPlayerFromCharacter(hit.Parent)
|
||||
if player then
|
||||
print("Отошёл")
|
||||
end
|
||||
end)`}</Code>}
|
||||
/>
|
||||
<p>Подписаться на <b>чужой</b> объект из глобального скрипта:</p>
|
||||
<ScriptKind kind="global" />
|
||||
<Code>{`const trap = game.scene.findOne('Ловушка');
|
||||
trap.onTouch(() => game.player.damage(20));`}</Code>
|
||||
<LangTabs
|
||||
js={<Code>{`const trap = game.scene.findOne('Ловушка');
|
||||
trap.onTouch(() => game.player.damage(20));`}</Code>}
|
||||
lua={<Code>{`local trap = workspace:WaitForChild("Ловушка")
|
||||
trap.Touched:Connect(function(hit)
|
||||
local humanoid = hit.Parent:FindFirstChild("Humanoid")
|
||||
if humanoid then humanoid:TakeDamage(20) end
|
||||
end)`}</Code>}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
@ -3870,30 +3890,65 @@ trap.onTouch(() => game.player.damage(20));`}</Code>
|
||||
<>
|
||||
<p>
|
||||
<b>Килблок</b> — объект, который наносит урон или мгновенно
|
||||
убивает, когда игрок его коснулся (лава, шипы, кислота).
|
||||
убивает при касании (лава, шипы, кислота).
|
||||
</p>
|
||||
<ScriptKind kind="object" on="блок-ловушку (лава, шипы)" />
|
||||
<Code>{`// Мгновенная смерть при касании
|
||||
<LangTabs
|
||||
js={<Code>{`// Мгновенная смерть при касании
|
||||
game.self.onTouch(() => {
|
||||
game.player.kill();
|
||||
game.ui.showText('💀 Ты сгорел в лаве!', 2);
|
||||
});`}</Code>
|
||||
game.ui.showText('Ты сгорел в лаве!', 2);
|
||||
});`}</Code>}
|
||||
lua={<Code>{`local part = script.Parent
|
||||
|
||||
part.Touched:Connect(function(hit)
|
||||
local humanoid = hit.Parent:FindFirstChild("Humanoid")
|
||||
if humanoid then
|
||||
humanoid.Health = 0 -- мгновенная смерть
|
||||
end
|
||||
end)`}</Code>}
|
||||
/>
|
||||
<p>Если хочешь не убивать сразу, а наносить урон:</p>
|
||||
<Code>{`// Урон 25 при касании (учитывает кадры неуязвимости)
|
||||
game.self.onTouch(() => {
|
||||
<LangTabs
|
||||
js={<Code>{`game.self.onTouch(() => {
|
||||
game.player.damage(25);
|
||||
game.camera.shake(0.2, 0.3); // лёгкая тряска
|
||||
});`}</Code>
|
||||
<p>
|
||||
<b>Постоянный урон</b>, пока игрок стоит в зоне (например,
|
||||
ядовитое облако) — урон каждые 0.5 сек, пока касается:
|
||||
</p>
|
||||
<Code>{`let inside = false;
|
||||
game.camera.shake(0.2, 0.3);
|
||||
});`}</Code>}
|
||||
lua={<Code>{`local part = script.Parent
|
||||
|
||||
part.Touched:Connect(function(hit)
|
||||
local humanoid = hit.Parent:FindFirstChild("Humanoid")
|
||||
if humanoid then humanoid:TakeDamage(25) end
|
||||
end)`}</Code>}
|
||||
/>
|
||||
<p><b>Постоянный урон</b>, пока игрок стоит в зоне:</p>
|
||||
<LangTabs
|
||||
js={<Code>{`let inside = false;
|
||||
game.self.onTouch(() => { inside = true; });
|
||||
game.self.onUntouch(() => { inside = false; });
|
||||
game.every(0.5, () => {
|
||||
if (inside) game.player.damage(5);
|
||||
});`}</Code>
|
||||
});`}</Code>}
|
||||
lua={<Code>{`local part = script.Parent
|
||||
local inside = {} -- humanoid → true
|
||||
|
||||
part.Touched:Connect(function(hit)
|
||||
local h = hit.Parent:FindFirstChild("Humanoid")
|
||||
if h then inside[h] = true end
|
||||
end)
|
||||
part.TouchEnded:Connect(function(hit)
|
||||
local h = hit.Parent:FindFirstChild("Humanoid")
|
||||
if h then inside[h] = nil end
|
||||
end)
|
||||
|
||||
-- Урон каждые 0.5 сек пока стоит
|
||||
while true do
|
||||
task.wait(0.5)
|
||||
for h in pairs(inside) do
|
||||
if h.Parent then h:TakeDamage(5) end
|
||||
end
|
||||
end`}</Code>}
|
||||
/>
|
||||
<Try>
|
||||
Сделай красный неоновый куб, повесь на него скрипт смерти —
|
||||
получится лава. Поставь его в проёме как преграду.
|
||||
@ -3907,38 +3962,73 @@ game.every(0.5, () => {
|
||||
body: (
|
||||
<>
|
||||
<p>
|
||||
Предмет <b>исчезает</b>, когда игрок его коснулся — основа
|
||||
сбора монеток, ключей, бонусов.
|
||||
Предмет <b>исчезает</b> при касании — основа сбора монет.
|
||||
</p>
|
||||
<ScriptKind kind="object" on="монетку / собираемый предмет" />
|
||||
<Code>{`// Простое исчезновение + звук
|
||||
game.self.onTouch(() => {
|
||||
<LangTabs
|
||||
js={<Code>{`game.self.onTouch(() => {
|
||||
game.sound.play('coin');
|
||||
game.self.delete();
|
||||
});`}</Code>
|
||||
<p>
|
||||
Со <b>счётчиком</b>: предмет сообщает глобальному скрипту,
|
||||
тот считает. На монетке:
|
||||
</p>
|
||||
<Code>{`game.self.onTouch(() => {
|
||||
game.broadcast('coin'); // сообщить всем скриптам
|
||||
});`}</Code>}
|
||||
lua={<Code>{`local part = script.Parent
|
||||
|
||||
part.Touched:Connect(function(hit)
|
||||
local h = hit.Parent:FindFirstChild("Humanoid")
|
||||
if h then
|
||||
part:Destroy()
|
||||
end
|
||||
end)`}</Code>}
|
||||
/>
|
||||
<p>Со <b>счётчиком</b>: монетка увеличивает leaderstats игрока:</p>
|
||||
<LangTabs
|
||||
js={<Code>{`game.self.onTouch(() => {
|
||||
game.broadcast('coin');
|
||||
game.self.delete();
|
||||
});`}</Code>
|
||||
<p>В глобальном скрипте — приём и счёт:</p>
|
||||
});`}</Code>}
|
||||
lua={<Code>{`local part = script.Parent
|
||||
|
||||
part.Touched:Connect(function(hit)
|
||||
local player = game:GetService("Players"):GetPlayerFromCharacter(hit.Parent)
|
||||
if not player then return end
|
||||
|
||||
-- Прибавить монетку в leaderstats
|
||||
local stats = player:FindFirstChild("leaderstats")
|
||||
if stats and stats:FindFirstChild("Монеты") then
|
||||
stats.Монеты.Value = stats.Монеты.Value + 1
|
||||
end
|
||||
|
||||
part:Destroy()
|
||||
end)`}</Code>}
|
||||
/>
|
||||
<p>JS: глобальный скрипт принимает broadcast и считает:</p>
|
||||
<ScriptKind kind="global" />
|
||||
<Code>{`let score = 0;
|
||||
<LangTabs
|
||||
js={<Code>{`let score = 0;
|
||||
game.ui.score = 0;
|
||||
game.onMessage('coin', () => {
|
||||
score = score + 1;
|
||||
game.ui.score = score; // обновить счётчик в углу
|
||||
if (score >= 10) game.ui.showText('🏆 Собрал все!', 3);
|
||||
});`}</Code>
|
||||
<Note>
|
||||
<b>Не</b> ставь счётчик на саму монетку — каждая монетка это
|
||||
свой скрипт, они не видят переменные друг друга. Считай в
|
||||
одном глобальном скрипте, монетки только шлют
|
||||
<code> game.broadcast</code>.
|
||||
</Note>
|
||||
game.ui.score = score;
|
||||
if (score >= 10) game.ui.showText('Собрал все!', 3);
|
||||
});`}</Code>}
|
||||
lua={<Code>{`-- В Lua счёт уже в leaderstats игрока (см. код на монетке выше).
|
||||
-- Проверим достижение цели в глобальном скрипте:
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
Players.PlayerAdded:Connect(function(player)
|
||||
-- Создаём leaderstats папку при заходе
|
||||
local stats = Instance.new("Folder", player)
|
||||
stats.Name = "leaderstats"
|
||||
local coins = Instance.new("IntValue", stats)
|
||||
coins.Name = "Монеты"
|
||||
coins.Value = 0
|
||||
|
||||
coins.Changed:Connect(function(newVal)
|
||||
if newVal >= 10 then
|
||||
print("Собрал все!")
|
||||
end
|
||||
end)
|
||||
end)`}</Code>}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
@ -3949,34 +4039,62 @@ game.onMessage('coin', () => {
|
||||
<>
|
||||
<p>
|
||||
При касании <b>переместить игрока</b> (портал) или
|
||||
<b> сдвинуть сам объект</b> (движущаяся платформа).
|
||||
<b> сдвинуть сам объект</b>.
|
||||
</p>
|
||||
<p><b>Портал</b> — телепорт игрока в точку:</p>
|
||||
<p><b>Портал</b> — телепорт игрока:</p>
|
||||
<ScriptKind kind="object" on="портал" />
|
||||
<Code>{`game.self.onTouch(() => {
|
||||
game.player.teleport(0, 20, 50); // x, y, z назначения
|
||||
<LangTabs
|
||||
js={<Code>{`game.self.onTouch(() => {
|
||||
game.player.teleport(0, 20, 50);
|
||||
game.sound.play('win');
|
||||
game.camera.shake(0.15, 0.2);
|
||||
});`}</Code>
|
||||
<p>
|
||||
<b>Сдвинуть сам объект</b> при касании (например, опустить
|
||||
мост). <code>game.self.move</code> ставит новую позицию:
|
||||
</p>
|
||||
<Code>{`let opened = false;
|
||||
});`}</Code>}
|
||||
lua={<Code>{`local part = script.Parent
|
||||
|
||||
part.Touched:Connect(function(hit)
|
||||
local h = hit.Parent:FindFirstChild("Humanoid")
|
||||
if not h then return end
|
||||
local hrp = hit.Parent:FindFirstChild("HumanoidRootPart")
|
||||
if hrp then
|
||||
hrp.CFrame = CFrame.new(0, 20, 50)
|
||||
end
|
||||
end)`}</Code>}
|
||||
/>
|
||||
<p><b>Сдвинуть сам объект</b> при касании (опустить мост):</p>
|
||||
<LangTabs
|
||||
js={<Code>{`let opened = false;
|
||||
game.self.onTouch(() => {
|
||||
if (opened) return;
|
||||
opened = true;
|
||||
const p = game.self.position;
|
||||
game.self.move(p.x, p.y - 3, p.z); // уехал вниз на 3 м
|
||||
});`}</Code>
|
||||
<p>
|
||||
<b>Плавно</b> сдвинуть — через <code>game.tween</code> (анимация):
|
||||
</p>
|
||||
<Code>{`// дверь уезжает вбок за 1 секунду
|
||||
const p = game.self.position;
|
||||
game.self.move(p.x, p.y - 3, p.z);
|
||||
});`}</Code>}
|
||||
lua={<Code>{`local part = script.Parent
|
||||
local opened = false
|
||||
|
||||
part.Touched:Connect(function(hit)
|
||||
if opened then return end
|
||||
if not hit.Parent:FindFirstChild("Humanoid") then return end
|
||||
opened = true
|
||||
part.Position = part.Position - Vector3.new(0, 3, 0)
|
||||
end)`}</Code>}
|
||||
/>
|
||||
<p><b>Плавно</b> сдвинуть — через TweenService:</p>
|
||||
<LangTabs
|
||||
js={<Code>{`const p = game.self.position;
|
||||
game.self.onTouch(() => {
|
||||
game.tween(game.self.ref, { x: p.x + 4 }, { duration: 1, easing: 'ease' });
|
||||
});`}</Code>
|
||||
game.tween(game.self.ref, { x: p.x + 4 },
|
||||
{ duration: 1, easing: 'ease' });
|
||||
});`}</Code>}
|
||||
lua={<Code>{`local TweenService = game:GetService("TweenService")
|
||||
local part = script.Parent
|
||||
|
||||
part.Touched:Connect(function(hit)
|
||||
if not hit.Parent:FindFirstChild("Humanoid") then return end
|
||||
local goal = { Position = part.Position + Vector3.new(4, 0, 0) }
|
||||
TweenService:Create(part, TweenInfo.new(1), goal):Play()
|
||||
end)`}</Code>}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
@ -3986,88 +4104,169 @@ game.self.onTouch(() => {
|
||||
body: (
|
||||
<>
|
||||
<p>
|
||||
Любой примитив можно <b>создать</b> и <b>менять</b> из
|
||||
скрипта. Вот все свойства и как их задать.
|
||||
Любой примитив можно <b>создать</b> и <b>менять</b> из скрипта.
|
||||
</p>
|
||||
<ScriptKind kind="global" />
|
||||
<p><b>Создать примитив</b> со всеми свойствами:</p>
|
||||
<Code>{`const box = game.scene.spawn('cube', {
|
||||
x: 0, y: 2, z: 0, // позиция
|
||||
sx: 2, sy: 1, sz: 3, // размер по осям (ширина/высота/глубина)
|
||||
rotationX: 0, rotationY: 0.8, rotationZ: 0, // поворот в радианах
|
||||
color: '#ff5533', // цвет (hex)
|
||||
material: 'neon', // matte | neon | metal | glass | studs
|
||||
<p><b>Создать примитив</b>:</p>
|
||||
<LangTabs
|
||||
js={<Code>{`const box = game.scene.spawn('cube', {
|
||||
x: 0, y: 2, z: 0,
|
||||
sx: 2, sy: 1, sz: 3,
|
||||
rotationX: 0, rotationY: 0.8, rotationZ: 0,
|
||||
color: '#ff5533',
|
||||
material: 'neon',
|
||||
name: 'МойКуб',
|
||||
anchored: true, // true = висит на месте; false = падает (физика)
|
||||
canCollide: true, // false = игрок проходит насквозь
|
||||
anchored: true,
|
||||
canCollide: true,
|
||||
visible: true,
|
||||
mass: 5, // масса (если anchored:false)
|
||||
});`}</Code>
|
||||
<p>Типы примитивов для <code>spawn</code>:</p>
|
||||
<Code>{`'cube' 'sphere' 'cylinder' 'cone' 'pyramid' 'torus' 'wedge' 'cornerwedge' 'plane'`}</Code>
|
||||
<p><b>Менять свойства</b> уже существующего объекта:</p>
|
||||
<Code>{`game.scene.setColor(box, '#00ff88'); // цвет
|
||||
game.scene.setMaterial(box, 'glass'); // материал
|
||||
game.scene.setVisible(box, false); // спрятать
|
||||
game.scene.setCollide(box, false); // сделать проходимым
|
||||
game.scene.setOpacity(box, 0.4); // полупрозрачность (1=видно, 0=невидимо)
|
||||
game.scene.setScale(box, 3, 1, 1); // новый размер
|
||||
game.scene.move(box, 5, 2, 0); // переместить
|
||||
game.scene.setRotation(box, 0, 1.57, 0); // повернуть (радианы)
|
||||
game.scene.setLabel(box, 'Привет!', { color:'#fff', height: 2.5 });`}</Code>
|
||||
<p>
|
||||
Удобнее — через <b>объект-прокси</b> (присваивание свойств):
|
||||
</p>
|
||||
<Code>{`const obj = game.scene.findOne('МойКуб');
|
||||
mass: 5,
|
||||
});`}</Code>}
|
||||
lua={<Code>{`local box = Instance.new("Part")
|
||||
box.Name = "МойКуб"
|
||||
box.Shape = Enum.PartType.Block
|
||||
box.Size = Vector3.new(2, 1, 3)
|
||||
box.Position = Vector3.new(0, 2, 0)
|
||||
box.Orientation = Vector3.new(0, math.deg(0.8), 0) -- градусы
|
||||
box.Color = Color3.fromRGB(255, 85, 51)
|
||||
box.Material = Enum.Material.Neon
|
||||
box.Anchored = true
|
||||
box.CanCollide = true
|
||||
box.Transparency = 0
|
||||
-- Если Anchored=false: box.Mass читается, не задаётся.
|
||||
-- Управляется через PhysicalProperties и Density.
|
||||
box.Parent = workspace`}</Code>}
|
||||
/>
|
||||
<p>Типы примитивов:</p>
|
||||
<LangTabs
|
||||
js={<Code>{`'cube' 'sphere' 'cylinder' 'cone' 'pyramid' 'torus' 'wedge' 'cornerwedge' 'plane'`}</Code>}
|
||||
lua={<Code>{`Enum.PartType.Block / Ball / Cylinder / Wedge / CornerWedge
|
||||
-- Для cone/pyramid/torus используются MeshPart или SpecialMesh:
|
||||
local sphere = Instance.new("Part")
|
||||
sphere.Shape = Enum.PartType.Ball -- сфера`}</Code>}
|
||||
/>
|
||||
<p><b>Менять свойства</b> существующего объекта:</p>
|
||||
<LangTabs
|
||||
js={<Code>{`game.scene.setColor(box, '#00ff88');
|
||||
game.scene.setMaterial(box, 'glass');
|
||||
game.scene.setVisible(box, false);
|
||||
game.scene.setCollide(box, false);
|
||||
game.scene.setOpacity(box, 0.4);
|
||||
game.scene.setScale(box, 3, 1, 1);
|
||||
game.scene.move(box, 5, 2, 0);
|
||||
game.scene.setRotation(box, 0, 1.57, 0);
|
||||
game.scene.setLabel(box, 'Привет!', { color:'#fff', height: 2.5 });
|
||||
|
||||
// Или через прокси:
|
||||
const obj = game.scene.findOne('МойКуб');
|
||||
obj.color = '#ffd700';
|
||||
obj.material = 'metal';
|
||||
obj.scale = 2; // равномерный масштаб
|
||||
obj.scale = 2;
|
||||
obj.opacity = 0.5;
|
||||
obj.visible = false;
|
||||
obj.canCollide = false;
|
||||
obj.position = { x: 0, y: 10, z: 0 };
|
||||
obj.rotateY(1.57);
|
||||
obj.destroy(); // удалить`}</Code>
|
||||
<Note>
|
||||
<b>Радианы:</b> поворот задаётся в радианах, не градусах.
|
||||
90° = <code>Math.PI/2</code> ≈ 1.57, 180° = <code>Math.PI</code> ≈ 3.14.
|
||||
</Note>
|
||||
obj.destroy();`}</Code>}
|
||||
lua={<Code>{`-- Прямое присваивание свойств Part
|
||||
box.Color = Color3.fromRGB(0, 255, 136)
|
||||
box.Material = Enum.Material.Glass
|
||||
box.Transparency = 0.6 -- 0=видно, 1=невидимо
|
||||
box.CanCollide = false
|
||||
box.Size = Vector3.new(3, 1, 1)
|
||||
box.Position = Vector3.new(5, 2, 0)
|
||||
box.Orientation = Vector3.new(0, 90, 0)
|
||||
-- Скрыть: Transparency = 1 (или Parent = nil)
|
||||
|
||||
box:Destroy() -- удалить`}</Code>}
|
||||
/>
|
||||
<LangTabs
|
||||
js={<Note>
|
||||
<b>Радианы:</b> поворот в радианах. 90° = <code>Math.PI/2</code> ≈ 1.57.
|
||||
</Note>}
|
||||
lua={<Note>
|
||||
<b>Градусы:</b> Orientation в градусах (не радианах).
|
||||
Для CFrame.Angles — в радианах: <code>math.rad(90)</code>.
|
||||
</Note>}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'recipes-anim',
|
||||
title: 'S7. Движение, вращение, мигание (onTick и tween)',
|
||||
title: 'S7. Движение, вращение, мигание',
|
||||
body: (
|
||||
<>
|
||||
<p>
|
||||
<b>Вращающийся объект</b> (монета, портал) — крутим каждый
|
||||
кадр через <code>game.onTick</code> (dt = время кадра):
|
||||
</p>
|
||||
<p><b>Вращающийся объект</b> (монета, портал):</p>
|
||||
<ScriptKind kind="object" on="вращающийся объект" />
|
||||
<Code>{`let angle = 0;
|
||||
<LangTabs
|
||||
js={<Code>{`let angle = 0;
|
||||
game.onTick((dt) => {
|
||||
angle = angle + dt * 2; // скорость вращения
|
||||
angle = angle + dt * 2;
|
||||
game.self.rotateY(angle);
|
||||
});`}</Code>
|
||||
<p><b>Парение вверх-вниз</b> (плавно качается):</p>
|
||||
<Code>{`const start = game.self.position;
|
||||
});`}</Code>}
|
||||
lua={<Code>{`local RunService = game:GetService("RunService")
|
||||
local part = script.Parent
|
||||
local angle = 0
|
||||
|
||||
RunService.Heartbeat:Connect(function(dt)
|
||||
angle = angle + dt * 2
|
||||
part.CFrame = CFrame.new(part.Position) * CFrame.Angles(0, angle, 0)
|
||||
end)`}</Code>}
|
||||
/>
|
||||
<p><b>Парение вверх-вниз</b>:</p>
|
||||
<LangTabs
|
||||
js={<Code>{`const start = game.self.position;
|
||||
let t = 0;
|
||||
game.onTick((dt) => {
|
||||
t = t + dt;
|
||||
const dy = Math.sin(t * 2) * 0.4; // амплитуда 0.4 м
|
||||
const dy = Math.sin(t * 2) * 0.4;
|
||||
game.self.move(start.x, start.y + dy, start.z);
|
||||
});`}</Code>
|
||||
<p><b>Пульсация размера</b> через tween (бесконечно туда-обратно):</p>
|
||||
<Code>{`game.tween(game.self.ref, { sy: 1.4 }, {
|
||||
});`}</Code>}
|
||||
lua={<Code>{`local RunService = game:GetService("RunService")
|
||||
local part = script.Parent
|
||||
local startPos = part.Position
|
||||
local t = 0
|
||||
|
||||
RunService.Heartbeat:Connect(function(dt)
|
||||
t = t + dt
|
||||
local dy = math.sin(t * 2) * 0.4
|
||||
part.Position = Vector3.new(startPos.X, startPos.Y + dy, startPos.Z)
|
||||
end)`}</Code>}
|
||||
/>
|
||||
<p><b>Пульсация размера</b>:</p>
|
||||
<LangTabs
|
||||
js={<Code>{`game.tween(game.self.ref, { sy: 1.4 }, {
|
||||
duration: 0.6, easing: 'ease', yoyo: true, repeat: -1
|
||||
});`}</Code>
|
||||
<p><b>Мигание цветом</b> каждые полсекунды:</p>
|
||||
<Code>{`let on = false;
|
||||
});`}</Code>}
|
||||
lua={<Code>{`local TweenService = game:GetService("TweenService")
|
||||
local part = script.Parent
|
||||
local origSize = part.Size
|
||||
|
||||
local info = TweenInfo.new(
|
||||
0.6, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut,
|
||||
-1, -- бесконечно
|
||||
true -- yoyo (туда-обратно)
|
||||
)
|
||||
local goal = { Size = Vector3.new(origSize.X, origSize.Y * 1.4, origSize.Z) }
|
||||
TweenService:Create(part, info, goal):Play()`}</Code>}
|
||||
/>
|
||||
<p><b>Мигание цветом</b>:</p>
|
||||
<LangTabs
|
||||
js={<Code>{`let on = false;
|
||||
game.every(0.5, () => {
|
||||
on = !on;
|
||||
game.self.setColor(on ? '#ff0000' : '#330000');
|
||||
});`}</Code>
|
||||
});`}</Code>}
|
||||
lua={<Code>{`local part = script.Parent
|
||||
local on = false
|
||||
|
||||
while true do
|
||||
task.wait(0.5)
|
||||
on = not on
|
||||
part.Color = on and Color3.fromRGB(255, 0, 0)
|
||||
or Color3.fromRGB(51, 0, 0)
|
||||
end`}</Code>}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
@ -4076,47 +4275,92 @@ game.every(0.5, () => {
|
||||
title: 'S8. Кнопка по E и дверь',
|
||||
body: (
|
||||
<>
|
||||
<p>
|
||||
<b>Взаимодействие по клавише E</b> (как в Roblox ProximityPrompt)
|
||||
— через <code>game.self.onInteract</code>. Появляется подсказка
|
||||
«[E] …» когда игрок рядом.
|
||||
</p>
|
||||
<p><b>Взаимодействие по клавише E</b>:</p>
|
||||
<ScriptKind kind="object" on="кнопку / рычаг / сундук" />
|
||||
<Code>{`game.self.onInteract(() => {
|
||||
<LangTabs
|
||||
js={<Code>{`game.self.onInteract(() => {
|
||||
game.ui.showText('Открыто!', 2);
|
||||
game.broadcast('open-door');
|
||||
}, { text: 'Открыть', key: 'e', distance: 4 });`}</Code>
|
||||
<p>На двери — глобальный/объектный скрипт, который её открывает:</p>
|
||||
}, { text: 'Открыть', key: 'e', distance: 4 });`}</Code>}
|
||||
lua={<Code>{`local part = script.Parent
|
||||
|
||||
local prompt = Instance.new("ProximityPrompt")
|
||||
prompt.ActionText = "Открыть"
|
||||
prompt.MaxActivationDistance = 4
|
||||
prompt.KeyboardKeyCode = Enum.KeyCode.E
|
||||
prompt.Parent = part
|
||||
|
||||
-- BindableEvent для оповещения "open-door"
|
||||
local doorEvent = workspace:FindFirstChild("DoorOpenEvent")
|
||||
or Instance.new("BindableEvent", workspace)
|
||||
doorEvent.Name = "DoorOpenEvent"
|
||||
|
||||
prompt.Triggered:Connect(function(player)
|
||||
print("Открыто!")
|
||||
doorEvent:Fire()
|
||||
end)`}</Code>}
|
||||
/>
|
||||
<p>На двери:</p>
|
||||
<ScriptKind kind="object" on="дверь" />
|
||||
<Code>{`const closed = game.self.position;
|
||||
<LangTabs
|
||||
js={<Code>{`const closed = game.self.position;
|
||||
game.onMessage('open-door', () => {
|
||||
// плавно уехать вверх (открыться)
|
||||
game.tween(game.self.ref, { y: closed.y + 4 }, { duration: 1, easing: 'ease' });
|
||||
game.self.setCollide(false); // через неё можно пройти
|
||||
});`}</Code>
|
||||
game.tween(game.self.ref, { y: closed.y + 4 },
|
||||
{ duration: 1, easing: 'ease' });
|
||||
game.self.setCollide(false);
|
||||
});`}</Code>}
|
||||
lua={<Code>{`local TweenService = game:GetService("TweenService")
|
||||
local door = script.Parent
|
||||
local closedPos = door.Position
|
||||
|
||||
local doorEvent = workspace:WaitForChild("DoorOpenEvent")
|
||||
|
||||
doorEvent.Event:Connect(function()
|
||||
local goal = { Position = closedPos + Vector3.new(0, 4, 0) }
|
||||
TweenService:Create(door, TweenInfo.new(1), goal):Play()
|
||||
door.CanCollide = false
|
||||
end)`}</Code>}
|
||||
/>
|
||||
<Note>
|
||||
<code>holdDuration: 1</code> в опциях onInteract — держать E
|
||||
1 секунду (для важных действий). <code>distance</code> —
|
||||
с какого расстояния появляется подсказка.
|
||||
<code>holdDuration: 1</code> в onInteract / <code>prompt.HoldDuration = 1</code>
|
||||
в Roblox — держать E одну секунду.
|
||||
</Note>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'recipes-gui-timer',
|
||||
title: 'S9. Надписи на экране, таймер, кнопки GUI',
|
||||
title: 'S9. HUD надписи, таймер, кнопки',
|
||||
body: (
|
||||
<>
|
||||
<p><b>HUD-надписи</b> в углу и по центру:</p>
|
||||
<p><b>HUD-надписи</b>:</p>
|
||||
<ScriptKind kind="global" />
|
||||
<Code>{`game.ui.score = 0; // счётчик «Очки: 0» в углу
|
||||
game.ui.score = 50; // обновить
|
||||
game.ui.timer = 60; // таймер mm:ss в углу
|
||||
game.ui.showText('Старт!', 2); // крупно по центру на 2 сек
|
||||
game.ui.set('hp', 'Жизни: 3', { x: 50, y: 90, color: '#fff' }); // своя метка
|
||||
game.ui.remove('hp'); // убрать метку`}</Code>
|
||||
<p><b>Обратный отсчёт</b> и проигрыш по времени:</p>
|
||||
<Code>{`let time = 30;
|
||||
<LangTabs
|
||||
js={<Code>{`game.ui.score = 0; // счётчик в углу
|
||||
game.ui.score = 50;
|
||||
game.ui.timer = 60; // таймер
|
||||
game.ui.showText('Старт!', 2);
|
||||
game.ui.set('hp', 'Жизни: 3', { x: 50, y: 90, color: '#fff' });
|
||||
game.ui.remove('hp');`}</Code>}
|
||||
lua={<Code>{`-- В Roblox HUD = leaderstats папка (см. G7) или свой ScreenGui
|
||||
local Players = game:GetService("Players")
|
||||
local player = Players.LocalPlayer
|
||||
local gui = player:WaitForChild("PlayerGui")
|
||||
|
||||
-- Своя метка по центру
|
||||
local screen = Instance.new("ScreenGui", gui)
|
||||
local label = Instance.new("TextLabel", screen)
|
||||
label.Size = UDim2.new(0.4, 0, 0.1, 0)
|
||||
label.Position = UDim2.new(0.3, 0, 0.4, 0)
|
||||
label.Text = "Старт!"
|
||||
label.TextScaled = true
|
||||
label.BackgroundTransparency = 0.5
|
||||
|
||||
task.delay(2, function() screen:Destroy() end)`}</Code>}
|
||||
/>
|
||||
<p><b>Обратный отсчёт</b>:</p>
|
||||
<LangTabs
|
||||
js={<Code>{`let time = 30;
|
||||
game.ui.timer = time;
|
||||
const id = game.every(1, () => {
|
||||
time = time - 1;
|
||||
@ -4126,16 +4370,48 @@ const id = game.every(1, () => {
|
||||
game.ui.showText('Время вышло!', 3);
|
||||
game.player.kill();
|
||||
}
|
||||
});`}</Code>
|
||||
<p><b>Кнопка на экране</b> (GUI) и обработка клика:</p>
|
||||
<Code>{`const btn = game.gui.create('button', {
|
||||
});`}</Code>}
|
||||
lua={<Code>{`local time = 30
|
||||
|
||||
while time > 0 do
|
||||
task.wait(1)
|
||||
time = time - 1
|
||||
print("Осталось: " .. time)
|
||||
end
|
||||
|
||||
print("Время вышло!")
|
||||
-- Убить локального игрока
|
||||
local player = game:GetService("Players").LocalPlayer
|
||||
if player.Character and player.Character:FindFirstChild("Humanoid") then
|
||||
player.Character.Humanoid.Health = 0
|
||||
end`}</Code>}
|
||||
/>
|
||||
<p><b>Кнопка GUI</b>:</p>
|
||||
<LangTabs
|
||||
js={<Code>{`const btn = game.gui.create('button', {
|
||||
name: 'start', text: 'НАЧАТЬ', x: 50, y: 80,
|
||||
w: 20, h: 8, bg: '#3a6ee0', color: '#fff', fontSize: 18, borderRadius: 12
|
||||
w: 20, h: 8, bg: '#3a6ee0', color: '#fff'
|
||||
});
|
||||
game.gui.onClick(btn, () => {
|
||||
game.ui.showText('Поехали!', 2);
|
||||
game.gui.hide(btn);
|
||||
});`}</Code>
|
||||
});`}</Code>}
|
||||
lua={<Code>{`local player = game:GetService("Players").LocalPlayer
|
||||
local gui = player:WaitForChild("PlayerGui")
|
||||
local screen = Instance.new("ScreenGui", gui)
|
||||
|
||||
local btn = Instance.new("TextButton", screen)
|
||||
btn.Size = UDim2.new(0.2, 0, 0.08, 0)
|
||||
btn.Position = UDim2.new(0.4, 0, 0.8, 0)
|
||||
btn.Text = "НАЧАТЬ"
|
||||
btn.BackgroundColor3 = Color3.fromRGB(58, 110, 224)
|
||||
btn.TextColor3 = Color3.new(1, 1, 1)
|
||||
|
||||
btn.MouseButton1Click:Connect(function()
|
||||
print("Поехали!")
|
||||
btn.Visible = false
|
||||
end)`}</Code>}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
@ -4144,34 +4420,75 @@ game.gui.onClick(btn, () => {
|
||||
title: 'S10. Спавн, падение, проверка падения вниз',
|
||||
body: (
|
||||
<>
|
||||
<p><b>Спавнить объекты с неба</b> каждую секунду (ловилка):</p>
|
||||
<p><b>Спавнить объекты с неба</b> каждую секунду:</p>
|
||||
<ScriptKind kind="global" />
|
||||
<Code>{`game.every(1, () => {
|
||||
<LangTabs
|
||||
js={<Code>{`game.every(1, () => {
|
||||
const x = game.random(-10, 10);
|
||||
game.scene.spawn('sphere', {
|
||||
x: x, y: 20, z: 0,
|
||||
color: '#ffd700', material: 'neon',
|
||||
anchored: false, // будет падать (физика)
|
||||
lifetime: 8 // само исчезнет через 8 сек
|
||||
anchored: false,
|
||||
lifetime: 8
|
||||
});
|
||||
});`}</Code>
|
||||
<p>
|
||||
<b>Игрок упал вниз</b> (за карту) — вернуть на спавн. Проверяем
|
||||
высоту каждый кадр:
|
||||
</p>
|
||||
<Code>{`game.onTick(() => {
|
||||
});`}</Code>}
|
||||
lua={<Code>{`local Debris = game:GetService("Debris")
|
||||
|
||||
while true do
|
||||
task.wait(1)
|
||||
local x = math.random(-10, 10)
|
||||
|
||||
local ball = Instance.new("Part")
|
||||
ball.Shape = Enum.PartType.Ball
|
||||
ball.Size = Vector3.new(1, 1, 1)
|
||||
ball.Position = Vector3.new(x, 20, 0)
|
||||
ball.Color = Color3.fromRGB(255, 215, 0)
|
||||
ball.Material = Enum.Material.Neon
|
||||
ball.Anchored = false -- падает
|
||||
ball.Parent = workspace
|
||||
|
||||
Debris:AddItem(ball, 8) -- удалить через 8 сек
|
||||
end`}</Code>}
|
||||
/>
|
||||
<p><b>Игрок упал вниз</b>:</p>
|
||||
<LangTabs
|
||||
js={<Code>{`game.onTick(() => {
|
||||
if (game.player.position.y < -10) {
|
||||
game.player.respawn();
|
||||
game.ui.showText('Упал! Назад на старт.', 2);
|
||||
game.ui.showText('Упал!', 2);
|
||||
}
|
||||
});`}</Code>
|
||||
<p><b>Финиш</b> — дошёл до зоны, победа:</p>
|
||||
});`}</Code>}
|
||||
lua={<Code>{`local Players = game:GetService("Players")
|
||||
local RunService = game:GetService("RunService")
|
||||
|
||||
RunService.Heartbeat:Connect(function()
|
||||
local player = Players.LocalPlayer
|
||||
if not player.Character then return end
|
||||
local hrp = player.Character:FindFirstChild("HumanoidRootPart")
|
||||
if hrp and hrp.Position.Y < -10 then
|
||||
player:LoadCharacter() -- респавн
|
||||
print("Упал!")
|
||||
end
|
||||
end)`}</Code>}
|
||||
/>
|
||||
<p><b>Финиш</b>:</p>
|
||||
<ScriptKind kind="object" on="финишную плиту" />
|
||||
<Code>{`game.self.onTouch(() => {
|
||||
game.ui.showText('🏁 ПОБЕДА!', 4);
|
||||
<LangTabs
|
||||
js={<Code>{`game.self.onTouch(() => {
|
||||
game.ui.showText('ПОБЕДА!', 4);
|
||||
game.sound.play('win');
|
||||
game.player.setInputBlocked(true); // заморозить управление
|
||||
});`}</Code>
|
||||
game.player.setInputBlocked(true);
|
||||
});`}</Code>}
|
||||
lua={<Code>{`local part = script.Parent
|
||||
|
||||
part.Touched:Connect(function(hit)
|
||||
local h = hit.Parent:FindFirstChild("Humanoid")
|
||||
if not h then return end
|
||||
print("ПОБЕДА!")
|
||||
h.WalkSpeed = 0 -- заморозить
|
||||
h.JumpPower = 0
|
||||
end)`}</Code>}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
@ -4180,30 +4497,68 @@ game.gui.onClick(btn, () => {
|
||||
title: 'S11. Враг, который идёт за игроком',
|
||||
body: (
|
||||
<>
|
||||
<p>
|
||||
<b>NPC/враг</b>, который преследует игрока и наносит урон.
|
||||
</p>
|
||||
<p><b>NPC/враг</b>, преследующий игрока:</p>
|
||||
<ScriptKind kind="global" />
|
||||
<Code>{`const enemy = game.scene.spawnNpc('zombie', {
|
||||
<LangTabs
|
||||
js={<Code>{`const enemy = game.scene.spawnNpc('zombie', {
|
||||
x: 10, y: 0, z: 10,
|
||||
hp: 100, name: 'Зомби', speed: 3
|
||||
});
|
||||
enemy.follow('player'); // идти за игроком
|
||||
enemy.follow('player');
|
||||
enemy.say('Хочу тебя поймать!', 3);
|
||||
|
||||
enemy.onDeath(() => {
|
||||
game.ui.showText('Враг повержен!', 2);
|
||||
game.fx.damageFloater(enemy.position, 0, { isHeal: true });
|
||||
});`}</Code>
|
||||
<p>Урон игроку, когда враг близко:</p>
|
||||
<Code>{`game.every(0.5, () => {
|
||||
});
|
||||
|
||||
// Урон когда близко
|
||||
game.every(0.5, () => {
|
||||
const d = game.distance(enemy.position, game.player.position);
|
||||
if (d < 2) game.player.damage(10);
|
||||
});`}</Code>
|
||||
<Note>
|
||||
Облачка урона над всеми мобами одной строкой:
|
||||
<code> game.fx.autoMobFloaters(true)</code>.
|
||||
</Note>
|
||||
});`}</Code>}
|
||||
lua={<Code>{`-- Враг должен быть Model с Humanoid и HumanoidRootPart в workspace.
|
||||
-- Например workspace.Зомби.
|
||||
local Players = game:GetService("Players")
|
||||
local enemy = workspace:WaitForChild("Зомби")
|
||||
local humanoid = enemy:WaitForChild("Humanoid")
|
||||
local hrp = enemy:WaitForChild("HumanoidRootPart")
|
||||
|
||||
-- Преследование игрока
|
||||
task.spawn(function()
|
||||
while enemy.Parent and humanoid.Health > 0 do
|
||||
local player = Players:GetPlayers()[1]
|
||||
if player and player.Character then
|
||||
local target = player.Character:FindFirstChild("HumanoidRootPart")
|
||||
if target then
|
||||
humanoid:MoveTo(target.Position)
|
||||
end
|
||||
end
|
||||
task.wait(0.5)
|
||||
end
|
||||
end)
|
||||
|
||||
humanoid.Died:Connect(function()
|
||||
print("Враг повержен!")
|
||||
end)
|
||||
|
||||
-- Урон когда близко
|
||||
task.spawn(function()
|
||||
while enemy.Parent and humanoid.Health > 0 do
|
||||
task.wait(0.5)
|
||||
local player = Players:GetPlayers()[1]
|
||||
if player and player.Character then
|
||||
local target = player.Character:FindFirstChild("HumanoidRootPart")
|
||||
local playerHum = player.Character:FindFirstChild("Humanoid")
|
||||
if target and playerHum then
|
||||
local dist = (target.Position - hrp.Position).Magnitude
|
||||
if dist < 2 then
|
||||
playerHum:TakeDamage(10)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)`}</Code>}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
@ -4212,35 +4567,74 @@ enemy.onDeath(() => {
|
||||
title: 'S12. Сохранение прогресса и лидерборд',
|
||||
body: (
|
||||
<>
|
||||
<p>
|
||||
<b>Лидерборд</b> (таблица очков справа) — объяви стат и
|
||||
прибавляй:
|
||||
</p>
|
||||
<p><b>Лидерборд</b>:</p>
|
||||
<ScriptKind kind="global" />
|
||||
<Code>{`game.leaderstats.define('Монеты', { initial: 0 });
|
||||
// прибавить текущему игроку:
|
||||
game.leaderstats.me.add('Монеты', 1);`}</Code>
|
||||
<p>
|
||||
<b>Сохранение между сессиями</b> (прогресс не теряется после
|
||||
выхода):
|
||||
</p>
|
||||
<Code>{`// записать
|
||||
game.save.merge('progress', {
|
||||
patch: { level: 3 }, // обычные поля
|
||||
increment: { coins: 10 }, // атомарно прибавить
|
||||
max: { bestScore: 5000 } // запишется только если больше старого
|
||||
<LangTabs
|
||||
js={<Code>{`game.leaderstats.define('Монеты', { initial: 0 });
|
||||
game.leaderstats.me.add('Монеты', 1);`}</Code>}
|
||||
lua={<Code>{`-- leaderstats: см. G7
|
||||
local Players = game:GetService("Players")
|
||||
Players.PlayerAdded:Connect(function(player)
|
||||
local stats = Instance.new("Folder", player)
|
||||
stats.Name = "leaderstats"
|
||||
local coins = Instance.new("IntValue", stats)
|
||||
coins.Name = "Монеты"
|
||||
coins.Value = 0
|
||||
end)
|
||||
|
||||
-- Прибавить монетку (например, при сборе)
|
||||
local function addCoin(player, amount)
|
||||
local stats = player:FindFirstChild("leaderstats")
|
||||
if stats then
|
||||
stats.Монеты.Value = stats.Монеты.Value + amount
|
||||
end
|
||||
end`}</Code>}
|
||||
/>
|
||||
<p><b>Сохранение между сессиями</b>:</p>
|
||||
<LangTabs
|
||||
js={<Code>{`game.save.merge('progress', {
|
||||
patch: { level: 3 },
|
||||
increment: { coins: 10 },
|
||||
max: { bestScore: 5000 }
|
||||
});
|
||||
|
||||
// прочитать при старте
|
||||
game.save.get('progress', (data) => {
|
||||
if (data) {
|
||||
game.ui.showText('С возвращением! Уровень ' + data.level, 3);
|
||||
}
|
||||
});`}</Code>
|
||||
});`}</Code>}
|
||||
lua={<Code>{`-- В Roblox сохранение через DataStoreService (требует онлайн-игру)
|
||||
local DataStoreService = game:GetService("DataStoreService")
|
||||
local progress = DataStoreService:GetDataStore("Progress")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
Players.PlayerAdded:Connect(function(player)
|
||||
-- Прочитать при входе
|
||||
local success, data = pcall(function()
|
||||
return progress:GetAsync(player.UserId)
|
||||
end)
|
||||
if success and data then
|
||||
print("С возвращением! Уровень " .. (data.level or 1))
|
||||
-- Применить прогресс: leaderstats.Монеты.Value = data.coins и т.п.
|
||||
end
|
||||
end)
|
||||
|
||||
Players.PlayerRemoving:Connect(function(player)
|
||||
-- Сохранить при выходе
|
||||
local data = {
|
||||
level = 3,
|
||||
coins = 10,
|
||||
bestScore = 5000,
|
||||
}
|
||||
pcall(function()
|
||||
progress:SetAsync(player.UserId, data)
|
||||
end)
|
||||
end)`}</Code>}
|
||||
/>
|
||||
<Try>
|
||||
Собери всё вместе: монетки шлют broadcast → глобальный скрипт
|
||||
считает в leaderstats → раз в N монет сохраняет через
|
||||
game.save. Получится игра с прогрессом как в настоящем Roblox.
|
||||
Собери всё вместе: монетки добавляются в leaderstats →
|
||||
глобальный скрипт раз в N монет вызывает save.merge
|
||||
или DataStore:SetAsync. Получится игра с прогрессом.
|
||||
</Try>
|
||||
</>
|
||||
),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user