This is an old revision of the document!
For our regular monthly tutorial nights, we're basing our teaching on the wonderful series by AJ's Learning Lab, Godot FPS Tutorial - Learn Godot by making Wolfenstein 3D.
He has made the assets he uses freely available (please consider donating) here: https://ajslearninglab.itch.io/boganstein-3d-learn-godot-4-with-wolfenstein-3d/
All the code we use is available on AJ's Learning Lab github - https://github.com/AJsLearningLab/GodotFPS
Goal: Learn about Godot, create a world/room (floor and walls)
Goal: Create a player character that can be controlled by the keyboard
const TURN_SPEED = 0.05
if Input.is_action_pressed("ui_left"): self.rotate_y(TURN_SPEED) if Input.is_action_pressed("ui_right"): self.rotate_y(-TURN_SPEED)
Goal: Add textures to the walls and floor
Goal: Add a UI/HUD and a knife
extends CanvasLayer var ammo = 0 var current_weapon = "knife" func _ready(): $AnimatedSprite2D.animation_finished.connect(_on_AnimatedSprite2D_animation_finished) func _process(delta): if Input.is_action_just_pressed("ui_select"): if current_weapon == "knife": $AnimatedSprite2D.play("stab") elif current_weapon == "gun": if ammo > 0: $AnimatedSprite2D.play("shoot") ammo -= 1 func _on_AnimatedSprite2D_animation_finished(): if current_weapon == "knife": $AnimatedSprite2D.play("knife_idle") elif current_weapon == "gun": $AnimatedSprite2D.play("gun_idle")
Goal:Add a hand gun, firing, add a global script
ammo -= 1
line, add:else: current_weapon = "knife" $AnimatedSprite2D.play("knife_idle")
Goal: More weapons
var time_since_last_shot = 0.0 var fire_rate = 1.0
$AnimatedSprite2D.play(Global.current_weapon + "_idle")
time_since_last_shot += delta var can_shoot = time_since_last_shot >= (1.0 / fire_rate) if Global.current_weapon != "knife" and Global.ammo <= 0: Global.current_weapon = "knife" $AnimatedSprite2D.player("knife_idle")
if Input.is_action_pressed("ui_select") and can_shoot:
else: $AnimatedSprite2D.play(Global.current_weapon + "_shoot") time_since_last_shot = 0.0 if Global.current_weapon != "knife": if Global.ammo > 0: Global.ammo -= 1 match Global.current_weapon: "gun": fire_rate = 3.0 "machine": fire_rate = 6.0 "mini": fire_rate = 10.0 "knife": fire_rate = 2.0 _: fire_rate = 1.0 func _on_AnimatedSprite2D_animation_finished(): $AnimatedSprite2D.play(Global.current_weapon + "_idle")
The full code for ui.gd should now be:
extends CanvasLayer var time_since_last_shot = 0.0 var fire_rate = 1.0 func _ready(): $AnimatedSprite2D.animation_finished.connect(_on_AnimatedSprite2D_animation_finished) $AnimatedSprite2D.play(Global.current_weapon + "_idle") func _process(delta): time_since_last_shot += delta var can_shoot = time_since_last_shot >= (1.0 / fire_rate) if Global.current_weapon != "knife" and Global.ammo <= 0: Global.current_weapon = "knife" $AnimatedSprite2D.play("knife_idle") if Input.is_action_pressed("ui_select") and can_shoot: if Global.current_weapon == "knife": $AnimatedSprite2D.play("stab") else: $AnimatedSprite2D.play(Global.current_weapon + "_shoot") time_since_last_shot = 0.0 if Global.current_weapon != "knife": if Global.ammo > 0: Global.ammo -= 1 match Global.current_weapon: "gun": fire_rate = 3.0 "machine": fire_rate = 6.0 "mini": fire_rate = 10.0 "knife": fire_rate = 2.0 _: fire_rate = 1.0 func _on_AnimatedSprite2D_animation_finished(): $AnimatedSprite2D.play(Global.current_weapon + "_idle")
extends CharacterBody3D @onready var player : CharacterBody3D = get_tree().get_first_node_in_group("player") const SPEED = 5.0 var gravity = ProjectSettings.get_setting("physics/3d/default_gravity") # Get the gravity from the project settings to be synced with RigidBody nodes. var dead = false var is_attacking = false var attack_range = 5 func _ready(): add_to_group("enemy") func _physics_process(delta): if dead or is_attacking: # Check if the enemy is dead or attacking return if player == null: return var dir = player.global_position - global_position dir.y = 0.0 dir = dir.normalized() velocity = dir * SPEED # Add the gravity. if not is_on_floor(): velocity.y -= gravity * delta move_and_slide() attack() func attack(): var dist_to_player = global_position.distance_to(player.global_position) if dist_to_player > attack_range: return else: is_attacking = true # Set the attacking flag $AnimatedSprite3D.play("shoot") await $AnimatedSprite3D.animation_finished # Wait for the animation to finish is_attacking = false # Reset the attacking flag func die(): dead = true # Corrected variable scope $AnimatedSprite3D.play("die") $CollisionShape3D.disabled = true