diff --git a/art/sprites/FG_Tree_Stages.png b/art/sprites/FG_Tree_Stages.png new file mode 100644 index 0000000..9b4881d Binary files /dev/null and b/art/sprites/FG_Tree_Stages.png differ diff --git a/art/sprites/FG_Tree_Stages.png.import b/art/sprites/FG_Tree_Stages.png.import new file mode 100644 index 0000000..a22b563 --- /dev/null +++ b/art/sprites/FG_Tree_Stages.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bws0uhalpfbln" +path="res://.godot/imported/FG_Tree_Stages.png-620ce94d31b96e88794dc081cb63ea2e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://art/sprites/FG_Tree_Stages.png" +dest_files=["res://.godot/imported/FG_Tree_Stages.png-620ce94d31b96e88794dc081cb63ea2e.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 diff --git a/scenes/entities/tree.gd b/scenes/entities/tree.gd index a77d505..8d5919f 100644 --- a/scenes/entities/tree.gd +++ b/scenes/entities/tree.gd @@ -81,6 +81,17 @@ const _TREE_VARIANT_W: int = 64 const _TREE_VARIANT_H: int = 80 const _TREE_SILHOUETTES: int = 4 # silhouettes per atlas (columns) +## Growth-stage atlas — 128×32, 8 columns × 1 row of 16×32 cells, left-to-right +## progressively larger trees. Used for sub-mature stages so the player sees +## a proper sapling → small tree silhouette change instead of a scaled-down +## mature canopy. Stage MATURE keeps using _TREE_TEXES above (the full 64×80 +## canopy). +const _STAGE_TEX: Texture2D = preload("res://art/sprites/FG_Tree_Stages.png") +const _STAGE_CELL_W: int = 16 +const _STAGE_CELL_H: int = 32 +## Atlas column to use per growth_stage. Stage 3 (MATURE) is unused here. +const _STAGE_COLS: Array[int] = [0, 1, 3, -1] + # ── lifecycle ───────────────────────────────────────────────────────────────── @@ -94,36 +105,33 @@ func _ready() -> void: ## Rebuild the Sprite2D child to match the current growth_stage. -## Sapling (stage 0): no Sprite2D — rendered procedurally in _draw(). -## Young (1) → scale 0.35, Growing (2) → 0.65, Mature (3) → 1.0. -## Any existing "Sprite" child is removed first so re-calls don't stack. +## Stages 0-2 use _STAGE_TEX (a dedicated growth-stage atlas with progressively +## larger trees per cell). Stage 3 (Mature) uses the seasonal full-canopy +## atlases. Any existing "Sprite" child is removed first so re-calls don't stack. func _refresh_sprite() -> void: var old := get_node_or_null("Sprite") if old != null: old.queue_free() - if growth_stage == STAGE_SAPLING: - # No Sprite2D for saplings — all rendering done in _draw(). - queue_redraw() - return - var scale_map: Array[float] = [1.0, 0.35, 0.65, 1.0] # indexed by stage - var sprite_scale: float = scale_map[growth_stage] var sprite := Sprite2D.new() sprite.name = "Sprite" - var hash_seed: int = tile.x * 31 + tile.y * 17 - var silhouette: int = hash_seed % _TREE_SILHOUETTES - # Independent hash mix for season so neighbouring tiles don't all match. - var season: int = ((hash_seed / _TREE_SILHOUETTES) + tile.x * 7 + tile.y * 11) % _TREE_TEXES.size() - sprite.texture = _TREE_TEXES[season] sprite.region_enabled = true - sprite.region_rect = Rect2(silhouette * _TREE_VARIANT_W, 0, _TREE_VARIANT_W, _TREE_VARIANT_H) sprite.centered = true - # Lift the sprite up so its bottom edge sits at the tile's bottom row. - # Sprite center is at offset.y; sprite half-height is _TREE_VARIANT_H/2 = 40. - # We want bottom edge at +8 (tile bottom) → center at 8 - 40 = -32. - sprite.offset = Vector2(0, -32) - sprite.scale = Vector2(sprite_scale, sprite_scale) - # Render behind pawns/items that are at higher z_index; trees live at z=0. sprite.z_index = 0 + if growth_stage < STAGE_MATURE: + # Sub-mature: 16×32 cell from _STAGE_TEX. Bottom edge at tile bottom + # (+8); sprite half-height 16 → centre offset y = 8 - 16 = -8. + var col: int = _STAGE_COLS[growth_stage] + sprite.texture = _STAGE_TEX + sprite.region_rect = Rect2(col * _STAGE_CELL_W, 0, _STAGE_CELL_W, _STAGE_CELL_H) + sprite.offset = Vector2(0, -8) + else: + # Mature: full 64×80 seasonal canopy. Bottom at +8 → centre at -32. + var hash_seed: int = tile.x * 31 + tile.y * 17 + var silhouette: int = hash_seed % _TREE_SILHOUETTES + var season: int = ((hash_seed / _TREE_SILHOUETTES) + tile.x * 7 + tile.y * 11) % _TREE_TEXES.size() + sprite.texture = _TREE_TEXES[season] + sprite.region_rect = Rect2(silhouette * _TREE_VARIANT_W, 0, _TREE_VARIANT_W, _TREE_VARIANT_H) + sprite.offset = Vector2(0, -32) add_child(sprite) queue_redraw() @@ -280,19 +288,12 @@ static func from_dict(d: Dictionary) -> Dictionary: # ── render ──────────────────────────────────────────────────────────────────── func _draw() -> void: - # Sapling stage: draw a procedural sprout — no atlas sprite available. - # Three small green leaf-dots clustered above a thin brown stem. - if growth_stage == STAGE_SAPLING: - # Ghost tint for pending-plant saplings so the player can tell it's - # waiting for a pawn to build it. - var alpha := 0.55 if pending_plant else 1.0 - # Stem - draw_line(Vector2(0.0, 6.0), Vector2(0.0, 0.0), Color(0.35, 0.22, 0.10, alpha), 1.5) - # Three leaf dots - draw_circle(Vector2(0.0, -2.0), 2.5, Color(0.30, 0.65, 0.20, alpha)) - draw_circle(Vector2(-3.0, 1.0), 1.8, Color(0.25, 0.58, 0.18, alpha)) - draw_circle(Vector2(3.0, 0.5), 1.8, Color(0.28, 0.62, 0.19, alpha)) - return + # Ghost tint for pending-plant saplings — apply via modulate on the sprite + # child instead of drawing extra overlay shapes here. + if pending_plant and growth_stage == STAGE_SAPLING: + var s := get_node_or_null("Sprite") + if s != null: + s.modulate = Color(1, 1, 1, 0.55) # Mature / growing stages: canopy + trunk come from the Sprite2D child. # This _draw renders only the chop-progress notch overlaid on the trunk. diff --git a/scenes/world/world.gd b/scenes/world/world.gd index d861ba9..d36e2b0 100644 --- a/scenes/world/world.gd +++ b/scenes/world/world.gd @@ -98,9 +98,9 @@ const HAUL_SWEEP_INTERVAL_TICKS: int = 100 # WildGrowth — spontaneous sapling spawning on eligible grass tiles. # 1200 ticks = 1 in-game hour at 20 Hz (20 ticks/s × 60 s/min = 1200 ticks/min, # but 1 in-game minute = 20 ticks at 1× so 1 hour = 1200 ticks at 1×). -const WILD_GROWTH_INTERVAL: int = 1200 -const WILD_GROWTH_SPAWN_PROBABILITY: float = 0.30 -const MAP_TREE_LIMIT: int = 60 +const WILD_GROWTH_INTERVAL: int = 3000 # ~2.5 in-game hours between attempts +const WILD_GROWTH_SPAWN_PROBABILITY: float = 0.12 # 12% chance per attempt +const MAP_TREE_LIMIT: int = 80 # Rejection-sample attempts before giving up for this tick. const WILD_GROWTH_MAX_ATTEMPTS: int = 10