this is the first time i made an ability and i was wandering if i did it good and if you have any suggestions (im also gonna add a hit detection for damege)
local script:
local UserInputService = game:GetService("UserInputService")
local repStorage = game:GetService("ReplicatedStorage")
local player = game:GetService("Players").LocalPlayer
local Animation = script.Animation
local remotesFile = repStorage:WaitForChild("Remots")
local SummonRemote = remotesFile:WaitForChild("FireBall"):WaitForChild("Summon")
local ThrowRemote = remotesFile:WaitForChild("FireBall"):WaitForChild("Throw")
local debounce = false
local function OnInputBegan(input)
if input.KeyCode == Enum.KeyCode.E and debounce == false then
debounce = true
local char = player.Character or player.CharacterAdded:Wait()
local humanoid = char:WaitForChild("Humanoid")
local anim = humanoid:LoadAnimation(Animation)
anim:Play()
local Summoned
anim:GetMarkerReachedSignal("Summon"):Connect(function()
Summoned = SummonRemote:InvokeServer(char)
end)
local Threw
anim:GetMarkerReachedSignal("Throw"):Connect(function()
Threw = ThrowRemote:InvokeServer()
end)
task.wait(3)
debounce = false
end
end
UserInputService.InputBegan:Connect(OnInputBegan)
server script:
local RunService = game:GetService("RunService")
local repStorage = game:GetService("ReplicatedStorage")
local Debris = game:GetService("Debris")
local RemotesFile = repStorage:WaitForChild("Remots")
local FireBallRemotes =
{
Summon = RemotesFile:WaitForChild("FireBall"):WaitForChild("Summon"),
Throw = RemotesFile:WaitForChild("FireBall"):WaitForChild("Throw")
}
local Debounce =
{
FireBall = {}
}
local DebounceTime =
{
FireBall = 3
}
FireBallRemotes.Summon.OnServerInvoke = function(player, char)
if not table.find(Debounce.FireBall, player.UserId) then
table.insert(Debounce.FireBall, player.UserId)
local hrp = char:WaitForChild("HumanoidRootPart")
local FireBall = repStorage:WaitForChild("Magics"):WaitForChild("FireBall"):Clone()
FireBall.Parent = workspace.Ignore
local Hand = char.RightHand
FireBall.CFrame = Hand.RightGripAttachment.WorldCFrame
local weld = Instance.new("WeldConstraint", FireBall)
weld.Part0 = Hand
weld.Part1 = FireBall
FireBallRemotes.Throw.OnServerInvoke = function()
Debris:AddItem(weld,0)
FireBall.Anchored = true
local direction = hrp.CFrame.LookVector
local StartTime = os.clock()
local connection
connection = RunService.Heartbeat:Connect(function(dt)
if os.clock() >= StartTime + 2.5 then
connection:Disconnect()
Debris:AddItem(FireBall,0)
end
FireBall.CFrame = CFrame.new(FireBall.Position) + direction * dt * 60
end)
return true
end
task.wait(DebounceTime.FireBall)
for i,v in Debounce.FireBall do
if v == player.UserId then
table.remove(Debounce.FireBall, i)
end
end
return true
end
return false
end
It does seem pretty good to me, it’s not messy, and it has mainly goos practices, it’s a bit advanced for me but I would say you don’t have anything wrong (I think)
Way better than when I first wrote my first ability, but if you wanna go an extra mile read this:
To improve the organization and scalability of the code, you can apply object-oriented programming (OOP) concepts such as classes. Here’s a refactored code that shows how the code might look with OOP applied. However, please note that this code was written in a hurry and has never been tested. If you want to use this code, you should delete the RemoteFunction and replace it with a RemoteEvent called “Fireball”. If you want to add new projectiles beside Fireball, I suggest to create a Projectile class and inherit all future projectiles from it. This will allow for easier extension without modifying the code.
What OOP? Why should I use it?
Better readibility
Can add more stuff later, without having to spend more time
local script
Warning! The code below does not follow the best practices (like SOLID principles)
local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local FireballThrower = {} --name sucks but it's fine
FireballThrower.__index = FireballThrower
function FireballThrower.new()
local self = setmetatable({}, FireballThrower)
self.Animation = script.Animation
self.Remotes = ReplicatedStorage.Remots.Fireball
return self
end
function FireballThrower:Cast()
if self.Debounce then
return
end
self.Remotes.FireballThrower:Fire("Create")
self.Debounce = true
local character = Players.LocalPlayer.Character or Players.LocalPlayer.CharacterAdded:Wait()
local humanoid = character:WaitForChild("Humanoid")
local animation = humanoid:LoadAnimation(self.Animation)
animation:Play()
animation:GetMarkerReachedSignal("Summon"):Once(function()
self.Remotes.Fireball:Fire("Summon")
end)
animation:GetMarkerReachedSignal("Throw"):Once(function()
self.Remotes.Fireball:Fire("Throw")
end)
task.wait(3)
self.Debounce = false
end
local FireBall = FireballThrower.new()
UserInputService.InputBegan:Connect(function(input)
if input.KeyCode == Enum.KeyCode.E then
FireBall:Cast()
end
end)
server script
Warning! The code below does not follow the best practices (like SOLID principles)
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RemotesFile = ReplicatedStorage:WaitForChild("Remots")
local PlayerFireballs = {}
local Fireball = {}
Fireball.__index = Fireball
function Fireball.new(Player)
local self = setmetatable({}, Fireball)
self.Player = Player
self.Debounce = false
self.FireballModel = ReplicatedStorage.Magics.Fireball
self.CurrentFireball = nil
self.Weld = nil
self.Character = nil
return self
end
function Fireball:Summon(Player: Player, Character: Model)
if self.Debounce then return end
self.Debounce = true
self.Character = Character
self.CurrentFireball = self.FireballModel:Clone()
self.Weld = Instance.new("WeldConstraint", self.CurrentFireball)
self.Weld.Part0 = Character.RightHand
self.Weld.Part1 = self.CurrentFireball
end
function Fireball:Throw()
self.Weld:Destroy()
self.CurrentFireball.Anchored = true
local Direction = self.Character.HumanoidRootPart.CFrame.LookVector
local StartTime = os.clock()
local Connection
Connection = RunService.Heartbeat:Connect(function(DeltaTime)
if os.clock() >= StartTime + 2.5 then
Connection:Disconnect()
self.CurrentFireball:Destroy()
end
self.CurrentFireball.CFrame = CFrame.new(self.CurrentFireball.Position) + Direction * DeltaTime * 60
end)
self:Destroy()
end
function Fireball:Destroy()
table.clear(self)
PlayerFireballs[Player.UserId] = nil
end
RemotesFile.Fireball:OnServerEvent(function(Player: Player, Action: string) --code is kinda meh below
if not Player then return end --ensure nonsensical value isn't passed.
if Action == "Create" then
if PlayerFireballs[Player.UserId] then return end
PlayerFireballs[Player.UserId] = Fireball.new(Player)
end
if not PlayerFireballs[Player.UserId] then return end
if Action == "Summon" then
PlayerFireballs[Player.UserId]:Summon(Player, Player.Character or Player.CharacterAdded:Wait())
elseif Action == "Throw" then
PlayerFireballs[Player.UserId]:Throw()
end
end)