Making game assets using OpenSCAD

In my day job, I have a task to design some 3D printed parts in a variety of repeating patterns, but with each part being just slightly different. I didn't really want to mess about in SolidWorks making them in all sorts of different sizes and then maintaining them every time the design specs changed. I decided what I really wanted was a procedural CAD program so I could code the outputs.

After some messing about I found OpenSCAD which is billed as "the Programmer's Solid 3D CAD modeller". Basically you can script 3D models using basic boolean operations like union, difference and intersect as well as primitive shapes like cube, cylinder and sphere. The language allows basic scripting including variables, for and if control statements. A minimal example is a cube, which you make like this:

A cube drawn with the `cube` statement in OpenSCAD

I wondered if I could use this as a really simple way to make game assets, particularly building up a lot of basic shapes very quickly. I think the technique is naturally suited to making things like castle walls which are basically a union of lots of primitive shapes. So, lets begin!

Blocking out the basic structure

Firstly, we can block out the structure of our wall. I'll just specify some proportions, then use a scale value to scale it up to our preferred size:

// scale the wall up
wall_scale = 10;

// set wall proportions
wall_length = 1;  
wall_thickness = 0.3;  
wall_height = 0.8;  

Here we are specifying a base wall is 1 unit long, 0.3 units thick and 0.8 units tall. We also want to scale the whole wall up by 10 units 1. We can scale and draw our cube the same was as before, except now we use the variable values instead of hard coding:

scale(wall_scale) cube([wall_length, wall_thickness, wall_height]);  

Making crennelations

The next thing a castle wall needs is crennelation, something for our archers to hide behind. We need a few variables here...

// crennelation properties
cren_duty_cycle = 0.5;  
cren_period = 0.1;  
cren_height = cren_duty_cycle * cren_period;  

A diagram should help:

Crennelation Dimensions

The period is the length of one "on and off" crennelation, the height is the height of a crennelation and the duty cycle is the percentage of on vs off of the crenellations, so 0.5 here indicates for each period 50% is crennelation and 50% is air. For simplicity we are going to assume that crennelations are cubes, so we can define the thickness as:

cren_thickness = cren_duty_cycle * cren_period;  

Now we can use a for, translate and cube to draw our crennelations:

for (x = [0 : cren_period : wall_length - cren_period]) {  
    translate([x, 0, wall_height]) {
        cube(cren_thickness);
    }
}

The first line is just the notation for a for loop in OpenSCAD. It loops from 0 to wall_length - cren_period, taking steps of cren_period. By default, the cube will be drawn at (0,0,0), but actually we want them to be placed on top of the wall. To achieve this we use a translate statement to wrap our cube primitive drawing statement. We provide the x, y and z coordinates where we want our cube to be drawn.

To join the crennelations to our base wall, we can wrap the whole lot in a union statement. Our model code now looks like this:

scale(wall_scale) {  
    union() {
        // wall base
        cube([wall_length, wall_thickness, wall_height]);

        // crennelations
        for (x = [0 : cren_period : wall_length - cren_period]) {
            translate([x, 0, wall_height]) {
                cube([cren_duty_cycle * cren_period, cren_thickness, cren_height]);
            }
        }
    }
}

Which produces:

The model with crennelation added

Reusing our wall

Now we have a fairly basic castle wall, but it would be really great if we could draw a bunch of wall really easily. Luckily OpenSCAD provides modules, which are essentially functions for this purpose. We can wrap our wall drawing code in a module like this:

module drawWall() {  
    // our wall drawing code from above in here,
    // except for the "scale" command
}

I'll also move the scale out of the module, as we want to wrap our entire castle in this later on, not just wall pieces one at a time.

You can provide function arguments to modules, but for simplicity I'm just letting modules access the global variables declared at the top of the file

We can then draw multiple wall segments using a for loop like this:

num_walls = 3;

scale(wall_scale) {  
    union() {
        for(x = [0 : wall_length : (num_walls - 1) * wall_length]) {
            translate([x, 0, 0]) drawWall();
        }
    }
}

Multiple walls drawn in a loop

We might also want to draw walls at different angles, so lets wrap that code in a module with a couple of arguments:

module drawLongWall(rotation, num_walls) {  
    rotate([0, 0, rotation]) union() {
        for(x = [0 : wall_length : (num_walls - 1) * wall_length]) {
            translate([x, 0, 0]) drawWall();
        }
    }
}

The only new part here is the rotate command, which rotates everything by the given x, y and z euler angles. Now we have this command, we can easily draw an entire enclosed wall inside another module:

module drawEnclosedWall(num_walls) {  
   union() {
        drawLongWall(0, num_walls);

        translate([
            num_walls * wall_length, 0, 0
        ]) drawLongWall(90, num_walls);

        translate([
            num_walls * wall_length, 
            num_walls * wall_length, 0
        ]) drawLongWall(180, num_walls);

        translate([
            0, num_walls * wall_length, 0
        ]) drawLongWall(270, num_walls);
    } 
}

All this does is draw four different longWalls, with the correct offsets and rotations. We can then draw a wall enclosure by writing

scale(wall_scale) {  
    drawEnclosedWall(5);
}

The resulting enclosed wall

Phew, that was actually "relatively" easy! Of course that's only the basics, and we still would need to export to STL, then do UV unwrapping, texturing and so on in our modelling program. The main advantage of this kind of approach is that it is really easy to generate a whole range of different geometry very fast.

For my day job, I built a command line tool that can generate tonnes of different geometry and export to STL at once. In the game dev world, this kind of approach could be used to generate castles of all different sizes in a batch process.

Of course, our castle above lacks a whole lot of things like a gate, a keep and towers... and it is debatable how useful this technique will be once things get a lot more complicated, but its an interesting experiment!

Here is something I put together with maybe twenty minutes more effort (you can see the code for it below):

Final castle

Notes

  1. A "unit" in OpenSCAD is one millimetre, but that doesn't really matter here

Code

The code I used for the basic castle and castle walls is below: