class_name MainCamera extends Camera3D static var instance: MainCamera @export_group("References") @export var _listener: AudioListener3D @export_group("Positioning") @export var _height_offset: float = 0.5 @export var _distance: float = 30 @export var _fov: float = 25 @export var _angle_degrees: Vector3 = Vector3(-45, -45, 0) @export var _aim_offset_factor_mouse: float = 0.2 @export var _aim_offset_factor_controller: float = 0.5 @export var _aim_damping: float = 1 @export_group("Damage shake") @export var _damage_shake_duration: float = 0.5 @export_exp_easing var _damage_shake_ease: float = 2 @export var _damage_shake_amplitude: float = 0.1 @export var _damage_shake_frequency: float = 50 @export_group("Hit shake") @export var _hit_shake_fov: float = 1 @export var _hit_shake_duration: float = 0.5 @export_exp_easing var _hit_shake_ease: float = 4 var _player_offsets: Dictionary[int, PlayerOffset] var _damage_shake_time: float = 0 var _hit_shake_time: float = 0 func _ready() -> void: instance = self _listener.make_current() for player in Player.instances: _connect_player_signals(player) func _process(delta: float) -> void: global_rotation_degrees = _angle_degrees fov = _fov _damage_shake(delta) _hit_shake(delta) func _physics_process(delta: float) -> void: call_deferred("_process_following", delta) func _process_following(delta: float) -> void: var follow_position: Vector3 = Vector3.ZERO for player in Player.instances: follow_position += _follow(player, delta) follow_position /= Player.instances.size() follow_position += Vector3.UP * _height_offset global_position = follow_position + transform.basis.z * _distance _listener.global_position = follow_position func _connect_player_signals(player: Player) -> void: player.stats.damaged.connect(_on_player_stats_damaged) player.attack.did_hit.connect(_on_player_attack_did_hit) func _follow(player: Player, delta: float) -> Vector3: var id := player.get_instance_id() if not _player_offsets.has(id): _player_offsets[id] = PlayerOffset.new() var player_offset := _player_offsets[id] var player_position := player.global_position if player.is_on_floor(): player_offset.floor_height = player_position.y player_position.y = player_offset.floor_height + Projectile.HEIGHT if not Player.is_single_player(): player_offset.aim_offset = Vector3.ZERO elif player.input_mode_is(Inputer.Mode.KB_MOUSE): player_offset.aim_offset = player.aiming.aim_offset elif player.input_mode_is(Inputer.Mode.CONTROLLER): var new_aim_offset := ( Vector3.ZERO if player.aiming.aim_input.length() == 0 else player.aiming.aim_offset ) player_offset.aim_offset = player_offset.aim_offset.lerp( new_aim_offset, _aim_damping * delta ) var follow_position := ( player_position + ( player_offset.aim_offset as Vector3 * ( _aim_offset_factor_mouse if player.input_mode_is(Inputer.Mode.KB_MOUSE) else _aim_offset_factor_controller ) ) ) Debugger.circle("follow_position" + str(player.name), follow_position) return follow_position func _damage_shake(delta: float) -> void: if _damage_shake_time <= 0: return _damage_shake_time -= delta var shake_value := ( sin(_damage_shake_frequency * _damage_shake_time) * _damage_shake_amplitude ) global_rotation.z += ( shake_value * ease(_damage_shake_time / _damage_shake_duration, _damage_shake_ease) ) func _hit_shake(delta: float) -> void: if _hit_shake_time <= 0: return _hit_shake_time -= delta fov += ( _hit_shake_fov * ease(_hit_shake_time / _hit_shake_duration, _hit_shake_ease) ) func _on_player_stats_damaged() -> void: _damage_shake_time = _damage_shake_duration func _on_player_attack_did_hit() -> void: _hit_shake_time = _hit_shake_duration class PlayerOffset: var floor_height: float = 0 var aim_offset: Vector3 = Vector3.ZERO