As far as functionality goes you did a pretty good job making this ability especially considering you’re new to it , although there are a couple things I’ll suggest at the end for making the code cleaner.
But first, both of the problems you mentioned come from the Script
:
The reason you freeze midair is because you are anchoring the character’s HumanoidRootPart
when the ability is activated: hrp.Anchored = true
. I’m unsure if you want the character to be anchored to some extent while casting the ability, but if not you can just remove this line and other the line hrp.Anchored = false
to stop the ability from freezing you in place.
If you do want the character to be anchored for the duration of the explosion effect (which is 2 seconds according to the wait(2)
), the problem with your current code is that the line to unanchor the character is inside of the .Touched
event, meaning the character will only be unfrozen if the explosion hits something (also the explosion effect ever being deleted also depends on if it hits something or not, for the same reason). To fix this, just move the last 3 lines in the .Touched
event to be outside:
The reason the explosion is 1-shotting the dummies is because the .Touched
event is firing for every part in the model. For an R6 model like the dummies, there are 7 body parts meaning the damage is applied 7 times for a total of 350 damage which is why its getting 1 shot. To make sure the damage is only applied once per humanoid you need to keep track of which have already been hit and only apply damage if the current touched part is not in the list:
In this code, hitList
is a dictionary for recording the characters that have been damaged. If a character has been damaged then the character model instance is used as a key for the dictionary with the entry set to true
(hitList[hitChar] == true
), otherwise the entry for the key is nil
(hitList[hitChar] == nil
) which is a falsy value. This way whether or not the hit character has already been damaged can be determined just by checking their entry in the hitList
.
Edited code
local remote = script.Parent.Parent.RemoteEvent
local replicated = game:GetService("ReplicatedStorage")
remote.OnServerEvent:Connect(function(char, player, tool)
print("Server Recieved Call From Client . . . Initiating")
local explosion = replicated.FX.template:Clone()
local hrp = player:WaitForChild("HumanoidRootPart")
hrp.Anchored = true
explosion.Parent = workspace
explosion.CFrame = CFrame.new((hrp.CFrame*CFrame.new(0, 0, 0)).Position)
local hitList = {}
explosion.Touched:Connect(function(hit)
if hit.Parent:FindFirstChild("Humanoid") then
local hitChar = hit.Parent --the character associated with the hit part
if hitChar == char or hitList[hitChar] then return end --dont damage if hitChar is you or if hitChar has already been hit
hitList[hitChar] = true --record that hitChar has been hit
hitChar.HumanoidRootPart.CFrame = CFrame.lookAt(hitChar.HumanoidRootPart.Position, explosion.Position) * CFrame.Angles(0, math.pi, 0)
local Damage = 50 -- Damage Amount
hitChar.Humanoid:TakeDamage(Damage)
end
end)
wait(2)
hrp.Anchored = false
explosion:Destroy()
end)
There’s another problem I noticed in the Local Script
One thing you left out is the gameProcessedEvent
parameter that the InputBegan
and InputEnded
events provide, which tells you whether or not the input was actually used for the game. One common use of this is determining whether the player is typing in chat (gameProcessedEvent == true
) or not (gameProcessedEvent == false
). As you have it now, typing the letter “e” (or “E”) into chat will fire the ability, which is probably not something you want. The fix is simply to check if the input is processed or not:
And that’s all for fixing the bugs. For general practices you can use to make your code cleaner, first I suggest keeping the indents of the scopes consistent. This means all the code inside something like an if statement or a function definition should be spaced one indent past the header:
Good:
if this then
that()
end
Not good:
if this then
that()
end
Also not good:
if this then
that()
end
Also I noticed in your original code that your end
s were kinda scattered (I fixed them in the edited code). They should always be lined up with the header that they are ending:
Good:
if this then
that()
end
Not good:
if this then
that()
end
These are small details that don’t affect the functionality of the code but as things get more and more complex and you start nesting lots of if statements and other things it’s really important for the sake of readability that everything is lined up properly.
Lastly I suggest sticking to one naming convention inside of scripts. This is probably the least important for readability but it is good practice. In the context of programming, the naming convention refers to how the names of things like variables and functions are formatted. The 3 main ones are:
- Snakecase, where words are all lowercase and separated by underscores. Example:
humanoid_root_part
- Pascalcase, where all words are capitalized. Example:
HumanoidRootPart
.
- Camelcase, where the first word is lowercase and all others are capitalized. Example:
humanoidRootPart
.
Personally for Roblox I use Camelcase for naming things in scripts and Pascalcase when naming instances in the workspace (like parts), but you can choose whatever you like as long as it’s consistent.
Hope this helps