Visual: sprinkle grass+flower decoration overlay (ElvGames bundle)
First visual pass: replaces nothing, adds a decoration TileMapLayer above Terrain that paints ~8% of cells with random overlay sprites pulled from FG_Grasslands_Spring.png. Mix of 3 grass-sprout variants, 3 white-with- colored-center flowers, and 2 scattered-grass-dot patches. ~540 cells populated on the 80x80 map; deterministic seed so layout is stable. The bundle's design assumes flat-color terrain + overlay sprites for visual richness — so this is the cheapest possible win that uses the art we own. Terrain base, walls, trees, pawns, UI all unchanged. Implementation lives in world.gd as _build_decoration_tileset() + _paint_decorations(); the Decoration TileMapLayer is added to world.tscn at z_index=0 (siblings render in tree order so it draws between Terrain and Floor). Tileset is built at runtime pointing at res://art/tiles/FG_Grasslands_Spring.png, mirroring the existing _build_placeholder_tileset pattern. Verified MCP runtime: world feels like a meadow now, no perf hit. Headless boot logs '[world] decoration: painted 541 overlay cells'. License: ElvGames Humble bundle — commercial use OK with credit (see docs/art.md). Credit string compilation is still an open audit item. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f435c3c467
commit
314c7a70b4
4 changed files with 111 additions and 0 deletions
BIN
art/tiles/FG_Grasslands_Spring.png
Normal file
BIN
art/tiles/FG_Grasslands_Spring.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 73 KiB |
40
art/tiles/FG_Grasslands_Spring.png.import
Normal file
40
art/tiles/FG_Grasslands_Spring.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://25dl87qxv8ks"
|
||||||
|
path="res://.godot/imported/FG_Grasslands_Spring.png-e9676b030c6767775ad374c0db805fa9.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://art/tiles/FG_Grasslands_Spring.png"
|
||||||
|
dest_files=["res://.godot/imported/FG_Grasslands_Spring.png-e9676b030c6767775ad374c0db805fa9.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
|
@ -85,6 +85,7 @@ const SEASON_TINTS: Dictionary = {
|
||||||
|
|
||||||
@onready var dark_overlay: CanvasModulate = $DarkOverlay
|
@onready var dark_overlay: CanvasModulate = $DarkOverlay
|
||||||
@onready var terrain_layer: TileMapLayer = $Terrain
|
@onready var terrain_layer: TileMapLayer = $Terrain
|
||||||
|
@onready var decoration_layer: TileMapLayer = $Decoration
|
||||||
@onready var floor_layer: TileMapLayer = $Floor
|
@onready var floor_layer: TileMapLayer = $Floor
|
||||||
@onready var wall_layer: TileMapLayer = $Wall
|
@onready var wall_layer: TileMapLayer = $Wall
|
||||||
@onready var designation_layer: TileMapLayer = $Designation
|
@onready var designation_layer: TileMapLayer = $Designation
|
||||||
|
|
@ -112,6 +113,11 @@ func _ready() -> void:
|
||||||
for layer in [terrain_layer, floor_layer, wall_layer, designation_layer, roof_layer, fog_layer]:
|
for layer in [terrain_layer, floor_layer, wall_layer, designation_layer, roof_layer, fog_layer]:
|
||||||
layer.tile_set = tileset
|
layer.tile_set = tileset
|
||||||
_paint_terrain()
|
_paint_terrain()
|
||||||
|
# Decoration overlay — sparse grass tufts + flowers from the ElvGames bundle.
|
||||||
|
# Lives on its own TileMapLayer above Terrain so a future season swap is one
|
||||||
|
# tileset reassignment.
|
||||||
|
decoration_layer.tile_set = _build_decoration_tileset()
|
||||||
|
_paint_decorations()
|
||||||
# Phase 5: removed _paint_sample_walls() — the old 8×8 stone ring rendered
|
# Phase 5: removed _paint_sample_walls() — the old 8×8 stone ring rendered
|
||||||
# only on the (now-hidden) Wall TileMap layer. With the rendering pivot
|
# only on the (now-hidden) Wall TileMap layer. With the rendering pivot
|
||||||
# to entity-based walls, that ring was invisible to the player but still
|
# to entity-based walls, that ring was invisible to the player but still
|
||||||
|
|
@ -285,6 +291,68 @@ func _paint_terrain() -> void:
|
||||||
terrain_layer.set_cell(Vector2i(x, y), PLACEHOLDER_SOURCE_ID, TILE_GRASS)
|
terrain_layer.set_cell(Vector2i(x, y), PLACEHOLDER_SOURCE_ID, TILE_GRASS)
|
||||||
|
|
||||||
|
|
||||||
|
# ── decoration overlay (ElvGames Grasslands sprinkle) ────────────────────────
|
||||||
|
|
||||||
|
## Source id for the decoration tileset (separate atlas from PLACEHOLDER_SOURCE_ID).
|
||||||
|
const DECORATION_SOURCE_ID: int = 10
|
||||||
|
|
||||||
|
## Atlas-tile coords inside FG_Grasslands_Spring.png for overlay variants.
|
||||||
|
## Each is a 16×16 sprite on transparent background, picked by visual eyeball
|
||||||
|
## (see /tmp/deco_review.png in the 2026-05-12 visual-pass session). Mix of
|
||||||
|
## small-grass sprouts, flowers, and scattered-tuft sprites — all sit cleanly
|
||||||
|
## on top of the base grass tile without occluding pawns.
|
||||||
|
const DECORATION_TILES: Array[Vector2i] = [
|
||||||
|
Vector2i(12, 0), # grass sprout A
|
||||||
|
Vector2i(13, 0), # grass sprout B
|
||||||
|
Vector2i(14, 0), # scattered grass dots — small
|
||||||
|
Vector2i(16, 0), # white flower / red center
|
||||||
|
Vector2i(17, 0), # white flower / yellow center
|
||||||
|
Vector2i(18, 0), # white flower / purple center
|
||||||
|
Vector2i(16, 2), # scattered grass dots — medium
|
||||||
|
Vector2i(17, 2), # scattered grass dots — large
|
||||||
|
]
|
||||||
|
|
||||||
|
## How often a tile gets a decoration. 0.08 = ~8% density — visually rich but
|
||||||
|
## not so dense that pawns/items get hidden under it. Tunable; tested at
|
||||||
|
## ~500 decorations on the 80×80 map.
|
||||||
|
const DECORATION_DENSITY: float = 0.08
|
||||||
|
|
||||||
|
|
||||||
|
func _build_decoration_tileset() -> TileSet:
|
||||||
|
var ts := TileSet.new()
|
||||||
|
ts.tile_size = Vector2i(TILE_SIZE_PX, TILE_SIZE_PX)
|
||||||
|
|
||||||
|
var tex: Texture2D = load("res://art/tiles/FG_Grasslands_Spring.png")
|
||||||
|
if tex == null:
|
||||||
|
Audit.log("world", "decoration: FG_Grasslands_Spring.png not loadable; skipping overlay")
|
||||||
|
return ts
|
||||||
|
|
||||||
|
var src := TileSetAtlasSource.new()
|
||||||
|
src.texture = tex
|
||||||
|
src.texture_region_size = Vector2i(TILE_SIZE_PX, TILE_SIZE_PX)
|
||||||
|
for coord in DECORATION_TILES:
|
||||||
|
src.create_tile(coord)
|
||||||
|
ts.add_source(src, DECORATION_SOURCE_ID)
|
||||||
|
return ts
|
||||||
|
|
||||||
|
|
||||||
|
func _paint_decorations() -> void:
|
||||||
|
# Deterministic-ish seed so the layout is stable across runs (cosmetic only;
|
||||||
|
# not save-round-tripped). Mix world size into seed for "feels random but
|
||||||
|
# reproducible." Use a fresh RNG so we don't perturb the global state.
|
||||||
|
var rng := RandomNumberGenerator.new()
|
||||||
|
rng.seed = MAP_SIZE_TILES.x * 31 + MAP_SIZE_TILES.y * 17
|
||||||
|
var placed: int = 0
|
||||||
|
for x in MAP_SIZE_TILES.x:
|
||||||
|
for y in MAP_SIZE_TILES.y:
|
||||||
|
if rng.randf() >= DECORATION_DENSITY:
|
||||||
|
continue
|
||||||
|
var coord: Vector2i = DECORATION_TILES[rng.randi() % DECORATION_TILES.size()]
|
||||||
|
decoration_layer.set_cell(Vector2i(x, y), DECORATION_SOURCE_ID, coord)
|
||||||
|
placed += 1
|
||||||
|
Audit.log("world", "decoration: painted %d overlay cells" % placed)
|
||||||
|
|
||||||
|
|
||||||
func _paint_sample_walls() -> void:
|
func _paint_sample_walls() -> void:
|
||||||
var origin := Vector2i(36, 36)
|
var origin := Vector2i(36, 36)
|
||||||
var size: int = 8
|
var size: int = 8
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,9 @@ color = Color(1, 1, 1, 1)
|
||||||
[node name="Terrain" type="TileMapLayer" parent="."]
|
[node name="Terrain" type="TileMapLayer" parent="."]
|
||||||
z_index = 0
|
z_index = 0
|
||||||
|
|
||||||
|
[node name="Decoration" type="TileMapLayer" parent="."]
|
||||||
|
z_index = 0
|
||||||
|
|
||||||
[node name="Floor" type="TileMapLayer" parent="."]
|
[node name="Floor" type="TileMapLayer" parent="."]
|
||||||
z_index = 1
|
z_index = 1
|
||||||
visible = false
|
visible = false
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue