Glitchy movements in crossy road type game

So, I have this LocalScript that controls the movement for the player (movement is replicated on the server just without tween animations) and right now I just can’t figure out how to stop the player from glitching a bit while stepping on the lilypads. The character also can float above or clip into it if they squish themselves (by holding down a movement button like wasd) before the lilypad has finished its sinking animations.

LocalScript:

local userInputService = game:GetService("UserInputService")
local tweenService = game:GetService("TweenService")
local Workspace = game:GetService("Workspace")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remotes = ReplicatedStorage.remotes

local player = script.Parent:WaitForChild("actualCharacter")
local bottomAttachment = player:FindFirstChild("BottomAttachment", true)
local playerHead = script.Parent:WaitForChild("Head")
local playerTorso = script.Parent:WaitForChild("Torso")
local humanoid = script.Parent:WaitForChild("Humanoid")
local bodyPosition = player.Parent:WaitForChild("cameraFollowPart"):WaitForChild("BodyPosition")

local localPlayer = Players.LocalPlayer
local mouse = localPlayer:GetMouse()

local barrierLeft = Workspace.barrierLeft
local barrierRight = Workspace.barrierRight

local distance = 8
local jumpHeight = 6
local moveTime = 0.14
local rotateTime = 0.21
local squishTime = 0.2

local moveCooldowns = {}
local moveCooldownTime = moveTime

local moveTweenInfo = TweenInfo.new(moveTime, Enum.EasingStyle.Quart)
local rotateTweenInfo = TweenInfo.new(rotateTime, Enum.EasingStyle.Back)
local squishTweenInfo = TweenInfo.new(squishTime, Enum.EasingStyle.Back)

local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {barrierLeft, barrierRight, player.Parent}
raycastParams.FilterType = Enum.RaycastFilterType.Exclude

local initialPlayerSize = player.Size
local initialPlayerPositionY = player.Position.Y

local viewGridSpace = false
local isMoving = false
local isSquishing = false

local platformOffset = Vector3.new()

local facingDirection = "back"
local currentPlatform = nil
local connection

function keyDownSquish(mode: boolean, x, z)
	if not x then x = 0 end
	if not z then z = 0 end

	local currentY = player.Position.Y

	isSquishing = true

	if mode then
		local newSize = Vector3.new(initialPlayerSize.X + 0.5, initialPlayerSize.Y - 3, initialPlayerSize.Z)
		local sizeDiffY = (initialPlayerSize.Y - newSize.Y) / 2

		tweenService:Create(player, squishTweenInfo, {
			Size = newSize,
			Position = Vector3.new(player.Position.X, currentY - sizeDiffY, player.Position.Z)
		}):Play()
	else
		tweenService:Create(player, squishTweenInfo, {
			Size = initialPlayerSize,
			Position = Vector3.new(player.Position.X + x, currentY, player.Position.Z + z)
		}):Play()

		task.delay(squishTime, function()
			isSquishing = false
		end)
	end
end

function shortestRotation(currentY, targetY)
	local delta = (targetY - currentY) % 360

	if delta > 180 then
		delta = delta - 360
	end

	return delta
end

function handleLilypadInteraction(hitPart)
	currentPlatform = hitPart
	platformOffset = player.Position - hitPart.Position

	local tweenInfo = TweenInfo.new(0.2, Enum.EasingStyle.Sine, Enum.EasingDirection.Out)
	local originalPosition = hitPart.Position
	local sinkPosition = originalPosition - Vector3.new(0, 0.8, 0)

	local sinkTween = tweenService:Create(hitPart, tweenInfo, {Position = sinkPosition})
	local riseTween = tweenService:Create(hitPart, tweenInfo, {Position = originalPosition})

	connection = RunService.RenderStepped:Connect(function()
		if currentPlatform and currentPlatform:IsDescendantOf(Workspace.environment.obstacles) then
			if isSquishing then 
				player.Position = Vector3.new(player.Position.X, currentPlatform.Position.Y + platformOffset.Y - 1.5, player.Position.Z)
			else
				player.Position = Vector3.new(player.Position.X, currentPlatform.Position.Y + platformOffset.Y, player.Position.Z)
			end
		end
	end)

	sinkTween:Play()
	sinkTween.Completed:Wait()
	riseTween:Play()
	riseTween.Completed:Wait()

	if connection then
		connection:Disconnect()
	end	
end

function moveCharacter(direction: string)
	if not direction or isMoving then return end

	if connection then
		connection:Disconnect()
	end

	isMoving = true

	local targetPosition
	local cameraNextPosition

	if direction == "front" then
		targetPosition = player.Position + Vector3.new(0, 0, -distance)
		cameraNextPosition = Vector3.new(0, 0, -distance)
	elseif direction == "back" then
		targetPosition = player.Position + Vector3.new(0, 0, distance)
		cameraNextPosition = Vector3.new(0, 0, distance)
	elseif direction == "left" then
		targetPosition = player.Position + Vector3.new(-distance, 0, 0)
		cameraNextPosition = Vector3.new(-distance, 0, 0)
	elseif direction == "right" then
		targetPosition = player.Position + Vector3.new(distance, 0, 0)
		cameraNextPosition = Vector3.new(distance, 0, 0)
	end

	if targetPosition then
		local raycastResult = workspace:Raycast(targetPosition + Vector3.new(0, 5, 0), Vector3.new(0, -10, 0), raycastParams)
		if raycastResult then
			local attachment = raycastResult.Instance:FindFirstChild("Attachment")
			if attachment and bottomAttachment then
				local desiredY = attachment.WorldPosition.Y - (bottomAttachment.WorldPosition.Y - player.Position.Y)
				targetPosition = Vector3.new(targetPosition.X, desiredY, targetPosition.Z)
			else
				targetPosition = Vector3.new(targetPosition.X, initialPlayerPositionY, targetPosition.Z)
			end
		end

		local yDiff = math.abs(targetPosition.Y - player.Position.Y)
		local adjustedJumpHeight = math.max(jumpHeight, yDiff + 2) --clearance for steeper jump e.g: falling into water
		local midPoint = player.Position:Lerp(targetPosition, 0.5) + Vector3.new(0, adjustedJumpHeight, 0)

		local jumpUp = tweenService:Create(player, TweenInfo.new(moveTime / 2, Enum.EasingStyle.Sine, Enum.EasingDirection.Out), {
			Position = midPoint
		})
		local jumpDown = tweenService:Create(player, TweenInfo.new(moveTime / 2, Enum.EasingStyle.Sine, Enum.EasingDirection.In), {
			Position = targetPosition
		})

		jumpUp:Play()
		bodyPosition.Position += cameraNextPosition

		if facingDirection ~= direction then
			local targetY
			if direction == "front" then
				targetY = 0
			elseif direction == "back" then
				targetY = -180
			elseif direction == "left" then
				targetY = 90
			elseif direction == "right" then
				targetY = -90
			end

			if targetY then
				local rotationDelta = shortestRotation(player.Orientation.Y, targetY)
				tweenService:Create(player, rotateTweenInfo, {
					Orientation = player.Orientation + Vector3.new(0, rotationDelta, 0)
				}):Play()
			end

			facingDirection = direction
		end

		jumpUp.Completed:Wait()
		jumpDown:Play()
		jumpDown.Completed:Wait()

		isMoving = false

		local raycastResult = workspace:Raycast(player.Position + Vector3.new(0, 5, 0), Vector3.new(0, -10, 0), raycastParams)
		if raycastResult then
			local hitPart = raycastResult.Instance
			if hitPart and hitPart.Name == "lilypad" then
				handleLilypadInteraction(hitPart)
			else
				currentPlatform = nil
			end
		end

		remotes.movementEvent:FireServer(direction, player.Position)
		remotes.rotateEvent:FireServer(direction)
	else
		isMoving = false
	end
end

bodyPosition.Position = playerTorso.Position + Vector3.new(-6, 0, -24)
player.Orientation = Vector3.new(0, -180, 0)
playerHead.Orientation = Vector3.new(0, -180, 0)
playerTorso.Orientation = Vector3.new(0, -180, 0)
humanoid.WalkSpeed = 0
humanoid.JumpPower = 0

--commences the squishing
userInputService.InputBegan:Connect(function(input, gameProcessedEvent)
	if not userInputService.KeyboardEnabled then return end
	if gameProcessedEvent then return end
	if isMoving then return end

	local key = input.KeyCode
	if key == Enum.KeyCode.W or key == Enum.KeyCode.Up 
		or key == Enum.KeyCode.S or key == Enum.KeyCode.Down
		or key == Enum.KeyCode.A or key == Enum.KeyCode.Left 
		or key == Enum.KeyCode.D or key == Enum.KeyCode.Right then
		keyDownSquish(true)
	end
end)

function canMove(key)
	local now = tick()

	if moveCooldowns[key] and now < moveCooldowns[key] then
		return false
	end

	moveCooldowns[key] = now + moveCooldownTime
	return true
end

--actually moves the character
userInputService.InputEnded:Connect(function(input, gameProcessedEvent)
	if not userInputService.KeyboardEnabled then return end
	if gameProcessedEvent then return end
	if isMoving then return end

	local key = input.KeyCode
	if (key == Enum.KeyCode.W or key == Enum.KeyCode.Up) and canMove("front") then	
		keyDownSquish(false, 0, -distance)
		moveCharacter("front")
	elseif (key == Enum.KeyCode.S or key == Enum.KeyCode.Down) and canMove("down") then
		keyDownSquish(false, 0, distance)
		moveCharacter("back")
	elseif (key == Enum.KeyCode.A or key == Enum.KeyCode.Left) and canMove("left") then
		keyDownSquish(false, -distance, 0)
		moveCharacter("left")
	elseif (key == Enum.KeyCode.D or key == Enum.KeyCode.Right) and canMove("right") then
		keyDownSquish(false, distance, 0)
		moveCharacter("right")
	end
end)

--commences the squishing for mouse
mouse.Button1Down:Connect(function()
	if not userInputService.KeyboardEnabled then return end
	if isMoving then return end

	keyDownSquish(true)
end)

--releases the squishing and does movement for mouse
mouse.Button1Up:Connect(function()
	if not userInputService.KeyboardEnabled then return end
	if isMoving then return end

	if not canMove("front") then return end
	keyDownSquish(false, 0, -distance)
	moveCharacter("front")
end) 

--mobile stuff
userInputService.TouchStarted:Connect(function(input, gameProcessedEvent)
	if gameProcessedEvent then return end
	if isMoving then return end

	keyDownSquish(true)
end)

userInputService.TouchTap:Connect(function(touchPosition, gameProcessedEvent)
	if gameProcessedEvent then return end
	if isMoving then return end

	if not canMove("front") then return end
	keyDownSquish(false, 0, -distance)
	moveCharacter("front")

	remotes.movementEvent:FireServer("front")
	remotes.rotateEvent:FireServer("front")
end)

userInputService.TouchSwipe:Connect(function(input)
	if input == Enum.SwipeDirection.Up then
		keyDownSquish(false, 0, -distance)
		moveCharacter("front")
	elseif input == Enum.SwipeDirection.Down then
		keyDownSquish(false, 0, distance)
		moveCharacter("back")
	elseif input == Enum.SwipeDirection.Left then
		keyDownSquish(false, -distance, 0)
		moveCharacter("left")
	elseif input == Enum.SwipeDirection.Right then
		keyDownSquish(false, distance, 0)
		moveCharacter("right")
	end
end)

if viewGridSpace then
	local part = Instance.new("Part")
	part.Size = Vector3.new(8, 10.5, 8)
	part.Anchored = false
	part.CanCollide = false
	part.CastShadow = false
	part.Transparency = 0.7
	part.Name = "GridSpacePart"
	part.Parent = player.Parent

	local weld = Instance.new("WeldConstraint")
	weld.Part0 = player.Parent:WaitForChild("GridSpacePart")
	weld.Part1 = player
	weld.Parent = part

	RunService.Heartbeat:Connect(function()
		part.Position = player.Position
		part.CFrame = CFrame.new(part.Position) * CFrame.Angles(0, 0, 0)
	end)
end
2 Likes

I think to make a proper fix for this would be a bit of a hassle, as you’d maybe have to lerp to move the character according to where the lilypad is, which depends on the timing of the player starting to squish and won’t be the same every time.
Personally I’d probably do a bit of a hacky trick by basically making it so that the lilypad and character has two different tweens for when the character jumps on the lilypad, then if the player wants to start squishing before both tween are finished, then you cancel the character’s tween and teleport it to where it will normally end up, and then start the squish tween while the lilypad tween is still running. Unless the teleporting/floating is very obvious, then I think this can end up looking quite nice, and if someone is playing the game slowly, then it wouldn’t affect them and wouldn’t look hacky at all.
An alternative to fix it without floating and teleporting and not having to use lerp or something alike, could be a debounce so you can’t squish and start the moving before the lilypad tween is done, while this looks good, I think it’d be extremely annoying having to wait for that after 5min of playing the game.

1 Like

I guess a temporary fix I have now is to just not make the squish animation able to happen if the character is on a lilypad tile. I don’t really know what else to do about this as this is already enough of a mess as it is.

Okay, so I was able to make some small improvements, but things are still a bit choppy. There is also the new issue where if the player quickly steps in one direction and then quickly moves back, they will be floating a bit above the ground. The players BottomAttachment that is used to align the bottom of the players feet with the ground now tweens along with the players squish animation so it moves up and stays inline with the bottom of the players feet even in its squished state yet it still looks out of time or something and I have no idea why.

Here is the entire code so far. I suspect the issue lies somewhere between the keydownSquish(), handleLilypadInteraction() and moveCharacter() functions:

local userInputService = game:GetService("UserInputService")
local tweenService = game:GetService("TweenService")
local Workspace = game:GetService("Workspace")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remotes = ReplicatedStorage.remotes

local player = script.Parent:WaitForChild("actualCharacter")
local bottomAttachment = player:FindFirstChild("BottomAttachment", true)
local playerHead = script.Parent:WaitForChild("Head")
local playerTorso = script.Parent:WaitForChild("Torso")
local humanoid = script.Parent:WaitForChild("Humanoid")
local bodyPosition = player.Parent:WaitForChild("cameraFollowPart"):WaitForChild("BodyPosition")

local localPlayer = Players.LocalPlayer
local mouse = localPlayer:GetMouse()

local barrierLeft = Workspace.barrierLeft
local barrierRight = Workspace.barrierRight

local distance = 8
local jumpHeight = 6
local moveTime = 0.14
local rotateTime = 0.21
local squishTime = 0.2

local moveCooldowns = {}
local moveCooldownTime = moveTime

local moveTweenInfo = TweenInfo.new(moveTime, Enum.EasingStyle.Quart)
local rotateTweenInfo = TweenInfo.new(rotateTime, Enum.EasingStyle.Back)
local squishTweenInfo = TweenInfo.new(squishTime, Enum.EasingStyle.Back)

local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {barrierLeft, barrierRight, player.Parent}
raycastParams.FilterType = Enum.RaycastFilterType.Exclude

local initialPlayerSize = player.Size
local initialPlayerPositionY = player.Position.Y
local initialBottomAttachmentPositionY = bottomAttachment.Position.Y

local viewGridSpace = false
local isMoving = false
local isSquished = false
local isSquishing = false

local facingDirection = "back"
local connection

function keyDownSquish(mode: boolean, x, z)
	if not x then x = 0 end
	if not z then z = 0 end

	local currentY = player.Position.Y

	if mode then
		if isSquished then return end

		isSquishing = true
		isSquished = true

		local newSize = Vector3.new(initialPlayerSize.X + 0.5, initialPlayerSize.Y - 3, initialPlayerSize.Z)
		local sizeDiffY = (initialPlayerSize.Y - newSize.Y) / 2

		tweenService:Create(player, squishTweenInfo, {
			Size = newSize,
			Position = Vector3.new(player.Position.X, currentY - sizeDiffY, player.Position.Z)
		}):Play()

		tweenService:Create(bottomAttachment, squishTweenInfo, {
			Position = Vector3.new(0, -newSize.Y / 2, 0)
		}):Play()
	else
		if not isSquished then return end

		isSquishing = true
		isSquished = false

		tweenService:Create(player, squishTweenInfo, {
			Size = initialPlayerSize,
			Position = Vector3.new(player.Position.X + x, currentY, player.Position.Z + z)
		}):Play()

		tweenService:Create(bottomAttachment, squishTweenInfo, {
			Position = Vector3.new(0, initialBottomAttachmentPositionY, 0)
		}):Play()

		task.delay(squishTime, function()
			isSquishing = false
		end)
	end
end

function shortestRotation(currentY, targetY)
	local delta = (targetY - currentY) % 360

	if delta > 180 then
		delta = delta - 360
	end

	return delta
end

function handleLilypadInteraction(hitPart)
	local tweenInfo = TweenInfo.new(0.2, Enum.EasingStyle.Sine, Enum.EasingDirection.Out)
	local originalPosition = hitPart.Position
	local sinkPosition = originalPosition - Vector3.new(0, 0.8, 0)

	local sinkTween = tweenService:Create(hitPart, tweenInfo, {Position = sinkPosition})
	local riseTween = tweenService:Create(hitPart, tweenInfo, {Position = originalPosition})

	connection = RunService.Heartbeat:Connect(function()
		local lilypadAttachment = hitPart:FindFirstChild("Attachment")
		if not bottomAttachment or not lilypadAttachment then return end

		local delta = bottomAttachment.WorldPosition - player.Position
		local newPosition = lilypadAttachment.WorldPosition - delta

		player.Position = Vector3.new(player.Position.X, newPosition.Y, player.Position.Z)
	end)

	sinkTween:Play()
	sinkTween.Completed:Wait()
	riseTween:Play()
	riseTween.Completed:Wait()

	if connection then
		connection:Disconnect()
	end
end

function moveCharacter(direction: string)
	if not direction or isMoving then return end

	isMoving = true

	local targetPosition
	local cameraNextPosition

	if direction == "front" then
		targetPosition = player.Position + Vector3.new(0, 0, -distance)
		cameraNextPosition = Vector3.new(0, 0, -distance)
	elseif direction == "back" then
		targetPosition = player.Position + Vector3.new(0, 0, distance)
		cameraNextPosition = Vector3.new(0, 0, distance)
	elseif direction == "left" then
		targetPosition = player.Position + Vector3.new(-distance, 0, 0)
		cameraNextPosition = Vector3.new(-distance, 0, 0)
	elseif direction == "right" then
		targetPosition = player.Position + Vector3.new(distance, 0, 0)
		cameraNextPosition = Vector3.new(distance, 0, 0)
	end

	--raycast to find attchment in next row to align with players BottomAttachment
	if targetPosition then
		local raycastResult = workspace:Raycast(targetPosition + Vector3.new(0, 5, 0), Vector3.new(0, -10, 0), raycastParams)
		if raycastResult then
			local attachment = raycastResult.Instance:FindFirstChild("Attachment")
			if attachment and bottomAttachment then
				local desiredY = attachment.WorldPosition.Y - ((bottomAttachment.WorldPosition.Y - 1.5) - player.Position.Y)
				targetPosition = Vector3.new(targetPosition.X, desiredY, targetPosition.Z)
			else
				targetPosition = Vector3.new(targetPosition.X, initialPlayerPositionY, targetPosition.Z)
			end
		end

		local yDiff = math.abs(targetPosition.Y - player.Position.Y)
		local adjustedJumpHeight = math.max(jumpHeight, yDiff + 2) --clearance for steeper jump e.g: falling into water
		local midPoint = player.Position:Lerp(targetPosition, 0.5) + Vector3.new(0, adjustedJumpHeight, 0)

		local jumpUp = tweenService:Create(player, TweenInfo.new(moveTime / 2, Enum.EasingStyle.Sine, Enum.EasingDirection.Out), {
			Position = midPoint
		})
		local jumpDown = tweenService:Create(player, TweenInfo.new(moveTime / 2, Enum.EasingStyle.Sine, Enum.EasingDirection.In), {
			Position = targetPosition
		})

		if connection then
			connection:Disconnect()
		end

		jumpUp:Play()
		bodyPosition.Position += cameraNextPosition

		if facingDirection ~= direction then
			local targetY
			if direction == "front" then
				targetY = 0
			elseif direction == "back" then
				targetY = -180
			elseif direction == "left" then
				targetY = 90
			elseif direction == "right" then
				targetY = -90
			end

			if targetY then
				local rotationDelta = shortestRotation(player.Orientation.Y, targetY)
				tweenService:Create(player, rotateTweenInfo, {
					Orientation = player.Orientation + Vector3.new(0, rotationDelta, 0)
				}):Play()
			end

			facingDirection = direction
		end

		jumpUp.Completed:Wait()
		jumpDown:Play()
		jumpDown.Completed:Wait()

		isMoving = false

		--raycast for if player landed on a lilypad to play a sink down and up animation
		local raycastResult = workspace:Raycast(player.Position + Vector3.new(0, 5, 0), Vector3.new(0, -10, 0), raycastParams)
		if raycastResult then
			local hitPart = raycastResult.Instance
			if hitPart and hitPart.Name == "lilypad" then
				handleLilypadInteraction(hitPart)
			end
		end

		remotes.movementEvent:FireServer(direction, player.Position)
		remotes.rotateEvent:FireServer(direction)
	else
		isMoving = false
	end
end

bodyPosition.Position = playerTorso.Position + Vector3.new(-6, 0, -24)
player.Orientation = Vector3.new(0, -180, 0)
playerHead.Orientation = Vector3.new(0, -180, 0)
playerTorso.Orientation = Vector3.new(0, -180, 0)
humanoid.WalkSpeed = 0
humanoid.JumpPower = 0

--commences the squishing
userInputService.InputBegan:Connect(function(input, gameProcessedEvent)
	if not userInputService.KeyboardEnabled then return end
	if gameProcessedEvent then return end
	if isMoving then return end

	local key = input.KeyCode
	if key == Enum.KeyCode.W or key == Enum.KeyCode.Up 
		or key == Enum.KeyCode.S or key == Enum.KeyCode.Down
		or key == Enum.KeyCode.A or key == Enum.KeyCode.Left 
		or key == Enum.KeyCode.D or key == Enum.KeyCode.Right then
		keyDownSquish(true)
	end
end)

function canMove(key)
	local now = tick()

	if moveCooldowns[key] and now < moveCooldowns[key] then
		return false
	end

	moveCooldowns[key] = now + moveCooldownTime
	return true
end

--actually moves the character
userInputService.InputEnded:Connect(function(input, gameProcessedEvent)
	if not userInputService.KeyboardEnabled then return end
	if gameProcessedEvent then return end
	if isMoving then return end

	local key = input.KeyCode
	if (key == Enum.KeyCode.W or key == Enum.KeyCode.Up) and canMove("front") then	
		keyDownSquish(false, 0, -distance)
		moveCharacter("front")
	elseif (key == Enum.KeyCode.S or key == Enum.KeyCode.Down) and canMove("down") then
		keyDownSquish(false, 0, distance)
		moveCharacter("back")
	elseif (key == Enum.KeyCode.A or key == Enum.KeyCode.Left) and canMove("left") then
		keyDownSquish(false, -distance, 0)
		moveCharacter("left")
	elseif (key == Enum.KeyCode.D or key == Enum.KeyCode.Right) and canMove("right") then
		keyDownSquish(false, distance, 0)
		moveCharacter("right")
	end
end)

--commences the squishing for mouse
mouse.Button1Down:Connect(function()
	if not userInputService.KeyboardEnabled then return end
	if isMoving then return end

	keyDownSquish(true)
end)

--releases the squishing and does movement for mouse
mouse.Button1Up:Connect(function()
	if not userInputService.KeyboardEnabled then return end
	if isMoving then return end

	if not canMove("front") then return end
	keyDownSquish(false, 0, -distance)
	moveCharacter("front")
end) 

--mobile stuff
userInputService.TouchStarted:Connect(function(input, gameProcessedEvent)
	if gameProcessedEvent then return end
	if isMoving then return end

	keyDownSquish(true)
end)

userInputService.TouchTap:Connect(function(touchPosition, gameProcessedEvent)
	if gameProcessedEvent then return end
	if isMoving then return end

	if not canMove("front") then return end
	keyDownSquish(false, 0, -distance)
	moveCharacter("front")

	remotes.movementEvent:FireServer("front")
	remotes.rotateEvent:FireServer("front")
end)

userInputService.TouchSwipe:Connect(function(input)
	if input == Enum.SwipeDirection.Up then
		keyDownSquish(false, 0, -distance)
		moveCharacter("front")
	elseif input == Enum.SwipeDirection.Down then
		keyDownSquish(false, 0, distance)
		moveCharacter("back")
	elseif input == Enum.SwipeDirection.Left then
		keyDownSquish(false, -distance, 0)
		moveCharacter("left")
	elseif input == Enum.SwipeDirection.Right then
		keyDownSquish(false, distance, 0)
		moveCharacter("right")
	end
end)

if viewGridSpace then
	local part = Instance.new("Part")
	part.Size = Vector3.new(8, 10.5, 8)
	part.Anchored = false
	part.CanCollide = false
	part.CastShadow = false
	part.Transparency = 0.7
	part.Name = "GridSpacePart"
	part.Parent = player.Parent

	local weld = Instance.new("WeldConstraint")
	weld.Part0 = player.Parent:WaitForChild("GridSpacePart")
	weld.Part1 = player
	weld.Parent = part

	RunService.Heartbeat:Connect(function()
		part.Position = player.Position
		part.CFrame = CFrame.new(part.Position) * CFrame.Angles(0, 0, 0)
	end)
end

I don’t know if I can’t understand desiredY because I can’t see anything about the attachements, while I think I understand where they are, to me it seems desiredY should be way higher than initialPlayerPositionY?
I think the issue comes somehow from the fact that moveCharacter never checks isSquished like keyDownSquish does. I don’t really see where the issue could come from the lilypad interaction, have you tried to see if you get the floating issue when jumping fast on two platforms that aren’t lilypads?

The targetPosition is based on player.Position.Y on line 162, which to me makes me think that it’s calculating the final height based on a height created from a non-squished character, which would make it end up higher than it should which fits the result of the issue. So basically due to the keyDownSquish with mode=false actually making sure that it’s squished, then I think that it makes the moveCharacter calculate height wrong.
So I would try to add the isSquished check also to moveCharacter and seeing if that fixes the floating issue (ideally after testing if you can make the bug happen without lilypads, just to see if the lilypads are the issue or not).

Another thing you can perhaps consider, is that if the map is flat then maybe removing desiredY and replacing it with always initialPlayerPositionY, then you don’t have to worry about character size having any effect on the targetPosition, and if it’s landing in the water then you just set the Y of targetPosition to be twice as low and doubling the tween’s time.

desiredY Is for getting the needed landed Y position for the player to be on rows that are of different height from the default height such as the street and railroad rows as they are a bit lower than the default grass rows (the default being initialPlayerPositionY).

The floating issue does indeed still happen regardless of if the player is on a lilypad or not. Just move one direction then quickly move in the opposite way just as the player lands, and they float. Heck this may even be an issue with how client server replication is being done right now as it is a bit rudimentary. The player right now can even walk on the water if they time their jumping right.

Im feeling a bit low on options so to help others get more of an understanding of this ill just give out the studio file. Be warned it is a bit of a mess lol.
crossyRoadCloneStudio.rbxl (110.3 KB)

1 Like