dust particles for hooves
This commit is contained in:
@@ -3,16 +3,24 @@ extends CharacterBody3D
|
|||||||
const WALK_SPEED = 6.0
|
const WALK_SPEED = 6.0
|
||||||
const CHARGE_SPEED = 18.0
|
const CHARGE_SPEED = 18.0
|
||||||
const WALK_ACCEL = 14.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 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_FAST = 8.0
|
||||||
const WALK_TURN_SLOW = 3.5 # rad/s at full walk speed
|
const WALK_TURN_SLOW = 3.5
|
||||||
const CHARGE_TURN = 0.55 # rad/s — ~31 deg/s, very wide committed arc
|
const CHARGE_TURN = 0.55
|
||||||
|
|
||||||
const JUMP_VELOCITY = 4.5
|
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
|
@export var visual_turn_speed: float = 12.0
|
||||||
|
|
||||||
@onready var camera_pivot: Node3D = $SpringArmPivot
|
@onready var camera_pivot: Node3D = $SpringArmPivot
|
||||||
@@ -21,6 +29,86 @@ const JUMP_VELOCITY = 4.5
|
|||||||
var current_speed: float = 0.0
|
var current_speed: float = 0.0
|
||||||
var facing_angle: 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:
|
func _physics_process(delta: float) -> void:
|
||||||
if not is_on_floor():
|
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 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()
|
var direction := (flat_forward * -input_dir.y + flat_right * input_dir.x).normalized()
|
||||||
|
|
||||||
# Logic for mimicking 4 legged animal movement
|
|
||||||
if direction:
|
if direction:
|
||||||
var target_angle := atan2(direction.x, direction.z)
|
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:
|
if current_speed < 0.01:
|
||||||
facing_angle = target_angle
|
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)
|
cube_guy.rotation.y = lerp_angle(cube_guy.rotation.y, facing_angle, delta * visual_turn_speed)
|
||||||
|
|
||||||
move_and_slide()
|
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)
|
||||||
|
|||||||
@@ -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)
|
"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]
|
[physics]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user