Compare commits

..

10 Commits

8 changed files with 207 additions and 169 deletions

18
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,18 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "GDScript: Launch Project",
"type": "godot",
"request": "launch",
"project": "${workspaceFolder}",
"debug_collisions": false,
"debug_paths": false,
"debug_navigation": false,
"additional_options": ""
}
]
}

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2023 Ryan Haskell-Glatz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,70 +0,0 @@
@tool
class_name FormatOnSave extends EditorPlugin
const SUCCESS: int = 0
const AUTO_RELOAD_SETTING: String = "text_editor/behavior/files/auto_reload_scripts_on_external_change"
var original_auto_reload_setting: bool
# LIFECYCLE EVENTS
func _enter_tree():
activate_auto_reload_setting()
resource_saved.connect(on_resource_saved)
func _exit_tree():
resource_saved.disconnect(on_resource_saved)
restore_original_auto_reload_setting()
# CALLED WHEN A SCRIPT IS SAVED
func on_resource_saved(resource: Resource):
if resource is Script:
var script: Script = resource
var current_script = get_editor_interface().get_script_editor().get_current_script()
var text_edit: CodeEdit = (
get_editor_interface().get_script_editor().get_current_editor().get_base_editor()
)
# Prevents other unsaved scripts from overwriting the active one
if current_script == script:
var filepath: String = ProjectSettings.globalize_path(resource.resource_path)
# Run gdformat
var exit_code = OS.execute("gdformat", [filepath])
# Replace source_code with formatted source_code
if exit_code == SUCCESS:
var formatted_source = FileAccess.get_file_as_string(resource.resource_path)
FormatOnSave.reload_script(text_edit, formatted_source)
# Workaround until this PR is merged:
# https://github.com/godotengine/godot/pull/83267
# Thanks, @KANAjetzt 💖
static func reload_script(text_edit: TextEdit, source_code: String) -> void:
var column := text_edit.get_caret_column()
var row := text_edit.get_caret_line()
var scroll_position_h := text_edit.get_h_scroll_bar().value
var scroll_position_v := text_edit.get_v_scroll_bar().value
text_edit.text = source_code
text_edit.set_caret_column(column)
text_edit.set_caret_line(row)
text_edit.scroll_horizontal = scroll_position_h
text_edit.scroll_vertical = scroll_position_v
text_edit.tag_saved_version()
# For this workaround to work, we need to disable the "Reload/Resave" pop-up
func activate_auto_reload_setting():
var settings := get_editor_interface().get_editor_settings()
original_auto_reload_setting = settings.get(AUTO_RELOAD_SETTING)
settings.set(AUTO_RELOAD_SETTING, true)
# If the plugin is disabled, let's attempt to restore the original editor setting
func restore_original_auto_reload_setting():
var settings := get_editor_interface().get_editor_settings()
settings.set(AUTO_RELOAD_SETTING, original_auto_reload_setting)

View File

@ -1,6 +0,0 @@
[plugin]
name="Format on Save"
description="Runs `gdformat` on save to automatically format your GD script as you code."
author="Ryan Haskell-Glatz"
version="1.2.0"
script="format_on_save.gd"

View File

@ -14,7 +14,6 @@ radius = 0.25
[node name="Player" type="StaticBody3D" node_paths=PackedStringArray("head", "body", "camera", "collision_shape", "state_chart")] [node name="Player" type="StaticBody3D" node_paths=PackedStringArray("head", "body", "camera", "collision_shape", "state_chart")]
script = ExtResource("1_i6r2s") script = ExtResource("1_i6r2s")
max_speed = 6
slope_limit = 40.0 slope_limit = 40.0
step_height = 0.3 step_height = 0.3
snap_to_ground_distance = 0.3 snap_to_ground_distance = 0.3
@ -49,7 +48,8 @@ script = ExtResource("5_mmqqh")
initial_state = NodePath("Grounded") initial_state = NodePath("Grounded")
[node name="Grounded" type="Node" parent="StateChart/Root"] [node name="Grounded" type="Node" parent="StateChart/Root"]
script = ExtResource("6_8xdrw") script = ExtResource("5_mmqqh")
initial_state = NodePath("Idle")
[node name="On Jump" type="Node" parent="StateChart/Root/Grounded"] [node name="On Jump" type="Node" parent="StateChart/Root/Grounded"]
script = ExtResource("7_525mu") script = ExtResource("7_525mu")
@ -61,13 +61,73 @@ script = ExtResource("7_525mu")
to = NodePath("../../Airborne/Falling") to = NodePath("../../Airborne/Falling")
event = &"Airborne" event = &"Airborne"
[node name="Idle" type="Node" parent="StateChart/Root/Grounded"]
script = ExtResource("5_mmqqh")
initial_state = NodePath("Standling")
[node name="Crouched" type="Node" parent="StateChart/Root/Grounded/Idle"]
script = ExtResource("6_8xdrw")
[node name="On Crouch" type="Node" parent="StateChart/Root/Grounded/Idle/Crouched"]
script = ExtResource("7_525mu")
to = NodePath("../../Standling")
event = &"Crouch"
[node name="On Move" type="Node" parent="StateChart/Root/Grounded/Idle/Crouched"]
script = ExtResource("7_525mu")
to = NodePath("../../../Moving/Crouch Walking")
event = &"Move"
[node name="Standling" type="Node" parent="StateChart/Root/Grounded/Idle"]
script = ExtResource("6_8xdrw")
[node name="On Crouch" type="Node" parent="StateChart/Root/Grounded/Idle/Standling"]
script = ExtResource("7_525mu")
to = NodePath("../../Crouched")
event = &"Crouch"
[node name="On Move" type="Node" parent="StateChart/Root/Grounded/Idle/Standling"]
script = ExtResource("7_525mu")
to = NodePath("../../../Moving/Running")
event = &"Move"
[node name="Moving" type="Node" parent="StateChart/Root/Grounded"]
script = ExtResource("5_mmqqh")
initial_state = NodePath("Running")
[node name="Crouch Walking" type="Node" parent="StateChart/Root/Grounded/Moving"]
script = ExtResource("6_8xdrw")
[node name="On Idle" type="Node" parent="StateChart/Root/Grounded/Moving/Crouch Walking"]
script = ExtResource("7_525mu")
to = NodePath("../../../Idle/Crouched")
event = &"Idle"
[node name="On Crouch" type="Node" parent="StateChart/Root/Grounded/Moving/Crouch Walking"]
script = ExtResource("7_525mu")
to = NodePath("../../Running")
event = &"Crouch"
[node name="Running" type="Node" parent="StateChart/Root/Grounded/Moving"]
script = ExtResource("6_8xdrw")
[node name="On Idle" type="Node" parent="StateChart/Root/Grounded/Moving/Running"]
script = ExtResource("7_525mu")
to = NodePath("../../../Idle/Standling")
event = &"Idle"
[node name="On Crouch" type="Node" parent="StateChart/Root/Grounded/Moving/Running"]
script = ExtResource("7_525mu")
to = NodePath("../../Crouch Walking")
event = &"Crouch"
[node name="Airborne" type="Node" parent="StateChart/Root"] [node name="Airborne" type="Node" parent="StateChart/Root"]
script = ExtResource("5_mmqqh") script = ExtResource("5_mmqqh")
initial_state = NodePath("Jumping") initial_state = NodePath("Jumping")
[node name="On Grounded" type="Node" parent="StateChart/Root/Airborne"] [node name="On Grounded" type="Node" parent="StateChart/Root/Airborne"]
script = ExtResource("7_525mu") script = ExtResource("7_525mu")
to = NodePath("../../Grounded") to = NodePath("../../Grounded/Idle/Standling")
event = &"Grounded" event = &"Grounded"
[node name="Jumping" type="Node" parent="StateChart/Root/Airborne"] [node name="Jumping" type="Node" parent="StateChart/Root/Airborne"]
@ -81,6 +141,9 @@ event = &"Airborne"
[node name="Falling" type="Node" parent="StateChart/Root/Airborne"] [node name="Falling" type="Node" parent="StateChart/Root/Airborne"]
script = ExtResource("6_8xdrw") script = ExtResource("6_8xdrw")
[connection signal="state_physics_processing" from="StateChart/Root/Grounded" to="." method="_on_grounded_state_physics_processing"] [connection signal="child_state_entered" from="StateChart/Root" to="." method="_on_root_child_state_entered"]
[connection signal="state_processing" from="StateChart/Root" to="." method="_on_root_state_processing"]
[connection signal="state_processing" from="StateChart/Root/Grounded/Idle" to="." method="_on_idle_state_processing"]
[connection signal="state_physics_processing" from="StateChart/Root/Grounded/Moving/Running" to="." method="_on_running_state_physics_processing"]
[connection signal="state_physics_processing" from="StateChart/Root/Airborne/Jumping" to="." method="_on_jumping_state_physics_processing"] [connection signal="state_physics_processing" from="StateChart/Root/Airborne/Jumping" to="." method="_on_jumping_state_physics_processing"]
[connection signal="state_physics_processing" from="StateChart/Root/Airborne/Falling" to="." method="_on_falling_state_physics_processing"] [connection signal="state_physics_processing" from="StateChart/Root/Airborne/Falling" to="." method="_on_falling_state_physics_processing"]

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=274 format=3 uid="uid://dd4qlg15cchu0"] [gd_scene load_steps=275 format=3 uid="uid://dd4qlg15cchu0"]
[ext_resource type="Script" path="res://addons/qodot/src/nodes/qodot_map.gd" id="1_7j4vn"] [ext_resource type="Script" path="res://addons/qodot/src/nodes/qodot_map.gd" id="1_7j4vn"]
[ext_resource type="PackedScene" uid="uid://ul8o2n823qod" path="res://content/scenes/player.tscn" id="1_qpwbx"] [ext_resource type="PackedScene" uid="uid://ul8o2n823qod" path="res://content/scenes/player.tscn" id="1_qpwbx"]
@ -8,6 +8,7 @@
[ext_resource type="Texture2D" uid="uid://cf6t1ogtfradb" path="res://content/trenchbroom/textures/Prototype/orange9.png" id="5_jv6xk"] [ext_resource type="Texture2D" uid="uid://cf6t1ogtfradb" path="res://content/trenchbroom/textures/Prototype/orange9.png" id="5_jv6xk"]
[ext_resource type="Texture2D" uid="uid://dsocqlbvrhy5m" path="res://content/trenchbroom/textures/Prototype/green2.png" id="6_hoyxc"] [ext_resource type="Texture2D" uid="uid://dsocqlbvrhy5m" path="res://content/trenchbroom/textures/Prototype/green2.png" id="6_hoyxc"]
[ext_resource type="Texture2D" uid="uid://dgxtptqsev380" path="res://content/trenchbroom/textures/Prototype/white6.png" id="7_xqo7b"] [ext_resource type="Texture2D" uid="uid://dgxtptqsev380" path="res://content/trenchbroom/textures/Prototype/white6.png" id="7_xqo7b"]
[ext_resource type="PackedScene" uid="uid://bcwkugn6v3oy7" path="res://addons/godot_state_charts/utilities/state_chart_debugger.tscn" id="9_87np7"]
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_2t7xo"] [sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_2t7xo"]
@ -1658,3 +1659,8 @@ shape = SubResource("ConvexPolygonShape3D_0n1bf")
[node name="Player" parent="." instance=ExtResource("1_qpwbx")] [node name="Player" parent="." instance=ExtResource("1_qpwbx")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7, 1, 10) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7, 1, 10)
[node name="StateChartDebugger" parent="." instance=ExtResource("9_87np7")]
offset_right = -772.0
offset_bottom = -415.0
initial_node_to_watch = NodePath("../Player")

View File

@ -2,22 +2,31 @@ class_name Player extends PhysicsBody3D
enum MovementType { VERTICAL, LATERAL } enum MovementType { VERTICAL, LATERAL }
@export var jump_speed := 4.5 #region Exports
@export var max_speed := 8
@export var slow_speed := 2
@export var mouse_sensitivity := 0.002 # radians/pixel @export var mouse_sensitivity := 0.002 # radians/pixel
## The character will be blocked from moving up slopes steeper than this angle @export_group("Movement")
## The character will be not be flagged as 'grounded' when stood on slopes steeper than this angle @export var slope_limit := 45.0
@export var slope_limit: float = 45
## The character will automatically adjust height to step over obstacles this high
@export var step_height := 0.2 @export var step_height := 0.2
## When grounded, the character will snap down this distance
## This keeps the character on steps, slopes, and helps keep behaviour consistent
@export var snap_to_ground_distance := 0.2 @export var snap_to_ground_distance := 0.2
@export_subgroup("Crouching")
@export var crouch_height_decrease := 1.0
@export var crouch_speed := 2.0
@export var crouch_acceleration_time := 0.5
@export var crouch_deceleration_time := 0.2
@export_subgroup("Running")
@export var run_speed := 8.0
@export var run_acceleration_time := 1.5
@export var run_deceleration_time := 0.5
# TODO Change jumping to take a height, time-to-peak, and time-to-ground. Gravity can be worked out from that
@export_subgroup("Jumping")
@export var jump_speed := 4.5
@export var jump_pre_grace_time := 0.1
@export var jump_post_grace_time := 0.1
@export_group("Connector Nodes") @export_group("Connector Nodes")
@export var head: Node3D @export var head: Node3D
@export var body: Node3D @export var body: Node3D
@ -60,13 +69,15 @@ enum MovementType { VERTICAL, LATERAL }
## accurate. 4 is a decent number for low poly terrain! ## accurate. 4 is a decent number for low poly terrain!
@export var max_iteration_count := 4 @export var max_iteration_count := 4
var jump_pressed := false #endregion
var target_speed := 0.0
var state_enter_speed := 0.0
var gravity := 9.8 var gravity := 9.8
var _velocity: Vector3 = Vector3() var _velocity: Vector3 = Vector3()
var at_max_speed := true var grounded_prev := false
var grounded := true
var grounded: bool = false
var ground_normal: Vector3 var ground_normal: Vector3
var steep_slope_normals: Array[Vector3] = [] var steep_slope_normals: Array[Vector3] = []
var total_stepped_height: float = 0 var total_stepped_height: float = 0
@ -76,17 +87,11 @@ var vertical_collisions: Array[KinematicCollision3D]
var lateral_collisions: Array[KinematicCollision3D] var lateral_collisions: Array[KinematicCollision3D]
var snap_collisions: Array[KinematicCollision3D] var snap_collisions: Array[KinematicCollision3D]
#region Godot Functions
func _ready() -> void: func _ready() -> void:
lock_mouse() _lock_mouse()
func lock_mouse() -> void:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
func unlock_mouse() -> void:
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
# TODO should this have an action associated? # TODO should this have an action associated?
@ -104,35 +109,13 @@ func _input(event: InputEvent) -> void:
var key_event := event as InputEventKey var key_event := event as InputEventKey
if key_event.keycode == KEY_ESCAPE: if key_event.keycode == KEY_ESCAPE:
if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED: if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
unlock_mouse() _unlock_mouse()
else: else:
lock_mouse() _lock_mouse()
# TODO should this be in unhandled input? #endregion
# TODO this should return void and just set the appropriate fields (input_dir!!) #region Core Movement
# Input buffering? lol lmao
func get_input() -> Vector2:
# We don't want to be moving if the mouse isn't captured!
if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED:
return Vector2(0, 0)
jump_pressed = Input.is_action_just_pressed("cc_jump")
at_max_speed = Input.is_action_pressed("cc_sprint")
var input_dir: Vector2 = Vector2()
if Input.is_action_pressed("cc_forward"):
input_dir += Vector2.UP
if Input.is_action_pressed("cc_backward"):
input_dir -= Vector2.UP
if Input.is_action_pressed("cc_left"):
input_dir += Vector2.LEFT
if Input.is_action_pressed("cc_right"):
input_dir -= Vector2.LEFT
input_dir = input_dir.normalized()
# Local rotation is fine given the parent isn't rotating ever
return input_dir.rotated(-body.rotation.y)
# Entry point to moving # Entry point to moving
@ -414,6 +397,10 @@ func snap_down() -> void:
position = before_snap_pos position = before_snap_pos
#endregion
#region Utilities
func _under_slope_limit(normal: Vector3) -> bool: func _under_slope_limit(normal: Vector3) -> bool:
return normal.angle_to(Vector3.UP) < deg_to_rad(slope_limit) return normal.angle_to(Vector3.UP) < deg_to_rad(slope_limit)
@ -426,35 +413,96 @@ func _vert(value: Vector3) -> Vector3:
return Vector3(0, value.y, 0) return Vector3(0, value.y, 0)
func _on_grounded_state_physics_processing(delta: float) -> void: func _lock_mouse() -> void:
var move_speed := max_speed if at_max_speed else slow_speed Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
var target_velocity_h := get_input() * move_speed
func _unlock_mouse() -> void:
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
func _get_move_dir() -> Vector2:
# We don't want to be moving if the mouse isn't captured!
if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED:
return Vector2.ZERO
var input_dir := Vector2.ZERO
if Input.is_action_pressed("cc_forward"):
input_dir += Vector2.UP
if Input.is_action_pressed("cc_backward"):
input_dir -= Vector2.UP
if Input.is_action_pressed("cc_left"):
input_dir += Vector2.LEFT
if Input.is_action_pressed("cc_right"):
input_dir -= Vector2.LEFT
input_dir = input_dir.normalized()
# Local rotation is fine given the parent isn't rotating ever
return input_dir.rotated(-body.rotation.y)
#endregion
#region States
func _on_root_state_processing(_delta: float) -> void:
# !HACK - Printing this here for tracking
print(target_speed)
if _get_move_dir() == Vector2.ZERO:
state_chart.send_event("Idle")
else:
state_chart.send_event("Move")
if Input.is_action_just_pressed("cc_sprint"):
state_chart.send_event("Crouch")
if Input.is_action_just_pressed("cc_jump"):
state_chart.send_event("Jump")
if grounded != grounded_prev:
var event := "Grounded" if grounded else "Airborne"
state_chart.send_event(event)
grounded_prev = grounded
# TODO Rename sprint to crouch
func _on_idle_state_processing(_delta: float) -> void:
# !HACK - No deceleration. Just reset the target speed for now
target_speed = 0.0
func _on_running_state_physics_processing(delta: float) -> void:
if state_enter_speed < run_speed:
target_speed += (run_speed / run_acceleration_time) * delta
target_speed = minf(target_speed, run_speed)
else:
var decel_rate := (state_enter_speed - run_speed) / run_deceleration_time
target_speed -= decel_rate * delta
target_speed = maxf(target_speed, run_speed)
var target_velocity_h := _get_move_dir() * target_speed
var target_velocity_v := -gravity * delta var target_velocity_v := -gravity * delta
var target_velocity := Vector3(target_velocity_h.x, target_velocity_v, target_velocity_h.y) var target_velocity := Vector3(target_velocity_h.x, target_velocity_v, target_velocity_h.y)
move(target_velocity, delta) move(target_velocity, delta)
if jump_pressed:
state_chart.send_event("Jump")
elif !grounded:
state_chart.send_event("Airborne")
func _on_falling_state_physics_processing(delta: float) -> void: func _on_falling_state_physics_processing(delta: float) -> void:
var move_speed := max_speed if at_max_speed else slow_speed var move_speed := run_speed
var target_velocity_h := get_input() * move_speed var target_velocity_h := _get_move_dir() * move_speed
var target_velocity_v := _velocity.y - gravity * delta var target_velocity_v := _velocity.y - gravity * delta
var target_velocity := Vector3(target_velocity_h.x, target_velocity_v, target_velocity_h.y) var target_velocity := Vector3(target_velocity_h.x, target_velocity_v, target_velocity_h.y)
move(target_velocity, delta) move(target_velocity, delta)
if grounded:
state_chart.send_event("Grounded")
func _on_jumping_state_physics_processing(delta: float) -> void: func _on_jumping_state_physics_processing(delta: float) -> void:
var move_speed := max_speed if at_max_speed else slow_speed # !HACK - We're setting this to test run deceleration
var target_velocity_h := get_input() * move_speed target_speed = run_speed + 2.0
var target_velocity_h := _get_move_dir() * target_speed
var target_velocity_v := jump_speed var target_velocity_v := jump_speed
var target_velocity := Vector3(target_velocity_h.x, target_velocity_v, target_velocity_h.y) var target_velocity := Vector3(target_velocity_h.x, target_velocity_v, target_velocity_h.y)
move(target_velocity, delta) move(target_velocity, delta)
state_chart.send_event("Airborne")
func _on_root_child_state_entered() -> void:
state_enter_speed = target_speed
#endregion

View File

@ -29,7 +29,7 @@ project/assembly_name="Character Controller"
[editor_plugins] [editor_plugins]
enabled=PackedStringArray("res://addons/format_on_save/plugin.cfg", "res://addons/godot_state_charts/plugin.cfg", "res://addons/qodot/plugin.cfg") enabled=PackedStringArray("res://addons/godot_state_charts/plugin.cfg", "res://addons/qodot/plugin.cfg")
[input] [input]