Я написал LocalScript (MessageController) для сюжетной игры Roblox под названием Police Raid. Этот сценарий управляет ходом истории, изменяя сообщения пользовательского интерфейса и запуская события.
-- This script controls the progress of the messages! (It's the GOAT)
local message = script.Parent.Message
-- Wait for character to load
local player = game:GetService("Players").LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
-- Change message
task.wait(7)
message.Text = "Back there is the panic room. If there's any trouble, click the big red button and get inside. We're just going to hole up in here until someone comes to help us."
-- Change message
task.wait(7)
message.Text = "[static] This is the Robloxian Police Department, Barricaded Supsects Division. We have a warrant for your arrest on counts of [static]..."
message.TextColor3 = BrickColor.Red().Color
script["Heli Sound"]:Play()
-- Change message
task.wait(5)
message.Text = "Uh oh..."
message.TextColor3 = BrickColor.Black().Color
-- Change message
task.wait(2)
message.Text = "If you don't come out and surrender now, we will raid the premises!"
message.TextColor3 = BrickColor.Red().Color
-- Change message
task.wait(3)
message.Text = "Get to the panic room! NOW!!!"
-- Enable panic room
game:GetService("Workspace").Button.Part.ClickDetector.MaxActivationDistance = 32
message.TextColor3 = BrickColor.Black().Color
game:GetService("Workspace").TouchDetector.Touched:Connect(function(otherPart: BasePart)
-- In the panic room
task.wait(1)
script["Explosion"]:Play()
-- Change message
task.wait(3)
message.Text = "They're raiding the house... we should be safe in here."
script["Loud Explosion"]:Play()
-- Change message
task.wait(2)
message.Text = "They're in the panic room!"
message.TextColor3 = BrickColor.Red().Color
script["Extra Loud Explosion"]:Play()
-- Change message
task.wait(2)
message.Text = "I don't think we're as safe as I thought. Grab that rifle from the floor and get to the basement, QUICK!"
message.TextColor3 = BrickColor.Black().Color
game:GetService("Workspace")["Panic Door"].Fire.Enabled = true
game:GetService("Workspace")["Panic Panel"].CanCollide = false
-- Change message
game:GetService("Workspace")["Basement Detector"].Touched:Connect(function(otherPart: BasePart)
task.wait(2)
message.Text = "Okay... this basement was designed to survive a raid, so we should be okay."
-- Change message
task.wait(7)
script["Extra Loud Explosion"]:Play()
game:GetService("Workspace")["Basement Wall"].Transparency = 1
message.Text = "Put your hands up!"
message.TextColor3 = BrickColor.Red().Color
-- Change message
task.wait(2)
message.Text = "Get down, part of the wall is still up!"
message.TextColor3 = BrickColor.Black().Color
local counter = 10
task.wait(1)
for i = counter, 0, -1 do
task.wait(1)
script.Gunfire:Play()
-- Damage player
-- Check if they are in a safe area
local safezone = game:GetService("Workspace")["Wall Parts"].Safezone
safezone.Touched:Connect(function() end) -- TouchInterest
local function CheckIfPlayerIsInArea(Part,Character)
local touching = Part:GetTouchingParts()
for i=1,#touching do
if touching[i] == Character.HumanoidRootPart then
return true
end
end
return false
end
if not CheckIfPlayerIsInArea(safezone, game:GetService("Players").LocalPlayer.Character) then
--game:GetService("ReplicatedStorage").DamagePlayer:FireServer(10)
game:GetService("Players").LocalPlayer.Character.Humanoid.Health -= 1
end
end
-- Change message
message.Text = "The Robloxian Freedom Fighters are here! Get into their helicopter and escape to headquarters!"
game:GetService("Workspace")["Basement Wall"].CanCollide = false
local fighters = game:GetService("Workspace")["Freedom Fighters"]
fighters["RFF-Beta-31"]:MoveTo(Vector3.new(-100.242, 3.003, 39.393))
fighters["RFF-Alpha-0"]:MoveTo(Vector3.new(-103.259, 3.003, 31.674))
fighters["RFF-Epsilon-97"]:PivotTo(CFrame.new(-90.501, 9.305, 40.968))
fighters["RFF-Sigma-69"]:MoveTo(Vector3.new(-98.693, 3.003, 24.884))
fighters["RFF-Gamma-3"]:MoveTo(Vector3.new(-122.102, 3.003, 25.833))
fighters["RFF-Foxtrot-11"]:MoveTo(Vector3.new(-96.868, 3.003, 48.219))
fighters["RFF-Delta-4"]:MoveTo(Vector3.new(-113.647, 3.003, 32.763))
game:GetService("Workspace")["RFF Heli"]:PivotTo(CFrame.new(-105.629, 3.5, 49.35))
local ChatService = game:GetService("Chat")
local talkpart = game:GetService("Workspace")["BSD Commander Parker"].Handle
do
ChatService:Chat(talkpart, "It's an ambush!")
end
print("Triggered")
task.wait(1)
script["Machine Gun Fire"]:Play()
end)
end)
Проблема в том, что «Triggered», кажется, выводится на консоль 528 раз, а звук «Gunfire» воспроизводится поверх самого себя очень быстро. NPC Паркер говорит: «Это засада!» снова и снова в пузырях чата над его головой. Борцы за свободу и RFF Heli перемещаются настолько сильно, что в конечном итоге исчезают, а игра тормозит из-за слишком быстрого выполнения кода. Я понятия не имею, почему код выполняет так много. Часть проблемы выглядит следующим образом:
local counter = 10
task.wait(1)
for i = counter, 0, -1 do
task.wait(1)
script.Gunfire:Play()
-- Damage player
-- Check if they are in a safe area
local safezone = game:GetService("Workspace")["Wall Parts"].Safezone
safezone.Touched:Connect(function() end) -- TouchInterest
local function CheckIfPlayerIsInArea(Part,Character)
local touching = Part:GetTouchingParts()
for i=1,#touching do
if touching[i] == Character.HumanoidRootPart then
return true
end
end
return false
end
if not CheckIfPlayerIsInArea(safezone, game:GetService("Players").LocalPlayer.Character) then
--game:GetService("ReplicatedStorage").DamagePlayer:FireServer(10)
game:GetService("Players").LocalPlayer.Character.Humanoid.Health -= 1
end
end
-- Change message
message.Text = "The Robloxian Freedom Fighters are here! Get into their helicopter and escape to headquarters!"
game:GetService("Workspace")["Basement Wall"].CanCollide = false
local fighters = game:GetService("Workspace")["Freedom Fighters"]
fighters["RFF-Beta-31"]:MoveTo(Vector3.new(-100.242, 3.003, 39.393))
fighters["RFF-Alpha-0"]:MoveTo(Vector3.new(-103.259, 3.003, 31.674))
fighters["RFF-Epsilon-97"]:PivotTo(CFrame.new(-90.501, 9.305, 40.968))
fighters["RFF-Sigma-69"]:MoveTo(Vector3.new(-98.693, 3.003, 24.884))
fighters["RFF-Gamma-3"]:MoveTo(Vector3.new(-122.102, 3.003, 25.833))
fighters["RFF-Foxtrot-11"]:MoveTo(Vector3.new(-96.868, 3.003, 48.219))
fighters["RFF-Delta-4"]:MoveTo(Vector3.new(-113.647, 3.003, 32.763))
game:GetService("Workspace")["RFF Heli"]:PivotTo(CFrame.new(-105.629, 3.5, 49.35))
local ChatService = game:GetService("Chat")
local talkpart = game:GetService("Workspace")["BSD Commander Parker"].Handle
do
ChatService:Chat(talkpart, "It's an ambush!")
end
print("Triggered")
task.wait(1)
script["Machine Gun Fire"]:Play()
Извините, если кода много или он плохо отформатирован.
Я попытался отладить код, напечатав текст «Триггерировано», и просмотрел сценарий на предмет всего, что может вызывать бесконечный цикл. Я также попробовал добавить task.wait(math.huge) в конец скрипта, чтобы предотвратить его многократное выполнение (над операторами end), но это не повлияло на циклическое выполнение. Интересно, что Machine Gun Fire (зацикленный звук) звучит не более одного раза. Если это полезно, LocalScript является родительским элементом ScreenGui, который является родительским для StarterGui.
Я не могу опубликовать этот вопрос на форуме разработчиков Roblox из-за отсутствия репутации, поскольку я просматриваю сайт без входа в систему в течение длительного времени.
Возможно, над линией game:GetService("Workspace")["Basement Detector"].Touched:Connect(function(otherPart: BasePart) добавьте print("Enabled the BasementDetector") и посмотрите, будет ли она вызываться более одного раза (и столько же раз, сколько печатается «Triggered»).
@Кайя Спасибо за помощь! Детектор подвала срабатывает сотни раз, а это, скорее всего, является причиной проблемы. Как мне остановить этот эффект? Я считаю, что события касания запускаются для каждого кадра, когда персонаж соприкасается с рассматриваемой частью.
Как сохранить состояние в скрипте Roblox? Это так же просто, как объявить глобальную переменную? Если да, то вы можете сделать (верхняя часть скрипта) local hasTriggeredTouchDetector = false, а затем в событии TouchDetector (косая черта — это новая строка) if hasTriggeredTouchDetector then / return / else / hasTriggeredTouchDetector = true / end. Это должно пропустить все, кроме первого вызова.
Вы также можете увидеть, есть ли Disconnect аналог Connect, чтобы можно было отключить события после их запуска. Я не уверен, что предпочтительнее в Roblox — я мог бы проверить, как с этим справляются другие хорошо продуманные скрипты. Другие вещи, которые вы можете искать (и, к сожалению, я не нахожу здесь хороших ресурсов), — это «обнаружение краев» и «устранение дребезга». Обнаружение края — это термин, обозначающий обнаружение того, когда событие (например, контакт с триггером) начинает происходить и когда оно прекращается. Устранение дребезга — это удаление коротких краев, например, если персонаж сходит со спускового крючка и сразу же возвращается обратно.
(Это похоже на то, если бы вам нужно, чтобы персонаж произносил свой диалог с игроком каждый раз, когда он приближался, но не хотел, чтобы он повторял это снова и снова, если игрок входил и выходил из зоны действия. Это также такое часто случается на физическом оборудовании, потому что кнопки подпрыгивают при нажатии!) Это не очень хорошие ссылки, но это что-то: резисторкапаситор.wordpress.com/2018/04/29/… stackoverflow.com /questions/24004791/…
Ах, эй!! Это выглядит полезным: create.roblox.com/docs/scripting/debounce. А если вы не хотите, чтобы событие перезагружалось и запускалось только один раз, удалите task.wait() и строку ниже.





Проблема, с которой вы сталкиваетесь, заключается в том, что вы подключаете прослушиватели к событиям и никогда не отключаете их, а также не устраняете события, которые срабатывают несколько раз. Распространенная проблема, особенно с событием Touched, заключается в том, что оно может срабатывать, когда что-либо в рабочей области касается его, а также срабатывать несколько раз для одной и той же модели персонажа. Если ваша рука и нога коснутся Детали, событие сработает для обеих этих частей.
Помимо этого, большая часть вашей игровой логики должна происходить в сценарии, таким образом, когда воспроизводятся звуки или объекты перемещаются, скрываются или уничтожаются, они должны происходить для всех одновременно.
Единственное, что должно произойти в LocalScript, — это отображение сообщений. Итак, давайте настроим серверу простой способ отправки сообщений в пользовательский интерфейс.
Итак, выполните следующие действия:
DisplayMessage.-- import services
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- find the UI elements
local lblMessage = script.Parent.Message
-- find the RemoteEvent telling us that the server has a message for us
local DisplayMessage = ReplicatedStorage.DisplayMessage
-- display the messages when the server sends them to us
DisplayMessage.OnClientEvent:Connect(function(message : string, color : Color3)
lblMessage.Text = message
lblMessage.Color3 = color
end)
debounceTouch. Мы будем использовать это, чтобы запретить пользователю многократно запускать событие Touch, когда его модель персонажа касается детали.local Players = game:GetService("Players")
-- Debounce a function so that it only fires once per player touch event
local function debounceTouch(part : BasePart, onPlayerTouched : (Player)->(), onPlayerTouchEnded : (Player)->()?)
-- keep track of which players are touching the part
local playersTouching : { [Player] : number } = {}
local touchedConnection = part.Touched:Connect(function(otherPart : BasePart)
local player = Players:GetPlayerFromCharacter(otherPart.Parent)
if not player then
return
end
if not playersTouching[player] then
-- a player has begun touching!
onPlayerTouched(player)
playersTouching[player] = 1
else
playersTouching[player] += 1
end
end)
local touchEndedConnection = part.TouchEnded:Connect(function(otherPart : BasePart)
local player = Players:GetPlayerFromCharacter(otherPart.Parent)
if not player then
return
end
-- when a player stops touching, decrement the counter so that we can clear the debounce flag
if playersTouching[player] then
playersTouching[player] -= 1
if (playersTouching[player] <= 0) then
playersTouching[player] = nil
-- the player has stopped touching, fire the callback if provided
if onPlayerTouchEnded then
onPlayerTouchEnded(player)
end
end
end
end)
-- return an object that functions like an RbxScriptConnection token so that we can disconnect it when it is no longer relevant
return {
Connected = (touchedConnection.Connected or touchEndedConnection.Connected),
Disconnect = function()
touchedConnection:Disconnect()
touchEndedConnection:Disconnect()
end,
}
end
return debounceTouch
-- import services
local ChatService = game:GetService("Chat")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
-- import helper functions
local debounceTouch = require(ReplicatedStorage.debounceTouch)
-- create a helper function to send the messages to all players
local DisplayMessage = ReplicatedStorage.DisplayMessage
local function displayMessageWithDelay(delay : number, message : string, color : Color3?)
task.wait(delay)
DisplayMessage:FireAllClients(message, color)
end
-- Wait for any player to load into the game
if #Players.GetPlayers() == 0 then
Players.PlayerAdded:Wait()
end
-- define some values
local SAFEZONE_DAMAGE_PER_SECOND = 10
-- write down all the messages at the top
local messages = {
"Back there is the panic room. If there's any trouble, click the big red button and get inside. We're just going to hole up in here until someone comes to help us.",
"[static] This is the Robloxian Police Department, Barricaded Suspects Division. We have a warrant for your arrest on counts of [static]...",
"Uh oh...",
"If you don't come out and surrender now, we will raid the premises!",
"Get to the panic room! NOW!!!",
"They're raiding the house... we should be safe in here.",
"They're in the panic room!",
"I don't think we're as safe as I thought. Grab that rifle from the floor and get to the basement, QUICK!",
"Okay... this basement was designed to survive a raid, so we should be okay.",
"Put your hands up!",
"Get down, part of the wall is still up!",
"The Robloxian Freedom Fighters are here! Get into their helicopter and escape to headquarters!",
}
local colors = {
black = BrickColor.Black().Color,
red = BrickColor.Red().Color,
}
-- load some workspace elements
local ButtonClickDetector = Workspace.Button.Part.ClickDetector
local TouchDetector = Workspace.TouchDetector
local PanicDoor = Workspace["Panic Door"]
local BasementDetector = Workspace["Basement Detector"]
local BasementWall = Workspace["Basement Wall"]
local safezone = Workspace["Wall Parts"].Safezone
local fighters = Workspace["Freedom Fighters"]
local helicopter = Workspace["RFF Heli"]
local talkPart = Workspace["BSD Commander Parker"].Handle
-- start playing the game
displayMessageWithDelay(7, messages[1])
displayMessageWithDelay(7, messages[2], colors.red)
script["Heli Sound"]:Play()
displayMessageWithDelay(5, messages[3], colors.black)
displayMessageWithDelay(2, messages[4], colors.red)
displayMessageWithDelay(3, messages[5])
-- Enable panic room
ClickDetector.MaxActivationDistance = 32
local touchedConnection
touchedConnection = TouchDetector.Touched:Connect(function(otherPart: BasePart)
-- make sure that this only fires if a player touches it
if not Players:GetPlayerFromCharacter(otherPart.Parent) then
return
end
-- disconnect the connection so that it doesn't fire again
touchedConnection:Disconnect()
-- In the panic room
task.wait(1)
script["Explosion"]:Play()
displayMessageWithDelay(3, messages[6], colors.black)
script["Loud Explosion"]:Play()
displayMessageWithDelay(2, messages[7], colors.red)
script["Extra Loud Explosion"]:Play()
displayMessageWithDelay(2, messages[8], colors.black)
-- set the door on fire
PanicDoor.Fire.Enabled = true
PanicDoor.CanCollide = false
local basementTouchedConnection
basementTouchedConnection = BasementDetector.Touched:Connect(function(otherPart: BasePart)
-- make sure that this only fires if a player touches it
if not Players:GetPlayerFromCharacter(otherPart.Parent) then
return
end
-- disconnect the connection so it only fires once
basementTouchedConnection:Disconnect()
-- start playing the next set of messages
displayMessageWithDelay(2, messages[9])
displayMessageWithDelay(7, messages[10], colors.red)
-- explode the basement walls
script["Extra Loud Explosion"]:Play()
BasementWall.Transparency = 1
displayMessageWithDelay(2, messages[11], colors.black)
-- create a list of players currently touching the safezone
local playersInSafezone = {}
local playerList = Players:GetPlayers()
for _, player in ipairs(playerList) do
playersInSafezone[player] = false
end
local safezoneTouchConnection = debounceTouch(safezone, function(player)
-- when they touch the safezone, mark them as safe
playersInSafezone[player] = true
end, function(player)
-- when they stop touching the safezone, mark them as unsafe
playersInSafezone[player] = false
end)
task.wait(1)
for i = 10, 0, -1 do
task.wait(1)
script.Gunfire:Play()
-- Check if they are in a safe area
for player, isSafe in pairs(playersInSafezone) do
if not isSafe then
-- Damage player
player.Character.Humanoid:TakeDamage(SAFEZONE_DAMAGE_PER_SECOND)
--ReplicatedStorage.DamagePlayer:FireServer(10)
end
end
end
-- clean up the safezone events
safezoneTouchConnection:Disconnect()
playersInSafezone = nil
-- Change message
displayMessageWithDelay(0, messages[12])
BasementWall.CanCollide = false
-- move the fighters into place
fighters["RFF-Beta-31"]:MoveTo(Vector3.new(-100.242, 3.003, 39.393))
fighters["RFF-Alpha-0"]:MoveTo(Vector3.new(-103.259, 3.003, 31.674))
fighters["RFF-Epsilon-97"]:PivotTo(CFrame.new(-90.501, 9.305, 40.968))
fighters["RFF-Sigma-69"]:MoveTo(Vector3.new(-98.693, 3.003, 24.884))
fighters["RFF-Gamma-3"]:MoveTo(Vector3.new(-122.102, 3.003, 25.833))
fighters["RFF-Foxtrot-11"]:MoveTo(Vector3.new(-96.868, 3.003, 48.219))
fighters["RFF-Delta-4"]:MoveTo(Vector3.new(-113.647, 3.003, 32.763))
helicopter:PivotTo(CFrame.new(-105.629, 3.5, 49.35))
ChatService:Chat(talkPart, "It's an ambush!")
print("Triggered")
task.wait(1)
script["Machine Gun Fire"]:Play()
end)
end)
Вы заметите, что я изменил способ нанесения урона игроку и принцип работы безопасной зоны. Таким образом, мы можем каждый раз быстро проверять, кто касается безопасной зоны, без необходимости просить движок искать детали у игроков.
Единственное, что я действительно сделал, это переместил все игровые сообщения в список вверху, в этом не было особой необходимости, но я подумал, что так будет проще увидеть логику игры, а не читать сообщения внутри тела. сценария. Это мое предпочтение, и оно не является необходимым для функционирования вашего кода.
Надеюсь, это поможет.
Спасибо за полезный ответ! Однако причина, по которой моя логика была полностью реализована в LocalScript, заключалась в том, что я не хочу, чтобы история развивалась для одного игрока из-за того, что другой игрок что-то запускает: если игрок А коснется BasementDetector, не вызовет ли это следующее сообщение для игрока B как хорошо? Если у игроков есть собственный локальный сюжетный прогресс, они не смогут проходить через события игры, не оказавшись в нужной области. Кроме того, LocalScript, похоже, вызывает ошибку «Players.Player.PlayerGui.ScreenGui.LocalController:12: попытка индексировать строку с ошибкой «Текст».
Все, что вы сказали, в точку. Я перенес логику на сервер, чтобы история могла развиваться по мере того, как кто-то выполнял соответствующие действия для ее развития. Очень часто в StackOverflow люди не уделяют активного внимания этому аспекту, поэтому я по умолчанию обращаюсь к авторитету сервера за предложениями. Вы можете перенести логику обратно в свой LocalScript, если индивидуальный прогресс жизненно важен для вашей игры. Вся логика устранения дребезга основана на «любом» игроке, но вместо этого вы можете сравнить игрока, возвращенного из Players:GetPlayerFromCharacter, с Players.LocalPlayer в качестве ключа. Также извините за ошибку!
Я не знаком со сценариями Roblox, но если
game:GetService("Workspace").TouchDetector.Touchedактивируется много раз, вы каждый раз подключаете новое событие кgame:GetService("Workspace")["Basement Detector"].Touched. Таким образом, если к TouchDetector прикоснуться 10 раз, у вас будет 10 событий, которые одновременно сработают на BasementDetector. Может быть, вместо того, чтобы создавать событие внутри другого события, вам следует создать событие в начале и не делать ничего, пока не будет установлена определенная условная переменная?