Circuity Blocks! You won’t believe what they do…

Now that I lured you in with a fantastic click-bait title, you’ll probably be feeling disappointed, perhaps a little relieved?

Well, I’m writing a fantastic little blog which will sum up some cool features and design concepts to do with our awesome game (which you should totally check out here).

http://imgur.com/GGfEFFQ

As you can see in the image above, the circuitry changes color as well as the type of circuitry block. This is used to achieve a different look on each level (which there is potentially thousands of)

Some background on the level system: Each level is split up into modules, each module is predefined by us (the designers) and allows us to create puzzles and areas with enemies. The level system loads in random modules and creates a level procedurally, choosing from a defined list of easy/medium/hard modules.

So behind that, some players may (probably will) come across the same module more than once, and if it were to look the same they may lose some immersion inside the game. Having it varied ever so slightly won’t bring about the feeling of deja-vu and instead, hopefully, keep the immersion whilst allowing us (the fabricators) to use the same modules multiple times.

So how is the effect achieved?

short answer? shaders and noise (not the sound)

detailed answer?

There are two specific images we use (a single block image which is on the left) and a circuitry block which is on the right. When these images are combined we get a familiar looking block:

Although the background isn’t colored and the circuitry isn’t pulsating (it’s only an image) this is what it looks like when the circuitry is overlayed on top of a block.

So how do I go about coloring the block?  Well that’s easy, using a default pixel/fragment shader we color the base image using our default (and very basic) shader:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifdef GL_ES
#define LOWP lowp
precision mediump float;
#else
#define LOWP 
#endif
varying LOWP vec4 v_color;
varying vec2 v_texCoords;
uniform sampler2D u_texture;

void main()
{
gl_FragColor = v_color * texture2D(u_texture, v_texCoords);
}

So as you can see there’s a variable in the shader code above which is called v_color, this is the color we send to the shader that we want the background color to be, this is multiplied by the base blocks texture color so that it becomes a different color. This variable is defined on the level start up, using a random generator, so we get a color for that level which will hopefully be different that the last few that was selected.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#ifdef GL_ES
    precision mediump float;
#endif

varying vec4 v_color;
varying vec2 v_texCoords;

uniform vec3 u_neon_color;

uniform sampler2D u_texture;
uniform mat4 u_projTrans;

void main() {
    vec4 color = texture2D(u_texture, v_texCoords);
    color.rgb = mix(v_color.rgb,color.rgb,0.25);
    float alpha = 0.0;
    if(color.a > 0.1){
	alpha = 0.75;
    }
    gl_FragColor = vec4(color.rgb, alpha) ;
}

This is applied to our circuitry, the mix function is used with a 99% to be taken from the u_neon_color that we pass in and the 1% from the circuitry texture. this gives the circuitry the color which has the shading from the original image (so it’s not a solid color).

1
2
3
4
float alpha = 0.0;
if(color.a > 0.1){
    alpha = 0.75;
}

This if statement ensures that the entire block is not colored in when all we want is the circuitry to be the color that we define. Similar to the base block the color is defined at the start of the level creation. Inside the update function, the block color pulsates by multiplying the update delta time by the direction if the pulsate is above a certain value it pulsates back down until it is below a certain value, an example would be the code below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
...
for(int x1 = 0; x1<3; x1++){
		neon_pulse[x1] += Gdx.graphics.getDeltaTime()*direction;
	}
	count += Gdx.graphics.getDeltaTime();
	if(prev != neon_pulse[0]){
		prev = neon_pulse[0];
	}
	if(count > 1.0){
		direction = -direction;
		count = 0;
	}
}
...

The base block is drawn first and then the circuitry block is drawn after all the base blocks are drawn on the screen (so we’re not swapping shaders all the time)

The circuits are chosen at random using a simplex noise generation. This function receives the x and y location of the block being drawn. The circuits are chosen by this code:

1
2
int randomX = (int) (Math.abs((ImprovedNoise.noise(x*rand_val.x, y*rand_val.y, x*rand_val.z)*1.5f))*15);
int randomY = (int) (Math.abs((ImprovedNoise.noise(x*rand_val.x, y*rand_val.y, y*rand_val.z)*1.5f))*7);

This chooses a number between 0 and 7 for x, and 0 and 6 for y and draws the corresponding circuitry multiplying the x and y, the UV coordinates of the texture is worked out and then drawn on the screen. Simplex noise is used as it allows us to randomly get circuit types but they remain constant throughout the level and does not change randomly as the player goes through the level.

You may have noticed RandomX generates between 0 and 7, and y is between 0 and 6, well if 7 is chosen the smaller circuit blocks are drawn (if we look back to the gif you may see what I mean) this is drawn in exactly the same principle as the normal blocks.

The larger blocks are hand placed separately, but it would be pretty easy to make it so it is generated if there are 2×2 normal blocks in an area. An argument is that we may want to hand place larger blocks and there may be parts we do not want larger blocks to appear.

And that’s basically how Algorithm S3: Star System Sabotage makes use of simplex noise and shaders to draw our cool circuitry blocks, thanks for reading! Any questions feel free to leave some comments and I’ll reply!

Leave a Reply

Your email address will not be published. Required fields are marked *