Roblox · Королевская битва · урок 9
Световая граната ослепляет всех, кто рядом и видит вспышку (отвернулся — слабее). Дымовая — облако дыма на 20 секунд. Граната вылетает в момент ключевого кадра «Release» анимации броска.
Синим — что добавляем на этом уроке.
ServerStorage → Loot ├─ Flashbang (Tool) │ ├─ Handle (Part) — Throw, Boom (Sound) │ ├─ Throw (RemoteEvent) │ ├─ Animation (Animation) — бросок с кадром «Release» │ ├─ GrenadeLocal (LocalScript) │ └─ FlashServer (Script) └─ SmokeGrenade (Tool) — Handle → Throw, Boom, Smoke ×2 (Enabled выкл.); Throw, Animation, GrenadeLocal, SmokeServer ReplicatedStorage └─ FlashEvent (RemoteEvent) StarterPlayer → StarterPlayerScripts └─ FlashEffect (LocalScript) — ослепление у каждого игрока
Где: Flashbang и SmokeGrenade · LocalScript
Зачем: Бросок по кадру «Release».
-- игроки
local Players = game:GetService("Players")
-- граната
local tool = script.Parent
-- просим сервер бросить
local throwEvent = tool:WaitForChild("Throw")
-- бросок; ключевой кадр «Release» — момент, когда граната вылетает
local anim = tool:WaitForChild("Animation")
-- куда целимся
local mouse = Players.LocalPlayer:GetMouse()
-- бросок уже начат
local busy = false
-- граната вылетает
local function Release()
-- точка под курсором
throwEvent:FireServer(mouse.Hit.Position)
-- конец функции
end
-- клик
tool.Activated:Connect(function()
-- кто держит
local humanoid = tool.Parent:FindFirstChildOfClass("Humanoid")
-- уже бросает или погиб
if busy or not humanoid or humanoid.Health <= 0 then return end
-- одна граната — один бросок
busy = true
-- анимации нет — бросаем сразу
if anim.AnimationId == "" then Release() return end
-- загружаем бросок
local track = humanoid:WaitForChild("Animator"):LoadAnimation(anim)
-- граната ещё в руке
local released = false
-- анимация дошла до ключевого кадра
track.KeyframeReached:Connect(function(name)
-- кадр «Release» — бросок
if name == "Release" and not released then released = true Release() end
-- конец функции
end)
-- анимация кончилась
track.Stopped:Once(function()
-- кадра «Release» нет — бросаем в конце
if not released then released = true Release() end
-- конец функции
end)
-- замах
track:Play()
-- конец функции
end)Где: ServerStorage → Loot → Flashbang · Script
Зачем: Вспышка.
-- игроки
local Players = game:GetService("Players")
-- световая граната
local tool = script.Parent
-- сама граната
local handle = tool:WaitForChild("Handle")
-- клиент бросил
local throwEvent = tool:WaitForChild("Throw")
-- ослепить игрока (ловит FlashEffect у каждого)
local flashEvent = game:GetService("ReplicatedStorage"):WaitForChild("FlashEvent")
-- дальше вспышка не слепит
local RADIUS = 40
-- секунд до взрыва
local FUSE = 2
-- сила броска
local POWER = 70
-- уже брошена
local thrown = false
-- клиент прислал точку
throwEvent.OnServerEvent:Connect(function(player, target)
-- кто держит
local character = tool.Parent
-- уже, чужая граната или мусор
if thrown or Players:GetPlayerFromCharacter(character) ~= player or typeof(target) ~= "Vector3" then return end
-- центр
local root = character:FindFirstChild("HumanoidRootPart")
-- персонажа нет
if not root then return end
-- брошена
thrown = true
-- летящая копия
local grenade = handle:Clone()
-- имя
grenade.Name = "Thrown" .. tool.Name
-- отскакивает от стен и пола
grenade.CanCollide = true
-- у неё есть вес
grenade.Massless = false
-- перед лицом
grenade.CFrame = root.CFrame * CFrame.new(0, 2, -2)
-- в мир
grenade.Parent = workspace
-- к точке
local direction = (target - grenade.Position).Unit
-- бросок чуть вверх — по дуге
grenade.AssemblyLinearVelocity = (direction + Vector3.new(0, 0.35, 0)).Unit * POWER
-- полёт считает сервер — у всех одинаково
grenade:SetNetworkOwner(nil)
-- звук броска
grenade.Throw:Play()
-- в руке гранаты больше нет
handle.Transparency = 1
-- её нельзя подобрать
handle.CanTouch = false
-- не падает сквозь карту
handle.Anchored = true
-- инструмент из рук убираем; скрипт продолжает работать (если удалить — остановится)
tool.Parent = workspace
-- фитиль
task.wait(FUSE)
-- взрыв
grenade.Boom:Play()
-- проверка прямой видимости
local params = RaycastParams.new()
-- исключаем…
params.FilterType = Enum.RaycastFilterType.Exclude
-- …саму гранату
params.FilterDescendantsInstances = {grenade}
-- все игроки
for _, other in ipairs(Players:GetPlayers()) do
-- голова
local head = other.Character and other.Character:FindFirstChild("Head")
-- близко (в методичке вычитали длины векторов)
if head and (head.Position - grenade.Position).Magnitude <= RADIUS then
-- луч от гранаты к голове
local result = workspace:Raycast(grenade.Position, head.Position - grenade.Position, params)
-- стены между ними нет
if not result or result.Instance:IsDescendantOf(other.Character) then
-- ослепить
flashEvent:FireClient(other, grenade.Position)
-- конец проверки
end
-- конец проверки
end
-- конец перебора
end
-- звук доиграет
task.wait(1)
-- гранаты больше нет
grenade:Destroy()
-- и инструмента
tool:Destroy()
-- конец функции
end)Где: ServerStorage → Loot → SmokeGrenade · Script
Зачем: Дым.
-- игроки
local Players = game:GetService("Players")
-- дымовая граната
local tool = script.Parent
-- сама граната
local handle = tool:WaitForChild("Handle")
-- клиент бросил
local throwEvent = tool:WaitForChild("Throw")
-- сколько секунд держится дым
local SMOKE_TIME = 20
-- секунд до взрыва
local FUSE = 2
-- сила броска
local POWER = 70
-- уже брошена
local thrown = false
-- клиент прислал точку
throwEvent.OnServerEvent:Connect(function(player, target)
-- кто держит
local character = tool.Parent
-- уже, чужая граната или мусор
if thrown or Players:GetPlayerFromCharacter(character) ~= player or typeof(target) ~= "Vector3" then return end
-- центр
local root = character:FindFirstChild("HumanoidRootPart")
-- персонажа нет
if not root then return end
-- брошена
thrown = true
-- летящая копия
local grenade = handle:Clone()
-- имя
grenade.Name = "Thrown" .. tool.Name
-- отскакивает от стен и пола
grenade.CanCollide = true
-- у неё есть вес
grenade.Massless = false
-- перед лицом
grenade.CFrame = root.CFrame * CFrame.new(0, 2, -2)
-- в мир
grenade.Parent = workspace
-- к точке
local direction = (target - grenade.Position).Unit
-- бросок чуть вверх — по дуге
grenade.AssemblyLinearVelocity = (direction + Vector3.new(0, 0.35, 0)).Unit * POWER
-- полёт считает сервер — у всех одинаково
grenade:SetNetworkOwner(nil)
-- звук броска
grenade.Throw:Play()
-- в руке гранаты больше нет
handle.Transparency = 1
-- её нельзя подобрать
handle.CanTouch = false
-- не падает сквозь карту
handle.Anchored = true
-- инструмент из рук убираем; скрипт продолжает работать (если удалить — остановится)
tool.Parent = workspace
-- фитиль
task.wait(FUSE)
-- взрыв
grenade.Boom:Play()
-- граната лежит на месте
grenade.Anchored = true
-- эффекты дыма
for _, smoke in ipairs(grenade:GetChildren()) do
-- включаем
if smoke:IsA("Smoke") then smoke.Enabled = true end
-- конец перебора
end
-- дым держится
task.wait(SMOKE_TIME)
-- гранаты больше нет
grenade:Destroy()
-- и инструмента
tool:Destroy()
-- конец функции
end)Где: StarterPlayer → StarterPlayerScripts · LocalScript
Зачем: Белый экран.
-- события
local RS = game:GetService("ReplicatedStorage")
-- плавность
local TS = game:GetService("TweenService")
-- эффекты экрана
local Lighting = game:GetService("Lighting")
-- сервер: тебя ослепило
local flashEvent = RS:WaitForChild("FlashEvent")
-- точка взрыва
flashEvent.OnClientEvent:Connect(function(position)
-- камера
local camera = workspace.CurrentCamera
-- направление на вспышку
local toFlash = (position - camera.CFrame.Position).Unit
-- 1 — смотрит прямо, −1 — спиной
local facing = camera.CFrame.LookVector:Dot(toFlash)
-- отвернулся — слепит слабее
local strength = facing > 0.3 and 1 or 0.35
-- белый экран — только у этого игрока
local flash = Instance.new("ColorCorrectionEffect")
-- имя
flash.Name = "Flash"
-- яркость
flash.Brightness = 2 * strength
-- включаем
flash.Parent = Lighting
-- ослеплён
task.wait(1.5 * strength)
-- проходит
local tween = TS:Create(flash, TweenInfo.new(1.5), {Brightness = 0})
-- плавно
tween:Play()
-- ждём
tween.Completed:Wait()
-- убираем
flash:Destroy()
-- конец функции
end)Запусти Play (или Run) и убедись: