diff --git a/objects/enemy.gd b/objects/enemy.gd index caaf9e4..30ef97a 100644 --- a/objects/enemy.gd +++ b/objects/enemy.gd @@ -1,6 +1,6 @@ extends Node3D -@export var player:Node3D +@export var player: Node3D @onready var raycast = $RayCast @onready var muzzle_a = $MuzzleA @@ -8,66 +8,67 @@ extends Node3D var health := 100 var time := 0.0 -var target_position:Vector3 +var target_position: Vector3 var destroyed := false # When ready, save the initial position + func _ready(): - target_position = position + func _process(delta): - - self.look_at(player.position + Vector3(0, 0.5, 0), Vector3.UP, true) # Look at player - target_position.y += (cos(time * 5) * 1) * delta # Sine movement (up and down) - + self.look_at(player.position + Vector3(0, 0.5, 0), Vector3.UP, true) # Look at player + target_position.y += (cos(time * 5) * 1) * delta # Sine movement (up and down) + time += delta - + position = target_position + # Take damage from player + func damage(amount): - Audio.play("sounds/enemy_hurt.ogg") - + health -= amount - + if health <= 0 and !destroyed: destroy() + # Destroy the enemy when out of health + func destroy(): - Audio.play("sounds/enemy_destroy.ogg") - + destroyed = true queue_free() + # Shoot when timer hits 0 + func _on_timer_timeout(): - raycast.force_raycast_update() - + if raycast.is_colliding(): - var collider = raycast.get_collider() - - if collider.has_method("damage"): # Raycast collides with player - + + if collider.has_method("damage"): # Raycast collides with player # Play muzzle flash animation(s) - + muzzle_a.frame = 0 muzzle_a.play("default") muzzle_a.rotation_degrees.z = randf_range(-45, 45) - + muzzle_b.frame = 0 muzzle_b.play("default") muzzle_b.rotation_degrees.z = randf_range(-45, 45) - + Audio.play("sounds/enemy_attack.ogg") - - collider.damage(5) # Apply damage to player + + collider.damage(5) # Apply damage to player diff --git a/objects/impact.gd b/objects/impact.gd index b0c7595..6cb6c14 100644 --- a/objects/impact.gd +++ b/objects/impact.gd @@ -2,5 +2,6 @@ extends AnimatedSprite3D # Remove this impact effect after the animation has completed + func _on_animation_finished(): queue_free() diff --git a/objects/player.gd b/objects/player.gd index 160c870..19c0b42 100644 --- a/objects/player.gd +++ b/objects/player.gd @@ -21,7 +21,7 @@ var rotation_delta: Vector2 # x: horizontal, y: vertical var input: Vector3 var input_mouse: Vector2 -var health:int = 100 +var health: int = 100 var gravity := 0.0 var previously_floored := false @@ -31,7 +31,7 @@ var jump_double := true var container_offset = Vector3(1.2, -1.1, -2.75) -var tween:Tween +var tween: Tween signal health_updated @@ -42,12 +42,12 @@ signal health_updated @onready var sound_footsteps = $SoundFootsteps @onready var blaster_cooldown = $Cooldown -@export var crosshair:TextureRect +@export var crosshair: TextureRect # Functions + func _ready(): - Input.mouse_mode = Input.MOUSE_MODE_CAPTURED rotation_delta = Vector2.ZERO @@ -69,24 +69,23 @@ func rotate_toward(from: float, to: float, delta: float) -> float: func _physics_process(delta: float): - # Handle functions - + handle_controls(delta) handle_gravity(delta) - + # Movement var applied_velocity: Vector3 - - movement_velocity = transform.basis * movement_velocity # Move forward - + + 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 # Roll a bit when turning horizontally. @@ -94,225 +93,235 @@ func _physics_process(delta: float): var look_amount := max_look_speed * delta # pitch - var pitch := rotate_toward(camera.rotation.x, camera.rotation.x + rotation_delta.y, look_amount.x) + var pitch := rotate_toward( + camera.rotation.x, camera.rotation.x + rotation_delta.y, look_amount.x + ) pitch = clamp(pitch, -TAU * 0.23, TAU * 0.23) camera.rotation.x = pitch # yaw - rotation.y = rotate_toward(rotation.y, rotation.y + rotation_delta.x, look_amount.y) + rotation.y = rotate_toward(rotation.y, rotation.y + rotation_delta.x, look_amount.y) rotation_delta = Vector2.ZERO - container.position = lerp(container.position, container_offset - (applied_velocity / 30), delta * 10) - + container.position = lerp( + 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 + + 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() - + # Falling/respawning - + if position.y < -10: get_tree().reload_current_scene() + # Mouse movement + func _input(event): if event is InputEventMouseMotion and mouse_captured: - input_mouse = event.relative / mouse_sensitivity - + rotation_delta.x -= event.relative.x / mouse_sensitivity rotation_delta.y -= event.relative.y / mouse_sensitivity + 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 - + input_mouse = Vector2.ZERO - + # 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 - + # Rotation - var rotation_input := Input.get_vector("camera_right", "camera_left", "camera_down", "camera_up") + var rotation_input := Input.get_vector( + "camera_right", "camera_left", "camera_down", "camera_up" + ) rotation_delta += rotation_input * gamepad_sensitivity # Shooting - + action_shoot() - + # Jumping - + if Input.is_action_just_pressed("jump"): - if jump_single or jump_double: Audio.play("sounds/jump_a.ogg, sounds/jump_b.ogg, sounds/jump_c.ogg") - + if jump_double: - gravity = -jump_strength jump_double = false - - if(jump_single): action_jump() - + + if jump_single: + action_jump() + # Weapon switching - + action_weapon_toggle() + # Handle gravity + func handle_gravity(delta): - gravity += 20 * delta - + if gravity > 0 and is_on_floor(): - jump_single = true gravity = 0 + # Jumping + func action_jump(): - gravity = -jump_strength - - jump_single = false; - jump_double = true; + + jump_single = false + jump_double = true + # Shooting + func action_shoot(): - if Input.is_action_pressed("shoot"): - - if !blaster_cooldown.is_stopped(): return # Cooldown for shooting - + if !blaster_cooldown.is_stopped(): + return # Cooldown for shooting + Audio.play(weapon.sound_shoot) - - container.position.z += 0.25 # Knockback of weapon visual - camera.rotation.x += 0.025 # Knockback of camera - movement_velocity += Vector3(0, 0, weapon.knockback) # Knockback - + + container.position.z += 0.25 # Knockback of weapon visual + camera.rotation.x += 0.025 # Knockback of camera + movement_velocity += Vector3(0, 0, weapon.knockback) # Knockback + # Set muzzle flash position, play animation - + muzzle.play("default") - + muzzle.rotation_degrees.z = randf_range(-45, 45) muzzle.scale = Vector3.ONE * randf_range(0.40, 0.75) muzzle.position = container.position - weapon.muzzle_position - + blaster_cooldown.start(weapon.cooldown) - + # Shoot the weapon, amount based on shot count - + 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(): continue # Don't create impact when raycast didn't hit - + + if !raycast.is_colliding(): + continue # Don't create impact when raycast didn't hit + var collider = raycast.get_collider() - + # Hitting an enemy - + if collider.has_method("damage"): collider.damage(weapon.damage) - + # Creating an impact animation - + 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(camera.global_transform.origin, Vector3.UP, true) + impact_instance.look_at(camera.global_transform.origin, Vector3.UP, true) + # Toggle between available weapons (listed in 'weapons') + func action_weapon_toggle(): - if Input.is_action_just_pressed("weapon_toggle"): - weapon_index = wrap(weapon_index + 1, 0, weapons.size()) initiate_change_weapon(weapon_index) - + Audio.play("sounds/weapon_change.ogg") + # Initiates the weapon changing animation (tween) + func initiate_change_weapon(index): - weapon_index = index - + tween = get_tree().create_tween() tween.set_ease(Tween.EASE_OUT_IN) tween.tween_property(container, "position", container_offset - Vector3(0, 1, 0), 0.1) - tween.tween_callback(change_weapon) # Changes the model + tween.tween_callback(change_weapon) # Changes the model + # Switches the weapon model (off-screen) + func change_weapon(): - weapon = weapons[weapon_index] # Step 1. Remove previous weapon model(s) from container - + for n in container.get_children(): container.remove_child(n) - + # Step 2. Place new weapon model in container - + var weapon_model = weapon.model.instantiate() 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 (the weapon camera) - + 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 damage(amount): - health -= amount - health_updated.emit(health) # Update health on HUD - + health_updated.emit(health) # Update health on HUD + if health < 0: - get_tree().reload_current_scene() # Reset when out of health + get_tree().reload_current_scene() # Reset when out of health diff --git a/scripts/audio.gd b/scripts/audio.gd index 77686bb..a1a0d8d 100644 --- a/scripts/audio.gd +++ b/scripts/audio.gd @@ -8,14 +8,14 @@ var bus = "master" var available = [] # The available players. var queue = [] # The queue of sounds to play. -func _ready(): +func _ready(): for i in num_players: var p = AudioStreamPlayer.new() add_child(p) - + available.append(p) - + p.volume_db = -10 p.finished.connect(_on_stream_finished.bind(p)) p.bus = bus @@ -24,16 +24,16 @@ func _ready(): func _on_stream_finished(stream): available.append(stream) -func play(sound_path): # Path (or multiple, separated by commas) + +func play(sound_path): # Path (or multiple, separated by commas) var sounds = sound_path.split(",") queue.append("res://" + sounds[randi() % sounds.size()].strip_edges()) -func _process(_delta): +func _process(_delta): if not queue.is_empty() and not available.is_empty(): - available[0].stream = load(queue.pop_front()) available[0].play() available[0].pitch_scale = randf_range(0.9, 1.1) - + available.pop_front() diff --git a/scripts/hud.gd b/scripts/hud.gd index 2585284..b3b6baa 100644 --- a/scripts/hud.gd +++ b/scripts/hud.gd @@ -1,5 +1,5 @@ extends CanvasLayer + func _on_health_updated(health): - $Health.text = str(health) + "%" diff --git a/scripts/weapon.gd b/scripts/weapon.gd index 4324497..2b72b9b 100644 --- a/scripts/weapon.gd +++ b/scripts/weapon.gd @@ -2,21 +2,21 @@ extends Resource class_name Weapon @export_subgroup("Model") -@export var model:PackedScene # Model of the weapon -@export var position:Vector3 # On-screen position -@export var rotation:Vector3 # On-screen rotation -@export var muzzle_position:Vector3 # On-screen position of muzzle flash +@export var model: PackedScene # Model of the weapon +@export var position: Vector3 # On-screen position +@export var rotation: Vector3 # On-screen rotation +@export var muzzle_position: Vector3 # On-screen position of muzzle flash @export_subgroup("Properties") -@export_range(0.1, 1) var cooldown: float = 0.1 # Firerate -@export_range(1, 20) var max_distance: int = 10 # Fire distance -@export_range(0, 100) var damage: float = 25 # Damage per hit -@export_range(0, 5) var spread: float = 0 # Spread of each shot -@export_range(1, 5) var shot_count: int = 1 # Amount of shots -@export_range(0, 50) var knockback: int = 20 # Amount of knockback +@export_range(0.1, 1) var cooldown: float = 0.1 # Firerate +@export_range(1, 20) var max_distance: int = 10 # Fire distance +@export_range(0, 100) var damage: float = 25 # Damage per hit +@export_range(0, 5) var spread: float = 0 # Spread of each shot +@export_range(1, 5) var shot_count: int = 1 # Amount of shots +@export_range(0, 50) var knockback: int = 20 # Amount of knockback @export_subgroup("Sounds") -@export var sound_shoot: String # Sound path +@export var sound_shoot: String # Sound path @export_subgroup("Crosshair") -@export var crosshair: Texture2D # Image of crosshair on-screen +@export var crosshair: Texture2D # Image of crosshair on-screen