mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-10 16:04:04 +00:00
feat(audio): drive engine tone from thrust
This commit is contained in:
@@ -7,6 +7,7 @@ const SAMPLE_RATE := 44100
|
||||
const MAX_INTENSITY := 1.0
|
||||
|
||||
var enabled := true
|
||||
var _engine_player: AudioStreamPlayer
|
||||
|
||||
|
||||
func bind_tree_buttons(root: Node) -> void:
|
||||
@@ -47,6 +48,35 @@ func play_goal() -> void:
|
||||
_play_tone(783.99, 0.30, 0.18)
|
||||
|
||||
|
||||
func set_engine_state(thrust: float, turbo: bool) -> void:
|
||||
var amount := clamp_intensity(thrust)
|
||||
if not enabled or amount <= 0.01:
|
||||
stop_engine()
|
||||
return
|
||||
if _engine_player == null or not is_instance_valid(_engine_player):
|
||||
_engine_player = AudioStreamPlayer.new()
|
||||
_engine_player.stream = _engine_stream()
|
||||
add_child(_engine_player)
|
||||
_engine_player.play()
|
||||
_engine_player.pitch_scale = engine_pitch(amount, turbo)
|
||||
_engine_player.volume_db = linear_to_db(engine_volume(amount, turbo))
|
||||
|
||||
|
||||
func stop_engine() -> void:
|
||||
if _engine_player != null and is_instance_valid(_engine_player):
|
||||
_engine_player.stop()
|
||||
|
||||
|
||||
static func engine_pitch(thrust: float, turbo: bool) -> float:
|
||||
var amount := clamp_intensity(thrust)
|
||||
return 0.75 + amount * 0.55 + (0.30 if turbo and amount > 0.01 else 0.0)
|
||||
|
||||
|
||||
static func engine_volume(thrust: float, turbo: bool) -> float:
|
||||
var amount := clamp_intensity(thrust)
|
||||
return clampf(0.015 + amount * 0.045 + (0.025 if turbo and amount > 0.01 else 0.0), 0.0, 0.1)
|
||||
|
||||
|
||||
static func clamp_intensity(value: float) -> float:
|
||||
if not is_finite(value):
|
||||
return 0.0
|
||||
@@ -72,6 +102,17 @@ func _play_tone(frequency: float, duration: float, volume: float) -> void:
|
||||
player.play()
|
||||
|
||||
|
||||
func _engine_stream() -> AudioStreamWAV:
|
||||
var stream := AudioStreamWAV.new()
|
||||
stream.format = AudioStreamWAV.FORMAT_16_BITS
|
||||
stream.mix_rate = SAMPLE_RATE
|
||||
stream.stereo = false
|
||||
stream.loop_mode = AudioStreamWAV.LOOP_FORWARD
|
||||
stream.data = _tone_data(92.0, 1.0, 0.65)
|
||||
stream.loop_end = SAMPLE_RATE
|
||||
return stream
|
||||
|
||||
|
||||
func _tone_data(frequency: float, duration: float, volume: float) -> PackedByteArray:
|
||||
var frames := maxi(1, int(duration * SAMPLE_RATE))
|
||||
var data := PackedByteArray()
|
||||
|
||||
@@ -100,6 +100,7 @@ func _connect_target() -> void:
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
AudioManager.stop_engine()
|
||||
if is_instance_valid(target) and target.ball_contact.is_connected(_on_target_ball_contact):
|
||||
target.ball_contact.disconnect(_on_target_ball_contact)
|
||||
|
||||
@@ -218,6 +219,8 @@ func _smooth_look_at(point: Vector3, delta: float) -> void:
|
||||
func _update_speed_feel(delta: float) -> void:
|
||||
var feel_t := 1.0 - exp(-feel_smoothing * delta)
|
||||
var turbo_target := 1.0 if target.is_turbo_active() else 0.0
|
||||
var engine_action := target.get_current_action_copy()
|
||||
AudioManager.set_engine_state(maxf(engine_action.thrust.z, 0.0), turbo_target > 0.5)
|
||||
_turbo_blend = lerpf(_turbo_blend, turbo_target, feel_t)
|
||||
_speed_blend = lerpf(_speed_blend, target.get_speed_ratio(), feel_t)
|
||||
var target_fov := base_fov + _speed_blend * speed_fov_add + _turbo_blend * turbo_fov_kick
|
||||
|
||||
@@ -25,3 +25,10 @@ func test_button_binding_is_idempotent() -> void:
|
||||
assert_eq(button.pressed.get_connections().size(), 1, "UI click hook is not duplicated")
|
||||
button.free()
|
||||
manager.free()
|
||||
|
||||
|
||||
func test_engine_mix_is_bounded_and_turbo_is_audible() -> void:
|
||||
assert_eq(AudioManager.engine_pitch(-1.0, false), 0.75, "negative thrust uses the idle pitch")
|
||||
assert_true(AudioManager.engine_pitch(1.0, true) > AudioManager.engine_pitch(1.0, false), "turbo raises engine pitch")
|
||||
assert_true(AudioManager.engine_volume(1.0, true) > AudioManager.engine_volume(1.0, false), "turbo raises engine volume")
|
||||
assert_true(AudioManager.engine_volume(100.0, true) <= 0.1, "engine volume remains bounded")
|
||||
|
||||
@@ -14,7 +14,7 @@ The training pipeline is built — see `TRAINING.md` (self-play PPO via the vend
|
||||
|
||||
The largest gap between this and a AAA-feeling product is presentation, not code. Sequenced after the above for pragmatic reasons, but this is the highest impact per hour.
|
||||
|
||||
- [ ] **Audio — authored sound design remains open.** A dependency-free procedural `AudioManager` now provides safe UI/countdown/impact/goal hooks and is wired into kickoff and goal events; replace the placeholder tones with engine hum pitched to throttle, turbo whoosh, impulse-scaled ball impacts, wall scrapes, goal explosion, crowd bed, UI clicks, and music after selecting distributable assets and mixing them on real hardware.
|
||||
- [ ] **Audio — authored sound design remains open.** A dependency-free procedural `AudioManager` now provides safe UI/countdown/impact/goal hooks, an engine tone pitched/levelled from local thrust and turbo state, and is wired into kickoff, goal, ball-contact, and menu events; replace the placeholder tones with authored engine/turbo/impact/wall/goal/crowd/music assets after selecting distributable files and mixing them on real hardware.
|
||||
- [ ] Custom font + a real `Theme` resource for the HUD. `ThemeDB.fallback_font` at 10-13 px reads as a debug overlay.
|
||||
- [ ] **Video settings menu is missing graphics presets, vsync, and resolution scaling.** `video_settings.gd` currently exposes only AA, glow, and brightness, while SDFGI, SSIL, SSAO, and five shadow-casting lights are on by default and unreachable by the player. Blocked on the same profiling gate as the multiplayer section's 0.16–0.28 tasks below (0.17/0.17b/0.17c/0.17d) — needs a human at the editor with real hardware, not further code changes on its own.
|
||||
|
||||
|
||||
+1
-1
@@ -1627,4 +1627,4 @@ Signed MatchNet claims now also require exact JSON string/integer types for ever
|
||||
|
||||
Presentation progress: a shared `Game/themes/cosmic_clash_theme.tres` now gives the menu, lobby, matchmaking, and settings surfaces consistent button, input, option, and label styling. The custom-font portion of `TODO.md` remains open until a distributable font asset is selected.
|
||||
|
||||
The audio TODO now has a runtime foundation: `AudioManager` generates bounded placeholder tones for kickoff countdowns, camera-reported ball impacts, goals, and UI clicks without adding binary assets; authored sound design, engine telemetry mixing, and production audio QA remain open.
|
||||
The audio TODO now has a runtime foundation: `AudioManager` generates bounded placeholder tones for kickoff countdowns, camera-reported ball impacts, goals, UI clicks, and a local thrust/turbo-pitched engine loop without adding binary assets; authored sound design and production audio QA remain open.
|
||||
|
||||
Reference in New Issue
Block a user