Tower Defense || Issue with Movement Between Waypoints || Help

  1. What do you want to achieve?
  • I want to resolve an issue with the movement between waypoints.
  1. What is the issue?
  • Sometimes, when it reach the end of a waypoint, it get teleportation-like behavior, which is unintended and affects the smoothness of the movement.
  1. What solutions have you tried so far?
  • I’ve attempted to address the issue by adjusting the movement and waypoint logic, but the problem persists. I’ve also looked for solutions on the Devforum without success.

Description:

  • The movement issue occurs when it reach the end of a waypoint. Instead of smoothly transitioning to the next waypoint, it seem to teleport back a bit.
External Media

My localscript

-- local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")

local player = game.Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui")
local pokemonsData = {}

local pokemonsDataEvent = ReplicatedStorage:WaitForChild("PokemonsData")
local pokemonModels = ReplicatedStorage:WaitForChild("Pokemons") -- Assuming you have a folder with Pokemon models

local function tweenPokemon(pokemonId, pokemonName, startPosition, endPosition, speed)
	local pokemonModel = pokemonModels:FindFirstChild(pokemonName)

	if pokemonModel then
		if not workspace:WaitForChild("PokemonFolder"):FindFirstChild(pokemonId) then
			local clonedPokemon = pokemonModel:Clone()
			clonedPokemon.Name = pokemonId -- Name the cloned Pokemon using the pokemonId
			clonedPokemon.Parent = workspace:WaitForChild("PokemonFolder") -- Assuming you have a folder to hold the Pokemon models
			clonedPokemon.PrimaryPart.Position = startPosition
		end
		local pokemon = workspace:WaitForChild("PokemonFolder"):FindFirstChild(pokemonId)
		local primaryPart = pokemon.PrimaryPart

		local direction = (endPosition - startPosition).Unit
		local lookAt = CFrame.new(primaryPart.Position, primaryPart.Position + direction)

		local tweenInfoPos = TweenInfo.new((endPosition - primaryPart.Position).Magnitude / speed, Enum.EasingStyle.Linear)
		local tweenInfoRota = TweenInfo.new((endPosition - primaryPart.Position).Magnitude / speed * 2, Enum.EasingStyle.Linear)
		
		local positionGoal = {}
		positionGoal.Position = endPosition

		local rotationGoal = {}
		rotationGoal.CFrame = lookAt

		local positionTween = TweenService:Create(primaryPart, tweenInfoPos, positionGoal)
		local rotationTween = TweenService:Create(primaryPart, tweenInfoRota, rotationGoal)

		positionTween:Play()
		rotationTween:Play()
	end
end



pokemonsDataEvent.OnClientEvent:Connect(function(updatedPokemonsData)
	pokemonsData = updatedPokemonsData

	for _, data in pairs(pokemonsData) do
		if data.Health <= 0 then
			local deadPokemon = workspace.PokemonFolder:FindFirstChild(data.pokemonId)
			if deadPokemon then
				deadPokemon:Destroy()
			end
		else
			tweenPokemon(data.pokemonId, data.Name, data.StartPos, data.EndPos, data.Speed)
		end
	end
end)

ModuleScript

-- 
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Pokemons = ReplicatedStorage.Pokemons
local RunService = game:GetService("RunService")

local WaveManager = {}

function WaveManager.new(map, difficulty)
	local self = {}

	local waveData = require(game.ServerScriptService.WaveData)
	local PokemonStatsEnemy = require(game.ServerScriptService.PokemonStatsEnemy)


	local waveInfo = {
		Easy = {waves = 10, coinMultiplier = 10, healthMultiplier = 1},
		Normal = {waves = 20, coinMultiplier = 25, healthMultiplier = 1.5},
		Hard = {waves = 30, coinMultiplier = 50, healthMultiplier = 2.5},
		Expert = {waves = 50, coinMultiplier = 100, healthMultiplier = 5},
		Nightmare = {waves = 70, coinMultiplier = 250, healthMultiplier = 15},
		Impossible = {waves = 100, coinMultiplier = 500, healthMultiplier = 50}
	}

	local currentWave = 0
	local pokemonsData = {}
	
	local uniqueIdCounter = 0 -- Unique identifier counter

	local function generateUniqueId()
		uniqueIdCounter = uniqueIdCounter + 1
		return uniqueIdCounter
	end

	local function updatePokemonPosition(pokemonId, currentWaypoint, elapsedTime, speed)
		local waypoints = workspace.Path.Waypoints

		local startPosition = waypoints[currentWaypoint].Position
		local endPosition = waypoints[currentWaypoint + 1].Position
		local totalDistance = (endPosition - startPosition).Magnitude

		local Speed = totalDistance / speed --pokemon.PrimaryPart.WalkSpeed
		local progress = elapsedTime / Speed

		if progress <= 1 then
			local newPosition = startPosition:Lerp(endPosition, progress)
			local pokemonData = pokemonsData[pokemonId]
			if pokemonData then
				pokemonData.StartPos = startPosition
				pokemonData.EndPos = newPosition
			end
		end
	end

	local function moveToNextWaypoint(pokemonId, pokemon, currentWaypoint, speed)
		local waypoints = workspace.Path.Waypoints
		local startTime = tick()

		if currentWaypoint <= #waypoints:GetChildren() - 1 then
			local elapsedTime = 0

			while elapsedTime <= (waypoints[currentWaypoint + 1].Position - waypoints[currentWaypoint].Position).Magnitude / speed do
				elapsedTime = tick() - startTime
				updatePokemonPosition(pokemonId, currentWaypoint, elapsedTime, speed)
				wait()
			end

			print(pokemon .. ' reached waypoint ' .. currentWaypoint)
			moveToNextWaypoint(pokemonId, pokemon, currentWaypoint + 1, speed)
		else
			print(pokemon .. ' reached the end of waypoints')
		end
	end

	local function spawnPokemon(pokemonName, parentFolder)
		if not parentFolder then
			parentFolder = workspace:FindFirstChild("PokemonFolder")
			if not parentFolder then
				parentFolder = Instance.new("Folder")
				parentFolder.Name = "PokemonFolder"
				parentFolder.Parent = workspace
			end
		end

		local pokemon = Pokemons:FindFirstChild(pokemonName)

		if pokemon and parentFolder then
			local startPosition = workspace.Path.Waypoints[1].CFrame.Position
			local maxHealth = PokemonStatsEnemy[pokemonName].maxHealth
			local speed = PokemonStatsEnemy[pokemonName].speed
			local uniqueId = generateUniqueId()
			local pokemonId = pokemonName .. "_" .. uniqueId
			local pokemonData = {Name = pokemonName, pokemonId = pokemonId, StartPos = startPosition, EndPos = startPosition, Health = maxHealth, maxHealth = maxHealth, Speed = speed}

			pokemonsData[pokemonId] = pokemonData  -- Use the Pokemon's name as the key
			moveToNextWaypoint(pokemonId, pokemonName, 1, speed) -- Start moving the Pokemon
		end
	end


	local function spawnWave()
		currentWave = currentWave + 1

		local mapData = waveData[map]

		if mapData then
			local difficultyData = mapData[difficulty]
			if difficultyData then
				local currentWaveData = difficultyData[currentWave]
				if currentWaveData then
					print("Wave " .. currentWave .. " on " .. map .. " has started!")
					for _, pokemonData in ipairs(currentWaveData) do
						for _ = 1, pokemonData.Quantity do
							spawnPokemon(pokemonData.Pokemon)
							wait(0.5)
						end
					end
				else
					print("No more waves for difficulty: " .. difficulty)
				end
			else
				warn("Invalid difficulty level.")
				return
			end
		else
			warn("Invalid map.")
			return
		end
	end

	local function rewardPlayer()
		local rewardCoins = waveInfo[difficulty].coinMultiplier * currentWave
		print("Wave " .. currentWave .. " cleared! You earned total " .. rewardCoins .. " coins.")
	end

	function self.startWaves()
		for wave = 1, waveInfo[difficulty].waves do
			spawnWave()

			repeat
				wait(.1)
			until #workspace:FindFirstChild("PokemonFolder"):GetChildren() == 0

			rewardPlayer()

			print('Starting next wave in: 3')
			wait(1)
			print('Starting next wave in: 2')
			wait(1)
			print('Starting next wave in: 1')
			wait(1)
		end

		local rewardCoins = waveInfo[difficulty].coinMultiplier * currentWave * 10
		print("All waves cleared! You earned total " .. rewardCoins .. " coins.")
	end

	local count = 0
	RunService.Heartbeat:Connect(function()
		count = count + 1
		if count >= 10 then
			count = 0
			if next(pokemonsData) then
				print(pokemonsData['Bulbasaur_1'].StartPos, pokemonsData['Bulbasaur_1'].EndPos)
				ReplicatedStorage.PokemonsData:FireAllClients(pokemonsData)
			end
		end
	end)

	return self
end

return WaveManager

I think is maby because updatePokemonPosition in the ModuleScript.

local function updatePokemonPosition(pokemonId, currentWaypoint, elapsedTime, speed)
	local waypoints = workspace.Path.Waypoints

	local startPosition = waypoints[currentWaypoint].Position
	local endPosition = waypoints[currentWaypoint + 1].Position
	local totalDistance = (endPosition - startPosition).Magnitude

	local Speed = totalDistance / speed --pokemon.PrimaryPart.WalkSpeed
	local progress = elapsedTime / Speed

	if progress <= 1 then
		local newPosition = startPosition:Lerp(endPosition, progress)
		local pokemonData = pokemonsData[pokemonId]
		if pokemonData then
			pokemonData.StartPos = startPosition
			pokemonData.EndPos = newPosition
		end
	end
end

Nvm i found out it was just the delay on 0.1sec from server to client so i just make so server have approximate pos of the enemy and then make so the client tween without the server needs :slight_smile:

not 100% the way i want it, but it work i guess.