How should I make a Tower Defense type movement system?

I am currently comtemplating on how I should crate a TD-esque moment system.

First, I’m curious on what I should do to move the enemies. Should I use tweens? Should I use lerping? :confused:

Where should I be rendering the movement, on the client or server? :computer:

How can I make smooth turns? :arrow_heading_down:

Screenshot 2023-10-28 144049

Any help is greatly appreciated :smile:

3 Likes

You can create bezier curves. They’re a type of spline which only require three parts to create a smooth curve. You can use this module to create bezier curves easily.

Thanks! I am still confused mainly how I should render the movement & how I should actually move the enemy/rig. :smiley:

humanoid:MoveTo(waypoint.Position) is what you are looking for

I have heard of this, but I believe Humanoids can be a drag on performance. Thanks for the suggestion! :smile:

No, that is unoptimized and looks bad.

You can use a rendering system called client-sided rendering.

1 Like

How would I access whether an enemy or such has entered the base? Using RemoteEvents could be easily manipulated.

Would I use tables to store data about the enemy?

This is currently my script for spawning enemies :


--// Services
local S_RS = game:GetService("ReplicatedStorage")

--// Constants
local COMMON = S_RS:WaitForChild("Common")

local RESOURCES = COMMON:WaitForChild("Resources")
local ENEMIES = RESOURCES:WaitForChild("Enemies")
local MODELS = ENEMIES:WaitForChild("Models")
local CONFIG = ENEMIES:WaitForChild("Config")

local ENUMS = require(COMMON:WaitForChild("Enums"))

--// Variables
local enemies = {}

--[[PRIVATE]]--



--[[PUBLIC]]--

function ENEMY.spawn_enemy(enemy_name: string, map, count: number, interval: number, start_delay: number): ()
	
	assert(enemy_name, "Enemy name was not given, enemy name is required to spawn the enemy.")
	
	local enemy_exists = MODELS:FindFirstChild(enemy_name)
	
	assert(enemy_exists, "Enemy model was not found or not loaded.")
	assert(enemy_exists.PrimaryPart, "Enemy model does not have a PrimaryPart.")
	
	assert(count, "Count was not given, a required argument.")
	assert(interval, "Interval number was not given, a required argument.")
	assert(start_delay, "The Start Delay number was not given, a required argument.")
	assert(map, "Map was not given, a required argument.")
	
	task.wait(start_delay)
	
	for enemy_count = 1, count, 1 do
		
		local enemy_model = enemy_exists:Clone()

		local enemy_config = require(CONFIG:WaitForChild(enemy_name) or CONFIG:WaitForChild("Default"))

		assert(enemy_config, "Enemy config was not loaded or not found.")

		local enemy = {}

		enemy.HEALTH = enemy_config.HEALTH
		enemy.CONFIG = enemy_config
		enemy.ENUM = ENUMS.ENEMIES[enemy_name]

		enemy.ENEMY = enemy_model.PrimaryPart

		enemies[enemy_model] = enemy

		enemy_model.Parent = workspace:WaitForChild("Enemies")
		
		task.wait(interval)
		
	end
	
	print(enemies)
	
end

return ENEMY

This is made for the Server

1 Like

Basically what you do is you fire the properties of a table;

local table = {
["health"] = 3
["cframe"] = CFrame.new(...),
["speed"] = 3
}

and then you use a model from the client side that you clone and use those properties to change the enemy.

Oh, I understand. I think my current spawning system uses a similar idea.

You can use _G.database = {[enemies here]} on the server side.

1 Like

I’ll do some research into Client Sided rendering.

1 Like

Okay, good luck creating your future tower defense!

1 Like

Thanks for helping! I really appreciate your contribution. :smiling_face:

2 Likes

You’re welcome, good luck with your tower defense game. Also, there was somebody on YouTube doing exactly what you are looking for, you can also message him, @samuelissad, about this topic, but unfortunately stopped the videos.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.