HammeriteTextureLibrary
Core: files, names and extension. Inherits RefCounted
Where a texture NAME becomes a texture.
A face records what it is textured with by name - "gbl_stone_cobble_01" - rather than by holding the resource. A name survives a texture moving between directories, being re-imported, or being added after the map was made; a resource reference is a path baked into every face of every map, and a map that references a texture the project no longer has fails to load rather than showing the author which wall is wrong. A game says where to look and what to fall back to:
HammeriteTextureLibrary.search_paths = ["res://textures", "res://textures/props"]
HammeriteTextureLibrary.fallback_name = &"notex"Looking a name up walks the search paths and tries the extensions Godot imports textures to, which is far too much work to do per face per frame - so every answer is remembered, including the answer "there is no such texture", which is the one that would otherwise be paid for again on every redraw of a map with a missing texture in it. A change to the search paths, however it is made, is noticed at the next lookup and every answer looked up again. Texture names mean things here, and this is the one place that says what: is_alpha_masked() for a name marked see-through, emission_for() for a texture's glow beside it, and the sky names a face is drawn as the sky through. A game naming its textures follows these, and nothing else in Hammerite reads a name for meaning.
Properties
| Type | Name | Default |
|---|---|---|
String[] | search_paths | [] |
StringName | fallback_name | &"" |
int | generation | 0 |
Methods
| Returns | Method |
|---|---|
void | set_library_paths(library: StringName, paths: String[]) static |
String[] | library_paths(library: StringName = &"") static |
Texture2D | resolve(name: StringName, library: StringName = &"") static |
String | file_of(name: StringName, library: StringName = &"") static |
bool | has_texture(name: StringName, library: StringName = &"") static |
void | add_source(find: Callable, list: Callable = Callable(), library: StringName = &"") static |
void | clear_sources() static |
StringName | name_of(texture: Texture2D) static |
bool | is_alpha_masked(texture: Texture2D) static |
void | add_texture(name: StringName, texture: Texture2D, library: StringName = &"") static |
void | remove_texture(name: StringName, library: StringName = &"") static |
Texture2D | emission_for(texture: Texture2D) static |
Image | readable_image(texture: Texture2D) static |
StringName[] | list_names(library: StringName = &"") static |
void | clear_cache() static |
Constants
DECALS
const DECALS = &"decals"
Surfaces are not the only thing a game keeps pictures of. Decals are chosen from their own directories and listed on their own, but they are looked up exactly the same way - so rather than a second library beside this one, a library has a NAME and the surface textures are the unnamed default.
HammeriteTextureLibrary.set_library_paths(HammeriteTextureLibrary.DECALS, ["res://decals"])
EXTENSIONS
const EXTENSIONS = PackedStringArray("png", "jpg", "jpeg", "webp", "tres", "res", "svg")
Extensions to try, in the order a game is likely to have them.
Property descriptions
search_paths
var search_paths: String[] = []
Directories searched for a name, in order: the first hit wins, so a game can shadow a shipped texture by putting its own earlier. Assign it or edit it in place; the next lookup notices, and forgets what was found under the old paths.
fallback_name
var fallback_name: StringName = &""
The texture used for a name that resolves to nothing, so a missing texture is a wall an author can see and click rather than an invisible one. Empty means missing textures stay missing.
generation
var generation: int = 0
Raised whenever what a texture is called may have changed - the remembered answers forgotten, a texture given a name - for whoever keeps something decided from the names (is_alpha_masked()).
Method descriptions
set_library_paths()
static func set_library_paths(library: StringName, paths: String[]) -> void
Where a named library looks. Empty paths leave it with nothing to find, which is what a game that has no decals has.
library_paths()
static func library_paths(library: StringName = &"") -> String[]
The directories library searches; empty is the surface textures. A copy.
resolve()
static func resolve(name: StringName, library: StringName = &"") -> Texture2D
The texture called name, the fallback when there is no such texture, or null when there is no fallback either. library picks which directories are searched; empty is the surface textures.
file_of()
static func file_of(name: StringName, library: StringName = &"") -> String
The file in the search paths that name loads from, or empty when none holds it. Loads nothing, so a browser can have Godot read the file on another thread (ResourceLoader.load_threaded_request()) and resolve() it once that is done. A source is asked before the files and may answer for the same name.
has_texture()
static func has_texture(name: StringName, library: StringName = &"") -> bool
Whether name is a texture of its own, rather than one the fallback stands in for.
add_source()
static func add_source(find: Callable, list: Callable = Callable(), library: StringName = &"") -> void
A place textures come from besides the search paths, asked first: find takes a name and returns the texture or null, list returns the names it has, for a browser to offer. What a game uses for textures that are not imported resources - a mod's PNGs, loaded as it starts.
clear_sources()
static func clear_sources() -> void
Drop every source add_source() registered, in every library, and forget every remembered answer. The Hammerite autoload calls it on exit, because a source is a game's Callable.
name_of()
static func name_of(texture: Texture2D) -> StringName
What a texture is called: the file's name without directory or extension, which is what a face records. A texture with no resource path - one built at runtime - is called what a source or add_texture() called it, and has no name at all if it came from neither.
is_alpha_masked()
static func is_alpha_masked(texture: Texture2D) -> bool
Whether this texture is an alpha mask, by the leading "!" its name carries. The convention itself is older than this function and is read in two other places - HammeriteBrush.build_face_material() picks the transparent material by it, and the lightmapper gives such a face a mask layer. Named here because HammeriteTextureLibrary is where the other things a texture's NAME means already live (emission_for(), and the "sky_" rule it sits beside), and because a third copy of a magic character is a convention that can drift apart from itself.
add_texture()
static func add_texture(name: StringName, texture: Texture2D, library: StringName = &"") -> void
Give a name a texture directly, for textures that are not files at all - built at runtime, fetched from somewhere a search path cannot reach. Replaces whatever that name resolved to before, and is asked before the sources and the search paths until remove_texture(); list_names() offers it. library is which library the name belongs to, as in resolve(): a decal added under the default would never be found by anything asking the decals for it.
remove_texture()
static func remove_texture(name: StringName, library: StringName = &"") -> void
Take back a texture add_texture() gave name in library, so the name is looked up in the sources and the search paths again.
emission_for()
static func emission_for(texture: Texture2D) -> Texture2D
The emission texture paired with texture, or null when it has none. Paired by NAMING CONVENTION, like the other surface kinds (a leading "!" marks an alpha mask, "sky_" an aperture): wall_01.png glows wherever wall_01_emission.png sits beside it. Nothing to assign and nothing to author twice - dropping the second file next to the first is the whole workflow, and removing it turns the glow off. Here rather than on HammeriteFace because a wall is no longer the only thing that can glow: a decal opted into the bake asks the same question of its own picture (#58), and a convention with two implementations is a convention that will come to mean two things.
readable_image()
static func readable_image(texture: Texture2D) -> Image
texture's pixels, in a form that can be read texel by texel, or null for a texture there is no reading. Compressed textures cannot be sampled as they are, and decompressing one is far too much work to do per query - a decal's alpha is asked for once per footstep - so the answer is remembered with the rest and forgotten by clear_cache().
list_names()
static func list_names(library: StringName = &"") -> StringName[]
Every texture add_texture() named and the sources and search paths hold, by name, in the order they are asked - so a texture browser can show what there is to choose from without knowing where any of it lives. Scanned once and remembered with the rest. Authoring-side: it reads the directories, which is a question worth asking while a map is being made and not one a shipped game asks at all.
clear_cache()
static func clear_cache() -> void
Forget every remembered answer, so names are looked up afresh. For when the search paths change, or a texture appears while the editor is running. What add_texture() named is not an answer and stays.