feat: add stylized villager presentation
This commit is contained in:
@@ -468,7 +468,10 @@ Tab-cycled inspector shows authoritative need values, task state, destination,
|
||||
decision branch, and utility scores. The existing carried-food prop and village
|
||||
panel expose immediate consequences. `F10` toggles the debug/cinematic
|
||||
presentation layer, while `F12` reloads the current scene as a repeatable
|
||||
simulation-garden demo reset.
|
||||
simulation-garden demo reset. The capsule NPCs have now been replaced by
|
||||
multi-part stylized villager placeholders with tunics, heads, hair, limbs,
|
||||
feet, and state-driven walk motion. Carried wood displays as a log bundle from
|
||||
authoritative inventory, matching the existing carried-food presentation.
|
||||
|
||||
### Exit condition
|
||||
|
||||
@@ -653,12 +656,20 @@ Completed:
|
||||
and waterfall widened; the fortress ridge landmark now has crenellations,
|
||||
a dedicated banner pole with larger banner, and three turrets for a more
|
||||
readable silhouette against the sky.
|
||||
16. Stylized villager readability and VFX cleanup: NPCs now use compact
|
||||
multi-part silhouettes with state-driven walk motion and visible carried
|
||||
logs. Waterfall mist is anchored to the waterfall instead of the village
|
||||
origin, duplicate wind layers are separated, and Metal smoke/wind
|
||||
materials retain restrained transparency.
|
||||
|
||||
Next:
|
||||
|
||||
1. Continue growing resource discovery with finite ResourceNode instances
|
||||
placed in meaningful contexts (from LEARNING_ROADMAP). See the roadmap
|
||||
for the next systems priority.
|
||||
1. Replace the remaining player capsule with a matching stylized presentation,
|
||||
then revisit runtime camera framing so the waterfall and village action
|
||||
remain readable in the same authored shot.
|
||||
2. Continue with the action precondition/material-cost contract and
|
||||
relationship consequence work in `LEARNING_ROADMAP.md` before expanding
|
||||
visual scope further.
|
||||
|
||||
Do not start with GIS data, a full city, a large asset pack, or more NPC
|
||||
mechanics. The next proof is a beautiful stage for the systems that already
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Simulation Garden 01
|
||||
|
||||
Captured: 2026-07-08
|
||||
Captured: 2026-07-09
|
||||
|
||||
## Files
|
||||
|
||||
@@ -50,3 +50,14 @@ The frame also makes the next presentation risks plain:
|
||||
|
||||
Use this baseline as the first runtime comparison image before adding more
|
||||
systems or expanding the terrain.
|
||||
|
||||
## July 9 visual follow-up
|
||||
|
||||
The baseline images now include the first stylized-villager pass. NPCs use
|
||||
multi-part silhouettes and real travel-driven limb motion instead of capsules;
|
||||
profession colors and props remain definition-driven, and authoritative wood
|
||||
inventory now appears as carried logs. The capture also fixes three effects
|
||||
that obscured the simulation on Metal: waterfall mist was incorrectly emitted
|
||||
at the village origin, ridge/valley wind systems overlapped, and chimney smoke
|
||||
ignored particle transparency. The player capsule and camera composition are
|
||||
the next visible presentation gaps.
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://clc1x0yydurdk"
|
||||
path="res://.godot/imported/jajce_lookdev_01.png-090cdbef692a973d175bce2e4bf10776.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://docs/baselines/jajce_lookdev_01.png"
|
||||
dest_files=["res://.godot/imported/jajce_lookdev_01.png-090cdbef692a973d175bce2e4bf10776.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
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 710 KiB After Width: | Height: | Size: 419 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dtr1bl6xe3pnk"
|
||||
path="res://.godot/imported/simulation_garden_01_cinematic.png-39560816a2b107dac16e5ba4e2c5dbb8.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://docs/baselines/simulation_garden_01_cinematic.png"
|
||||
dest_files=["res://.godot/imported/simulation_garden_01_cinematic.png-39560816a2b107dac16e5ba4e2c5dbb8.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
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 696 KiB After Width: | Height: | Size: 428 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://wmk4fykdlafb"
|
||||
path="res://.godot/imported/simulation_garden_01_debug.png-c2c8d5bb4081c8fa4b5fa05e512fa49e.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://docs/baselines/simulation_garden_01_debug.png"
|
||||
dest_files=["res://.godot/imported/simulation_garden_01_debug.png-c2c8d5bb4081c8fa4b5fa05e512fa49e.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
|
||||
+69
-8
@@ -13,9 +13,16 @@ const DEBUG_LOGS := true
|
||||
|
||||
@onready var nav_agent: NavigationAgent3D = $NavigationAgent3D
|
||||
@onready var body_mesh: MeshInstance3D = $MeshInstance3D
|
||||
@onready var head_mesh: MeshInstance3D = $Head
|
||||
@onready var hair_mesh: MeshInstance3D = $Hair
|
||||
@onready var left_arm: MeshInstance3D = $ArmLeft
|
||||
@onready var right_arm: MeshInstance3D = $ArmRight
|
||||
@onready var left_leg: MeshInstance3D = $LegLeft
|
||||
@onready var right_leg: MeshInstance3D = $LegRight
|
||||
@onready var profession_prop: MeshInstance3D = $ProfessionProp
|
||||
@onready var profession_label: Label3D = $ProfessionLabel
|
||||
@onready var carried_food_visual: MeshInstance3D = $CarriedFood
|
||||
@onready var carried_wood_visual: Node3D = $CarriedWood
|
||||
@onready var task_glyph_root: Node3D = $TaskGlyphRoot
|
||||
@onready var task_glyph: MeshInstance3D = $TaskGlyphRoot/TaskGlyph
|
||||
|
||||
@@ -35,6 +42,8 @@ var path_pending := false
|
||||
var is_dead_visual := false
|
||||
var dead_material := StandardMaterial3D.new()
|
||||
var debug_overlay_visible := true
|
||||
var walk_phase := 0.0
|
||||
var glyph_phase := 0.0
|
||||
|
||||
|
||||
func debug_log(message: String) -> void:
|
||||
@@ -46,16 +55,62 @@ func _ready() -> void:
|
||||
dead_material.albedo_color = Color(0.25, 0.25, 0.25, 1.0)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if is_dead_visual:
|
||||
return
|
||||
glyph_phase = fmod(glyph_phase + delta * 2.2, TAU)
|
||||
if task_glyph_root.visible:
|
||||
task_glyph_root.position.y = 2.35 + sin(glyph_phase) * 0.045
|
||||
task_glyph_root.rotation.y = glyph_phase * 0.35
|
||||
|
||||
var horizontal_speed := Vector2(velocity.x, velocity.z).length()
|
||||
var blend := minf(delta * 9.0, 1.0)
|
||||
if horizontal_speed > 0.1:
|
||||
walk_phase = fmod(walk_phase + delta * horizontal_speed * 3.8, TAU)
|
||||
var swing := sin(walk_phase)
|
||||
left_leg.rotation_degrees.x = lerpf(left_leg.rotation_degrees.x, swing * 28.0, blend)
|
||||
right_leg.rotation_degrees.x = lerpf(right_leg.rotation_degrees.x, -swing * 28.0, blend)
|
||||
left_arm.rotation_degrees.x = lerpf(left_arm.rotation_degrees.x, -swing * 20.0, blend)
|
||||
right_arm.rotation_degrees.x = lerpf(right_arm.rotation_degrees.x, swing * 20.0, blend)
|
||||
var step_bob := absf(sin(walk_phase * 2.0)) * 0.035
|
||||
body_mesh.position.y = lerpf(body_mesh.position.y, 1.05 + step_bob, blend)
|
||||
head_mesh.position.y = lerpf(head_mesh.position.y, 1.68 + step_bob, blend)
|
||||
hair_mesh.position.y = lerpf(hair_mesh.position.y, 1.9 + step_bob, blend)
|
||||
else:
|
||||
left_leg.rotation_degrees.x = lerpf(left_leg.rotation_degrees.x, 0.0, blend)
|
||||
right_leg.rotation_degrees.x = lerpf(right_leg.rotation_degrees.x, 0.0, blend)
|
||||
left_arm.rotation_degrees.x = lerpf(left_arm.rotation_degrees.x, 0.0, blend)
|
||||
right_arm.rotation_degrees.x = lerpf(right_arm.rotation_degrees.x, 0.0, blend)
|
||||
body_mesh.position.y = lerpf(body_mesh.position.y, 1.05, blend)
|
||||
head_mesh.position.y = lerpf(head_mesh.position.y, 1.68, blend)
|
||||
hair_mesh.position.y = lerpf(hair_mesh.position.y, 1.9, blend)
|
||||
|
||||
|
||||
func setup_from_sim(npc: SimNPC) -> void:
|
||||
sim_id = npc.id
|
||||
npc_name = npc.npc_name
|
||||
profession = npc.profession
|
||||
name = "NPC_%s_%s" % [sim_id, npc_name]
|
||||
_apply_personal_details()
|
||||
_apply_profession_presentation()
|
||||
set_carried_food_visible(npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD) > 0.0)
|
||||
set_carried_wood_visible(npc.get_inventory_amount(SimulationIds.RESOURCE_WOOD) > 0.0)
|
||||
set_task_presentation(npc.current_task, npc.task_state)
|
||||
|
||||
|
||||
func _apply_personal_details() -> void:
|
||||
var hair_colors := [
|
||||
Color(0.16, 0.09, 0.055, 1.0),
|
||||
Color(0.29, 0.16, 0.075, 1.0),
|
||||
Color(0.09, 0.065, 0.05, 1.0),
|
||||
Color(0.42, 0.27, 0.12, 1.0),
|
||||
]
|
||||
var hair_material := StandardMaterial3D.new()
|
||||
hair_material.albedo_color = hair_colors[maxi(sim_id, 0) % hair_colors.size()]
|
||||
hair_material.roughness = 0.9
|
||||
hair_mesh.material_override = hair_material
|
||||
|
||||
|
||||
func _apply_profession_presentation() -> void:
|
||||
var definition := SimulationDefinitions.get_profession(profession)
|
||||
if definition == null:
|
||||
@@ -67,6 +122,8 @@ func _apply_profession_presentation() -> void:
|
||||
body_material.albedo_color = definition.visual_color
|
||||
body_material.roughness = 0.82
|
||||
body_mesh.material_override = body_material
|
||||
left_arm.material_override = body_material
|
||||
right_arm.material_override = body_material
|
||||
|
||||
var prop_material := StandardMaterial3D.new()
|
||||
prop_material.albedo_color = definition.prop_color
|
||||
@@ -117,6 +174,10 @@ func set_carried_food_visible(is_visible: bool) -> void:
|
||||
carried_food_visual.visible = is_visible and not is_dead_visual
|
||||
|
||||
|
||||
func set_carried_wood_visible(is_visible: bool) -> void:
|
||||
carried_wood_visual.visible = is_visible and not is_dead_visual
|
||||
|
||||
|
||||
func set_task_presentation(action_id: StringName, task_state: StringName) -> void:
|
||||
if task_glyph_root == null or task_glyph == null:
|
||||
return
|
||||
@@ -264,6 +325,7 @@ func _report_navigation_failure_once() -> void:
|
||||
func apply_dead_visual_state() -> void:
|
||||
is_dead_visual = true
|
||||
carried_food_visual.visible = false
|
||||
carried_wood_visual.visible = false
|
||||
profession_prop.visible = false
|
||||
profession_label.visible = false
|
||||
task_glyph_root.visible = false
|
||||
@@ -278,9 +340,8 @@ func apply_dead_visual_state() -> void:
|
||||
scale = Vector3(0.8, 0.25, 1.2)
|
||||
rotation_degrees.x = 90.0
|
||||
|
||||
for child in get_children():
|
||||
if child is MeshInstance3D:
|
||||
child.material_override = dead_material
|
||||
for child in find_children("*", "MeshInstance3D", true, false):
|
||||
(child as MeshInstance3D).material_override = dead_material
|
||||
|
||||
collision_layer = 0
|
||||
collision_mask = 0
|
||||
@@ -305,7 +366,7 @@ func _create_task_glyph_mesh(action_id: StringName) -> PrimitiveMesh:
|
||||
food.radius = 0.18
|
||||
food.height = 0.26
|
||||
return food
|
||||
SimulationIds.ACTION_GATHER_WOOD:
|
||||
SimulationIds.ACTION_GATHER_WOOD, SimulationIds.ACTION_DEPOSIT_WOOD:
|
||||
var wood := BoxMesh.new()
|
||||
wood.size = Vector3(0.18, 0.42, 0.18)
|
||||
task_glyph.rotation_degrees = Vector3(0, 0, -18)
|
||||
@@ -325,7 +386,7 @@ func _create_task_glyph_mesh(action_id: StringName) -> PrimitiveMesh:
|
||||
var book := BoxMesh.new()
|
||||
book.size = Vector3(0.36, 0.08, 0.24)
|
||||
return book
|
||||
SimulationIds.ACTION_REST:
|
||||
SimulationIds.ACTION_REST, SimulationIds.ACTION_SLEEP:
|
||||
var rest := SphereMesh.new()
|
||||
rest.radius = 0.2
|
||||
rest.height = 0.12
|
||||
@@ -337,7 +398,7 @@ func _create_task_glyph_material(action_id: StringName) -> StandardMaterial3D:
|
||||
var material := StandardMaterial3D.new()
|
||||
material.roughness = 0.65
|
||||
material.emission_enabled = true
|
||||
material.emission_energy_multiplier = 0.35
|
||||
material.emission_energy_multiplier = 0.12
|
||||
var color := _get_task_glyph_color(action_id)
|
||||
material.albedo_color = color
|
||||
material.emission = color
|
||||
@@ -348,7 +409,7 @@ func _get_task_glyph_color(action_id: StringName) -> Color:
|
||||
match action_id:
|
||||
SimulationIds.ACTION_GATHER_FOOD, SimulationIds.ACTION_EAT, SimulationIds.ACTION_WITHDRAW_FOOD:
|
||||
return Color(0.95, 0.42, 0.25, 1.0)
|
||||
SimulationIds.ACTION_GATHER_WOOD:
|
||||
SimulationIds.ACTION_GATHER_WOOD, SimulationIds.ACTION_DEPOSIT_WOOD:
|
||||
return Color(0.58, 0.34, 0.16, 1.0)
|
||||
SimulationIds.ACTION_DEPOSIT_FOOD:
|
||||
return Color(0.95, 0.72, 0.32, 1.0)
|
||||
@@ -356,6 +417,6 @@ func _get_task_glyph_color(action_id: StringName) -> Color:
|
||||
return Color(0.36, 0.58, 0.95, 1.0)
|
||||
SimulationIds.ACTION_STUDY:
|
||||
return Color(0.62, 0.46, 0.9, 1.0)
|
||||
SimulationIds.ACTION_REST:
|
||||
SimulationIds.ACTION_REST, SimulationIds.ACTION_SLEEP:
|
||||
return Color(0.4, 0.78, 0.72, 1.0)
|
||||
return Color(0.9, 0.9, 0.85, 1.0)
|
||||
|
||||
+133
-11
@@ -1,16 +1,69 @@
|
||||
[gd_scene load_steps=8 format=3 uid="uid://dhxxyprqflotq"]
|
||||
[gd_scene load_steps=18 format=3 uid="uid://dhxxyprqflotq"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bha8uf40p3sa0" path="res://player/npc/NpcVisual.gd" id="1_ixfoq"]
|
||||
|
||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_d844k"]
|
||||
|
||||
[sub_resource type="CapsuleMesh" id="CapsuleMesh_d844k"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_d844k"]
|
||||
albedo_color = Color(1, 0.22352941, 1, 1)
|
||||
albedo_color = Color(0.46, 0.58, 0.33, 1)
|
||||
roughness = 0.82
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_d844k"]
|
||||
[sub_resource type="CylinderMesh" id="CylinderMesh_body"]
|
||||
material = SubResource("StandardMaterial3D_d844k")
|
||||
top_radius = 0.28
|
||||
bottom_radius = 0.42
|
||||
height = 0.9
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_skin"]
|
||||
albedo_color = Color(0.84, 0.61, 0.39, 1)
|
||||
roughness = 0.88
|
||||
|
||||
[sub_resource type="SphereMesh" id="SphereMesh_head"]
|
||||
material = SubResource("StandardMaterial3D_skin")
|
||||
radius = 0.29
|
||||
height = 0.56
|
||||
|
||||
[sub_resource type="CylinderMesh" id="CylinderMesh_hair"]
|
||||
top_radius = 0.24
|
||||
bottom_radius = 0.29
|
||||
height = 0.18
|
||||
|
||||
[sub_resource type="CylinderMesh" id="CylinderMesh_arm"]
|
||||
material = SubResource("StandardMaterial3D_d844k")
|
||||
top_radius = 0.095
|
||||
bottom_radius = 0.11
|
||||
height = 0.58
|
||||
|
||||
[sub_resource type="SphereMesh" id="SphereMesh_hand"]
|
||||
material = SubResource("StandardMaterial3D_skin")
|
||||
radius = 0.12
|
||||
height = 0.22
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_pants"]
|
||||
albedo_color = Color(0.16, 0.22, 0.25, 1)
|
||||
roughness = 0.9
|
||||
|
||||
[sub_resource type="CapsuleMesh" id="CapsuleMesh_leg"]
|
||||
material = SubResource("StandardMaterial3D_pants")
|
||||
radius = 0.115
|
||||
height = 0.62
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_boot"]
|
||||
albedo_color = Color(0.13, 0.075, 0.04, 1)
|
||||
roughness = 0.95
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_foot"]
|
||||
material = SubResource("StandardMaterial3D_boot")
|
||||
size = Vector3(0.23, 0.15, 0.36)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_eye"]
|
||||
albedo_color = Color(0.06, 0.045, 0.035, 1)
|
||||
roughness = 0.8
|
||||
|
||||
[sub_resource type="SphereMesh" id="SphereMesh_eye"]
|
||||
material = SubResource("StandardMaterial3D_eye")
|
||||
radius = 0.035
|
||||
height = 0.06
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_food"]
|
||||
albedo_color = Color(0.38, 0.22, 0.08, 1)
|
||||
@@ -21,6 +74,16 @@ material = SubResource("StandardMaterial3D_food")
|
||||
radius = 0.28
|
||||
height = 0.48
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_wood"]
|
||||
albedo_color = Color(0.34, 0.17, 0.065, 1)
|
||||
roughness = 0.96
|
||||
|
||||
[sub_resource type="CylinderMesh" id="CylinderMesh_wood"]
|
||||
material = SubResource("StandardMaterial3D_wood")
|
||||
top_radius = 0.09
|
||||
bottom_radius = 0.11
|
||||
height = 0.62
|
||||
|
||||
[node name="NpcVisual" type="CharacterBody3D"]
|
||||
collision_layer = 2
|
||||
collision_mask = 1
|
||||
@@ -31,12 +94,58 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.85, 0)
|
||||
shape = SubResource("CapsuleShape3D_d844k")
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.85, 0)
|
||||
mesh = SubResource("CapsuleMesh_d844k")
|
||||
position = Vector3(0, 1.05, 0)
|
||||
mesh = SubResource("CylinderMesh_body")
|
||||
|
||||
[node name="FaceMarker" type="MeshInstance3D" parent="."]
|
||||
transform = Transform3D(0.15, 0, 0, 0, 0.15, 0, 0, 0, 0.35, 0, 1.2, 0.59052056)
|
||||
mesh = SubResource("BoxMesh_d844k")
|
||||
[node name="Head" type="MeshInstance3D" parent="."]
|
||||
position = Vector3(0, 1.68, 0)
|
||||
mesh = SubResource("SphereMesh_head")
|
||||
|
||||
[node name="Hair" type="MeshInstance3D" parent="."]
|
||||
position = Vector3(0, 1.9, -0.015)
|
||||
mesh = SubResource("CylinderMesh_hair")
|
||||
|
||||
[node name="EyeLeft" type="MeshInstance3D" parent="."]
|
||||
position = Vector3(-0.095, 1.72, 0.274)
|
||||
mesh = SubResource("SphereMesh_eye")
|
||||
|
||||
[node name="EyeRight" type="MeshInstance3D" parent="."]
|
||||
position = Vector3(0.095, 1.72, 0.274)
|
||||
mesh = SubResource("SphereMesh_eye")
|
||||
|
||||
[node name="ArmLeft" type="MeshInstance3D" parent="."]
|
||||
position = Vector3(-0.39, 1.08, 0)
|
||||
rotation = Vector3(0, 0, -0.139626)
|
||||
mesh = SubResource("CylinderMesh_arm")
|
||||
|
||||
[node name="Hand" type="MeshInstance3D" parent="ArmLeft"]
|
||||
position = Vector3(0, -0.35, 0)
|
||||
mesh = SubResource("SphereMesh_hand")
|
||||
|
||||
[node name="ArmRight" type="MeshInstance3D" parent="."]
|
||||
position = Vector3(0.39, 1.08, 0)
|
||||
rotation = Vector3(0, 0, 0.139626)
|
||||
mesh = SubResource("CylinderMesh_arm")
|
||||
|
||||
[node name="Hand" type="MeshInstance3D" parent="ArmRight"]
|
||||
position = Vector3(0, -0.35, 0)
|
||||
mesh = SubResource("SphereMesh_hand")
|
||||
|
||||
[node name="LegLeft" type="MeshInstance3D" parent="."]
|
||||
position = Vector3(-0.18, 0.43, 0)
|
||||
mesh = SubResource("CapsuleMesh_leg")
|
||||
|
||||
[node name="Foot" type="MeshInstance3D" parent="LegLeft"]
|
||||
position = Vector3(0, -0.28, 0.09)
|
||||
mesh = SubResource("BoxMesh_foot")
|
||||
|
||||
[node name="LegRight" type="MeshInstance3D" parent="."]
|
||||
position = Vector3(0.18, 0.43, 0)
|
||||
mesh = SubResource("CapsuleMesh_leg")
|
||||
|
||||
[node name="Foot" type="MeshInstance3D" parent="LegRight"]
|
||||
position = Vector3(0, -0.28, 0.09)
|
||||
mesh = SubResource("BoxMesh_foot")
|
||||
|
||||
[node name="ProfessionProp" type="MeshInstance3D" parent="."]
|
||||
|
||||
@@ -55,9 +164,22 @@ visible = false
|
||||
transform = Transform3D(0.75, 0, 0, 0, 0.75, 0, 0, 0, 0.75, 0.48, 0.7, 0)
|
||||
mesh = SubResource("SphereMesh_food")
|
||||
|
||||
[node name="CarriedWood" type="Node3D" parent="."]
|
||||
visible = false
|
||||
position = Vector3(-0.48, 0.88, -0.04)
|
||||
rotation = Vector3(0, 0, 1.5708)
|
||||
|
||||
[node name="LogA" type="MeshInstance3D" parent="CarriedWood"]
|
||||
position = Vector3(0, -0.11, -0.08)
|
||||
mesh = SubResource("CylinderMesh_wood")
|
||||
|
||||
[node name="LogB" type="MeshInstance3D" parent="CarriedWood"]
|
||||
position = Vector3(0, 0.11, 0.08)
|
||||
mesh = SubResource("CylinderMesh_wood")
|
||||
|
||||
[node name="TaskGlyphRoot" type="Node3D" parent="."]
|
||||
visible = false
|
||||
position = Vector3(0, 2.25, 0)
|
||||
position = Vector3(0, 2.35, 0)
|
||||
|
||||
[node name="TaskGlyph" type="MeshInstance3D" parent="TaskGlyphRoot"]
|
||||
|
||||
|
||||
@@ -78,6 +78,10 @@ func _run() -> void:
|
||||
waterfall_mist.draw_pass_1 != null,
|
||||
"Waterfall mist particles should have visible billboard geometry"
|
||||
)
|
||||
_check(
|
||||
waterfall_mist.position.distance_to(Vector3(25.0, 0.8, -24.0)) < 0.01,
|
||||
"Waterfall mist should stay at the waterfall base instead of obscuring the village"
|
||||
)
|
||||
_check(world.has_node("WaterRoot/RiverSurface"), "WaterRoot should include a river surface")
|
||||
var environment: Environment = (
|
||||
(world.get_node("WorldEnvironment") as WorldEnvironment).environment
|
||||
@@ -97,16 +101,25 @@ func _run() -> void:
|
||||
"VillageRoot should include the mill and bridge blockouts"
|
||||
)
|
||||
var smoke_count := 0
|
||||
var smoke_is_translucent := true
|
||||
for suffix in ["House_01", "House_02", "House_03"]:
|
||||
if world.has_node("VillageRoot/%s/Smoke" % suffix):
|
||||
var smoke: GPUParticles3D = world.get_node("VillageRoot/%s/Smoke" % suffix)
|
||||
if smoke.draw_pass_1 == null:
|
||||
continue
|
||||
var smoke_material := (smoke.draw_pass_1 as QuadMesh).material as StandardMaterial3D
|
||||
smoke_is_translucent = (
|
||||
smoke_is_translucent
|
||||
and smoke_material != null
|
||||
and smoke_material.transparency != BaseMaterial3D.TRANSPARENCY_DISABLED
|
||||
and smoke_material.albedo_color.a < 0.5
|
||||
)
|
||||
smoke_count += 1
|
||||
_check(
|
||||
smoke_count >= 2,
|
||||
"At least two houses should have chimney smoke particles (found %s)" % smoke_count
|
||||
)
|
||||
_check(smoke_is_translucent, "Chimney smoke should not render as opaque white quads")
|
||||
var lookdev: Node3D = load("res://world/jajce/JajceLookdev.tscn").instantiate()
|
||||
_check(lookdev.has_node("JajceWorld"), "Lookdev should instance JajceWorld")
|
||||
_check(lookdev.has_node("BeautyCamera"), "Lookdev should provide a beauty camera")
|
||||
|
||||
@@ -27,6 +27,15 @@ func _run() -> void:
|
||||
body.material_override.albedo_color == profession_definition.visual_color,
|
||||
"NPC body color should come from its profession definition"
|
||||
)
|
||||
_check(
|
||||
(
|
||||
body.mesh is CylinderMesh
|
||||
and visual.has_node("Head")
|
||||
and visual.has_node("ArmLeft")
|
||||
and visual.has_node("LegRight/Foot")
|
||||
),
|
||||
"NPC presentation should use a readable multi-part villager silhouette"
|
||||
)
|
||||
_check(
|
||||
visual.get_node("ProfessionProp").mesh != null
|
||||
and profession_definition.display_name in visual.get_node("ProfessionLabel").text,
|
||||
@@ -74,6 +83,14 @@ func _run() -> void:
|
||||
npc.remove_inventory(SimulationIds.RESOURCE_FOOD, 1.0)
|
||||
manager.emit_signal("npc_inventory_changed", npc, SimulationIds.RESOURCE_FOOD, 0.0)
|
||||
_check(not carried_food.visible, "Deposited or eaten food should be hidden")
|
||||
var carried_wood: Node3D = visual.get_node("CarriedWood")
|
||||
_check(not carried_wood.visible, "NPC should begin without carried logs")
|
||||
npc.add_inventory(SimulationIds.RESOURCE_WOOD, 2.0)
|
||||
manager.emit_signal("npc_inventory_changed", npc, SimulationIds.RESOURCE_WOOD, 2.0)
|
||||
_check(carried_wood.visible, "Carried wood should show a visible log bundle")
|
||||
npc.remove_inventory(SimulationIds.RESOURCE_WOOD, 2.0)
|
||||
manager.emit_signal("npc_inventory_changed", npc, SimulationIds.RESOURCE_WOOD, 0.0)
|
||||
_check(not carried_wood.visible, "Deposited wood should hide the log bundle")
|
||||
|
||||
var resource_state: ResourceStateRecord = manager.get_resource_state(&"berry_bush_01")
|
||||
npc.set_task(SimulationIds.ACTION_GATHER_FOOD)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://bts3v8ybsqche
|
||||
@@ -0,0 +1 @@
|
||||
uid://cr22fjs3577fa
|
||||
@@ -0,0 +1 @@
|
||||
uid://dcquhx5tin7a
|
||||
@@ -11,19 +11,21 @@ scale_max = 0.6
|
||||
color = Color(0.8, 0.8, 0.8, 0.6)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="4"]
|
||||
albedo_color = Color(1, 1, 1, 1)
|
||||
albedo_color = Color(0.5, 0.52, 0.5, 0.24)
|
||||
transparency = 1
|
||||
shading_mode = 0
|
||||
vertex_color_use_as_albedo = true
|
||||
vertex_color_is_srgb = true
|
||||
billboard_mode = 1
|
||||
cull_mode = 0
|
||||
|
||||
[sub_resource type="QuadMesh" id="5"]
|
||||
size = Vector2(1, 1)
|
||||
size = Vector2(0.8, 0.8)
|
||||
material = SubResource("4")
|
||||
|
||||
[node name="ChimneySmoke" type="GPUParticles3D"]
|
||||
emitting = true
|
||||
amount = 16
|
||||
amount = 12
|
||||
lifetime = 3.0
|
||||
one_shot = false
|
||||
local_coords = true
|
||||
|
||||
@@ -610,10 +610,12 @@ directional_shadow_max_distance = 180.0
|
||||
[node name="WindStrokes_Valley" parent="." instance=ExtResource("19_windstrokes")]
|
||||
|
||||
[node name="WindStrokes_Ridge" parent="." instance=ExtResource("19_windstrokes")]
|
||||
position = Vector3(-25, 7, 9)
|
||||
|
||||
[node name="WindSpecks_Valley" parent="." instance=ExtResource("20_windspecks")]
|
||||
|
||||
[node name="WindSpecks_Ridge" parent="." instance=ExtResource("20_windspecks")]
|
||||
position = Vector3(-25, 7, 9)
|
||||
|
||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
|
||||
environment = SubResource("Environment_greybox")
|
||||
|
||||
@@ -24,6 +24,7 @@ material = SubResource("Material_mist")
|
||||
size = Vector2(1.8, 1.8)
|
||||
|
||||
[node name="WaterfallMist" type="GPUParticles3D"]
|
||||
position = Vector3(25, 0.8, -24)
|
||||
emitting = true
|
||||
amount = 72
|
||||
lifetime = 3.0
|
||||
|
||||
@@ -7,7 +7,7 @@ shader = ExtResource("1_shader")
|
||||
|
||||
[sub_resource type="QuadMesh" id="Mesh_stroke"]
|
||||
material = SubResource("ShaderMaterial_stroke")
|
||||
size = Vector2(2.4, 0.14)
|
||||
size = Vector2(1.5, 0.07)
|
||||
|
||||
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_wind"]
|
||||
lifetime_randomness = 0.6
|
||||
@@ -30,7 +30,7 @@ emission_box_extents = Vector3(28.0, 8.0, 28.0)
|
||||
|
||||
[node name="WindStrokes" type="GPUParticles3D"]
|
||||
emitting = true
|
||||
amount = 24
|
||||
amount = 10
|
||||
lifetime = 5.0
|
||||
one_shot = false
|
||||
preprocess = 3.0
|
||||
|
||||
@@ -10,7 +10,7 @@ uniform vec3 foam_color : source_color = vec3(0.82, 0.88, 0.86);
|
||||
uniform float rim_power : hint_range(0.5, 5.0) = 2.5;
|
||||
uniform float sparkle_strength : hint_range(0.0, 1.0) = 0.12;
|
||||
|
||||
global uniform vec3 sun_direction = vec3(0.4, 0.8, 0.4);
|
||||
uniform vec3 sun_direction = vec3(0.4, 0.8, 0.4);
|
||||
|
||||
void vertex() {
|
||||
vec3 pos = VERTEX;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
shader_type spatial;
|
||||
render_mode blend_mix, depth_draw_opaque, cull_disabled, unshaded;
|
||||
render_mode blend_mix, depth_draw_never, cull_disabled, unshaded;
|
||||
|
||||
uniform float softness : hint_range(0.0, 1.0) = 0.45;
|
||||
uniform vec3 tint : source_color = vec3(0.95, 0.93, 0.88);
|
||||
@@ -8,7 +8,7 @@ void fragment() {
|
||||
float edge_dist = abs(UV.y - 0.5) * 2.0;
|
||||
float alpha = 1.0 - smoothstep(0.0, 1.0, edge_dist);
|
||||
alpha *= smoothstep(0.0, 0.2, UV.x) * smoothstep(0.0, 0.2, 1.0 - UV.x);
|
||||
alpha *= softness * 0.35;
|
||||
alpha *= softness * 0.1;
|
||||
ALBEDO = tint;
|
||||
ALPHA = alpha;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user