feat: make pantry crises visible in the world
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user