# --------------------------------------------------------------------- # This script is supposed to handle character movement logic # --------------------------------------------------------------------- class_name PlayerInput extends CharacterBody3D @export var health: int = 100 @export_category("PlayerInput") const SPEED = 5.0 const JUMP_VELOCITY = 4.5 var mouse_captured: bool = false @onready var player_synchronizer: MultiplayerSynchronizer = $PlayerSynchronizer @onready var first_view_camera_mount: Node3D = $FirstPersonCameraMount @onready var first_view_camera: Camera3D = $FirstPersonCameraMount/Camera @onready var model_mount: Node3D = $ModelMount var model: Node3D = null # -- This node is supposed to be spawned per player, and since each # -- player has an id, it is used for giving a node a name. So we can # -- use it here to tell a controlled node from the rest models @onready var owner_id: int = str($".".name).to_int() var multiplayer_id: int = 0 # -- Character state var alive: bool = true func _ready() -> void: player_synchronizer.set_multiplayer_authority(owner_id) multiplayer_id = multiplayer.get_unique_id() # -- Separate logic for player and other models that are controlled # -- by other players on the server # -- TODO: If player is alive, it must not be able to see other # -- cameras, but if player is dead, it must be able to # -- switch between other players if _is_current_player(): var err := _add_first_view_model() if err != OK: print("Error occured: " + str(err)) err = _add_legs_to_first_view() if err != OK: print("Error occured: " + str(err)) var world_model := _add_world_model() if world_model == null: print("Error occured: " + "couldn't load the world model") _enable_camera() else: var world_model := _add_world_model() if world_model == null: print("Error occured: " + "couldn't load the world model") _hide_camera_mount() _capture_mouse() # -- Add a world model to the player, that should be seen by other players # -- on the server func _add_world_model() -> Node3D : # -- TODO: It should not be hardcoded var path := "res://scenes/characters/blue/dummy.tscn" if not ResourceLoader.exists(path): push_error(ERR_DOES_NOT_EXIST) return null var scene: PackedScene = ResourceLoader.load(path) if not scene.can_instantiate(): push_error(ERR_CANT_OPEN) return null var node: Node3D = scene.instantiate() model = node model_mount.add_child(node) if _is_current_player(): node.make_invisible() first_view_camera.cull_mask &= ~(1 << 1) return node func _hide_camera_mount(): first_view_camera_mount.visible = 0 hud.visible = 0 func make_node_invisible_for_camera(node: Node3D, camera: Camera3D): if node and camera: # Set the node to Layer 2 (or any other layer you want) node.visibility_layer = 1 << 1 # Assign the node to Layer 2 # Disable Layer 2 on the camera's culling mask (this makes it invisible to this camera) camera.cull_mask &= ~(1 << 1) # Disable Layer 2 on this camera var current_gun: String = "ak" @onready var gun_mount: Node3D = $FirstPersonCameraMount/GunMount @onready var gun_mount_anim: AnimationPlayer = $FirstPersonCameraMount/GunMount/AnimationPlayer var gun_with_hands: Node3D = null # -- Add the first person view to pthe player func _add_first_view_model() -> Error : # -- TODO: It should not be hardcoded # Define a format string with placeholder '%s' var path_tmpl := "res://scenes/weapon/guns/%s/with_hands.tscn" var path := path_tmpl % current_gun if not ResourceLoader.exists(path): return ERR_DOES_NOT_EXIST var scene: PackedScene = ResourceLoader.load(path) if not scene.can_instantiate(): return ERR_CANT_OPEN var node: Node3D = scene.instantiate() node.scale = Vector3(0.03,0.03,0.03) node.position = Vector3(0.02, -0.03, -0.07) current_weapon_bullet_speed = node.bullet_speed current_weapon_cooldown_interwal = node.cooldown current_weapon_damage = node.damage gun_with_hands = node gun_mount.add_child(node) return OK var first_view_legs_anim: AnimationPlayer = null # -- Add a world model to the player, that should be seen by other players # -- on the server func _add_legs_to_first_view() -> Error : # -- TODO: It should not be hardcoded var path := "res://scenes/characters/blue/dummy.tscn" if not ResourceLoader.exists(path): return ERR_DOES_NOT_EXIST var scene: PackedScene = ResourceLoader.load(path) if not scene.can_instantiate(): return ERR_CANT_OPEN var node: Node3D = scene.instantiate() var skeleton: Skeleton3D = node.find_child("Skeleton3D") var bone := skeleton.find_bone("mixamorig_Spine") if bone != -1: skeleton.set_bone_pose_scale(bone, Vector3(0, 0, 0)) model_mount.add_child(node) var animation_node: AnimationPlayer = node.find_child("AnimationPlayer") if animation_node != null: first_view_legs_anim = animation_node return OK func _enable_camera(): first_view_camera.make_current() func _capture_mouse() -> void: Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) mouse_captured = true func _release_mouse() -> void: Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) mouse_captured = false # -- Does the peer own the node func _is_current_player() -> bool: if owner_id == multiplayer_id: return true return false # --------------------------------------------------------------------- # -- Input controller # --------------------------------------------------------------------- var move_dir: Vector2 # Input direction for movement var look_dir: Vector2 # Input direction for look/aim var walk_vel: Vector3 # Walking velocity var grav_vel: Vector3 # Gravity velocity var jump_vel: Vector3 # Jumping velocity @export_range(0.1, 3.0, 0.1) var jump_height: float = 1 # m @export_range(0.1, 3.0, 0.1, "or_greater") var camera_sens: float = 1 func _unhandled_input(event: InputEvent) -> void: if _is_current_player(): if event is InputEventMouseMotion: look_dir = event.relative * 0.001 if mouse_captured: _rotate_camera() #if Input.is_action_just_pressed("jump"): jumping = true if Input.is_action_just_pressed("exit"): get_tree().quit() if Input.is_action_pressed("shot"): _shoot() #if str($"..".name).to_int() == multiplayer.get_unique_id(): #if Input.is_action_just_pressed("shot"): $UpperTorso/ViewModelCamera.shot() #if Input.is_action_just_pressed("reload"): $Body/UpperTorso/CameraMount/Camera.reload() func _rotate_camera(sens_mod: float = 1.0) -> void: #if str($"..".name).to_int() == multiplayer.get_unique_id(): rotation.y -= look_dir.x * camera_sens * sens_mod first_view_camera_mount.rotation.x = clamp(first_view_camera_mount.rotation.x - look_dir.y * camera_sens * sens_mod, -1.5, 1.5) @onready var hud = $FirstPersonCameraMount/HUD @onready var health_indicator = $FirstPersonCameraMount/HUD/HealthIndicator @onready var fps_indicator = $FirstPersonCameraMount/HUD/FPS func _process(delta: float) -> void: health_indicator.text = str(health) fps_indicator.text = str(Engine.get_frames_per_second()) if health == 0: alive = false var world: Node3D = find_parent("ElTest") var spawner: MultiplayerSpawner = world.find_child("ObjectsSpawner") var objects_root: Node3D = world.find_child("Objects") spawner.spawn(model) model.reparent(get_tree().get_root()) model.die() queue_free() func _physics_process(delta: float) -> void: # Add the gravity. if not is_on_floor(): velocity += get_gravity() * delta # Handle jump. if Input.is_action_just_pressed("jump") and is_on_floor(): velocity.y = JUMP_VELOCITY # Get the input direction and handle the movement/deceleration. # As good practice, you should replace UI actions with custom gameplay actions. #var input_dir := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down") # -- It shouldn't be possible to change direction during the jumps if is_on_floor(): var input_dir := Input.get_vector("move_left", "move_right", "move_forward", "move_backwards") var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized() if direction: gun_mount_anim.play("move") if first_view_legs_anim != null: first_view_legs_anim.play("Run Forward") velocity.x = direction.x * SPEED velocity.z = direction.z * SPEED else: if gun_mount_anim.is_playing(): gun_mount_anim.stop() if first_view_legs_anim != null and first_view_legs_anim.is_playing(): first_view_legs_anim.stop() velocity.x = move_toward(velocity.x, 0, SPEED) velocity.z = move_toward(velocity.z, 0, SPEED) if alive: move_and_slide() # -- TODO: It shouldn't be hardcoded var bullet = load("res://scenes/weapon/bullet.tscn") @onready var shooting_raycast: RayCast3D = $FirstPersonCameraMount/RayCast3D @onready var bullet_starting_point: Node3D = $FirstPersonCameraMount/BulletStartingPoint @onready var aim_ray: RayCast3D = $FirstPersonCameraMount/BulletStartingPoint/AimRay var current_weapon_damage: int var current_weapon_bullet_speed: int var current_weapon_cooldown_interwal: float # --find the gun node and exec shoot var cant_shoot: bool = false func _shoot(): if not cant_shoot: _send_shot_to_server.rpc_id(1, aim_ray.global_position) if aim_ray.is_colliding(): var collider := aim_ray.get_collider() if collider != null and collider.is_in_group("target"): aim_ray.get_collider().take_damage() if collider != null and collider.is_in_group("body"): collider.hit(50) var root := get_tree().get_root() gun_with_hands.shoot() cant_shoot = true await get_tree().create_timer(current_weapon_cooldown_interwal).timeout cant_shoot = false @rpc("any_peer", "call_local", "unreliable_ordered") func _send_shot_to_server(start_position): # -- TODO: Should not be hardcoded var world: Node3D = find_parent("ElTest") world.spawn_bullet(bullet_starting_point, current_weapon_bullet_speed, current_weapon_damage) func _get_camera_collision(): var viewport = get_viewport().size var ray_origin = first_view_camera.project_ray_origin(viewport / 2) var ray_end = ray_origin + first_view_camera.project_ray_normal(viewport / 2 * 100) var new_intersection = PhysicsRayQueryParameters3D.create(ray_origin, ray_end) var intersection = get_world_3d().direct_space_state.intersect_ray(new_intersection) if not intersection.is_empty(): var collision_point = intersection.position print("gotcha") return collision_point else: return ray_end func _hit_scan_collision(collision_point): var viewport = get_viewport().size var ray_origin = first_view_camera.project_ray_origin(viewport / 2) var ray_end = ray_origin + first_view_camera.project_ray_normal(viewport / 2 * 100) var new_intersection = PhysicsRayQueryParameters3D.create(ray_origin, ray_end) var bullet_collision = get_world_3d().direct_space_state.intersect_ray(new_intersection) if bullet_collision: print(bullet_collision) _hit_scan_damage(bullet_collision.collider) func _hit_scan_damage(Collider): #if Collider.is_in_group("target") and Collider.has_method("take_damage"): print("damaged") func take_damage(dam: int): var new_health = health - dam set_health(new_health) if multiplayer.is_server(): set_health.rpc(new_health) @rpc("call_local", "reliable") func set_health(val: int): health = val