This event runs monthly on the 2nd Monday of the month. See the home page for the date of our next monthly event.
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
Glasgow Social Game Dev Chatroom Link (Matrix): https://glasgow.social/matrix
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: Add enemies
add_to_group("player")
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
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.play("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")
Goal: Enemy Death and Raycasting
@onready var ui_script = $ui @onready var ray = $Camera3D/RayCast3D
if Input.is_action_just_pressed("ui_accept"): if ui_script.can_shoot: shoot()
func shoot(): if ray.is_colliding() and ray.get_collider().has_method("die"): ray.get_collider().die()
If Input.is_action_just_pressed(“ui_accept”):
toif Input.is_action_pressed("ui_accept"):
Goal: HUD labels and player death
var player_health = 100
function update_player_health(): $health.text = str(get_parent().player_health)
update_player_health()
func damage(): player_health -= 10 print(player_health) if player_health <= 0: queue_free()
play(shoot)
line in the attack() function, add:if $RayCast3D.is_colliding() and $RayCast3D.get_collider().has_method('damage'): $RayCast3D.get_collider().damage()
Goal: Ammo pickups
Goal: Add sound effects
var sound_player = $AudioStreamPlayer match Global.current_weapon: "gun": sound_player.stream = preload("res://gun.ogg") "machine": sound_player.stream = preload("res://machine.ogg") "mini": sound_player.stream = preload("res://mini.ogg") sound_player.play()