Alright, there are no errors, but nothing happens on ;pressing the Key. The server script is in ServerScriptService, and the localscript in startergui.
server script:
local remotes = {
uppercut = game:GetService('ReplicatedStorage'):WaitForChild('ClickHitbox');
}
local function isAlive(player)
local char = player.Character
if not char or not char:IsDescendantOf(game.Workspace) or not char:FindFirstChild "Humanoid" or char.Humanoid:GetState() == Enum.HumanoidStateType.Dead then
return
end
return char
end
local function getArms(character, side) -- gets all the arm parts of the side the player wants to damage from so we can check length
-- Will only work for R15
local hand = character:FindFirstChild(side .. 'Arm')
if hand then
return hand
end
end
remotes.uppercut.OnServerEvent:connect(function (player, arm)
if player and arm and typeof(arm) == 'string' and arm == 'Left' or arm == 'Right' then
-- some typechecking, highly recommend you also add a check to limit the amount of times a player can fire
-- based on the cooldown of each attack etc to stop exploiting
local character = isAlive(player)
if character then
local arms = getArms(character, arm)
if arms then
local active = { } -- a table we've defined as active, which we'll store a bunch of characters within
for i, v in next, game.Players:GetPlayers() do -- get all players in the game, and loop through them
if v and v ~= player then -- if the player we're looking at in the list of the players in the game is NOT the player who sent this remote then
local char = isAlive(v)
if char then -- is this player that we're looking at alive?
active[#active + 1] = char -- add that character to the 'active list'
end
end
end
local length = arms.Size.y
local bounding = character.PrimaryPart
local ray = Ray.new(bounding.Position, bounding.CFrame.lookVector * (length + 1)) -- Fire a ray from our character's torso, towards its lookvector with a length of our arm size
local hit, pos = game.Workspace:FindPartOnRayWithWhitelist(ray, active) -- find any parts we hit that's a part of a character's
if hit and hit.Parent then
local char = hit.Parent
local hum = hit.Parent:FindFirstChild 'Humanoid'
if hum then
hum:TakeDamage(99) -- Whatever we want to do to that other player's character
print("Hit")
end
end
end
end
end
end)
localscript:
local player = game.Players.LocalPlayer
local character
local animationFolder = game:GetService('ReplicatedStorage'):WaitForChild('AnimationFolder'):GetChildren() --> Change 'AnimationFolder' to a folder with all of your animations in it
local anims = { }
--> This will create an easy to access dict for our anims, for this, we need an animation in there called the name of the action, in this case:
--> 'Uppercut' will need to be in the animation folder
for i, v in next, animationFolder do
anims[v.Name] = v
end
local remotes = {
--> Create a table of remotes, so that you can access them at will for each command
Uppercut = game:GetService('ReplicatedStorage'):WaitForChild('Remotes'):WaitForChild('ClickHitbox');
--[?] To add another, you would write: otherAnimation = game:GetService('ReplicatedStorage'):WaitForChild('Remotes'):WaitForChild('otherRemote')';
}
local states = { --> This is used to keep track of what our player is currently doing
playing = nil; --> This determines whether or not we're performing an action currently so that we can't perform another
last = {
--> This is where we'll store the last performed action times
};
}
local cooldowns = { --[!] You could put this in a module for both the server + the client to access
Uppercut = 6; --> How long you should wait before someone can perform another Uppercut (in seconds)
}
local actions = { --[!] This could also be put in a module as above
Uppercut = Enum.KeyCode.R; --> What input are we looking for when we want to perform this action
}
local function isAlive()
local char = character or player.Character
if not char or not char:IsDescendantOf(game.Workspace) or not char:FindFirstChild "Humanoid" or char.Humanoid:GetState() == Enum.HumanoidStateType.Dead then
return
end
return char
end
local function checkCooldown(class)
if cooldowns[class] then --> Do we have a cooldown value to compare to for this class of action?
if (time() - (states.last[class] or 0)) > cooldowns[class] then --> Returns true if the last time we performed this action has passed the length of the cooldown
return true
end
end
return false
end
local function performAction(class, args)
local anim = anims[class]
local remote = remotes[class]
if anim and remote then
local track = character.Humanoid:LoadAnimation(anim)
track.Priority = Enum.AnimationPriority.Action
track:GetMarkerReachedSignal("ActionFrame"):Connect(function ()
--> Tell the server we're at the action frame of the animation, so it needs to look to do any damage
remote:FireServer(args)
--> Update our states so we can perform other actions again + add our last time to the list
states.last[class] = time()
states.playing = false
end)
track:Play()
end
end
game:GetService("UserInputService").InputBegan:Connect(function(Input, gameProcessed)
character = isAlive()
if not character or gameProcessed then
return
end
if Input.UserInputType == Enum.UserInputType.Keyboard then
if Input.KeyCode == actions.Uppercut then
if not states.playing and checkCooldown('Uppercut') then
states.playing = true
performAction('Uppercut', 'Right')
end
end
end
end)
You need to publish the animation âUppercutâ, those are keyframes. Go back into the animation editor, click the three dots and it will either say âExportâ or âPublishâ (Canât remember which). Export/Publish it to a Roblox animation. It will provide you with an url when you publish it, copy that url and then insert an Animation object into âAnimationFolderâ and set the AnimationID as the url you just got given when you published it.
Are you trying to damage that dummy? If so, it wonât work. Itâs because Iâve made the server only check for collisions with playerâs parts i.e. other players other than yourself, not NPCs.
Again, this is why details are important. Do you only want the uppercut to damage NPCs? Or both?
Can you remove everything thatâs not related to this and send the rbxl file? Will check and ping it back to you, itâs difficult unless you can add debug output for me and print the if statements to determine whatâs failing
You had two remote events called ClickHitbox, one was inside ReplicatedStorage, and the other was inside a folder called âRemotesâ inside ReplicatedStorage⌠We only needed the latter remote because thatâs the only one being fired to the client. On the server code in the place you sent, it was referencing the replicated remote which was never fired by the client.
The other issue was my fault, I forgot that R6 has spaces between âRightâ and âArmâ for their limbs in my haste to provide you with a solution. I have since changed this for you, and it now works.
Hope this has provided you with a comprehensive solution and youâve learned a little.
Change the server code to this:
local remotes = {
uppercut = game:GetService('ReplicatedStorage'):WaitForChild('Remotes'):WaitForChild('ClickHitbox');
}
local function isAlive(player)
local char = player.Character
if not char or not char:IsDescendantOf(game.Workspace) or not char:FindFirstChild "Humanoid" or char.Humanoid:GetState() == Enum.HumanoidStateType.Dead then
return
end
return char
end
local function getArms(character, side) -- gets all the arm parts of the side the player wants to damage from so we can check length
-- Will only work for R15
local hand = character:FindFirstChild(side .. ' Arm')
if hand then
return hand
end
end
remotes.uppercut.OnServerEvent:connect(function (player, arm)
if player and arm and typeof(arm) == 'string' and arm == 'Left' or arm == 'Right' then
-- some typechecking, highly recommend you also add a check to limit the amount of times a player can fire
-- based on the cooldown of each attack etc to stop exploiting
local character = isAlive(player)
if character then
local arms = getArms(character, arm)
if arms then
local active = { } -- a table we've defined as active, which we'll store a bunch of characters within
for i, v in next, game.Players:GetPlayers() do -- get all players in the game, and loop through them
if v and v ~= player then -- if the player we're looking at in the list of the players in the game is NOT the player who sent this remote then
local char = isAlive(v)
if char then -- is this player that we're looking at alive?
active[#active + 1] = char -- add that character to the 'active list'
end
end
end
local length = arms.Size.y
local bounding = character.PrimaryPart
local ray = Ray.new(bounding.Position, bounding.CFrame.lookVector * (length + 1)) -- Fire a ray from our character's torso, towards its lookvector with a length of our arm size
local hit, pos = game.Workspace:FindPartOnRayWithWhitelist(ray, active) -- find any parts we hit that's a part of a character's
if hit and hit.Parent then
local char = hit.Parent
local hum = hit.Parent:FindFirstChild 'Humanoid'
if hum then
hum:TakeDamage(99) -- Whatever we want to do to that other player's character
end
end
end
end
end
end)
Itâs not that itâs wonky, it just shoots a single ray from the centre of your character forwards at the length of your arms (+ 1 stud). If you want a longer hit box, change the â + 1â in the code to a value to make it longer.
If you want a wider hitbox, you would either need to:
Add more rays at X offsets in the width you desire OR change the raycasting entirely, and do it from points whilst the animation is playing, damaging any raycast hits along this path (debounced to previously hit objects)
OR (the better options imo)
Use magnitude on active players and determine whether theyâre in the direction you need and within FOV of the width of the hitbox OR do a region3 check in front of your character for a whitelist of the active players
Hey, just a question. I did this with a kick animation, and it works 100% well, but it sometimes breaks randomly, and only the client can see the animation. You can reply whenever you want, I can wait for you.
Local script:
local player = game.Players.LocalPlayer
local character
local animationFolder = game:GetService('ReplicatedStorage'):WaitForChild('AnimationFolder'):GetChildren() --> Change 'AnimationFolder' to a folder with all of your animations in it
local anims = { }
--> This will create an easy to access dict for our anims, for this, we need an animation in there called the name of the action, in this case:
--> 'Kick' will need to be in the animation folder
for i, v in next, animationFolder do
anims[v.Name] = v
end
local remotes = {
--> Create a table of remotes, so that you can access them at will for each command
Kick = game:GetService('ReplicatedStorage'):WaitForChild('Remotes'):WaitForChild('Kick');
--[?] To add another, you would write: otherAnimation = game:GetService('ReplicatedStorage'):WaitForChild('Remotes'):WaitForChild('otherRemote')';
}
local states = { --> This is used to keep track of what our player is currently doing
playing = nil; --> This determines whether or not we're performing an action currently so that we can't perform another
last = {
--> This is where we'll store the last performed action times
};
}
local cooldowns = { --[!] You could put this in a module for both the server + the client to access
Kick = 3; --> How long you should wait before someone can perform another Kick (in seconds)
}
local actions = { --[!] This could also be put in a module as above
Kick = Enum.KeyCode.E; --> What input are we looking for when we want to perform this action
}
local function isAlive()
local char = character or player.Character
if not char or not char:IsDescendantOf(game.Workspace) or not char:FindFirstChild "Humanoid" or char.Humanoid:GetState() == Enum.HumanoidStateType.Dead then
return
end
return char
end
local function checkCooldown(class)
if cooldowns[class] then
if (time() - (states.last[class] or 0)) > cooldowns[class] then
return true
end
end
return false
end
local function performAction(class, arge)
local anim = anims[class]
local remote = remotes[class]
if anim and remote then
local track = character.Humanoid:LoadAnimation(anim)
track.Priority = Enum.AnimationPriority.Action
track:GetMarkerReachedSignal("ActionFrame"):Connect(function ()
remote:FireServer(arge)
--> Update our states so we can perform other actions again + add our last time to the list
states.last[class] = time()
states.playing = false
end)
track:Play()
end
end
game:GetService("UserInputService").InputBegan:Connect(function(Input, gameProcessed)
character = isAlive()
if not character or gameProcessed then
return
end
if Input.UserInputType == Enum.UserInputType.Keyboard then
if Input.KeyCode == actions.Kick then
if not states.playing and checkCooldown('Kick') then
states.playing = true
performAction('Kick', 'Right') -- Change 'Right' to whatever side of the arm the uppercut is happening on
end
end
end
end)
Serverscript:
local remotes = {
Kick = game:GetService('ReplicatedStorage'):WaitForChild('Remotes'):WaitForChild('Kick');
}
local function isAlive(player)
local char = player.Character
if not char or not char:IsDescendantOf(game.Workspace) or not char:FindFirstChild "Humanoid" or char.Humanoid:GetState() == Enum.HumanoidStateType.Dead then
return
end
return char
end
local function getLegs(character, side) -- gets all the arm parts of the side the player wants to damage from so we can check length
-- Will only work for R15
local leg = character:FindFirstChild(side .. ' Leg')
if leg then
return leg
end
end
remotes.Kick.OnServerEvent:connect(function (player, Leg)
if player and Leg and typeof(Leg) == 'string' and Leg == 'Left' or Leg == 'Right' then
-- some typechecking, highly recommend you also add a check to limit the amount of times a player can fire
-- based on the cooldown of each attack etc to stop exploiting
local character = isAlive(player)
if character then
local arms = getLegs(character, Leg)
if arms then
local active = { } -- a table we've defined as active, which we'll store a bunch of characters within
for i, v in next, game.Players:GetPlayers() do -- get all players in the game, and loop through them
if v and v ~= player then -- if the player we're looking at in the list of the players in the game is NOT the player who sent this remote then
local char = isAlive(v)
if char then -- is this player that we're looking at alive?
active[#active + 1] = char -- add that character to the 'active list'
end
end
end
local length = arms.Size.y
local bounding = character.PrimaryPart
local ray = Ray.new(bounding.Position, bounding.CFrame.lookVector * (length + 3)) -- Fire a ray from our character's torso, towards its lookvector with a length of our arm size
local hit, pos = game.Workspace:FindPartOnRayWithWhitelist(ray, active) -- find any parts we hit that's a part of a character's
if hit and hit.Parent then
local char = hit.Parent
local hum = hit.Parent:FindFirstChild 'Humanoid'
if hum then
hum:TakeDamage(20) -- Whatever we want to do to that other player's character
game.Workspace.BOOM:Play()
local vel = Instance.new 'BodyVelocity'
vel.MaxForce = Vector3.new(0, 5000, 0)
vel.Velocity = Vector3.new(0, 50, 0)
vel.Parent = char.PrimaryPart
delay(0.2, function()
vel:Destroy()
end)
end
end
end
end
end
end)