feat(wiki): LangTabs во все 8 статей раздела F (Игровая логика)
F1 HP: damage/heal vs humanoid:TakeDamage / Health F2 Физика: raycast vs workspace:Raycast (полный пример со стрельбой) F3 Атрибуты: setData/getData vs :SetAttribute/:GetAttribute F4 Теги: scene.tag vs CollectionService:AddTag/GetTagged F5 E-взаимодействие: onInteract vs ProximityPrompt F6 Billboard: setLabel vs BillboardGui+TextLabel F7 passThrough: physics.passThrough vs CanCollide=false F8 Связи: constraints.hinge vs HingeConstraint+Attachment Lua-примеры по канонам Roblox: Instance.new, Attachment, Vector3.new, UDim2.new, Color3, BrickColor, math.min.
This commit is contained in:
parent
c6899c0528
commit
76fba9cb35
@ -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>}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user