extends CharacterBody3D @export_subgroup("Properties") @export var movement_speed = 250 @export var jump_strength = 7 @export_subgroup("Weapons") @export var weapons: Array[Weapon] = [] var weapon: Weapon var weapon_index := 0 var mouse_sensitivity = 700 var mouse_captured := true var movement_velocity: Vector3 var rotation_target: Vector3 var input: Vector3 var input_mouse: Vector2 var gravity := 0.0 var previously_floored := false var jump_single := true var jump_double := true var container_offset = Vector3(1.2, -1.1, -2.75) var tween:Tween @onready var sound_footsteps = $SoundFootsteps @onready var item_container = $Head/Camera/SubViewportContainer/SubViewport/CameraItem/Container @onready var camera = $Head/Camera @onready var raycast = $Head/Camera/ShootCast @onready var blaster_cooldown = $BlasterCooldown @onready var burst = $Head/Camera/SubViewportContainer/SubViewport/CameraItem/Burst @export var crosshair:TextureRect # Functions func _ready(): Input.mouse_mode = Input.MOUSE_MODE_CAPTURED change_weapon(weapon_index) func _process(delta): # Handle functions handle_controls(delta) handle_gravity(delta) # Movement var applied_velocity: Vector3 movement_velocity = transform.basis * movement_velocity # Move forward applied_velocity = velocity.lerp(movement_velocity, delta * 10) applied_velocity.y = -gravity velocity = applied_velocity move_and_slide() # Rotation camera.rotation.z = lerp_angle(camera.rotation.z, -input_mouse.x * 1.25, delta * 5) camera.rotation.x = lerp_angle(camera.rotation.x, rotation_target.x, delta * 25) rotation.y = lerp_angle(rotation.y, rotation_target.y, delta * 25) item_container.rotation.y = lerp_angle(item_container.rotation.y, -input_mouse.x * 4, delta * 5) #item_container.rotation.x = lerp_angle(item_container.rotation.x, -rotation_target.x / 3, delta * 10) item_container.position = lerp(item_container.position, container_offset - (applied_velocity / 30), delta * 10) # Movement sound sound_footsteps.stream_paused = true if is_on_floor(): if abs(velocity.x) > 1 or abs(velocity.z) > 1: sound_footsteps.stream_paused = false # Landing after jump or falling camera.position.y = lerp(camera.position.y, 0.0, delta * 5) if is_on_floor() and gravity > 1 and !previously_floored: # Landed Audio.play("sounds/land.ogg") camera.position.y = -0.1 previously_floored = is_on_floor() input_mouse = Vector2.ZERO # Mouse movement func _input(event): if event is InputEventMouseMotion and mouse_captured: input_mouse = event.relative / mouse_sensitivity rotation_target.y -= event.relative.x / mouse_sensitivity rotation_target.x -= event.relative.y / mouse_sensitivity rotation_target.x = clamp(rotation_target.x, deg_to_rad(-90), deg_to_rad(90)) func handle_controls(delta): # Mouse capture if Input.is_action_just_pressed("mouse_capture"): Input.mouse_mode = Input.MOUSE_MODE_CAPTURED mouse_captured = true if Input.is_action_just_pressed("mouse_capture_exit"): Input.mouse_mode = Input.MOUSE_MODE_VISIBLE mouse_captured = false # Movement input.x = Input.get_axis("move_left", "move_right") input.z = Input.get_axis("move_forward", "move_back") movement_velocity = input.normalized() * movement_speed * delta # Shooting if Input.is_action_pressed("shoot"): shoot() # Jumping if Input.is_action_just_pressed("jump"): if jump_single or jump_double: Audio.play_random("sounds/jump_a.ogg, sounds/jump_b.ogg, sounds/jump_c.ogg") if jump_double: gravity = -jump_strength jump_double = false if(jump_single): jump() # Weapon switching if Input.is_action_just_pressed("weapon_next"): next_weapon() # Handle gravity func handle_gravity(delta): gravity += 20 * delta if gravity > 0 and is_on_floor(): jump_single = true gravity = 0 # Jumping func jump(): gravity = -jump_strength jump_single = false; jump_double = true; # Shooting func shoot(): if !blaster_cooldown.is_stopped(): return Audio.play_random(weapon.sound_shoot) item_container.position.z += 0.25 burst.play("default") burst.rotation_degrees.z = randf_range(-45, 45) burst.scale = Vector3.ONE * randf_range(0.40, 0.75) burst.position = item_container.position - Vector3(0.1, -0.4, 1.5) blaster_cooldown.start(weapon.cooldown) # What or where the blaster hit for n in weapon.shot_count: raycast.target_position.x = randf_range(-weapon.spread, weapon.spread) raycast.target_position.y = randf_range(-weapon.spread, weapon.spread) raycast.force_raycast_update() if !raycast.is_colliding(): return var collider = raycast.get_collider() if collider.has_method("damage"): collider.damage(weapon.damage) var impact = preload("res://objects/impact.tscn") var impact_instance = impact.instantiate() impact_instance.play("shot") get_tree().root.add_child(impact_instance) impact_instance.position = raycast.get_collision_point() + (raycast.get_collision_normal() / 10) impact_instance.look_at(position, Vector3.UP, true) #impact_instance.rotation_degrees.z = randf_range(-45, 45) # Weapons func next_weapon(): Audio.play("sounds/blaster_change.ogg") if weapon_index < weapons.size() - 1: weapon_index += 1 else: weapon_index = 0 change_weapon(weapon_index) func change_weapon_apply(): weapon = weapons[weapon_index] # Step 1. Remove all children in (weapon) container for n in item_container.get_children(): item_container.remove_child(n) # Step 2. Load new model into container var weapon_model = weapon.model.instantiate() item_container.add_child(weapon_model) weapon_model.position = weapon.position weapon_model.rotation_degrees = weapon.rotation # Step 3. Set model to only render on layer 2 for child in weapon_model.find_children("*", "MeshInstance3D"): child.layers = 2 # Set weapon data raycast.target_position = Vector3(0, 0, -1) * weapon.max_distance crosshair.texture = weapon.crosshair func change_weapon(index): weapon_index = index tween = get_tree().create_tween() tween.set_ease(Tween.EASE_OUT_IN) tween.tween_property(item_container, "position", container_offset - Vector3(0, 1, 0), 0.1) tween.tween_callback(change_weapon_apply) func get_hurt(): pass