Help with adding a turn feature to my skis

  1. What do you want to achieve? I am trying to add a turning feature into my skis but i can’t figure how to.

  2. What is the issue? The Problem I have is in my skis don’t have a turning feature like a d and i can’t figure out how to add my animations in and script it.

  3. What solutions have you tried so far? I have tried to fix but i don’t know were to add to even start (i am trying learning lua)

local SwingTrack = nil
local TuckTrack = nil

local SKI_MESH = 142468161
local POLE_MESH = 142468332
local SKI_TX1 = 142465527
local SKI_TX2 = 142465545

--- FRICTION COEFFIECIENTS ---
local KineticWoodSnow = 0.1
local KineticSkiSnow = 0.05
local StaticWoodSnow = 0.14
local StaticSkiSnow = 0.1

local BodyVelocity = nil
local BodyGyro = nil

local Tool = script.Parent
local Handle = Tool:WaitForChild('Handle')


local SKI_SWING_ANIM = Tool:WaitForChild('Swing')
local SKI_TUCK_ANIM =  Tool:WaitForChild('Tuck')


local PlayersService = game:GetService('Players')

local Character, Torso, LeftLeg, RightLeg = nil
local Skis = {}

local function New(ty)
	return function(data)
		local obj = Instance.new(ty)
		for k, v in pairs(data) do
			if type(k) == 'number' then
				v.Parent = obj
			else
				if type(v) == 'function' then
					obj[k]:connect(v)
				else
					obj[k] = v
				end
			end
		end
		return obj
	end
end

local function GetAssetUrl(assetId)
	return 'http://www.roblox.com/asset/?id=' .. tostring(assetId)
end

local function GetCharacter()
	if PlayersService:GetPlayerFromCharacter(Tool.Parent) then
		return Tool.Parent
	end
end

local function GetTorso()
	return GetCharacter() and GetCharacter():FindFirstChild('HumanoidRootPart')
end

local function GetLeftLeg()
	return GetCharacter() and GetCharacter():FindFirstChild('Left Leg')
end

local function GetRightLeg()
	return GetCharacter() and GetCharacter():FindFirstChild('Right Leg')
end

local function GetPlayer()
	return PlayersService:GetPlayerFromCharacter(Tool.Parent)
end

local function GetLA()
	return GetCharacter() and GetCharacter():FindFirstChild('Left Arm')
end

local function GetRA()
	return GetCharacter() and GetCharacter():FindFirstChild('Right Arm')
end

local NormalIdToSurfaceId =
{
	[Enum.NormalId.Back] = 'BackSurface';
	[Enum.NormalId.Front] = 'FrontSurface';
	[Enum.NormalId.Left] = 'LeftSurface';
	[Enum.NormalId.Right] = 'RightSurface';
	[Enum.NormalId.Top] = 'TopSurface';
	[Enum.NormalId.Bottom] = 'BottomSurface';
}

local function ClassifySurface(part, point)
	local psize = part.Size
	local localPoint = part.CFrame:pointToObjectSpace(point)
	local posDiff = psize/2 - localPoint
	local negDiff = psize/2 + localPoint
	local minDiff = Vector3.new(
			math.min(math.abs(posDiff.x), math.abs(negDiff.x)),
			math.min(math.abs(posDiff.y), math.abs(negDiff.y)),
			math.min(math.abs(posDiff.z), math.abs(negDiff.z)))
		 
	if minDiff.x <= math.min(minDiff.y, minDiff.z) then -- On x-axis
		if math.abs(posDiff.x) < math.abs(negDiff.x) then
			return Enum.NormalId.Right
		else
			return Enum.NormalId.Left
		end
	elseif minDiff.y <= minDiff.z then -- On y-axis
		if math.abs(posDiff.y) < math.abs(negDiff.y) then
			return Enum.NormalId.Top
		else
			return Enum.NormalId.Bottom
		end
	else -- On z-axis
		if math.abs(posDiff.z) < math.abs(negDiff.z) then
			return Enum.NormalId.Back
		else
			return Enum.NormalId.Front
		end
	end
end


local function GetNormal(part, point)
	local normal = ClassifySurface(part, point)
	return ((part.CFrame-part.CFrame.p)*Vector3.FromNormalId(normal))
end

local function AngleFromFlat(vec1)
	local vec1 = vec1.unit
	local flatVec = (vec1 * Vector3.new(1,0,1)).unit
	local angle = math.acos(vec1:Dot(flatVec))
	-- if the angle is nan then assume flat surface
	if angle ~= angle then
		return math.rad(90)
	end
	return math.rad(90) - angle
end

------ RAYCASTING FUNCTIONS ---------

-- Defaulted ray ignore check if none specified
local function RayIgnoreCheck(hit, pos)
	return false
end

local function RayIgnoreCamera(hit, pos)
	if hit then
		if hit.CanCollide == false then return true end
	end
	return false
end

-- @preconditions: vec should be a unit vector, and 0 < rayLength <= 1000
local function RayCast(startPos, vec, rayLength, ignoreFunc, ignoreList)
	ignoreFunc = ignoreFunc or RayIgnoreCheck
	ignoreList = ignoreList or {GetCharacter()}
	vec = vec.unit
	local ray = Ray.new(startPos, vec*rayLength)
	local hitObject, hitPos = workspace:FindPartOnRayWithIgnoreList(ray, ignoreList)
	if hitObject and hitPos then
		if ignoreFunc(hitObject, hitPos) then
			table.insert(ignoreList, hitObject)
			return RayCast(startPos, vec, rayLength, ignoreFunc, ignoreList)
		end
	end
	return hitObject, hitPos
end

------ END RAYCASTING FUNCTIONS ---------


local function GetGroundBelowCharacter(character)
	local torso = GetTorso(character)
	local leftLeg = GetLeftLeg()
	if torso and leftLeg then
		local torsoCFrame = torso.CFrame
		local hitData = {}
		for z = -1, 1, 2 do
			for x = -1, 1, 2 do
				local hit, pos = RayCast(
					torsoCFrame.p + torsoCFrame:vectorToWorldSpace(Vector3.new(torso.Size.X/2*x,0,torso.Size.Z/2*z)),
					Vector3.new(0,-1,0),
					torso.Size.Y/2 + leftLeg.Size.Y + 6, 
					RayIgnoreCamera)
				if hit and pos then
					table.insert(hitData, {['Pos'] = CFrame.new(Vector3.new(torsoCFrame.p.X, pos.Y + 3, torsoCFrame.p.Z)), ['Part'] = hit})
				end
			end
		end
		table.sort(hitData, function(a,b) return a['Pos'].p.Y > b['Pos'].p.Y end)
		if #hitData > 0 then
			return hitData[1]['Pos'], hitData[1]['Part']
		end
		return torsoCFrame, nil
	end
end


local function StudsPerSToMetersPerS(studs)
	return studs / 2
end

local function MetersPerSToStudsPerS(meters)
	return meters * 2.5
end

local LastUpdate = nil
local State = "Stopped"
local LastStateTime = tick()
--[[
Cd*Ap = 0.11 for an upright body, minimal frontal area
Cd*Ap = 0.84 for a horizontal body, maximal frontal area
Cd*Ap = 0.46 for a body in tuck position
--]]
-- http://www.math.utah.edu/~eyre/rsbfaq/physics.html
local function UpdateCharacter()
	local now = tick()
	if LastUpdate == nil then
		LastUpdate = now
		return
	end
	local char = GetCharacter()
	local torso = char and char:FindFirstChild('Torso')
	if char and torso then
		local groundHit, groundPart = GetGroundBelowCharacter(char)
		if groundHit and groundPart then
			local normal = GetNormal(groundPart, groundHit.p)
			-- orient you so you are standing on the surface
			if BodyGyro then
				if normal:Dot(Vector3.new(0,1,0)) < 0.7 then -- we would trip here so abort
					BodyGyro.cframe = CFrame.new()
				else
					local newCFrame = CFrame.new(Vector3.new(), normal) * CFrame.Angles(-math.pi/2,0,0)
					BodyGyro.cframe = newCFrame
				end
			end
			local theta = AngleFromFlat(normal) --[[ Angle of slope --]]
			local deltaTime = now - LastUpdate
			-- on flat, if speed is slow need to push
			if normal.unit:Dot(Vector3.new(0,1,0)) > 0.9 and BodyVelocity.velocity.magnitude < 22 then
				-- use poles!
				if State ~= "Poles" and tick() - LastStateTime > 0.5 then
					print'Poles State engage'
					TuckTrack:Stop()
					SwingTrack:Play()
					State = "Poles"
					LastStateTime = tick()
				end
				local skiOrientation = (torso.CFrame.lookVector * Vector3.new(1,0,1)).unit
				if BodyVelocity then
					BodyVelocity.maxForce = Vector3.new(100000000, 0, 10000000)
					if math.abs(math.acos(skiOrientation:Dot(BodyVelocity.velocity.unit))) > math.pi/3 then
						BodyVelocity.velocity = BodyVelocity.velocity * (1 - 0.55 * deltaTime)
					end
					BodyVelocity.velocity = (BodyVelocity.velocity + (skiOrientation * 19 * deltaTime))
				end
			else -- on the slope
				if State ~= "Tuck" and tick() - LastStateTime > 0.5 and (State ~= "Poles" or BodyVelocity.velocity.magnitude > 24) then
					print'Tuck State engage'
					SwingTrack:Stop()
					TuckTrack:Play()
					State = "Tuck"
					LastStateTime = tick()
				end

				local mu = KineticSkiSnow --[[ dynamic coeff of snow --]]
				local g = 9.81 --[[ gravity --]]
				local CdAp = 0.65 --[[ Drag coeff with frontal area --]]
				--[[ Density of air, http://en.wikipedia.org/wiki/Density_of_air --]]
				local rho = 1.3413 --[[ kg*m^3 --]]
				local V = StudsPerSToMetersPerS(torso.Velocity).magnitude --[[ current velocity --]]
				local m = 65  --[[ mass --]]
				
				local downhillVec = Vector3.new(0,1,0):Cross(normal):Cross(normal)
				
				local skiOrientation = (torso.CFrame.lookVector * Vector3.new(1,0,1)).unit
				local angleBetweenLookAndHill = math.acos((downhillVec * Vector3.new(1,0,1)).unit:Dot(skiOrientation))
				if math.abs(angleBetweenLookAndHill) > math.rad(90) then -- if we are pointed uphill, flip direction
					skiOrientation = skiOrientation * -1
					angleBetweenLookAndHill = math.acos((downhillVec * Vector3.new(1,0,1)).unit:Dot(skiOrientation))
				end
				
				local accel = g*math.sin(theta) - mu*g*math.cos(theta) - (CdAp*rho*(V*V))/(2*m)
				if BodyVelocity then
					BodyVelocity.maxForce = Vector3.new(100000000, 0, 10000000)
					if math.abs(math.acos(skiOrientation:Dot(BodyVelocity.velocity.unit))) > math.pi/3 then
						BodyVelocity.velocity = BodyVelocity.velocity * (1 - 0.55 * deltaTime)
					end
					BodyVelocity.velocity = (BodyVelocity.velocity + ((skiOrientation + Vector3.new(0,0.0001,0)).unit * accel) * deltaTime) 
				end
			end
			
			
			LastUpdate = now
		else
			--[[
			if State ~= "Falling" and tick() - LastStateTime > 0.5 then
				SwingTrack:Stop()
				TuckTrack:Play()
				State = "Falling"
				LastStateTime = tick()
			end
			--]]
			if BodyGyro then
				BodyGyro.cframe = CFrame.new()
			end
			if BodyVelocity then
				--BodyVelocity.maxForce = Vector3.new()
			end
		end

	end
end

local function CreateSkis(leftLeg, rightLeg, leftArm, rightArm)
	DestroySkis()
	local leftSki = New'Part'
	{
		Name = 'LeftSki';
		FormFactor = 'Custom';
		Size = Vector3.new(0.7, 0.2, 5.5);
		TopSurface = 'Smooth';
		BottomSurface = 'Smooth';
		CanCollide = false;
		New'SpecialMesh'
		{
			TextureId = GetAssetUrl(SKI_TX2);
			MeshId = GetAssetUrl(SKI_MESH);
			Scale = Vector3.new(3,3,3);
		}
	}
	local rightSki = leftSki:Clone()
	rightSki.Name = 'RightSki';

	Skis['left'] = leftSki
	Skis['right'] = rightSki
	--Skis['left'].CFrame = CFrame.new(0, 10, 0);
	--Skis['right'].CFrame = CFrame.new(3, 10, 0);
	
	local leftLegWeld = New'ManualWeld'
	{
		Name = 'LeftSkiWeld';
		Part0 = leftLeg;
		Part1 = leftSki;
		C0 = CFrame.new(0,-leftLeg.Size.Y/2 + 0.1,-0.5);
		Parent = leftSki;
	}
	local rightLegWeld = New'ManualWeld'
	{
		Name = 'RightSkiWeld';
		Part0 = rightLeg;
		Part1 = rightSki;
		C0 = CFrame.new(0,-rightLeg.Size.Y/2 + 0.1,-0.5);
		Parent = rightSki;
	}
	
	local leftPole = New'Part'
	{
		Name = 'LeftPole';
		FormFactor = 'Custom';
		Size = Vector3.new(0.3, 0.3, 4);
		TopSurface = 'Smooth';
		BottomSurface = 'Smooth';
		CanCollide = false;
		New'SpecialMesh'
		{
			TextureId = GetAssetUrl(SKI_TX2);
			MeshId = GetAssetUrl(POLE_MESH);
			Scale = Vector3.new(3,3,3);
		}
	}
	local rightPole = leftPole:Clone()
	rightPole.Name = 'RightPole';

	Skis['leftPole'] = leftPole
	Skis['rightPole'] = rightPole
	
	local leftArmWeld = New'ManualWeld'
	{
		Name = 'LeftArmWeld';
		Part0 = leftArm;
		Part1 = leftPole;
		C0 = CFrame.new(0,-leftArm.Size.Y/2 + 0.1,1.8);-- * CFrame.Angles(math.pi,0,0);
		Parent = leftPole;
	}
	local rightArmWeld = New'ManualWeld'
	{
		Name = 'RightArmWeld';
		Part0 = rightArm;
		Part1 = rightPole;
		C0 = CFrame.new(0,-rightArm.Size.Y/2 + 0.1,1.8);-- * CFrame.Angles(math.pi,0,0);
		Parent = rightSki;
	}
	
	Skis['left'].Parent = GetCharacter()
	Skis['right'].Parent = GetCharacter()
	Skis['leftPole'].Parent = GetCharacter()
	Skis['rightPole'].Parent = GetCharacter()
end

function DestroySkis()
	for key, ski in pairs(Skis) do
		ski:Destroy()
		Skis[key] = nil
	end
end

function OnTouched(hitPart)
	
end

function OnActivated()
	if not Tool.Enabled then return end
	Tool.Enabled = false

	Tool.Enabled = true
end

function ControlLoop()
	UpdateCharacter()
end

local Equipped = false

function OnEquipped(mouse)
	Equipped = true
	Character, Torso, LeftLeg, RightLeg = GetCharacter(), GetTorso(), GetLeftLeg(), GetRightLeg()
	LeftArm, RightArm = GetLA(), GetRA()
	if LeftLeg and RightLeg then
		-- Spawn because welds
		spawn(function()
			CreateSkis(LeftLeg, RightLeg, LeftArm, RightArm)
			BodyGyro = Instance.new('BodyGyro')
			BodyGyro.Parent = Torso
			BodyVelocity = Instance.new('BodyVelocity')
			BodyVelocity.maxForce = Vector3.new(100000000, 0, 10000000)
			BodyVelocity.velocity = Vector3.new()
			BodyVelocity.Parent = Torso
		end)
	end
	LastUpdate = nil
	local co = coroutine.create(function() while Equipped do ControlLoop() wait() end end)
	coroutine.resume(co)
	local humanoid = Character:WaitForChild('Humanoid')
	SwingTrack = humanoid:LoadAnimation(SKI_SWING_ANIM)
	TuckTrack = humanoid:LoadAnimation(SKI_TUCK_ANIM)
	SwingTrack:Play()
	Handle.Transparency = 1
end

function OnUnequipped()
	Equipped = false
	Character, Torso, LeftLeg, RightLeg = nil, nil, nil, nil
	DestroySkis()
	if BodyVelocity then
		BodyVelocity:Destroy()
		BodyVelocity = nil
	end
	if BodyGyro then
		BodyGyro:Destroy()
		BodyGyro = nil
	end
	if SwingTrack then
		SwingTrack:Stop()
		SwingTrack = nil
	end
	if TuckTrack then
		TuckTrack:Stop()
		TuckTrack = nil
	end
	LastUpdate = nil
	Handle.Transparency = 0
end


Tool.Activated:connect(OnActivated)
Tool.Equipped:connect(OnEquipped)
Tool.Unequipped:connect(OnUnequipped)

here is the tool i am using (I have all ready animated in the left/right turn animation)
Annotation 2020-06-05 143528

If you could tell me what and were I need to script in the turning feature that would be great :grin:

You’ll need to create custom controls. UserInputService | Documentation - Roblox Creator Hub and also check out Input and Camera | Documentation - Roblox Creator Hub. I’m working on some custom controls for a similar situation.

ok thank very much you :grinning:!