VoxelDestruct | Voxelated destruction physics with greedy meshing, hitboxes, and more!

Ever wanted customizable, optimal voxel destruction? Try VoxelDestruct!

| Download Model |

VoxelDestruct is a voxelating destruction tool created for developers to add destruction to their games in the most performance friendly way using greedy meshing, part caching, a splitting algorithm, and queueing.

Features

  • Single destruction & OOP hitbox destruction

  • Greedy meshing

  • Function queueing

  • Voxelization

  • Destruction states with preset animations and materials support, (Cracked, shatter, protrude)

| Brief Showcase |

Credit is not required from me but you should always consider giving credit to these contributors out of respect.

This tool wouldn’t have been possible without the Splitting Algorithm from @EgoMoose , Non-Voxel Greedy Mesher from @RoyalTHEUSERNAME , PartCache from @Xan_TheDragon , and inspiration from VoxBreaker made by @Bartokens

A huge thank you to these developers for their contributions!

This is my first community resource so be patient with me about missing information. I consider all suggestions and questions about usage, please don’t hesitate to @ me with any suggestions, questions, or concerns you may have!

Suggestions & Future Updates?

  • Support for wedge parts

  • Chunk generator

  • Welding touching parts, converting disconnected fragments into debris

  • Debris animation presets

  • Destruction states with preset animations and materials support, (Cracked, shatter, protrude)

  • Part durability and collision damage

Methodology & Concepts

(This section is optional but I highly recommend you read it anyways to get a better understanding of the controls)

Before introducing the methods and settings we need to understand what concepts are applied to the parameters and settings at your disposal.

Here is the approach this module uses:

  1. Get a bounding box oriented with your wall but surrounding your hitbox region/part, it should engulf the part while having a rotation of your wall.

  2. Slice the wall using all sides of the bounding box that are within your wall, each slice will give you the part that you will then use the next slice on as well as an extra part that will remain as a part of your wall.

  3. You will be left with a part representing the intersection of your bounding box with the wall and at most six extra pieces(one for every face of a cuboid) that belong to your wall. Take your intersection piece and voxelize it.

  4. Resize your region to get the voxels you would like to turn into debris, get the voxels touching this resized region and make them debris.

  5. Greedy mesh everything except for your debris.

  6. For another hole repeat the first five steps on the wall pieces gotten from step 2. This entire process should be wrapped in a queue so that destruction does not occur simultaneously on a single part or issue would occur.

Region/Hitbox part (green)
Bounding box (black)
Wall parts (orange / blue / pink / yellow)
Intersection part (white)
Voxels (red)
Debris region (purple)
image

Now to go over concepts-

Our first concept is a simple one, Relativity. Relativity is optional and can be applied to voxel size to avoid having extreme detail for large hitboxes (as too much detail would cause lag due to high part count). Imagine having a large hitbox with very small voxels, this would lead to a high part count even with greedy meshing. Relativity prevents this issue by resizing your voxels based off of your region’s size.

Relativity is simple to understand but things get complicated when we consider that your hitbox has a distance parameter which specifies how far the hitbox must travel before it destroys again, and that is to prevent your hitbox from firing constantly.

So you may be asking, why shouldn’t a hitbox destroy everything at every position? And the reason for that is you will have insignificant detail which is caught by greedy meshing but slows down the meshing process causing flickering in the visibility of your wall.

image

image
(travel distance 0)

Specifying a distance works to combat this but we still wind up with an unnecessary amount of parts.

image
(travel distance 1)

We can take it even further and apply relativity to it by having the distance parameter be nil, this is meant to optimize large hitboxes. If your distance parameter is nil then your voxelSize parameter will be used instead. In the case that you pass voxelSize as nil your hitbox will not return debris but will return the entire intersection of your region and the wall, in this special scenario your voxel size cannot be used so relativity is applied using the hitboxRelative setting.

image
(Distance of nil, relative voxel size)

By passing distance as nil and voxelSize as 0 we have applied relativity to our hitbox’s distance as well as our voxelSize.

That’s not confusing right? >_> In case you didn’t understand…
Here is the order for what is used as your hitbox’s distance limitation IF the previous value was nil:
distance parameter > voxelSize parameter > hitboxRelative setting

Making Destructible Parts

To make a destructible part you need to add a boolean attribute to your part named after your attribute setting, by default the name is “Breakable”.

When your destructible part is first destroyed you will get 4 extra attributes that you can edit with your scripts or change through parameters. These extra attributes are named after your attribute setting and are necessary for cross-collision interaction.

image

The BreakableDestroy attribute decides whether or not the wall will be deleted after the timer runs out. This attribute can be updated with the destroy parameter.

The BreakableFreeze attribute set to true will yield the timer until it is set back to false. This attribute can be updated with the freeze parameter.

The BreakableLocked attribute set to true will prevent the wall from being broken again. This attribute can be updated with the lock parameter.

The BreakableTimer attribute is the countdown timer until the wall either repairs itself or destroyed if the BreakableDestroy attribute is true. This attribute can be updated with the reset parameter.

Methods

Require the module to begin using methods. You can get the stored part cache with vox.Cache

local vox = require(game:GetService("ReplicatedStorage"):WaitForChild("VoxelDestruction"))
local cache = vox.Cache -- If you ever need it for checking if parts belong to this module? 
.destroy()
function breaker.destroy(
cframe: CFrame, 
size: Vector3, 
shape: Enum.PartType?,  
parameters: OverlapParams?,
voxelSize: number?, 
debrisCount: number?, 
cutoutSize: number?, 
gridLock: boolean?,
reset: number?,
freeze: boolean?,
destroy: boolean?,
lock: boolean?
)

Used for singular destruction.

  • Returns a table of debris and a table of affected walls.
  • The cframe and size parameters are required but everything else is optional with defaults, some defaults are applied through settings.
cframe: CFrame

The CFrame of your region.

size: Vector3

The Vector3 size of your region.

shape: Enum.PartType?

The shape of your region.

  • If passed as nill will default to shapeDefault setting
parameters: OverlapParams?

The parameters for whitelisting or blacklisting destructible parts.
Example:

local params = OverlapParams.new()
params.FilterType = Enum.RaycastFilterType.Include -- .Exclude for BlackList
params.FilterDescendantsInstances = {game.Workspace.Baseplate}
voxelSize: number?

The detail-providing voxel size of your destruction.

  • If passed as nil your intersections are not voxelized and are returned instead of voxels
  • If passed as 0 and useRelativity setting is true then relativity is applied
  • If passed as 0 and useRelativity setting is false then voxelDefault setting is applied
debrisCount: number?

The quantity of debris produced.

  • If passed as nil all the debris will be returned
cutoutSize: number?

The size of the region getting debris relative to the original size of your region. This is to provide more detail for non-cuboid shaped regions, a promising value seems to be 3/4ths.

  • If passed as nil will default to cutoutDefault setting
  • Should be a float - a number between 0 and 1
  • *Check the diagram in *Methodology & Concepts
gridLock: boolean?

Determines whether or not parameters will be rounded to the nearest unit intended to produce a uniform unit detail to your destructions.

  • If passed as nil will default to gridLockDefault setting
  • Will round the cframe parameter, size parameter, and voxelSize parameter to the nearest unit
reset: number?

The timer until a destructible part is cleaned up, reverted, or destroyed if your BreakableDestroy attribute is true.

  • If passed as nil the destroy parameter will not update the current BreakableTimer attribute
freeze: boolean?

Whether or not the current timer will yield and resume when the attribute BreakableFreeze becomes true.

  • If passed as nil the freeze parameter will not update the current BreakableFreeze attribute
destroy: boolean?

Whether or not the destructible part will be destroyed or cleaned up when the BreakableTimer attribute becomes 0.

  • If passed as nil the destroy parameter will not update the current BreakableDestroy attribute
lock: boolean?

Whether or not the destructible part is locked from further destruction until the BreakableLocked attribute becomes false.

  • If passed as nil the lock parameter will not update the current BreakableLocked attribute
  • Intended to be used alongside the destroy parameter to destructible parts cannot be updated before being removed
.hitbox()

Used to create hitboxes from parts or models and allows runtime editting.

  • Models for hitboxes must have a primary part set or one will be randomly assigned, changes in any of the part’s cframe, size, or shape will fire the hitbox
  • Models for hitboxes are not limited to being flat models - models which only consist of parts and have no difference in hierarchy (no nested parts within parts). They can have nested parts and any changes to any descendant’s regions will fire the hitbox.
function breaker.hitbox(
focus: Instance, 
limit: number?, 
auto: boolean?,  
revert: boolean?,
distance: number?,
parameters: OverlapParams?,
voxelSize: number?, 
debrisCount: number?, 
cutoutSize: number?, 
gridLock: boolean?,
reset: number?,
freeze: boolean?,
destroy: boolean?,
lock: boolean?
)
Controls
hitbox:Start()

Starts your hitbox.

  • Your hitbox can be restarted by calling :Start() while it is active without needing to call :Stop() first. It will stop itself then start again.
  • Will fire the hitbox.Started:Connect() event
hitbox:Stop()

Stops your hitbox.

  • Will disconnect connections associated with all parts in the assembly and reset the hitbox.collisions data to 0 if the hitbox.revert parameter is true
  • Will fire the hitbox.Stopped:Connect() event passing the present debris and affected destructible parts from the hitboxes runtime from start to stop
hitbox:Fire()

Will activate the hitbox once.

  • If destructible parts are detected it will fire the hitbox.Collision:Connect() event passing the collision’s debris and affected parts
hitbox:GetRuntimeParts()

Will return the all the created debris and affected destructible parts from the hitbox’s runtime between when it was started and stopped.

hitbox:GetLifetimeParts()

Will return the all the created debris and affected destructible parts from the hitbox’s runtime between when it was started and stopped.

Runtime Updating

Hitboxes store your original parameters, you are able to update these parameters without stopping and restarting the hitbox to see their effects. All parameters passed to the .hitbox() are retrievable and updatable like this. To update a hitbox’s stored data get the returned object and the parameter name inside of it like so:

local hitbox = vox.hitbox(focus, limit, auto, distance, parameters, voxelSize, debrisCount, cutoutSize, gridLock, reset) -- Whatever your parameters are?

hitbox.reset = 60 -- Update the reset timer
hitbox.voxelSize = 5 -- Update the voxel size
hitbox.gridLock = true -- Update grid lock
Unique Parameters

The following parameters are unique to the .hitbox() method.

focus: Instance

The part or model the hitbox is focused on.

  • If O is the entire operation of the destruction algorithm then your runtime based off of part count is linear, O(n). Keep your models within a realistic amount of detail to avoid slowing the queue.
limit: number?

The ceiling for the number of collisions a hitbox can handle.

  • If passed as 0 then there is no limit for the amount of collisions a hitbox can handle
  • Defaults to 0
auto: boolean?

Whether or not the hitbox will stop itself after reaching the limit ceiling.

  • Defaults to true
  • Intended to allow hitboxes to have penetration depth into wide parts when used in conjunction with the limit parameter and to make stopping the hitbox optional
revert: boolean?

Whether or not the hitbox’s collision counter will be reset to 0 every time the hitbox is stopped.

  • Defaults to false
distance: number?

The distance that the hitbox has to travel before it fires again.

  • If passed as nil will apply the voxelSize parameter, if the voxelSize parameter is nil then hitboxRelative setting is applied. The useRelativity setting does not have to be true for relativity to be applied to a hitbox’s distance
  • Check Methodology & Concepts for more information on how this works and why it is important
Inherited Parameters

All of these parameters are inherited from the .destroy() method, check the method description above for more information on their use.

parameters: OverlapParams?,
voxelSize: number?, 
debrisCount: number?, 
cutoutSize: number?, 
gridLock: boolean?,
reset: number?,
freeze: boolean?,
destroy: boolean?,
lock: boolean?
Read Only Data

Hitboxes contain various read-only (intended use) properties but you can still use them if you understand them. The only one you will likely use frequently is hitbox.collisions = 0 to reset the collisions.

  • The hitbox.runDebris, hitbox.totalDebris, hitbox.runWalls, and hitbox.totalWalls data are intended for storage, to retrieve these outside of events use the :GetRuntimeParts() and :GetLifetimeParts() methods
hitbox.queue = nil
hitbox.collisions = 0
hitbox.connections = {}
hitbox.runDebris = {}
hitbox.totalDebris = {}
hitbox.runWalls = {}
hitbox.totalWalls = {}
hitbox.queue = nil

The queue which handles hitbox position collisions to prevent skipping material in walls for high velocity hitboxes.

hitbox.collisions = 0

The current collision amount detected by the hitbox. Whenever the hitbox breaks a part. Used to create depth.

hitbox.connections = {}

The connections applied to all parts in the assembly which check for updated cframe, size, and part shape.

hitbox.runDebris = {}

All of the debris created from the hitbox from when the hitbox was started to when it was stopped.

  • Handling debris in separate events may show nil values in this table, it is best to loop through and check for non-nil parts
hitbox.totalDebris = {}

All of the debris created from the hit from when the hitbox was created.

  • Handling debris in separate events may show nil values in this table, it is best to loop through and check for non-nil parts
hitbox.runWalls = {}

All of the walls interacted with from the hitbox from when the hitbox was started to when it was stopped.

  • Handling walls in separate events may show nil values in this table, it is best to loop through and check for non-nil parts
hitbox.totalWalls = {}

All of the walls interacted with from when the hitbox was created.

  • Handling walls in separate events may show nil values in this table, it is best to loop through and check for non-nil parts
Events
hitbox.Started:Connect(function()
	print("Started")
end)

Fires when the hitbox is activated with the :Start() method.

hitbox.Stopped:Connect(function(debris, walls)
	print("Stopped")
end)

Fires when the hitbox is stopped for any case.

  • Returns all the debris created and breakable parts affected during the runtime of the hitbox, starting from when the hitbox was activated to when it was deactivated
hitbox.Ceiling:Connect(function()
	print("Ceiling hit")
end)

Fires when the collisions counter reaches the limit ceiling. The hitbox.collisions parameter must reach the hitbox.limit parameter.

hitbox.Collision:Connect(function(debris, walls)
	print("Collision detected")
end)

Fires when a hitbox interacts with a destructible part in any way.

  • Returns the debris created from the interaction as well as the walls affected

Do NOT destroy debris and destructible parts when finished using them!

Instead use the .cleanup() method of the module.

You can pass either a single part or a table of parts to the .cleanup() method.

Instead of using :Destroy() to destroy debris and affected destructible parts you need to use the .cleanup() method of the module since destroyed parts cannot be returned to cache.

  • If the useCache setting is true and the parts are stored in cache they will be returned, otherwise they will be destroyed if either of those conditions are false

It is optional to cleanup the hitbox object since ROBLOX’s garbage collection should clean up the object once it is no longer in scope, since it is good practice to free up any references immediately use hitbox = nil when you no longer need the hitbox.

Settings

Inside of the module’s hierarchy you will find a “Settings” module containing default settings that change the functionality of your methods. Defaults have been set to be what I deem to be the most appropriate settings for immediate use.

image

Settings.attribute = "Breakable"

The name of the attribute which you must apply to all parts that you want to use this module on.

Settings.debrisContainer = game:GetService("Workspace")

Where your debris are stored.

Settings.resetDefault = 60

The default time used for a timer if the passed reset parameter is 0.

Settings.resetMinimum= 10

The minimum amount of time a reset timer can be set to, passing the reset parameter as less than this will update it to this minimum.

Settings.shapeDefault = Enum.PartType.Ball

The default region shape.

Settings.voxelDefault = 1

The default voxel size used if the passed voxelSize parameter is 0 and useRelativity setting is false.

Settings.debrisAnchored = true

Whether or not debris are anchored by default.

Settings.cutoutDefault = 1 

The default cutout size relative to your region’s size used if the passed cutoutSize is nil or 0.

Settings.gridLockDefault = false

Determines whether grid locking is applied if the passed gridLock parameter is nil.

Settings.useRelativity = true

Determines whether relativity is applied instead of default values if passed voxelSize or debrisSize parameters are nil.

  • If true then relativity is applied if false then default values are applied
Settings.voxelRelative = 1/3

The relative size of voxels to your region size if relativity is applied.

Settings.voxelMinimum = 1

The minimum size voxels can be.

Settings.hitboxRelative = 1/3

The relative size of your hitboxes size determining the distance your hitbox needs to travel before the next collision. 
* *Check the hitbox distance concept under Methodology & Concepts for more information*

Settings.useGreedyMeshing = true

Whether or not greedy meshing will be utilized after destruction, it is highly recommend to leave this as true as meshing parts limits part count reducing lag considerably.

Settings.useRunService = false

Whether or not RunService will be utilized for hitbox distance checking. Requires more memory but may produce better results for high velocity hitboxes.

Settings.usePartCache = true

Whether or not part caching will be utilized. t is highly recommended to leave this as true as part caching prevents lag caused by instancing numerous parts. 

Settings.cachePrecreated = 10000

The quantity of pre-created parts if part caching is active.

Settings.cacheExtra = 100

The amount of additional parts cached if the cache ceiling is reached.

[details="Default Settings"]

local Settings = {}

Settings.attribute = “Breakable” – Name of attribute to check for
Settings.debrisContainer = game:GetService(“Workspace”) – Debris storage

Settings.resetDefault = 60 – Time it takes for the wall to repair itself since last break
Settings.resetMinimum = 10 – Minimum reset timer, passing a value less than this will use this

Settings.shapeDefault = Enum.PartType.Ball – Default region shape
Settings.voxelDefault = 1 – Default voxel size
Settings.debrisAnchored = true – Whether or not debris are anchored by default
Settings.cutoutDefault = 1 – Default cutout size
Settings.gridLockDefault = false – Default grid lock,

Settings.useRelativity = true – If true relativity will be applied instead of default size when size is not specified
Settings.voxelRelative = 1/8 – Voxel size relative to hitbox size
Settings.voxelMinimum = 1 – Minimum voxel size
Settings.hitboxRelative = 1/3 – If voxel size is not specified hitboxes will use a relative size applied to the part size
– *this only applies to movement, to have your intersection sizes match your part size keep this at 1

Settings.useGreedyMeshing = true – Recommended to leave this on for reduced part count to combat lag
Settings.useRunService = false – Use RunService instead of detecting changed position, may be more accurate
Settings.usePartCache = true – Use PartCache so instancing parts does not create lag
Settings.cachePrecreated = 10000 – The original cache ceiling
Settings.cacheExtra = 100 – The amount of extra parts created everytime the cache ceiling is reached

return Settings

[/details]

Examples

Example 1#

local vox = require(game:GetService("ReplicatedStorage"):WaitForChild("VoxelDestruction"))
local fling = 500

local unitSize = 20
local size = Vector3.new(unitSize, unitSize, unitSize)
local shape = Enum.PartType.Ball
local parameters = nil
local voxelSize = 0
local debrisCount = math.random(3,5)
local cutoutSize = 1
local gridLock = false
local reset = 5
local freeze = nil
local destroy = nil
local lock = nil

game:GetService("ReplicatedStorage"):WaitForChild("VoxelRemote").OnServerEvent:Connect(function(player, position)
	
	local debris, walls = vox.destroy(
		CFrame.new(position),
		size,
		shape,
		parameters,
		voxelSize,
		debrisCount,
		cutoutSize,
		gridLock,
		reset,
		freeze,
		destroy,
		lock
	)
	
	if #debris > 0 then
		for i,debri in ipairs(debris) do
			debri.Anchored = false; debri:SetNetworkOwner(nil)
			debri:ApplyImpulse( Vector3.new(math.random(-fling, fling), fling / 2, math.random(-fling, fling)) )
		end

		task.wait(5)
		vox.cleanup(debris)
	end
end)
27 Likes

Since this uses PartCache, is this module performant enough to be completely server-sided since voxels wont be able to be cloned on the client.

If it is ill definitely check it out and possible replace my current voxel system.

12 Likes

Hello,

This module should be optimized enough to run it completely on the server as long as you don’t go overboard with it as part count lag wont ever be a resolved issue. The module does wonderful job at minimizing part count as much as possible using greedy meshing(combining parts), caching parts, and providing a relative voxel size which is customizable in the settings module.

Make sure to read the Methodology & Concepts dropdown to understand how relativity is used for hitbox distance before using the voxelSize parameter. Pass your voxelSize parameter as 0 and set the useRelativity setting to true in order to make voxel sizes relative to the size of your region/hitbox.

As long as you do not have huge hitboxes with a very small voxel size you will not end up with many parts and your game should run well.

So, to answer your question blatantly, yes this module is performant enough to run it on the server entirely.

If you have any more questions please feel free to ask!

11 Likes

how could i specify the max amount of debris created?

11 Likes

The meshing algorithm seems to be flawed since it just creates random shapes and does not at all follow the set voxel size. Also this suffers from the same pause before the voxel actually moves which can only be fixed by cloning voxels on the client; unless there is a better way like Jujutsu shenanigans.

Honestly I could be using the module completely wrong but alot of the settings just dont make any sense.

Also its seems like the voxelization is just super slow. I am spam clicking throughout the video but the voxels are coming out every few seconds. I dont know if the module waits before voxelizing again but its not looking that great.

Settings:

8 Likes

Use the debrisCount parameter.

8 Likes

I will look into the second issue and post a fix when I can, my internet is out right now.

Off the top of my head I see that your cutoutDefault setting is set very low, this means that your region will be a tenth of was it originally was in order to choose what voxels become debris, this setting is meant to provide more detail for circular regions by reducing the voxels turned into debris and I would suggest limiting it to somewhere between .75 and 1 for the best result. Otherwise 90% of the space of your intersection will be voxels and not debris.

The voxelating method is subpar and I will be looking into how to make it faster but from what I can tell it seems like most of your space is being voxelated without turning into debris.

Could you provide all of the parameters you are using? Having parameters set to nil or 0 could be changing the intended result.

7 Likes

image
I am using a wrapper for Typescript but I am only using the required parameters.

5 Likes

I want the voxel size to be strictly 2 studs so how would I get that without having the relative debris size change the voxels.

5 Likes

So, if you don’t pass the voxelSize parameter its being read as nil. If this parameter is nil then the intersection with the breakable part and your hitbox’s bounding box is returned instead of debris. To actually voxelize the intersection you need a number passed.

If your voxelSize is 0 then two things could happen, if the useRelativity is true then the voxelRelative will be applied to the hitboxes largest axis size to create a voxelSize. And if useRelativity is false then the defaultVoxel is used as your voxel size.

It appears that you are getting intersections because you havent specified a number. You could either pass 2 as the parameter, or if you want to pass 0 and have voxelDefault equal to 2 and useRelativity set to false that would be defaulting it to 2 as well.

Debris size works the same way with defaults and relativity if you pass it as 0, except if passed as nil it will match your voxel size so keep that as nil.

I know the parameters dont make the most sense, I’ve written everything down under the hitbox dropdowns in case you ever need it. I made it this way so people could easily change a few settings and use a single parameter to choose between a specified size, a default size, or a relative size.

Let me see how your code runs after specifying 2 for voxelSize.

I forgot to mention- if you don’t pass a cutoutSize then your defaultCutout setting is used. Keep the defaultCutout setting at 1 just in case. 1 is the normal size. If you keep it at .1 youll wind up with very little debris and a whole lot of voxels that stay as a part of the wall and provide no detail, it would be a huge waste of space and will actually slow down the module considerably.

Let me see how it turns out!

5 Likes

This fixed my issue actually. But there is still the issue of a brief pause where the voxels dont move. This breaks the seamlessness of the destruction:

Here is voxbreaker (non-PartCache version) handling the debris client side:

I really want to use this module for my project but it still needs alot of work. And definitely a API re-design. Also another issue is that the voxels are forced into the voxel size without respecting the original parts size. Basically causing the voxels to be 2,2,2 while the original part was like 16, 1, 16. If that doesnt make any sense I can send a video showing this.

And the last issue being that this runs considerably slower than VoxBreaker. I can spam it as much as I want without slowdown but with this module, after a few voxels everything slows to a crawl.

6 Likes

Could you try the video of mine again but with useCache disabled in settings. I’m sure handling debris on the client would speed things up but I didn’t expect it to be necessary. If theres a recovery in speed with cache disabled it’ll help me find the issue faster.

In the mean time I will find ways to speed up voxelizing and hopefully greedy meshing.

4 Likes

Its exactly the same with and without part-cache. Also it would be preferable if there was a way of doing this without having to off the load to the client.

This module is really nice because if gives consistently sized voxels while VoxBreaker is not so consistent (getting voxels that are big are super small).

I think all the parts that are being cached should be unanchored by default and should have their network owner be set before the show up to prevent the brief pause (i think).

Overall this just needs some improvements before it can really be used.

2 Likes

Hey,

I’ve fixed most of the issue and am working on a way to send methods to run debris on the client through a parameter that way you can write what you want on the server and still see the changes on the client. When I was testing it I was seeing that jump that you were talking about, the funny thing is that I saw all of the voxels in the air as the jump happened so that tells me its purely network ownership and not the module- although I did change the way voxelating works to speed things up.

I also removed the debrisSize parameter as I realized its useless.

I tried changing network ownership of parts once they were unanchored but I still saw a jump which is very strange? Still trying to find a fix for that as well as provide a way for client sided debris so I’ll keep you updated about that.

I’ll update the model once everything is done.

2 Likes

Alright its fixed, updated the module and the instructions.

Fixed voxels not adhering to the part size, sped up voxelizing, and removed the debrisSize parameter as it was useless from the beginning.

Was never able to fix the lag with apply impulse, even if I set ownership it would still work the same. You can see that the module actually returns debris immediately because they are already in the air when you click. The module won’t unanchor them automatically so set network owner to the server :SetNetworkOwner(nil) and try it outside of studio. It could just be studio perhaps?

Never got around to client sided debris since I thought it was just pointless, if you’d still like that I can make it happen. I think a workaround for that velocity lag would be to apply velocity on the client but still have the part be on the server. If that doesn’t work ill make a method to move a part to clients.

1 Like

Awesome improvements!

But no, you shouldnt make debris client sided since most games using this module are going to be fighting games of some sort. Offloading the physics to the client would cause people to lag and make their game feel sluggish.

Could this just be a setting?

I’ll check this update out and see if I can make something work.

Adding a setting for that right now and fixing the thing where it flashes briefly.

Alright I published it. Those things have been added.

1 Like

Uh nothing happens now when I use the .destroy()?

image

And empty table just gets printed out:

Settings:

I don’t think its the module because I run this code and get the following result. Perhaps since I updated it the original attribute name setting is being used but in your parts still have your updated one. After looking at an image you sent the other day it seems like you changed the attribute name to “Destroyable”, try setting this in the settings again and see if that was the case.

local debris, walls = vox.destroy(
		CFrame.new(position),
		Vector3.new(10,10,10),
		Enum.PartType.Ball,
		OverlapParams.new(),
		2
	)