How to edit platformer starter place?

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

Sorry for all the troubles

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.