├── .gitignore ├── .gitattributes ├── StairsSetting.gd ├── ThirdPersonButton.gd ├── art ├── blockmesh texture red.png ├── blockmesh texture blue.png ├── blockmesh texture green.png ├── blockmesh texture purple.png ├── blockmesh texture darkgray.png ├── blockmesh texture red.png.import ├── blockmesh texture blue.png.import ├── blockmesh texture green.png.import ├── blockmesh texture purple.png.import └── blockmesh texture darkgray.png.import ├── CollisionBoxPicker.gd ├── README.md ├── ResetButton.gd ├── icon.svg ├── icon.svg.import ├── world.gd ├── LICENSE ├── world.tscn ├── project.godot ├── player └── SimplePlayer.gd └── StairsTestScene.tscn /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot 4+ specific ignores 2 | .godot/ 3 | 4 | .export/ 5 | .trash/ 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize EOL for all files that Git considers text files. 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /StairsSetting.gd: -------------------------------------------------------------------------------- 1 | extends CheckButton 2 | 3 | func _on_pressed() -> void: 4 | release_focus() 5 | -------------------------------------------------------------------------------- /ThirdPersonButton.gd: -------------------------------------------------------------------------------- 1 | extends CheckButton 2 | 3 | func _on_pressed() -> void: 4 | release_focus() 5 | -------------------------------------------------------------------------------- /art/blockmesh texture red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wareya/GodotStairTester/HEAD/art/blockmesh texture red.png -------------------------------------------------------------------------------- /art/blockmesh texture blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wareya/GodotStairTester/HEAD/art/blockmesh texture blue.png -------------------------------------------------------------------------------- /art/blockmesh texture green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wareya/GodotStairTester/HEAD/art/blockmesh texture green.png -------------------------------------------------------------------------------- /art/blockmesh texture purple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wareya/GodotStairTester/HEAD/art/blockmesh texture purple.png -------------------------------------------------------------------------------- /CollisionBoxPicker.gd: -------------------------------------------------------------------------------- 1 | extends OptionButton 2 | 3 | func _on_item_selected(_index: int) -> void: 4 | release_focus() 5 | -------------------------------------------------------------------------------- /art/blockmesh texture darkgray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wareya/GodotStairTester/HEAD/art/blockmesh texture darkgray.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GodotStairTester 2 | Test project for stair-stepping in Godot 4. Comes with some basic test geometry and an FPS character controller that supports stair-stepping. 3 | 4 | ![Godot_v4 1-stable_win64_2023-07-24_19-54-26](https://github.com/wareya/GodotStairTester/assets/585488/2b8ec849-dc41-45c3-a69d-61d3efead1cf) 5 | -------------------------------------------------------------------------------- /ResetButton.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | var start_pos = Vector3() 4 | func _ready() -> void: 5 | start_pos = $"../CharacterBody3D".global_position 6 | 7 | func _on_pressed() -> void: 8 | $"../CharacterBody3D".global_position = start_pos 9 | $"../CharacterBody3D".velocity *= 0.0 10 | $"../CharacterBody3D/CameraHolder".rotation.y = 0.0 11 | $"../CharacterBody3D/CameraHolder".rotation.x = 0.0 12 | release_focus() 13 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /icon.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dqg75eco4aidf" 6 | path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://icon.svg" 14 | dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /art/blockmesh texture red.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cqtrnlqmmhffx" 6 | path.s3tc="res://.godot/imported/blockmesh texture red.png-261196aedbe9f08e82453cb4bc82d9b3.s3tc.ctex" 7 | metadata={ 8 | "imported_formats": ["s3tc_bptc"], 9 | "vram_texture": true 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://art/blockmesh texture red.png" 15 | dest_files=["res://.godot/imported/blockmesh texture red.png-261196aedbe9f08e82453cb4bc82d9b3.s3tc.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=2 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/hdr_compression=1 23 | compress/normal_map=0 24 | compress/channel_pack=0 25 | mipmaps/generate=true 26 | mipmaps/limit=-1 27 | roughness/mode=0 28 | roughness/src_normal="" 29 | process/fix_alpha_border=true 30 | process/premult_alpha=false 31 | process/normal_map_invert_y=false 32 | process/hdr_as_srgb=false 33 | process/hdr_clamp_exposure=false 34 | process/size_limit=0 35 | detect_3d/compress_to=0 36 | -------------------------------------------------------------------------------- /art/blockmesh texture blue.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://blejfycf2xpm7" 6 | path.s3tc="res://.godot/imported/blockmesh texture blue.png-c5de409b1f47d4683cb5c287863dc4b9.s3tc.ctex" 7 | metadata={ 8 | "imported_formats": ["s3tc_bptc"], 9 | "vram_texture": true 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://art/blockmesh texture blue.png" 15 | dest_files=["res://.godot/imported/blockmesh texture blue.png-c5de409b1f47d4683cb5c287863dc4b9.s3tc.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=2 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/hdr_compression=1 23 | compress/normal_map=0 24 | compress/channel_pack=0 25 | mipmaps/generate=true 26 | mipmaps/limit=-1 27 | roughness/mode=0 28 | roughness/src_normal="" 29 | process/fix_alpha_border=true 30 | process/premult_alpha=false 31 | process/normal_map_invert_y=false 32 | process/hdr_as_srgb=false 33 | process/hdr_clamp_exposure=false 34 | process/size_limit=0 35 | detect_3d/compress_to=0 36 | -------------------------------------------------------------------------------- /art/blockmesh texture green.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cuy43ijkbhula" 6 | path.s3tc="res://.godot/imported/blockmesh texture green.png-d4a2694b88bf12eb0d4bc67ce326e07d.s3tc.ctex" 7 | metadata={ 8 | "imported_formats": ["s3tc_bptc"], 9 | "vram_texture": true 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://art/blockmesh texture green.png" 15 | dest_files=["res://.godot/imported/blockmesh texture green.png-d4a2694b88bf12eb0d4bc67ce326e07d.s3tc.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=2 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/hdr_compression=1 23 | compress/normal_map=0 24 | compress/channel_pack=0 25 | mipmaps/generate=true 26 | mipmaps/limit=-1 27 | roughness/mode=0 28 | roughness/src_normal="" 29 | process/fix_alpha_border=true 30 | process/premult_alpha=false 31 | process/normal_map_invert_y=false 32 | process/hdr_as_srgb=false 33 | process/hdr_clamp_exposure=false 34 | process/size_limit=0 35 | detect_3d/compress_to=0 36 | -------------------------------------------------------------------------------- /art/blockmesh texture purple.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://1ihienek4uv4" 6 | path.s3tc="res://.godot/imported/blockmesh texture purple.png-fd0855cc9af3c4b0694c16869bf59261.s3tc.ctex" 7 | metadata={ 8 | "imported_formats": ["s3tc_bptc"], 9 | "vram_texture": true 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://art/blockmesh texture purple.png" 15 | dest_files=["res://.godot/imported/blockmesh texture purple.png-fd0855cc9af3c4b0694c16869bf59261.s3tc.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=2 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/hdr_compression=1 23 | compress/normal_map=0 24 | compress/channel_pack=0 25 | mipmaps/generate=true 26 | mipmaps/limit=-1 27 | roughness/mode=0 28 | roughness/src_normal="" 29 | process/fix_alpha_border=true 30 | process/premult_alpha=false 31 | process/normal_map_invert_y=false 32 | process/hdr_as_srgb=false 33 | process/hdr_clamp_exposure=false 34 | process/size_limit=0 35 | detect_3d/compress_to=0 36 | -------------------------------------------------------------------------------- /art/blockmesh texture darkgray.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://8jjphj6non1s" 6 | path.s3tc="res://.godot/imported/blockmesh texture darkgray.png-41b063196bfff81347eae2a74f8492c7.s3tc.ctex" 7 | metadata={ 8 | "imported_formats": ["s3tc_bptc"], 9 | "vram_texture": true 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://art/blockmesh texture darkgray.png" 15 | dest_files=["res://.godot/imported/blockmesh texture darkgray.png-41b063196bfff81347eae2a74f8492c7.s3tc.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=2 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/hdr_compression=1 23 | compress/normal_map=0 24 | compress/channel_pack=0 25 | mipmaps/generate=true 26 | mipmaps/limit=-1 27 | roughness/mode=0 28 | roughness/src_normal="" 29 | process/fix_alpha_border=true 30 | process/premult_alpha=false 31 | process/normal_map_invert_y=false 32 | process/hdr_as_srgb=false 33 | process/hdr_clamp_exposure=false 34 | process/size_limit=0 35 | detect_3d/compress_to=0 36 | -------------------------------------------------------------------------------- /world.gd: -------------------------------------------------------------------------------- 1 | extends Node3D 2 | 3 | 4 | 5 | func _ready() -> void: 6 | $"CollisionBox".add_item("AABB") 7 | $"CollisionBox".add_item("Cylinder") 8 | $"CollisionBox".add_item("Capsule") 9 | $"CollisionBox".add_item("Gem") 10 | $"CollisionBox".add_item("Octogem") 11 | 12 | # Called every frame. 'delta' is the elapsed time since the previous frame. 13 | func _process(delta: float) -> void: 14 | $CharacterBody3D.do_stairs = $StairsSetting.button_pressed 15 | $CharacterBody3D.do_skipping_hack = $SkippingSetting.button_pressed 16 | $CharacterBody3D.third_person = $ThirdPerson.button_pressed 17 | 18 | $"FPS".text = "framerate: " + str(Engine.get_frames_per_second()) 19 | $"Vel".text = str($CharacterBody3D.velocity) 20 | 21 | $CharacterBody3D/AABB.disabled = $CollisionBox.selected != 0 22 | $CharacterBody3D/Gem.disabled = $CollisionBox.selected != 3 23 | $CharacterBody3D/Octogem.disabled = $CollisionBox.selected != 4 24 | $CharacterBody3D/Cylinder.disabled = $CollisionBox.selected != 1 25 | $CharacterBody3D/Capsule.disabled = $CollisionBox.selected != 2 26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /world.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=20 format=3 uid="uid://byikl3mg1l0io"] 2 | 3 | [ext_resource type="PackedScene" uid="uid://b5rmjajnlo22w" path="res://StairsTestScene.tscn" id="1_0ystr"] 4 | [ext_resource type="Script" path="res://player/SimplePlayer.gd" id="1_2s7tp"] 5 | [ext_resource type="Script" path="res://world.gd" id="1_mkwvl"] 6 | [ext_resource type="Script" path="res://ResetButton.gd" id="3_2mp0y"] 7 | [ext_resource type="Script" path="res://CollisionBoxPicker.gd" id="4_1xxhe"] 8 | [ext_resource type="Script" path="res://StairsSetting.gd" id="4_05imk"] 9 | [ext_resource type="Script" path="res://ThirdPersonButton.gd" id="6_im2oe"] 10 | 11 | [sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_21fc1"] 12 | sky_top_color = Color(0.172549, 0.517647, 1, 1) 13 | sky_horizon_color = Color(0.772549, 0.858824, 0.996078, 1) 14 | ground_horizon_color = Color(0.64625, 0.65575, 0.67075, 1) 15 | 16 | [sub_resource type="Sky" id="Sky_7nx6r"] 17 | sky_material = SubResource("ProceduralSkyMaterial_21fc1") 18 | 19 | [sub_resource type="Environment" id="Environment_5u6b6"] 20 | background_mode = 2 21 | sky = SubResource("Sky_7nx6r") 22 | tonemap_mode = 2 23 | glow_enabled = true 24 | 25 | [sub_resource type="BoxShape3D" id="BoxShape3D_x1wx2"] 26 | size = Vector3(0.625, 1.75, 0.625) 27 | 28 | [sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_kujgx"] 29 | points = PackedVector3Array(-0.312, 0.6, -0.312, -0.312, 0.6, 0.313, 0.313, 0.6, -0.312, 0.313, 0.6, 0.313, 0, 0.875, 0, 0, -0.875, 0, -0.312, -0.6, -0.312, -0.312, -0.6, 0.312, 0.312, -0.6, -0.312, 0.312, -0.6, 0.312) 30 | 31 | [sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_mkkux"] 32 | points = PackedVector3Array(0, 0.6, -0.313, 0, 0.6, 0.313, -0.313, 0.6, 0, 0.313, 0.6, 0, 0, 0.875, 0, 0, -0.875, 0, 0, -0.6, -0.313, 0, -0.6, 0.313, -0.313, -0.6, 0, 0.313, -0.6, 0, 0.221, 0.6, 0.221, 0.221, 0.6, -0.221, -0.221, 0.6, 0.221, -0.221, 0.6, -0.221, 0.221, -0.6, 0.221, 0.221, -0.6, -0.221, -0.221, -0.6, 0.221, -0.221, -0.6, -0.221) 33 | 34 | [sub_resource type="CylinderShape3D" id="CylinderShape3D_eu7ao"] 35 | margin = 0.0 36 | height = 1.75 37 | radius = 0.313 38 | 39 | [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_oswhk"] 40 | radius = 0.313 41 | height = 1.75 42 | 43 | [sub_resource type="TextMesh" id="TextMesh_t6kdy"] 44 | text = "+" 45 | font_size = 22 46 | pixel_size = 0.05 47 | 48 | [sub_resource type="Shader" id="Shader_wk5yv"] 49 | code = "// NOTE: Shader automatically converted from Godot Engine 4.1.stable's StandardMaterial3D. 50 | 51 | shader_type spatial; 52 | render_mode blend_mix,cull_back,unshaded,shadows_disabled,skip_vertex_transform; 53 | uniform vec4 albedo : source_color; 54 | 55 | 56 | uniform float pixels_size = 32.0; 57 | uniform float scale = 1.0; 58 | 59 | void vertex() { 60 | float near = 0.05; 61 | float fov_scale = 2.0 / PROJECTION_MATRIX[1][1]; 62 | VERTEX *= fov_scale / VIEWPORT_SIZE.y * pixels_size * scale; 63 | VERTEX.xyz = vec3(-VERTEX.x, -VERTEX.y, -1.0)*near; 64 | } 65 | 66 | void fragment() { 67 | ALBEDO = albedo.rgb; 68 | ALPHA = 2.0; 69 | } 70 | " 71 | 72 | [sub_resource type="ShaderMaterial" id="ShaderMaterial_05pxi"] 73 | render_priority = 0 74 | shader = SubResource("Shader_wk5yv") 75 | shader_parameter/albedo = Color(0.501961, 0.501961, 0.501961, 1) 76 | shader_parameter/pixels_size = 32.0 77 | shader_parameter/scale = 1.0 78 | 79 | [sub_resource type="QuadMesh" id="QuadMesh_48u70"] 80 | size = Vector2(0.1, 0.1) 81 | 82 | [node name="World" type="Node3D"] 83 | script = ExtResource("1_mkwvl") 84 | 85 | [node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] 86 | transform = Transform3D(-0.866023, -0.433016, 0.250001, 0, 0.499998, 0.866027, -0.500003, 0.749999, -0.43301, 0, 0, 0) 87 | shadow_enabled = true 88 | 89 | [node name="WorldEnvironment" type="WorldEnvironment" parent="."] 90 | environment = SubResource("Environment_5u6b6") 91 | 92 | [node name="CharacterBody3D" type="CharacterBody3D" parent="."] 93 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 3) 94 | wall_min_slide_angle = 0.0 95 | floor_constant_speed = true 96 | script = ExtResource("1_2s7tp") 97 | 98 | [node name="AABB" type="CollisionShape3D" parent="CharacterBody3D"] 99 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.875, 0) 100 | shape = SubResource("BoxShape3D_x1wx2") 101 | disabled = true 102 | 103 | [node name="Gem" type="CollisionShape3D" parent="CharacterBody3D"] 104 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.875, 0) 105 | visible = false 106 | shape = SubResource("ConvexPolygonShape3D_kujgx") 107 | disabled = true 108 | 109 | [node name="Octogem" type="CollisionShape3D" parent="CharacterBody3D"] 110 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.875, 0) 111 | visible = false 112 | shape = SubResource("ConvexPolygonShape3D_mkkux") 113 | disabled = true 114 | 115 | [node name="Cylinder" type="CollisionShape3D" parent="CharacterBody3D"] 116 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.875, 0) 117 | visible = false 118 | shape = SubResource("CylinderShape3D_eu7ao") 119 | disabled = true 120 | 121 | [node name="Capsule" type="CollisionShape3D" parent="CharacterBody3D"] 122 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.875, 0) 123 | visible = false 124 | shape = SubResource("CapsuleShape3D_oswhk") 125 | 126 | [node name="CameraHolder" type="Node3D" parent="CharacterBody3D"] 127 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0) 128 | 129 | [node name="Camera3D" type="Camera3D" parent="CharacterBody3D/CameraHolder"] 130 | fov = 90.0 131 | 132 | [node name="Crosshair" type="MeshInstance3D" parent="CharacterBody3D/CameraHolder/Camera3D"] 133 | extra_cull_margin = 16384.0 134 | mesh = SubResource("TextMesh_t6kdy") 135 | skeleton = NodePath("../../../..") 136 | surface_material_override/0 = SubResource("ShaderMaterial_05pxi") 137 | 138 | [node name="Level" parent="." instance=ExtResource("1_0ystr")] 139 | 140 | [node name="Panel" type="Panel" parent="."] 141 | offset_right = 292.0 142 | offset_bottom = 235.0 143 | 144 | [node name="FPS" type="Label" parent="."] 145 | offset_right = 40.0 146 | offset_bottom = 23.0 147 | 148 | [node name="Vel" type="Label" parent="."] 149 | offset_top = 19.0 150 | offset_right = 40.0 151 | offset_bottom = 42.0 152 | 153 | [node name="Instruction" type="Label" parent="."] 154 | offset_left = -1.0 155 | offset_top = 38.0 156 | offset_right = 39.0 157 | offset_bottom = 61.0 158 | text = "click to focus/unfocus mouse" 159 | 160 | [node name="ResetButton" type="Button" parent="."] 161 | offset_left = 3.0 162 | offset_top = 76.0 163 | offset_right = 263.0 164 | offset_bottom = 107.0 165 | text = "PANIC (reset to original position)" 166 | script = ExtResource("3_2mp0y") 167 | 168 | [node name="StairsSetting" type="CheckButton" parent="."] 169 | offset_left = 3.0 170 | offset_top = 114.0 171 | offset_right = 185.0 172 | offset_bottom = 145.0 173 | button_pressed = true 174 | text = "Do Stair Stepping" 175 | script = ExtResource("4_05imk") 176 | 177 | [node name="SkippingSetting" type="CheckButton" parent="."] 178 | offset_left = 3.0 179 | offset_top = 145.0 180 | offset_right = 185.0 181 | offset_bottom = 176.0 182 | text = "Do Skipping Hack" 183 | script = ExtResource("4_05imk") 184 | 185 | [node name="CollisionBox" type="OptionButton" parent="."] 186 | offset_left = 3.0 187 | offset_top = 176.0 188 | offset_right = 17.0 189 | offset_bottom = 196.0 190 | script = ExtResource("4_1xxhe") 191 | 192 | [node name="ThirdPerson" type="CheckButton" parent="."] 193 | offset_left = 3.0 194 | offset_top = 204.0 195 | offset_right = 185.0 196 | offset_bottom = 235.0 197 | text = "Third Person" 198 | script = ExtResource("6_im2oe") 199 | 200 | [node name="MeshInstance3D" type="MeshInstance3D" parent="."] 201 | mesh = SubResource("QuadMesh_48u70") 202 | 203 | [connection signal="pressed" from="ResetButton" to="ResetButton" method="_on_pressed"] 204 | [connection signal="pressed" from="StairsSetting" to="StairsSetting" method="_on_pressed"] 205 | [connection signal="pressed" from="SkippingSetting" to="SkippingSetting" method="_on_pressed"] 206 | [connection signal="item_selected" from="CollisionBox" to="CollisionBox" method="_on_item_selected"] 207 | [connection signal="pressed" from="ThirdPerson" to="ThirdPerson" method="_on_pressed"] 208 | -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=5 10 | 11 | [application] 12 | 13 | config/name="StairTester" 14 | run/main_scene="res://world.tscn" 15 | config/features=PackedStringArray("4.1", "Forward Plus") 16 | config/icon="res://icon.svg" 17 | 18 | [input] 19 | 20 | ui_accept={ 21 | "deadzone": 0.5, 22 | "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":4194309,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null) 23 | , 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":4194310,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null) 24 | , 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":32,"physical_keycode":0,"key_label":0,"unicode":32,"echo":false,"script":null) 25 | , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":true,"script":null) 26 | ] 27 | } 28 | ui_left={ 29 | "deadzone": 0.5, 30 | "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) 31 | , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":13,"pressure":0.0,"pressed":false,"script":null) 32 | , Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":-1.0,"script":null) 33 | , 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) 34 | ] 35 | } 36 | ui_right={ 37 | "deadzone": 0.5, 38 | "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) 39 | , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":14,"pressure":0.0,"pressed":false,"script":null) 40 | , Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":1.0,"script":null) 41 | , 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) 42 | ] 43 | } 44 | ui_up={ 45 | "deadzone": 0.5, 46 | "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) 47 | , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":11,"pressure":0.0,"pressed":false,"script":null) 48 | , Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":-1.0,"script":null) 49 | , 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) 50 | ] 51 | } 52 | ui_down={ 53 | "deadzone": 0.5, 54 | "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) 55 | , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":12,"pressure":0.0,"pressed":false,"script":null) 56 | , Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":1.0,"script":null) 57 | , 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) 58 | ] 59 | } 60 | m1={ 61 | "deadzone": 0.5, 62 | "events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null) 63 | ] 64 | } 65 | m2={ 66 | "deadzone": 0.5, 67 | "events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":2,"canceled":false,"pressed":false,"double_click":false,"script":null) 68 | ] 69 | } 70 | camera_left={ 71 | "deadzone": 0.5, 72 | "events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":2,"axis_value":-1.0,"script":null) 73 | ] 74 | } 75 | camera_right={ 76 | "deadzone": 0.5, 77 | "events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":2,"axis_value":1.0,"script":null) 78 | ] 79 | } 80 | camera_up={ 81 | "deadzone": 0.5, 82 | "events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":3,"axis_value":-1.0,"script":null) 83 | ] 84 | } 85 | camera_down={ 86 | "deadzone": 0.5, 87 | "events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":3,"axis_value":1.0,"script":null) 88 | ] 89 | } 90 | forward={ 91 | "deadzone": 0.5, 92 | "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":87,"key_label":0,"unicode":119,"echo":false,"script":null) 93 | ] 94 | } 95 | backward={ 96 | "deadzone": 0.5, 97 | "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":83,"key_label":0,"unicode":115,"echo":false,"script":null) 98 | ] 99 | } 100 | left={ 101 | "deadzone": 0.5, 102 | "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":65,"key_label":0,"unicode":97,"echo":false,"script":null) 103 | ] 104 | } 105 | right={ 106 | "deadzone": 0.5, 107 | "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":68,"key_label":0,"unicode":100,"echo":false,"script":null) 108 | ] 109 | } 110 | stick_forward={ 111 | "deadzone": 0.5, 112 | "events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":-1.0,"script":null) 113 | ] 114 | } 115 | stick_backward={ 116 | "deadzone": 0.5, 117 | "events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":1.0,"script":null) 118 | ] 119 | } 120 | stick_left={ 121 | "deadzone": 0.5, 122 | "events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":-1.0,"script":null) 123 | ] 124 | } 125 | stick_right={ 126 | "deadzone": 0.5, 127 | "events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":1.0,"script":null) 128 | ] 129 | } 130 | -------------------------------------------------------------------------------- /player/SimplePlayer.gd: -------------------------------------------------------------------------------- 1 | extends CharacterBody3D 2 | class_name SimplePlayer 3 | 4 | const mouse_sens = 0.022 * 3.0 5 | 6 | const unit_conversion = 64.0 7 | 8 | const gravity = 800.0/unit_conversion 9 | const jumpvel = 270.0/unit_conversion 10 | 11 | const max_speed = 320.0/unit_conversion 12 | const max_speed_air = 320.0/unit_conversion 13 | 14 | const accel = 15.0 15 | const accel_air = 2.0 16 | 17 | var wish_dir = Vector3() 18 | var friction = 6.0 19 | 20 | func _ready(): 21 | floor_constant_speed = true 22 | 23 | func _friction(_velocity : Vector3, delta : float) -> Vector3: 24 | _velocity *= pow(0.9, delta*60.0) 25 | if wish_dir == Vector3(): 26 | _velocity = _velocity.move_toward(Vector3(), delta * max_speed) 27 | return _velocity 28 | 29 | func handle_friction(delta): 30 | if is_on_floor(): 31 | velocity = _friction(velocity, delta) 32 | 33 | func handle_accel(delta): 34 | if wish_dir != Vector3(): 35 | var actual_maxspeed = max_speed if is_on_floor() else max_speed_air 36 | var wish_dir_length = wish_dir.length() 37 | var actual_accel = (accel if is_on_floor() else accel_air) * actual_maxspeed * wish_dir_length 38 | 39 | var floor_velocity = Vector3(velocity.x, 0, velocity.z) 40 | var speed_in_wish_dir = floor_velocity.dot(wish_dir.normalized()) 41 | var speed = floor_velocity.length() 42 | if speed_in_wish_dir < actual_maxspeed: 43 | var add_limit = actual_maxspeed - speed_in_wish_dir 44 | var add_amount = min(add_limit, actual_accel*delta) 45 | velocity += wish_dir.normalized() * add_amount 46 | if is_on_floor() and speed > actual_maxspeed: 47 | velocity = velocity.normalized() * speed 48 | 49 | func handle_friction_and_accel(delta): 50 | handle_friction(delta) 51 | handle_accel(delta) 52 | 53 | @export var do_camera_smoothing : bool = true 54 | @export var do_stairs : bool = true 55 | @export var do_skipping_hack : bool = false 56 | @export var stairs_cause_floor_snap = false 57 | @export var skipping_hack_distance : float = 0.08 58 | @export var step_height = 0.5 59 | 60 | var started_process_on_floor = false 61 | 62 | func check_and_attempt_skipping_hack(distance : float, floor_normal : float): 63 | # try again with a certain minimum horizontal step distance if there was no wall collision and the wall trace was close 64 | if !found_stairs and (wall_test_travel * Vector3(1,0,1)).length() < distance: 65 | # go back to where we were at the end of the ceiling collision test 66 | global_position = ceiling_position 67 | # calculate a new path for the wall test: horizontal only, length of our fallback distance 68 | var floor_velocity = Vector3(velocity.x, 0.0, velocity.z) 69 | var factor = distance / floor_velocity.length() 70 | 71 | # step 2, skipping hack version 72 | wall_test_travel = floor_velocity * factor 73 | var info = move_and_collide_n_times(floor_velocity, factor, 2) 74 | velocity = info[0] 75 | wall_remainder = info[1] 76 | wall_collision = info[2] 77 | 78 | # step 3, skipping hack version 79 | floor_collision = move_and_collide(Vector3.DOWN * (ceiling_travel_distance + (step_height if started_process_on_floor else 0.0))) 80 | if floor_collision and floor_collision.get_collision_count() > 0 and floor_collision.get_normal(0).y > floor_normal: 81 | found_stairs = true 82 | 83 | var found_stairs = false 84 | var wall_test_travel = Vector3() 85 | var wall_remainder = Vector3() 86 | var ceiling_position = Vector3() 87 | var ceiling_travel_distance = Vector3() 88 | var ceiling_collision : KinematicCollision3D = null 89 | var wall_collision : KinematicCollision3D = null 90 | var floor_collision : KinematicCollision3D = null 91 | 92 | var slide_snap_offset = Vector3() 93 | 94 | func move_and_collide_n_times(vector : Vector3, delta : float, slide_count : int, skip_reject_if_ceiling : bool = true): 95 | var collision = null 96 | var remainder = vector 97 | var adjusted_vector = vector * delta 98 | var _floor_normal = cos(floor_max_angle) 99 | for _i in slide_count: 100 | var new_collision = move_and_collide(adjusted_vector) 101 | if new_collision: 102 | collision = new_collision 103 | remainder = collision.get_remainder() 104 | adjusted_vector = remainder 105 | if !skip_reject_if_ceiling or collision.get_normal().y >= -_floor_normal: 106 | adjusted_vector = adjusted_vector.slide(collision.get_normal()) 107 | vector = vector.slide(collision.get_normal()) 108 | else: 109 | remainder = Vector3() 110 | break 111 | 112 | return [vector, remainder, collision] 113 | 114 | func move_and_climb_stairs(delta : float, allow_stair_snapping : bool): 115 | var start_position = global_position 116 | var start_velocity = velocity 117 | 118 | found_stairs = false 119 | wall_test_travel = Vector3() 120 | wall_remainder = Vector3() 121 | ceiling_position = Vector3() 122 | ceiling_travel_distance = Vector3() 123 | ceiling_collision = null 124 | wall_collision = null 125 | floor_collision = null 126 | 127 | # do move_and_slide and check if we hit a wall 128 | move_and_slide() 129 | var slide_velocity = velocity 130 | var slide_position = global_position 131 | var hit_wall = false 132 | var floor_normal = cos(floor_max_angle) 133 | var max_slide = get_slide_collision_count()-1 134 | var accumulated_position = start_position 135 | for slide in max_slide+1: 136 | var collision = get_slide_collision(slide) 137 | var y = collision.get_normal().y 138 | if y < floor_normal and y > -floor_normal: 139 | hit_wall = true 140 | accumulated_position += collision.get_travel() 141 | slide_snap_offset = accumulated_position - global_position 142 | 143 | # if we hit a wall, check for simple stairs; three steps 144 | if hit_wall and do_stairs and (start_velocity.x != 0.0 or start_velocity.z != 0.0): 145 | global_position = start_position 146 | velocity = start_velocity 147 | # step 1: upwards trace 148 | var up_height = probe_probable_step_height() # NOT NECESSARY. can just be step_height. 149 | 150 | ceiling_collision = move_and_collide(up_height * Vector3.UP) 151 | ceiling_travel_distance = step_height if not ceiling_collision else abs(ceiling_collision.get_travel().y) 152 | ceiling_position = global_position 153 | # step 2: "check if there's a wall" trace 154 | wall_test_travel = velocity * delta 155 | var info = move_and_collide_n_times(velocity, delta, 2) 156 | velocity = info[0] 157 | wall_remainder = info[1] 158 | wall_collision = info[2] 159 | 160 | # step 3: downwards trace 161 | floor_collision = move_and_collide(Vector3.DOWN * (ceiling_travel_distance + (step_height if started_process_on_floor else 0.0))) 162 | if floor_collision: 163 | if floor_collision.get_normal(0).y > floor_normal: 164 | found_stairs = true 165 | # NOTE: NOT NECESSARY 166 | # try to skip over small sloped walls if we failed to find a stair and the skipping hack is enabled 167 | if !floor_collision or floor_collision.get_normal(0).y < floor_normal: 168 | check_and_attempt_skipping_hack(0.01, floor_normal) 169 | if !floor_collision or floor_collision.get_normal(0).y < floor_normal and do_skipping_hack: 170 | check_and_attempt_skipping_hack(skipping_hack_distance, floor_normal) 171 | 172 | # (this section is more complex than it needs to be, because of move_and_slide taking velocity and delta for granted) 173 | # if we found stairs, climb up them 174 | if found_stairs: 175 | if allow_stair_snapping and stairs_cause_floor_snap: 176 | velocity.y = 0.0 177 | var oldvel = velocity 178 | velocity = wall_remainder / delta 179 | move_and_slide() 180 | velocity = oldvel 181 | # no stairs, do "normal" non-stairs movement 182 | else: 183 | global_position = slide_position 184 | velocity = slide_velocity 185 | 186 | return found_stairs 187 | 188 | func probe_probable_step_height(): 189 | const hull_height = 1.75 # edit me 190 | const center_offset = 0.875 # edit to match the offset between your origin and the center of your hitbox 191 | const hull_width = 0.625 # approximately the full width of your hull 192 | 193 | var heading = (velocity * Vector3(1, 0, 1)).normalized() 194 | 195 | var offset = Vector3() 196 | var test = move_and_collide(heading * hull_width, true) 197 | if test and abs(test.get_normal().y) < 0.8: 198 | offset = (test.get_position(0) - test.get_travel() - global_position) * Vector3(1, 0, 1) 199 | 200 | var raycast = ShapeCast3D.new() 201 | var shape = CylinderShape3D.new() 202 | shape.radius = hull_width/2.0 203 | shape.height = max(0.01, hull_height - step_height*2.0 - 0.1) 204 | raycast.shape = shape 205 | raycast.max_results = 1 206 | add_child(raycast) 207 | raycast.collision_mask = collision_mask 208 | raycast.position = Vector3(0.0, center_offset, 0.0) 209 | if offset != Vector3(): 210 | raycast.target_position = heading * hull_width * 0.22 + offset 211 | else: 212 | raycast.target_position = heading * hull_width * 0.72 213 | #raycast.force_raycast_update() 214 | raycast.force_shapecast_update() 215 | if raycast.is_colliding(): 216 | #raycast.position = raycast.get_collision_point(0) 217 | raycast.global_position = raycast.get_collision_point(0) 218 | else: 219 | raycast.global_position += raycast.target_position 220 | 221 | var up_distance = 50.0 222 | raycast.target_position = Vector3(0.0, 50.0, 0.0) 223 | #raycast.force_raycast_update() 224 | raycast.force_shapecast_update() 225 | if raycast.is_colliding(): 226 | up_distance = raycast.get_collision_point(0).y - raycast.position.y 227 | 228 | var down_distance = center_offset 229 | raycast.target_position = Vector3(0.0, -center_offset, 0.0) 230 | #raycast.force_raycast_update() 231 | raycast.force_shapecast_update() 232 | if raycast.is_colliding(): 233 | down_distance = raycast.position.y - raycast.get_collision_point(0).y 234 | 235 | raycast.queue_free() 236 | 237 | if up_distance + down_distance < hull_height: 238 | return step_height 239 | else: 240 | var highest = up_distance - center_offset 241 | var lowest = center_offset - down_distance 242 | return clamp(highest/2.0 + lowest/2.0, 0.0, step_height) 243 | 244 | func _process(delta: float) -> void: 245 | started_process_on_floor = is_on_floor() 246 | # for controller camera control 247 | handle_stick_input(delta) 248 | 249 | var allow_stair_snapping = true 250 | if Input.is_action_pressed("ui_accept") and is_on_floor(): 251 | allow_stair_snapping = false 252 | velocity.y = jumpvel 253 | floor_snap_length = 0.0 254 | elif started_process_on_floor: 255 | floor_snap_length = step_height + safe_margin 256 | 257 | var input_dir := Input.get_vector("left", "right", "forward", "backward") + Input.get_vector("stick_left", "stick_right", "stick_forward", "stick_backward") 258 | wish_dir = Vector3(input_dir.x, 0, input_dir.y).rotated(Vector3.UP, $CameraHolder.global_rotation.y) 259 | if wish_dir.length_squared() > 1.0: 260 | wish_dir = wish_dir.normalized() 261 | 262 | handle_friction_and_accel(delta) 263 | 264 | if not is_on_floor(): 265 | velocity.y -= gravity * delta * 0.5 266 | 267 | var start_position = global_position 268 | 269 | # CHANGE ME: replace this with your own movement-and-stair-climbing code 270 | move_and_climb_stairs(delta, allow_stair_snapping) 271 | 272 | if not is_on_floor(): 273 | velocity.y -= gravity * delta * 0.5 274 | 275 | handle_camera_adjustment(start_position, delta) 276 | 277 | add_collision_debug_visualizer(delta) 278 | 279 | const stick_camera_speed = 240.0 280 | func handle_stick_input(delta): 281 | var camera_dir := Input.get_vector("camera_left", "camera_right", "camera_up", "camera_down", 0.15) 282 | var tilt = camera_dir.length() 283 | var acceleration = lerp(0.25, 1.0, tilt) 284 | camera_dir *= acceleration 285 | $CameraHolder.rotation_degrees.y -= camera_dir.x * stick_camera_speed * delta 286 | $CameraHolder.rotation_degrees.x -= camera_dir.y * stick_camera_speed * delta 287 | $CameraHolder.rotation_degrees.x = clamp($CameraHolder.rotation_degrees.x, -90.0, 90.0) 288 | 289 | func _input(event: InputEvent) -> void: 290 | if event is InputEventMouseMotion: 291 | if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED: 292 | $CameraHolder.rotation_degrees.y -= event.relative.x * mouse_sens 293 | $CameraHolder.rotation_degrees.x -= event.relative.y * mouse_sens 294 | $CameraHolder.rotation_degrees.x = clamp($CameraHolder.rotation_degrees.x, -90.0, 90.0) 295 | 296 | func _unhandled_input(event: InputEvent) -> void: 297 | if event.is_action_pressed("m1") or event.is_action_pressed("m2"): 298 | if Input.mouse_mode != Input.MOUSE_MODE_CAPTURED: 299 | Input.mouse_mode = Input.MOUSE_MODE_CAPTURED 300 | else: 301 | Input.mouse_mode = Input.MOUSE_MODE_VISIBLE 302 | 303 | @export var third_person = false 304 | @export var camera_smoothing_meters_per_sec = 3.0 305 | # used to smooth out the camera when climbing stairs 306 | var camera_offset_y = 0.0 307 | func handle_camera_adjustment(start_position, delta): 308 | # first/third-person adjustment 309 | $CameraHolder.position.y = 1.2 if third_person else 1.5 310 | $CameraHolder/Camera3D.position.z = 1.5 if third_person else 0.0 311 | 312 | if do_camera_smoothing: 313 | # NOT NEEDED: camera smoothing 314 | var stair_climb_distance = 0.0 315 | if found_stairs: 316 | stair_climb_distance = global_position.y - start_position.y 317 | elif is_on_floor(): 318 | stair_climb_distance = -slide_snap_offset.y 319 | 320 | camera_offset_y -= stair_climb_distance 321 | camera_offset_y = clamp(camera_offset_y, -step_height, step_height) 322 | camera_offset_y = move_toward(camera_offset_y, 0.0, delta * camera_smoothing_meters_per_sec) 323 | 324 | $CameraHolder/Camera3D.position.y = 0.0 325 | $CameraHolder/Camera3D.position.x = 0.0 326 | $CameraHolder/Camera3D.global_position.y += camera_offset_y 327 | 328 | 329 | static func make_debug_mesh(color : Color): 330 | var texture = GradientTexture2D.new() 331 | texture.fill_from = Vector2(0.5, 0.5) 332 | texture.fill_to = Vector2(0.5, 1.0) 333 | texture.fill = GradientTexture2D.FILL_RADIAL 334 | texture.gradient = Gradient.new() 335 | texture.gradient.add_point(0.0, Color(1.0, 1.0, 1.0, 1.0)) 336 | texture.gradient.add_point(1.0, Color(1.0, 1.0, 1.0, 0.0)) 337 | texture.gradient.remove_point(0) 338 | texture.gradient.remove_point(0) 339 | texture.gradient.interpolation_mode = Gradient.GRADIENT_INTERPOLATE_CUBIC 340 | 341 | var mat = StandardMaterial3D.new() 342 | mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED 343 | mat.albedo_color = color 344 | mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA 345 | mat.cull_mode = BaseMaterial3D.CULL_DISABLED 346 | mat.albedo_texture = texture 347 | 348 | var mesh = QuadMesh.new() 349 | mesh.size = Vector2(0.25, 0.25) 350 | mesh.material = mat 351 | 352 | return mesh 353 | 354 | @onready var _collision_debug_mesh = SimplePlayer.make_debug_mesh(Color(1.0, 0.75, 0.5, 0.5)) 355 | @onready var _collision_debug_mesh_unwalkable = SimplePlayer.make_debug_mesh(Color(1.0, 0.0, 0.0, 0.5)) 356 | 357 | class Visualizer extends MeshInstance3D: 358 | var life = 5.0 359 | func _process(delta): 360 | life -= delta 361 | if life < 0.0: 362 | queue_free() 363 | else: 364 | transparency = 1.0 - life/5.0 365 | 366 | var _debug_timer_max = 0.016 367 | var _debug_timer = _debug_timer_max 368 | func add_collision_debug_visualizer(delta): 369 | _debug_timer -= delta 370 | if _debug_timer > 0.0: 371 | return 372 | _debug_timer += _debug_timer_max 373 | if _debug_timer < 0.0: 374 | _debug_timer = 0.0 375 | 376 | var collision = floor_collision if floor_collision else wall_collision 377 | if collision: 378 | var normal = collision.get_normal(0) 379 | if true:#normal.y > 0.1 and normal.y < 0.999: 380 | var visualizer = Visualizer.new() as Visualizer 381 | if acos(normal.y) < floor_max_angle: 382 | visualizer.mesh = _collision_debug_mesh 383 | else: 384 | visualizer.mesh = _collision_debug_mesh_unwalkable 385 | get_tree().current_scene.add_child(visualizer) 386 | if normal.abs().dot(Vector3.UP) != 1.0: 387 | visualizer.look_at_from_position(Vector3(), normal) 388 | else: 389 | visualizer.look_at_from_position(Vector3(), normal, Vector3.RIGHT) 390 | visualizer.global_position = collision.get_position(0) 391 | visualizer.global_position += normal*0.01 392 | -------------------------------------------------------------------------------- /StairsTestScene.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=14 format=3 uid="uid://b5rmjajnlo22w"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://8jjphj6non1s" path="res://art/blockmesh texture darkgray.png" id="1_x707m"] 4 | [ext_resource type="Texture2D" uid="uid://blejfycf2xpm7" path="res://art/blockmesh texture blue.png" id="2_x5sg2"] 5 | [ext_resource type="Texture2D" uid="uid://cuy43ijkbhula" path="res://art/blockmesh texture green.png" id="3_35qmt"] 6 | [ext_resource type="Texture2D" uid="uid://1ihienek4uv4" path="res://art/blockmesh texture purple.png" id="4_by77g"] 7 | [ext_resource type="Texture2D" uid="uid://cqtrnlqmmhffx" path="res://art/blockmesh texture red.png" id="5_73yqw"] 8 | 9 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_kp031"] 10 | albedo_texture = ExtResource("1_x707m") 11 | uv1_triplanar = true 12 | uv1_world_triplanar = true 13 | texture_filter = 5 14 | 15 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_6j7a6"] 16 | albedo_texture = ExtResource("2_x5sg2") 17 | uv1_triplanar = true 18 | uv1_triplanar_sharpness = 150.0 19 | uv1_world_triplanar = true 20 | texture_filter = 5 21 | 22 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_uatdx"] 23 | albedo_texture = ExtResource("3_35qmt") 24 | uv1_triplanar = true 25 | uv1_triplanar_sharpness = 150.0 26 | uv1_world_triplanar = true 27 | texture_filter = 5 28 | 29 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_fxdaq"] 30 | albedo_texture = ExtResource("4_by77g") 31 | uv1_triplanar = true 32 | uv1_triplanar_sharpness = 150.0 33 | uv1_world_triplanar = true 34 | texture_filter = 5 35 | 36 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ihnv6"] 37 | albedo_texture = ExtResource("5_73yqw") 38 | uv1_triplanar = true 39 | uv1_world_triplanar = true 40 | texture_filter = 5 41 | 42 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ly0nc"] 43 | albedo_color = Color(0.447059, 0.447059, 0.447059, 1) 44 | albedo_texture = ExtResource("3_35qmt") 45 | uv1_triplanar = true 46 | uv1_triplanar_sharpness = 150.0 47 | uv1_world_triplanar = true 48 | texture_filter = 5 49 | 50 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_6qc48"] 51 | albedo_color = Color(0.27451, 0.27451, 0.27451, 1) 52 | albedo_texture = ExtResource("5_73yqw") 53 | uv1_triplanar = true 54 | uv1_world_triplanar = true 55 | texture_filter = 5 56 | 57 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_j7qiy"] 58 | albedo_color = Color(0.27451, 0.27451, 0.27451, 1) 59 | albedo_texture = ExtResource("5_73yqw") 60 | uv1_triplanar = true 61 | uv1_world_triplanar = true 62 | texture_filter = 5 63 | 64 | [node name="Level" type="Node3D"] 65 | 66 | [node name="CSGBox3D" type="CSGBox3D" parent="."] 67 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.5, 0) 68 | use_collision = true 69 | size = Vector3(59.7816, 1, 34) 70 | material = SubResource("StandardMaterial3D_kp031") 71 | 72 | [node name="CSGBox3D56" type="CSGBox3D" parent="."] 73 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 13.25) 74 | use_collision = true 75 | size = Vector3(59.7816, 1, 4.5) 76 | material = SubResource("StandardMaterial3D_kp031") 77 | 78 | [node name="CSGBox3D2" type="CSGBox3D" parent="."] 79 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.125, -1.25) 80 | use_collision = true 81 | size = Vector3(1, 0.25, 0.5) 82 | material = SubResource("StandardMaterial3D_6j7a6") 83 | 84 | [node name="CSGBox3D58" type="CSGBox3D" parent="."] 85 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6, 0.0625, -6.25) 86 | use_collision = true 87 | size = Vector3(1, 0.125, 0.5) 88 | material = SubResource("StandardMaterial3D_6j7a6") 89 | 90 | [node name="CSGBox3D59" type="CSGBox3D" parent="."] 91 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6, 0.1875, -6.75) 92 | use_collision = true 93 | size = Vector3(1, 0.125, 0.5) 94 | material = SubResource("StandardMaterial3D_6j7a6") 95 | 96 | [node name="CSGBox3D60" type="CSGBox3D" parent="."] 97 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6, 0.3125, -7.25) 98 | use_collision = true 99 | size = Vector3(1, 0.125, 0.5) 100 | material = SubResource("StandardMaterial3D_6j7a6") 101 | 102 | [node name="CSGBox3D61" type="CSGBox3D" parent="."] 103 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6, 0.4375, -7.75) 104 | use_collision = true 105 | size = Vector3(1, 0.125, 0.5) 106 | material = SubResource("StandardMaterial3D_6j7a6") 107 | 108 | [node name="CSGBox3D12" type="CSGPolygon3D" parent="."] 109 | transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -3.70001, 0, -2) 110 | use_collision = true 111 | polygon = PackedVector2Array(-1, 0.9375, -1, 1, 1, 0, 1, -0.0625) 112 | material = SubResource("StandardMaterial3D_uatdx") 113 | 114 | [node name="CSGBox3D32" type="CSGPolygon3D" parent="."] 115 | transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -2.0101, 0, -2) 116 | use_collision = true 117 | polygon = PackedVector2Array(-1, 0.9375, -1, 1, 1, 0, 1, -0.0625) 118 | material = SubResource("StandardMaterial3D_uatdx") 119 | 120 | [node name="CSGBox3D20" type="CSGPolygon3D" parent="."] 121 | transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 7.25, 0, -2.5) 122 | use_collision = true 123 | polygon = PackedVector2Array(-1, 0.9375, -1, 1, 1, 0, 1, -0.0625) 124 | material = SubResource("StandardMaterial3D_uatdx") 125 | 126 | [node name="CSGBox3D13" type="CSGPolygon3D" parent="."] 127 | transform = Transform3D(-4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, -2.70001, 2.1875, -2) 128 | use_collision = true 129 | polygon = PackedVector2Array(-1, 0.9375, -1, 1, 1, 0, 1, -0.0625) 130 | material = SubResource("StandardMaterial3D_uatdx") 131 | 132 | [node name="CSGBox3D14" type="CSGPolygon3D" parent="."] 133 | transform = Transform3D(-4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, -3.97459, 1.0625, -2) 134 | use_collision = true 135 | polygon = PackedVector2Array(-1, 0.9375, -1, 1, 1, 0, 1, -0.0625) 136 | material = SubResource("StandardMaterial3D_uatdx") 137 | 138 | [node name="CSGBox3D23" type="CSGPolygon3D" parent="."] 139 | transform = Transform3D(-4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, -5.52437, 1.4375, -2) 140 | use_collision = true 141 | polygon = PackedVector2Array(-1, 0.8125, -1, 0.875, 1, 0.625, 1, 0.5625) 142 | material = SubResource("StandardMaterial3D_uatdx") 143 | 144 | [node name="CSGBox3D25" type="CSGPolygon3D" parent="."] 145 | transform = Transform3D(-4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, -7.64937, 1.1875, -2) 146 | use_collision = true 147 | polygon = PackedVector2Array(-1, 0.8125, -1, 0.875, 1, 0.625, 1, 0.5625) 148 | material = SubResource("StandardMaterial3D_uatdx") 149 | 150 | [node name="CSGBox3D26" type="CSGPolygon3D" parent="."] 151 | transform = Transform3D(1.91069e-15, -1, -4.37114e-08, -4.37114e-08, -4.37114e-08, 1, -1, 0, -4.37114e-08, -10.1494, 3, -2) 152 | use_collision = true 153 | polygon = PackedVector2Array(-1, 0.8125, -1, 0.875, 1, 0.625, 1, 0.5625) 154 | depth = 3.0 155 | material = SubResource("StandardMaterial3D_fxdaq") 156 | 157 | [node name="CSGBox3D27" type="CSGPolygon3D" parent="."] 158 | transform = Transform3D(-5.73206e-15, 1, 1.31134e-07, 4.37114e-08, 1.31134e-07, -1, -1, 0, -4.37114e-08, -10.6494, 0, -2) 159 | use_collision = true 160 | polygon = PackedVector2Array(-1, 0.8125, -1, 0.875, 1, 0.625, 1, 0.5625) 161 | depth = 3.0 162 | material = SubResource("StandardMaterial3D_fxdaq") 163 | 164 | [node name="CSGBox3D24" type="CSGPolygon3D" parent="."] 165 | transform = Transform3D(1.31134e-07, 0, -1, 0, 1, 0, 1, 0, 1.31134e-07, -6.52437, -0.625, -2) 166 | use_collision = true 167 | polygon = PackedVector2Array(-1, 0.8125, -1, 0.875, 1, 0.625, 1, 0.5625) 168 | material = SubResource("StandardMaterial3D_uatdx") 169 | 170 | [node name="CSGBox3D57" type="CSGPolygon3D" parent="."] 171 | transform = Transform3D(-4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, -5.5, 1.438, -7) 172 | use_collision = true 173 | polygon = PackedVector2Array(-1, 0.8125, -1, 0.875, 1, 0.625, 1, 0.5625) 174 | material = SubResource("StandardMaterial3D_uatdx") 175 | 176 | [node name="CSGBox3D28" type="CSGPolygon3D" parent="."] 177 | transform = Transform3D(1.31134e-07, 0, -1, 0, 1, 0, 1, 0, 1.31134e-07, -10.8994, -0.625, -2) 178 | use_collision = true 179 | polygon = PackedVector2Array(-1, 0.8125, -1, 0.875, 1, 0.625, 1, 0.5625) 180 | material = SubResource("StandardMaterial3D_uatdx") 181 | 182 | [node name="CSGBox3D10" type="CSGBox3D" parent="."] 183 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.25, -0.5625) 184 | use_collision = true 185 | size = Vector3(1, 0.25, 0.5) 186 | material = SubResource("StandardMaterial3D_6j7a6") 187 | 188 | [node name="CSGBox3D11" type="CSGBox3D" parent="."] 189 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 2.25, -0.6875) 190 | use_collision = true 191 | size = Vector3(1, 0.25, 0.5) 192 | material = SubResource("StandardMaterial3D_6j7a6") 193 | 194 | [node name="CSGBox3D3" type="CSGBox3D" parent="."] 195 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.375, -1.75) 196 | use_collision = true 197 | size = Vector3(1, 0.25, 0.5) 198 | material = SubResource("StandardMaterial3D_6j7a6") 199 | 200 | [node name="CSGBox3D4" type="CSGBox3D" parent="."] 201 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.625, -2.25) 202 | use_collision = true 203 | size = Vector3(1, 0.25, 0.5) 204 | material = SubResource("StandardMaterial3D_6j7a6") 205 | 206 | [node name="CSGBox3D5" type="CSGBox3D" parent="."] 207 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.875, -2.75) 208 | use_collision = true 209 | size = Vector3(1, 0.25, 0.5) 210 | material = SubResource("StandardMaterial3D_6j7a6") 211 | 212 | [node name="CSGBox3D6" type="CSGBox3D" parent="."] 213 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 0.125, -1.25) 214 | use_collision = true 215 | size = Vector3(1, 0.25, 0.5) 216 | material = SubResource("StandardMaterial3D_6j7a6") 217 | 218 | [node name="CSGBox3D7" type="CSGBox3D" parent="."] 219 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 0.375, -1.75) 220 | use_collision = true 221 | size = Vector3(1, 0.25, 0.5) 222 | material = SubResource("StandardMaterial3D_6j7a6") 223 | 224 | [node name="CSGBox3D8" type="CSGBox3D" parent="."] 225 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 0.625, -2.25) 226 | use_collision = true 227 | size = Vector3(1, 0.25, 0.5) 228 | material = SubResource("StandardMaterial3D_6j7a6") 229 | 230 | [node name="CSGBox3D9" type="CSGBox3D" parent="."] 231 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 0.875, -2.75) 232 | use_collision = true 233 | size = Vector3(1, 0.25, 0.5) 234 | material = SubResource("StandardMaterial3D_6j7a6") 235 | 236 | [node name="CSGBox3D15" type="CSGBox3D" parent="."] 237 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 2.1875, -1.25) 238 | use_collision = true 239 | size = Vector3(0.990841, 0.25, 0.491446) 240 | material = SubResource("StandardMaterial3D_6j7a6") 241 | 242 | [node name="CSGBox3D16" type="CSGBox3D" parent="."] 243 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 0.125, -1.25) 244 | use_collision = true 245 | size = Vector3(1, 0.25, 0.5) 246 | material = SubResource("StandardMaterial3D_6j7a6") 247 | 248 | [node name="CSGBox3D21" type="CSGBox3D" parent="."] 249 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7.5, 1.125, -1.25) 250 | use_collision = true 251 | size = Vector3(1, 3.5, 0.5) 252 | material = SubResource("StandardMaterial3D_ihnv6") 253 | 254 | [node name="CSGBox3D22" type="CSGBox3D" parent="."] 255 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.75, 1.125, -1.25) 256 | use_collision = true 257 | size = Vector3(0.5, 3.5, 0.5) 258 | material = SubResource("StandardMaterial3D_ihnv6") 259 | 260 | [node name="CSGBox3D17" type="CSGBox3D" parent="."] 261 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 0.375, -1.75) 262 | use_collision = true 263 | size = Vector3(1, 0.25, 0.5) 264 | material = SubResource("StandardMaterial3D_6j7a6") 265 | 266 | [node name="CSGBox3D18" type="CSGBox3D" parent="."] 267 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 0.625, -2.25) 268 | use_collision = true 269 | size = Vector3(1, 0.25, 0.5) 270 | material = SubResource("StandardMaterial3D_6j7a6") 271 | 272 | [node name="CSGBox3D19" type="CSGBox3D" parent="."] 273 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 0.875, -2.75) 274 | use_collision = true 275 | size = Vector3(1, 0.25, 0.5) 276 | material = SubResource("StandardMaterial3D_6j7a6") 277 | 278 | [node name="CSGBox3D51" type="CSGBox3D" parent="."] 279 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 0.125, -5.25) 280 | use_collision = true 281 | size = Vector3(1, 0.25, 0.5) 282 | material = SubResource("StandardMaterial3D_6j7a6") 283 | 284 | [node name="CSGBox3D55" type="CSGBox3D" parent="."] 285 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 2.1875, -5.25) 286 | use_collision = true 287 | size = Vector3(1, 0.25, 0.5) 288 | material = SubResource("StandardMaterial3D_6j7a6") 289 | 290 | [node name="CSGBox3D52" type="CSGBox3D" parent="."] 291 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 0.375, -5.75) 292 | use_collision = true 293 | size = Vector3(1, 0.25, 0.5) 294 | material = SubResource("StandardMaterial3D_6j7a6") 295 | 296 | [node name="CSGBox3D53" type="CSGBox3D" parent="."] 297 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 0.625, -6.25) 298 | use_collision = true 299 | size = Vector3(1, 0.25, 0.5) 300 | material = SubResource("StandardMaterial3D_6j7a6") 301 | 302 | [node name="CSGBox3D54" type="CSGBox3D" parent="."] 303 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 0.875, -6.75) 304 | use_collision = true 305 | size = Vector3(1, 0.25, 0.5) 306 | material = SubResource("StandardMaterial3D_6j7a6") 307 | 308 | [node name="Label3D" type="Label3D" parent="."] 309 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.46182, -1) 310 | pixel_size = 0.0025 311 | text = "It *should* be possible to climb 312 | this entire staircase" 313 | 314 | [node name="Label3D10" type="Label3D" parent="."] 315 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.893967, 1.375) 316 | pixel_size = 0.0025 317 | text = "Assuming the right collision shape size, and a 318 | perfect stair-stepping algorithm, everything on 319 | this map should work with both AABB 320 | and cylinder character collision shapes. 321 | 322 | Capsules won't work without a capsule-specific 323 | algorithm; the sloped part at the bottom of 324 | a capsule acts like a sloped wall when it touches 325 | the edge of a step, making it impossible to climb. 326 | 327 | AABB dimensions: 0.625, 1.75, 0.625 328 | (full extents, not half extents)" 329 | 330 | [node name="Label3D2" type="Label3D" parent="."] 331 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.03169, 1.46182, -1) 332 | pixel_size = 0.0025 333 | text = "It should *NOT* be possible to climb 334 | this entire staircase. the player should 335 | get snagged on the 336 | ceiling block." 337 | 338 | [node name="Label3D3" type="Label3D" parent="."] 339 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.90669, 1.46182, -1) 340 | pixel_size = 0.0025 341 | text = "It should be possible to step 342 | onto the first step, but not 343 | onto the second, from 344 | this direction." 345 | 346 | [node name="Label3D19" type="Label3D" parent="."] 347 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.90669, 1.46182, -4.98793) 348 | pixel_size = 0.0025 349 | text = "It should be possible to step 350 | onto the first step, but not 351 | onto the second, from 352 | this direction. 353 | Harder version of the one 354 | in front of it." 355 | 356 | [node name="Label3D4" type="Label3D" parent="."] 357 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6.47481, 1.46182, -0.977026) 358 | pixel_size = 0.0025 359 | text = "It *SHOULD* be possible 360 | to step onto the green 361 | ramp from this direction, 362 | without being forced 363 | to jump, even with 364 | an AABB collision shape. 365 | Cylinders might require 366 | a hack to skip over the 367 | part of their bottom that 368 | forms a 'sloped wall' with 369 | the ramp edge." 370 | 371 | [node name="CSGBox3D42" type="CSGPolygon3D" parent="."] 372 | transform = Transform3D(-0.5, 0, 0.866025, 0, 1, 0, -0.866025, 0, -0.5, 7.86785, 0, -6.55866) 373 | use_collision = true 374 | polygon = PackedVector2Array(-1, 0.9375, -1, 1, 1, 0, 1, -0.0625) 375 | material = SubResource("StandardMaterial3D_uatdx") 376 | 377 | [node name="CSGBox3D43" type="CSGBox3D" parent="."] 378 | transform = Transform3D(0.5, 0, -0.866025, 0, 1, 0, 0.866025, 0, 0.5, 6.91032, 1.125, -5.71716) 379 | use_collision = true 380 | size = Vector3(1, 3.5, 0.5) 381 | material = SubResource("StandardMaterial3D_ihnv6") 382 | 383 | [node name="CSGBox3D44" type="CSGBox3D" parent="."] 384 | transform = Transform3D(0.5, 0, -0.866025, 0, 1, 0, 0.866025, 0, 0.5, 6.03532, 1.125, -7.2327) 385 | use_collision = true 386 | size = Vector3(0.5, 3.5, 0.5) 387 | material = SubResource("StandardMaterial3D_ihnv6") 388 | 389 | [node name="Label3D16" type="Label3D" parent="."] 390 | transform = Transform3D(0.5, 0, -0.866025, 0, 1, 0, 0.866025, 0, 0.5, 6.16132, 1.46182, -6.46851) 391 | pixel_size = 0.0025 392 | text = "It *SHOULD* be possible 393 | to step onto the green 394 | ramp from this direction, 395 | without being forced 396 | to jump, even with 397 | an AABB collision shape. 398 | Cylinders might require 399 | a hack to skip over the 400 | part of their bottom that 401 | forms a 'sloped wall' with 402 | the ramp edge." 403 | 404 | [node name="CSGBox3D45" type="CSGPolygon3D" parent="."] 405 | transform = Transform3D(-0.258819, 0, 0.965926, 0, 1, 0, -0.965926, 0, -0.258819, 11.8462, 0, -6.26552) 406 | use_collision = true 407 | polygon = PackedVector2Array(-1, 0.9375, -1, 1, 1, 0, 1, -0.0625) 408 | material = SubResource("StandardMaterial3D_ly0nc") 409 | 410 | [node name="CSGBox3D46" type="CSGBox3D" parent="."] 411 | transform = Transform3D(0.258819, 0, -0.965926, 0, 1, 0, 0.965926, 0, 0.258819, 10.7035, 1.125, -5.70051) 412 | use_collision = true 413 | size = Vector3(1, 3.5, 0.5) 414 | material = SubResource("StandardMaterial3D_6qc48") 415 | 416 | [node name="CSGBox3D47" type="CSGBox3D" parent="."] 417 | transform = Transform3D(0.258819, 0, -0.965926, 0, 1, 0, 0.965926, 0, 0.258819, 10.2506, 1.125, -7.39088) 418 | use_collision = true 419 | size = Vector3(0.5, 3.5, 0.5) 420 | material = SubResource("StandardMaterial3D_j7qiy") 421 | 422 | [node name="Label3D17" type="Label3D" parent="."] 423 | transform = Transform3D(0.258819, 0, -0.965926, 0, 1, 0, 0.965926, 0, 0.258819, 10.1745, 1.46182, -6.62012) 424 | pixel_size = 0.0025 425 | text = "Unless you've applied a hack 426 | to skip over small sloped walls, 427 | It should NOT be possible 428 | to step onto the ramp 429 | from thie direction with 430 | an AABB collision 431 | hull. The bottom edge 432 | of the AABB should form 433 | a sloped wall collision 434 | surface with the ramp 435 | edge, preventing 436 | stair-stepping." 437 | 438 | [node name="CSGBox3D33" type="CSGPolygon3D" parent="."] 439 | transform = Transform3D(-0.707107, 0, 0.707107, 0, 1, 0, -0.707107, 0, -0.707107, 11.8129, 0, -1.84742) 440 | use_collision = true 441 | polygon = PackedVector2Array(-1, 0.9375, -1, 1, 1, 0, 1, -0.0625) 442 | material = SubResource("StandardMaterial3D_uatdx") 443 | 444 | [node name="CSGBox3D34" type="CSGBox3D" parent="."] 445 | transform = Transform3D(0.707107, 0, -0.707107, 0, 1, 0, 0.707107, 0, 0.707107, 11.1058, 1.125, -0.786758) 446 | use_collision = true 447 | size = Vector3(1, 3.5, 0.5) 448 | material = SubResource("StandardMaterial3D_ihnv6") 449 | 450 | [node name="CSGBox3D35" type="CSGBox3D" parent="."] 451 | transform = Transform3D(0.707107, 0, -0.707107, 0, 1, 0, 0.707107, 0, 0.707107, 9.86833, 1.125, -2.02419) 452 | use_collision = true 453 | size = Vector3(0.5, 3.5, 0.5) 454 | material = SubResource("StandardMaterial3D_ihnv6") 455 | 456 | [node name="Label3D13" type="Label3D" parent="."] 457 | transform = Transform3D(0.707107, 0, -0.707107, 0, 1, 0, 0.707107, 0, 0.707107, 10.1878, 1.46182, -1.31865) 458 | pixel_size = 0.0025 459 | text = "It *SHOULD* be possible 460 | to step onto the green 461 | ramp from this direction, 462 | without being forced 463 | to jump, even with 464 | an AABB collision shape. 465 | Cylinders might require 466 | a hack to skip over the 467 | part of their bottom that 468 | forms a 'sloped wall' with 469 | the ramp edge." 470 | 471 | [node name="CSGBox3D36" type="CSGPolygon3D" parent="."] 472 | transform = Transform3D(-0.965926, 0, 0.258819, 0, 1, 0, -0.258819, 0, -0.965926, 15.4931, 0, -2.3347) 473 | use_collision = true 474 | polygon = PackedVector2Array(-1, 0.9375, -1, 1, 1, 0, 1, -0.0625) 475 | material = SubResource("StandardMaterial3D_uatdx") 476 | 477 | [node name="CSGBox3D37" type="CSGBox3D" parent="."] 478 | transform = Transform3D(0.965926, 0, -0.258819, 0, 1, 0, 0.258819, 0, 0.965926, 15.411, 1.125, -1.06259) 479 | use_collision = true 480 | size = Vector3(1, 3.5, 0.5) 481 | material = SubResource("StandardMaterial3D_ihnv6") 482 | 483 | [node name="CSGBox3D38" type="CSGBox3D" parent="."] 484 | transform = Transform3D(0.965926, 0, -0.258819, 0, 1, 0, 0.258819, 0, 0.965926, 13.7206, 1.125, -1.5155) 485 | use_collision = true 486 | size = Vector3(0.5, 3.5, 0.5) 487 | material = SubResource("StandardMaterial3D_ihnv6") 488 | 489 | [node name="Label3D14" type="Label3D" parent="."] 490 | transform = Transform3D(0.965926, 0, -0.258819, 0, 1, 0, 0.258819, 0, 0.965926, 14.3501, 1.46182, -1.06422) 491 | pixel_size = 0.0025 492 | text = "It *SHOULD* be possible 493 | to step onto the green 494 | ramp from this direction, 495 | without being forced 496 | to jump, even with 497 | an AABB collision shape. 498 | Cylinders might require 499 | a hack to skip over the 500 | part of their bottom that 501 | forms a 'sloped wall' with 502 | the ramp edge." 503 | 504 | [node name="CSGBox3D39" type="CSGPolygon3D" parent="."] 505 | transform = Transform3D(-0.866026, 0, 0.5, 0, 1, 0, -0.5, 0, -0.866026, 19.6851, 0, -2.11211) 506 | use_collision = true 507 | polygon = PackedVector2Array(-1, 0.9375, -1, 1, 1, 0, 1, -0.0625) 508 | material = SubResource("StandardMaterial3D_uatdx") 509 | 510 | [node name="CSGBox3D40" type="CSGBox3D" parent="."] 511 | transform = Transform3D(0.866026, 0, -0.5, 0, 1, 0, 0.5, 0, 0.866026, 19.2766, 1.125, -0.904582) 512 | use_collision = true 513 | size = Vector3(1, 3.5, 0.5) 514 | material = SubResource("StandardMaterial3D_ihnv6") 515 | 516 | [node name="CSGBox3D41" type="CSGBox3D" parent="."] 517 | transform = Transform3D(0.866026, 0, -0.5, 0, 1, 0, 0.5, 0, 0.866026, 17.761, 1.125, -1.77957) 518 | use_collision = true 519 | size = Vector3(0.5, 3.5, 0.5) 520 | material = SubResource("StandardMaterial3D_ihnv6") 521 | 522 | [node name="Label3D15" type="Label3D" parent="."] 523 | transform = Transform3D(0.866026, 0, -0.5, 0, 1, 0, 0.5, 0, 0.866026, 18.2522, 1.46182, -1.18075) 524 | pixel_size = 0.0025 525 | text = "It *SHOULD* be possible 526 | to step onto the green 527 | ramp from this direction, 528 | without being forced 529 | to jump, even with 530 | an AABB collision shape. 531 | Cylinders might require 532 | a hack to skip over the 533 | part of their bottom that 534 | forms a 'sloped wall' with 535 | the ramp edge." 536 | 537 | [node name="Label3D5" type="Label3D" parent="."] 538 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.12485, 1.46182, -0.977026) 539 | pixel_size = 0.0025 540 | text = "glitch testing" 541 | 542 | [node name="Label3D12" type="Label3D" parent="."] 543 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.57804, 1.46182, -0.977026) 544 | pixel_size = 0.0025 545 | text = "simple 2:1 ramp" 546 | 547 | [node name="Label3D6" type="Label3D" parent="."] 548 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.35497, 1.46182, -0.977026) 549 | pixel_size = 0.0025 550 | text = "glitch testing" 551 | 552 | [node name="Label3D7" type="Label3D" parent="."] 553 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6.15945, 1.46182, -0.977026) 554 | pixel_size = 0.0025 555 | text = "glitch testing" 556 | 557 | [node name="Label3D8" type="Label3D" parent="."] 558 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.00267, 1.46182, -0.977026) 559 | pixel_size = 0.0025 560 | text = "glitch testing" 561 | 562 | [node name="Label3D9" type="Label3D" parent="."] 563 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -10.3718, 1.46182, -0.977026) 564 | pixel_size = 0.0025 565 | text = "glitch testing" 566 | 567 | [node name="CSGBox3D29" type="CSGPolygon3D" parent="."] 568 | transform = Transform3D(-0.707107, -0.707107, -6.18172e-08, -4.37114e-08, -4.37114e-08, 1, -0.707107, 0.707107, -7.10543e-15, -13.6402, 3, -2.10452) 569 | use_collision = true 570 | polygon = PackedVector2Array(-1, 0.8125, -1, 0.875, 1, 0.625, 1, 0.5625) 571 | depth = 3.0 572 | material = SubResource("StandardMaterial3D_fxdaq") 573 | 574 | [node name="CSGBox3D30" type="CSGPolygon3D" parent="."] 575 | transform = Transform3D(-0.707107, 0.707107, 6.18172e-08, 4.37114e-08, 1.31134e-07, -1, -0.707107, -0.707107, -1.23634e-07, -13.8137, 0, -1.93107) 576 | use_collision = true 577 | polygon = PackedVector2Array(-1, 0.8125, -1, 0.875, 1, 0.625, 1, 0.5625) 578 | depth = 3.0 579 | material = SubResource("StandardMaterial3D_fxdaq") 580 | 581 | [node name="CSGBox3D31" type="CSGPolygon3D" parent="."] 582 | transform = Transform3D(0.707107, 0, -0.707107, 0, 1, 0, 0.707107, 0, 0.707107, -14.0934, -0.625, -1.65506) 583 | use_collision = true 584 | polygon = PackedVector2Array(-1, 0.8125, -1, 0.875, 1, 0.625, 1, 0.5625) 585 | material = SubResource("StandardMaterial3D_uatdx") 586 | 587 | [node name="Label3D11" type="Label3D" parent="."] 588 | transform = Transform3D(0.707107, 0, 0.707107, 0, 1, 0, -0.707107, 0, 0.707107, -12.997, 1.46182, -1.30475) 589 | pixel_size = 0.0025 590 | text = "glitch testing" 591 | 592 | [node name="CSGBox3D48" type="CSGPolygon3D" parent="."] 593 | transform = Transform3D(-0.707107, -0.707107, -6.18172e-08, -4.37114e-08, -4.37114e-08, 1, -0.707107, 0.707107, -7.10543e-15, -16.7505, 3, -2.24789) 594 | use_collision = true 595 | polygon = PackedVector2Array(-1, 0.8125, -1, 0.875, 1, 0.625, 1, 0.5625) 596 | depth = 3.0 597 | material = SubResource("StandardMaterial3D_fxdaq") 598 | 599 | [node name="CSGBox3D49" type="CSGPolygon3D" parent="."] 600 | transform = Transform3D(-0.707107, 0.707107, 6.18172e-08, 4.37114e-08, 1.31134e-07, -1, -0.707107, -0.707107, -1.23634e-07, -17.1041, 0, -1.89433) 601 | use_collision = true 602 | polygon = PackedVector2Array(-1, 0.8125, -1, 0.875, 1, 0.625, 1, 0.5625) 603 | depth = 3.0 604 | material = SubResource("StandardMaterial3D_fxdaq") 605 | 606 | [node name="CSGBox3D50" type="CSGPolygon3D" parent="."] 607 | transform = Transform3D(0.707107, 0, -0.707107, 0, 1, 0, 0.707107, 0, 0.707107, -17.2184, -0.625, -1.65506) 608 | use_collision = true 609 | polygon = PackedVector2Array(-1, 0.8125, -1, 0.875, 1, 0.625, 1, 0.5625) 610 | material = SubResource("StandardMaterial3D_uatdx") 611 | 612 | [node name="Label3D18" type="Label3D" parent="."] 613 | transform = Transform3D(0.707107, 0, 0.707107, 0, 1, 0, -0.707107, 0, 0.707107, -16.122, 1.46182, -1.30475) 614 | pixel_size = 0.0025 615 | text = "glitch testing 616 | (slightly tighter)" 617 | --------------------------------------------------------------------------------