In 2020 I had some time available to myself and decided to look back at some of my previous work. BoxByte was something I always wanted to make more feature complete than it was originally, but lack of time and development roadblocks kept me from doing more. It was always meant to have multiple levels and even secrets hidden in the game to be discovered, that failed to make it in to the first release.
By this time, I had developed games and systems using three engines; Unity, Unreal, and Godot, and was then confident in creating something in each of them. Ultimately, I thought Unreal would be overkill for a simple game like BoxByte and was curious to see how Godot could handle 3D. That led me down another path that I wasn't intending originally, to push Godot engine to its limit.
This game would carry over the secret story from the previous version by removing some of the narrative designs subtlety. I wanted to have more for players to search for to discover the origins of Box, in BoxByte, as a character.
Technically speaking, there were many facets that converged to give BoxByte it's characteristic look. The background was an independent scene that was rendered in advance for the purpose of switching level aesthetic dynamically. Separating level logic and the level manager was a challenge, as this required a check for completion of level requirements and automate the transition of the following level. As well as tooling custom shaders (GLSL for Godot) for the menu, backgrounds, gameplay and everything in between.
shader_type spatial;
render_mode cull_disabled;
uniform vec4 albedo: hint_color;
uniform vec4 glowColor: hint_color;
uniform sampler2D reflections;
float fresnel(float amount, vec3 normal, vec3 view) {
return pow((1.0 - clamp(dot(normalize(normal), normalize(view)), 0.0, 1.0 )), amount);
}
void fragment() {
// Constantly translate UV to make Ethos look metaphysical
vec2 refUV = SCREEN_UV + TIME * 0.01;
vec4 refl = texture(reflections, refUV);
float fres = fresnel(3, NORMAL, VIEW);
ALBEDO = albedo.rgb * refl.rgb;
// this makes a cool negative glow like effect that I really like
EMISSION = -refl.rgb*0.5 + glowColor.rgb * fres*2.0;
ALPHA = 1.0 - fresnel(2.0, NORMAL, VIEW);
}
shader_type spatial;
render_mode unshaded, cull_disabled;
uniform sampler2D textImage;
uniform sampler2D ribbonImage;
uniform sampler2D colorize;
float fresnel(float amount, vec3 normal, vec3 view)
{
return pow((1.0 - clamp(dot(normalize(normal), normalize(view)), 0.0, 1.0 )), amount);
}
void fragment() {
// Text and ribbon move in opposite directions
vec2 uvText = vec2(UV.x+TIME*0.03, UV.y);
vec2 uvRibbon = vec2(UV.x+TIME*-0.03, UV.y);
float fres = fresnel(3.0, NORMAL, VIEW);
vec4 text = texture(textImage, uvText);
vec4 ribbon = texture(ribbonImage, uvRibbon);
vec4 color = texture(colorize, UV);
ALBEDO = (ribbon.rgb + text.rgb) * color.rgb * (1.0-fres);
ALPHA = fres + ribbon.a + text.a;
}
The story centers on a rogue AI scientist turned ethical hacker, determined to take down the system that burned him. Discarding his old identity, he creates a new name for himself, Ethos, and hatches a plan to take over the US missile silos to destroy the corrupted world and remake it as he sees fit. And none would guess his chosen instrument to orchestrate all this ... you.
You are an AI he calls Box. A fitting name for what you really are; a package holding a dangerous worm to infect the US defense system and bring it to its knees. Combined with a new exploit Ethos has devised, you are to travel to major server hubs of the defense network and compromise each to gain elevated privileges to attack the next. Eventually, taking control of the whole US defense network and turning its weapons on the beast itself.
However, he could not have predicted what you would eventually become and how that would alter his plans. As players progress through the levels, they run across two bosses, Logos and Pathos. After each is defeated, a small part of their neural networks becomes integrated into yours. Throughout the game, the player eventually gains the ability of using logic. Unlocking dialog options with Ethos which were completely unavailable before. And when you defeat Pathos, you are given the ability to question your motives and Ethos himself.
This time during development I went out to document as much as I could before starting to code. I had the progression of the story all gathered as well as each level designed in 2D along with their secrets. I wanted secrets to actually make up a system in the game that gave players a benefit to play. Each level had its own secret that would appear in the menu of the Level Select scene.
The text of the secret itself takes form of a snippet of a news article or transcript that would give players a look into events going on inside the real world. Here's some examples from the design document:
Level 1 secret
White House Public Relations have signaled that they hope the process of disolving the FBI will be completed by the end of the year. The Department of Homeland Secerety is expected to take over its responcibilities. Many have voiced concerns over this unprecedented move but Secretary Mathews made assurances saying "Americans have nothing to fear if they have nothing to hide." Could this move be related to the recent gutting of privacy laws that this reporter...
Level 4 secret
A Dollor Common in Dairy, Mi. is now the site of the 3rd largest mass shooting since just earlier this year. And now a newly released police report depicts a chilling story of the events that unfolded that day. Sometime around 2AM, 1 man and 1 woman in a pickup truck pulled up to the entrance of the store armed with a shotgun and a semi-automatic rifle. The two demanded to see the establishment's basement believing the site was part of an immigrant smuggling operation. Blueprints show that no such basement exists and can't exist because of high ground water in the area.
As for the levels, they of course increase with difficulty by including new barriers and enemies from the first along with additional ones. Each level's style would also change subtlety based on the boss they were about to head up against. Logos levels would by basic shapes, Pathos' levels would be based on world symbols, and the levels leading up to the final boss, Ethos, would consist of things that look manmade and molecular structures.
Pentagon
Diamond
Vesica
extends Control
## List of intros
var intros = {
"square_port_intro" : "res://background/intros/square_port_intro.tscn"
}
## List of backgrounds
var backgrounds = {
"flakefield" : "res://background/flakefield.tscn",
"square_tunnel" : "res://background/Square_tunnel.tscn"
}
onready var tween : Tween = $tween
var index = 0
## Begin by setting rendering viewports to window size
func _ready() -> void:
$Viewport0.set_size(OS.window_size)
$Viewport1.set_size(OS.window_size)
## remove all children from passed in viewport index to prepare it for a new scene
func _clearViewport(ind) -> void:
var viewportScene = get_node("Viewport"+str(ind)).get_children();
for child in viewportScene:
child.queue_free()
## Receives a packedScene and loads it, then transitions to the viewport
func _flipBackground(background:PackedScene):
#get the next index and set it to the appropriate values
var nextIndex = 0 if index == 1 else 1
var nextViewport : Viewport = get_node("Viewport"+str(nextIndex))
nextViewport.add_child(background.instance())
var nextBuffer : TextureRect = get_node("buffer"+str(nextIndex))
nextBuffer.show()
# transition the next render target by interpolating alpha
tween.interpolate_property(nextBuffer, "modulate", Color(1,1,1,0), Color(1,1,1,1),
2,Tween.TRANS_CUBIC, Tween.EASE_IN)
# transition away the current render target, switch indexes, start cleanup timer
var oldBuffer : TextureRect = get_node("buffer"+str(index))
tween.interpolate_property(oldBuffer, "modulate", Color(1,1,1,1), Color(1,1,1,0),
2,Tween.TRANS_CUBIC,Tween.EASE_OUT)
tween.start()
$cleanup.start()
index = nextIndex
## Gets an introID string, sourced from level JSON, and plays it. Swaping or transitioning is unnecesary
## because intro is the first scene played
func play_level_intro(introID:String) -> void:
var introBack : PackedScene = load(intros[introID])
$Viewport0.add_child(introBack.instance())
## Gets a backgroundID string, sourced from level JSON, and loads a packedScene.
## Begins transition to new background
func changeBackground(backgroundID:String):
if not backgroundID.empty():
var back : PackedScene = load(backgrounds[backgroundID])
_flipBackground(back)
## After a viewport has faded, this cleans it up
func _on_cleanup_timeout():
var lastIndex = 0 if index == 1 else 1
var oldBuffer : TextureRect = get_node("buffer"+str(lastIndex))
oldBuffer.hide()
_clearViewport(lastIndex)
With these designs I designed a shader to display them in game by making them appear like holographic projections. Each level represented a gate in the firewall players passed through and so I thought it wanted it to look like a digital projection of software. I also wanted it to look badass and I think I achieved it. It uses a parallax offset to the camera to draw the level textures multiple times creating a sense of depth. There's no other trick, or outside shader scripts, this is displayed on a single plane in-game.
It's written in Godot's own shader language which is very similar to GLSL ES 3.0
shader_type spatial;
render_mode cull_disabled, specular_schlick_ggx, ambient_light_disabled;
uniform vec4 color : hint_color = vec4(1);
uniform sampler2D level;
uniform sampler2D specTexture;
uniform sampler2D shatterGlow;
uniform float shatter = 0.0;
uniform float holoSeperation = 0.005;
uniform float iterations = 5.0;
uniform float shine = 0.2;
uniform float glow = 0.2;
void fragment() {
// Create hologram by creating parallax effect and sampling level texture at lower levels
vec4 holo;
for ( float i = iterations; i > 0.0; i--) {
vec2 offset = vec2(i * -holoSeperation* VIEW.x, i * holoSeperation * VIEW.y);
float opacityBase = iterations*0.06;
float opacitySteps = opacityBase/iterations*0.1;
holo += ( opacityBase - i * opacitySteps) * texture(level, offset + UV);
}
// mix main level texture on top to make it stand out stronger
vec4 game = texture(level, UV);
vec4 holoGame = vec4(mix(holo.rgb, game.rgb, game.a), max(game.a, holo.a));
// Create specular with texture to add additional detail
vec4 s = texture(specTexture, UV*5.0);
vec2 specular = vec2(max(0.0, s.b-game.a)*10.0, max(0.0,s.b-game.a) * shine);
// make the outer ring glow more by sampling texture at low lod
vec4 glowRing = textureLod(level,UV,6);
// make the specular details glow
vec4 specGlow = mix( vec4(0), specular.xxxx, glowRing.a);
specGlow.a = mix( 0.0, specular.y, specGlow.r);
// Glowing for when level shatters
vec4 shtr = vec4(0);
if (shatter > 0.0) {
shtr = texture(shatterGlow, UV);
}
ALBEDO = color.rgb;
ALPHA = mix(specular.y, holoGame.a, holoGame.a);
EMISSION = color.rgb * mix( specGlow.rgb*glow, game.rgb*glow+holo.rgb, holoGame.a);
SPECULAR = specular.x;
}
The UI was something I paid serious attention to this round. I also started this game with a new methodology in mind and that was to treat individual part as though it was its own project.
With the Level Select, I wanted to do something that looked like out of a cyberpunk world and vaguely reminded players of the N64/PS1 era of gaming but with modern shaders. The level select is placed inside the physical world, Ethos' actual lair. This is located inside an abandoned water tower in a rundown area of the industrial district.
I also wanted this area to add to the story and lore of the game by eventually adding more environment art to hint at the lore of the outside world. Eventually, as players reach the last level, they would see Ethos himself in his human form hanging from the ceiling by hundreds of wires entering his flesh.
For the HUD I wanted it to look appropriate to the game while also having some overlay elements for hacking. At the bottom I have a new connection meter to serve as health with a custom shader that dynamically changes the lines and color based on the character's state. At the top I have another indicator with a custom shader showing your danger level and little stats around it containing the actual memory usage of the game, verts in the scene, fps, and the name of the computer being played on. On the left you can see an indicator for the bytes remaining, and on the right one for the player's score.
shader_type canvas_item;
uniform vec4 ui_Color : hint_color;
void fragment(){
vec4 col = ui_Color;
// make a gradient
col.a = col.a - (col.a * UV.x) + 0.2;
//curves
float curve_col = col.a;
//curve 1
float curve = (.2-.1*sin(TIME+2.5) ) * sin((15.25 * UV.x) + (-2.5 * TIME+2.5));
float c1 = max(curve_col - smoothstep(1.0 - clamp( abs(0.5 - (curve + UV.y)) * 1.0, 0.0, 1.0), 1.0, 0.99), 0.);
col += c1*0.5;
//curve 2
curve = (.15-.08*sin(TIME) ) * sin((15.25 * UV.x) + (-2.0 * TIME));
float c2 = max(curve_col - smoothstep(1.0 - clamp( abs(0.5 - (curve + UV.y)) * 1.0, 0.0, 1.0), 1.0, 0.99), 0.) * 0.5;
col -= c2;
COLOR = col;
}