Tree growth: dedicated stage atlas + tuned WildGrowth rate
Sub-mature stages (0/1/2) now use FG_Tree_Stages.png (a 128×32 atlas with 8 progressively-larger tree cells from the bundle's "Crops with Stages 03" pack). Stage 0 = tiny sprout (col 0), Stage 1 = small leaf (col 1), Stage 2 = small tree (col 3). Stage 3 (Mature) keeps the existing 64×80 seasonal canopy atlases. Visually distinct progression replaces the previous scale-down-the- mature-texture placeholder + procedural sapling dots. WildGrowth pacing tuned: INTERVAL 1200 → 3000, PROBABILITY 0.30 → 0.12, LIMIT 60 → 80. Previous values flooded the map with saplings within ~30 seconds of 12× play. New rate gives a slow but visible regrowth over a season at default speed. _draw simplified: removed procedural sapling fallback (atlas handles all stages now). Pending-plant ghosts get the alpha tint via sprite.modulate.
This commit is contained in:
parent
d98d2c2425
commit
5a6ec53b12
4 changed files with 78 additions and 37 deletions
BIN
art/sprites/FG_Tree_Stages.png
Normal file
BIN
art/sprites/FG_Tree_Stages.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 636 B |
40
art/sprites/FG_Tree_Stages.png.import
Normal file
40
art/sprites/FG_Tree_Stages.png.import
Normal file
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue