Roblox · RPG · урок 8
Посох выпускает огненный шар: взрыв, урон и замедление цели. Навык — телепортация: держишь E, выбираешь место маркером, отпускаешь — ты там.
Синим — что добавляем на этом уроке.
ReplicatedStorage └─ MageEffects (Folder) ├─ FireBall (Part) — шар с эффектами, прозрачный, CanCollide выкл. ├─ Explosion (Part) — эффект взрыва └─ TeleportLocation (Part) — плоский цилиндр-маркер ServerStorage ├─ Weapons (Folder) │ └─ Staff (Tool) │ ├─ Handle (Part) — посох; Fire (Sound) │ ├─ SpellCast (Part) — невидимый, на конце посоха │ ├─ FireBall (RemoteEvent) │ ├─ YouAreNotGui (ScreenGui) — «Ты не маг» │ ├─ FireBallLocal (LocalScript) — Animation — выпад, кадр Release │ └─ FireBallServer (Script) └─ Classes → Mage (Folder) └─ Teleportation (LocalScript)
Где: ServerStorage → Weapons → Staff · LocalScript
Зачем: Выпад и кадр Release.
-- игроки
local Players = game:GetService("Players")
-- посох
local tool = script.Parent
-- сигнал «заклинание» на сервер
local fireballEvent = tool:WaitForChild("FireBall")
-- «Ты не маг»
local notGui = tool:WaitForChild("YouAreNotGui")
-- выпад посохом; кадр Release — момент выстрела
local anim = script:WaitForChild("Animation")
-- этот игрок
local player = Players.LocalPlayer
-- куда целимся
local mouse = player:GetMouse()
-- чей посох
local CLASS = "Mage"
-- перезарядка
local COOLDOWN = 1
-- дорожка анимации
local track = nil
-- колдуем
local busy = false
-- взяли посох
local function OnEquipped()
-- Humanoid
local humanoid = tool.Parent:FindFirstChildOfClass("Humanoid")
-- анимации
local animator = humanoid and humanoid:FindFirstChildOfClass("Animator")
-- готовим выпад
if animator and anim.AnimationId ~= "" then track = animator:LoadAnimation(anim) end
-- конец функции
end
-- каждый раз, когда берут
tool.Equipped:Connect(OnEquipped)
-- уже в руках
if tool.Parent and tool.Parent:FindFirstChildOfClass("Humanoid") then OnEquipped() end
-- заклинание
local function Cast()
-- колдуем
busy = true
-- шар ещё не вылетел
local fired = false
-- выпустить шар
local function Fire()
-- только один раз
if fired then return end
-- вылетел
fired = true
-- серверу — куда лететь
fireballEvent:FireServer(mouse.Hit.Position)
-- конец функции
end
-- анимация есть
if track then
-- анимация дошла до именованного кадра
local connection = track.KeyframeReached:Connect(function(name)
-- кадр выстрела
if name == "Release" then Fire() end
-- конец функции
end)
-- выпад
track:Play()
-- ждём кадр Release
task.wait(0.6)
-- подписка только на этот выпад
connection:Disconnect()
-- конец проверки
end
-- кадра не было или анимации нет — стреляем сейчас
Fire()
-- перезарядка
task.wait(COOLDOWN)
-- можно снова
busy = false
-- конец функции
end
-- клик с посохом
tool.Activated:Connect(function()
-- ещё колдуем
if busy then return end
-- чужой класс
if player:GetAttribute("Class") ~= CLASS then
-- надпись ещё не показана
if notGui.Parent == tool then
-- «Ты не …» на экран
notGui.Parent = player.PlayerGui
-- 2 секунды
task.wait(2)
-- прячем обратно в оружие
notGui.Parent = tool
-- конец проверки
end
-- не колдуем
return
-- конец проверки
end
-- колдуем
Cast()
-- конец функции
end)Где: ServerStorage → Weapons → Staff · Script
Зачем: Огненный шар.
-- игроки
local Players = game:GetService("Players")
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- удаляет эффекты
local Debris = game:GetService("Debris")
-- посох
local tool = script.Parent
-- откуда вылетает шар
local spell = tool:WaitForChild("SpellCast")
-- звук заклинания
local fireSound = tool:WaitForChild("Handle"):WaitForChild("Fire")
-- заклинание от клиента
local fireballEvent = tool:WaitForChild("FireBall")
-- образцы эффектов
local effects = RS:WaitForChild("MageEffects")
-- огненный шар
local fireballSample = effects:WaitForChild("FireBall")
-- взрыв (в методичке по ошибке брался FireBall)
local explosionSample = effects:WaitForChild("Explosion")
-- кто колдует
local CLASS = "Mage"
-- скорость шара
local SPEED = 80
-- перезарядка (проверка на сервере)
local COOLDOWN = 1
-- время прошлого заклинания
local lastCast = 0
-- клиент колдует
fireballEvent.OnServerEvent:Connect(function(player, target)
-- кто держит посох
local character = tool.Parent
-- посох не у него или он не маг
if Players:GetPlayerFromCharacter(character) ~= player or player:GetAttribute("Class") ~= CLASS then return end
-- мусор вместо точки или слишком часто
if typeof(target) ~= "Vector3" or os.clock() - lastCast < COOLDOWN then return end
-- запоминаем
lastCast = os.clock()
-- звук
fireSound:Play()
-- новый шар
local ball = fireballSample:Clone()
-- летит
ball.Anchored = false
-- сквозь своих
ball.CanCollide = false
-- у конца посоха, смотрит на цель
ball.CFrame = CFrame.new(spell.Position, target)
-- в мир
ball.Parent = workspace
-- полёт считает сервер
ball:SetNetworkOwner(nil)
-- точка для силы
local attachment = Instance.new("Attachment", ball)
-- держит скорость
local move = Instance.new("LinearVelocity")
-- за центр шара
move.Attachment0 = attachment
-- без предела — летит прямо, без падения
move.MaxForce = math.huge
-- к цели
move.VectorVelocity = ball.CFrame.LookVector * SPEED
-- в шар
move.Parent = ball
-- ещё не взорвался
local done = false
-- шар коснулся
ball.Touched:Connect(function(hit)
-- уже взорвался или задел мага
if done or hit:IsDescendantOf(character) then return end
-- чья деталь
local model = hit:FindFirstAncestorOfClass("Model")
-- Humanoid цели
local target = model and model:FindFirstChildOfClass("Humanoid")
-- сквозь эффекты и невидимые зоны
if not target and not hit.CanCollide then return end
-- взрываемся
done = true
-- взрыв
local boom = explosionSample:Clone()
-- на месте
boom.Anchored = true
-- не мешает
boom.CanCollide = false
-- где шар
boom.Position = ball.Position
-- показываем
boom.Parent = workspace
-- секунду
Debris:AddItem(boom, 1)
-- шара больше нет
ball:Destroy()
-- попали в моба
if target and target.Health > 0 and not Players:GetPlayerFromCharacter(model) then
-- прошлая метка «кто ударил»
local old = target:FindFirstChild("creator")
-- убираем
if old then old:Destroy() end
-- новая метка
local tag = Instance.new("ObjectValue")
-- стандартное имя метки в Roblox
tag.Name = "creator"
-- кто ударил — получит опыт за победу
tag.Value = player
-- в Humanoid цели
tag.Parent = target
-- через 3 секунды метка исчезнет
Debris:AddItem(tag, 3)
-- урон
target:TakeDamage(15)
-- обычная скорость цели (запоминаем один раз)
local base = target:GetAttribute("BaseSpeed") or target.WalkSpeed
-- храним в самой цели
target:SetAttribute("BaseSpeed", base)
-- замедление
target.WalkSpeed = base * 0.3
-- через 3 секунды
task.delay(3, function()
-- её собственная скорость, а не 16
if target.Parent then target.WalkSpeed = base end
-- конец функции
end)
-- конец проверки
end
-- конец функции
end)
-- промах — через 4 секунды исчезнет
Debris:AddItem(ball, 4)
-- конец функции
end)Где: ServerStorage → Classes → Mage · LocalScript
Зачем: Телепорт на E.
-- клавиатура
local UIS = game:GetService("UserInputService")
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- каждый кадр
local RunService = game:GetService("RunService")
-- игроки
local Players = game:GetService("Players")
-- персонаж
local character = script.Parent.Parent
-- центр
local root = character:WaitForChild("HumanoidRootPart")
-- куда указывает курсор
local mouse = Players.LocalPlayer:GetMouse()
-- своя копия маркера — образец не трогаем
local marker = RS:WaitForChild("MageEffects"):WaitForChild("TeleportLocation"):Clone()
-- не падает
marker.Anchored = true
-- сквозь
marker.CanCollide = false
-- курсор его не замечает
marker.CanQuery = false
-- и mouse.Hit тоже
mouse.TargetFilter = marker
-- держать E — выбор места, отпустить — телепорт
local KEY = Enum.KeyCode.E
-- перезарядка
local COOLDOWN = 3
-- дальность телепорта
local function Range() return 35 end
-- выбираем место
local aiming = false
-- можно телепортироваться
local ready = true
-- место достаточно близко?
local function InRange()
-- Magnitude — длина вектора
return (mouse.Hit.Position - root.Position).Magnitude < Range()
-- конец функции
end
-- нажали
UIS.InputBegan:Connect(function(input, processed)
-- не E или перезарядка
if processed or input.KeyCode ~= KEY or not ready or aiming then return end
-- выбираем место
aiming = true
-- маркер виден только этому игроку
marker.Parent = workspace
-- конец функции
end)
-- каждый кадр
RunService.RenderStepped:Connect(function()
-- не выбираем
if not aiming then return end
-- маркер под курсором
marker.Position = mouse.Hit.Position
-- голубой — можно, красный — далеко
marker.Color = InRange() and Color3.fromRGB(0, 255, 255) or Color3.fromRGB(255, 0, 0)
-- конец функции
end)
-- отпустили
UIS.InputEnded:Connect(function(input)
-- не E или не выбирали
if input.KeyCode ~= KEY or not aiming then return end
-- выбор закончен
aiming = false
-- прячем маркер
marker.Parent = nil
-- далеко — не телепортируемся
if not InRange() then return end
-- перезарядка
ready = false
-- переносимся, смотрим в ту же сторону
character:PivotTo(root.CFrame.Rotation + mouse.Hit.Position + Vector3.new(0, 3, 0))
-- ждём
task.wait(COOLDOWN)
-- можно снова
ready = true
-- конец функции
end)Запусти Play (или Run) и убедись: