NSANE MANTLE MECHANICS

This commit is contained in:
2026-01-04 14:01:19 +02:00
parent 79f6542faf
commit 7b4c8c7574
12 changed files with 176 additions and 59 deletions

View File

@@ -114,12 +114,12 @@ Anim={
"listener": true,
"lock_rotation": false,
"orthogonal": false,
"position": Vector3(-0.7700564, 8.38965, 0.80201924),
"position": Vector3(-10.162007, 11.304508, -11.346103),
"transform_gizmo": true,
"use_environment": false,
"view_type": 0,
"x_rotation": 0.45814914,
"y_rotation": -8.102711
"x_rotation": 0.5759586,
"y_rotation": -9.202266
}, {
"auto_orthogonal": false,
"auto_orthogonal_enabled": true,
@@ -187,4 +187,4 @@ Anim={
"zfar": 4000.01,
"znear": 0.05
}
selected_nodes=Array[NodePath]([NodePath("/root/@EditorNode@20438/@Panel@14/@VBoxContainer@15/DockHSplitLeftL/DockHSplitLeftR/DockHSplitMain/@VBoxContainer@26/DockVSplitCenter/@VSplitContainer@62/@VBoxContainer@63/@EditorMainScreen@103/MainScreen/@CanvasItemEditor@10871/@VSplitContainer@10516/@HSplitContainer@10518/@HSplitContainer@10520/@Control@10521/@SubViewportContainer@10522/@SubViewport@10523/Node3D")])
selected_nodes=Array[NodePath]([NodePath("/root/@EditorNode@20438/@Panel@14/@VBoxContainer@15/DockHSplitLeftL/DockHSplitLeftR/DockHSplitMain/@VBoxContainer@26/DockVSplitCenter/@VSplitContainer@62/@VBoxContainer@63/@EditorMainScreen@103/MainScreen/@CanvasItemEditor@10871/@VSplitContainer@10516/@HSplitContainer@10518/@HSplitContainer@10520/@Control@10521/@SubViewportContainer@10522/@SubViewport@10523/Node3D/Box2")])

View File

@@ -104,7 +104,7 @@ Anim={
"auto_orthogonal_enabled": true,
"cinematic_preview": false,
"display_mode": 22,
"distance": 3.4293556,
"distance": 4.0000005,
"doppler": false,
"frame_time": false,
"gizmos": true,
@@ -114,12 +114,12 @@ Anim={
"listener": true,
"lock_rotation": false,
"orthogonal": true,
"position": Vector3(0.25380337, 0.7973664, 1.5201828),
"position": Vector3(-2.2754533, 1.4412576, -0.76566285),
"transform_gizmo": true,
"use_environment": false,
"view_type": 6,
"view_type": 4,
"x_rotation": 0.0,
"y_rotation": 3.1415927
"y_rotation": -1.5707964
}, {
"auto_orthogonal": false,
"auto_orthogonal_enabled": true,
@@ -187,4 +187,4 @@ Anim={
"zfar": 4000.01,
"znear": 0.05
}
selected_nodes=Array[NodePath]([NodePath("/root/@EditorNode@20438/@Panel@14/@VBoxContainer@15/DockHSplitLeftL/DockHSplitLeftR/DockHSplitMain/@VBoxContainer@26/DockVSplitCenter/@VSplitContainer@62/@VBoxContainer@63/@EditorMainScreen@103/MainScreen/@CanvasItemEditor@10871/@VSplitContainer@10516/@HSplitContainer@10518/@HSplitContainer@10520/@Control@10521/@SubViewportContainer@10522/@SubViewport@10523/Player/OnAir")])
selected_nodes=Array[NodePath]([NodePath("/root/@EditorNode@20438/@Panel@14/@VBoxContainer@15/DockHSplitLeftL/DockHSplitLeftR/DockHSplitMain/@VBoxContainer@26/DockVSplitCenter/@VSplitContainer@62/@VBoxContainer@63/@EditorMainScreen@103/MainScreen/@CanvasItemEditor@10871/@VSplitContainer@10516/@HSplitContainer@10518/@HSplitContainer@10520/@Control@10521/@SubViewportContainer@10522/@SubViewport@10523/Player/LedgeForwardRay")])

View File

@@ -1,5 +1,5 @@
[folding]
node_unfolds=[NodePath("CameraPivot/Camera3D"), PackedStringArray("Transform"), NodePath("OnAir"), PackedStringArray("shape")]
node_unfolds=[NodePath("CameraPivot/Camera3D"), PackedStringArray("Transform"), NodePath("ClimbCollider"), PackedStringArray("shape"), NodePath("LedgeForwardRay"), PackedStringArray("Transform")]
resource_unfolds=["res://Player.tscn::CapsuleMesh_kpjcp", PackedStringArray(), "res://Player.tscn::CapsuleShape3D_kne1u", PackedStringArray(), "res://Player.tscn::CapsuleShape3D_xhfnw", PackedStringArray()]
nodes_folded=[NodePath("CameraPivot")]

View File

@@ -1,3 +1,4 @@
RayCast3D
Panel
CanvasLayer
CollisionShape3D

View File

@@ -28,9 +28,9 @@ dock_5="Inspector,Node,History"
[EditorNode]
open_scenes=PackedStringArray("res://DevTest.tscn", "res://Player.tscn")
current_scene="res://Player.tscn"
center_split_offset=0
open_scenes=PackedStringArray("res://DevTest.tscn", "res://Player.tscn", "res://Floor.tscn")
current_scene="res://Floor.tscn"
center_split_offset=-257
selected_default_debugger_tab_idx=0
selected_main_editor_idx=2
selected_bottom_panel_item=0

View File

@@ -1,8 +1,8 @@
63f7b34db8d8cdea90c76aacccf841ec
::res://::1767487807
DevTest.tscn::PackedScene::9123247578013174228::1767411651::0::1::::<><><>0<>0<><>::uid://grcsjnqdsbmj::::res://Floor.tscn<>uid://dsj41jw17qh40::::res://Player.tscn
Floor.tscn::PackedScene::456381555447325093::1767482397::0::1::::<><><>0<>0<><>::uid://dwdp3f073q201::::res://assets/Materials/stone_pathway/textures/stone_pathway_02_diff_1k.png<>uid://dth2ctcqe0nri::::res://assets/Materials/stone_pathway/textures/stone_pathway_02_nor_gl_1k.png<>uid://c8vnh4apwdyy6::::res://assets/Materials/stone_pathway/textures/stone_pathway_02_rough_1k.png<>uid://c6c1fcbsll0mm::::res://assets/Materials/wood_planks/textures/wood_planks_diff_1k.png<>uid://dwxtktkem6kgb::::res://assets/Materials/wood_planks/textures/wood_planks_nor_gl_1k.png<>uid://54xt1ehp2lqm::::res://assets/Materials/wood_planks/textures/wood_planks_rough_1k.png
Player.tscn::PackedScene::8443048367702749007::1767481734::0::1::::<><><>0<>0<><>::uid://b8ddkhlwoiuvm::::res://Scripts/player.gd
::res://::1767524602
DevTest.tscn::PackedScene::9123247578013174228::1767488108::0::1::::<><><>0<>0<><>::uid://grcsjnqdsbmj::::res://Floor.tscn<>uid://dsj41jw17qh40::::res://Player.tscn
Floor.tscn::PackedScene::456381555447325093::1767487883::0::1::::<><><>0<>0<><>::uid://dwdp3f073q201::::res://assets/Materials/stone_pathway/textures/stone_pathway_02_diff_1k.png<>uid://dth2ctcqe0nri::::res://assets/Materials/stone_pathway/textures/stone_pathway_02_nor_gl_1k.png<>uid://c8vnh4apwdyy6::::res://assets/Materials/stone_pathway/textures/stone_pathway_02_rough_1k.png<>uid://c6c1fcbsll0mm::::res://assets/Materials/wood_planks/textures/wood_planks_diff_1k.png<>uid://dwxtktkem6kgb::::res://assets/Materials/wood_planks/textures/wood_planks_nor_gl_1k.png<>uid://54xt1ehp2lqm::::res://assets/Materials/wood_planks/textures/wood_planks_rough_1k.png
Player.tscn::PackedScene::8443048367702749007::1767489077::0::1::::<><><>0<>0<><>::uid://b8ddkhlwoiuvm::::res://Scripts/player.gd
::res://assets/::1767416168
icon.svg::CompressedTexture2D::347234531240620840::1767410708::1767410719::1::::<><><>0<>0<>cd8caee1c8a7b4b3cbd67faa4c96c3c0<>res://.godot/imported/icon.svg-56083ea2a1f1a4f1e49773bdc6d7826c.ctex::
::res://assets/Materials/::1767481029
@@ -18,5 +18,5 @@ wood_planks_diff_1k.png::CompressedTexture2D::6954411953879281808::1767480996::1
wood_planks_disp_1k.png::CompressedTexture2D::189196940330241145::1767480998::1767481050::1::::<><><>0<>0<>322da655e040dd3a43fbb683f8f6268a<>res://.godot/imported/wood_planks_disp_1k.png-210b824a140b4c19e9a2cb4661fb0a7e.ctex::
wood_planks_nor_gl_1k.png::CompressedTexture2D::8752069793851707053::1767481002::1767481442::1::::<><><>0<>0<>6f26dd0a61f6c720bb5117cbe149e0db<>res://.godot/imported/wood_planks_nor_gl_1k.png-d024cbb03b846bef5796ea2d214c03f5.s3tc.ctex::
wood_planks_rough_1k.png::CompressedTexture2D::2166964057052015616::1767481004::1767481487::1::::<><><>0<>0<>0cffb3c51099170e58ad0c6876de9261<>res://.godot/imported/wood_planks_rough_1k.png-1147de2f8d0257affd8f6fc6e09389b5.s3tc.ctex::
::res://Scripts/::1767482349
player.gd::GDScript::4709046051602623302::1767482349::0::1::::<>CharacterBody3D<><>0<>0<><>::
::res://Scripts/::1767488944
player.gd::GDScript::4709046051602623302::1767488944::0::1::::<>CharacterBody3D<><>0<>0<><>::

View File

@@ -1,4 +1,3 @@
res://Floor.tscn
res://Player.tscn
res://DevTest.tscn
res://Scripts/player.gd

View File

@@ -1,7 +1,7 @@
[game_view]
select_mode=1
embed_on_play=true
embed_on_play=false
[editor_metadata]
@@ -15,7 +15,7 @@ search_help=Rect2(1920, 300, 960, 600)
[recent_files]
scenes=["res://Player.tscn", "res://DevTest.tscn", "res://node_3d.tscn", "res://Floor.tscn"]
scenes=["res://Floor.tscn", "res://Player.tscn", "res://DevTest.tscn", "res://node_3d.tscn"]
scripts=["res://Scripts/player.gd"]
[script_setup]

View File

@@ -6,8 +6,8 @@ state={
"column": 28,
"folded_lines": Array[int]([]),
"h_scroll_position": 0,
"row": 95,
"scroll_position": 78.0,
"row": 303,
"scroll_position": 284.0,
"selection": false,
"syntax_highlighter": "GDScript"
}

View File

@@ -55,7 +55,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1.0000001, -0.0024477243, -0.001
shape = SubResource("BoxShape3D_mb0cl")
[node name="Box2" type="StaticBody3D" parent="."]
transform = Transform3D(5, 0, 0, 0, 5, 0, 0, 0, 5, -16.142433, 1.5642476, 0)
transform = Transform3D(5, 0, 0, 0, 5, 0, 0, 0, 5, -14.108793, 0.0832777, 0)
[node name="CSGMesh3D" type="CSGMesh3D" parent="Box2"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.029260352, 0)

View File

@@ -9,8 +9,8 @@ radius = 0.49804688
height = 1.9969274
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_xhfnw"]
radius = 0.49804688
height = 1.0760319
radius = 0.58447266
height = 1.310791
[node name="Player" type="CharacterBody3D"]
script = ExtResource("1_xhfnw")
@@ -30,6 +30,14 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.54473054, 0)
current = true
fov = 52.5
[node name="OnAir" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.46046007, 0)
[node name="ClimbCollider" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.3495748, 0)
visible = false
shape = SubResource("CapsuleShape3D_xhfnw")
disabled = true
[node name="LedgeForwardRay" type="RayCast3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
target_position = Vector3(0, 0, -1.4)
[node name="LedgeDownRay" type="RayCast3D" parent="."]

View File

@@ -1,46 +1,65 @@
extends CharacterBody3D
@export var mouse_sensitivity := 1.0
@export var mouse_sensitivity = 1.0
# ===================
# Player
# ===================
@export var capsule_height = 1.8
@export var mantle_forward_offset = 0.6
# =====================
# Movement
# =====================
@export var walk_speed := 10.0
@export var crouch_speed := 5.0
@export var slide_speed := 18.0
@export var jump_velocity := 8.5
@export var walk_speed = 10.0
@export var crouch_speed = 5.0
@export var slide_speed= 18.0
@export var jump_velocity = 8.5
@export var ground_friction := 35.0
@export var slide_friction := 15.0
@export var ground_friction = 35.0
@export var slide_friction = 15.0
# ===== Air Control
@export var air_turn_speed := 7.0
@export var air_input_strength := 50.0
@export var air_drag := 0.5
@export var max_air_speed := 15.0
@export var air_turn_speed = 7.0
@export var air_input_strength = 50.0
@export var air_drag = 0.5
@export var max_air_speed = 15.0
# =====================
# Camera
# =====================
@export var base_fov := 70.0
const SLIDE_FOV_ADD := 35.0
@export var base_fov = 70.0
const SLIDE_FOV_ADD = 35.0
@export var crouch_height := 0.5
@export var stand_height := 1.0
@export var crouch_height = 0.5
@export var stand_height = 1.0
const MAX_LOOK_UP = deg_to_rad(80)
const MAX_LOOK_DOWN= deg_to_rad(-80)
# =====================
# Mantle
# =====================
@export var mantle_max_height = 4.0
@export var mantle_min_height = 0.2
@export var mantle_speed = 14.0
var is_mantling = false
var mantle_target = Vector3.ZERO
const MAX_LOOK_UP := deg_to_rad(80)
const MAX_LOOK_DOWN := deg_to_rad(-80)
# =====================
# Stuff
# =====================
var is_crouching := false
var is_sliding := false
var preserve_air_momentum := false
var just_jumped := false
var is_crouching = false
var is_sliding = false
var preserve_air_momentum = false
var just_jumped = false
var pitch := 0.0
var slide_direction := Vector3.ZERO
var pitch = 0.0
var slide_direction = Vector3.ZERO
@onready var camera_pivot: Node3D = $CameraPivot
@onready var camera: Camera3D = $CameraPivot/Camera3D
@@ -69,6 +88,21 @@ func _physics_process(delta):
target_height,
10.0 * delta
)
if is_mantling:
global_position = global_position.move_toward(
mantle_target,
mantle_speed * delta
)
if global_position.distance_to(mantle_target) < 0.05:
is_mantling = false
return
# Gravity
if not is_on_floor():
@@ -97,6 +131,7 @@ func _physics_process(delta):
apply_air_control(input_dir, delta)
move_and_slide()
try_mantle(delta)
func _input(event):
if event.is_action_pressed("ui_cancel"):
@@ -138,7 +173,7 @@ func handle_ground(input_dir: Vector3, delta):
camera.fov = lerp(camera.fov, base_fov, 10.0 * delta)
if input_dir == Vector3.ZERO:
var horizontal := Vector3(velocity.x, 0, velocity.z)
var horizontal = Vector3(velocity.x, 0, velocity.z)
horizontal = horizontal.move_toward(Vector3.ZERO, ground_friction * delta)
velocity.x = horizontal.x
velocity.z = horizontal.z
@@ -159,11 +194,11 @@ func stop_crouch():
func apply_air_control(input_dir: Vector3, delta):
var horizontal := Vector3(velocity.x, 0, velocity.z)
var horizontal = Vector3(velocity.x, 0, velocity.z)
var forward := -global_transform.basis.z
var right := global_transform.basis.x
var forward = -global_transform.basis.z
var right = global_transform.basis.x
forward.y = 0
right.y = 0
forward = forward.normalized()
@@ -171,11 +206,11 @@ func apply_air_control(input_dir: Vector3, delta):
if input_dir != Vector3.ZERO:
var air_wish_dir := (forward * input_dir.dot(forward)) + (right * input_dir.dot(right))
var air_wish_dir = (forward * input_dir.dot(forward)) + (right * input_dir.dot(right))
horizontal += air_wish_dir.normalized() * air_input_strength * delta
var speed := horizontal.length()
var speed = horizontal.length()
if speed > max_air_speed:
horizontal = horizontal.normalized() * lerp(
speed,
@@ -189,7 +224,7 @@ func apply_air_control(input_dir: Vector3, delta):
func get_input_direction() -> Vector3:
var dir := Vector3.ZERO
var dir = Vector3.ZERO
if Input.is_action_pressed("move_forward"):
dir -= global_transform.basis.z
if Input.is_action_pressed("move_backward"):
@@ -199,3 +234,77 @@ func get_input_direction() -> Vector3:
if Input.is_action_pressed("move_right"):
dir += global_transform.basis.x
return dir.normalized()
func try_mantle(delta):
if is_mantling:
return
if is_on_floor():
return
# Falling or near apex only
if velocity.y > 1.0:
return
var forward_ray = $LedgeForwardRay
if not forward_ray.is_colliding():
return
# Ensure it's a wall
var normal = forward_ray.get_collision_normal()
if normal.dot(Vector3.UP) > 0.2:
return
var wall_point = forward_ray.get_collision_point()
var space_state = get_world_3d().direct_space_state
var from = wall_point + Vector3.UP * mantle_max_height
var to = wall_point - Vector3.UP * 0.2
var query = PhysicsRayQueryParameters3D.create(from, to)
query.exclude = [self]
var result = space_state.intersect_ray(query)
if result.is_empty():
return
var ledge_point = result.position
var ledge_y = ledge_point.y
var feet_y = global_position.y
var chest_y = global_position.y + 1.0
# Feet below ledge
if feet_y > ledge_y - 0.2:
return
# Chest close to ledge height
if abs(ledge_y - chest_y) > 0.8:
return
start_mantle(ledge_point)
func start_mantle(ledge_point: Vector3):
print("MANTLE")
is_mantling = true
velocity = Vector3.ZERO
# Direction player is facing (flat)
var forward = -global_transform.basis.z
forward.y = 0
forward = forward.normalized()
# Move onto the ledge
mantle_target = ledge_point
mantle_target += forward * mantle_forward_offset
# Place feet on top of ledge
mantle_target.y = ledge_point.y + (capsule_height * 0.5)
print("TARGET:", mantle_target)