feat: make pantry crises visible in the world

This commit is contained in:
Rijad Zuzo
2026-07-14 23:54:22 +02:00
parent c722e32a88
commit c23f6887c8
21 changed files with 800 additions and 73 deletions
+149
View File
@@ -0,0 +1,149 @@
class_name PantryStockVisual
extends Node3D
enum StockLevel { EMPTY, LOW, STOCKED }
const LOW_STOCK_THRESHOLD := 4.0
const REFILL_RESPONSE_START := Vector3(0.0, 0.08, -0.5)
const REFILL_RESPONSE_END := Vector3(0.0, 0.36, -0.5)
@onready var empty_root: Node3D = $EmptyStock
@onready var low_stock_root: Node3D = $LowStock
@onready var full_stock_root: Node3D = $FullStock
@onready var refill_response_root: Node3D = $RefillResponse
@onready var refill_response_visuals: Array[MeshInstance3D] = [
$RefillResponse/Ring,
$RefillResponse/MoteLeft,
$RefillResponse/MoteCenter,
$RefillResponse/MoteRight,
]
var displayed_amount := -1.0
var stock_level := StockLevel.EMPTY
var transition_tween: Tween
var refill_tween: Tween
func _ready() -> void:
refill_response_root.visible = false
func set_food_amount(amount: float, animate: bool = true) -> void:
var normalized_amount := maxf(amount, 0.0)
var next_level := _get_stock_level(normalized_amount)
if next_level == StockLevel.EMPTY:
_stop_refill_response()
var was_initialized := displayed_amount >= 0.0
var was_empty := was_initialized and displayed_amount <= 0.001
if was_initialized and next_level == stock_level:
displayed_amount = normalized_amount
return
var should_animate := animate and was_initialized
displayed_amount = normalized_amount
stock_level = next_level
if should_animate:
_transition_to_level(next_level)
else:
_apply_level_immediately(next_level)
if was_empty and normalized_amount > 0.001 and animate:
_play_refill_response()
func get_stock_level() -> StockLevel:
return stock_level
func _get_stock_level(amount: float) -> StockLevel:
if amount <= 0.001:
return StockLevel.EMPTY
if amount < LOW_STOCK_THRESHOLD:
return StockLevel.LOW
return StockLevel.STOCKED
func _apply_level_immediately(level: StockLevel) -> void:
_stop_transition()
_set_root_visible(empty_root, level == StockLevel.EMPTY)
_set_root_visible(low_stock_root, level in [StockLevel.LOW, StockLevel.STOCKED])
_set_root_visible(full_stock_root, level == StockLevel.STOCKED)
func _transition_to_level(level: StockLevel) -> void:
_stop_transition()
transition_tween = create_tween().set_parallel(true)
_transition_root(empty_root, level == StockLevel.EMPTY)
_transition_root(low_stock_root, level in [StockLevel.LOW, StockLevel.STOCKED])
_transition_root(full_stock_root, level == StockLevel.STOCKED)
transition_tween.chain().tween_callback(_finish_transition.bind(level))
func _transition_root(root: Node3D, should_show: bool) -> void:
if should_show:
if not root.visible:
root.visible = true
root.scale = Vector3.ONE * 0.62
(
transition_tween
. tween_property(root, "scale", Vector3.ONE, 0.28)
. set_trans(Tween.TRANS_BACK)
. set_ease(Tween.EASE_OUT)
)
return
if not root.visible:
return
(
transition_tween
. tween_property(root, "scale", Vector3.ONE * 0.18, 0.16)
. set_trans(Tween.TRANS_SINE)
. set_ease(Tween.EASE_IN)
)
func _finish_transition(level: StockLevel) -> void:
transition_tween = null
_set_root_visible(empty_root, level == StockLevel.EMPTY)
_set_root_visible(low_stock_root, level in [StockLevel.LOW, StockLevel.STOCKED])
_set_root_visible(full_stock_root, level == StockLevel.STOCKED)
func _set_root_visible(root: Node3D, is_visible: bool) -> void:
root.visible = is_visible
root.scale = Vector3.ONE
func _stop_transition() -> void:
if transition_tween != null:
transition_tween.kill()
transition_tween = null
func _play_refill_response() -> void:
_stop_refill_response()
refill_response_root.visible = true
refill_response_root.position = REFILL_RESPONSE_START
refill_response_root.scale = Vector3.ONE * 0.45
_set_refill_transparency(0.0)
refill_tween = create_tween()
refill_tween.set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT)
refill_tween.tween_property(refill_response_root, "scale", Vector3.ONE, 0.24)
refill_tween.parallel().tween_method(_set_refill_transparency, 0.0, 1.0, 0.18)
refill_tween.tween_property(refill_response_root, "position", REFILL_RESPONSE_END, 0.5)
refill_tween.parallel().tween_method(_set_refill_transparency, 1.0, 0.0, 0.5)
refill_tween.tween_callback(_stop_refill_response)
func _set_refill_transparency(value: float) -> void:
for visual in refill_response_visuals:
visual.transparency = clampf(1.0 - value, 0.0, 1.0)
func _stop_refill_response() -> void:
if refill_tween != null:
refill_tween.kill()
refill_tween = null
if refill_response_root == null:
return
refill_response_root.visible = false
refill_response_root.position = REFILL_RESPONSE_START
refill_response_root.scale = Vector3.ONE * 0.45
_set_refill_transparency(0.0)