Godot 4.4
I’m trying to make my boomer shooter multiplayer and i feel like i have everything set up properly but it’s not behaving right.
Here are some screenshots of how i have my scene set up and some of the scripts
Additional if it’s the problem, here is my player code:
extends CharacterBody3D
====== CONFIG ======
const SPEED = 15.0
const JUMP_VELOCITY = 13
@export var SENSITIVITY = 0.004
@export var WALL_JUMP_FORCE := 14.0
@export var WALL_JUMP_COOLDOWN := 1
@export var WALL_DETECT_DISTANCE := 1.0
@export var weapon = “None”
@export var last_received_network_pos: Vector3
@export var last_received_network_rot: Vector3
@export var network_update_received := false
headbob / tilt / fov
var bob_frequency = 1.1
var tilt_amount := 0.0
var tilt_speed := 7.0
var max_tilt := deg_to_rad(2)
var bob_amplitude = 0.1
var t_bob = 0.0
var default_fov := 90
var max_fov := 105
var fov_speed := 4.0
var prev_bob_y = 0.0
var prev_bob_dir = 0
wall jump state
var can_wall_jump = true
var wall_jump_timer = 0.0
====== NODES ======
@onready var aimray = $Head/Cameras/FirstPerson/AimRay
@onready var first_person_cam := $Head/Cameras/FirstPerson as Camera3D
@onready var third_person_cam := $Head/Cameras/ThirdPerson as Camera3D
@onready var cameras_container := $Head/Cameras
@onready var head = $Head
@onready var base_head_pos = head.transform.origin
@onready var body = $“Chest Model/Node/Body”
@onready var footstep = $Footstep
@onready var weaponsmanager = $Head/Cameras/FirstPerson/WeaponsManager
@onready var playermodelweaponmanager = $“Chest Model/Node/Body/ThirdPersonWeapon”
@onready var barrel = $Head/Cameras/FirstPerson/Barrel
@onready var camera = $Head/Cameras
var bullet_trail = load(“res://Player/pellet.tscn”)
var bodyrotation
var cam_mode = “First”
knockback
var is_local_player = false
var knockback_velocity := Vector3.ZERO
var knockback_damping := 2.0
var cam_rot_x := 0.0 # vertical rotation (pitch)
====== NETWORKED ======
@export var network_position: Vector3
@export var network_rotation: Vector3
@export var network_velocity: Vector3
====== READY / AUTHORITY ======
func _ready() → void:
refresh_weapons()
if is_multiplayer_authority() and get_tree().get_multiplayer().get_unique_id() == get_multiplayer_authority():
set_local_player()
_send_network_state()
else:
set_remote_player()
func set_local_player():
is_local_player = true
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
if cameras_container:
first_person_cam.current = true
third_person_cam.current = false
set_process_input(true)
set_physics_process(true)
print(“Local player ready”)
func set_remote_player():
is_local_player = false
if cameras_container:
cameras_container.queue_free()
set_process_input(false)
set_physics_process(true)
print(“Remote player ready”)
====== INPUT / LOOK ======
func _unhandled_input(event: InputEvent) → void:
if not is_local_player:
return
if event is InputEventMouseMotion:
# yaw
rotate_y(-event.relative.x * SENSITIVITY)
# pitch
body.rotate_x(-event.relative.y * SENSITIVITY)
body.rotation.x = clamp(body.rotation.x, deg_to_rad(-90), deg_to_rad(90))
first_person_cam.rotation.x = body.rotation.x
third_person_cam.rotation.x = body.rotation.x
func _input(event: InputEvent) → void:
if not is_local_player:
return
if Input.is_action_just_pressed("toggle_camera_mode"):
match cam_mode:
"First":
cam_mode = "Third"
third_person_cam.make_current()
"Third":
cam_mode = "First"
first_person_cam.make_current()
if Input.is_action_just_pressed("None") and weapon != "None":
weapon = "None"
refresh_weapons()
if Input.is_action_just_pressed("Bomb") and weapon != "Bomb":
weapon = "Bomb"
refresh_weapons()
====== PHYSICS / MOVEMENT ======
func _physics_process(delta: float) → void:
if is_local_player:
# === LOCAL MOVEMENT ===
if wall_jump_timer > 0.0:
wall_jump_timer -= delta
else:
can_wall_jump = true
# gravity
if not is_on_floor():
velocity += get_gravity() * delta
# jump / wall jump
if Input.is_action_just_pressed("jump"):
if is_on_floor():
velocity.y = JUMP_VELOCITY
$Jump.play()
$Jump.pitch_scale = 1.0
elif can_wall_jump:
var wall_normal = _check_wall()
if wall_normal != Vector3.ZERO:
_perform_wall_jump(wall_normal)
$Jump.play()
$Jump.pitch_scale = 1.5
# input
var input_dir := Input.get_vector("move_left", "move_right", "move_forwards", "move_back")
var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
# tilt
tilt_amount = lerp(tilt_amount, -input_dir.x * max_tilt, delta * tilt_speed)
head.rotation.z = tilt_amount
# smooth movement
if is_on_floor():
if direction != Vector3.ZERO:
$"Legs Model/AnimationPlayer".play("Walk")
velocity.x = lerp(velocity.x, direction.x * SPEED, delta * 14.0)
velocity.z = lerp(velocity.z, direction.z * SPEED, delta * 14.0)
else:
$"Legs Model/AnimationPlayer".play("Normal")
velocity.x = 0
velocity.z = 0
else:
$"Legs Model/AnimationPlayer".play("Normal")
velocity.x = lerp(velocity.x, direction.x * SPEED, delta * 10.0)
velocity.z = lerp(velocity.z, direction.z * SPEED, delta * 10.0)
# headbob
t_bob += delta * velocity.length() * float(is_on_floor())
head.transform.origin = base_head_pos + _headbob(t_bob)
# knockback
if knockback_velocity.length() > 0.01:
velocity.x += knockback_velocity.x * delta
velocity.z += knockback_velocity.z * delta
knockback_velocity = knockback_velocity.lerp(Vector3.ZERO, delta * knockback_damping)
# FOV
var horizontal_speed := Vector3(velocity.x, 0, velocity.z).length()
var target_fov = default_fov + (max_fov - default_fov) * clamp(horizontal_speed / SPEED, 0, 1)
first_person_cam.fov = lerp(float(first_person_cam.fov), float(target_fov), delta * fov_speed)
third_person_cam.fov = lerp(float(third_person_cam.fov), float(target_fov), delta * fov_speed)
# move & broadcast
move_and_slide()
_send_network_state()
else:
if not is_local_player:
# Apply gravity
if not is_on_floor():
network_velocity += get_gravity() * delta
else:
network_velocity.y = 0
# Move character based on network_velocity
velocity = network_velocity
move_and_slide()
# Interpolate position & rotation toward networked state
global_position = global_position.lerp(network_position, clamp(delta * 10.0, 0.0, 1.0))
rotation = rotation.lerp(network_rotation, clamp(delta * 10.0, 0.0, 1.0))
if not is_local_player and network_update_received:
print("Remote player network update received at ", network_position)
====== NETWORK RPC ======
@rpc(“any_peer”)
func update_network_state(peer_id: int, pos: Vector3, rot: Vector3):
if not is_local_player:
network_position = pos
network_rotation = rot
print(“Received network update from peer:”, peer_id, “pos:”, pos, “rot:”, rot)
func _send_network_state():
if is_local_player:
var peer = multiplayer.get_multiplayer_peer()
# Only send if this is a real network peer (ENet)
if peer and peer.get_class() != “OfflineMultiplayerPeer”:
rpc(“update_network_state”, global_position, rotation)
====== WEAPONS / UTIL ======
func refresh_weapons() → void:
if weaponsmanager and playermodelweaponmanager:
weaponsmanager._weapon_check()
playermodelweaponmanager._weapon_check()
func _weapon_idle():
match weapon:
“Shotgun”:
$“Chest Model/AnimationPlayer”.play(“Shotgun Idle”)
$Head/Cameras/FirstPerson/ViewModel/AnimationPlayer.play(“Shotgun Idle”)
“None”:
$“Chest Model/AnimationPlayer”.play(“Normal”)
$Head/Cameras/FirstPerson/ViewModel/AnimationPlayer.play(“Normal”)
---- WALL JUMP HELPERS ----
func _check_wall() → Vector3:
var space_state = get_world_3d().direct_space_state
var left_query = PhysicsRayQueryParameters3D.create(global_position, global_position + -head.global_transform.basis.x * WALL_DETECT_DISTANCE)
left_query.exclude = [self]
var left_result = space_state.intersect_ray(left_query)
var right_query = PhysicsRayQueryParameters3D.create(global_position, global_position + head.global_transform.basis.x * WALL_DETECT_DISTANCE)
right_query.exclude = [self]
var right_result = space_state.intersect_ray(right_query)
if left_result and left_result.has("normal"):
return left_result.normal
elif right_result and right_result.has("normal"):
return right_result.normal
return Vector3.ZERO
func _perform_wall_jump(normal: Vector3) → void:
can_wall_jump = false
wall_jump_timer = WALL_JUMP_COOLDOWN
velocity = normal * WALL_JUMP_FORCE
velocity.y = JUMP_VELOCITY * 0.9
---- HEADBOB ----
func _headbob(time) → Vector3:
return Vector3(cos(time * bob_frequency / 2) * bob_amplitude, sin(time * bob_frequency) * bob_amplitude, 0)
---- FOOTSTEP ----
func _on_footstep() → void:
footstep.pitch_scale = randf_range(0.9, 1.1)
footstep.volume_db = randf_range(0, 6.0)
if is_on_floor():
footstep.play()
func _on_animation_player_animation_finished(anim_name: StringName) → void:
_weapon_idle()
=== KNOCKBACK / EXPLOSION ===
func apply_explosion_force(origin: Vector3, force: float) → void:
var dir := (global_position - origin)
if dir.length() < 0.5:
dir = -transform.basis.z
dir.y = 0
dir = dir.normalized()
var horizontal_force = dir * max(force * 2.5, force * 2.0)
knockback_velocity += horizontal_force
var vertical_diff := global_position.y - origin.y
if vertical_diff > -2:
velocity.y = max(velocity.y, force * 0.35)
elif vertical_diff < -0.1:
velocity.y = min(velocity.y, -force * 0.25)



