From 4f1126563ca3f4aa9ba47cfc51d109dcc1b6512a Mon Sep 17 00:00:00 2001 From: Richard Aasa Date: Sun, 10 May 2026 17:01:18 +0300 Subject: [PATCH] dust particles for hooves --- player.gd | 106 ++++++++++++++++++++++++++++++++++++++++++++++---- project.godot | 5 +++ 2 files changed, 104 insertions(+), 7 deletions(-) diff --git a/player.gd b/player.gd index f0458ca..47f161d 100644 --- a/player.gd +++ b/player.gd @@ -3,16 +3,24 @@ extends CharacterBody3D const WALK_SPEED = 6.0 const CHARGE_SPEED = 18.0 const WALK_ACCEL = 14.0 -const CHARGE_ACCEL = 40.0 # snaps to charge speed quickly — burst feel +const CHARGE_ACCEL = 40.0 const DECELERATION = 12.0 -const CHARGE_DECEL = 3.5 # momentum lingers after releasing shift +const CHARGE_DECEL = 3.5 -const WALK_TURN_FAST = 8.0 # rad/s at rest -const WALK_TURN_SLOW = 3.5 # rad/s at full walk speed -const CHARGE_TURN = 0.55 # rad/s — ~31 deg/s, very wide committed arc +const WALK_TURN_FAST = 8.0 +const WALK_TURN_SLOW = 3.5 +const CHARGE_TURN = 0.55 const JUMP_VELOCITY = 4.5 +# Hoof positions in bull_v10 local space — tweak y/z if hooves don't line up with the model +const HOOF_OFFSETS: Array = [ + Vector3(-0.30, -0.25, -0.50), + Vector3( 0.30, -0.25, -0.50), + Vector3(-0.30, -0.25, 0.60), + Vector3( 0.30, -0.25, 0.60), +] + @export var visual_turn_speed: float = 12.0 @onready var camera_pivot: Node3D = $SpringArmPivot @@ -21,6 +29,86 @@ const JUMP_VELOCITY = 4.5 var current_speed: float = 0.0 var facing_angle: float = 0.0 +var _hoof_emitters: Array[CPUParticles3D] = [] + +enum _DustState { NONE, WALK, CHARGE } +var _dust_state: _DustState = _DustState.NONE + + +func _ready() -> void: + _setup_hoof_dust() + + +func _setup_hoof_dust() -> void: + var mat := StandardMaterial3D.new() + mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED + mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA + mat.vertex_color_use_as_albedo = true + mat.cull_mode = BaseMaterial3D.CULL_DISABLED + + # 4×2 sphere = chunky low-poly diamond shape + var sphere := SphereMesh.new() + sphere.radius = 0.10 + sphere.height = 0.20 + sphere.radial_segments = 4 + sphere.rings = 2 + sphere.material = mat + + # sand colour, fades to transparent over lifetime + var ramp := Gradient.new() + ramp.set_color(0, Color(0.80, 0.69, 0.46, 1.0)) + ramp.set_color(1, Color(0.80, 0.69, 0.46, 0.0)) + + # puff blooms up then vanishes + var scale_curve := Curve.new() + scale_curve.add_point(Vector2(0.0, 0.2)) + scale_curve.add_point(Vector2(0.4, 1.0)) + scale_curve.add_point(Vector2(1.0, 0.0)) + + for offset: Vector3 in HOOF_OFFSETS: + var p := CPUParticles3D.new() + p.position = offset + p.emitting = false + p.amount = 12 + p.lifetime = 0.9 + p.explosiveness = 0.35 + p.randomness = 0.6 + p.local_coords = false + p.direction = Vector3(0.0, 1.0, 0.0) + p.spread = 45.0 + p.gravity = Vector3(0.0, -1.5, 0.0) + p.initial_velocity_min = 0.8 + p.initial_velocity_max = 1.8 + p.scale_amount_min = 0.4 + p.scale_amount_max = 0.7 + p.mesh = sphere + p.color_ramp = ramp + p.scale_amount_curve = scale_curve + cube_guy.add_child(p) + _hoof_emitters.append(p) + + +func _set_dust_state(new_state: _DustState) -> void: + if new_state == _dust_state: + return + _dust_state = new_state + for p: CPUParticles3D in _hoof_emitters: + match new_state: + _DustState.NONE: + p.emitting = false + _DustState.WALK: + p.scale_amount_min = 0.35 + p.scale_amount_max = 0.65 + p.initial_velocity_min = 0.8 + p.initial_velocity_max = 1.8 + p.emitting = true + _DustState.CHARGE: + p.scale_amount_min = 1.0 + p.scale_amount_max = 2.2 + p.initial_velocity_min = 2.8 + p.initial_velocity_max = 5.5 + p.emitting = true + func _physics_process(delta: float) -> void: if not is_on_floor(): @@ -35,11 +123,9 @@ func _physics_process(delta: float) -> void: var flat_right := Vector3(cam_basis.x.x, 0.0, cam_basis.x.z).normalized() var direction := (flat_forward * -input_dir.y + flat_right * input_dir.x).normalized() - # Logic for mimicking 4 legged animal movement if direction: var target_angle := atan2(direction.x, direction.z) - # Snap on first movement so there is no startup arc from 0 if current_speed < 0.01: facing_angle = target_angle @@ -64,3 +150,9 @@ func _physics_process(delta: float) -> void: cube_guy.rotation.y = lerp_angle(cube_guy.rotation.y, facing_angle, delta * visual_turn_speed) move_and_slide() + + var charging := Input.is_key_pressed(KEY_SHIFT) + if current_speed > 0.5 and is_on_floor(): + _set_dust_state(_DustState.CHARGE if charging else _DustState.WALK) + else: + _set_dust_state(_DustState.NONE) diff --git a/project.godot b/project.godot index f120495..a869e94 100644 --- a/project.godot +++ b/project.godot @@ -61,6 +61,11 @@ toggle_mouse_capture={ "events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":4,"position":Vector2(461, 22),"global_position":Vector2(470, 70),"factor":1.0,"button_index":3,"canceled":false,"pressed":true,"double_click":false,"script":null) ] } +charge={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"location":1,"echo":false,"script":null) +] +} [physics]