scanlines, sounds, bumper walls, score keep, control legend, timer
This commit is contained in:
@@ -14,18 +14,24 @@ const HOOF_OFFSETS: Array = [
|
||||
var _hoof_emitters: Array[CPUParticles3D] = []
|
||||
var _legs: Node
|
||||
|
||||
var _kick_timer: float = 0.0
|
||||
var _was_on_floor: bool = false
|
||||
var _pre_slide_vel_y: float = 0.0
|
||||
var _charge_dir: Vector3 = Vector3.ZERO
|
||||
var _both_held: bool = false
|
||||
var _kick_timer: float = 0.0
|
||||
var _was_on_floor: bool = false
|
||||
var _pre_slide_vel_y: float = 0.0
|
||||
var _charge_dir: Vector3 = Vector3.ZERO
|
||||
var _charge_hold_time: float = 0.0
|
||||
var _both_held: bool = false
|
||||
var _thrust_left_charge: float = 0.0
|
||||
var _thrust_right_charge: float = 0.0
|
||||
var _charge_was_full: bool = false
|
||||
var _charge_was_full: bool = false
|
||||
|
||||
var _charge_overlay: ColorRect = null
|
||||
var _charge_ready_emitter: CPUParticles3D = null
|
||||
|
||||
var _huff_player: AudioStreamPlayer = null
|
||||
var _crowd_player: AudioStreamPlayer = null
|
||||
var _huff_timer: float = 0.0
|
||||
var _roll_damping: float = 0.15
|
||||
|
||||
enum DustState { NONE, WALK, CHARGE }
|
||||
var _dust_state: DustState = DustState.NONE
|
||||
|
||||
@@ -35,7 +41,9 @@ func _ready() -> void:
|
||||
_setup_hoof_dust()
|
||||
_setup_legs()
|
||||
_setup_charge_indicators()
|
||||
_setup_audio()
|
||||
DP.any_changed.connect(_on_dp_changed)
|
||||
_roll_damping = DP.f("roll_damping")
|
||||
|
||||
|
||||
func _setup_legs() -> void:
|
||||
@@ -144,6 +152,23 @@ func _setup_charge_indicators() -> void:
|
||||
cube_guy.add_child(_charge_ready_emitter)
|
||||
|
||||
|
||||
func _setup_audio() -> void:
|
||||
if ResourceLoader.exists("res://sounds/bull_huff.ogg"):
|
||||
_huff_player = AudioStreamPlayer.new()
|
||||
_huff_player.stream = load("res://sounds/bull_huff.ogg")
|
||||
_huff_player.volume_db = 0.0
|
||||
add_child(_huff_player)
|
||||
if ResourceLoader.exists("res://sounds/crowd_ambient.ogg"):
|
||||
_crowd_player = AudioStreamPlayer.new()
|
||||
var s := load("res://sounds/crowd_ambient.ogg") as AudioStreamOggVorbis
|
||||
if s:
|
||||
s.loop = true
|
||||
_crowd_player.stream = s
|
||||
_crowd_player.volume_db = -14.0
|
||||
_crowd_player.autoplay = true
|
||||
add_child(_crowd_player)
|
||||
|
||||
|
||||
func _set_dust_state(new_state: DustState) -> void:
|
||||
if new_state == _dust_state:
|
||||
return
|
||||
@@ -171,6 +196,7 @@ func _set_dust_state(new_state: DustState) -> void:
|
||||
|
||||
|
||||
func _on_dp_changed(_key: String, _val: Variant) -> void:
|
||||
_roll_damping = DP.f("roll_damping")
|
||||
if _hoof_emitters.is_empty() or _dust_state == DustState.NONE:
|
||||
return
|
||||
var prev := _dust_state
|
||||
@@ -194,41 +220,27 @@ func _apply_thrust(dir: Vector3) -> void:
|
||||
velocity.z *= s
|
||||
|
||||
|
||||
func _apply_kick(direction: Vector3, charging: bool) -> void:
|
||||
var base := DP.f("kick_impulse") * (DP.f("charge_kick_mult") if charging else 1.0)
|
||||
|
||||
# Random force variance — each kick punches a different amount
|
||||
var force := base * randf_range(1.0 - DP.f("kick_force_var"), 1.0 + DP.f("kick_force_var"))
|
||||
|
||||
# Random angle spread — slight mis-kick like a real ball
|
||||
func _apply_kick(direction: Vector3) -> void:
|
||||
var force := DP.f("kick_impulse") * randf_range(1.0 - DP.f("kick_force_var"), 1.0 + DP.f("kick_force_var"))
|
||||
var spread := deg_to_rad(randf_range(-DP.f("kick_spread"), DP.f("kick_spread")))
|
||||
var kick_dir := direction.rotated(Vector3.UP, spread)
|
||||
|
||||
velocity.x += kick_dir.x * force
|
||||
velocity.z += kick_dir.z * force
|
||||
|
||||
# Vertical arc — only when grounded so arcs don't chain infinitely in the air
|
||||
if is_on_floor():
|
||||
velocity.y += DP.f("kick_vertical") * randf_range(0.75, 1.25)
|
||||
|
||||
# Cap immediately after kick so charge doesn't overshoot by too much
|
||||
var flat := Vector2(velocity.x, velocity.z)
|
||||
var spd := flat.length()
|
||||
var cap := DP.f("max_speed") * (DP.f("charge_kick_mult") if charging else 1.0)
|
||||
if spd > cap:
|
||||
var s := cap / spd
|
||||
if spd > DP.f("max_speed"):
|
||||
var s := DP.f("max_speed") / spd
|
||||
velocity.x *= s
|
||||
velocity.z *= s
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
# ── Gravity & jump ────────────────────────────────────────────────────────
|
||||
# ── Gravity ───────────────────────────────────────────────────────────────
|
||||
if not is_on_floor():
|
||||
velocity += get_gravity() * delta
|
||||
|
||||
if Input.is_action_just_pressed(&"jump") and is_on_floor():
|
||||
velocity.y = DP.f("jump_velocity")
|
||||
|
||||
# ── Camera-relative input direction ───────────────────────────────────────
|
||||
var input_dir := Input.get_vector("move_left", "move_right", "move_forward", "move_back")
|
||||
var cam_basis := camera_pivot.global_transform.basis
|
||||
@@ -236,8 +248,7 @@ 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()
|
||||
|
||||
var charging := Input.is_action_pressed(&"charge")
|
||||
var on_floor := is_on_floor()
|
||||
var on_floor := is_on_floor()
|
||||
|
||||
# ── Mouse button actions ──────────────────────────────────────────────────
|
||||
# Both held → charge: lock direction, ramp speed, lower head
|
||||
@@ -250,12 +261,24 @@ func _physics_process(delta: float) -> void:
|
||||
if both_held and not _both_held:
|
||||
var raw := cube_guy.global_transform.basis.z
|
||||
_charge_dir = Vector3(raw.x, 0.0, raw.z).normalized()
|
||||
if _huff_player:
|
||||
_huff_player.pitch_scale = randf_range(0.9, 1.1)
|
||||
_huff_player.play()
|
||||
_huff_timer = randf_range(1.4, 2.2)
|
||||
|
||||
if both_held:
|
||||
_thrust_left_charge = 0.0
|
||||
_thrust_right_charge = 0.0
|
||||
velocity.x += _charge_dir.x * DP.f("bull_charge_accel") * delta
|
||||
velocity.z += _charge_dir.z * DP.f("bull_charge_accel") * delta
|
||||
_charge_hold_time += delta
|
||||
_huff_timer -= delta
|
||||
if _huff_timer <= 0.0 and _huff_player and not _huff_player.playing:
|
||||
_huff_player.pitch_scale = randf_range(0.9, 1.1)
|
||||
_huff_player.play()
|
||||
_huff_timer = randf_range(1.4, 2.2)
|
||||
var ramp := minf(_charge_hold_time / DP.f("charge_ramp_time"), 1.0)
|
||||
var accel := DP.f("bull_charge_accel") * lerpf(0.2, 1.0, ramp)
|
||||
velocity.x += _charge_dir.x * accel * delta
|
||||
velocity.z += _charge_dir.z * accel * delta
|
||||
var flat_c := Vector2(velocity.x, velocity.z)
|
||||
var spd_c := flat_c.length()
|
||||
if spd_c > DP.f("bull_charge_max_speed"):
|
||||
@@ -263,6 +286,7 @@ func _physics_process(delta: float) -> void:
|
||||
velocity.z *= DP.f("bull_charge_max_speed") / spd_c
|
||||
_legs.set(&"charge_pitch_target", DP.f("charge_head_pitch"))
|
||||
else:
|
||||
_charge_hold_time = 0.0
|
||||
_legs.set(&"charge_pitch_target", 0.0)
|
||||
|
||||
if left_held and not right_held:
|
||||
@@ -305,11 +329,11 @@ func _physics_process(delta: float) -> void:
|
||||
# ── Periodic kick ─────────────────────────────────────────────────────────
|
||||
_kick_timer = maxf(0.0, _kick_timer - delta)
|
||||
if direction and _kick_timer <= 0.0 and not both_held:
|
||||
_apply_kick(direction, charging)
|
||||
_apply_kick(direction)
|
||||
_kick_timer = DP.f("kick_interval")
|
||||
|
||||
# ── Rolling friction (exponential, frame-rate independent) ────────────────
|
||||
var retain := pow(DP.f("roll_damping"), delta)
|
||||
var retain := pow(_roll_damping, delta)
|
||||
velocity.x *= retain
|
||||
velocity.z *= retain
|
||||
|
||||
@@ -321,8 +345,26 @@ func _physics_process(delta: float) -> void:
|
||||
cube_guy.rotation.y, target_angle, delta * DP.f("visual_turn_speed"))
|
||||
|
||||
_pre_slide_vel_y = velocity.y
|
||||
var pre_wall_vel := Vector3(velocity.x, 0.0, velocity.z)
|
||||
move_and_slide()
|
||||
|
||||
# ── Wall bounce — no sliding, guaranteed exit ────────────────────────────
|
||||
# pre_wall_vel is captured before move_and_slide strips the into-wall component,
|
||||
# so into_spd correctly reflects how hard the bull hit the wall even when stationary.
|
||||
for i in get_slide_collision_count():
|
||||
var col := get_slide_collision(i)
|
||||
var normal := col.get_normal()
|
||||
if abs(normal.y) < 0.5:
|
||||
var flat_n := Vector3(normal.x, 0.0, normal.z).normalized()
|
||||
var into_spd := -pre_wall_vel.dot(flat_n)
|
||||
var exit_spd := maxf(into_spd * DP.f("wall_bounce_restitution"),
|
||||
DP.f("wall_min_bounce_speed"))
|
||||
velocity.x = flat_n.x * exit_spd
|
||||
velocity.z = flat_n.z * exit_spd
|
||||
pre_wall_vel = Vector3(velocity.x, 0.0, velocity.z)
|
||||
if both_held and _charge_dir.dot(flat_n) < 0.0:
|
||||
_charge_dir = _charge_dir.reflect(flat_n)
|
||||
|
||||
# ── Bounce on landing ─────────────────────────────────────────────────────
|
||||
if is_on_floor() and not _was_on_floor:
|
||||
var impact := -_pre_slide_vel_y
|
||||
@@ -336,3 +378,5 @@ func _physics_process(delta: float) -> void:
|
||||
_set_dust_state(DustState.WALK)
|
||||
else:
|
||||
_set_dust_state(DustState.NONE)
|
||||
|
||||
# ── Hoof sounds ───────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user