From 26f7e773e2a322c42416113a9ed9cd1a490cf3ef Mon Sep 17 00:00:00 2001 From: Zakarya Date: Tue, 19 Dec 2023 18:34:49 -0800 Subject: [PATCH] Much better state machine, crouch ceiling detection and GamePad support --- README.md | 8 +- addons/fpc/character.gd | 172 ++++++++++++++++++++++++-------------- addons/fpc/character.tscn | 7 +- 3 files changed, 122 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 060f2bc..f6cc03c 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Move with WASD, space to jump, shift to sprint, C to crouch. - Crouching - Sprinting - 2 crosshairs/reticles, one is animated (more to come?) + - Controller/GamePad support (enabled through code, see wiki) If you make a cool game with this addon, I would love to hear about it! @@ -26,7 +27,12 @@ If you make a cool game with this addon, I would love to hear about it! You can make this a super basic controller by just disabling everything. -**How to change settings:** +**How to add controller/GamePad support** +In the controls export group, there is a commented section at the end that says "Uncomment this if you want full controller support". Uncomment that block. +Make a key map for each direction (left, right, up, down) and map them to your joystick. +Write in these keymaps in the controls section of the player settings. +In the `_process` function, there is another block of commented code at the end that says the same thing. Uncomment that too. +You should now be able to look around with the joystick. Make sure you add the other controls to the input map. (movement, jumping, crouching, sprinting, etc.) **How to change settings:** Click on the character node and there should be settings in the "Feature Settings" group. diff --git a/addons/fpc/character.gd b/addons/fpc/character.gd index b62fa5c..1a8c824 100644 --- a/addons/fpc/character.gd +++ b/addons/fpc/character.gd @@ -15,6 +15,7 @@ extends CharacterBody3D @export var HEAD : Node3D @export var CAMERA : Camera3D @export var CAMERA_ANIMATION : AnimationPlayer +@export var COLLISION_MESH : CollisionShape3D @export_group("Controls") # We are using UI controls because they are built into Godot Engine so they can be used right away @@ -27,6 +28,12 @@ extends CharacterBody3D @export var CROUCH : String @export var SPRINT : String +# Uncomment if you want full controller support +#@export var LOOK_LEFT : String +#@export var LOOK_RIGHT : String +#@export var LOOK_UP : String +#@export var LOOK_DOWN : String + @export_group("Feature Settings") @export var immobile : bool = false @export var jumping_enabled : bool = true @@ -42,8 +49,9 @@ extends CharacterBody3D # Member variables var speed : float = base_speed -var is_crouching : bool = false -var is_sprinting : bool = false +# States: normal, crouching, sprinting +var state : String = "normal" +var low_ceiling : bool = false # This is for when the cieling is too low and the player needs to crouch. # Get the gravity from the project settings to be synced with RigidBody nodes var gravity : float = ProjectSettings.get_setting("physics/3d/default_gravity") # Don't set this as a const, see the gravity section in _physics_process @@ -52,6 +60,7 @@ var gravity : float = ProjectSettings.get_setting("physics/3d/default_gravity") func _ready(): Input.mouse_mode = Input.MOUSE_MODE_CAPTURED + func _physics_process(delta): # Add some debug data @@ -68,19 +77,13 @@ func _physics_process(delta): var input_dir = Vector2.ZERO if !immobile: input_dir = Input.get_vector(LEFT, RIGHT, FORWARD, BACKWARD) - handle_movement(delta, input_dir) - toggle_crouch() - toggle_sprint(input_dir) - - if is_crouching: - speed = crouch_speed - elif is_sprinting: - speed = sprint_speed - else: - speed = base_speed + low_ceiling = $CrouchCeilingDetection.is_colliding() + handle_state(input_dir) + update_camera_fov() + update_collision_scale() if view_bobbing: headbob_animation(input_dir) @@ -95,15 +98,14 @@ func handle_jumping(): if Input.is_action_just_pressed(JUMP) and is_on_floor(): velocity.y += jump_velocity + func handle_movement(delta, input_dir): - var direction = input_dir.rotated(-HEAD.rotation.y) direction = Vector3(direction.x, 0, direction.y) - move_and_slide() if in_air_momentum: - if is_on_floor(): # Don't lerp y movement + if is_on_floor(): if motion_smoothing: velocity.x = lerp(velocity.x, direction.x * speed, acceleration * delta) velocity.z = lerp(velocity.z, direction.z * speed, acceleration * delta) @@ -119,58 +121,78 @@ func handle_movement(delta, input_dir): velocity.z = direction.z * speed -func _process(delta): - - $UserInterface/DebugPanel.add_property("FPS", 1.0/delta, 0) - - if Input.is_action_just_pressed(PAUSE): - if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED: - Input.mouse_mode = Input.MOUSE_MODE_VISIBLE - elif Input.mouse_mode == Input.MOUSE_MODE_VISIBLE: - Input.mouse_mode = Input.MOUSE_MODE_CAPTURED - - -func _unhandled_input(event): - if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED: - HEAD.rotation_degrees.y -= event.relative.x * mouse_sensitivity - HEAD.rotation_degrees.x -= event.relative.y * mouse_sensitivity - HEAD.rotation.x = clamp(HEAD.rotation.x, deg_to_rad(-90), deg_to_rad(90)) - - -func toggle_crouch(): - if crouch_enabled: - if crouch_mode == 0: - is_crouching = Input.is_action_pressed(CROUCH) - elif crouch_mode == 1: - if Input.is_action_just_pressed(CROUCH): - is_crouching = !is_crouching - - # Replace with your own crouch animation code - if is_crouching: - $Collision.scale.y = lerp($Collision.scale.y, 0.75, 0.2) - else: - $Collision.scale.y = lerp($Collision.scale.y, 1.0, 0.2) - - -func toggle_sprint(moving): +func handle_state(moving): if sprint_enabled: if sprint_mode == 0: - if !is_crouching: # Crouching takes priority over sprinting - is_sprinting = Input.is_action_pressed(SPRINT) - else: - is_sprinting = false # Fix a bug where if you are sprinting and then crouch then let go of the sprinting button you keep sprinting - elif sprint_mode == 1: - if Input.is_action_just_pressed(SPRINT): - if !is_crouching: - is_sprinting = !is_sprinting + if Input.is_action_pressed("sprint") and !Input.is_action_pressed("crouch"): + if moving: + if state != "sprinting": + enter_sprint_state() else: - is_sprinting = false + if state == "sprinting": + enter_normal_state() + elif state == "sprinting": + enter_normal_state() + elif sprint_mode == 1: + if moving: + if Input.is_action_just_pressed("sprint"): + match state: + "normal": + enter_sprint_state() + "sprinting": + enter_normal_state() + elif state == "sprinting": + enter_normal_state() - if dynamic_fov: - if is_sprinting and moving: - CAMERA.fov = lerp(CAMERA.fov, 85.0, 0.3) - else: - CAMERA.fov = lerp(CAMERA.fov, 75.0, 0.3) + if crouch_enabled: + if crouch_mode == 0: + if Input.is_action_pressed("crouch") and !Input.is_action_pressed("sprint"): + if state != "crouching": + enter_crouch_state() + elif state == "crouching" and !$CrouchCeilingDetection.is_colliding(): + enter_normal_state() + elif crouch_mode == 1: + if Input.is_action_just_pressed("crouch"): + match state: + "normal": + enter_crouch_state() + "crouching": + if !$CrouchCeilingDetection.is_colliding(): + enter_normal_state() + + +func enter_normal_state(): + #print("entering normal state") + var prev_state = state + state = "normal" + speed = base_speed + +func enter_crouch_state(): + #print("entering crouch state") + var prev_state = state + state = "crouching" + speed = crouch_speed + + +func enter_sprint_state(): + #print("entering sprint state") + var prev_state = state + state = "sprinting" + speed = sprint_speed + + +func update_camera_fov(): + if state == "sprinting": + CAMERA.fov = lerp(CAMERA.fov, 85.0, 0.3) + else: + CAMERA.fov = lerp(CAMERA.fov, 75.0, 0.3) + + +func update_collision_scale(): + if state == "crouching": # Add your own crouch animation code + COLLISION_MESH.scale.y = lerp(COLLISION_MESH.scale.y, 0.75, 0.2) + else: + COLLISION_MESH.scale.y = lerp(COLLISION_MESH.scale.y, 1.0, 0.2) func headbob_animation(moving): @@ -179,3 +201,27 @@ func headbob_animation(moving): CAMERA_ANIMATION.speed_scale = speed / base_speed else: CAMERA_ANIMATION.play("RESET") + + +func _process(delta): + $UserInterface/DebugPanel.add_property("FPS", 1.0/delta, 0) + $UserInterface/DebugPanel.add_property("State", state, 0) + + if Input.is_action_just_pressed(PAUSE): + if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED: + Input.mouse_mode = Input.MOUSE_MODE_VISIBLE + elif Input.mouse_mode == Input.MOUSE_MODE_VISIBLE: + Input.mouse_mode = Input.MOUSE_MODE_CAPTURED + + HEAD.rotation.x = clamp(HEAD.rotation.x, deg_to_rad(-90), deg_to_rad(90)) + + # Uncomment if you want full controller support + #var controller_view_rotation = Input.get_vector(LOOK_LEFT, LOOK_RIGHT, LOOK_UP, LOOK_DOWN) + #HEAD.rotation_degrees.y -= controller_view_rotation.x * 1.5 + #HEAD.rotation_degrees.x -= controller_view_rotation.y * 1.5 + + +func _unhandled_input(event): + if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED: + HEAD.rotation_degrees.y -= event.relative.x * mouse_sensitivity + HEAD.rotation_degrees.x -= event.relative.y * mouse_sensitivity diff --git a/addons/fpc/character.tscn b/addons/fpc/character.tscn index 2bd165e..dd7aa20 100644 --- a/addons/fpc/character.tscn +++ b/addons/fpc/character.tscn @@ -59,11 +59,12 @@ MarginContainer/constants/margin_left = 10 MarginContainer/constants/margin_right = 10 MarginContainer/constants/margin_top = 10 -[node name="Character" type="CharacterBody3D" node_paths=PackedStringArray("HEAD", "CAMERA", "CAMERA_ANIMATION")] +[node name="Character" type="CharacterBody3D" node_paths=PackedStringArray("HEAD", "CAMERA", "CAMERA_ANIMATION", "COLLISION_MESH")] script = ExtResource("1_0t4e8") HEAD = NodePath("Head") CAMERA = NodePath("Head/Camera") CAMERA_ANIMATION = NodePath("Head/camera_animation") +COLLISION_MESH = NodePath("Collision") CROUCH = "crouch" SPRINT = "sprint" @@ -117,4 +118,8 @@ layout_mode = 2 [node name="VBoxContainer" type="VBoxContainer" parent="UserInterface/DebugPanel/MarginContainer"] layout_mode = 2 +[node name="CrouchCeilingDetection" type="RayCast3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) +target_position = Vector3(0, 1, 0) + [editable path="UserInterface/Reticle_1"]