Voxel Management Script - Create voxels in code easily and efficiently

Preface:
Feel free to ignore this blurb, but I wanted to give you perspective and motivation to use this package.

In Crayta, I had to generate a random maze that could be of any size, potentially huge. As an example, I’ll use a 50x50 maze.

Each tile of the maze was represented as a voxel mesh in-game (with dimensions of 20x20x1 voxel cubes). Each voxel cube has a width of 25cm. So one tile was 500x500x25cm big. I cloned a base tile (that I manually made in-engine) for each tile of my maze. So by the end of it (just counting the floor tiles, not the walls), I had 50x50 or 2500 tiles on the floor.

Since I cloned a tile, that was 2500 entities just on the floor. Once I added in walls (up to 4 walls per tile, lets assume an average of 2 walls), I was close to 7,500 entities! Now The game struggles with this many entities. It’s actually better to have one entity (voxel mesh) that is complex, than many many simple voxel meshes. Now at this point, the framerate of my maze was around 9fps with this solution.

Now I wanted to make one voxel mesh that was complex (the entire maze) versus many smaller voxel meshes. However, I quickly ran into a problem. A voxel mesh can only be 6000x6000x6000cm big. My maze was much bigger than this, and would never fit into a single voxel mesh. So optimizing for voxel meshes wasn’t easy.

Introducing: Voxel Management

In an effort to combat having many simple voxel meshes, or one complex, but small and hard to deal with voxel mesh, I made Voxel Management.

The script will allow you to make voxels in any point in space to any other point in space. The script will take care of the rest. It’ll make the least amount of voxel meshes* as possible (* - may not be the absolute least in extreme edge cases).

In addition, it can support many different types of voxel meshes. For example, you can have a mesh that is static, and one that is destructible. This trivializes placements of large amounts of voxels, and is the perfect package for procedurally generated terrain.

Installing & Prep Work
Name of the package: Voxel Management

Simply add the Voxel Management Template to wherever you desire (such as your gameController).

In the property panel, ensure that the “voxelMeshTemplate” property is pointing to the Empty Voxel Mesh template. This is critical as this is what the script is spawning whenever it needs to create new voxel meshes for your request.

Now on the script that will be generating voxels (hereby referred to as SpawningScript), implement the following local variable:

local staticMeshIndex
local destructibleMeshIndex

-- quick reference to the voxel management script
local  voxelManagement

These two variables will keep track of which voxel mesh “type” to use when generating voxels. For example, in this case we’re generating one voxel mesh that will be static (cannot be damaged) and one that is destructible (can be damaged/destroyed).

Now, make the following function in your script:

function SpawningScript:OnVoxelMeshCreated(voxelMeshIndex, voxelMesh)
  if voxelMeshIndex == destructibleMeshIndex then
    voxelMesh.damageEnabled = true
  end
end

This is a function that gets called whenever a new voxel mesh is created (again, due to the size limits of a single voxel mesh). Your job is to see if the voxel mesh being created is relevant to you, and if so, make any changes if necessary. In this case, we’re making any destructible voxel meshes have damage enabled.

The last thing we need to do as prep work is register our voxel meshes with the script. To do that, add the following code to your scripts init:

function SpawnScript:Init()
  voxelManagement = self:GetEntity().voxelManagementScript
  voxelManagement:GetProperties().onVoxelMeshCreated:Listen(self, "OnVoxelMeshCreated")
  
  staticMeshIndex = voxelManagement:CreateNewVoxelMeshType()
  destructibleMeshIndex = voxelManagement:CreateNewVoxelMeshType()
end

Using the package

To use the package, simply call the following function:

voxelManagement:PlaceVoxel(voxelAsset <voxelasset>, voxelMeshIndex <number>, position <vector>, size <vector>)

Explaining the properties:

  • voxelAsset - VoxelAsset is the material you want to add. You can expose this as a property on your script, and reference that property (i.e. self.properties.myVoxelAsset)
  • voxelMeshIndex - this is referencing which voxel mesh you want to use for these voxels. Remember that earlier we specified two, one that is static and one that is destructible. Simply pass in which one you want to use
  • position - this is the absolute position that you want to add the voxels to in <x,y,z> space
  • size - this is the size of the object in terms of width, length, and height. One voxel cube is generally 25 voxels wide, so to make a 2x2x2 voxel cube square, you would pass in a vector of <50,50,50>

Thanks everyone!

5 Likes

An example of what is possible with voxel meshes: (a 50,000 x 50,000 x 25 cube)

I fixed a bug with the script. I’ll let this paint image do all the talking.

Wow! Thanks for sharing this!

1 Like

I can’t update the main post anymore, but updated the plugin.
When creating a voxel mesh type, you can now specify a parent to attach to. This will allow you, for example. to attach your voxel meshes relative to a locator you have.

Example code:

  staticMeshIndex = voxelManagement:CreateNewVoxelMeshType(self:GetEntity())
  destructibleMeshIndex = voxelManagement:CreateNewVoxelMeshType(self.properties.someLocator)

@Alex this script looks amazing. I actually don’t know how to use it. For instance: you say ‘hereby referred to as SpawningScript’, is that a script you need to make new? And if so, where would you put that local variable? I asume it’s not in the init, as you specify some stuff later, that does need to go in the init.
Then you say, simply call the following function. But how? Do I put that in the spawning script? Or do I make another script for that? And where in the script would that need to be put?

Last question. The voxels only spawn as soon as you enter the game or simulate the script? Is there a way to retain those voxels in the editor, as to manipulate them later?

I hope these questions makes sense to you, I’m pretty new at programming.

1 Like

This is great - thanks for sharing! Will give it a try.

Are there any other options for programmatically generating the terrain in Crayta?

Hi Zaratustra,

Yes, there are a couple of ways you are able to do this, but the Crayta Engine does not have a built-in terrain generator. Best way is to get into the editor advanced mode by pressing Tab and use the Voxel tools to shape your world as you want it. The Erode, Grow and Smooth tools allow for creating noise you’d see in nature.

The crayta engine does have some options to make generative maps, but the engine is not optimized to accommodate these methods. However, you can generate terrain by using code to get your random patterns/erosion/noise date, these would be position Vectors/coordinates.
Then you use the Crayta API, mainly SetVoxel(), SetVoxelBox() & SetVoxelSphere() to draw voxels at those randomly generated coordinates into your map at runtime.

Currently there is an interesting Community Package which does all that for you automatically, it’s called ‘Random Terrain Generator’, loads of options to customize the terrain you’d want, but it isn’t the fastest.

But you’re best bet is to go into advanced mode and start sculpting the world with the tools. Making it yourself is also the most fun way to create maps imho.

That’s an interesting package, and thanks for the detailed reply.

Yeah I haven’t touched this plug-in in a long time but it definitely is abusing the crayta engine. It’s for sure not designed to accommodate this.

Ah, maybe I sounded too dramatic about procedural content in my previous post, haha.

I mean, the API allows you to programmatically draw voxels, so I wouldn’t call it abusing the engine. Instead let’s call it creative ‘alternative’ map development :p.

Technically the engine can accomodate procedural terrain generation, but it’s not designed with loading procedural content at runtime though. I believe that the design assumes you have a static (but customizable of course) map. This map is saved in the game’s storage.

SetVoxel() calls may draw the voxels and store them in memory instead of in your game’s storage. The drawings are volatile, so what you draw with these functions will not be saved after you exit the game, next time you start the game it has to draw each voxel from scratch again, instead of just loading a map from disk.

Victor,
This is great info - thanks so much for sharing the details!
I have experimented with the dynamically creating Meshes in-game using events attached to objects. Here is a demo with examples of code: https://youtu.be/nknrb4zOXzI

Very cool Zaratustra! It looks like you have a good grip on what is possible dynamically / from the player’s input. Curious to see what cool games you come up with that utilize these methods :smiley: