Kennungen, Kachelformen und Platzhalter für die Grafik

Jede Grafik auf dem Spielfeld bekommt eine Kennung. Art zeichnet sie im
gewählten Stil oder aus einem Bild gleichen Namens unter game/art.
ArtStyle zeichnet vorerst schlichte Platzhalter. TileShape beschreibt
die Nachbarn einer Kachel, damit Felsmassen außen rund und innen
geschlossen gezeichnet werden können. Die drei Stile Warm, Pastell und
Glimmen gibt es schon mit Namen, noch ohne eigene Zeichnungen.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
Steven Wroblewski
2026-10-02 11:24:45 +02:00
parent 5e9836c64a
commit 9921aba63a
19 changed files with 574 additions and 0 deletions
+3
View File
@@ -35,3 +35,6 @@ LOAD_ONLY_ALONE,Laden gibt es nur beim Spielen allein,Loading only works when pl
HOST_SAVES,Nur der Host speichert,Only the host saves
KEY_UNREADABLE,Dein Spielerschlüssel lässt sich gerade nicht lesen,Your player key cannot be read right now
SAVE_UNREADABLE,Der Spielstand lässt sich gerade nicht lesen,The save cannot be read right now
STYLE_WARM,Warm,Warm
STYLE_PASTEL,Pastell,Pastel
STYLE_GLOW,Glimmen,Glow
1 keys de en
35 HOST_SAVES Nur der Host speichert Only the host saves
36 KEY_UNREADABLE Dein Spielerschlüssel lässt sich gerade nicht lesen Your player key cannot be read right now
37 SAVE_UNREADABLE Der Spielstand lässt sich gerade nicht lesen The save cannot be read right now
38 STYLE_WARM Warm Warm
39 STYLE_PASTEL Pastell Pastel
40 STYLE_GLOW Glimmen Glow
+105
View File
@@ -0,0 +1,105 @@
class_name Art
extends RefCounted
## Every graphic on the playing field has an id. Art draws it in the chosen
## style, or from a picture whose file name is the id, so that an artist can
## replace a graphic without changing code: res://art/terrain/ground.png
## replaces the drawing of GROUND.
## The style changed, or pictures were loaded. Everything drawn by Art has to
## be drawn again.
signal changed
const GROUND := &"terrain/ground"
const SOFT_ROCK := &"terrain/soft_rock"
const HARD_ROCK := &"terrain/hard_rock"
const BEDROCK := &"terrain/bedrock"
## The cracks of a tile that took hits, drawn over the tile.
const CRACKS := &"terrain/cracks"
## The burst where a pickaxe lands.
const HIT := &"effect/hit"
const FIGURE := &"figure"
## The outline of the tile under the mouse.
const MARKER := &"marker"
## Graphics of later milestones, shown in the showcase of the style samples.
const DEAD_SOIL := &"soil/dead"
const LIVING_SOIL := &"soil/living"
const MOSS := &"plant/moss"
const VINE := &"plant/vine"
const FIBER_PLANT := &"plant/fiber"
const STONE := &"item/stone"
const GRAZER := &"animal/grazer"
const RUIN := &"ruin"
## Every id, in the order the showcase and the checks go through them.
const IDS: Array[StringName] = [
GROUND, SOFT_ROCK, HARD_ROCK, BEDROCK, CRACKS, HIT, FIGURE, MARKER,
DEAD_SOIL, LIVING_SOIL, MOSS, VINE, FIBER_PLANT, STONE, GRAZER, RUIN,
]
## The folder where pictures that replace drawings are looked for.
const OVERRIDE_DIR := "res://art"
## The styles to choose from, in the order the game offers them.
var styles: Array[ArtStyle] = [WarmStyle.new(), PastelStyle.new(), GlowStyle.new()]
## The style everything is drawn in.
var style: ArtStyle = styles[0]
var _overrides: Dictionary[StringName, Texture2D] = {}
## The keys of the styles, as LaunchOptions and the settings name them.
static func style_keys() -> Array[StringName]:
return [WarmStyle.KEY, PastelStyle.KEY, GlowStyle.KEY]
## Draws everything in the style named `key` from now on. Returns false, and
## changes nothing, if no style has that key.
func select(key: StringName) -> bool:
for candidate in styles:
if candidate.key() == key:
if candidate != style:
style = candidate
changed.emit()
return true
return false
## Switches to the style after the current one, and from the last to the
## first.
func select_next() -> void:
style = styles[(styles.find(style) + 1) % styles.size()]
changed.emit()
## Uses the picture `<directory>/<id>.png` instead of the drawing, for every
## id that has one. Pictures loaded earlier are dropped.
func load_overrides(directory: String) -> void:
_overrides.clear()
for id in IDS:
var texture := _load_texture(directory.path_join(String(id) + ".png"))
if texture != null:
_overrides[id] = texture
changed.emit()
## True if a picture replaces the drawing of `id`.
func has_override(id: StringName) -> bool:
return _overrides.has(id)
## Draws the graphic `id` into `rect` on `canvas`. Call it only while
## `canvas` draws, from its _draw(). `state` carries what the graphic shows,
## for example how far a plant has grown; see the styles for the entries.
func draw(canvas: CanvasItem, id: StringName, rect: Rect2, state: Dictionary = {}) -> void:
var texture: Texture2D = _overrides.get(id)
if texture != null:
canvas.draw_texture_rect(texture, rect, false)
else:
style.draw(canvas, id, rect, state)
static func _load_texture(path: String) -> Texture2D:
if path.begins_with("res://"):
return load(path) as Texture2D if ResourceLoader.exists(path) else null
if not FileAccess.file_exists(path):
return null
var image := Image.load_from_file(path)
return ImageTexture.create_from_image(image) if image != null else null
+1
View File
@@ -0,0 +1 @@
uid://dxmii13634y22
+192
View File
@@ -0,0 +1,192 @@
class_name ArtStyle
extends RefCounted
## One look of the playing field. Draws every graphic that Art names, with
## code. This base class draws plain placeholders; each style overrides the
## drawing of every id.
##
## The entries of `state`, by id:
## - GROUND, SOFT_ROCK, HARD_ROCK, BEDROCK, DEAD_SOIL and LIVING_SOIL:
## `solid`, a TileShape mask of the neighbours that are rock or the edge of
## the map; `living`, for LIVING_SOIL, the mask of living neighbours;
## `variant`, from TileShape.variant
## - CRACKS: `hits`, 1 or more; `variant`
## - HIT: `age`, from 0 when the pickaxe lands to 1 when the burst is over;
## `variant`
## - FIGURE: `player`, the player's number or -1; `facing`, 1.0 to the
## right or -1.0 to the left; `step`, an angle that grows while the figure
## walks; `moving`
## - MARKER: `reachable`
## - MOSS: `variant`; `time`, in seconds
## - VINE: `points`, the path of the vine; `time`
## - FIBER_PLANT: `growth`, from 0 (a seed) to 1 (grown); `time`
## - GRAZER: `hop`, from 0 to 1 during one hop; `facing`
## - RUIN: `awake`, from 0 (asleep) to 1 (awake); `time`
## - STONE: nothing
## The name of the style on the command line and in the settings. The base
## class is no style to choose and has none.
func key() -> StringName:
return &""
## The key of the style's name in the texts, or an empty text for the base
## class.
func title_key() -> String:
return ""
## Draws the graphic `id` into `rect` on `canvas`, while `canvas` draws.
func draw(canvas: CanvasItem, id: StringName, rect: Rect2, state: Dictionary) -> void:
match id:
Art.GROUND:
_ground(canvas, rect, state)
Art.SOFT_ROCK:
_soft_rock(canvas, rect, state)
Art.HARD_ROCK:
_hard_rock(canvas, rect, state)
Art.BEDROCK:
_bedrock(canvas, rect, state)
Art.CRACKS:
_cracks(canvas, rect, state)
Art.HIT:
_hit(canvas, rect, state)
Art.FIGURE:
_figure(canvas, rect, state)
Art.MARKER:
_marker(canvas, rect, state)
Art.DEAD_SOIL:
_dead_soil(canvas, rect, state)
Art.LIVING_SOIL:
_living_soil(canvas, rect, state)
Art.MOSS:
_moss(canvas, rect, state)
Art.VINE:
_vine(canvas, rect, state)
Art.FIBER_PLANT:
_fiber_plant(canvas, rect, state)
Art.STONE:
_stone(canvas, rect, state)
Art.GRAZER:
_grazer(canvas, rect, state)
Art.RUIN:
_ruin(canvas, rect, state)
_:
push_error("no drawing for the graphic %s" % id)
## The colour of the figure of `player`. Players keep their colour; -1, a
## player not in the world yet, gets the first.
func player_color(player: int) -> Color:
return Color(0.95, 0.85, 0.6) if player <= 0 else Color(0.55, 0.8, 0.95)
func _ground(canvas: CanvasItem, rect: Rect2, _state: Dictionary) -> void:
canvas.draw_rect(rect, Color(0.42, 0.36, 0.28))
func _soft_rock(canvas: CanvasItem, rect: Rect2, _state: Dictionary) -> void:
canvas.draw_rect(rect, Color(0.56, 0.55, 0.52))
func _hard_rock(canvas: CanvasItem, rect: Rect2, _state: Dictionary) -> void:
canvas.draw_rect(rect, Color(0.33, 0.33, 0.37))
func _bedrock(canvas: CanvasItem, rect: Rect2, _state: Dictionary) -> void:
canvas.draw_rect(rect, Color(0.12, 0.12, 0.14))
func _cracks(canvas: CanvasItem, rect: Rect2, state: Dictionary) -> void:
var hits: int = state.get("hits", 1)
var inset := rect.grow(-3.0)
for line in hits:
var part := (line + 1.0) / (hits + 1.0)
var from := Vector2(inset.position.x, inset.position.y + inset.size.y * part)
var to := Vector2(inset.end.x, inset.position.y + inset.size.y * (1.0 - part))
canvas.draw_line(from, to, Color(0.1, 0.08, 0.06, 0.8), 1.0)
func _hit(canvas: CanvasItem, rect: Rect2, state: Dictionary) -> void:
var age: float = state.get("age", 0.0)
for chip in 4:
var direction := Vector2.RIGHT.rotated(TAU * chip / 4.0 + 0.4)
var at := rect.get_center() + direction * rect.size.x * 0.6 * age
canvas.draw_rect(Rect2(at - Vector2(1, 1), Vector2(2, 2)), Color(0.8, 0.8, 0.75, 1.0 - age))
func _figure(canvas: CanvasItem, rect: Rect2, state: Dictionary) -> void:
var radius := rect.size.x * 0.3
canvas.draw_circle(rect.get_center(), radius + 1.0, Color(0.15, 0.1, 0.05))
canvas.draw_circle(rect.get_center(), radius, player_color(state.get("player", -1)))
func _marker(canvas: CanvasItem, rect: Rect2, state: Dictionary) -> void:
var color := Color(0.4, 1.0, 0.4) if state.get("reachable", false) else Color(1.0, 0.4, 0.4)
canvas.draw_rect(rect, color, false, 1.0)
func _dead_soil(canvas: CanvasItem, rect: Rect2, _state: Dictionary) -> void:
canvas.draw_rect(rect, Color(0.45, 0.42, 0.38))
func _living_soil(canvas: CanvasItem, rect: Rect2, _state: Dictionary) -> void:
canvas.draw_rect(rect, Color(0.35, 0.5, 0.25))
func _moss(canvas: CanvasItem, rect: Rect2, _state: Dictionary) -> void:
canvas.draw_circle(rect.get_center(), rect.size.x * 0.3, Color(0.3, 0.65, 0.3))
func _vine(canvas: CanvasItem, _rect: Rect2, state: Dictionary) -> void:
var points: PackedVector2Array = state.get("points", PackedVector2Array())
if points.size() >= 2:
canvas.draw_polyline(points, Color(0.25, 0.6, 0.25), 2.0)
func _fiber_plant(canvas: CanvasItem, rect: Rect2, state: Dictionary) -> void:
var growth: float = state.get("growth", 1.0)
var foot := Vector2(rect.get_center().x, rect.end.y)
canvas.draw_line(foot, foot - Vector2(0, rect.size.y * growth), Color(0.4, 0.7, 0.3), 2.0)
func _stone(canvas: CanvasItem, rect: Rect2, _state: Dictionary) -> void:
canvas.draw_circle(rect.get_center(), rect.size.x * 0.4, Color(0.6, 0.6, 0.58))
func _grazer(canvas: CanvasItem, rect: Rect2, state: Dictionary) -> void:
var lift := sin(PI * float(state.get("hop", 0.0))) * rect.size.y * 0.3
canvas.draw_circle(rect.get_center() - Vector2(0, lift), rect.size.x * 0.35, Color(0.92, 0.9, 0.85))
func _ruin(canvas: CanvasItem, rect: Rect2, state: Dictionary) -> void:
canvas.draw_rect(rect, Color(0.5, 0.5, 0.48))
var awake: float = state.get("awake", 0.0)
canvas.draw_circle(rect.get_center(), rect.size.x * 0.2, Color(1.0, 0.85, 0.3, awake))
# Helpers for the styles.
## A share from 0 to 1 that is the same for the same `variant` and `salt`.
static func share(variant: int, salt: int) -> float:
return float(TileShape.variant(Vector2i(variant & 0xffff, salt)) % 10007) / 10007.0
## A gentle swing from -1 to 1, slow and different for every `variant`.
static func sway(time: float, variant: int, speed := 1.3) -> float:
return sin(time * speed + share(variant, 7) * TAU)
## The point at `part` of the way along `points`, from 0 to 1.
static func along(points: PackedVector2Array, part: float) -> Vector2:
var total := 0.0
for i in points.size() - 1:
total += points[i].distance_to(points[i + 1])
var left := clampf(part, 0.0, 1.0) * total
for i in points.size() - 1:
var length := points[i].distance_to(points[i + 1])
if left <= length:
return points[i].lerp(points[i + 1], left / length if length > 0.0 else 0.0)
left -= length
return points[points.size() - 1]
+1
View File
@@ -0,0 +1 @@
uid://o2dqirvunvw4
+14
View File
@@ -0,0 +1,14 @@
class_name GlowStyle
extends ArtStyle
## A dim, cool world in which everything living shines and glows.
## Until it draws pixel art, it draws the placeholders of ArtStyle.
const KEY := &"glow"
func key() -> StringName:
return KEY
func title_key() -> String:
return "STYLE_GLOW"
+1
View File
@@ -0,0 +1 @@
uid://ce6e46e8mwi8f
+14
View File
@@ -0,0 +1,14 @@
class_name PastelStyle
extends ArtStyle
## Soft, light colours with outlines in a darker shade of what they surround.
## Until it draws pixel art, it draws the placeholders of ArtStyle.
const KEY := &"pastel"
func key() -> StringName:
return KEY
func title_key() -> String:
return "STYLE_PASTEL"
+1
View File
@@ -0,0 +1 @@
uid://dr2fry8j3yai8
+49
View File
@@ -0,0 +1,49 @@
class_name TileShape
extends RefCounted
## Neighbourhoods on the tile grid, so that a style can draw each tile to fit
## the tiles around it, and a stable number per tile for small variations.
## Bits of a neighbour mask, clockwise from north.
const N := 1
const NE := 2
const E := 4
const SE := 8
const S := 16
const SW := 32
const W := 64
const NW := 128
## Where the neighbour of each bit lies, in the order of the bits.
const OFFSETS: Array[Vector2i] = [
Vector2i(0, -1), Vector2i(1, -1), Vector2i(1, 0), Vector2i(1, 1),
Vector2i(0, 1), Vector2i(-1, 1), Vector2i(-1, 0), Vector2i(-1, -1),
]
## One byte per tile of `codes`: 1 if `members` holds its code, else 0.
static func flags(codes: PackedByteArray, members: Array[int]) -> PackedByteArray:
var result := PackedByteArray()
result.resize(codes.size())
for index in codes.size():
result[index] = 1 if members.has(codes[index]) else 0
return result
## The neighbours of `tile` whose flag is 1, as a mask of the bits above, on
## a map of `width` by `height` tiles. Beyond the edge of the map, every
## neighbour counts as set if `outside_set` is true.
static func mask(tile_flags: PackedByteArray, width: int, height: int, tile: Vector2i, outside_set: bool) -> int:
var result := 0
for bit in 8:
var at := tile + OFFSETS[bit]
var inside := at.x >= 0 and at.y >= 0 and at.x < width and at.y < height
if (inside and tile_flags[at.y * width + at.x] == 1) or (not inside and outside_set):
result |= 1 << bit
return result
## A number from 0 to 2147483647 that is the same for `tile` every time and
## looks random from one tile to the next.
static func variant(tile: Vector2i) -> int:
var h := (tile.x * 374761393 + tile.y * 668265263) & 0x7fffffff
h = ((h ^ (h >> 13)) * 1274126177) & 0x7fffffff
return h ^ (h >> 16)
+1
View File
@@ -0,0 +1 @@
uid://dfj1ix7h3wky2
+14
View File
@@ -0,0 +1,14 @@
class_name WarmStyle
extends ArtStyle
## Warm, full colours and dark outlines, close to the classic farming games.
## Until it draws pixel art, it draws the placeholders of ArtStyle.
const KEY := &"warm"
func key() -> StringName:
return KEY
func title_key() -> String:
return "STYLE_WARM"
+1
View File
@@ -0,0 +1 @@
uid://7kb2jlqpvvoe
+110
View File
@@ -0,0 +1,110 @@
extends TestCase
## Draws every graphic of `art` twice, with `state` and with nothing, while
## it draws.
class Probe extends Node2D:
var art: Art
var state: Dictionary
var drawn := 0
func _draw() -> void:
for id in Art.IDS:
var rect := Rect2(0, 0, 48, 48) if id == Art.RUIN else Rect2(16, 16, 16, 16)
art.draw(self, id, rect, state)
art.draw(self, id, rect, {})
drawn += 1
## Everything a graphic can be asked to show, so that each drawing runs
## through all its parts.
static func _full_state() -> Dictionary:
return {
"solid": TileShape.N | TileShape.W | TileShape.NE, "living": TileShape.E | TileShape.S,
"variant": 123456, "hits": 2, "age": 0.5, "player": 1, "facing": -1.0, "step": 1.0,
"moving": true, "reachable": true, "time": 2.5, "growth": 0.9, "hop": 0.5, "awake": 0.7,
"points": PackedVector2Array([Vector2(0, 8), Vector2(10, 4), Vector2(24, 9)]),
}
## Lets `art` draw every graphic once and returns how many were drawn.
func _draw_everything(art: Art) -> int:
var probe := Probe.new()
probe.art = art
probe.state = _full_state()
tree.root.add_child(probe)
await tree.process_frame
await tree.process_frame
var drawn := probe.drawn
tree.root.remove_child(probe)
probe.free()
return drawn
func test_every_style_draws_every_graphic() -> void:
var art := Art.new()
for style in art.styles:
art.select(style.key())
check_eq(await _draw_everything(art), Art.IDS.size(), "%s draws them all" % style.key())
art.style = ArtStyle.new()
check_eq(await _draw_everything(art), Art.IDS.size(), "the placeholders too")
func test_the_styles_are_offered_in_a_fixed_order() -> void:
var art := Art.new()
var keys: Array[StringName] = []
for style in art.styles:
keys.append(style.key())
check_eq(keys, Art.style_keys(), "the keys in the order of the styles")
check_eq(keys, [WarmStyle.KEY, PastelStyle.KEY, GlowStyle.KEY], "warm, pastel, glow")
check_eq(art.style, art.styles[0], "the first one is chosen at first")
func test_a_style_is_chosen_by_its_key() -> void:
var art := Art.new()
var changes := [0]
art.changed.connect(func() -> void: changes[0] += 1)
check(art.select(PastelStyle.KEY), "the pastel style exists")
check_eq(art.style.key(), PastelStyle.KEY, "and is chosen")
check(art.select(PastelStyle.KEY), "choosing it again works")
check_eq(changes[0], 1, "only a real change is announced")
check(not art.select(&"watercolour"), "an unknown key is refused")
check_eq(art.style.key(), PastelStyle.KEY, "and changes nothing")
func test_the_next_style_follows_and_the_first_comes_after_the_last() -> void:
var art := Art.new()
var seen: Array[StringName] = []
for i in art.styles.size() + 1:
seen.append(art.style.key())
art.select_next()
check_eq(seen, [WarmStyle.KEY, PastelStyle.KEY, GlowStyle.KEY, WarmStyle.KEY], "round and round")
func test_every_style_has_a_name_in_every_language() -> void:
for translation in I18n.load_translations(I18n.TEXTS_FILE):
for style in Art.new().styles:
var title := String(translation.get_message(style.title_key()))
check(not title.is_empty(), "%s in %s" % [style.title_key(), translation.locale])
func test_a_picture_named_after_an_id_replaces_its_drawing() -> void:
var directory := TestFiles.temporary_dir("art")
DirAccess.make_dir_recursive_absolute(directory.path_join("terrain"))
var image := Image.create(16, 16, false, Image.FORMAT_RGBA8)
image.fill(Color.RED)
image.save_png(directory.path_join("terrain/ground.png"))
var art := Art.new()
art.load_overrides(directory)
check(art.has_override(Art.GROUND), "the ground has a picture")
check(not art.has_override(Art.SOFT_ROCK), "soft rock has none")
check_eq(await _draw_everything(art), Art.IDS.size(), "the picture is drawn like the rest")
art.load_overrides(directory.path_join("missing"))
check(not art.has_override(Art.GROUND), "loading again drops the picture")
TestFiles.remove_dir(directory)
func test_without_the_folder_of_pictures_everything_is_drawn() -> void:
var art := Art.new()
art.load_overrides(Art.OVERRIDE_DIR)
for id in Art.IDS:
check(not art.has_override(id), "no picture for %s" % id)
+1
View File
@@ -0,0 +1 @@
uid://dmuqgq1uvws3x
+26
View File
@@ -0,0 +1,26 @@
extends TestCase
## The helpers that every style draws with.
func test_a_point_can_be_found_along_a_path() -> void:
var path := PackedVector2Array([Vector2(0, 0), Vector2(10, 0), Vector2(10, 10)])
check_eq(ArtStyle.along(path, 0.0), Vector2(0, 0), "the start")
check_eq(ArtStyle.along(path, 0.5), Vector2(10, 0), "halfway is the bend")
check_eq(ArtStyle.along(path, 0.75), Vector2(10, 5), "three quarters")
check_eq(ArtStyle.along(path, 1.0), Vector2(10, 10), "the end")
check_eq(ArtStyle.along(path, 2.0), Vector2(10, 10), "never beyond the end")
func test_shares_lie_between_zero_and_one_and_stay_the_same() -> void:
for variant in [0, 1, 99, 123456, 2147483647]:
for salt in 4:
var share := ArtStyle.share(variant, salt)
check(share >= 0.0 and share < 1.0, "share of %d and %d: %f" % [variant, salt, share])
check_eq(ArtStyle.share(variant, salt), share, "the same again")
func test_a_sway_swings_gently_and_differs_between_things() -> void:
for tenth in 100:
var swing := ArtStyle.sway(tenth * 0.1, 7)
check(swing >= -1.0 and swing <= 1.0, "between left and right at %.1f s: %f" % [tenth * 0.1, swing])
check(ArtStyle.sway(0.0, 1) != ArtStyle.sway(0.0, 2), "two things do not swing alike")
+1
View File
@@ -0,0 +1 @@
uid://cbkbq72ph6wdn
+38
View File
@@ -0,0 +1,38 @@
extends TestCase
## A map of three by three tiles, row by row, with rock (1) in the middle and
## in the top right corner.
const CODES := [0, 0, 1, 0, 1, 0, 0, 0, 0]
func _flags() -> PackedByteArray:
return TileShape.flags(PackedByteArray(CODES), [1])
func test_flags_mark_the_tiles_of_the_given_codes() -> void:
check_eq(_flags(), PackedByteArray([0, 0, 1, 0, 1, 0, 0, 0, 0]), "rock is marked")
check_eq(TileShape.flags(PackedByteArray([0, 1, 2]), [1, 2]), PackedByteArray([0, 1, 1]), "several codes")
func test_a_mask_holds_the_marked_neighbours_clockwise_from_north() -> void:
check_eq(TileShape.mask(_flags(), 3, 3, Vector2i(1, 1), false), TileShape.NE, "the middle sees the corner")
check_eq(TileShape.mask(_flags(), 3, 3, Vector2i(1, 2), false), TileShape.N, "below the middle")
check_eq(TileShape.mask(_flags(), 3, 3, Vector2i(2, 1), false), TileShape.N | TileShape.W, "right of the middle")
check_eq(TileShape.mask(_flags(), 3, 3, Vector2i(0, 0), false), TileShape.SE, "the top left corner")
func test_beyond_the_edge_neighbours_count_as_marked_if_asked() -> void:
var corner := TileShape.mask(_flags(), 3, 3, Vector2i(0, 0), true)
var outside := TileShape.N | TileShape.NE | TileShape.NW | TileShape.W | TileShape.SW
check_eq(corner, outside | TileShape.SE, "five neighbours lie outside")
func test_variants_stay_the_same_and_differ_between_neighbours() -> void:
check_eq(TileShape.variant(Vector2i(4, 7)), TileShape.variant(Vector2i(4, 7)), "the same every time")
var seen := {}
for y in 4:
for x in 4:
var variant := TileShape.variant(Vector2i(x, y))
check(variant >= 0, "never negative: %d" % variant)
seen[variant] = true
check_eq(seen.size(), 16, "sixteen tiles, sixteen variants")
+1
View File
@@ -0,0 +1 @@
uid://c4ecwk4sri50q