Creating your own simple zone module!

If you don’t know anything about MetaTables and MetaMethods, I suggest you read up on articles about those:

Hey, this tutorial will teach you on how to create your own zone module!

Let’s start by creating a module script. It should look like this

local Module = {}

return Module

You can change the module name to anything you like!

Next, we need to set variables.

--// Variables

local RunService = game:GetService("RunService")
local Players = game:GetService("Players")

One of the most important things here is the __index metamethod.

module.__index = module

This will basically make it so when you index the module and it returns nil, it returns back to the module.

We also need a function to make this zone module happen!

function getPlayer(part: Model)
	return Players:GetPlayerFromCharacter(part.Parent)
end

This function will basically return the player when the first parameter is a Character model.

We need to create a function within the module which will create the zone.

function module.new(Container: Part)

end

The container parameter is basically the whole base. Its the whole zone which we will use to detect Touches.

function module.new(Container: Part)
	local self = setmetatable({}, ZoneModule)

	return self
end
function module.new(Container: Part)
	local self = setmetatable({}, ZoneModule)
	self.Name = Container.Name
	self.CFrame = Container.CFrame
end

We created properties within self (the table) which will help us creating Touched Events and stuff like that.

We also need BindableEvents to create events like Zone.Touched

	self.PlayerEnteredEvent = Instance.new("BindableEvent")
	self.PlayerLeftEvent = Instance.new("BindableEvent")
	self.TouchedEvent = Instance.new("BindableEvent")

	self.PlayerEntered = self.PlayerEnteredEvent.Event
	self.Touched = self.TouchedEvent.Event

Lets create our touched event!

RunService.Stepped:Connect(function()
	local PlayersInZone = {}

	local PartsInZone = workspace:GetPartBoundsInBox(self.CFrame, self.Size)
end)

GetPartBoundsInBox will basically detect what parts are in a certain part, which will be really handy.

We also need a line of code to stop the function if there is nothing in the box.

if #PartsInZone == 0 then return end

Then, we need to loop through all the parts in the box

for _, part in pairs(PartsInZone) do
	if part.Name ~= self.Name then -- Checks if the part in the box is not the container part.
		self.TouchedEvent:Fire(part) -- Fires the bindable event when a part touches the zone.
	end
end

Now, we need to make the player entered event work.

local PlayerInBox = getPlayer(part.Parent)

if PlayerInBox then
	if PlayersInZone[PlayerInBox] then return end -- If theres already a player in the zone then stop the function

	PlayersInZone[PlayerInBox] = PlayerInBox
	self.PlayerEnteredEvent:Fire(PlayerInBox)
end

That’s basically it on creating the module! The full script should look like this:

local ZoneModule = {}

ZoneModule.__index = ZoneModule

local RunService = game:GetService("RunService")
local Players = game:GetService("Players")

function getPlayer(part: Model)
	return Players:GetPlayerFromCharacter(part.Parent)
end

ZoneModule.new = function(Container: Part)
	local self = setmetatable({}, ZoneModule)
	
	self.Name = Container.Name
	self.CFrame = Container.CFrame
	self.PlayerEnteredEvent = Instance.new("BindableEvent")
	self.TouchedEvent = Instance.new("BindableEvent")
	
	self.PlayerEntered = self.PlayerEnteredEvent.Event
	self.PlayerLeft = self.PlayerLeftEvent.Event
	self.Touched = self.TouchedEvent.Event
	

	local PlayersInZone = {}



	RunService.Stepped:Connect(function()
		local PartsInZone = workspace:GetPartBoundsInBox(self.CFrame, self.Size)
		
		if #PartsInZone == 0 then return end
		
		for _, part: Instance in (PartsInZone) do
			if part.Name ~= self.Name then
				self.TouchedEvent:Fire(part)
				local PlayerInBox = getPlayer(part.Parent)



				if PlayerInBox then
					if PlayersInZone[PlayerInBox] then return end
					PlayersInZone[PlayerInBox] = PlayerInBox
					self.PlayerEnteredEvent:Fire(PlayerInBox)
				end
			end
		end
	end)
	
	return self
end

This is my first tutorial ever. Tips would really help me learn, so if you have any please type them below.

3 Likes

No offense but reading the title sounds like you are reinventing ZonePlus

Here’s some tips

self.PlayerEnteredEvent should be self._playerEntered
self.TouchedEvent should be self._touched

1 Like

I would recommend changing it from RunService event bind to a task.wait(...) loop, you dont really need that much speed in a zone module.

3 Likes

It would be better to do something like this:

local TimePool = 0
local UpdateDelay = 0.1

RunService.Heartbeat:Connect(function(DeltaTime)
 TimePool += DeltaTime
 If TimePool < UpdateDelay then
  return
 else
  TimePool = 0
 end

 ...
end)
1 Like