HammeriteSkybox

Core: the map. Inherits Resource

What is outside a map: a scale model of the world beyond it, and the sky over that.

The idea is Half-Life 2's, and it is worth stating plainly because the geometry is otherwise baffling: the city outside a level is built SMALL, and looked at from a camera that stands inside the model rather than where the player stands. Move the player and the sky does not shift, because it is not where they are - it is where camera_position is, at model_scale. Which is how a hundred metres of rooftop can sit behind a window in a room eight metres across, and hold still while you walk past it. A map has one (HammeriteMap.skybox). Which part of the city a map is in is then a matter of moving the virtual camera about inside the same model, rather than building a second one - so a dozen maps in one city share a model and differ by a position. What is authored here is the OUTSIDE. Everything about where it shows through - which walls are sky - is the map's, and already decided: a face whose texture is named sky_* is an aperture, and it is those the sky is drawn on. Hammerite draws a gradient; a game draws its own sky. What is here is what lights the world - the gradient, the sun or moon, their light - and dome_material is what the sky is drawn with: Hammerite's own is the gradient and nothing in it. A game's clouds, its sun's disc, its weather are properties it declares (HammeriteSkyPropertyRegistry) and uniforms its shader reads: each skybox field and each declared property reaches the dome as the uniform named after it, a property clouds/cover as clouds_cover.

HammeriteSkybox.dome_material = preload("res://materials/our_sky.tres")  # shader_type sky

Every field and property emits Resource.changed when it is set, and the sky that is up follows: a game changing its weather, or horizon_color at dusk, sees it on the next frame.

Properties

TypeNameDefault
ShaderMaterialdome_material<Object>
PackedScenemodelnull
Vector3camera_positionVector3(0, 0, 0)
floatmodel_scale16.0
Colorhorizon_colorColor(0.62, 0.66, 0.72, 1)
Colorzenith_colorColor(0.24, 0.4, 0.62, 1)
intbody0
Vector3body_directionVector3(0.35, -0.8, 0.45)
Colorbody_colorColor(1, 0.94, 0.8, 1)
floatbody_energy2000.0
floatlight_energy100.0
boollight_is_dynamicfalse
StringNamelight_group&""
Dictionaryproperties{}

Methods

ReturnsMethod
boolis_moon()
Colorambient_color()
Variantget_property(key: StringName, fallback = null)
voidset_property(key: StringName, value)

Enumerations

enum Body

ValueNameDescription
0SUNA sun: its light is what body_energy says.
1MOONA moon: the sky lights the world a hundredth as brightly (ambient_color()).

Constants

STOCK_DOME_MATERIAL

const STOCK_DOME_MATERIAL = <Object>

What the sky dome is drawn with until a game says otherwise: the gradient.

Property descriptions

dome_material

var dome_material: ShaderMaterial = <Object>

What every sky's dome is drawn with: a sky shader, given horizon_color, zenith_color, body_color, body_energy, light_energy and each declared property, by the names of the uniforms it declares, and two worked out: body_direction, the way TOWARDS the body (the field negated and normalized), and body_is_moon. One for every sky in the process, every map and every renderer: set it once, before a map is built. Null draws STOCK_DOME_MATERIAL.

model

var model: PackedScene = null

The scale model of what is out there: rooftops, hills, a distant cathedral. Anything Godot can instance. Null is a perfectly good answer - the sky itself is drawn either way, and an early map wants a sky long before it wants a city.

camera_position

var camera_position: Vector3 = Vector3(0, 0, 0)

Where the sky is looked at FROM, inside that model. This is the parameter that puts a map somewhere: the same model seen from the docks and from the cathedral roof is two skies.

model_scale

var model_scale: float = 16.0

How much smaller the model is than the world it stands for. 16 is the Half-Life 2 number and a good default: everything in the model is a sixteenth of life size, so it reads as distant.

horizon_color

var horizon_color: Color = Color(0.62, 0.66, 0.72, 1)

The colour of the sky dome at the horizon, blended towards zenith_color overhead.

zenith_color

var zenith_color: Color = Color(0.24, 0.4, 0.62, 1)

The colour of the sky straight overhead.

body

var body: int = 0

A sun or a moon.

body_direction

var body_direction: Vector3 = Vector3(0.35, -0.8, 0.45)

The direction its light travels, towards the ground (so a high sun points down), in world space; need not be normalized. The same direction a HammeriteDirectionalLight3D should point.

body_color

var body_color: Color = Color(1, 0.94, 0.8, 1)

The colour of the disc and of the light body_energy describes.

body_energy

var body_energy: float = 2000.0

What its light is worth on the ground, in the units a light's energy is in: a directional light that follows the sky shines with this and body_color. A sun of a couple of thousand is a clear day; a moon is a few dozen.

light_energy

var light_energy: float = 100.0

How brightly this sky lights what is open to it, in the units a light's energy is in: 100 is roughly a bright overcast day against a sun of a couple of thousand. Its colour is ambient_color()'s. A noon sky and a dusk sky differ in this as much as in their colour, and both are the sky's to say rather than the bake's. Baked: an edit shows after the next bake.

light_is_dynamic

var light_is_dynamic: bool = false

Whether the light this sky casts is switchable at runtime, or baked into the surface for good. Static by default, and that is not only compatibility. Daylight through a hole is the one light in a level that genuinely never changes for most maps, and a static one costs no slot: a face has four, and spending one on a sky that nothing will ever modulate is a slot a lamp wanted. Turn it on for a map whose weather moves. The sky's light then claims a slot like any other light and a HammeriteBakedLight drives it, so cloud crossing the sun dims the ground it falls on - a game dimming only its sun would leave an overcast level with bright sky-lit floors.

light_group

var light_group: StringName = &""

Which slot group the sky's light joins, when it is dynamic (light_is_dynamic). Naming the group a directional light already uses is the usual thing: the sun and the daylight around it are one sky and should dim together, and one handle drives both. Empty gives the sky a group of its own, which is right when the sky is the only thing that moves. Ignored entirely while the light is static, since a static light claims no slot to group.

properties

var properties: Dictionary = {}

What games and addons have put on this sky (HammeriteSkyPropertyRegistry) - a game's weather. Sparse: only what an author changed from its registered default. Read through get_property(), and write through set_property(): an edit of the dictionary in place is not heard.

Method descriptions

is_moon()

func is_moon() -> bool

Is this a moon rather than a sun? What it changes is the light: see ambient_color().

ambient_color()

func ambient_color() -> Color

What colour this sky lights the world with - the average of what a surface open to it can see, which is mostly its zenith. An overcast sky is a grey one. This is what a sky face should EMIT (sky faces are apertures, and an aperture with nothing behind it lights nothing, which is why a room open to the sky came out black at the bottom).

get_property()

func get_property(key: StringName, fallback = null) -> Variant

The value of registered sky property key: this sky's own, or the registered default.

set_property()

func set_property(key: StringName, value) -> void

Author sky property key. Setting it back to its registered default erases the override, as a brush property's does, so the sparse invariant holds however the author gets there.

Hammerite 1.0 · built from a6dd634, 2026-10-10