I have been struggling to add a cooldown to the dash system found in the new platformer starter place. Any help?
2 Likes
Could you post the code for it here?
3 Likes
Controller script Module:
--[[
Controller - This module script implements the character controller class. This class
handles character movement, momentum, and performing actions, as well as utility functions
to read the humanoid's state.
--]]
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Constants = require(ReplicatedStorage.Platformer.Constants)
local loadModules = require(ReplicatedStorage.Utility.loadModules)
local disconnectAndClear = require(ReplicatedStorage.Utility.disconnectAndClear)
local Actions = loadModules(script.Parent.Actions)
local remotes = ReplicatedStorage.Platformer.Remotes
local setActionRemote = remotes.SetAction
local Controller = {}
Controller.__index = Controller
function Controller.new(character: Model)
-- Characters are not replicated atomically so we need to wait for children to replicate
local humanoid = character:WaitForChild("Humanoid")
local root = character:WaitForChild("HumanoidRootPart")
local self = {
character = character,
humanoid = humanoid,
root = root,
inputDirection = Vector3.zero,
moveDirection = Vector3.zero,
connections = {},
actionChanged = character:GetAttributeChangedSignal(Constants.ACTION_ATTRIBUTE),
}
setmetatable(self, Controller)
return self
end
function Controller:setInputDirection(inputDirection: Vector3)
if inputDirection.Magnitude > 1 then
inputDirection = inputDirection.Unit
end
self.inputDirection = inputDirection
end
function Controller:isSwimming(): boolean
local humanoidState = self.humanoid:GetState()
return humanoidState == Enum.HumanoidStateType.Swimming
end
function Controller:isClimbing(): boolean
local humanoidState = self.humanoid:GetState()
return humanoidState == Enum.HumanoidStateType.Climbing
end
function Controller:isGrounded(): boolean
return self.humanoid.FloorMaterial ~= Enum.Material.Air and self.humanoid.FloorMaterial == Enum.Material.SmoothPlastic
end
function Controller:getAction(): string
return self.character:GetAttribute(Constants.ACTION_ATTRIBUTE) or "None"
end
function Controller:setAction(action: string)
local lastTimeAttribute = string.format(Constants.LAST_TIME_FORMAT_STRING, action)
self.character:SetAttribute(lastTimeAttribute, os.clock())
self.character:SetAttribute(Constants.ACTION_ATTRIBUTE, action)
setActionRemote:FireServer(action)
end
function Controller:getTimeSinceAction(action: string): number
local lastTimeAttribute = string.format(Constants.LAST_TIME_FORMAT_STRING, action)
local lastTime = self.character:GetAttribute(lastTimeAttribute) or 0
return os.clock() - lastTime
end
function Controller:getTimeSinceGrounded(): number
local lastGroundedTime = self.character:GetAttribute(Constants.LAST_GROUNDED_ATTRIBUTE) or 0
return os.clock() - lastGroundedTime
end
function Controller:performAction(action: string, ...)
local actionModule = Actions[action]
if not actionModule then
warn(`Invalid action: {action}`)
return
end
actionModule.perform(self, ...)
end
function Controller:getAcceleration(): number
-- Check if the current action has a set acceleration to use
local action = self:getAction()
local actionModule = Actions[action]
if actionModule and actionModule.movementAcceleration then
return actionModule.movementAcceleration
end
if self:isClimbing() then
return Constants.LADDER_ACCELERATION
elseif self:isSwimming() then
return Constants.WATER_ACCELERATION
elseif not self:isGrounded() then
return Constants.AIR_ACCELERATION
end
return Constants.GROUND_ACCELERATION
end
function Controller:update(deltaTime: number)
local isGrounded = self:isGrounded()
-- Allow dashing and double jumping again once the character is grounded/climbing/swimming.
-- Additionally, check if the current action needs to be cleared
if isGrounded or self:isClimbing() or self:isSwimming() then
self:tryClearGroundedAction()
self.character:SetAttribute(Constants.CAN_DOUBLE_JUMP_ATTRIBUTE, true)
self.character:SetAttribute(Constants.CAN_DASH_ATTRIBUTE, true)
end
if isGrounded then
self.character:SetAttribute(Constants.LAST_GROUNDED_ATTRIBUTE, os.clock())
end
-- Lerp moveDirection to inputDirection at a constant rate
if self.moveDirection ~= self.inputDirection then
-- Get the character's current acceleration and update moveDirection toward inputDirection
local acceleration = self:getAcceleration()
local offset = self.inputDirection - self.moveDirection
local maxChange = acceleration * deltaTime
-- Make sure we don't overshoot the target
if offset.Magnitude <= maxChange then
self.moveDirection = self.inputDirection
else
self.moveDirection += offset.Unit * maxChange
end
end
-- Move the character
self.humanoid:Move(self.moveDirection)
end
function Controller:tryClearGroundedAction()
local action = self:getAction()
local actionModule = Actions[action]
if actionModule and actionModule.clearOnGrounded then
local minTimeInAction = actionModule.minTimeInAction or 0
local timeSinceAction = self:getTimeSinceAction(action)
if timeSinceAction >= minTimeInAction then
self:setAction("None")
end
end
end
function Controller:destroy()
disconnectAndClear(self.connections)
end
return Controller
1 Like
Try this new code to see if it fixes it:
This will add constants.DASH_CoolDOWN = 2 which will add a 2 second cooldown time you can change the value to needed.
-- Constants module
Constants.DASH_COOLDOWN = 2 -- Cooldown time in seconds
-- Controller module
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Constants = require(ReplicatedStorage.Platformer.Constants)
local loadModules = require(ReplicatedStorage.Utility.loadModules)
local disconnectAndClear = require(ReplicatedStorage.Utility.disconnectAndClear)
local Actions = loadModules(script.Parent.Actions)
local remotes = ReplicatedStorage.Platformer.Remotes
local setActionRemote = remotes.SetAction
local Controller = {}
Controller.__index = Controller
function Controller.new(character: Model)
-- Characters are not replicated atomically so we need to wait for children to replicate
local humanoid = character:WaitForChild("Humanoid")
local root = character:WaitForChild("HumanoidRootPart")
local self = {
character = character,
humanoid = humanoid,
root = root,
inputDirection = Vector3.zero,
moveDirection = Vector3.zero,
connections = {},
actionChanged = character:GetAttributeChangedSignal(Constants.ACTION_ATTRIBUTE),
}
setmetatable(self, Controller)
-- Initialize LastDashTime attribute
character:SetAttribute("LastDashTime", 0)
return self
end
function Controller:setInputDirection(inputDirection: Vector3)
if inputDirection.Magnitude > 1 then
inputDirection = inputDirection.Unit
end
self.inputDirection = inputDirection
end
function Controller:isSwimming(): boolean
local humanoidState = self.humanoid:GetState()
return humanoidState == Enum.HumanoidStateType.Swimming
end
function Controller:isClimbing(): boolean
local humanoidState = self.humanoid:GetState()
return humanoidState == Enum.HumanoidStateType.Climbing
end
function Controller:isGrounded(): boolean
return self.humanoid.FloorMaterial ~= Enum.Material.Air and self.humanoid.FloorMaterial == Enum.Material.SmoothPlastic
end
function Controller:getAction(): string
return self.character:GetAttribute(Constants.ACTION_ATTRIBUTE) or "None"
end
function Controller:setAction(action: string)
local lastTimeAttribute = string.format(Constants.LAST_TIME_FORMAT_STRING, action)
self.character:SetAttribute(lastTimeAttribute, os.clock())
self.character:SetAttribute(Constants.ACTION_ATTRIBUTE, action)
setActionRemote:FireServer(action)
end
function Controller:getTimeSinceAction(action: string): number
local lastTimeAttribute = string.format(Constants.LAST_TIME_FORMAT_STRING, action)
local lastTime = self.character:GetAttribute(lastTimeAttribute) or 0
return os.clock() - lastTime
end
function Controller:getTimeSinceGrounded(): number
local lastGroundedTime = self.character:GetAttribute(Constants.LAST_GROUNDED_ATTRIBUTE) or 0
return os.clock() - lastGroundedTime
end
function Controller:performAction(action: string, ...)
local actionModule = Actions[action]
if not actionModule then
warn(`Invalid action: {action}`)
return
end
actionModule.perform(self, ...)
end
function Controller:getAcceleration(): number
-- Check if the current action has a set acceleration to use
local action = self:getAction()
local actionModule = Actions[action]
if actionModule and actionModule.movementAcceleration then
return actionModule.movementAcceleration
end
if self:isClimbing() then
return Constants.LADDER_ACCELERATION
elseif self:isSwimming() then
return Constants.WATER_ACCELERATION
elseif not self:isGrounded() then
return Constants.AIR_ACCELERATION
end
return Constants.GROUND_ACCELERATION
end
function Controller:update(deltaTime: number)
local isGrounded = self:isGrounded()
-- Allow dashing and double jumping again once the character is grounded/climbing/swimming.
-- Additionally, check if the current action needs to be cleared
if isGrounded or self:isClimbing() or self:isSwimming() then
self:tryClearGroundedAction()
self.character:SetAttribute(Constants.CAN_DOUBLE_JUMP_ATTRIBUTE, true)
self.character:SetAttribute(Constants.CAN_DASH_ATTRIBUTE, true)
end
if isGrounded then
self.character:SetAttribute(Constants.LAST_GROUNDED_ATTRIBUTE, os.clock())
end
-- Lerp moveDirection to inputDirection at a constant rate
if self.moveDirection ~= self.inputDirection then
-- Get the character's current acceleration and update moveDirection toward inputDirection
local acceleration = self:getAcceleration()
local offset = self.inputDirection - self.moveDirection
local maxChange = acceleration * deltaTime
-- Make sure we don't overshoot the target
if offset.Magnitude <= maxChange then
self.moveDirection = self.inputDirection
else
self.moveDirection += offset.Unit * maxChange
end
end
-- Move the character
self.humanoid:Move(self.moveDirection)
end
function Controller:tryClearGroundedAction()
local action = self:getAction()
local actionModule = Actions[action]
if actionModule and actionModule.clearOnGrounded then
local minTimeInAction = actionModule.minTimeInAction or 0
local timeSinceAction = self:getTimeSinceAction(action)
if timeSinceAction >= minTimeInAction then
self:setAction("None")
end
end
end
function Controller:dash()
local currentTime = os.clock()
local lastDashTime = self.character:GetAttribute("LastDashTime")
if currentTime - lastDashTime < Constants.DASH_COOLDOWN then
warn("Dash is on cooldown!")
return
end
-- Perform the dash
self.character:SetAttribute("LastDashTime", currentTime)
-- Add your existing dash code here
end
function Controller:handleInput(input)
if input == "Dash" then
self:dash()
end
-- Existing input handling code...
end
function Controller:destroy()
disconnectAndClear(self.connections)
end
return Controller
2 Likes
It still isn’t working for some reason. The player is still able to just repeatedly spam dash.
You can try adding a wait time to the script
try this new code:
-- Constants module
Constants.DASH_COOLDOWN = 2 -- Cooldown time in seconds
-- Controller module
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Constants = require(ReplicatedStorage.Platformer.Constants)
local loadModules = require(ReplicatedStorage.Utility.loadModules)
local disconnectAndClear = require(ReplicatedStorage.Utility.disconnectAndClear)
local Actions = loadModules(script.Parent.Actions)
local remotes = ReplicatedStorage.Platformer.Remotes
local setActionRemote = remotes.SetAction
local Controller = {}
Controller.__index = Controller
function Controller.new(character: Model)
-- Characters are not replicated atomically so we need to wait for children to replicate
local humanoid = character:WaitForChild("Humanoid")
local root = character:WaitForChild("HumanoidRootPart")
local self = {
character = character,
humanoid = humanoid,
root = root,
inputDirection = Vector3.zero,
moveDirection = Vector3.zero,
connections = {},
actionChanged = character:GetAttributeChangedSignal(Constants.ACTION_ATTRIBUTE),
}
setmetatable(self, Controller)
-- Initialize LastDashTime attribute
character:SetAttribute("LastDashTime", 0)
return self
end
function Controller:setInputDirection(inputDirection: Vector3)
if inputDirection.Magnitude > 1 then
inputDirection = inputDirection.Unit
end
self.inputDirection = inputDirection
end
function Controller:isSwimming(): boolean
local humanoidState = self.humanoid:GetState()
return humanoidState == Enum.HumanoidStateType.Swimming
end
function Controller:isClimbing(): boolean
local humanoidState = self.humanoid:GetState()
return humanoidState == Enum.HumanoidStateType.Climbing
end
function Controller:isGrounded(): boolean
return self.humanoid.FloorMaterial ~= Enum.Material.Air and self.humanoid.FloorMaterial == Enum.Material.SmoothPlastic
end
function Controller:getAction(): string
return self.character:GetAttribute(Constants.ACTION_ATTRIBUTE) or "None"
end
function Controller:setAction(action: string)
local lastTimeAttribute = string.format(Constants.LAST_TIME_FORMAT_STRING, action)
self.character:SetAttribute(lastTimeAttribute, os.clock())
self.character:SetAttribute(Constants.ACTION_ATTRIBUTE, action)
setActionRemote:FireServer(action)
end
function Controller:getTimeSinceAction(action: string): number
local lastTimeAttribute = string.format(Constants.LAST_TIME_FORMAT_STRING, action)
local lastTime = self.character:GetAttribute(lastTimeAttribute) or 0
return os.clock() - lastTime
end
function Controller:getTimeSinceGrounded(): number
local lastGroundedTime = self.character:GetAttribute(Constants.LAST_GROUNDED_ATTRIBUTE) or 0
return os.clock() - lastGroundedTime
end
function Controller:canDash(): boolean
local currentTime = os.clock()
local lastDashTime = self.character:GetAttribute("LastDashTime")
return (currentTime - lastDashTime) >= Constants.DASH_COOLDOWN
end
function Controller:dash()
local currentTime = os.clock()
local lastDashTime = self.character:GetAttribute("LastDashTime")
if currentTime - lastDashTime < Constants.DASH_COOLDOWN then
warn("Dash is on cooldown!")
return
end
-- Perform the dash
self.character:SetAttribute("LastDashTime", currentTime)
-- Add your existing dash code here
end
function Controller:handleInput(input)
if input == "Dash" then
self:dash()
end
-- Existing input handling code...
end
function Controller:getAcceleration(): number
-- Check if the current action has a set acceleration to use
local action = self:getAction()
local actionModule = Actions[action]
if actionModule and actionModule.movementAcceleration then
return actionModule.movementAcceleration
end
if self:isClimbing() then
return Constants.LADDER_ACCELERATION
elseif self:isSwimming() then
return Constants.WATER_ACCELERATION
elseif not self:isGrounded() then
return Constants.AIR_ACCELERATION
end
return Constants.GROUND_ACCELERATION
end
function Controller:update(deltaTime: number)
local isGrounded = self:isGrounded()
-- Allow dashing and double jumping again once the character is grounded/climbing/swimming.
-- Additionally, check if the current action needs to be cleared
if isGrounded or self:isClimbing() or self:isSwimming() then
self:tryClearGroundedAction()
self.character:SetAttribute(Constants.CAN_DOUBLE_JUMP_ATTRIBUTE, true)
self.character:SetAttribute(Constants.CAN_DASH_ATTRIBUTE, true)
end
if isGrounded then
self.character:SetAttribute(Constants.LAST_GROUNDED_ATTRIBUTE, os.clock())
end
-- Lerp moveDirection to inputDirection at a constant rate
if self.moveDirection ~= self.inputDirection then
-- Get the character's current acceleration and update moveDirection toward inputDirection
local acceleration = self:getAcceleration()
local offset = self.inputDirection - self.moveDirection
local maxChange = acceleration * deltaTime
-- Make sure we don't overshoot the target
if offset.Magnitude <= maxChange then
self.moveDirection = self.inputDirection
else
self.moveDirection += offset.Unit * maxChange
end
end
-- Move the character
self.humanoid:Move(self.moveDirection)
end
function Controller:tryClearGroundedAction()
local action = self:getAction()
local actionModule = Actions[action]
if actionModule and actionModule.clearOnGrounded then
local minTimeInAction = actionModule.minTimeInAction or 0
local timeSinceAction = self:getTimeSinceAction(action)
if timeSinceAction >= minTimeInAction then
self:setAction("None")
end
end
end
function Controller:destroy()
disconnectAndClear(self.connections)
end
return Controller
1 Like
The performAction method need to be defined in the script.
Example:
local myTable = {}
function myTable:performAction()
print("Action performed!")
end
This should fix that error:
-- Controller module
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Constants = require(ReplicatedStorage.Platformer.Constants)
local loadModules = require(ReplicatedStorage.Utility.loadModules)
local disconnectAndClear = require(ReplicatedStorage.Utility.disconnectAndClear)
local Actions = loadModules(script.Parent.Actions)
local remotes = ReplicatedStorage.Platformer.Remotes
local setActionRemote = remotes.SetAction
local Controller = {}
Controller.__index = Controller
function Controller.new(character: Model)
-- Characters are not replicated atomically so we need to wait for children to replicate
local humanoid = character:WaitForChild("Humanoid")
local root = character:WaitForChild("HumanoidRootPart")
local self = {
character = character,
humanoid = humanoid,
root = root,
inputDirection = Vector3.zero,
moveDirection = Vector3.zero,
connections = {},
actionChanged = character:GetAttributeChangedSignal(Constants.ACTION_ATTRIBUTE),
}
setmetatable(self, Controller)
-- Initialize LastDashTime attribute
character:SetAttribute("LastDashTime", 0)
return self
end
function Controller:setInputDirection(inputDirection: Vector3)
if inputDirection.Magnitude > 1 then
inputDirection = inputDirection.Unit
end
self.inputDirection = inputDirection
end
function Controller:performAction()
print("Action performed!")
-- Add your action logic here
end
function Controller:isSwimming(): boolean
local humanoidState = self.humanoid:GetState()
return humanoidState == Enum.HumanoidStateType.Swimming
end
function Controller:isClimbing(): boolean
local humanoidState = self.humanoid:GetState()
return humanoidState == Enum.HumanoidStateType.Climbing
end
function Controller:isGrounded(): boolean
return self.humanoid.FloorMaterial ~= Enum.Material.Air and self.humanoid.FloorMaterial == Enum.Material.SmoothPlastic
end
function Controller:getAction(): string
return self.character:GetAttribute(Constants.ACTION_ATTRIBUTE) or "None"
end
function Controller:setAction(action: string)
local lastTimeAttribute = string.format(Constants.LAST_TIME_FORMAT_STRING, action)
self.character:SetAttribute(lastTimeAttribute, os.clock())
self.character:SetAttribute(Constants.ACTION_ATTRIBUTE, action)
setActionRemote:FireServer(action)
end
function Controller:getTimeSinceAction(action: string): number
local lastTimeAttribute = string.format(Constants.LAST_TIME_FORMAT_STRING, action)
local lastTime = self.character:GetAttribute(lastTimeAttribute) or 0
return os.clock() - lastTime
end
function Controller:getTimeSinceGrounded(): number
local lastGroundedTime = self.character:GetAttribute(Constants.LAST_GROUNDED_ATTRIBUTE) or 0
return os.clock() - lastGroundedTime
end
function Controller:canDash(): boolean
local currentTime = os.clock()
local lastDashTime = self.character:GetAttribute("LastDashTime")
return (currentTime - lastDashTime) >= Constants.DASH_COOLDOWN
end
function Controller:dash()
local currentTime = os.clock()
local lastDashTime = self.character:GetAttribute("LastDashTime")
if currentTime - lastDashTime < Constants.DASH_COOLDOWN then
warn("Dash is on cooldown!")
return
end
-- Perform the dash
self.character:SetAttribute("LastDashTime", currentTime)
-- Add your existing dash code here
end
function Controller:handleInput(input)
if input == "Dash" then
self:dash()
end
-- Existing input handling code...
end
function Controller:getAcceleration(): number
-- Check if the current action has a set acceleration to use
local action = self:getAction()
local actionModule = Actions[action]
if actionModule and actionModule.movementAcceleration then
return actionModule.movementAcceleration
end
if self:isClimbing() then
return Constants.LADDER_ACCELERATION
elseif self:isSwimming() then
return Constants.WATER_ACCELERATION
elseif not self:isGrounded() then
return Constants.AIR_ACCELERATION
end
return Constants.GROUND_ACCELERATION
end
function Controller:update(deltaTime: number)
local isGrounded = self:isGrounded()
-- Allow dashing and double jumping again once the character is grounded/climbing/swimming.
-- Additionally, check if the current action needs to be cleared
if isGrounded or self:isClimbing() or self:isSwimming() then
self:tryClearGroundedAction()
self.character:SetAttribute(Constants.CAN_DOUBLE_JUMP_ATTRIBUTE, true)
self.character:SetAttribute(Constants.CAN_DASH_ATTRIBUTE, true)
end
if isGrounded then
self.character:SetAttribute(Constants.LAST_GROUNDED_ATTRIBUTE, os.clock())
end
-- Lerp moveDirection to inputDirection at a constant rate
if self.moveDirection ~= self.inputDirection then
-- Get the character's current acceleration and update moveDirection toward inputDirection
local acceleration = self:getAcceleration()
local offset = self.inputDirection - self.moveDirection
local maxChange = acceleration * deltaTime
-- Make sure we don't overshoot the target
if offset.Magnitude <= maxChange then
self.moveDirection = self.inputDirection
else
self.moveDirection += offset.Unit * maxChange
end
end
-- Move the character
self.humanoid:Move(self.moveDirection)
end
function Controller:tryClearGroundedAction()
local action = self:getAction()
local actionModule = Actions[action]
if actionModule and actionModule.clearOnGrounded then
local minTimeInAction = actionModule.minTimeInAction or 0
local timeSinceAction = self:getTimeSinceAction(action)
if timeSinceAction >= minTimeInAction then
self:setAction("None")
end
end
end
function Controller:destroy()
disconnectAndClear(self.connections)
end
return Controller
1 Like
Thank you so much, with some slight tweaking I managed to get it to work.
This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.