This shows you the differences between two versions of the page.
| Both sides previous revision Previous revision Next revision | Previous revision | ||
|
godot [2024/03/18 12:38] admin |
godot [2024/09/09 12:50] (current) admin [Part 5] |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| ====== Getting Started with Godot ====== | ====== Getting Started with Godot ====== | ||
| + | This event runs monthly on the **2nd Monday** of the month. See the [[start|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 [[https://www.youtube.com/@AJsLearningLab|AJ's Learning Lab]], [[https://www.youtube.com/playlist?list=PL4vjw0qHwNZIQZScBFaON0WGkz-BMyoCh|Godot FPS Tutorial - Learn Godot by making Wolfenstein 3D]]. | For our regular monthly tutorial nights, we're basing our teaching on the wonderful series by [[https://www.youtube.com/@AJsLearningLab|AJ's Learning Lab]], [[https://www.youtube.com/playlist?list=PL4vjw0qHwNZIQZScBFaON0WGkz-BMyoCh|Godot FPS Tutorial - Learn Godot by making Wolfenstein 3D]]. | ||
| Line 6: | Line 7: | ||
| All the code we use is available on AJ's Learning Lab github - https://github.com/AJsLearningLab/GodotFPS | 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 | ||
| ===== First Steps ===== | ===== First Steps ===== | ||
| Line 42: | Line 45: | ||
| * In the world scene, drag the player scene into the world | * In the world scene, drag the player scene into the world | ||
| * In the three dot menu (world scene), use 'Add Sun to scene', 'add environment to scene' | * In the three dot menu (world scene), use 'Add Sun to scene', 'add environment to scene' | ||
| - | * In the player scene, use the Mesh button to add a 'Simplified Convext Collision Sibling' mesh to the player's MeshInstance3D | + | * In the player scene, use the Mesh button to add a 'Simplified Convex Collision Sibling' mesh to the player's MeshInstance3D (not 'Single', that'll cause performance issues - why?) |
| * Set up WASD controls in the Project Settings | * Set up WASD controls in the Project Settings | ||
| Line 103: | Line 106: | ||
| $AnimatedSprite2D.play("knife_idle") | $AnimatedSprite2D.play("knife_idle") | ||
| </code> | </code> | ||
| - | * Set the ammo and current_weapon to 'gun'. Test. | + | * Set the ammo to '3' and current_weapon to 'gun'. Test. |
| * Create a global script. File -> New Script (based on Node). Name it global. | * Create a global script. File -> New Script (based on Node). Name it global. | ||
| - | * Move the ammo and current weapon variables from the player script to the global script. | + | * Move the ammo and current weapon variables from the ui script to the global script. |
| * Open the project settings, in Autoload, add the global script and mark it global | * Open the project settings, in Autoload, add the global script and mark it global | ||
| * Tweak the project setting page, allow fullscreen too | * Tweak the project setting page, allow fullscreen too | ||
| * Edit the ui.gd script and change all references to current_weapon and ammo to be Global.current_weapon and Global.ammo | * Edit the ui.gd script and change all references to current_weapon and ammo to be Global.current_weapon and Global.ammo | ||
| * Test everything still works | * Test everything still works | ||
| + | |||
| + | ==== Part 7 ==== | ||
| + | **Goal: Add enemies** | ||
| + | * Create new scene. Other node->CharacterBody3D. Name it guard. | ||
| + | * Add two children to the enemy node. A CollisionShape3D (set to capsule), and a AnimatedSprite3D. | ||
| + | * Set the Y to 1, to make it on the floor. Adjust the capsule size to be 2m. | ||
| + | * Import the guard sprite to the file system. | ||
| + | * Select the AnimatedSprite3D, Click Sprite Frame-> New Sprite Frame. Click it again to set a default animation using the grid, set to 7x2 | ||
| + | * Adjust the eize, choose AnimatedSprite3D. Transform -> y =1, scale = 4 ish. | ||
| + | * A new animations, 'die' and 'shoot' | ||
| + | * Select the AnimatedSprite3D, in the inspector find 'Flags', enable 'Billboard - Y:Billboard' | ||
| + | * Edit the player script and add this line to the _ready() function: | ||
| + | <code> | ||
| + | add_to_group("player") | ||
| + | </code> | ||
| + | * Select the guard root node. Create a new script, use the code below: | ||
| + | <code guard.gd> | ||
| + | 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 | ||
| + | |||
| + | </code> | ||
| + | |||
| ==== Part 6 ==== | ==== Part 6 ==== | ||
| Line 131: | Line 201: | ||
| if Global.current_weapon != "knife" and Global.ammo <= 0: | if Global.current_weapon != "knife" and Global.ammo <= 0: | ||
| Global.current_weapon = "knife" | Global.current_weapon = "knife" | ||
| - | $AnimatedSprite2D.player("knife_idle") | + | $AnimatedSprite2D.play("knife_idle") |
| </code> | </code> | ||
| * Tweak first if statement, change to: | * Tweak first if statement, change to: | ||
| Line 214: | Line 284: | ||
| </code> | </code> | ||
| - | ==== Part 7 ==== | ||
| - | **Goal: Add enemies** | ||
| - | * Create new scene. Other node->CharacterBody3D. Name it guard. | ||
| - | * Add two children to the enemy node. A CollisionShape3D (set to capsule), and a AnimatedSprite3D. | ||
| - | * Set the Y to 1, to make it on the floor. Adjust the capsule size to be 2m. | ||
| - | * Import the guard sprite to the file system. | ||
| - | * Select the AnimatedSprite3D, Click Sprite Frame-> New Sprite Frame. Click it again to set a default animation using the grid, set to 7x2 | ||
| - | * Adjust the eize, choose AnimatedSprite3D. Transform -> y =1, scale = 4 ish. | ||
| - | * A new animations, 'die' and 'shoot' | ||
| - | * Select the AnimatedSprite3D, in the inspector find 'Flags', enable 'Billboard - Y:Billboard' | ||
| - | * Edit the player script and add this line to the _ready() function: | ||
| - | <code> | ||
| - | add_to_group("player") | ||
| - | </code> | ||
| - | * Select the guard root node. Create a new script, use the code below: | ||
| - | <code guard.gd> | ||
| - | 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 | ||
| - | |||
| - | </code> | ||
| ==== Part 8 ==== | ==== Part 8 ==== | ||
| **Goal: Enemy Death and Raycasting** | **Goal: Enemy Death and Raycasting** | ||
| Line 303: | Line 309: | ||
| if ray.is_colliding() and ray.get_collider().has_method("die"): | if ray.is_colliding() and ray.get_collider().has_method("die"): | ||
| ray.get_collider().die() | ray.get_collider().die() | ||
| + | </code> | ||
| + | * To allow strafing, change the ''If Input.is_action_just_pressed("ui_accept"):'' to | ||
| + | <code> | ||
| + | if Input.is_action_pressed("ui_accept"): | ||
| + | </code> | ||
| + | |||
| + | ===== Part 9 ===== | ||
| + | **Goal: HUD labels and player death** | ||
| + | * Add a player health variable to the player.gd script | ||
| + | <code> | ||
| + | var player_health = 100 | ||
| + | </code> | ||
| + | * Create two new Label nodes as a child of the UI, name one 'health'. | ||
| + | * Create a new function in the ui.gd script | ||
| + | <code> | ||
| + | function update_player_health(): | ||
| + | $health.text = str(get_parent().player_health) | ||
| + | </code> | ||
| + | * Add the function to the bottom of the _process() function in the ui.gd script | ||
| + | <code> | ||
| + | update_player_health() | ||
| + | </code> | ||
| + | * Edit the player script and add a new function: | ||
| + | <code> | ||
| + | func damage(): | ||
| + | player_health -= 10 | ||
| + | print(player_health) | ||
| + | if player_health <= 0: | ||
| + | queue_free() | ||
| + | </code> | ||
| + | * Add a RayCast3D to the guard scene | ||
| + | * Set Y = 0, Z = 5 | ||
| + | * After the ''play(shoot)'' line in the attack() function, add: | ||
| + | <code> | ||
| + | if $RayCast3D.is_colliding() and $RayCast3D.get_collider().has_method('damage'): | ||
| + | $RayCast3D.get_collider().damage() | ||
| + | </code> | ||
| + | |||
| + | ===== Part 10 ===== | ||
| + | **Goal: Ammo pickups** | ||
| + | |||
| + | |||
| + | ===== Next Steps ===== | ||
| + | **Goal: Add sound effects** | ||
| + | * Import the sound effect files (gun.ogg, machine.ogg, mini.ogg) | ||
| + | * In the player scene, add a new node, AudioStreamPlayer. | ||
| + | * Add the following code to the player shoot() function: | ||
| + | <code> | ||
| + | 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() | ||
| </code> | </code> | ||