UNSCALED 2 - MESARYTH

Welcome back! In UNSCALED 1 I talk about the general premise of the game. This entry is more of a deep dive into how the characters kits will work. But if you want a quick visual, check out the embedded gameplay footage below! 

The 6 input rule.

To help keep the game accessible and conisistent input-wise, I devised a new golden rule for characters kits:

Each character has active 6 abilities. Each ability only has one assigned input or 'key' at a time. Therefore, only these 6 keys can contribute directly to abilities (the only exceptions being the unrelated WASD movement and camera controls).

But wait, Mesaryth has two different uses for her primary fire inputs!

  • Usual - shoot alternating pistol shots
  • Fire a big cooldown shot when aiming with secondary fire.

There solution lies in the wording: Each character has 6 active abilities. If we allow the currently active abilities to swap, more complex characters can still follow the rule.

Here is Mesaryth's ability set with two primary abilities. By default, her active primary will be the basic revolver shot, but this is changed when the Secondary_ReadyDrakeshot is held down:

# Start ability function in Secondary_ReadyDrakeshot.gd
func start_ability()->void:
	super()
	aiming_drakeshot.emit()
	
	# Swap primary ability to the actual drakeshot fire
	old_primary = this_character.ability_set.primary
	old_primary.ability_disabled = true # Disable regular revolver fire primary
	
	ability_active = true
	
	if !ability_active:
		return

	# Enable the drakeshot fire primary ability
	this_character.ability_set.primary = drakeshot_primary 
	print("Drakeshot ready!")

A lot more code goes into linking everything, handling weird exceptions etc. but this is the general flow of ability 'transformations'! I hope to use this to make complex but conistent ability combos. For example, a werewolf character who swaps between ranged combat and a more primal slash attack when a temporary ability is activated.

Data-driven Gameplay Systems

Lots of work has also gone into simlar modular, resource-driven gameplay systems. The health/hurtbox system is probably the most important of these, using custom HealthPool and DamageInstance classes for easy balancing and customisation!

extends Resource
class_name DamageInstance

# Standard - x health is taken away.
# Heal - x health is granted


enum DAMAGE_TYPE {STANDARD, HEAL}
@export var damage_type : DAMAGE_TYPE

@export var value : float = 1.0

@export var ignore_tags : Array[String] = ["Player"]

 DamageInstances aren't just a number - they allow for new damage types as well as ignoring certain hurtboxes with the same tag. For example, later on maybe only 'armor-piercing' abilities can penetrate shields, or there could be some kind of elemental damage system.

Miscellaneous Additions

Crosshair + Healthbar UI Shaders

I've made two shaders for the game: A crosshair canvas_item (UI) shader and a spatial healthbar shader. I did the crosshair first as it its relatively simple, for now only drawing a basic circle. I plan to extend this shader greatly, using paramaters for lots of user customisation such as adding extra lines, rings etc.

shader_type canvas_item;

// Simple crosshair shader. Can use the control's rect_size to change
// the crosshair size (recommended around 2x2px to 16x16 px)

uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, filter_linear_mipmap;

uniform bool use_dot = true;

uniform float dot_radius = 0.1;
uniform vec4 dot_color : source_color = vec4(1.0, 1.0, 1.0, 1.0);
uniform bool dot_invert = true;

uniform bool use_dot_outline = true;
uniform float dot_outline_width = 0.005;
uniform vec4 dot_outline_color : source_color = vec4(0.0, 0.0, 0.0, 0.9);


bool draw_dot(vec2 uv) {
	float e = pow(uv.x-0.5, 2.0) + pow(uv.y-0.5, 2.0);
	if (e < dot_radius){
		return true;
	}
	return false;
}

bool draw_dot_outline(vec2 uv){
	float e = pow(uv.x-0.5, 2.0) + pow(uv.y-0.5, 2.0);
	if (e > dot_radius-dot_outline_width && e < dot_radius){
		return true;
	}
return false;
}


void fragment() {
	COLOR = vec4(0.0);
	if (use_dot){
		if (draw_dot(UV)){
			if (dot_invert){
			    // Get the current screen color
			    vec4 screen_color = texture(SCREEN_TEXTURE, SCREEN_UV);
			    // Invert the RGB components, keep Alpha the same
			    COLOR = vec4(1.0 - screen_color.rgb, screen_color.a);
			}
			else{
				COLOR = dot_color;	
			}

		}
		
	if (use_dot_outline){
		if (draw_dot_outline(UV)){
			COLOR += dot_outline_color;
		}
	}

Below is the crosshair with invert_dot enabled.

 

Healthbars are just as barebones currently, but will be updated as the HealthPool becomes more complex!