feat: define reusable animal archetypes
This commit is contained in:
@@ -9,10 +9,13 @@ signal routine_arrived(animal_id: StringName, site_id: StringName, arrival_posit
|
||||
signal navigation_failed(animal_id: StringName, site_id: StringName)
|
||||
|
||||
static var _all: Array[AnimalNode] = []
|
||||
static var _core_animal_catalog: AnimalCatalog
|
||||
|
||||
@export var animal_id: StringName
|
||||
@export var display_name := "Animal"
|
||||
@export var species_id: StringName = SimulationIds.SPECIES_GOAT
|
||||
@export var animal_definition_id: StringName
|
||||
@export var species_id: StringName
|
||||
@export var supported_action_ids: Array[StringName] = []
|
||||
@export_range(0.0, 100.0, 0.5) var initial_hunger := 70.0
|
||||
@export var initial_enabled := true
|
||||
@export var can_npcs_feed := true
|
||||
@@ -35,9 +38,20 @@ var navigation_target := Vector3.INF
|
||||
var navigation_request_id := 0
|
||||
var path_pending := false
|
||||
var has_reported_navigation_result := true
|
||||
var _animal_definition: AnimalDefinition
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not _ensure_animal_definition():
|
||||
push_error(
|
||||
(
|
||||
"AnimalNode at %s has invalid animal_definition_id '%s'"
|
||||
% [get_path(), animal_definition_id]
|
||||
)
|
||||
)
|
||||
initial_enabled = false
|
||||
_update_presentation()
|
||||
return
|
||||
if animal_id.is_empty():
|
||||
push_error("AnimalNode at %s has empty animal_id" % get_path())
|
||||
initial_enabled = false
|
||||
@@ -69,7 +83,11 @@ func _exit_tree() -> void:
|
||||
|
||||
|
||||
func bind_state(animal_state: AnimalStateRecord) -> bool:
|
||||
if animal_state == null or animal_state.get_animal_id() != animal_id:
|
||||
if (
|
||||
animal_state == null
|
||||
or animal_state.get_animal_id() != animal_id
|
||||
or animal_state.get_animal_definition_id() != animal_definition_id
|
||||
):
|
||||
return false
|
||||
_disconnect_state()
|
||||
state = animal_state
|
||||
@@ -111,7 +129,42 @@ func get_interaction_position() -> Vector3:
|
||||
|
||||
|
||||
func supports_action(action_id: StringName) -> bool:
|
||||
return action_id == SimulationIds.ACTION_FEED_ANIMAL
|
||||
return action_id in supported_action_ids
|
||||
|
||||
|
||||
func apply_animal_definition(definition: AnimalDefinition) -> bool:
|
||||
if definition == null or not definition.validate().is_empty():
|
||||
return false
|
||||
if state != null and state.get_animal_definition_id() != definition.animal_definition_id:
|
||||
return false
|
||||
_animal_definition = definition
|
||||
animal_definition_id = definition.animal_definition_id
|
||||
species_id = definition.species_id
|
||||
supported_action_ids = definition.supported_action_ids.duplicate()
|
||||
move_speed = definition.movement_speed
|
||||
var visual := get_node_or_null("Visual")
|
||||
if visual != null and visual.has_method("apply_animal_definition"):
|
||||
return bool(visual.apply_animal_definition(definition))
|
||||
return true
|
||||
|
||||
|
||||
func get_animal_definition() -> AnimalDefinition:
|
||||
return _animal_definition
|
||||
|
||||
|
||||
func _ensure_animal_definition() -> bool:
|
||||
if (
|
||||
_animal_definition != null
|
||||
and _animal_definition.animal_definition_id == animal_definition_id
|
||||
):
|
||||
return true
|
||||
if animal_definition_id.is_empty():
|
||||
return false
|
||||
if _core_animal_catalog == null:
|
||||
_core_animal_catalog = AnimalCatalog.create_core()
|
||||
if _core_animal_catalog == null:
|
||||
return false
|
||||
return apply_animal_definition(_core_animal_catalog.get_definition(animal_definition_id))
|
||||
|
||||
|
||||
func get_hunger() -> float:
|
||||
|
||||
@@ -6,7 +6,7 @@ static var _all: Array[AnimalRoutineSite] = []
|
||||
|
||||
@export var site_id: StringName
|
||||
@export var display_name := "Animal site"
|
||||
@export var species_id: StringName = SimulationIds.SPECIES_GOAT
|
||||
@export var species_id: StringName
|
||||
@export var resident_animal_id: StringName
|
||||
@export var debug_label_enabled := false
|
||||
|
||||
|
||||
@@ -1,316 +1,12 @@
|
||||
[gd_scene load_steps=24 format=3]
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://world/animals/animal_node.gd" id="1_animal"]
|
||||
[ext_resource type="Script" path="res://world/animals/goat/cozy_goat_visual.gd" id="2_visual"]
|
||||
[ext_resource type="PackedScene" path="res://world/animals/grazer/cozy_grazer.tscn" id="1_grazer"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="Material_wool"]
|
||||
albedo_color = Color(0.88, 0.82, 0.67, 1)
|
||||
roughness = 0.96
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="Material_face"]
|
||||
albedo_color = Color(0.58, 0.43, 0.29, 1)
|
||||
roughness = 0.92
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="Material_dark"]
|
||||
albedo_color = Color(0.13, 0.09, 0.065, 1)
|
||||
roughness = 0.9
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="Material_horn"]
|
||||
albedo_color = Color(0.78, 0.68, 0.5, 1)
|
||||
roughness = 0.86
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="Material_collar"]
|
||||
albedo_color = Color(0.12, 0.5, 0.5, 1)
|
||||
roughness = 0.72
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="Material_hunger"]
|
||||
transparency = 1
|
||||
shading_mode = 0
|
||||
albedo_color = Color(0.98, 0.62, 0.22, 0.9)
|
||||
emission_enabled = true
|
||||
emission = Color(0.92, 0.36, 0.08, 1)
|
||||
emission_energy_multiplier = 0.32
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="Material_fed"]
|
||||
transparency = 1
|
||||
shading_mode = 0
|
||||
albedo_color = Color(0.72, 0.92, 0.5, 0.92)
|
||||
emission_enabled = true
|
||||
emission = Color(0.45, 0.78, 0.28, 1)
|
||||
emission_energy_multiplier = 0.38
|
||||
|
||||
[sub_resource type="CapsuleMesh" id="Mesh_body"]
|
||||
material = SubResource("Material_wool")
|
||||
radius = 0.48
|
||||
height = 1.55
|
||||
radial_segments = 12
|
||||
rings = 4
|
||||
|
||||
[sub_resource type="SphereMesh" id="Mesh_fluff"]
|
||||
material = SubResource("Material_wool")
|
||||
radius = 0.42
|
||||
height = 0.72
|
||||
radial_segments = 12
|
||||
rings = 6
|
||||
|
||||
[sub_resource type="CapsuleMesh" id="Mesh_leg"]
|
||||
material = SubResource("Material_face")
|
||||
radius = 0.105
|
||||
height = 0.62
|
||||
radial_segments = 8
|
||||
rings = 3
|
||||
|
||||
[sub_resource type="BoxMesh" id="Mesh_hoof"]
|
||||
material = SubResource("Material_dark")
|
||||
size = Vector3(0.2, 0.13, 0.28)
|
||||
|
||||
[sub_resource type="SphereMesh" id="Mesh_head"]
|
||||
material = SubResource("Material_face")
|
||||
radius = 0.34
|
||||
height = 0.58
|
||||
radial_segments = 12
|
||||
rings = 6
|
||||
|
||||
[sub_resource type="SphereMesh" id="Mesh_snout"]
|
||||
material = SubResource("Material_wool")
|
||||
radius = 0.21
|
||||
height = 0.32
|
||||
radial_segments = 10
|
||||
rings = 5
|
||||
|
||||
[sub_resource type="SphereMesh" id="Mesh_ear"]
|
||||
material = SubResource("Material_face")
|
||||
radius = 0.24
|
||||
height = 0.42
|
||||
radial_segments = 10
|
||||
rings = 5
|
||||
|
||||
[sub_resource type="SphereMesh" id="Mesh_eye"]
|
||||
material = SubResource("Material_dark")
|
||||
radius = 0.045
|
||||
height = 0.075
|
||||
radial_segments = 8
|
||||
rings = 4
|
||||
|
||||
[sub_resource type="CylinderMesh" id="Mesh_horn"]
|
||||
material = SubResource("Material_horn")
|
||||
top_radius = 0.035
|
||||
bottom_radius = 0.09
|
||||
height = 0.42
|
||||
radial_segments = 8
|
||||
|
||||
[sub_resource type="CylinderMesh" id="Mesh_beard"]
|
||||
material = SubResource("Material_wool")
|
||||
top_radius = 0.015
|
||||
bottom_radius = 0.11
|
||||
height = 0.34
|
||||
radial_segments = 8
|
||||
|
||||
[sub_resource type="CylinderMesh" id="Mesh_tail"]
|
||||
material = SubResource("Material_wool")
|
||||
top_radius = 0.07
|
||||
bottom_radius = 0.12
|
||||
height = 0.42
|
||||
radial_segments = 8
|
||||
|
||||
[sub_resource type="TorusMesh" id="Mesh_collar"]
|
||||
material = SubResource("Material_collar")
|
||||
inner_radius = 0.27
|
||||
outer_radius = 0.32
|
||||
rings = 14
|
||||
ring_segments = 8
|
||||
|
||||
[sub_resource type="TorusMesh" id="Mesh_hunger_bowl"]
|
||||
material = SubResource("Material_hunger")
|
||||
inner_radius = 0.18
|
||||
outer_radius = 0.27
|
||||
rings = 14
|
||||
ring_segments = 8
|
||||
|
||||
[sub_resource type="SphereMesh" id="Mesh_hunger_food"]
|
||||
material = SubResource("Material_hunger")
|
||||
radius = 0.09
|
||||
height = 0.14
|
||||
radial_segments = 8
|
||||
rings = 4
|
||||
|
||||
[sub_resource type="TorusMesh" id="Mesh_fed_ring"]
|
||||
material = SubResource("Material_fed")
|
||||
inner_radius = 0.32
|
||||
outer_radius = 0.42
|
||||
rings = 16
|
||||
ring_segments = 8
|
||||
|
||||
[sub_resource type="SphereMesh" id="Mesh_fed_mote"]
|
||||
material = SubResource("Material_fed")
|
||||
radius = 0.075
|
||||
height = 0.13
|
||||
radial_segments = 8
|
||||
rings = 4
|
||||
|
||||
[node name="CozyGoat" type="Node3D"]
|
||||
script = ExtResource("1_animal")
|
||||
[node name="CozyGoat" instance=ExtResource("1_grazer")]
|
||||
animal_definition_id = &"domestic_goat"
|
||||
animal_id = &"goat_dunja"
|
||||
display_name = "Dunja"
|
||||
species_id = &"goat"
|
||||
initial_hunger = 40.0
|
||||
initial_routine_site_id = &"dunja_shelter"
|
||||
initial_next_routine_tick = 8
|
||||
|
||||
[node name="Visual" type="Node3D" parent="."]
|
||||
script = ExtResource("2_visual")
|
||||
|
||||
[node name="Body" type="MeshInstance3D" parent="Visual"]
|
||||
position = Vector3(0, 0.95, 0)
|
||||
rotation_degrees = Vector3(90, 0, 0)
|
||||
mesh = SubResource("Mesh_body")
|
||||
|
||||
[node name="FluffFront" type="MeshInstance3D" parent="Visual"]
|
||||
position = Vector3(0, 1.02, 0.42)
|
||||
scale = Vector3(1.08, 1.05, 1)
|
||||
mesh = SubResource("Mesh_fluff")
|
||||
|
||||
[node name="FluffBack" type="MeshInstance3D" parent="Visual"]
|
||||
position = Vector3(0, 1.02, -0.42)
|
||||
scale = Vector3(1.08, 1.05, 1)
|
||||
mesh = SubResource("Mesh_fluff")
|
||||
|
||||
[node name="LegFrontLeft" type="MeshInstance3D" parent="Visual"]
|
||||
position = Vector3(-0.3, 0.43, 0.48)
|
||||
mesh = SubResource("Mesh_leg")
|
||||
|
||||
[node name="Hoof" type="MeshInstance3D" parent="Visual/LegFrontLeft"]
|
||||
position = Vector3(0, -0.29, 0.035)
|
||||
mesh = SubResource("Mesh_hoof")
|
||||
|
||||
[node name="LegFrontRight" type="MeshInstance3D" parent="Visual"]
|
||||
position = Vector3(0.3, 0.43, 0.48)
|
||||
mesh = SubResource("Mesh_leg")
|
||||
|
||||
[node name="Hoof" type="MeshInstance3D" parent="Visual/LegFrontRight"]
|
||||
position = Vector3(0, -0.29, 0.035)
|
||||
mesh = SubResource("Mesh_hoof")
|
||||
|
||||
[node name="LegBackLeft" type="MeshInstance3D" parent="Visual"]
|
||||
position = Vector3(-0.3, 0.43, -0.48)
|
||||
mesh = SubResource("Mesh_leg")
|
||||
|
||||
[node name="Hoof" type="MeshInstance3D" parent="Visual/LegBackLeft"]
|
||||
position = Vector3(0, -0.29, 0.035)
|
||||
mesh = SubResource("Mesh_hoof")
|
||||
|
||||
[node name="LegBackRight" type="MeshInstance3D" parent="Visual"]
|
||||
position = Vector3(0.3, 0.43, -0.48)
|
||||
mesh = SubResource("Mesh_leg")
|
||||
|
||||
[node name="Hoof" type="MeshInstance3D" parent="Visual/LegBackRight"]
|
||||
position = Vector3(0, -0.29, 0.035)
|
||||
mesh = SubResource("Mesh_hoof")
|
||||
|
||||
[node name="HeadRoot" type="Node3D" parent="Visual"]
|
||||
position = Vector3(0, 1.28, 0.88)
|
||||
rotation_degrees = Vector3(-3, 0, 0)
|
||||
|
||||
[node name="Head" type="MeshInstance3D" parent="Visual/HeadRoot"]
|
||||
mesh = SubResource("Mesh_head")
|
||||
|
||||
[node name="Snout" type="MeshInstance3D" parent="Visual/HeadRoot"]
|
||||
position = Vector3(0, -0.08, 0.31)
|
||||
scale = Vector3(1.05, 0.72, 0.9)
|
||||
mesh = SubResource("Mesh_snout")
|
||||
|
||||
[node name="EyeLeft" type="MeshInstance3D" parent="Visual/HeadRoot"]
|
||||
position = Vector3(-0.18, 0.08, 0.285)
|
||||
mesh = SubResource("Mesh_eye")
|
||||
|
||||
[node name="EyeRight" type="MeshInstance3D" parent="Visual/HeadRoot"]
|
||||
position = Vector3(0.18, 0.08, 0.285)
|
||||
mesh = SubResource("Mesh_eye")
|
||||
|
||||
[node name="EarLeft" type="MeshInstance3D" parent="Visual/HeadRoot"]
|
||||
position = Vector3(-0.35, 0.13, 0)
|
||||
rotation_degrees = Vector3(8, 0, 67)
|
||||
scale = Vector3(0.82, 0.28, 1)
|
||||
mesh = SubResource("Mesh_ear")
|
||||
|
||||
[node name="EarRight" type="MeshInstance3D" parent="Visual/HeadRoot"]
|
||||
position = Vector3(0.35, 0.13, 0)
|
||||
rotation_degrees = Vector3(8, 0, -67)
|
||||
scale = Vector3(0.82, 0.28, 1)
|
||||
mesh = SubResource("Mesh_ear")
|
||||
|
||||
[node name="HornLeft" type="MeshInstance3D" parent="Visual/HeadRoot"]
|
||||
position = Vector3(-0.17, 0.34, -0.06)
|
||||
rotation_degrees = Vector3(-24, 0, -20)
|
||||
mesh = SubResource("Mesh_horn")
|
||||
|
||||
[node name="HornRight" type="MeshInstance3D" parent="Visual/HeadRoot"]
|
||||
position = Vector3(0.17, 0.34, -0.06)
|
||||
rotation_degrees = Vector3(-24, 0, 20)
|
||||
mesh = SubResource("Mesh_horn")
|
||||
|
||||
[node name="Beard" type="MeshInstance3D" parent="Visual/HeadRoot"]
|
||||
position = Vector3(0, -0.35, 0.08)
|
||||
mesh = SubResource("Mesh_beard")
|
||||
|
||||
[node name="Collar" type="MeshInstance3D" parent="Visual/HeadRoot"]
|
||||
position = Vector3(0, -0.31, -0.16)
|
||||
rotation_degrees = Vector3(90, 0, 0)
|
||||
scale = Vector3(0.85, 0.85, 0.85)
|
||||
mesh = SubResource("Mesh_collar")
|
||||
|
||||
[node name="TailRoot" type="Node3D" parent="Visual"]
|
||||
position = Vector3(0, 1.16, -0.92)
|
||||
rotation_degrees = Vector3(60, 0, 0)
|
||||
|
||||
[node name="Tail" type="MeshInstance3D" parent="Visual/TailRoot"]
|
||||
position = Vector3(0, 0.18, 0)
|
||||
mesh = SubResource("Mesh_tail")
|
||||
|
||||
[node name="HungerCueRoot" type="Node3D" parent="Visual"]
|
||||
position = Vector3(0, 2.05, 0.18)
|
||||
|
||||
[node name="EmptyBowl" type="MeshInstance3D" parent="Visual/HungerCueRoot"]
|
||||
rotation_degrees = Vector3(72, 0, 0)
|
||||
scale = Vector3(1.1, 0.72, 1.1)
|
||||
mesh = SubResource("Mesh_hunger_bowl")
|
||||
|
||||
[node name="FoodMoteLeft" type="MeshInstance3D" parent="Visual/HungerCueRoot"]
|
||||
position = Vector3(-0.13, 0.22, 0)
|
||||
mesh = SubResource("Mesh_hunger_food")
|
||||
|
||||
[node name="FoodMoteRight" type="MeshInstance3D" parent="Visual/HungerCueRoot"]
|
||||
position = Vector3(0.13, 0.29, 0)
|
||||
scale = Vector3(0.82, 0.82, 0.82)
|
||||
mesh = SubResource("Mesh_hunger_food")
|
||||
|
||||
[node name="FedResponseRoot" type="Node3D" parent="Visual"]
|
||||
visible = false
|
||||
position = Vector3(0, 1.8, 0.12)
|
||||
scale = Vector3(0.2, 0.2, 0.2)
|
||||
|
||||
[node name="Halo" type="MeshInstance3D" parent="Visual/FedResponseRoot"]
|
||||
rotation_degrees = Vector3(74, 0, 0)
|
||||
mesh = SubResource("Mesh_fed_ring")
|
||||
|
||||
[node name="MoteLeft" type="MeshInstance3D" parent="Visual/FedResponseRoot"]
|
||||
position = Vector3(-0.34, 0.18, 0)
|
||||
mesh = SubResource("Mesh_fed_mote")
|
||||
|
||||
[node name="MoteRight" type="MeshInstance3D" parent="Visual/FedResponseRoot"]
|
||||
position = Vector3(0.34, 0.28, 0)
|
||||
scale = Vector3(0.82, 0.82, 0.82)
|
||||
mesh = SubResource("Mesh_fed_mote")
|
||||
|
||||
[node name="InteractionPoint" type="Marker3D" parent="."]
|
||||
position = Vector3(0, 0, 2.05)
|
||||
|
||||
[node name="DebugLabel" type="Label3D" parent="."]
|
||||
position = Vector3(0, 2.55, 0)
|
||||
billboard = 1
|
||||
no_depth_test = true
|
||||
font_size = 22
|
||||
outline_size = 5
|
||||
text = "Dunja"
|
||||
modulate = Color(1, 0.94, 0.78, 1)
|
||||
pixel_size = 0.006
|
||||
|
||||
@@ -1,90 +1 @@
|
||||
extends Node3D
|
||||
|
||||
const HUNGER_CUE_BASE_Y := 2.05
|
||||
const FED_RESPONSE_BASE_Y := 1.8
|
||||
|
||||
@onready var head_root: Node3D = $HeadRoot
|
||||
@onready var tail_root: Node3D = $TailRoot
|
||||
@onready var hunger_cue_root: Node3D = $HungerCueRoot
|
||||
@onready var fed_response_root: Node3D = $FedResponseRoot
|
||||
@onready var leg_front_left: Node3D = $LegFrontLeft
|
||||
@onready var leg_front_right: Node3D = $LegFrontRight
|
||||
@onready var leg_back_left: Node3D = $LegBackLeft
|
||||
@onready var leg_back_right: Node3D = $LegBackRight
|
||||
|
||||
var idle_phase := 0.0
|
||||
var is_hungry := false
|
||||
var is_moving := false
|
||||
var fed_tween: Tween
|
||||
var head_base_position: Vector3
|
||||
var visual_base_position: Vector3
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
head_base_position = head_root.position
|
||||
visual_base_position = position
|
||||
reset_transient_feedback()
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
idle_phase = fmod(idle_phase + delta, TAU)
|
||||
var breath := sin(idle_phase * 1.7) * 0.025
|
||||
var hungry_drop := -0.12 if is_hungry else 0.0
|
||||
var walking_bob := absf(sin(idle_phase * 5.4)) * 0.045 if is_moving else 0.0
|
||||
position.y = visual_base_position.y + walking_bob
|
||||
head_root.position.y = head_base_position.y + breath + hungry_drop
|
||||
head_root.rotation_degrees.x = (
|
||||
8.0 + sin(idle_phase * 1.2) * 2.0 if is_hungry else -3.0 + sin(idle_phase * 1.4) * 2.5
|
||||
)
|
||||
tail_root.rotation_degrees.z = sin(idle_phase * 3.1) * (4.0 if is_hungry else 13.0)
|
||||
var stride := sin(idle_phase * 5.4) * 14.0 if is_moving else 0.0
|
||||
leg_front_left.rotation_degrees.x = stride
|
||||
leg_back_right.rotation_degrees.x = stride
|
||||
leg_front_right.rotation_degrees.x = -stride
|
||||
leg_back_left.rotation_degrees.x = -stride
|
||||
if hunger_cue_root.visible:
|
||||
hunger_cue_root.position.y = HUNGER_CUE_BASE_Y + sin(idle_phase * 2.2) * 0.07
|
||||
|
||||
|
||||
func set_hungry(value: bool) -> void:
|
||||
is_hungry = value
|
||||
hunger_cue_root.visible = value
|
||||
|
||||
|
||||
func set_moving(value: bool) -> void:
|
||||
is_moving = value
|
||||
if not value:
|
||||
position = visual_base_position
|
||||
leg_front_left.rotation_degrees.x = 0.0
|
||||
leg_front_right.rotation_degrees.x = 0.0
|
||||
leg_back_left.rotation_degrees.x = 0.0
|
||||
leg_back_right.rotation_degrees.x = 0.0
|
||||
|
||||
|
||||
func reset_transient_feedback() -> void:
|
||||
if fed_tween != null:
|
||||
fed_tween.kill()
|
||||
fed_tween = null
|
||||
fed_response_root.visible = false
|
||||
fed_response_root.position.y = FED_RESPONSE_BASE_Y
|
||||
fed_response_root.scale = Vector3(0.2, 0.2, 0.2)
|
||||
|
||||
|
||||
func play_fed_response() -> void:
|
||||
reset_transient_feedback()
|
||||
fed_response_root.visible = true
|
||||
fed_tween = create_tween()
|
||||
fed_tween.set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT)
|
||||
fed_tween.tween_property(fed_response_root, "scale", Vector3.ONE, 0.34)
|
||||
fed_tween.parallel().tween_property(
|
||||
fed_response_root, "position:y", FED_RESPONSE_BASE_Y + 0.22, 0.34
|
||||
)
|
||||
fed_tween.set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN)
|
||||
fed_tween.tween_interval(0.7)
|
||||
fed_tween.tween_property(fed_response_root, "scale", Vector3(0.2, 0.2, 0.2), 0.42)
|
||||
fed_tween.finished.connect(_on_fed_response_finished)
|
||||
|
||||
|
||||
func _on_fed_response_finished() -> void:
|
||||
fed_response_root.visible = false
|
||||
fed_tween = null
|
||||
extends "res://world/animals/grazer/cozy_grazer_visual.gd"
|
||||
|
||||
@@ -0,0 +1,311 @@
|
||||
[gd_scene load_steps=24 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://world/animals/animal_node.gd" id="1_animal"]
|
||||
[ext_resource type="Script" path="res://world/animals/grazer/cozy_grazer_visual.gd" id="2_visual"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="Material_wool"]
|
||||
albedo_color = Color(0.88, 0.82, 0.67, 1)
|
||||
roughness = 0.96
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="Material_face"]
|
||||
albedo_color = Color(0.58, 0.43, 0.29, 1)
|
||||
roughness = 0.92
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="Material_dark"]
|
||||
albedo_color = Color(0.13, 0.09, 0.065, 1)
|
||||
roughness = 0.9
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="Material_horn"]
|
||||
albedo_color = Color(0.78, 0.68, 0.5, 1)
|
||||
roughness = 0.86
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="Material_collar"]
|
||||
albedo_color = Color(0.12, 0.5, 0.5, 1)
|
||||
roughness = 0.72
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="Material_hunger"]
|
||||
transparency = 1
|
||||
shading_mode = 0
|
||||
albedo_color = Color(0.98, 0.62, 0.22, 0.9)
|
||||
emission_enabled = true
|
||||
emission = Color(0.92, 0.36, 0.08, 1)
|
||||
emission_energy_multiplier = 0.32
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="Material_fed"]
|
||||
transparency = 1
|
||||
shading_mode = 0
|
||||
albedo_color = Color(0.72, 0.92, 0.5, 0.92)
|
||||
emission_enabled = true
|
||||
emission = Color(0.45, 0.78, 0.28, 1)
|
||||
emission_energy_multiplier = 0.38
|
||||
|
||||
[sub_resource type="CapsuleMesh" id="Mesh_body"]
|
||||
material = SubResource("Material_wool")
|
||||
radius = 0.48
|
||||
height = 1.55
|
||||
radial_segments = 12
|
||||
rings = 4
|
||||
|
||||
[sub_resource type="SphereMesh" id="Mesh_fluff"]
|
||||
material = SubResource("Material_wool")
|
||||
radius = 0.42
|
||||
height = 0.72
|
||||
radial_segments = 12
|
||||
rings = 6
|
||||
|
||||
[sub_resource type="CapsuleMesh" id="Mesh_leg"]
|
||||
material = SubResource("Material_face")
|
||||
radius = 0.105
|
||||
height = 0.62
|
||||
radial_segments = 8
|
||||
rings = 3
|
||||
|
||||
[sub_resource type="BoxMesh" id="Mesh_hoof"]
|
||||
material = SubResource("Material_dark")
|
||||
size = Vector3(0.2, 0.13, 0.28)
|
||||
|
||||
[sub_resource type="SphereMesh" id="Mesh_head"]
|
||||
material = SubResource("Material_face")
|
||||
radius = 0.34
|
||||
height = 0.58
|
||||
radial_segments = 12
|
||||
rings = 6
|
||||
|
||||
[sub_resource type="SphereMesh" id="Mesh_snout"]
|
||||
material = SubResource("Material_wool")
|
||||
radius = 0.21
|
||||
height = 0.32
|
||||
radial_segments = 10
|
||||
rings = 5
|
||||
|
||||
[sub_resource type="SphereMesh" id="Mesh_ear"]
|
||||
material = SubResource("Material_face")
|
||||
radius = 0.24
|
||||
height = 0.42
|
||||
radial_segments = 10
|
||||
rings = 5
|
||||
|
||||
[sub_resource type="SphereMesh" id="Mesh_eye"]
|
||||
material = SubResource("Material_dark")
|
||||
radius = 0.045
|
||||
height = 0.075
|
||||
radial_segments = 8
|
||||
rings = 4
|
||||
|
||||
[sub_resource type="CylinderMesh" id="Mesh_horn"]
|
||||
material = SubResource("Material_horn")
|
||||
top_radius = 0.035
|
||||
bottom_radius = 0.09
|
||||
height = 0.42
|
||||
radial_segments = 8
|
||||
|
||||
[sub_resource type="CylinderMesh" id="Mesh_beard"]
|
||||
material = SubResource("Material_wool")
|
||||
top_radius = 0.015
|
||||
bottom_radius = 0.11
|
||||
height = 0.34
|
||||
radial_segments = 8
|
||||
|
||||
[sub_resource type="CylinderMesh" id="Mesh_tail"]
|
||||
material = SubResource("Material_wool")
|
||||
top_radius = 0.07
|
||||
bottom_radius = 0.12
|
||||
height = 0.42
|
||||
radial_segments = 8
|
||||
|
||||
[sub_resource type="TorusMesh" id="Mesh_collar"]
|
||||
material = SubResource("Material_collar")
|
||||
inner_radius = 0.27
|
||||
outer_radius = 0.32
|
||||
rings = 14
|
||||
ring_segments = 8
|
||||
|
||||
[sub_resource type="TorusMesh" id="Mesh_hunger_bowl"]
|
||||
material = SubResource("Material_hunger")
|
||||
inner_radius = 0.18
|
||||
outer_radius = 0.27
|
||||
rings = 14
|
||||
ring_segments = 8
|
||||
|
||||
[sub_resource type="SphereMesh" id="Mesh_hunger_food"]
|
||||
material = SubResource("Material_hunger")
|
||||
radius = 0.09
|
||||
height = 0.14
|
||||
radial_segments = 8
|
||||
rings = 4
|
||||
|
||||
[sub_resource type="TorusMesh" id="Mesh_fed_ring"]
|
||||
material = SubResource("Material_fed")
|
||||
inner_radius = 0.32
|
||||
outer_radius = 0.42
|
||||
rings = 16
|
||||
ring_segments = 8
|
||||
|
||||
[sub_resource type="SphereMesh" id="Mesh_fed_mote"]
|
||||
material = SubResource("Material_fed")
|
||||
radius = 0.075
|
||||
height = 0.13
|
||||
radial_segments = 8
|
||||
rings = 4
|
||||
|
||||
[node name="CozyGrazer" type="Node3D"]
|
||||
script = ExtResource("1_animal")
|
||||
display_name = "Animal"
|
||||
|
||||
[node name="Visual" type="Node3D" parent="."]
|
||||
script = ExtResource("2_visual")
|
||||
|
||||
[node name="Body" type="MeshInstance3D" parent="Visual"]
|
||||
position = Vector3(0, 0.95, 0)
|
||||
rotation_degrees = Vector3(90, 0, 0)
|
||||
mesh = SubResource("Mesh_body")
|
||||
|
||||
[node name="FluffFront" type="MeshInstance3D" parent="Visual"]
|
||||
position = Vector3(0, 1.02, 0.42)
|
||||
scale = Vector3(1.08, 1.05, 1)
|
||||
mesh = SubResource("Mesh_fluff")
|
||||
|
||||
[node name="FluffBack" type="MeshInstance3D" parent="Visual"]
|
||||
position = Vector3(0, 1.02, -0.42)
|
||||
scale = Vector3(1.08, 1.05, 1)
|
||||
mesh = SubResource("Mesh_fluff")
|
||||
|
||||
[node name="LegFrontLeft" type="MeshInstance3D" parent="Visual"]
|
||||
position = Vector3(-0.3, 0.43, 0.48)
|
||||
mesh = SubResource("Mesh_leg")
|
||||
|
||||
[node name="Hoof" type="MeshInstance3D" parent="Visual/LegFrontLeft"]
|
||||
position = Vector3(0, -0.29, 0.035)
|
||||
mesh = SubResource("Mesh_hoof")
|
||||
|
||||
[node name="LegFrontRight" type="MeshInstance3D" parent="Visual"]
|
||||
position = Vector3(0.3, 0.43, 0.48)
|
||||
mesh = SubResource("Mesh_leg")
|
||||
|
||||
[node name="Hoof" type="MeshInstance3D" parent="Visual/LegFrontRight"]
|
||||
position = Vector3(0, -0.29, 0.035)
|
||||
mesh = SubResource("Mesh_hoof")
|
||||
|
||||
[node name="LegBackLeft" type="MeshInstance3D" parent="Visual"]
|
||||
position = Vector3(-0.3, 0.43, -0.48)
|
||||
mesh = SubResource("Mesh_leg")
|
||||
|
||||
[node name="Hoof" type="MeshInstance3D" parent="Visual/LegBackLeft"]
|
||||
position = Vector3(0, -0.29, 0.035)
|
||||
mesh = SubResource("Mesh_hoof")
|
||||
|
||||
[node name="LegBackRight" type="MeshInstance3D" parent="Visual"]
|
||||
position = Vector3(0.3, 0.43, -0.48)
|
||||
mesh = SubResource("Mesh_leg")
|
||||
|
||||
[node name="Hoof" type="MeshInstance3D" parent="Visual/LegBackRight"]
|
||||
position = Vector3(0, -0.29, 0.035)
|
||||
mesh = SubResource("Mesh_hoof")
|
||||
|
||||
[node name="HeadRoot" type="Node3D" parent="Visual"]
|
||||
position = Vector3(0, 1.28, 0.88)
|
||||
rotation_degrees = Vector3(-3, 0, 0)
|
||||
|
||||
[node name="Head" type="MeshInstance3D" parent="Visual/HeadRoot"]
|
||||
mesh = SubResource("Mesh_head")
|
||||
|
||||
[node name="Snout" type="MeshInstance3D" parent="Visual/HeadRoot"]
|
||||
position = Vector3(0, -0.08, 0.31)
|
||||
scale = Vector3(1.05, 0.72, 0.9)
|
||||
mesh = SubResource("Mesh_snout")
|
||||
|
||||
[node name="EyeLeft" type="MeshInstance3D" parent="Visual/HeadRoot"]
|
||||
position = Vector3(-0.18, 0.08, 0.285)
|
||||
mesh = SubResource("Mesh_eye")
|
||||
|
||||
[node name="EyeRight" type="MeshInstance3D" parent="Visual/HeadRoot"]
|
||||
position = Vector3(0.18, 0.08, 0.285)
|
||||
mesh = SubResource("Mesh_eye")
|
||||
|
||||
[node name="EarLeft" type="MeshInstance3D" parent="Visual/HeadRoot"]
|
||||
position = Vector3(-0.35, 0.13, 0)
|
||||
rotation_degrees = Vector3(8, 0, 67)
|
||||
scale = Vector3(0.82, 0.28, 1)
|
||||
mesh = SubResource("Mesh_ear")
|
||||
|
||||
[node name="EarRight" type="MeshInstance3D" parent="Visual/HeadRoot"]
|
||||
position = Vector3(0.35, 0.13, 0)
|
||||
rotation_degrees = Vector3(8, 0, -67)
|
||||
scale = Vector3(0.82, 0.28, 1)
|
||||
mesh = SubResource("Mesh_ear")
|
||||
|
||||
[node name="HornLeft" type="MeshInstance3D" parent="Visual/HeadRoot"]
|
||||
position = Vector3(-0.17, 0.34, -0.06)
|
||||
rotation_degrees = Vector3(-24, 0, -20)
|
||||
mesh = SubResource("Mesh_horn")
|
||||
|
||||
[node name="HornRight" type="MeshInstance3D" parent="Visual/HeadRoot"]
|
||||
position = Vector3(0.17, 0.34, -0.06)
|
||||
rotation_degrees = Vector3(-24, 0, 20)
|
||||
mesh = SubResource("Mesh_horn")
|
||||
|
||||
[node name="Beard" type="MeshInstance3D" parent="Visual/HeadRoot"]
|
||||
position = Vector3(0, -0.35, 0.08)
|
||||
mesh = SubResource("Mesh_beard")
|
||||
|
||||
[node name="Collar" type="MeshInstance3D" parent="Visual/HeadRoot"]
|
||||
position = Vector3(0, -0.31, -0.16)
|
||||
rotation_degrees = Vector3(90, 0, 0)
|
||||
scale = Vector3(0.85, 0.85, 0.85)
|
||||
mesh = SubResource("Mesh_collar")
|
||||
|
||||
[node name="TailRoot" type="Node3D" parent="Visual"]
|
||||
position = Vector3(0, 1.16, -0.92)
|
||||
rotation_degrees = Vector3(60, 0, 0)
|
||||
|
||||
[node name="Tail" type="MeshInstance3D" parent="Visual/TailRoot"]
|
||||
position = Vector3(0, 0.18, 0)
|
||||
mesh = SubResource("Mesh_tail")
|
||||
|
||||
[node name="HungerCueRoot" type="Node3D" parent="Visual"]
|
||||
position = Vector3(0, 2.05, 0.18)
|
||||
|
||||
[node name="EmptyBowl" type="MeshInstance3D" parent="Visual/HungerCueRoot"]
|
||||
rotation_degrees = Vector3(72, 0, 0)
|
||||
scale = Vector3(1.1, 0.72, 1.1)
|
||||
mesh = SubResource("Mesh_hunger_bowl")
|
||||
|
||||
[node name="FoodMoteLeft" type="MeshInstance3D" parent="Visual/HungerCueRoot"]
|
||||
position = Vector3(-0.13, 0.22, 0)
|
||||
mesh = SubResource("Mesh_hunger_food")
|
||||
|
||||
[node name="FoodMoteRight" type="MeshInstance3D" parent="Visual/HungerCueRoot"]
|
||||
position = Vector3(0.13, 0.29, 0)
|
||||
scale = Vector3(0.82, 0.82, 0.82)
|
||||
mesh = SubResource("Mesh_hunger_food")
|
||||
|
||||
[node name="FedResponseRoot" type="Node3D" parent="Visual"]
|
||||
visible = false
|
||||
position = Vector3(0, 1.8, 0.12)
|
||||
scale = Vector3(0.2, 0.2, 0.2)
|
||||
|
||||
[node name="Halo" type="MeshInstance3D" parent="Visual/FedResponseRoot"]
|
||||
rotation_degrees = Vector3(74, 0, 0)
|
||||
mesh = SubResource("Mesh_fed_ring")
|
||||
|
||||
[node name="MoteLeft" type="MeshInstance3D" parent="Visual/FedResponseRoot"]
|
||||
position = Vector3(-0.34, 0.18, 0)
|
||||
mesh = SubResource("Mesh_fed_mote")
|
||||
|
||||
[node name="MoteRight" type="MeshInstance3D" parent="Visual/FedResponseRoot"]
|
||||
position = Vector3(0.34, 0.28, 0)
|
||||
scale = Vector3(0.82, 0.82, 0.82)
|
||||
mesh = SubResource("Mesh_fed_mote")
|
||||
|
||||
[node name="InteractionPoint" type="Marker3D" parent="."]
|
||||
position = Vector3(0, 0, 2.05)
|
||||
|
||||
[node name="DebugLabel" type="Label3D" parent="."]
|
||||
position = Vector3(0, 2.55, 0)
|
||||
billboard = 1
|
||||
no_depth_test = true
|
||||
font_size = 22
|
||||
outline_size = 5
|
||||
text = "Animal"
|
||||
modulate = Color(1, 0.94, 0.78, 1)
|
||||
pixel_size = 0.006
|
||||
@@ -0,0 +1,125 @@
|
||||
extends Node3D
|
||||
|
||||
const HUNGER_CUE_BASE_Y := 2.05
|
||||
const FED_RESPONSE_BASE_Y := 1.8
|
||||
|
||||
@onready var head_root: Node3D = $HeadRoot
|
||||
@onready var tail_root: Node3D = $TailRoot
|
||||
@onready var hunger_cue_root: Node3D = $HungerCueRoot
|
||||
@onready var fed_response_root: Node3D = $FedResponseRoot
|
||||
@onready var leg_front_left: Node3D = $LegFrontLeft
|
||||
@onready var leg_front_right: Node3D = $LegFrontRight
|
||||
@onready var leg_back_left: Node3D = $LegBackLeft
|
||||
@onready var leg_back_right: Node3D = $LegBackRight
|
||||
@onready var body: Node3D = $Body
|
||||
@onready var fluff_front: Node3D = $FluffFront
|
||||
@onready var fluff_back: Node3D = $FluffBack
|
||||
@onready var horn_left: Node3D = $HeadRoot/HornLeft
|
||||
@onready var horn_right: Node3D = $HeadRoot/HornRight
|
||||
@onready var beard: Node3D = $HeadRoot/Beard
|
||||
|
||||
var idle_phase := 0.0
|
||||
var is_hungry := false
|
||||
var is_moving := false
|
||||
var fed_tween: Tween
|
||||
var head_base_position: Vector3
|
||||
var visual_base_position: Vector3
|
||||
var body_base_scale: Vector3
|
||||
var fluff_front_base_scale: Vector3
|
||||
var fluff_back_base_scale: Vector3
|
||||
var _animal_definition: AnimalDefinition
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
head_base_position = head_root.position
|
||||
visual_base_position = position
|
||||
body_base_scale = body.scale
|
||||
fluff_front_base_scale = fluff_front.scale
|
||||
fluff_back_base_scale = fluff_back.scale
|
||||
_apply_definition_presentation()
|
||||
reset_transient_feedback()
|
||||
|
||||
|
||||
func apply_animal_definition(definition: AnimalDefinition) -> bool:
|
||||
if definition == null or not definition.validate().is_empty():
|
||||
return false
|
||||
_animal_definition = definition
|
||||
if is_node_ready():
|
||||
_apply_definition_presentation()
|
||||
return true
|
||||
|
||||
|
||||
func _apply_definition_presentation() -> void:
|
||||
if _animal_definition == null:
|
||||
return
|
||||
scale = _animal_definition.visual_scale
|
||||
body.scale = body_base_scale * _animal_definition.body_scale
|
||||
fluff_front.scale = fluff_front_base_scale * _animal_definition.fluff_scale
|
||||
fluff_back.scale = fluff_back_base_scale * _animal_definition.fluff_scale
|
||||
horn_left.visible = _animal_definition.show_horns
|
||||
horn_right.visible = _animal_definition.show_horns
|
||||
beard.visible = _animal_definition.show_beard
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
idle_phase = fmod(idle_phase + delta, TAU)
|
||||
var breath := sin(idle_phase * 1.7) * 0.025
|
||||
var hungry_drop := -0.12 if is_hungry else 0.0
|
||||
var walking_bob := absf(sin(idle_phase * 5.4)) * 0.045 if is_moving else 0.0
|
||||
position.y = visual_base_position.y + walking_bob
|
||||
head_root.position.y = head_base_position.y + breath + hungry_drop
|
||||
head_root.rotation_degrees.x = (
|
||||
8.0 + sin(idle_phase * 1.2) * 2.0 if is_hungry else -3.0 + sin(idle_phase * 1.4) * 2.5
|
||||
)
|
||||
tail_root.rotation_degrees.z = sin(idle_phase * 3.1) * (4.0 if is_hungry else 13.0)
|
||||
var stride := sin(idle_phase * 5.4) * 14.0 if is_moving else 0.0
|
||||
leg_front_left.rotation_degrees.x = stride
|
||||
leg_back_right.rotation_degrees.x = stride
|
||||
leg_front_right.rotation_degrees.x = -stride
|
||||
leg_back_left.rotation_degrees.x = -stride
|
||||
if hunger_cue_root.visible:
|
||||
hunger_cue_root.position.y = HUNGER_CUE_BASE_Y + sin(idle_phase * 2.2) * 0.07
|
||||
|
||||
|
||||
func set_hungry(value: bool) -> void:
|
||||
is_hungry = value
|
||||
hunger_cue_root.visible = value
|
||||
|
||||
|
||||
func set_moving(value: bool) -> void:
|
||||
is_moving = value
|
||||
if not value:
|
||||
position = visual_base_position
|
||||
leg_front_left.rotation_degrees.x = 0.0
|
||||
leg_front_right.rotation_degrees.x = 0.0
|
||||
leg_back_left.rotation_degrees.x = 0.0
|
||||
leg_back_right.rotation_degrees.x = 0.0
|
||||
|
||||
|
||||
func reset_transient_feedback() -> void:
|
||||
if fed_tween != null:
|
||||
fed_tween.kill()
|
||||
fed_tween = null
|
||||
fed_response_root.visible = false
|
||||
fed_response_root.position.y = FED_RESPONSE_BASE_Y
|
||||
fed_response_root.scale = Vector3(0.2, 0.2, 0.2)
|
||||
|
||||
|
||||
func play_fed_response() -> void:
|
||||
reset_transient_feedback()
|
||||
fed_response_root.visible = true
|
||||
fed_tween = create_tween()
|
||||
fed_tween.set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT)
|
||||
fed_tween.tween_property(fed_response_root, "scale", Vector3.ONE, 0.34)
|
||||
fed_tween.parallel().tween_property(
|
||||
fed_response_root, "position:y", FED_RESPONSE_BASE_Y + 0.22, 0.34
|
||||
)
|
||||
fed_tween.set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN)
|
||||
fed_tween.tween_interval(0.7)
|
||||
fed_tween.tween_property(fed_response_root, "scale", Vector3(0.2, 0.2, 0.2), 0.42)
|
||||
fed_tween.finished.connect(_on_fed_response_finished)
|
||||
|
||||
|
||||
func _on_fed_response_finished() -> void:
|
||||
fed_response_root.visible = false
|
||||
fed_tween = null
|
||||
@@ -0,0 +1 @@
|
||||
uid://ckytaadvxm4sh
|
||||
Reference in New Issue
Block a user