So I’m working on some sort of watch tool and what I’m trying to do is when the tool gets activated, I make the watch fire a remote to the server to open the watch cover in the public, so everyone can see it.
Although, I have had a lot of issues with OnServerEvent, and not a single with OnClientEvent. I have 2 types of scripts. The local script & server script.
Local Script:
-- // SERVICES
local Lighting = game:GetService("Lighting")
local Players = game:GetService("Players")
-- // VARIABLES
local Player = Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local Humanoid = Character:WaitForChild("Humanoid")
local ViewingAnimation = Instance.new("Animation")
ViewingAnimation.AnimationId = "rbxassetid://4663729666"
local Handle = script.Parent:WaitForChild("Handle")
local Load = Humanoid:LoadAnimation(ViewingAnimation)
local Thought = Handle:WaitForChild("Gui")
local Label = Thought:WaitForChild("TextLabel")
local Tool = script.Parent
local WatchRemote = script.Parent:WaitForChild("WatchRemote")
local Toggled = false
-- // ANIMATIONS
TrackLoad = Humanoid:LoadAnimation(ViewingAnimation)
-- // MAIN
Tool.Unequipped:Connect(function()
if Toggled then
Toggled = false
Label.Visible = false
if Load.IsPlaying then Load:Stop() end
end
end)
Tool.Activated:Connect(function()
if not Toggled then
Toggled = true
Label.Visible = true
Load:Play()
WatchRemote:FireServer("Open")
while Toggled do
wait(.1)
Label.Text = "It is " .. Lighting.TimeOfDay:sub(1, 5)
end
elseif Toggled then
Toggled = false
Label.Visible = false
Load:Stop()
WatchRemote:FireServer("Close")
end
end)
Server Script:
local Remote = script.Parent:WaitForChild("WatchRemote")
local TweenService = game:GetService("TweenService")
local Cover = script.Parent:WaitForChild("Cover")
local Open = false
function TweenModel(Obj, Info, Pos, Wait)
local OldCFrame = Obj:FindFirstChild(Obj.Name)
if OldCFrame and OldCFrame:IsA('CFrameValue') then OldCFrame:Destroy() end
local CFrameValue = Instance.new("CFrameValue")
CFrameValue.Name = Obj.Name
CFrameValue.Parent = Obj
CFrameValue.Value = Obj:GetPrimaryPartCFrame()
CFrameValue:GetPropertyChangedSignal("Value"):Connect(function() Obj:SetPrimaryPartCFrame(CFrameValue.Value) end)
local TweenInf = TweenInfo.new(Info.Time,Info.Style,Info.Direction,Info.Repeat or 0,Info.Reverse or false)
local Tween = TweenService:Create(CFrameValue, TweenInf, {Value = Pos})
Tween:Play()
Tween.Completed:Connect(function()
if Wait then
Wait()
end
if CFrameValue ~= nil then
CFrameValue:Destroy()
end
end)
end
function OpenCover()
local Info = {Time = 1.3, Style = Enum.EasingStyle.Linear, Direction = Enum.EasingDirection.Out}
local Pos = Cover.PrimaryPart.CFrame * CFrame.Angles(math.rad(-45), 0, 0)
TweenModel(Cover, Info, Pos)
end
function CloseCover()
local Info = {Time = 1.3, Style = Enum.EasingStyle.Linear, Direction = Enum.EasingDirection.Out}
local Pos = Cover.PrimaryPart.CFrame * CFrame.Angles(math.rad(45), 0, 0)
TweenModel(Cover, Info, Pos)
end
Remote.OnServerEvent:Connect(function(Player, Action)
if Action == "Open" then
coroutine.wrap(OpenCover)()
elseif Action == "Close" then
coroutine.wrap(CloseCover)()
end
end)
script.Parent.Unequipped:Connect(function()
coroutine.wrap(CloseCover)()
end)
Keep in case these scripts are inside the tool, so is the event.
I just tested your code on some parts in the Workspace and it worked fine for me. Could you elaborate on the issues you’ve been experiencing and maybe include a place file with the affected tool in it?
There’s no issues with the tweening part as far as I can tell, provided the parts are Anchored.
If the parts are Unanchored, you might be better off manipulating welds or constraints because if the object moves at all during the animation it may end up looking weird.
It’s because the model you are trying to CFrame is unanchored and welded to the rest of the tool. You’re trying to move it but the weld keeps bringing it back. You want to manipulate either the C0 or C1 property of the Weld instead of the model’s PrimaryPart CFrame.
You’ll need to make sure any welds relating to the lid and its design are inside the Cover model, and you’ll want a single weld between the hinge pieces - this weld is the one you want to manipulate.
No worries. I’ve updated your place file to have the correct welds and updated the code to tween the weld’s C0 instead of the model CFrame.
Take a look at the hierarchy, which parts are welded to which, and the code that I edited, and hopefully you’ll be able to understand why I’ve set it up like that.
For anyone who doesn't want to open the place file, here's the changes in steps:
The welds from Handle to HingeCover, Lid and LidDesign were removed.
LidDesign and Lid were then welded to HingeCover.
HingeCover was then welded to Hinge. The C1 of the weld offset it so the axis of the weld sits at the hinge point.
The tween then adjusts the C0 of the HingeCover-to-Hinge weld to rotate it by the amount that kanchoplets wanted in the original script - 45 degrees. It had to switch from X to Z axis due to the weld orientation. The signs also flipped for open and closed.
Here is the updated hierarchy of the parts and welds:
In red is the Weld we are manipulating in the animation:
The local script and remote event did not need editing.
Here is the updated server script:
local Remote = script.Parent:WaitForChild("WatchRemote")
local TweenService = game:GetService("TweenService")
local Cover = script.Parent:WaitForChild("Cover")
local Open = false
function TweenWeld(Weld, Info, Pos, Wait)
local OldCFrame = Weld:FindFirstChild(Weld.Name)
if OldCFrame and OldCFrame:IsA('CFrameValue') then OldCFrame:Destroy() end
local CFrameValue = Instance.new("CFrameValue")
CFrameValue.Name = Weld.Name
CFrameValue.Parent = Weld
CFrameValue.Value = Weld.C0
CFrameValue:GetPropertyChangedSignal("Value"):Connect(function() Weld.C0 = CFrameValue.Value end)
local TweenInf = TweenInfo.new(Info.Time,Info.Style,Info.Direction,Info.Repeat or 0,Info.Reverse or false)
local Tween = TweenService:Create(CFrameValue, TweenInf, {Value = Pos})
Tween:Play()
Tween.Completed:Connect(function()
if Wait then
Wait()
end
if CFrameValue ~= nil then
CFrameValue:Destroy()
end
end)
end
function OpenCover()
local Info = {Time = 1.3, Style = Enum.EasingStyle.Linear, Direction = Enum.EasingDirection.Out}
local Pos = Cover.HingeCover.Hinge.C0 * CFrame.Angles(0, 0, math.rad(45))
TweenWeld(Cover.HingeCover.Hinge, Info, Pos)
end
function CloseCover()
local Info = {Time = 1.3, Style = Enum.EasingStyle.Linear, Direction = Enum.EasingDirection.Out}
local Pos = Cover.HingeCover.Hinge.C0 * CFrame.Angles(0, 0, math.rad(-45))
TweenWeld(Cover.HingeCover.Hinge, Info, Pos)
end
Remote.OnServerEvent:Connect(function(Player, Action)
print("event fired")
if Action == "Open" then
print("opening cover")
coroutine.wrap(OpenCover)()
elseif Action == "Close" then
print("closing cover")
coroutine.wrap(CloseCover)()
end
end)
script.Parent.Unequipped:Connect(function()
coroutine.wrap(CloseCover)()
end)
Yeah you can record the tween into a variable outside the function instead of using a local variable, and cancel it if another click comes in (or ignore the click if it’s still running by using a debounce).
-- // SERVICES
local Lighting = game:GetService("Lighting")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
-- // VARIABLES
local Player = Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local Humanoid = Character:WaitForChild("Humanoid")
local ViewingAnimation = Instance.new("Animation")
ViewingAnimation.AnimationId = "rbxassetid://4663729666"
local Handle = script.Parent:WaitForChild("Handle")
local Load = Humanoid:LoadAnimation(ViewingAnimation)
local Thought = Handle:WaitForChild("Gui")
local Label = Thought:WaitForChild("TextLabel")
local Tool = script.Parent
local WatchRemote = script.Parent:WaitForChild("WatchRemote")
local Toggled = false
local Debounce = false
-- // ANIMATIONS
TrackLoad = Humanoid:LoadAnimation(ViewingAnimation)
-- // MAIN
Tool.Unequipped:Connect(function()
if Toggled and not Debounce then
Debounce = true
Toggled = false
Label.Visible = false
if Load.IsPlaying then Load:Stop() end
end
end)
Tool.Activated:Connect(function()
if not Toggled and not Debounce then
Toggled = true
Label.Visible = true
Debounce = true
Load:Play()
WatchRemote:FireServer("Open")
while Toggled do
Label.Text = "It is " .. Lighting.TimeOfDay:sub(1, 5)
RunService.Heartbeat:Wait()
end
elseif Toggled and not Debounce then
Toggled = false
Label.Visible = false
Debounce = true
Load:Stop()
WatchRemote:FireServer("Close")
end
end)
RunService.Stepped:Connect(function()
if Debounce then
wait(.6)
Debounce = false
end
end)
If so, for some reason the debounce doesn’t work sometimes.
You probably want a debounce on the server rather than the client. So the server just rejects the input from the client until it’s ready to do a new animation.
Unfortunately I’m out the house now so if you’re still stuck by this evening I’ll show you how to set that up within the tween function you’ve got.
local Remote = script.Parent:WaitForChild("WatchRemote")
local TweenService = game:GetService("TweenService")
local Cover = script.Parent:WaitForChild("Cover")
local Open = false
local OriginalC0 = Cover:WaitForChild( 'HingeCover' ):WaitForChild( 'Hinge' ).C0
local CurrentTween = nil
function TweenWeld(Weld, Info, Pos, Wait)
local OldCFrame = Weld:FindFirstChild(Weld.Name)
if OldCFrame and OldCFrame:IsA('CFrameValue') then OldCFrame:Destroy() end
local CFrameValue = Instance.new("CFrameValue")
CFrameValue.Name = Weld.Name
CFrameValue.Parent = Weld
CFrameValue.Value = Weld.C0
CFrameValue:GetPropertyChangedSignal("Value"):Connect(function() Weld.C0 = CFrameValue.Value end)
local TweenInf = TweenInfo.new(Info.Time,Info.Style,Info.Direction,Info.Repeat or 0,Info.Reverse or false)
local Tween = TweenService:Create(CFrameValue, TweenInf, {Value = Pos})
Tween:Play()
Tween.Completed:Connect(function( tweenStatus )
if tweenStatus == Enum.TweenStatus.Completed then
if Wait then
Wait()
end
if CFrameValue ~= nil then
CFrameValue:Destroy()
end
end
end)
return Tween
end
function OpenCover()
if CurrentTween and CurrentTween.PlaybackState == Enum.PlaybackState.Playing then
return
end
local Info = {Time = 1.3, Style = Enum.EasingStyle.Linear, Direction = Enum.EasingDirection.Out}
local Pos = OriginalC0 * CFrame.Angles(0, 0, math.rad(45))
CurrentTween = TweenWeld(Cover.HingeCover.Hinge, Info, Pos, function()
Cover.OpenStatus.Value = true
end)
end
function CloseCover()
if CurrentTween and CurrentTween.PlaybackState == Enum.PlaybackState.Playing then
return
end
local Info = {Time = 1.3, Style = Enum.EasingStyle.Linear, Direction = Enum.EasingDirection.Out}
local Pos = OriginalC0
CurrentTween = TweenWeld(Cover.HingeCover.Hinge, Info, Pos, function()
Cover.OpenStatus.Value = false
end)
end
local Bool = Instance.new('BoolValue', Cover)
Bool.Name = 'OpenStatus'
Remote.OnServerEvent:Connect(function(Player, Action)
print("event fired")
if Action == "Open" then
print("opening cover")
coroutine.wrap(OpenCover)()
elseif Action == "Close" then
print("closing cover")
coroutine.wrap(CloseCover)()
end
end)
script.Parent.Unequipped:Connect(function()
coroutine.wrap(CloseCover)()
end)
And on the client, instead of using Toggled to determine if the watch should be opened or closed, use Tool.Cover.OpenStatus.Value in the condition instead.
I wrote this on my phone so let me know if there are any issues.
Essentially it blocks the request to open or close if there is currently an animation going on for the cover. The change on the client and the addition of OpenStatus is to prevent the client’s Toggled value becoming out of sync with the actual state of the cover due to blocked requests for it to open/close.