29 Commits
2.5 ... main

Author SHA1 Message Date
Austin Dennis
2885aadf4e Various enhancements, fixes, and tweaks focused on improving the inspector (#34)
* Added Dynamic Gravity Toggle

Added an editor toggle for dynamic gravity so users don't need to uncomment code in character.gd.

* Refactor controls export variables

Changed controls export variables to be a single dictionary so they can be edited completely in the editor without having to open the script. I also refactored the controller support section to allow similar functionality for controller specific controls and removed the need to uncomment/comment out lines of code to turn on/off controller support.

* Add more editor descriptions and implement camera axis inversion options

Added/tweaked export variable descriptions so everything has one. Also slightly refactored the camera input inversion code and added the ability to invert the camera x axis.

* Added direction based landing animations

Added some logic to determine which landing animation to play so it's not random anymore. Also added a land_center animation for the case of neutral landings. This can be expanded if desired, but it's pretty convincing as is and is ulitmately a small detail.

* Correct various grammar and spelling errors

A final pass to clean up any grammatical or spelling errors in the comments

* Re-organized Character.gd

Hope I'm not overstepping here, but I re-organized Character.gd to make it a bit easier to read and work with. Added editor bookmarks and collapsible code regions. It's hard to parse in the diff so I recommend you look at it in Godot proper.

* Commented out debug print statement

I assume this was left uncommented by mistake. I was wondering what was printing that to my console.

* Fixed Landing Animation Logic

I guess I was up too late on my first attempt because it straight up didn't work. This new version works as far as my testing can tell.

* Removed some unused variables
2024-10-25 13:30:03 -07:00
288618e6a3 Update to Godot 4.3 2024-09-27 19:47:50 -07:00
Zakarya
5d45e48931 Merge pull request #29 from austinrdennis/main
Update character.gd for base_speed fix
2024-09-26 12:06:04 -07:00
Austin Dennis
b59bbb3673 Update character.gd for base_speed fix
base_speed is assigned to speed before the default is updated to what is set in the export variable causing it to be the incorrect value. Calling enter_normal_state() on ready sets speed to the correct value before the player gains control. From my testing, this has no side effects.
2024-09-21 17:31:43 -04:00
Zakarya
b57da3669c fix README 3 2024-08-20 18:27:22 -07:00
Zakarya
e45406d35c fix README 2 2024-08-20 18:26:05 -07:00
Zakarya
7ace3c48b8 fix README 2024-08-20 18:24:39 -07:00
Zakarya
54a07cc6cf Slope/staircase guide 2024-08-20 18:23:22 -07:00
0e2a12ce5c Fix head rotation 2024-07-24 11:35:05 -07:00
aef3c9c0f9 Fixup #25
Make the PR code more readable and match the style of the codebase
2024-07-24 11:29:38 -07:00
3567b157c0 Improve editor module 2024-07-23 20:18:20 -07:00
6c719b52cc Add a toggle button for the debug panel and clean up some comments 2024-07-23 20:18:20 -07:00
Zakarya
7009032757 Merge pull request #25 from DanielKinsman/invert_pitch
Option to invert mouse/joystick y axis (pitch)
2024-07-23 19:03:48 -07:00
Daniel Kinsman
40f5d21452 Option to invert mouse/joystick y axis (pitch) 2024-07-24 11:31:18 +10:00
14a3fb15f0 Remove hardcoded controller sensitivity 2024-07-17 17:25:17 -07:00
bd603973d0 Add descriptions for exported values 2024-07-17 17:21:27 -07:00
a8d151d568 Add controller support to PR #22 2024-07-15 15:10:44 -07:00
b458cba1a1 Rename handle_rotation to handle_head_rotation 2024-07-15 15:10:20 -07:00
Zakarya
4782f80ec2 Merge pull request #22 from Knockturnal/main
Fixed camera jitter
2024-07-15 14:52:49 -07:00
Knockturnal
8a398bade0 Fixed camera jitter 2024-07-15 17:24:58 +02:00
28becd88c2 Set default values for crouch and sprint in the script 2024-07-02 08:56:27 -07:00
Zakarya
1f9bbd73b3 Merge pull request #20 from Phlegmlee/main
Removed hardcoded lines (causing the sprint and crouch bug) from Character.tscn
2024-07-02 08:55:18 -07:00
Tanner
4c0152e688 Merge branch 'ColormaticStudios:main' into main 2024-07-01 22:04:16 -06:00
Tanner
2d654f7600 Remove hard coded lines
Removed the hard coded lines that were causing the sprint and crouch variables to initialize to lowercase "crouch" and "sprint" instead of user specified values in the script.
2024-07-01 22:03:30 -06:00
6257f07766 Add a comment for check_controls 2024-07-01 19:52:49 -07:00
Zakarya
6a6de243dd Merge pull request #19 from Phlegmlee/main
Removed a stray word in the pause error message.
2024-07-01 19:47:09 -07:00
Tanner
669c951a81 Remove stray word in error message
Removed the word "move" from the error statement referring to the action PAUSE.
2024-07-01 19:55:15 -06:00
c64deaa27a Remove an accidental printf debug
I had added this here to find out why the  headbob animation reset
without any smoothing, but didn't realize it was there in the latest
commit.
2024-07-01 17:15:56 -07:00
be0001e554 Reset a RESET value, attempt to fix a camera bug 2024-06-21 00:11:36 -07:00
6 changed files with 346 additions and 212 deletions

View File

@ -33,9 +33,13 @@ You can make this a super basic controller by just disabling everything.
- 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.
- In the `handle_head_rotation` function, there is another block of commented code 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.)
**Slope/staircase:**
Credit to @roberto-urbani23
In the character inspector, you can uncheck Stop on Slope and set the max angle to 89 (for some reason, 90 will make the player stuck). Also Snap Length to 1 otherwise your character will not remain attached to stairs if you sprint while going downstairs.
**How to change settings:**
Click on the character node and there should be settings in the "Feature Settings" group.

View File

@ -1,39 +1,40 @@
@tool
extends Node
# This module affects runtime nad
# This does not effect runtime yet but will in the future.
#TODO: Add descriptions
@export_category("Controller Editor Module")
@export var head_y_rotation : float = 0:
@export_range(-360.0, 360.0, 0.01, "or_greater", "or_less") var head_y_rotation : float = 0.0:
set(new_rotation):
if HEAD:
head_y_rotation = new_rotation
HEAD.rotation.y = head_y_rotation
HEAD.rotation.y = deg_to_rad(head_y_rotation)
update_configuration_warnings()
@export_range(-90.0, 90.0, 0.01, "or_greater", "or_less") var head_x_rotation : float = 0.0:
set(new_rotation):
if HEAD:
head_x_rotation = new_rotation
HEAD.rotation.x = deg_to_rad(head_x_rotation)
update_configuration_warnings()
@export_group("Nodes")
@export var CHARACTER : CharacterBody3D
@export var head_path : String = "Head" # From this nodes parent node
@export var head_path : String = "Head" # Relative to the parent node
#@export var CAMERA : Camera3D
#@export var HEADBOB_ANIMATION : AnimationPlayer
#@export var JUMP_ANIMATION : AnimationPlayer
#@export var CROUCH_ANIMATION : AnimationPlayer
#@export var COLLISION_MESH : CollisionShape3D
var HEAD
@onready var HEAD = get_node("../" + head_path)
func _ready():
HEAD = get_node("../" + head_path)
if Engine.is_editor_hint():
pass
else:
HEAD.rotation.y = head_y_rotation
if !Engine.is_editor_hint():
#print("not editor")
HEAD.rotation.y = deg_to_rad(head_y_rotation)
HEAD.rotation.x = deg_to_rad(head_x_rotation)
func _process(delta):
if Engine.is_editor_hint():
pass
func _get_configuration_warnings():
var warnings = []
@ -41,8 +42,8 @@ func _get_configuration_warnings():
if head_y_rotation > 360:
warnings.append("The head rotation is greater than 360")
if head_y_rotation < 0:
warnings.append("The head rotation is less than 0")
if head_y_rotation < -360:
warnings.append("The head rotation is less than -360")
# Returning an empty array gives no warnings
return warnings

View File

@ -1,72 +1,129 @@
# COPYRIGHT Colormatic Studios
# MIT licence
# MIT license
# Quality Godot First Person Controller v2
extends CharacterBody3D
# TODO: Add descriptions for each value
#region Character Export Group
## The settings for the character's movement and feel.
@export_category("Character")
## The speed that the character moves at without crouching or sprinting.
@export var base_speed : float = 3.0
## The speed that the character moves at when sprinting.
@export var sprint_speed : float = 6.0
## The speed that the character moves at when crouching.
@export var crouch_speed : float = 1.0
## How fast the character speeds up and slows down when Motion Smoothing is on.
@export var acceleration : float = 10.0
## How high the player jumps.
@export var jump_velocity : float = 4.5
## How far the player turns when the mouse is moved.
@export var mouse_sensitivity : float = 0.1
## Invert the X axis input for the camera.
@export var invert_camera_x_axis : bool = false
## Invert the Y axis input for the camera.
@export var invert_camera_y_axis : bool = false
## Whether the player can use movement inputs. Does not stop outside forces or jumping. See Jumping Enabled.
@export var immobile : bool = false
## The reticle file to import at runtime. By default are in res://addons/fpc/reticles/. Set to an empty string to remove.
@export_file var default_reticle
#endregion
#region Nodes Export Group
@export_group("Nodes")
## A reference to the camera for use in the character script. This is the parent node to the camera and is rotated instead of the camera for mouse input.
@export var HEAD : Node3D
## A reference to the camera for use in the character script.
@export var CAMERA : Camera3D
## A reference to the headbob animation for use in the character script.
@export var HEADBOB_ANIMATION : AnimationPlayer
## A reference to the jump animation for use in the character script.
@export var JUMP_ANIMATION : AnimationPlayer
## A reference to the crouch animation for use in the character script.
@export var CROUCH_ANIMATION : AnimationPlayer
## A reference to the the player's collision shape for use in the character script.
@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
@export var JUMP : String = "ui_accept"
@export var LEFT : String = "ui_left"
@export var RIGHT : String = "ui_right"
@export var FORWARD : String = "ui_up"
@export var BACKWARD : String = "ui_down"
@export var PAUSE : String = "ui_cancel"
@export var CROUCH : String
@export var SPRINT : String
#endregion
# 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
#region Controls Export Group
# We are using UI controls because they are built into Godot Engine so they can be used right away
@export_group("Controls")
## Use the Input Map to map a mouse/keyboard input to an action and add a reference to it to this dictionary to be used in the script.
@export var controls : Dictionary = {
LEFT = "ui_left",
RIGHT = "ui_right",
FORWARD = "ui_up",
BACKWARD = "ui_down",
JUMP = "ui_accept",
CROUCH = "crouch",
SPRINT = "sprint",
PAUSE = "ui_cancel"
}
@export_subgroup("Controller Specific")
## This only affects how the camera is handled, the rest should be covered by adding controller inputs to the existing actions in the Input Map.
@export var controller_support : bool = false
## Use the Input Map to map a controller input to an action and add a reference to it to this dictionary to be used in the script.
@export var controller_controls : Dictionary = {
LOOK_LEFT = "look_left",
LOOK_RIGHT = "look_right",
LOOK_UP = "look_up",
LOOK_DOWN = "look_down"
}
## The sensitivity of the analog stick that controls camera rotation. Lower is less sensitive and higher is more sensitive.
@export_range(0.001, 1, 0.001) var look_sensitivity : float = 0.035
#endregion
#region Feature Settings Export Group
@export_group("Feature Settings")
## Enable or disable jumping. Useful for restrictive storytelling environments.
@export var jumping_enabled : bool = true
## Whether the player can move in the air or not.
@export var in_air_momentum : bool = true
## Smooths the feel of walking.
@export var motion_smoothing : bool = true
## Enables or disables sprinting.
@export var sprint_enabled : bool = true
@export var crouch_enabled : bool = true
@export_enum("Hold to Crouch", "Toggle Crouch") var crouch_mode : int = 0
## Toggles the sprinting state when button is pressed or requires the player to hold the button down to remain sprinting.
@export_enum("Hold to Sprint", "Toggle Sprint") var sprint_mode : int = 0
## Enables or disables crouching.
@export var crouch_enabled : bool = true
## Toggles the crouch state when button is pressed or requires the player to hold the button down to remain crouched.
@export_enum("Hold to Crouch", "Toggle Crouch") var crouch_mode : int = 0
## Wether sprinting should effect FOV.
@export var dynamic_fov : bool = true
## If the player holds down the jump button, should the player keep hopping.
@export var continuous_jumping : bool = true
## Enables the view bobbing animation.
@export var view_bobbing : bool = true
## Enables an immersive animation when the player jumps and hits the ground.
@export var jump_animation : bool = true
## This determines wether the player can use the pause button, not wether the game will actually pause.
@export var pausing_enabled : bool = true
## Use with caution.
@export var gravity_enabled : bool = true
## If your game changes the gravity value during gameplay, check this property to allow the player to experience the change in gravity.
@export var dynamic_gravity : bool = false
#endregion
# Member variables
#region Member Variable Initialization
# These are variables used in this script that don't need to be exposed in the editor.
var speed : float = base_speed
var current_speed : float = 0.0
# 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.
var low_ceiling : bool = false # This is for when the ceiling is too low and the player needs to crouch.
var was_on_floor : bool = true # Was the player on the floor last frame (for landing animation)
# The reticle should always have a Control node as the root
@ -75,6 +132,14 @@ var RETICLE : Control
# 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
# Stores mouse input for rotating the camera in the physics process
var mouseInput : Vector2 = Vector2(0,0)
#endregion
#region Main Control Flow
func _ready():
#It is safe to comment this line if your game doesn't start with the mouse captured
@ -87,76 +152,36 @@ func _ready():
if default_reticle:
change_reticle(default_reticle)
# Reset the camera position
# If you want to change the default head height, change these animations.
HEADBOB_ANIMATION.play("RESET")
JUMP_ANIMATION.play("RESET")
CROUCH_ANIMATION.play("RESET")
initialize_animations()
check_controls()
func check_controls(): # If you add a control, you might want to add a check for it here.
if !InputMap.has_action(JUMP):
push_error("No control mapped for jumping. Please add an input map control. Disabling jump.")
jumping_enabled = false
if !InputMap.has_action(LEFT):
push_error("No control mapped for move left. Please add an input map control. Disabling movement.")
immobile = true
if !InputMap.has_action(RIGHT):
push_error("No control mapped for move right. Please add an input map control. Disabling movement.")
immobile = true
if !InputMap.has_action(FORWARD):
push_error("No control mapped for move forward. Please add an input map control. Disabling movement.")
immobile = true
if !InputMap.has_action(BACKWARD):
push_error("No control mapped for move backward. Please add an input map control. Disabling movement.")
immobile = true
if !InputMap.has_action(PAUSE):
push_error("No control mapped for move pause. Please add an input map control. Disabling pausing.")
pausing_enabled = false
if !InputMap.has_action(CROUCH):
push_error("No control mapped for crouch. Please add an input map control. Disabling crouching.")
crouch_enabled = false
if !InputMap.has_action(SPRINT):
push_error("No control mapped for sprint. Please add an input map control. Disabling sprinting.")
sprint_enabled = false
enter_normal_state()
func change_reticle(reticle): # Yup, this function is kinda strange
if RETICLE:
RETICLE.queue_free()
func _process(_delta):
if pausing_enabled:
handle_pausing()
RETICLE = load(reticle).instantiate()
RETICLE.character = self
$UserInterface.add_child(RETICLE)
update_debug_menu_per_frame()
func _physics_process(delta):
# Big thanks to github.com/LorenzoAncora for the concept of the improved debug values
current_speed = Vector3.ZERO.distance_to(get_real_velocity())
$UserInterface/DebugPanel.add_property("Speed", snappedf(current_speed, 0.001), 1)
$UserInterface/DebugPanel.add_property("Target speed", speed, 2)
var cv : Vector3 = get_real_velocity()
var vd : Array[float] = [
snappedf(cv.x, 0.001),
snappedf(cv.y, 0.001),
snappedf(cv.z, 0.001)
]
var readable_velocity : String = "X: " + str(vd[0]) + " Y: " + str(vd[1]) + " Z: " + str(vd[2])
$UserInterface/DebugPanel.add_property("Velocity", readable_velocity, 3)
func _physics_process(delta): # Most things happen here.
# Gravity
#gravity = ProjectSettings.get_setting("physics/3d/default_gravity") # If the gravity changes during your game, uncomment this code
if dynamic_gravity:
gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
if not is_on_floor() and gravity and gravity_enabled:
velocity.y -= gravity * delta
handle_jumping()
var input_dir = Vector2.ZERO
if !immobile: # Immobility works by interrupting user input, so other forces can still be applied to the player
input_dir = Input.get_vector(LEFT, RIGHT, FORWARD, BACKWARD)
if not immobile: # Immobility works by interrupting user input, so other forces can still be applied to the player
input_dir = Input.get_vector(controls.LEFT, controls.RIGHT, controls.FORWARD, controls.BACKWARD)
handle_movement(delta, input_dir)
handle_head_rotation()
# The player is not able to stand up if the ceiling is too low
low_ceiling = $CrouchCeilingDetection.is_colliding()
@ -165,28 +190,28 @@ func _physics_process(delta):
update_camera_fov()
if view_bobbing:
headbob_animation(input_dir)
play_headbob_animation(input_dir)
if jump_animation:
if !was_on_floor and is_on_floor(): # The player just landed
match randi() % 2: #TODO: Change this to detecting velocity direction
0:
JUMP_ANIMATION.play("land_left", 0.25)
1:
JUMP_ANIMATION.play("land_right", 0.25)
play_jump_animation()
update_debug_menu_per_tick()
was_on_floor = is_on_floor() # This must always be at the end of physics_process
#endregion
#region Input Handling
func handle_jumping():
if jumping_enabled:
if continuous_jumping: # Hold down the jump button
if Input.is_action_pressed(JUMP) and is_on_floor() and !low_ceiling:
if Input.is_action_pressed(controls.JUMP) and is_on_floor() and !low_ceiling:
if jump_animation:
JUMP_ANIMATION.play("jump", 0.25)
velocity.y += jump_velocity # Adding instead of setting so jumping on slopes works properly
else:
if Input.is_action_just_pressed(JUMP) and is_on_floor() and !low_ceiling:
if Input.is_action_just_pressed(controls.JUMP) and is_on_floor() and !low_ceiling:
if jump_animation:
JUMP_ANIMATION.play("jump", 0.25)
velocity.y += jump_velocity
@ -214,10 +239,68 @@ func handle_movement(delta, input_dir):
velocity.z = direction.z * speed
func handle_head_rotation():
if invert_camera_x_axis:
HEAD.rotation_degrees.y -= mouseInput.x * mouse_sensitivity * -1
else:
HEAD.rotation_degrees.y -= mouseInput.x * mouse_sensitivity
if invert_camera_y_axis:
HEAD.rotation_degrees.x -= mouseInput.y * mouse_sensitivity * -1
else:
HEAD.rotation_degrees.x -= mouseInput.y * mouse_sensitivity
if controller_support:
var controller_view_rotation = Input.get_vector(controller_controls.LOOK_DOWN, controller_controls.LOOK_UP, controller_controls.LOOK_RIGHT, controller_controls.LOOK_LEFT) * look_sensitivity # These are inverted because of the nature of 3D rotation.
if invert_camera_x_axis:
HEAD.rotation.x += controller_view_rotation.x * -1
else:
HEAD.rotation.x += controller_view_rotation.x
if invert_camera_y_axis:
HEAD.rotation.y += controller_view_rotation.y * -1
else:
HEAD.rotation.y += controller_view_rotation.y
mouseInput = Vector2(0,0)
HEAD.rotation.x = clamp(HEAD.rotation.x, deg_to_rad(-90), deg_to_rad(90))
func check_controls(): # If you add a control, you might want to add a check for it here.
# The actions are being disabled so the engine doesn't halt the entire project in debug mode
if !InputMap.has_action(controls.JUMP):
push_error("No control mapped for jumping. Please add an input map control. Disabling jump.")
jumping_enabled = false
if !InputMap.has_action(controls.LEFT):
push_error("No control mapped for move left. Please add an input map control. Disabling movement.")
immobile = true
if !InputMap.has_action(controls.RIGHT):
push_error("No control mapped for move right. Please add an input map control. Disabling movement.")
immobile = true
if !InputMap.has_action(controls.FORWARD):
push_error("No control mapped for move forward. Please add an input map control. Disabling movement.")
immobile = true
if !InputMap.has_action(controls.BACKWARD):
push_error("No control mapped for move backward. Please add an input map control. Disabling movement.")
immobile = true
if !InputMap.has_action(controls.PAUSE):
push_error("No control mapped for pause. Please add an input map control. Disabling pausing.")
pausing_enabled = false
if !InputMap.has_action(controls.CROUCH):
push_error("No control mapped for crouch. Please add an input map control. Disabling crouching.")
crouch_enabled = false
if !InputMap.has_action(controls.SPRINT):
push_error("No control mapped for sprint. Please add an input map control. Disabling sprinting.")
sprint_enabled = false
#endregion
#region State Handling
func handle_state(moving):
if sprint_enabled:
if sprint_mode == 0:
if Input.is_action_pressed(SPRINT) and state != "crouching":
if Input.is_action_pressed(controls.SPRINT) and state != "crouching":
if moving:
if state != "sprinting":
enter_sprint_state()
@ -228,10 +311,10 @@ func handle_state(moving):
enter_normal_state()
elif sprint_mode == 1:
if moving:
# If the player is holding sprint before moving, handle that cenerio
if Input.is_action_pressed(SPRINT) and state == "normal":
# If the player is holding sprint before moving, handle that scenario
if Input.is_action_pressed(controls.SPRINT) and state == "normal":
enter_sprint_state()
if Input.is_action_just_pressed(SPRINT):
if Input.is_action_just_pressed(controls.SPRINT):
match state:
"normal":
enter_sprint_state()
@ -242,13 +325,13 @@ func handle_state(moving):
if crouch_enabled:
if crouch_mode == 0:
if Input.is_action_pressed(CROUCH) and state != "sprinting":
if Input.is_action_pressed(controls.CROUCH) and state != "sprinting":
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):
if Input.is_action_just_pressed(controls.CROUCH):
match state:
"normal":
enter_crouch_state()
@ -258,7 +341,6 @@ func handle_state(moving):
# Any enter state function should only be called once when you want to enter that state, not every frame.
func enter_normal_state():
#print("entering normal state")
var prev_state = state
@ -269,7 +351,6 @@ func enter_normal_state():
func enter_crouch_state():
#print("entering crouch state")
var prev_state = state
state = "crouching"
speed = crouch_speed
CROUCH_ANIMATION.play("crouch")
@ -282,15 +363,18 @@ func enter_sprint_state():
state = "sprinting"
speed = sprint_speed
#endregion
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)
#region Animation Handling
func initialize_animations():
# Reset the camera position
# If you want to change the default head height, change these animations.
HEADBOB_ANIMATION.play("RESET")
JUMP_ANIMATION.play("RESET")
CROUCH_ANIMATION.play("RESET")
func headbob_animation(moving):
func play_headbob_animation(moving):
if moving and is_on_floor():
var use_headbob_animation : String
match state:
@ -313,36 +397,92 @@ func headbob_animation(moving):
# This code is extremely performant but it makes no sense.
else:
if HEADBOB_ANIMATION.is_playing():
HEADBOB_ANIMATION.play("RESET", 0.25)
if HEADBOB_ANIMATION.current_animation == "sprint" or HEADBOB_ANIMATION.current_animation == "walk":
HEADBOB_ANIMATION.speed_scale = 1
HEADBOB_ANIMATION.play("RESET", 1)
func play_jump_animation():
if !was_on_floor and is_on_floor(): # The player just landed
var facing_direction : Vector3 = CAMERA.get_global_transform().basis.x
var facing_direction_2D : Vector2 = Vector2(facing_direction.x, facing_direction.z).normalized()
var velocity_2D : Vector2 = Vector2(velocity.x, velocity.z).normalized()
func _process(delta):
# Compares velocity direction against the camera direction (via dot product) to determine which landing animation to play.
var side_landed : int = round(velocity_2D.dot(facing_direction_2D))
if side_landed > 0:
JUMP_ANIMATION.play("land_right", 0.25)
elif side_landed < 0:
JUMP_ANIMATION.play("land_left", 0.25)
else:
JUMP_ANIMATION.play("land_center", 0.25)
#endregion
#region Debug Menu
func update_debug_menu_per_frame():
$UserInterface/DebugPanel.add_property("FPS", Performance.get_monitor(Performance.TIME_FPS), 0)
var status : String = state
if !is_on_floor():
status += " in the air"
$UserInterface/DebugPanel.add_property("State", status, 4)
if pausing_enabled:
if Input.is_action_just_pressed(PAUSE):
func update_debug_menu_per_tick():
# Big thanks to github.com/LorenzoAncora for the concept of the improved debug values
current_speed = Vector3.ZERO.distance_to(get_real_velocity())
$UserInterface/DebugPanel.add_property("Speed", snappedf(current_speed, 0.001), 1)
$UserInterface/DebugPanel.add_property("Target speed", speed, 2)
var cv : Vector3 = get_real_velocity()
var vd : Array[float] = [
snappedf(cv.x, 0.001),
snappedf(cv.y, 0.001),
snappedf(cv.z, 0.001)
]
var readable_velocity : String = "X: " + str(vd[0]) + " Y: " + str(vd[1]) + " Z: " + str(vd[2])
$UserInterface/DebugPanel.add_property("Velocity", readable_velocity, 3)
func _unhandled_input(event : InputEvent):
if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
mouseInput.x += event.relative.x
mouseInput.y += event.relative.y
# Toggle debug menu
elif event is InputEventKey:
if event.is_released():
# Where we're going, we don't need InputMap
if event.keycode == 4194338: # F7
$UserInterface/DebugPanel.visible = !$UserInterface/DebugPanel.visible
#endregion
#region Misc Functions
func change_reticle(reticle): # Yup, this function is kinda strange
if RETICLE:
RETICLE.queue_free()
RETICLE = load(reticle).instantiate()
RETICLE.character = self
$UserInterface.add_child(RETICLE)
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 handle_pausing():
if Input.is_action_just_pressed(controls.PAUSE):
# You may want another node to handle pausing, because this player may get paused too.
match Input.mouse_mode:
Input.MOUSE_MODE_CAPTURED:
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
#get_tree().paused = false
Input.MOUSE_MODE_VISIBLE:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
#get_tree().paused = false
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
#endregion

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=21 format=3 uid="uid://cc1m2a1obsyn4"]
[gd_scene load_steps=22 format=3 uid="uid://cc1m2a1obsyn4"]
[ext_resource type="Script" path="res://addons/fpc/character.gd" id="1_0t4e8"]
[ext_resource type="Script" path="res://addons/fpc/EditorModule.gd" id="3_v3ckk"]
@ -174,17 +174,6 @@ tracks/1/keys = {
"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0),
"times": PackedFloat32Array(0)
}
tracks/2/type = "bezier"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("Camera:position:z")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"handle_modes": PackedInt32Array(0),
"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0),
"times": PackedFloat32Array(0)
}
[sub_resource type="Animation" id="Animation_8ku67"]
resource_name = "sprint"
@ -212,17 +201,6 @@ tracks/1/keys = {
"points": PackedFloat32Array(0.05, -0.25, 0, 0.2, -0.01, 0, -0.2, 0.000186046, 0.2, 0.000186046, 0.05, -0.2, -0.01, 0.2, -0.01, 0, -0.2, 0, 0.2, 0, 0.05, -0.2, -0.01, 0.25, 0),
"times": PackedFloat32Array(0, 0.5, 1, 1.5, 2)
}
tracks/2/type = "bezier"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("Camera:position:z")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"handle_modes": PackedInt32Array(0, 0, 0, 0, 0),
"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0, 0, -0.25, 0, 0.25, 0, 0, -0.25, 0, 0.25, 0, 0, -0.25, 0, 0.25, 0, 0, -0.25, 0, 0.25, 0),
"times": PackedFloat32Array(0, 0.5, 1, 1.5, 2)
}
[sub_resource type="Animation" id="Animation_lrqmv"]
resource_name = "walk"
@ -250,17 +228,6 @@ tracks/1/keys = {
"points": PackedFloat32Array(-0.05, -0.25, 0, 0.2, 0.005, 0, -0.2, 0.000186046, 0.2, 0.000186046, -0.05, -0.2, 0.005, 0.2, 0.005, 0, -0.2, 0, 0.2, 0, -0.05, -0.2, 0.005, 0.25, 0),
"times": PackedFloat32Array(0, 0.5, 1, 1.5, 2)
}
tracks/2/type = "bezier"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("Camera:position:z")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"handle_modes": PackedInt32Array(0, 0, 0, 0, 0),
"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0, 0, -0.25, 0, 0.25, 0, 0, -0.25, 0, 0.25, 0, 0, -0.25, 0, 0.25, 0, 0, -0.25, 0, 0.25, 0),
"times": PackedFloat32Array(0, 0.5, 1, 1.5, 2)
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_o0unb"]
_data = {
@ -281,7 +248,7 @@ tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector3(0.0349066, 0, 0)]
"values": [Vector3(0, 0, 0)]
}
tracks/1/type = "value"
tracks/1/imported = false
@ -368,10 +335,39 @@ tracks/1/keys = {
"values": [Vector3(0, 0, 0), Vector3(0, -0.1, 0), Vector3(0, 0, 0)]
}
[sub_resource type="Animation" id="Animation_3eyjl"]
resource_name = "land_center"
length = 1.5
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Camera:rotation")
tracks/0/interp = 2
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.5, 1.5),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 0,
"values": [Vector3(0, 0, 0), Vector3(-0.0349066, 0, 0), Vector3(0, 0, 0)]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Camera:position")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.5, 1.5),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 0,
"values": [Vector3(0, 0, 0), Vector3(0, -0.1, 0), Vector3(0, 0, 0)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_qeg5r"]
_data = {
"RESET": SubResource("Animation_fvvjq"),
"jump": SubResource("Animation_s07ye"),
"land_center": SubResource("Animation_3eyjl"),
"land_left": SubResource("Animation_l1rph"),
"land_right": SubResource("Animation_vsknp")
}
@ -385,6 +381,7 @@ MarginContainer/constants/margin_top = 10
[sub_resource type="SphereShape3D" id="SphereShape3D_k4wwl"]
[node name="Character" type="CharacterBody3D" node_paths=PackedStringArray("HEAD", "CAMERA", "HEADBOB_ANIMATION", "JUMP_ANIMATION", "CROUCH_ANIMATION", "COLLISION_MESH")]
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 0, 0, 0)
script = ExtResource("1_0t4e8")
default_reticle = "res://addons/fpc/reticles/reticle_1.tscn"
HEAD = NodePath("Head")
@ -393,8 +390,6 @@ HEADBOB_ANIMATION = NodePath("Head/HeadbobAnimation")
JUMP_ANIMATION = NodePath("Head/JumpAnimation")
CROUCH_ANIMATION = NodePath("CrouchAnimation")
COLLISION_MESH = NodePath("Collision")
CROUCH = "crouch"
SPRINT = "sprint"
[node name="Mesh" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
@ -413,7 +408,6 @@ libraries = {
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
[node name="Camera" type="Camera3D" parent="Head"]
transform = Transform3D(1, 0, 0, 0, 0.999391, -0.0348995, 0, 0.0348995, 0.999391, 0, 0, 0)
[node name="HeadbobAnimation" type="AnimationPlayer" parent="Head"]
libraries = {

View File

@ -12,7 +12,7 @@ config_version=5
config/name="first person controller 2.0"
run/main_scene="res://test_world.tscn"
config/features=PackedStringArray("4.2", "Forward Plus")
config/features=PackedStringArray("4.3", "Forward Plus")
config/icon="res://icon.svg"
[display]
@ -23,43 +23,43 @@ window/size/mode=2
ui_left={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194319,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194319,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":13,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":-1.0,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
]
}
ui_right={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194321,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194321,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":14,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":1.0,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
]
}
ui_up={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194320,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194320,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":11,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":-1.0,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
]
}
ui_down={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194322,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194322,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":12,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":1.0,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
]
}
crouch={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":67,"key_label":0,"unicode":99,"echo":false,"script":null)
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":67,"key_label":0,"unicode":99,"location":0,"echo":false,"script":null)
]
}
sprint={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"echo":false,"script":null)
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}

View File

@ -65,9 +65,6 @@ uv1_triplanar_sharpness = 0.000850145
[node name="Character" parent="." instance=ExtResource("1_e18vq")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
[node name="Camera" parent="Character/Head" index="0"]
transform = Transform3D(1, 0, 0, 0, 0.999391, -0.0348995, 0, 0.0348995, 0.999391, 0, 0, 0)
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_20rw3")
@ -107,5 +104,3 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.5, 3, -15.5)
use_collision = true
size = Vector3(19, 8, 1)
material = SubResource("StandardMaterial3D_7j4uu")
[editable path="Character"]