VoxelDestruct 2.1 | 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, and client off loading.

Making Destructible Parts

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

Tagging a model will make all of its children (parts only) destructible as well.

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(
focus: Part | MeshPart,  
parameters: OverlapParams?,
voxelSize: number?, 
debrisCount: number?, 
reset: number?
)

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.
focus: Part | MeshPart

The part representing the shape, size, and position of your destruction.

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 Relativity setting is true then relativity is applied
  • If passed as 0 and Relativity 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
reset: number?

The timer until destruction is cleaned up.

.hitbox()

Used to create hitboxes from parts, meshparts, or models.

function breaker.hitbox(
focus: Part | MeshPart, 
parameters: OverlapParams?,
voxelSize: number?, 
debrisCount: number?, 
reset: number?
)
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:IsActive()

Returns a boolean displaying whether or not the hitbox is running or stopped.

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.

Inherited Parameters

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

focus: Part | MeshPart,
parameters: OverlapParams?,
voxelSize: number?, 
debrisCount: number?, 
reset: number?
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.

  • 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.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.

Settings.Tag= "Breakable"

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

Settings.OnClient = false

Whether or not the destruction is off loaded onto clients.

Settings.OnServer = false

If OnClient is true, whether or not the server uses un-replicated destruction. This is intended to allow server NPCs to go walk through destruction, it is recommended to keep this false as it can lag the server.

Settings.RecordDestruction = false

Whether or not the server will store destruction and hitboxes and generate them for players who join the server. This is meant to combat not being able to see destruction when you join a server if destruction is offloaded to clients, depending on how much destruction is stored it may take some time before destruction generates on clients.

Settings.ResetModel = false

Whether or not tagged models repair the whole model.

Settings.ResetYields = false

Whether or not parts and models yield repairing while humanoids are standing inside of them. This is meant to combat players and NPCs getting stuck in walls and glitched out of the map when destruction gets repaired.

Settings.ResetDefault = 60

The default reset time applied if the reset parameter is passed as 0.

Settings.ResetMinimum = 3

The minimum reset time applied if the reset parameter is pass as a number greater than zero.

Settings.CutoutSize = 1

The scalar multiplier to the focus parameter’s size meant to provide more or less voxel detail. This can be between .75 - .9 for best detail. It is recommended to keep this as 1 as its a developer setting.

Settings.GridLock = false

Whether or not decimals will be dropped from the focus parameter’s position and size properties.

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

Where debris will be placed upon creation.

Settings.DebrisDefaultBehavior = false

Whether or not debris will have default behaviors like anchoring and reset timers.

Settings.DebrisAnchored = false

Whether or not debris are anchored by default.

The DebrisDefaultBehavior setting must be true for this to be used.

Settings.DebrisReset = 10

Whether or not debris have a reset timer applied by default.

The DebrisDefaultBehavior setting must be true for this to be used.

Settings.Relativity = true

Whether or not relativity is applied to voxels’ size if the VoxelSize parameter is passed as 0.

If true, the VoxelRelative setting will be used. If false, the VoxelDefault setting will be used.

Settings.VoxelRelative = 1/8

The relative size of voxels to the focus parameter’s size.

This setting is used if the VoxelSize parameter is passed as 0 and the Relativity setting is true.

Settings.VoxelDefault = 1

The default size of voxels.

This setting is used if the VoxelSize parameter is passed as 0 and the ***Relativity setting is false.

Settings.VoxelMinimum = 1

If the VoxelSize parameter is passed as a number greater than zero then it cannot be below this number.

Settings.HitboxRelative = 1/3

The relative distance to hitbox size that must be travelled before the hitbox destroys again.

For hitboxes, if the VoxelSize parameter is passed and the Relativity setting is true then this setting will be used.

Settings.GreedyMeshing = true

Whether or not greedy meshing is used. It is recommended to always keep this on.

Settings.RunService = false

Whether or not a run service loop is used instead of listening for hitbox cframe changes. This setting being true results in destruction every frame but it very performance heavy, it is recommended to keep this setting off.

Settings.PartCache = true

Whether or not parts will be cached to prevent instancing lag, it is recommended to keep this setting true.

Settings.CachePrecreated = 10000

The size of the cache.

The PartCache setting must be true for this setting to be used.

Settings.CacheExtra = 100

The number of parts created when the cache is emptied.
The PartCache setting must be true for this setting to be used.

Update 2.1: New Features!

  • Simplified method parameters, .Destroy() now takes a focus part/meshpart.
  • Meshparts can now be used as a focus
  • New .Repair() method to repair models and parts before their timer ends.
  • Client-Sided destruction via the OnClient setting
  • Un-Replicated server destruction via the OnServer setting
  • Destruction storage via the RecordDestruction setting so that newly joined players can see old destruction on their clients
  • The ResetModel setting allows models to be repaired as a whole
  • The ResetYields setting allows destruction repairing to yield while players stand in it, used to prevent being stuck in walls that get repaired
132 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.

8 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!

12 Likes

how could i specify the max amount of debris created?

4 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:

3 Likes

Use the debrisCount parameter.

3 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.

3 Likes

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

2 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.

2 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!

1 Like

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.

2 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.

2 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.

3 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.

2 Likes

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
	)