Skip to content

Commit

Permalink
Implement rain, water sounds and player death, add README
Browse files Browse the repository at this point in the history
- Add large plane surrounding the level with invisible walls
  to prevent the player from falling out.
  • Loading branch information
Calinou committed Jul 24, 2023
1 parent 5b8dc0b commit e2f83b6
Show file tree
Hide file tree
Showing 22 changed files with 252 additions and 48 deletions.
53 changes: 53 additions & 0 deletions 3d/first_person_shooter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# First-Person Shooter

This is a demo implementing a retro-styled first-person shooter with a 2.5D
aesthetic (level is 3D, but enemies are 2D sprites).

Controls:

- Mouse, arrow keys or <kbd>Gamepad Right Stick</kbd>: Look around
- <kbd>W</kbd>, <kbd>Gamepad Left Stick</kbd>: Move forward
- <kbd>S</kbd>, <kbd>Gamepad Left Stick</kbd>: Move backward
- <kbd>A</kbd>, <kbd>Gamepad Left Stick</kbd>: Move left
- <kbd>D</kbd>, <kbd>Gamepad Left Stick</kbd>: Move right
- <kbd>Space</kbd>, <kbd>Right mouse button</kbd>, <kbd>Gamepad A/Cross</kbd>: Jump
- <kbd>Left mouse button</kbd>, <kbd>Gamepad Right Trigger</kbd>: Attack (or respawn if dead)
- <kbd>F</kbd>, <kbd>Thumb mouse buttons</kbd>, <kbd>Gamepad B/Circle</kbd>: Toggle flashlight
- <kbd>Escape</kbd>, <kbd>Gamepad D-Pad Up</kbd>: Quit

Language: GDScript

Renderer: Forward Plus

## How does it work?

The base vehicle uses a
[`VehicleBody`](https://docs.godotengine.org/en/latest/classes/class_vehiclebody.html)
node. The trailer truck is tied together using a
[`ConeJointTwist`](https://docs.godotengine.org/en/latest/classes/class_conetwistjoint.html)
node, and the tow truck is tried together using a chain made of
[`RigidBody`](https://docs.godotengine.org/en/latest/classes/class_rigidbody.html)
nodes which are pinned together using
[`PinJoint`](https://docs.godotengine.org/en/latest/classes/class_pinjoint.html) nodes.

## Screenshot

![Screenshot](screenshots/first_person_shooter.webp)

## License

- `player/shotgun/spritesheet.png`, `player/shotgun/*.wav` and
`enemy/spritesheet.png` are Copyright © 2001-2022
[Contributors to the Freedoom project](https://freedoom.github.io/)
and are licensed under
[3-clause BSD](https://github.com/freedoom/freedoom/blob/master/COPYING.adoc).
- `player/water_splash_in.ogg` and `player/water_splash_out.ogg` are Copyright ©
2009-2019 [Red Eclipse Team](https://www.redeclipse.net/) and are licensed under
[CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
- `player/rain.ogg` is
[Copyright © Paul Hertz ("ignotus")](https://freesound.org/people/ignotus/sounds/14779/)
and is licensed under
[CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
- `fonts/vga-rom-font.png` is edited from
<https://doomwiki.org/wiki/File:Vga-rom-font.png>, which is considered to be
ineligible for copyright and therefore in the public domain.
2 changes: 1 addition & 1 deletion 3d/first_person_shooter/box.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,4 @@ surface_material_override/0 = SubResource("StandardMaterial3D_ka0y5")
shape = SubResource("BoxShape3D_abq30")

[node name="GPUParticlesCollisionBox3D" type="GPUParticlesCollisionBox3D" parent="."]
extents = Vector3(0.63, 0.63, 0.63)
size = Vector3(1.26, 1.26, 1.26)
2 changes: 1 addition & 1 deletion 3d/first_person_shooter/enemy/enemy.gd
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func _on_line_of_sight_timer_timeout() -> void:
# Allow performing queries immediately after enabling the RayCast.
# Otherwise, we would have to wait one physics frame.
$LineOfSight.force_raycast_update()
can_see_player = not $LineOfSight.is_colliding()
can_see_player = player.health >= 1 and not $LineOfSight.is_colliding()

# Disable RayCast once it's not needed anymore (until the next timer timeout) to improve performance.
$LineOfSight.enabled = false
Expand Down
2 changes: 2 additions & 0 deletions 3d/first_person_shooter/enemy/enemy.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ tracks/1/keys = {
}],
"times": PackedFloat32Array(0.1)
}
tracks/1/use_blend = true
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
Expand Down Expand Up @@ -205,6 +206,7 @@ mesh = SubResource("QuadMesh_he27t")
surface_material_override/0 = SubResource("ShaderMaterial_4tdqe")

[node name="Decal" parent="." instance=ExtResource("4_nscyu")]
size = Vector3(1.2, 2.4, 1.2)
cull_mask = 1048569

[node name="WeaponSound" type="AudioStreamPlayer3D" parent="."]
Expand Down
Binary file modified 3d/first_person_shooter/level.GPUParticlesCollisionSDF3D_data.exr
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ dest_files=["res://.godot/imported/level.GPUParticlesCollisionSDF3D_data.exr-544
[params]

compress/mode=3
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
compress/channel_pack=1
mipmaps/generate=false
mipmaps/limit=-1
Expand Down
59 changes: 47 additions & 12 deletions 3d/first_person_shooter/level.tscn
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[gd_scene load_steps=28 format=3 uid="uid://bcmepm05qldy5"]
[gd_scene load_steps=29 format=3 uid="uid://bcmepm05qldy5"]

[ext_resource type="Texture2D" uid="uid://bc3n2sen32mqx" path="res://sky.png" id="1_0n6b1"]
[ext_resource type="PackedScene" uid="uid://dfyswwfb8j6iv" path="res://player.tscn" id="2_cwuyg"]
Expand Down Expand Up @@ -120,6 +120,8 @@ _data = {
"water_surface": SubResource("Animation_as77u")
}

[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_vy5j4"]

[node name="Node3D" type="Node3D"]

[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
Expand Down Expand Up @@ -169,6 +171,12 @@ use_collision = true
size = Vector3(100, 40, 100)
material = ExtResource("3_b18pi")

[node name="Outside" type="CSGBox3D" parent="Main"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -21, 0)
use_collision = true
size = Vector3(10000, 40, 10000)
material = ExtResource("3_b18pi")

[node name="Inner" type="CSGBox3D" parent="Main"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 16, 0)
operation = 2
Expand Down Expand Up @@ -220,7 +228,7 @@ shape = SubResource("BoxShape3D_04k8c")
[node name="GPUParticlesCollisionBox3D" type="GPUParticlesCollisionBox3D" parent="Main/Water"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -3, 0)
cull_mask = 4294967293
extents = Vector3(7.5, 3, 15)
size = Vector3(15, 6, 30)

[node name="AnimationPlayer" type="AnimationPlayer" parent="Main/Water"]
autoplay = "water_surface"
Expand All @@ -229,7 +237,7 @@ libraries = {
}

[node name="Building" type="CSGBox3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 3, 36)
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 3, 36.5)
use_collision = true
size = Vector3(16, 25, 16)
material = ExtResource("3_b18pi")
Expand Down Expand Up @@ -267,7 +275,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5, -8.25, -10)
distance = 20.5

[node name="Building3" type="CSGBox3D" parent="."]
transform = Transform3D(0.995645, 0, 0.0932221, 0, 1, 0, -0.0932221, 0, 0.995645, -36.2054, 3, 36.0096)
transform = Transform3D(0.995645, 0, 0.0932221, 0, 1, 0, -0.0932221, 0, 0.995645, -36.2054, 3, 36.5096)
use_collision = true
size = Vector3(16, 25, 16)
material = ExtResource("3_b18pi")
Expand Down Expand Up @@ -428,7 +436,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 22.25, -9.5, 16)

[node name="GPUParticlesCollisionSDF3D" type="GPUParticlesCollisionSDF3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.5, 0)
extents = Vector3(51, 17, 51)
size = Vector3(102, 34, 102)
resolution = 3
bake_mask = 4294967281
texture = ExtResource("5_myd3n")
Expand Down Expand Up @@ -473,20 +481,47 @@ transform = Transform3D(0.5, 0, 0.866025, 0, 1, 0, -0.866026, 0, 0.5, -7, -8.875
[node name="Box4" parent="Boxes" instance=ExtResource("5_o3uqf")]
transform = Transform3D(0.965926, 0, -0.258819, 0, 1, 0, 0.258819, 0, 0.965926, -8.5, -8.875, 36.5)

[node name="Box5" parent="Boxes" instance=ExtResource("5_o3uqf")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 28, -10, 39.25)

[node name="Box9" parent="Boxes" instance=ExtResource("5_o3uqf")]
transform = Transform3D(0.5, 0, -0.866025, 0, 1, 0, 0.866025, 0, 0.5, 22.25, -8.875, 42.25)

[node name="Box6" parent="Boxes" instance=ExtResource("5_o3uqf")]
transform = Transform3D(0.906603, 0.421985, 0, -0.421985, 0.906603, 0, 0, 0, 1, 26.8351, -10.0595, 29.75)
[node name="Box10" parent="Boxes" instance=ExtResource("5_o3uqf")]
transform = Transform3D(2.08616e-07, 0, -1, 0, 1, 0, 1, 0, 2.08616e-07, 20.75, -8.875, 40.75)

[node name="Box11" parent="Boxes" instance=ExtResource("5_o3uqf")]
transform = Transform3D(-0.707106, 0, -0.707107, 0, 1, 0, 0.707107, 0, -0.707106, 10.4697, 12.125, 14.1686)

[node name="Box8" parent="Boxes" instance=ExtResource("5_o3uqf")]
transform = Transform3D(-0.234646, -0.109218, -0.965926, -0.421985, 0.906603, 0, 0.875711, 0.407607, -0.258819, 34.3351, -10.0595, 38.5)
[node name="Box12" parent="Boxes" instance=ExtResource("5_o3uqf")]
transform = Transform3D(-0.965926, 0, -0.258819, 0, 1, 0, 0.258819, 0, -0.965926, 11.5303, 12.125, 12.3314)

[node name="Box13" parent="Boxes" instance=ExtResource("5_o3uqf")]
transform = Transform3D(-0.707106, 0, -0.707107, 0, 1, 0, 0.707107, 0, -0.707106, 8.46967, 16.125, 38.6686)

[node name="Box14" parent="Boxes" instance=ExtResource("5_o3uqf")]
transform = Transform3D(-0.965926, 0, -0.258819, 0, 1, 0, 0.258819, 0, -0.965926, 1.03033, 16.125, 31.8314)

[node name="Box15" parent="Boxes" instance=ExtResource("5_o3uqf")]
transform = Transform3D(-0.965926, 0, -0.258819, 0, 1, 0, 0.258819, 0, -0.965926, -33.7197, 6.125, 25.3314)

[node name="Box7" parent="Boxes" instance=ExtResource("5_o3uqf")]
transform = Transform3D(0.5, 0, 0.866025, 0, 1, 0, -0.866026, 0, 0.5, -5.7476, -8.875, 34.6871)

[node name="InvisibleWalls" type="StaticBody3D" parent="."]

[node name="CollisionShape3D" type="CollisionShape3D" parent="InvisibleWalls"]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 2.16371e-06, -49.5)
shape = SubResource("WorldBoundaryShape3D_vy5j4")

[node name="CollisionShape3D2" type="CollisionShape3D" parent="InvisibleWalls"]
transform = Transform3D(-1, -8.74228e-08, 3.82137e-15, 0, -4.37114e-08, -1, 8.74228e-08, -1, 4.37114e-08, 0, -2.16371e-06, 49.5)
shape = SubResource("WorldBoundaryShape3D_vy5j4")

[node name="CollisionShape3D3" type="CollisionShape3D" parent="InvisibleWalls"]
transform = Transform3D(-4.37114e-08, 1, 3.82137e-15, -4.37114e-08, 0, -1, -1, -4.37114e-08, 4.37114e-08, -49.5, -4.58969e-07, 10.5)
shape = SubResource("WorldBoundaryShape3D_vy5j4")

[node name="CollisionShape3D4" type="CollisionShape3D" parent="InvisibleWalls"]
transform = Transform3D(-4.37114e-08, -1, 0, -4.37114e-08, 0, -1, 1, -4.37114e-08, -4.37114e-08, 49.5, -2.16371e-06, 13.75)
shape = SubResource("WorldBoundaryShape3D_vy5j4")

[connection signal="body_entered" from="Main/Water" to="Main/Water" method="_on_body_entered"]
[connection signal="body_exited" from="Main/Water" to="Main/Water" method="_on_body_exited"]
2 changes: 1 addition & 1 deletion 3d/first_person_shooter/platform.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ mesh = SubResource("BoxMesh_tg3xl")
surface_material_override/0 = SubResource("StandardMaterial3D_y10mx")

[node name="GPUParticlesCollisionBox3D" type="GPUParticlesCollisionBox3D" parent="."]
extents = Vector3(3, 0.25, 2)
size = Vector3(6, 0.5, 4)

[node name="MoveTrigger" type="Area3D" parent="."]
collision_layer = 2
Expand Down
58 changes: 47 additions & 11 deletions 3d/first_person_shooter/player.gd
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ var health: int = 100:
$Crosshair.modulate.g = crosshair_color.g
$Crosshair.modulate.b = crosshair_color.b

if health <= 0:
die()

# Time counter for view bobbing (doesn't increment while airborne).
var bob_cycle_counter := 0.0
# Fall impact accumulator for camera landing effect.
Expand All @@ -81,17 +84,26 @@ var damage_roll := 0.0
var touching_ground := false

# `true` if the player is currently in water, `false` otherwise.
var in_water := false
var in_water := false:
set(value):
if value != in_water:
# Water state has changed, play sound accordingly.
if value:
$WaterInSound.play()
else:
$WaterOutSound.play()

in_water = value

# The height of the water plane the player is currently in (in global Y coordinates).
# This is used to check whether the camera is underwater to apply effects.
# When not in water, this is set to negative infinity to ensure checks against it are always `false`.
var water_plane_y := -INF

var base_height := ProjectSettings.get_setting("display/window/size/viewport_height")
var base_height: int = ProjectSettings.get_setting("display/window/size/viewport_height")

# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity := ProjectSettings.get_setting("physics/3d/default_gravity")
var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")

@onready var initial_camera_position: Vector3 = $Camera3D.position
@onready var initial_weapon_sprite_position: Vector3 = $Camera3D/WeaponSprite.position
Expand Down Expand Up @@ -136,7 +148,11 @@ func _physics_process(delta: float) -> void:

# Perform view bobbing based on horizontal movement speed, and also apply the fall bobbing offset.
# We can't use `v_offset` as it would depend on view pitch.
$Camera3D.position.y = initial_camera_position.y + sin(bob_cycle_counter * 10) * 0.01 * Vector3(velocity.x, 0, velocity.z).length() - bob_fall_counter
if health >= 1:
$Camera3D.position.y = initial_camera_position.y + sin(bob_cycle_counter * 10) * 0.01 * Vector3(velocity.x, 0, velocity.z).length() - bob_fall_counter
else:
# Put camera near the floor level while dead.
$Camera3D.position.y = -0.65

# Perform weapon sprite bobbing (horizontal and vertical), and also apply the fall bobbing offset on the weapon sprite.
$Camera3D/WeaponSprite.position.x = initial_weapon_sprite_position.x + cos(bob_cycle_counter * 10) * 0.002 * Vector3(velocity.x, 0, velocity.z).length()
Expand All @@ -147,7 +163,12 @@ func _physics_process(delta: float) -> void:

# Roll the camera based on sideways movement speed.
var roll := velocity.dot($Camera3D.transform.basis.x)
$Camera3D.rotation.z = -roll * 0.003 + damage_roll
if health >= 1:
$Camera3D.rotation.z = -roll * 0.003 + damage_roll
else:
# Roll camera permanently and hide the weapon sprite when dead.
$Camera3D.rotation.z = deg_to_rad(-75)
$Camera3D/WeaponSprite.visible = false

# Character controller.

Expand All @@ -166,7 +187,8 @@ func _physics_process(delta: float) -> void:
velocity.y -= gravity * delta


if Input.is_action_pressed("jump") and (in_water or touching_ground):
if health >= 1 and Input.is_action_pressed("jump") and (in_water or touching_ground):
# Only allow jumping while alive.
if in_water:
# Allow jumping while in water to swim upwards, but more slowly.
# Here, jumping is performed every frame if underwater, so multiply it by `delta`.
Expand All @@ -175,8 +197,12 @@ func _physics_process(delta: float) -> void:
velocity.y = JUMP_VELOCITY

# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
var input_dir := Input.get_vector("move_left", "move_right", "move_forward", "move_backward")
var input_dir := Vector2()
if health >= 1:
input_dir = Input.get_vector("move_left", "move_right", "move_forward", "move_backward")
else:
# Don't allow player movement while dead, but still process gravity and water damping.
input_dir = Vector2()

var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
var motion := Vector3()
Expand All @@ -202,8 +228,6 @@ func _physics_process(delta: float) -> void:

if not Input.is_action_pressed("jump") and touching_ground:
# Stair climbing.
# FIXME: Prevent camera from snapping downwards after landing from a jump.
# FIXME: Check collision normal to prevent stepping on steep slopes (> 46 degrees).
global_position.y = 1.25 + $ShapeCast3D.get_collision_point(0).y
# Apply downwards velocity to stick to slopes and small steps.
velocity.y = -5.0
Expand Down Expand Up @@ -233,8 +257,12 @@ func _process(delta: float) -> void:
$Camera3D.rotation.x = clampf($Camera3D.rotation.x + look.x * LOOK_SENSITIVITY * delta, MIN_VIEW_PITCH, MAX_VIEW_PITCH)
$Camera3D.rotation.y -= look.y * LOOK_SENSITIVITY * delta

# Shooting.
# Shooting (or respawning if currently dead).
if Input.is_action_pressed("attack") and is_zero_approx($ShootTimer.time_left):
if health <= 0:
get_tree().reload_current_scene()
return

for i in SHOTGUN_BULLET_COUNT:
var bullet = preload("res://bullet.tscn").instantiate()
# Bullets are not child of the player to prevent moving along the player.
Expand All @@ -256,6 +284,9 @@ func _process(delta: float) -> void:
else:
$Crosshair.modulate.a = 1.0

# Hide crosshair if dead.
$Crosshair.visible = health >= 1

# Fade out damage effect over time.
$DamageEffect.modulate.a = lerpf($DamageEffect.modulate.a, 0.0, 3 * delta)
damage_roll = lerpf(damage_roll, 0.0, 4 * delta)
Expand All @@ -278,3 +309,8 @@ func _input(event: InputEvent) -> void:

if event.is_action_pressed(&"quit"):
get_tree().quit()


# Called when the player dies.
func die() -> void:
$HUD/Health.text = "You died! Click to retry."
Loading

0 comments on commit e2f83b6

Please sign in to comment.