Skip to content

Commit

Permalink
Merge branch 'development' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
1ForeverHD committed Mar 5, 2021
2 parents 7fd42a8 + 8076ef5 commit ae5a6a5
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 32 deletions.
12 changes: 12 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
## [2.1.0] - March 5 2021
### Added
- Detection Enum
- ``zone.enterDetection``
- ``zone.exitDetection``
- ``zone:setDetection(enumItemName)``
- An Optimisation section to Introduction



--------

## [2.0.0] - January 19 2021
### Added
- Non-player part checking! (see methods below)
Expand Down
61 changes: 59 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
[BasePart.CanTouch]: https://developer.roblox.com/en-us/api-reference/property/BasePart/CanTouch
[baseparts]: https://developer.roblox.com/en-us/api-reference/class/BasePart
[zone]: https://1foreverhd.github.io/ZonePlus/zone/
[Zone module docs]: https://1foreverhd.github.io/ZonePlus/zone/
[Zone API]: https://1foreverhd.github.io/ZonePlus/zone/
[Accuracy Enum]: https://github.com/1ForeverHD/ZonePlus/blob/main/src/Zone/Enum/Accuracy.lua
[Detection Enum]: https://github.com/1ForeverHD/ZonePlus/blob/main/src/Zone/Enum/Detection.lua

## Summary

ZonePlus is a module enabling the construction of dynamic zones. These zones utilise region checking, raycasting and the new [BasePart.CanTouch] property to effectively determine players and parts within their boundaries.

Expand Down Expand Up @@ -39,11 +43,64 @@ end)
!!! info
On the client you may only wish to listen for the LocalPlayer (such as for an ambient system). To achieve this you would alternatively use the ``.localPlayer`` events.

!!! important
Zone group parts must remain within the workspace for zones to fully work

If you don't intend to frequently check for items entering and exiting a zone, you can utilise zone methods:

```lua
local playersArray = zone:getPlayers()
```

Discover the full set of methods, events and properties at the [Zone module docs].
Discover the full set of methods, events and properties at the [Zone API].

----

## Optimisations
Zones by default perform up to 10 checks per second around a *whole* players character. This behaviour can be changed by modifying the **Accuracy** and **Detection** of zones:

### Accuracy
This determines the *frequency* of checks per second.

The accuracy of a zone can be changed two ways with a corresponding [Accuracy Enum]:

1. Using the ``zone:setAccuracy(itemName)`` method:
```lua
zone:setAccuracy("High")
```

2. Setting the ``zone.accuracy`` property:
```lua
zone.accuracy = Zone.enum.Accuracy.High
```

By default accuracy is ``High``.

!!! info
Modifying the accuracy of one zone may impact the accuracy of another due to the modules collaborative nature.


### Detection
This determines the *precision* of checks.

The way a zone detects players and parts can be changed two ways with a corresponding [Detection Enum]:

1. Using the ``zone:setDetection(itemName)`` method:
```lua
zone:setDetection("Automatic")
```

2. Setting the ``zone.enterDetection`` and ``zone.exitDetection`` properties:
```lua
zone.enterDetection = Zone.enum.Detection.Automatic
zone.exitDetection = Zone.enum.Detection.Automatic
```

By default enterDetection and exitDetection are ``Automatic``.

!!! info
Modifying the detection of one zone may impact the detection of another due to the modules collaborative nature.

!!! warning
Setting ``enterDetection`` to (``Zone.enum.Detection.WholeBody`` or ``Zone.enum.Detection.Automatic``) and ``exitDetection`` to ``Zone.enum.Detection.Centre`` will cause the entered and exit events to trigger rapidly when the player lies on the bounds of the zone.

8 changes: 8 additions & 0 deletions src/Zone/Enum/Detection.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- Important note: Precision checks currently only for 'players' and the 'localplayer', not 'parts'.

-- enumName, enumValue, additionalProperty
return {
{"Automatic", 1}, -- ZonePlus will dynamically switch between 'WholeBody' and 'Centre' depending upon the number of players in a server (this typically only occurs for servers with 100+ players when volume checks begin to exceed 0.5% in script performance).
{"Centre", 2}, -- A tiny lightweight Region3 check will be casted at the centre of the player of part
{"WholeBody", 3}, -- A RotatedRegion3 check will be casted over a player or parts entire body
}
7 changes: 4 additions & 3 deletions src/Zone/Enum/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,10 @@ function Enum.createEnum(enumName, details)
assert(typeof(not enumMetaFunctions[name]), ("bad argument #2.%s.1 - that name is reserved."):format(i, name))
usedNames[tostring(name)] = i
local value = detail[2]
assert(typeof(value) == "number" and math.ceil(value)/value == 1, ("bad argument #2.%s.2 - detail value must be an integer!"):format(i))
assert(typeof(not usedValues[value]), ("bad argument #2.%s.2 - the detail value '%s' already exists!"):format(i, value))
usedValues[tostring(value)] = i
local valueString = tostring(value)
--assert(typeof(value) == "number" and math.ceil(value)/value == 1, ("bad argument #2.%s.2 - detail value must be an integer!"):format(i))
assert(typeof(not usedValues[valueString]), ("bad argument #2.%s.2 - the detail value '%s' already exists!"):format(i, valueString))
usedValues[valueString] = i
local property = detail[3]
if property then
assert(typeof(not usedProperties[property]), ("bad argument #2.%s.3 - the detail property '%s' already exists!"):format(i, tostring(property)))
Expand Down
80 changes: 67 additions & 13 deletions src/Zone/ZoneController.lua
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ local runService = game:GetService("RunService")
local heartbeat = runService.Heartbeat
local heartbeatConnections = {}
local localPlayer = runService:IsClient() and players.LocalPlayer
local playerExitDetections = {}
local WHOLE_BODY_DETECTION_LIMIT = 729000 -- This is roughly the volume where Region3 checks begin to exceed 0.5% in Script Performance



Expand All @@ -85,12 +87,12 @@ local function fillOccupants(zonesAndOccupantsTable, zone, occupant)
end

local heartbeatActions = {
["player"] = function()
return ZoneController._getZonesAndPlayers(activeZones, activeZonesTotalVolume, true)
["player"] = function(recommendedDetection)
return ZoneController._getZonesAndPlayers(activeZones, activeZonesTotalVolume, true, recommendedDetection)
end,
["localPlayer"] = function()
["localPlayer"] = function(recommendedDetection)
local zonesAndOccupants = {}
local touchingZones = ZoneController.getTouchingZones(localPlayer, true)
local touchingZones = ZoneController.getTouchingZones(localPlayer, true, recommendedDetection)
for _, zone in pairs(touchingZones) do
if zone.activeTriggers["localPlayer"] then
fillOccupants(zonesAndOccupants, zone, localPlayer)
Expand Down Expand Up @@ -151,6 +153,7 @@ players.PlayerAdded:Connect(function(plr)
end)
players.PlayerRemoving:Connect(function(plr)
updateCharactersTotalVolume()
playerExitDetections[plr] = nil
end)


Expand Down Expand Up @@ -191,6 +194,26 @@ function ZoneController._registerConnection(registeredZone, registeredTriggerTyp
end
end

-- This decides what to do if detection is 'Automatic'
-- This is placed in ZoneController instead of the Zone object due to the ZoneControllers all-knowing group-minded logic
function ZoneController.updateDetection(zone)
local detectionTypes = {
["enterDetection"] = "_currentEnterDetection",
["exitDetection"] = "_currentExitDetection",
}
for detectionType, currentDetectionName in pairs(detectionTypes) do
local detection = zone[detectionType]
if detection == enum.Detection.Automatic then
if charactersTotalVolume > WHOLE_BODY_DETECTION_LIMIT then
detection = enum.Detection.Centre
else
detection = enum.Detection.WholeBody
end
end
zone[currentDetectionName] = detection
end
end

function ZoneController._formHeartbeat(registeredTriggerType)
local heartbeatConnection = heartbeatConnections[registeredTriggerType]
if heartbeatConnection then return end
Expand All @@ -205,16 +228,22 @@ function ZoneController._formHeartbeat(registeredTriggerType)
local clockTime = os.clock()
if clockTime >= nextCheck then
local lowestAccuracy
local lowestDetection
for zone, _ in pairs(activeZones) do
if zone.activeTriggers[registeredTriggerType] then
local zAccuracy = zone.accuracy
if lowestAccuracy == nil or zAccuracy < lowestAccuracy then
lowestAccuracy = zAccuracy
end
ZoneController.updateDetection(zone)
local zDetection = zone._currentEnterDetection
if lowestDetection == nil or zDetection < lowestDetection then
lowestDetection = zDetection
end
end
end
local highestAccuracy = lowestAccuracy
local zonesAndOccupants = heartbeatActions[registeredTriggerType]()
local zonesAndOccupants = heartbeatActions[registeredTriggerType](lowestDetection)
for zone, _ in pairs(activeZones) do
if zone.activeTriggers[registeredTriggerType] then
local zAccuracy = zone.accuracy
Expand Down Expand Up @@ -281,7 +310,7 @@ function ZoneController._updateZoneDetails()
end
end

function ZoneController._getZonesAndPlayers(zonesDictToCheck, zoneCustomVolume, onlyActiveZones)
function ZoneController._getZonesAndPlayers(zonesDictToCheck, zoneCustomVolume, onlyActiveZones, recommendedDetection)
local totalZoneVolume = zoneCustomVolume
if not totalZoneVolume then
for zone, _ in pairs(zonesDictToCheck) do
Expand All @@ -295,7 +324,7 @@ function ZoneController._getZonesAndPlayers(zonesDictToCheck, zoneCustomVolume,
-- then it's more efficient cast regions and rays within each character and
-- then determine the zones they belong to
for _, plr in pairs(players:GetPlayers()) do
local touchingZones = ZoneController.getTouchingZones(plr, onlyActiveZones)
local touchingZones = ZoneController.getTouchingZones(plr, onlyActiveZones, recommendedDetection)
for _, zone in pairs(touchingZones) do
if not onlyActiveZones or zone.activeTriggers["player"] then
fillOccupants(zonesAndOccupants, zone, plr)
Expand Down Expand Up @@ -363,8 +392,19 @@ function ZoneController.getCharacterRegion(player)
return charRegion, regionCFrame, charSize
end

function ZoneController.getTouchingZones(player, onlyActiveZones)
local charRegion = ZoneController.getCharacterRegion(player)
function ZoneController.getTouchingZones(player, onlyActiveZones, recommendedDetection)
local exitDetection = playerExitDetections[player]
playerExitDetections[player] = nil
local finalDetection = exitDetection or recommendedDetection
local charRegion
if finalDetection == enum.Detection.WholeBody then
charRegion = ZoneController.getCharacterRegion(player)
else
local char = player.Character
local hrp = char and char:FindFirstChild("HumanoidRootPart")
local regionCFrame = hrp and hrp.CFrame
charRegion = regionCFrame and RotatedRegion3.new(regionCFrame, Vector3.new(0.1, 0.1, 0.1))
end
if not charRegion then return {} end
--[[
local part = Instance.new("Part")
Expand All @@ -384,10 +424,17 @@ function ZoneController.getTouchingZones(player, onlyActiveZones)
local hrp = player.Character.HumanoidRootPart
local hrpCFrame = hrp.CFrame
local hrpSizeX = hrp.Size.X
local pointsToVerify = {
(hrpCFrame * CFrame.new(-hrpSizeX, 0, 0)).Position,
(hrpCFrame * CFrame.new(hrpSizeX, 0, 0)).Position,
}
local pointsToVerify
if finalDetection == enum.Detection.WholeBody then
pointsToVerify = {
(hrpCFrame * CFrame.new(-hrpSizeX, 0, 0)).Position,
(hrpCFrame * CFrame.new(hrpSizeX, 0, 0)).Position,
}
else
pointsToVerify = {
hrp.Position,
}
end
if not ZoneController.verifyTouchingParts(pointsToVerify, parts) then
return {}
end
Expand All @@ -398,9 +445,16 @@ function ZoneController.getTouchingZones(player, onlyActiveZones)
zonesDict[correspondingZone] = true
end
local touchingZonesArray = {}
local newExitDetection
for zone, _ in pairs(zonesDict) do
if newExitDetection == nil or zone._currentExitDetection < newExitDetection then
newExitDetection = zone._currentExitDetection
end
table.insert(touchingZonesArray, zone)
end
if newExitDetection then
playerExitDetections[player] = newExitDetection
end
return touchingZonesArray
end

Expand Down
Loading

0 comments on commit ae5a6a5

Please sign in to comment.