(Dash) Linear Velocity and lookvector not applying

hey, trynna apply a dash velocity but the lookvector is not being applied. I cannot pivot the direction of the dash velocity…

External Media
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local camera = game.Workspace.CurrentCamera

local dashStrength = 50
local timeToDecrease = 2

local player = game.Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local humanoidRootPart = character:WaitForChild("HumanoidRootPart")

local isDashing = false
local dashDirection = Vector3.new()
local startTime

local movementKeys = {
	[Enum.KeyCode.W] = function() return camera.CFrame.LookVector end,
	[Enum.KeyCode.A] = function() return -camera.CFrame.RightVector end,
	[Enum.KeyCode.S] = function() return -camera.CFrame.LookVector end,
	[Enum.KeyCode.D] = function() return camera.CFrame.RightVector end
}
local currentKeys = {}

local function updateDirection()
	dashDirection = Vector3.new()
	for key, getDirection in pairs(movementKeys) do
		if currentKeys[key] then
			dashDirection = dashDirection + getDirection()
		end
	end
	if dashDirection.Magnitude > 0 then
		dashDirection = dashDirection.Unit
	end
end

local function onInputBegan(input, gameProcessed)
	if gameProcessed then return end
	currentKeys[input.KeyCode] = true

	if input.KeyCode == Enum.KeyCode.Q and not isDashing then
		updateDirection()
		if dashDirection.Magnitude > 0 then
			startTime = tick()
			isDashing = true
			humanoidRootPart.Velocity = dashDirection * dashStrength
		end
	end
end

local function onInputEnded(input, gameProcessed)
	if gameProcessed then return end
	currentKeys[input.KeyCode] = false
end

local function updateVelocity()
	if isDashing then
		local elapsedTime = tick() - startTime
		if elapsedTime < timeToDecrease then
			local newVelocity = (dashDirection * dashStrength):Lerp(Vector3.new(0, 0, 0), elapsedTime / timeToDecrease)
			humanoidRootPart.Velocity = newVelocity
		else
			humanoidRootPart.Velocity = Vector3.new(0, 0, 0)
			isDashing = false
		end
	end
end

UserInputService.InputBegan:Connect(onInputBegan)
UserInputService.InputEnded:Connect(onInputEnded)
RunService.RenderStepped:Connect(updateVelocity)

instead of getting the camera’s lookvector trying getting the humanoidrootparts look vector and for updating it use camera. My dash script is a bit more simple and uses bv which is deprecated but it might help:

coroutine.wrap(function()
			task.wait(.4)
			DashCompleted = true
		end)()

		local BV = Instance.new("BodyVelocity")
			BV.Name = "DashVelocity"
			BV.MaxForce = Vector3.new(80000,0,80000)
			BV.Velocity = char.HumanoidRootPart.CFrame.LookVector * 65
			BV.Parent = char.HumanoidRootPart

		repeat task.wait()
			char.HumanoidRootPart.CFrame = CFrame.new(char.HumanoidRootPart.Position, char.HumanoidRootPart.Position + workspace.CurrentCamera.CFrame.LookVector * Vector3.new(1,0,1))
			BV.Velocity = char.HumanoidRootPart.CFrame.LookVector * 65
		until DashCompleted == true

		task.delay(.1, function()
			BV:Destroy()
		end)

Edit: Just noticed what you were doing in your video. Are you asking why the character doesn’t turn mid-dash? If so, is that what you’re hoping to do?

I assumed it was because you weren’t dashing correctly in the direction you wanted to move, or is it the case that you want to be able to turn during a dash? Can you explain a little more what you’re hoping to do here?

If you want to be able to turn whilst dashing then:

Example with turning
--[!] SERVICES
local RunService = game:GetService('RunService')
local PlayerService = game:GetService('Players')
local UserInputService = game:GetService('UserInputService')
local ContextActionService = game:GetService('ContextActionService')


--[!] CONST
local EPSILON = 1e-6

local DASH_STRNGTH = 120      -- i.e. max target velocity
local DASH_TIME_LIM = 2.0     -- dash time length, in seconds
local DASH_DEBOUNCE = 1       -- how long, in seconds, between dashes (timed from end of last dash)
local DASH_TOUCH_BTN = true   -- whether we should a touch button to dash
local DASH_KEYBIND_CODES = {  -- the key(s) that can be used to dash
  Enum.KeyCode.Q,
  Enum.KeyCode.ButtonB,       -- i.e. B on XBOX, Circle on PlayStation
}


--[!] UTILS

--> used to check whether a player is alive
--    i.e. one that's alive and has both a humanoid + a root part
local function isAlive(character)
  if typeof(character) ~= 'Instance' or not character:IsA('Model') or not character:IsDescendantOf(workspace) then
    return false
  end

  local humanoid = character:FindFirstChildOfClass('Humanoid')
  local humanoidState = humanoid and humanoid:GetState() or Enum.HumanoidStateType.Dead
  local humanoidRootPart = humanoid and humanoid.RootPart or nil
  if humanoidState == Enum.HumanoidStateType.Dead or not humanoidRootPart then
    return false
  end

  return true
end

--> used to await a player's character
--   i.e. one that exists + is alive
local function tryGetCharacter(player)
  if typeof(player) ~= 'Instance' or not player:IsA('Player') then
    return nil
  end

  local character
  while not character do
    if not player or not player:IsDescendantOf(PlayerService) then
      break
    end

    local char = player.Character
    if not char then
      player.CharacterAdded:Wait()
      continue
    end

    if not isAlive(char) then
      RunService.Stepped:Wait()
      continue
    end

    character = char
  end

  return character
end

--[=[
  implements a critically damped harmonic oscillator
  which gradually changes a number towards a desired
  target number over time

  see reference(s) at...
    - Interpolation explained: https://www.gamedev.net/tutorials/programming/general-and-gameplay-programming/linear-interpolation-explained-r5892/
    - SmoothDamp reference: https://www.alexisbacot.com/blog/the-art-of-damping

  @param a Vector2 -- the current position
  @param b Vector2 -- the target position
  @param velocity Vector2 -- the current velocity of the system
  @param deltaTime number|optional -- time, in seconds, since the last call of this method; defaults to 1 / 60
  @param smoothTime number|optional -- the approximate time, in seconds, that we'll aim to reach the target within; defaults to 1e-5
  @param maxSpeed number|optional -- the maximum speed of the system; optional and defaults to INF
  @return Position<Vec2>, Velocity<Vec2> -- the former is the current position at time X, and the latter is the system's current velocity
]=]
local function smoothDamp(a, b, velocity, deltaTime, smoothTime, maxSpeed)
  deltaTime = deltaTime or 1 / 60
  maxSpeed = maxSpeed or math.huge
  smoothTime = math.max(1e-5, smoothTime or 0)

  local o = 2 / smoothTime
  local x = o * deltaTime

  local e = 1 + x + 0.48 * x * x + 0.235 * x * x * x
  e = 1 / e

  local ax, ay = a.X, a.Y
  local bx, by = b.X, b.Y
  local vx, vy = velocity.X, velocity.Y

  local dx = ax - bx
  local dy = ay - by

  local c = maxSpeed * smoothTime
  local c_2 = c * c
  local sqr = dx*dx + dy*dy
  if sqr > c_2 then
    local d = 1 / math.sqrt(sqr)
    dx = dx * d * c
    dy = dy * d * c
  end

  local rx = ax - dx
  local ry = ay - dy

  local tx = deltaTime * (vx + o * dx)
  local ty = deltaTime * (vy + o * dy)

  vx = e * (vx - o * tx)
  vy = e * (vy - o * ty)

  rx = rx + (dx + tx) * e
  ry = ry + (dy + ty) * e

  local bao = (bx - ax)*(rx - bx) + (by - ay)*(ry - by)
  if bao > 0 then
    rx = bx
    ry = by

    local dt = 1 / deltaTime
    vx = dt * (rx - bx)
    vy = dt * (ry - by)
  end

  return Vector2.new(rx, ry), Vector2.new(vx, vy)
end

--> used to cleanup any connections/instances
local function cleanupDisposables(disposables)
  for _, disposable in next, disposables do
    local t = typeof(disposable)
    if t == 'RBXScriptConnection' then
      pcall(disposable.Disconnect, disposable)
    elseif t == 'Instance' then
      pcall(disposable.Destroy, disposable)
    elseif t == 'function' then
      disposable()
    end
  end
  table.clear(disposables)
end

--> get the humanoid move direction
local function getMoveDirection(humanoid, camera, rootPart, defaultValue)
  -- check to see if the player is moving in any direction
  -- before allowing them to dash
  local moveVec = humanoid.MoveDirection
  local sqrMagnitude = moveVec.X*moveVec.X + moveVec.Y*moveVec.Y + moveVec.Z*moveVec.Z
  if sqrMagnitude <= EPSILON then
    return defaultValue
  end

  -- i.e. if we're shiftlocked / fps then get the
  --      move direction relative to the camera
  --
  -- otherwise, we can just use the Humanoid.MoveDirection
  --
  if UserInputService.MouseBehavior == Enum.MouseBehavior.LockCenter then
    moveVec = camera.CFrame:VectorToObjectSpace(rootPart.CFrame:VectorToWorldSpace(moveVec))
  end

  -- ensure we're using the unit vector
  -- can be important on mobile because it can be greater than 1
  -- in some cases from the touchpad controls
  if sqrMagnitude > 1 then
    moveVec = moveVec.Unit
  end

  return moveVec
end


--[!] MAIN
local function beginTracking(player)
  local character = tryGetCharacter(player)
  if not character then
    return
  end

  -- cleanup
  local disposables = { }

  -- state
  local isDashing = false
  local lastDashTime = nil

  -- set up
  local camera = game.Workspace.CurrentCamera
  local humanoid = character:FindFirstChildOfClass('Humanoid')
  local rootPart = humanoid.RootPart

  -- set up listener(s)
  local died
  died = humanoid.Died:Connect(function ()
    cleanupDisposables(disposables)
  end)
  table.insert(disposables, died)

  local function handleDash(_, inputState, inputObject)
    if not isAlive(character) or inputState ~= Enum.UserInputState.Begin then
      return Enum.ContextActionResult.Pass
    end

    -- make sure they're not on a cooldown / already dashing
    local now = os.clock()
    if isDashing or (lastDashTime and now - lastDashTime < DASH_DEBOUNCE) then
      return Enum.ContextActionResult.Pass
    end

    local moveVec = getMoveDirection(humanoid, camera, rootPart)
    if not moveVec then
      return Enum.ContextActionResult.Pass
    end

    isDashing = true
    lastDashTime = now

    -- create our dash instances
    local attachment = Instance.new('Attachment')
    attachment.Name = 'DashAttachment'
    attachment.Parent = rootPart
    disposables._attachment = attachment

    local velo = Instance.new('LinearVelocity')
    velo.MaxForce = math.huge
    velo.PlaneVelocity = Vector2.zero
    velo.RelativeTo = Enum.ActuatorRelativeTo.World
    velo.Attachment0 = attachment
    velo.VelocityConstraintMode = Enum.VelocityConstraintMode.Plane
    velo.PrimaryTangentAxis = Vector3.xAxis
    velo.SecondaryTangentAxis = Vector3.zAxis
    velo.Parent = rootPart
    disposables._velo = velo

    -- compute the desired velocity in the direction they want to move
    local desiredVelocity = DASH_STRNGTH * Vector2.new(moveVec.X, moveVec.Z)
    local currentVelocity = Vector2.zero

    -- interpolate the dash velocity
    -- and then stop dashing once we reach our DASH_TIME_LIM target
    local runtime
    runtime = RunService.Stepped:Connect(function (gt, dt)
      if not isAlive(character) then
        return
      end

      -- update dash velocity
      local elapsed = os.clock() - now
      if elapsed < DASH_TIME_LIM then
        -- compute the desired velocity
        moveVec = getMoveDirection(humanoid, camera, rootPart, moveVec)
        desiredVelocity = DASH_STRNGTH * Vector2.new(moveVec.X, moveVec.Z)

        -- update our velocity instance
        velo.PlaneVelocity, currentVelocity = smoothDamp(currentVelocity, desiredVelocity, currentVelocity, dt, DASH_TIME_LIM, DASH_STRNGTH)
        return
      end

      -- we've finished dashing so let's cleanup
      if attachment then
        if attachment == disposables._attachment then
          disposables._attachment = nil
        end
        pcall(attachment.Destroy, attachment)
      end

      if velo then
        if velo == disposables._velo then
          disposables._velo = nil
        end
        pcall(velo.Destroy, velo)
      end

      if runtime then
        if runtime == disposables._runtime then
          disposables._runtime = nil
        end

        if runtime.Connected then
          runtime:Disconnect()
        end
      end

      isDashing = false
      lastDashTime = os.clock()
    end)
    disposables._runtime = runtime

    return Enum.ContextActionResult.Sink
  end

  -- bind the dash keybindings +/- the dash touch button
  ContextActionService:BindAction('DashAction', handleDash, DASH_TOUCH_BTN, table.unpack(DASH_KEYBIND_CODES))

  table.insert(disposables, function ()
    pcall(ContextActionService.UnbindAction, ContextActionService, 'DashAction')
  end)
end


--[!] INIT
local player = PlayerService.LocalPlayer
if typeof(player.Character) ~= 'nil' then
  beginTracking(player)
end

player.CharacterAdded:Connect(function ()
  return beginTracking(player)
end)


Original:

I’ve added comments to explain what I’m doing:

Example Code without turning
--[!] SERVICES
local RunService = game:GetService('RunService')
local PlayerService = game:GetService('Players')
local UserInputService = game:GetService('UserInputService')
local ContextActionService = game:GetService('ContextActionService')


--[!] CONST
local EPSILON = 1e-6

local DASH_STRNGTH = 120      -- i.e. max target velocity
local DASH_TIME_LIM = 0.5     -- dash time length, in seconds
local DASH_DEBOUNCE = 1       -- how long, in seconds, between dashes (timed from end of last dash)
local DASH_TOUCH_BTN = true   -- whether we should a touch button to dash for touch screen/mobile users
local DASH_KEYBIND_CODES = {  -- the key(s) that can be used to dash
  Enum.KeyCode.Q,
  Enum.KeyCode.ButtonB,       -- i.e. B on XBOX, Circle on PlayStation
}


--[!] UTILS

--> used to check whether a player is alive
--    i.e. one that's alive and has both a humanoid + a root part
local function isAlive(character)
  if typeof(character) ~= 'Instance' or not character:IsA('Model') or not character:IsDescendantOf(workspace) then
    return false
  end

  local humanoid = character:FindFirstChildOfClass('Humanoid')
  local humanoidState = humanoid and humanoid:GetState() or Enum.HumanoidStateType.Dead
  local humanoidRootPart = humanoid and humanoid.RootPart or nil
  if humanoidState == Enum.HumanoidStateType.Dead or not humanoidRootPart then
    return false
  end

  return true
end

--> used to await a player's character
--   i.e. one that exists + is alive
local function tryGetCharacter(player)
  if typeof(player) ~= 'Instance' or not player:IsA('Player') then
    return nil
  end

  local character
  while not character do
    if not player or not player:IsDescendantOf(PlayerService) then
      break
    end

    local char = player.Character
    if not char then
      player.CharacterAdded:Wait()
      continue
    end

    if not isAlive(char) then
      RunService.Stepped:Wait()
      continue
    end

    character = char
  end

  return character
end

--> used to cleanup any connections/instances
local function cleanupDisposables(disposables)
  for _, disposable in next, disposables do
    local t = typeof(disposable)
    if t == 'RBXScriptConnection' then
      pcall(disposable.Disconnect, disposable)
    elseif t == 'Instance' then
      pcall(disposable.Destroy, disposable)
    elseif t == 'function' then
      disposable()
    end
  end
  table.clear(disposables)
end


--[!] MAIN
local function beginTracking(player)
  local character = tryGetCharacter(player)
  if not character then
    return
  end

  -- cleanup
  local disposables = { }

  -- state
  local isDashing = false
  local lastDashTime = nil

  -- set up
  local camera = game.Workspace.CurrentCamera
  local humanoid = character:FindFirstChildOfClass('Humanoid')
  local rootPart = humanoid.RootPart

  -- set up listener(s)
  local died
  died = humanoid.Died:Connect(function ()
    cleanupDisposables(disposables)
  end)
  table.insert(disposables, died)

  local function handleDash(_, inputState, inputObject)
    if not isAlive(character) or inputState ~= Enum.UserInputState.Begin then
      return Enum.ContextActionResult.Pass
    end

    -- make sure they're not on a cooldown / already dashing
    local now = os.clock()
    if isDashing or (lastDashTime and now - lastDashTime < DASH_DEBOUNCE) then
      return Enum.ContextActionResult.Pass
    end

    -- check to see if the player is moving in any direction
    -- before allowing them to dash
    local moveVec = humanoid.MoveDirection
    local sqrMagnitude = moveVec.X*moveVec.X + moveVec.Y*moveVec.Y + moveVec.Z*moveVec.Z
    if sqrMagnitude <= EPSILON then
      return Enum.ContextActionResult.Pass
    end

    -- i.e. if we're shiftlocked / fps then get the
    --      move direction relative to the camera
    --
    -- otherwise, we can just use the Humanoid.MoveDirection
    --
    if UserInputService.MouseBehavior == Enum.MouseBehavior.LockCenter then
      moveVec = camera.CFrame:VectorToObjectSpace(rootPart.CFrame:VectorToWorldSpace(moveVec))
    end

    -- ensure we're using the unit vector
    -- can be important on mobile because it can be greater than 1
    -- in some cases from the touchpad controls
    if sqrMagnitude > 1 then
      moveVec = moveVec.Unit
    end

    isDashing = true
    lastDashTime = now

    -- create our dash instances
    local attachment = Instance.new('Attachment')
    attachment.Name = 'DashAttachment'
    attachment.Parent = rootPart
    disposables._attachment = attachment

    local velo = Instance.new('LinearVelocity')
    velo.MaxForce = math.huge
    velo.PlaneVelocity = Vector2.zero
    velo.RelativeTo = Enum.ActuatorRelativeTo.World
    velo.Attachment0 = attachment
    velo.VelocityConstraintMode = Enum.VelocityConstraintMode.Plane
    velo.PrimaryTangentAxis = Vector3.xAxis
    velo.SecondaryTangentAxis = Vector3.zAxis
    velo.Parent = rootPart
    disposables._velo = velo

    -- compute the desired velocity in the direction they want to move
    local desiredVelocity = DASH_STRNGTH * Vector2.new(moveVec.X, moveVec.Z)

    -- interpolate the dash velocity
    -- and then stop dashing once we reach our DASH_TIME_LIM target
    local runtime
    runtime = RunService.Stepped:Connect(function (gt, dt)
      if not isAlive(character) then
        return
      end

      -- update dash velocity
      local elapsed = os.clock() - now
      if elapsed < DASH_TIME_LIM then
        -- we're going to use an outExpo easing here
        -- so that we accelerate to the max speed in a
        -- short duration
        --
        --   - Note: see easing reference @ https://easings.net/
        --
        local alpha = elapsed / DASH_TIME_LIM
        alpha = alpha == 1 and alpha or 1 - math.pow(2, -10 * alpha)
        velo.PlaneVelocity = desiredVelocity*alpha
        return
      end

      -- we've finished dashing so let's cleanup
      if attachment then
        if attachment == disposables._attachment then
          disposables._attachment = nil
        end
        pcall(attachment.Destroy, attachment)
      end

      if velo then
        if velo == disposables._velo then
          disposables._velo = nil
        end
        pcall(velo.Destroy, velo)
      end

      if runtime then
        if runtime == disposables._runtime then
          disposables._runtime = nil
        end

        if runtime.Connected then
          runtime:Disconnect()
        end
      end

      isDashing = false
      lastDashTime = os.clock()
    end)
    disposables._runtime = runtime

    return Enum.ContextActionResult.Sink
  end

  -- bind the dash keybindings +/- the dash touch button
  ContextActionService:BindAction('DashAction', handleDash, DASH_TOUCH_BTN, table.unpack(DASH_KEYBIND_CODES))

  table.insert(disposables, function ()
    pcall(ContextActionService.UnbindAction, ContextActionService, 'DashAction')
  end)
end


--[!] INIT
local player = PlayerService.LocalPlayer
if typeof(player.Character) ~= 'nil' then
  beginTracking(player)
end

player.CharacterAdded:Connect(function ()
  return beginTracking(player)
end)

1 Like