Initial commit

main
copygirl 2 years ago
commit b656d6e772
  1. 2
      .gitattributes
  2. 8
      .gitignore
  3. 158
      Player.cs
  4. BIN
      assets/character.blend
  5. BIN
      assets/character.glb
  6. 32
      assets/character.glb.import
  7. BIN
      assets/terrain_grass.png
  8. 35
      assets/terrain_grass.png.import
  9. 1
      icon.svg
  10. 37
      icon.svg.import
  11. 120
      project.godot
  12. 33
      scenes/Main.tscn
  13. 148
      scenes/Player.tscn
  14. 185
      scenes/World.tscn
  15. 137
      scripts/AnimationController.gd
  16. 45
      scripts/Camera.gd
  17. 6
      scripts/CloneMainCamera.gd
  18. 8
      scripts/Item.gd
  19. 7
      scripts/PhysicsLayer.gd
  20. 119
      scripts/PickupController.gd
  21. 123
      scripts/Player.gd
  22. 6
      scripts/RenderLayer.gd
  23. 24
      shaders/outline.gdshader

2
.gitattributes vendored

@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

8
.gitignore vendored

@ -0,0 +1,8 @@
# Godot 4+ specific ignore.
.godot/
# Don't include git addon.
addons/godot-git-plugin/
# Ignore blender backups.
*.blend[0-9]

@ -0,0 +1,158 @@
using Godot;
using System;
public partial class Player : CharacterBody3D
{
public float MouseSensitivity { get; set; } = 0.2F;
/// <summary> Time after pressing the jump button a jump may occur late. </summary>
public TimeSpan JumpEarlyTime { get; set; } = TimeSpan.FromSeconds(0.2);
/// <summary> Time after leaving a jumpable surface when a jump may still occur. </summary>
public TimeSpan JumpCoyoteTime { get; set; } = TimeSpan.FromSeconds(0.2);
public Vector3 Gravity { get; set; } = new(0, -12.0F, 0);
public float JumpVelocity { get; set; } = 5.0F;
public float MoveAccel { get; set; } = 6.0F;
public float MaxMoveSpeed { get; set; } = 4.0F;
public float FrictionFloor { get; set; } = 12.0F;
public float FrictionAir { get; set; } = 2.0F;
public enum MovementMode { Default, Flying, NoClip }
public MovementMode Movement { get; set; } = MovementMode.Default;
private Node3D _neckBone = null!;
private Node3D _headBone = null!;
private Camera3D _camera = null!;
private DateTime? _jumpPressed = null;
private DateTime? _lastOnFloor = null;
public bool IsSprinting { get; private set; }
public override void _Ready()
{
_neckBone = GetNode<Node3D>("Neck");
_headBone = GetNode<Node3D>("Neck/Head");
_camera = GetNode<Camera3D>("Neck/Head/Camera");
}
public override void _Input(InputEvent ev)
{
// Inputs that are valid when the game is focused.
// ===============================================
if (ev.IsAction("move_sprint"))
{
IsSprinting = ev.IsPressed();
GetViewport().SetInputAsHandled();
}
if (ev.IsActionPressed("move_jump"))
{
_jumpPressed = DateTime.Now;
GetViewport().SetInputAsHandled();
}
// Cycle movement mode between default, flying and flying+noclip.
if (ev.IsActionPressed("cycle_movement_mode"))
{
if (++Movement > MovementMode.NoClip)
Movement = MovementMode.Default;
GetViewport().SetInputAsHandled();
}
// Inputs that are valid only when the mouse is captured.
// ======================================================
if (Input.MouseMode == Input.MouseModeEnum.Captured) {
}
}
public override void _UnhandledInput(InputEvent ev)
{
var isMouseCaptured = Input.MouseMode == Input.MouseModeEnum.Captured;
// When pressing escape and mouse is currently captured, release it.
if (ev.IsActionPressed("ui_cancel") && isMouseCaptured)
Input.MouseMode = Input.MouseModeEnum.Visible;
// Grab the mouse when pressing the primary mouse button.
// TODO: Make "primary mouse button" configurable.
if (ev is InputEventMouseButton button && button.ButtonIndex == MouseButton.Left)
Input.MouseMode = Input.MouseModeEnum.Captured;
if (ev is InputEventMouseMotion motion && isMouseCaptured)
{
_neckBone.RotateX(Mathf.DegToRad(motion.Relative.Y * -MouseSensitivity));
_headBone.RotateY(Mathf.DegToRad(motion.Relative.X * -MouseSensitivity));
var rotation = _neckBone.RotationDegrees;
rotation.X = Mathf.Clamp(rotation.X, -80, 80);
_neckBone.RotationDegrees = rotation;
}
}
public override void _PhysicsProcess(double delta)
{
var movementVector = new Vector3(
Input.GetActionStrength("move_strafe_right") - Input.GetActionStrength("move_strafe_left"),
Input.GetActionStrength("move_upward") - Input.GetActionStrength("move_downward"),
Input.GetActionStrength("move_backward") - Input.GetActionStrength("move_forward"));
if (Movement == MovementMode.Default)
{
Velocity += Gravity * (float)delta;
var dir = Vector3.Zero;
var camTransform = _camera.GlobalTransform;
dir += camTransform.Basis.Z.Normalized() * movementVector.Z;
dir += camTransform.Basis.X.Normalized() * movementVector.X;
dir.Y = 0;
dir = dir.Normalized() * movementVector.Length();
var hvel = Velocity;
hvel.Y = 0;
var target = dir * MaxMoveSpeed;
var friction = IsOnFloor() ? FrictionFloor : FrictionAir;
var accel = (dir.Dot(hvel) > 0) ? MoveAccel : friction;
if (IsSprinting) { target *= 5; accel *= 5; }
hvel = hvel.Lerp(target, accel * (float)delta);
Velocity = new(hvel.X, Velocity.Y, hvel.Z);
// Sometimes, when pushing into a wall, jumping wasn't working.
// Possibly due to `IsOnFloor` returning `false` for some reason.
// The `JumpEarlyTime` feature seems to avoid this issue, thankfully.
if (IsOnFloor()) _lastOnFloor = DateTime.Now;
if (((DateTime.Now - _jumpPressed) <= JumpEarlyTime)
&& ((DateTime.Now - _lastOnFloor) <= JumpCoyoteTime))
{
Velocity = new(Velocity.X, JumpVelocity, Velocity.Z);
_jumpPressed = null;
_lastOnFloor = null;
}
}
else
{
Velocity *= 1 - FrictionAir * (float)delta;
var cameraRot = _headBone.GlobalTransform.Basis.GetRotationQuaternion();
var dir = cameraRot * movementVector;
var target = dir * MaxMoveSpeed;
var accel = (dir.Dot(Velocity) > 0) ? MoveAccel : FrictionAir;
target *= 4; accel *= 4;
if (IsSprinting) { target *= 5; accel *= 5; }
Velocity = Velocity.Lerp(target, accel * (float)delta);
}
if (Movement == MovementMode.NoClip)
Translate(Velocity * (float)delta);
else MoveAndSlide();
}
}

Binary file not shown.

Binary file not shown.

@ -0,0 +1,32 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://brbatwk2mtko4"
path="res://.godot/imported/character.glb-9c9a0ab135b4ff50eb19bfdefe1081c3.scn"
[deps]
source_file="res://assets/character.glb"
dest_files=["res://.godot/imported/character.glb-9c9a0ab135b4ff50eb19bfdefe1081c3.scn"]
[params]
nodes/root_type="Node3D"
nodes/root_name="Scene Root"
nodes/apply_root_scale=true
nodes/root_scale=1.0
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
import_script/path=""
_subresources={}
gltf/embedded_image_handling=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bmormqcp2qv4k"
path.s3tc="res://.godot/imported/terrain_grass.png-b5e6be896538434c286abbba16efbfcb.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://assets/terrain_grass.png"
dest_files=["res://.godot/imported/terrain_grass.png-b5e6be896538434c286abbba16efbfcb.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

@ -0,0 +1 @@
<svg height="128" width="128" xmlns="http://www.w3.org/2000/svg"><g transform="translate(32 32)"><path d="m-16-32c-8.86 0-16 7.13-16 15.99v95.98c0 8.86 7.13 15.99 16 15.99h96c8.86 0 16-7.13 16-15.99v-95.98c0-8.85-7.14-15.99-16-15.99z" fill="#363d52"/><path d="m-16-32c-8.86 0-16 7.13-16 15.99v95.98c0 8.86 7.13 15.99 16 15.99h96c8.86 0 16-7.13 16-15.99v-95.98c0-8.85-7.14-15.99-16-15.99zm0 4h96c6.64 0 12 5.35 12 11.99v95.98c0 6.64-5.35 11.99-12 11.99h-96c-6.64 0-12-5.35-12-11.99v-95.98c0-6.64 5.36-11.99 12-11.99z" fill-opacity=".4"/></g><g stroke-width="9.92746" transform="matrix(.10073078 0 0 .10073078 12.425923 2.256365)"><path d="m0 0s-.325 1.994-.515 1.976l-36.182-3.491c-2.879-.278-5.115-2.574-5.317-5.459l-.994-14.247-27.992-1.997-1.904 12.912c-.424 2.872-2.932 5.037-5.835 5.037h-38.188c-2.902 0-5.41-2.165-5.834-5.037l-1.905-12.912-27.992 1.997-.994 14.247c-.202 2.886-2.438 5.182-5.317 5.46l-36.2 3.49c-.187.018-.324-1.978-.511-1.978l-.049-7.83 30.658-4.944 1.004-14.374c.203-2.91 2.551-5.263 5.463-5.472l38.551-2.75c.146-.01.29-.016.434-.016 2.897 0 5.401 2.166 5.825 5.038l1.959 13.286h28.005l1.959-13.286c.423-2.871 2.93-5.037 5.831-5.037.142 0 .284.005.423.015l38.556 2.75c2.911.209 5.26 2.562 5.463 5.472l1.003 14.374 30.645 4.966z" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 919.24059 771.67186)"/><path d="m0 0v-47.514-6.035-5.492c.108-.001.216-.005.323-.015l36.196-3.49c1.896-.183 3.382-1.709 3.514-3.609l1.116-15.978 31.574-2.253 2.175 14.747c.282 1.912 1.922 3.329 3.856 3.329h38.188c1.933 0 3.573-1.417 3.855-3.329l2.175-14.747 31.575 2.253 1.115 15.978c.133 1.9 1.618 3.425 3.514 3.609l36.182 3.49c.107.01.214.014.322.015v4.711l.015.005v54.325c5.09692 6.4164715 9.92323 13.494208 13.621 19.449-5.651 9.62-12.575 18.217-19.976 26.182-6.864-3.455-13.531-7.369-19.828-11.534-3.151 3.132-6.7 5.694-10.186 8.372-3.425 2.751-7.285 4.768-10.946 7.118 1.09 8.117 1.629 16.108 1.846 24.448-9.446 4.754-19.519 7.906-29.708 10.17-4.068-6.837-7.788-14.241-11.028-21.479-3.842.642-7.702.88-11.567.926v.006c-.027 0-.052-.006-.075-.006-.024 0-.049.006-.073.006v-.006c-3.872-.046-7.729-.284-11.572-.926-3.238 7.238-6.956 14.642-11.03 21.479-10.184-2.264-20.258-5.416-29.703-10.17.216-8.34.755-16.331 1.848-24.448-3.668-2.35-7.523-4.367-10.949-7.118-3.481-2.678-7.036-5.24-10.188-8.372-6.297 4.165-12.962 8.079-19.828 11.534-7.401-7.965-14.321-16.562-19.974-26.182 4.4426579-6.973692 9.2079702-13.9828876 13.621-19.449z" fill="#478cbf" transform="matrix(4.162611 0 0 -4.162611 104.69892 525.90697)"/><path d="m0 0-1.121-16.063c-.135-1.936-1.675-3.477-3.611-3.616l-38.555-2.751c-.094-.007-.188-.01-.281-.01-1.916 0-3.569 1.406-3.852 3.33l-2.211 14.994h-31.459l-2.211-14.994c-.297-2.018-2.101-3.469-4.133-3.32l-38.555 2.751c-1.936.139-3.476 1.68-3.611 3.616l-1.121 16.063-32.547 3.138c.015-3.498.06-7.33.06-8.093 0-34.374 43.605-50.896 97.781-51.086h.066.067c54.176.19 97.766 16.712 97.766 51.086 0 .777.047 4.593.063 8.093z" fill="#478cbf" transform="matrix(4.162611 0 0 -4.162611 784.07144 817.24284)"/><path d="m0 0c0-12.052-9.765-21.815-21.813-21.815-12.042 0-21.81 9.763-21.81 21.815 0 12.044 9.768 21.802 21.81 21.802 12.048 0 21.813-9.758 21.813-21.802" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 389.21484 625.67104)"/><path d="m0 0c0-7.994-6.479-14.473-14.479-14.473-7.996 0-14.479 6.479-14.479 14.473s6.483 14.479 14.479 14.479c8 0 14.479-6.485 14.479-14.479" fill="#414042" transform="matrix(4.162611 0 0 -4.162611 367.36686 631.05679)"/><path d="m0 0c-3.878 0-7.021 2.858-7.021 6.381v20.081c0 3.52 3.143 6.381 7.021 6.381s7.028-2.861 7.028-6.381v-20.081c0-3.523-3.15-6.381-7.028-6.381" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 511.99336 724.73954)"/><path d="m0 0c0-12.052 9.765-21.815 21.815-21.815 12.041 0 21.808 9.763 21.808 21.815 0 12.044-9.767 21.802-21.808 21.802-12.05 0-21.815-9.758-21.815-21.802" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 634.78706 625.67104)"/><path d="m0 0c0-7.994 6.477-14.473 14.471-14.473 8.002 0 14.479 6.479 14.479 14.473s-6.477 14.479-14.479 14.479c-7.994 0-14.471-6.485-14.471-14.479" fill="#414042" transform="matrix(4.162611 0 0 -4.162611 656.64056 631.05679)"/></g></svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bmc3w2hmvgi2q"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.svg"
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

@ -0,0 +1,120 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="Inventory"
run/main_scene="res://scenes/Main.tscn"
config/features=PackedStringArray("4.0", "GL Compatibility")
config/icon="res://icon.svg"
[dotnet]
project/assembly_name="Inventory"
[editor]
version_control/plugin_name="GitPlugin"
version_control/autoload_on_startup=true
[input]
move_forward={
"deadzone": 0.1,
"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)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":-1.0,"script":null)
]
}
move_backward={
"deadzone": 0.1,
"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)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":1.0,"script":null)
]
}
move_strafe_left={
"deadzone": 0.1,
"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)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":-1.0,"script":null)
]
}
move_strafe_right={
"deadzone": 0.1,
"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)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":1.0,"script":null)
]
}
move_upward={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"echo":false,"script":null)
]
}
move_downward={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194326,"key_label":0,"unicode":0,"echo":false,"script":null)
]
}
move_sprint={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"echo":false,"script":null)
]
}
move_jump={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":false,"script":null)
]
}
cycle_movement_mode={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194336,"key_label":0,"unicode":0,"echo":false,"script":null)
]
}
look_up={
"deadzone": 0.1,
"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":3,"axis_value":-1.0,"script":null)
]
}
look_down={
"deadzone": 0.1,
"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":3,"axis_value":1.0,"script":null)
]
}
look_left={
"deadzone": 0.1,
"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":2,"axis_value":-1.0,"script":null)
]
}
look_right={
"deadzone": 0.1,
"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":2,"axis_value":1.0,"script":null)
]
}
interact_pickup={
"deadzone": 0.5,
"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":1,"position":Vector2(134, 20),"global_position":Vector2(138, 63),"factor":1.0,"button_index":1,"pressed":true,"double_click":false,"script":null)
]
}
interact_place={
"deadzone": 0.5,
"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":2,"position":Vector2(119, 9),"global_position":Vector2(123, 52),"factor":1.0,"button_index":2,"pressed":true,"double_click":false,"script":null)
]
}
[layer_names]
3d_render/layer_1="Default"
3d_render/layer_2="Outline"
3d_physics/layer_1="World"
3d_physics/layer_2="Player"
3d_physics/layer_3="Item"
[physics]
common/physics_ticks_per_second=120

@ -0,0 +1,33 @@
[gd_scene load_steps=5 format=3]
[ext_resource type="PackedScene" uid="uid://b7o2y54duqxft" path="res://scenes/World.tscn" id="1_px6sj"]
[ext_resource type="Shader" path="res://shaders/outline.gdshader" id="2_dd8cf"]
[ext_resource type="Script" path="res://scripts/CloneMainCamera.gd" id="2_m7qgc"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_7rvwk"]
shader = ExtResource("2_dd8cf")
shader_parameter/line_color = Color(1, 1, 1, 0.75)
shader_parameter/line_thickness = 2.0
[node name="Main" type="Node"]
[node name="World" parent="." instance=ExtResource("1_px6sj")]
[node name="SubViewportContainer" type="SubViewportContainer" parent="."]
material = SubResource("ShaderMaterial_7rvwk")
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
stretch = true
[node name="SubViewport" type="SubViewport" parent="SubViewportContainer"]
transparent_bg = true
handle_input_locally = false
size = Vector2i(1152, 648)
render_target_update_mode = 4
[node name="ClonedCamera" type="Camera3D" parent="SubViewportContainer/SubViewport"]
cull_mask = 2
script = ExtResource("2_m7qgc")

@ -0,0 +1,148 @@
[gd_scene load_steps=13 format=3 uid="uid://bgymmkpfgntea"]
[ext_resource type="Script" path="res://scripts/Player.gd" id="1_kae1r"]
[ext_resource type="Script" path="res://scripts/Camera.gd" id="3_wo4mm"]
[ext_resource type="PackedScene" uid="uid://brbatwk2mtko4" path="res://assets/character.glb" id="4_e6k8n"]
[ext_resource type="Script" path="res://scripts/AnimationController.gd" id="4_y3wch"]
[ext_resource type="Script" path="res://scripts/PickupController.gd" id="5_wmkdb"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_2p66e"]
radius = 0.3
height = 1.5
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_30188"]
animation = &"idle"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_hov4s"]
animation = &"walk_backward"
[sub_resource type="AnimationNodeTransition" id="AnimationNodeTransition_881to"]
sync = true
xfade_time = 0.1
input_0/name = "forward"
input_0/auto_advance = false
input_0/reset = true
input_1/name = "backward"
input_1/auto_advance = false
input_1/reset = true
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_rer12"]
animation = &"walk_forward"
[sub_resource type="AnimationNodeTransition" id="AnimationNodeTransition_5e4h6"]
xfade_time = 0.25
input_0/name = "idle"
input_0/auto_advance = false
input_0/reset = false
input_1/name = "move"
input_1/auto_advance = false
input_1/reset = true
[sub_resource type="AnimationNodeBlendTree" id="AnimationNodeBlendTree_bgirn"]
nodes/idle/node = SubResource("AnimationNodeAnimation_30188")
nodes/idle/position = Vector2(240, 180)
nodes/output/position = Vector2(640, 280)
nodes/walk_backward/node = SubResource("AnimationNodeAnimation_hov4s")
nodes/walk_backward/position = Vector2(0, 420)
nodes/walk_direction/node = SubResource("AnimationNodeTransition_881to")
nodes/walk_direction/position = Vector2(220, 320)
nodes/walk_forward/node = SubResource("AnimationNodeAnimation_rer12")
nodes/walk_forward/position = Vector2(0, 280)
nodes/walk_state/node = SubResource("AnimationNodeTransition_5e4h6")
nodes/walk_state/position = Vector2(440, 220)
node_connections = [&"output", 0, &"walk_state", &"walk_direction", 0, &"walk_forward", &"walk_direction", 1, &"walk_backward", &"walk_state", 0, &"idle", &"walk_state", 1, &"walk_direction"]
[node name="Player" type="CharacterBody3D" node_paths=PackedStringArray("camera")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.75, 0)
collision_layer = 2
script = ExtResource("1_kae1r")
camera = NodePath("Character/Skeleton/Root/LowerBody/UpperBody/Neck/Head/Camera")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
shape = SubResource("CapsuleShape3D_2p66e")
[node name="Character" parent="." instance=ExtResource("4_e6k8n")]
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 0, -0.75, 0)
[node name="Root" type="BoneAttachment3D" parent="Character/Skeleton" index="0"]
transform = Transform3D(-1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0.199674, 0)
bone_name = "Root"
bone_idx = 0
use_external_skeleton = true
external_skeleton = NodePath("../Skeleton3D")
[node name="LowerBody" type="BoneAttachment3D" parent="Character/Skeleton/Root"]
transform = Transform3D(-1, -3.0926e-10, 8.74222e-08, 8.71645e-08, 0.0732855, 0.997311, -6.71521e-09, 0.997311, -0.0732856, 0, 0, -0.199593)
bone_name = "LowerBody"
bone_idx = 1
use_external_skeleton = true
external_skeleton = NodePath("../../Skeleton3D")
[node name="UpperBody" type="BoneAttachment3D" parent="Character/Skeleton/Root/LowerBody"]
transform = Transform3D(1, -1.02448e-08, 8.68204e-08, 4.44089e-16, 0.99311, 0.117186, -8.74228e-08, -0.117186, 0.99311, 0, 0.154362, -7.45058e-09)
bone_name = "UpperBody"
bone_idx = 2
use_external_skeleton = true
external_skeleton = NodePath("../../../Skeleton3D")
[node name="Neck" type="BoneAttachment3D" parent="Character/Skeleton/Root/LowerBody/UpperBody"]
transform = Transform3D(1, -1.66533e-16, 0, 0, 0.998891, -0.0470728, -7.10543e-15, 0.0470728, 0.998892, -1.11022e-16, 0.251888, 5.58794e-09)
bone_name = "Neck"
bone_idx = 3
use_external_skeleton = true
external_skeleton = NodePath("../../../../Skeleton3D")
[node name="Head" type="BoneAttachment3D" parent="Character/Skeleton/Root/LowerBody/UpperBody/Neck"]
transform = Transform3D(-1, 1.20146e-10, 8.74227e-08, 8.74228e-08, 0.0013743, 0.999999, -7.10543e-15, 0.999999, -0.00137436, -2.22045e-16, 0.101598, 3.72529e-09)
bone_name = "Head"
bone_idx = 4
use_external_skeleton = true
external_skeleton = NodePath("../../../../../Skeleton3D")
[node name="Camera" type="Camera3D" parent="Character/Skeleton/Root/LowerBody/UpperBody/Neck/Head"]
transform = Transform3D(1, -1.67666e-09, 1.78995e-09, 1.82243e-09, 0.0195853, -0.999808, 1.6413e-09, 0.999808, 0.0195851, 6.66283e-09, 0.0576435, 0.0321114)
cull_mask = 1
script = ExtResource("3_wo4mm")
[node name="Skeleton3D" parent="Character/Skeleton" index="1"]
bones/0/position = Vector3(0, 0.199674, 0)
bones/1/rotation = Quaternion(2.9641e-08, 0.732559, 0.680703, 3.21262e-08)
bones/2/rotation = Quaternion(-0.0586944, 4.3636e-08, 2.56562e-09, 0.998276)
bones/4/rotation = Quaternion(3.09298e-08, 0.707592, 0.706621, 3.08874e-08)
bones/5/rotation = Quaternion(-0.00410346, -0.0231825, 0.984812, -0.172018)
bones/6/rotation = Quaternion(1.52056e-05, 5.1329e-05, -0.0647817, 0.997899)
bones/7/rotation = Quaternion(-1.1035e-05, -5.20782e-05, -0.0156829, 0.999877)
bones/12/rotation = Quaternion(0.00410346, -0.0231825, 0.984812, 0.172018)
bones/13/rotation = Quaternion(1.52056e-05, -5.1329e-05, 0.0647817, 0.997899)
bones/14/rotation = Quaternion(-1.1035e-05, 5.20782e-05, 0.0156829, 0.999877)
bones/19/rotation = Quaternion(0.0101145, -0.706764, 0.707308, 0.00993137)
bones/20/rotation = Quaternion(0.026743, 0.00254827, 0.0016329, 0.999638)
bones/21/rotation = Quaternion(-0.00105386, -0.689614, 0.723924, -0.0191383)
bones/22/rotation = Quaternion(-0.0101145, -0.706764, 0.707308, -0.00993136)
bones/23/rotation = Quaternion(0.026743, -0.00254827, -0.0016329, 0.999638)
bones/24/rotation = Quaternion(0.00105386, -0.689614, 0.723924, 0.0191383)
[node name="AnimationController" type="Node3D" parent="." node_paths=PackedStringArray("camera", "skeleton", "root_bone")]
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 0, 0, 0)
script = ExtResource("4_y3wch")
camera = NodePath("../Character/Skeleton/Root/LowerBody/UpperBody/Neck/Head/Camera")
skeleton = NodePath("../Character/Skeleton/Skeleton3D")
root_bone = NodePath("../Character/Skeleton/Root")
[node name="AnimationTree" type="AnimationTree" parent="AnimationController"]
tree_root = SubResource("AnimationNodeBlendTree_bgirn")
anim_player = NodePath("../../Character/AnimationPlayer")
active = true
parameters/walk_direction/current_state = "forward"
parameters/walk_direction/transition_request = ""
parameters/walk_direction/current_index = 0
parameters/walk_state/current_state = "idle"
parameters/walk_state/transition_request = ""
parameters/walk_state/current_index = 0
[node name="PickupController" type="Node3D" parent="." node_paths=PackedStringArray("camera")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.324461, -0.291141)
script = ExtResource("5_wmkdb")
camera = NodePath("../Character/Skeleton/Root/LowerBody/UpperBody/Neck/Head/Camera")
[editable path="Character"]

@ -0,0 +1,185 @@
[gd_scene load_steps=19 format=3 uid="uid://b7o2y54duqxft"]
[ext_resource type="Texture2D" uid="uid://bmormqcp2qv4k" path="res://assets/terrain_grass.png" id="1_mi8br"]
[ext_resource type="PackedScene" uid="uid://bgymmkpfgntea" path="res://scenes/Player.tscn" id="2_4nlbn"]
[ext_resource type="Script" path="res://scripts/Item.gd" id="3_2aosx"]
[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_4q4bb"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_eyy3t"]
albedo_texture = ExtResource("1_mi8br")
uv1_scale = Vector3(8, 8, 8)
texture_filter = 0
[sub_resource type="PlaneMesh" id="PlaneMesh_ce3ji"]
material = SubResource("StandardMaterial3D_eyy3t")
size = Vector2(256, 256)
[sub_resource type="BoxShape3D" id="BoxShape3D_1w5kh"]
size = Vector3(2, 0.1, 1)
[sub_resource type="BoxMesh" id="BoxMesh_jergk"]
size = Vector3(2, 0.1, 1)
[sub_resource type="BoxMesh" id="BoxMesh_yyb1u"]
[sub_resource type="BoxShape3D" id="BoxShape3D_cx0e5"]
size = Vector3(0.05, 0.2, 0.05)
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_4jkru"]
albedo_color = Color(1, 0.364706, 1, 1)
[sub_resource type="BoxMesh" id="BoxMesh_2cpu0"]
material = SubResource("StandardMaterial3D_4jkru")
size = Vector3(0.05, 0.2, 0.05)
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_2pcea"]
albedo_color = Color(1, 0, 0, 1)
[sub_resource type="CylinderMesh" id="CylinderMesh_t8qr8"]
material = SubResource("StandardMaterial3D_2pcea")
top_radius = 0.01
bottom_radius = 0.01
height = 0.1
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_befdk"]
albedo_color = Color(0, 1, 0, 1)
[sub_resource type="CylinderMesh" id="CylinderMesh_kgvwt"]
material = SubResource("StandardMaterial3D_befdk")
top_radius = 0.01
bottom_radius = 0.01
height = 0.1
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_gx7gw"]
albedo_color = Color(0, 0, 1, 1)
[sub_resource type="CylinderMesh" id="CylinderMesh_8iq4p"]
material = SubResource("StandardMaterial3D_gx7gw")
top_radius = 0.01
bottom_radius = 0.01
height = 0.1
[node name="World" type="Node3D"]
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(0.56961, 0.533344, -0.625371, -2.77304e-08, 0.760871, 0.648904, 0.821916, -0.369622, 0.433399, 0, 4, 0)
light_color = Color(0.968627, 0.858824, 0.717647, 1)
shadow_enabled = true
[node name="Floor" type="StaticBody3D" parent="."]
collision_mask = 0
metadata/_edit_lock_ = true
[node name="CollisionShape3D" type="CollisionShape3D" parent="Floor"]
shape = SubResource("WorldBoundaryShape3D_4q4bb")
[node name="MeshInstance3D" type="MeshInstance3D" parent="Floor"]
mesh = SubResource("PlaneMesh_ce3ji")
[node name="Player" parent="." instance=ExtResource("2_4nlbn")]
[node name="Table" type="Node3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -2)
metadata/_edit_lock_ = true
[node name="Box" type="StaticBody3D" parent="Table"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
[node name="CollisionShape3D" type="CollisionShape3D" parent="Table/Box"]
shape = SubResource("BoxShape3D_1w5kh")
[node name="MeshInstance3D" type="MeshInstance3D" parent="Table/Box"]
mesh = SubResource("BoxMesh_jergk")
[node name="MeshInstance3D" type="MeshInstance3D" parent="Table"]
transform = Transform3D(0.099, 0, -8.12102e-05, 0, 1, 0, 8.12102e-05, 0, 0.099, -0.900327, 0.5, 0.399261)
mesh = SubResource("BoxMesh_yyb1u")
skeleton = NodePath("")
[node name="MeshInstance3D2" type="MeshInstance3D" parent="Table"]
transform = Transform3D(0.099, 0, -8.12102e-05, 0, 1, 0, 8.12102e-05, 0, 0.099, -0.899678, 0.5, -0.392738)
mesh = SubResource("BoxMesh_yyb1u")
skeleton = NodePath("")
[node name="MeshInstance3D3" type="MeshInstance3D" parent="Table"]
transform = Transform3D(0.099, 0, -8.12102e-05, 0, 1, 0, 8.12102e-05, 0, 0.099, 0.9, 0.5, 0.401)
mesh = SubResource("BoxMesh_yyb1u")
skeleton = NodePath("")
[node name="MeshInstance3D4" type="MeshInstance3D" parent="Table"]
transform = Transform3D(0.099, 0, -8.12102e-05, 0, 1, 0, 8.12102e-05, 0, 0.099, 0.9, 0.5, -0.391)
mesh = SubResource("BoxMesh_yyb1u")
skeleton = NodePath("")
[node name="Item" type="RigidBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.762804, 1.15199, -1.85385)
collision_layer = 4
collision_mask = 7
script = ExtResource("3_2aosx")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Item"]
shape = SubResource("BoxShape3D_cx0e5")
[node name="MeshInstance3D" type="MeshInstance3D" parent="Item"]
mesh = SubResource("BoxMesh_2cpu0")
[node name="X" type="MeshInstance3D" parent="Item"]
transform = Transform3D(-4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 0, 0, 1, 0.1, 0, 0)
mesh = SubResource("CylinderMesh_t8qr8")
[node name="Y" type="MeshInstance3D" parent="Item"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 0)
mesh = SubResource("CylinderMesh_kgvwt")
[node name="Z" type="MeshInstance3D" parent="Item"]
transform = Transform3D(1.91069e-15, -4.37114e-08, -1, -1, -4.37114e-08, 0, -4.37114e-08, 1, -4.37114e-08, 0, 0, 0.1)
mesh = SubResource("CylinderMesh_8iq4p")
[node name="Item2" type="RigidBody3D" parent="."]
transform = Transform3D(-3.79793e-08, -0.868865, 0.495049, 1, -4.37114e-08, 0, 2.16393e-08, 0.495049, 0.868865, -0.49893, 1.07653, -1.68684)
collision_layer = 4
collision_mask = 7
script = ExtResource("3_2aosx")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Item2"]
shape = SubResource("BoxShape3D_cx0e5")
[node name="MeshInstance3D" type="MeshInstance3D" parent="Item2"]
mesh = SubResource("BoxMesh_2cpu0")
[node name="X" type="MeshInstance3D" parent="Item2"]
transform = Transform3D(-4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 0, 0, 1, 0.1, 0, 0)
mesh = SubResource("CylinderMesh_t8qr8")
[node name="Y" type="MeshInstance3D" parent="Item2"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 0)
mesh = SubResource("CylinderMesh_kgvwt")
[node name="Z" type="MeshInstance3D" parent="Item2"]
transform = Transform3D(1.91069e-15, -4.37114e-08, -1, -1, -4.37114e-08, 0, -4.37114e-08, 1, -4.37114e-08, 0, 0, 0.1)
mesh = SubResource("CylinderMesh_8iq4p")
[node name="Item3" type="RigidBody3D" parent="."]
transform = Transform3D(0.994627, -0.103522, -4.52508e-09, 0, -4.37114e-08, 1, -0.103522, -0.994627, -4.34765e-08, -0.106395, 1.07776, -1.65152)
collision_layer = 4
collision_mask = 7
script = ExtResource("3_2aosx")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Item3"]
shape = SubResource("BoxShape3D_cx0e5")
[node name="MeshInstance3D" type="MeshInstance3D" parent="Item3"]
mesh = SubResource("BoxMesh_2cpu0")
[node name="X" type="MeshInstance3D" parent="Item3"]
transform = Transform3D(-4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 0, 0, 1, 0.1, 0, 0)
mesh = SubResource("CylinderMesh_t8qr8")
[node name="Y" type="MeshInstance3D" parent="Item3"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 0)
mesh = SubResource("CylinderMesh_kgvwt")
[node name="Z" type="MeshInstance3D" parent="Item3"]
transform = Transform3D(1.91069e-15, -4.37114e-08, -1, -1, -4.37114e-08, 0, -4.37114e-08, 1, -4.37114e-08, 0, 0, 0.1)
mesh = SubResource("CylinderMesh_8iq4p")

@ -0,0 +1,137 @@
extends Node3D
@export var camera : Camera
@export var skeleton : Skeleton3D
@export var root_bone : BoneAttachment3D
@onready var player := find_parent("Player") as Player
@onready var anim_tree := $AnimationTree as AnimationTree
@onready var anim_player := anim_tree.get_node(anim_tree.anim_player) as AnimationPlayer
@onready var camera_default_transform := camera.transform
@onready var walk_forward_anim := anim_player.get_animation("walk_forward")
@onready var walk_backward_anim := anim_player.get_animation("walk_backward")
# TODO: @onready var walk_loop_length := walk_forward_anim.length
# Contains all the bones in the skeleton, keyed by name (e.g. "LowerArm_L").
var bones := { }
# Whether the player's body is currently turning to match up with the camera rotation.
var is_turning := false
# Current amount the body is turned due to walking sideways.
var body_yaw := 0.0 # radians
func _ready() -> void:
var add_bone := func(bone: BoneAttachment3D) -> void:
bone.override_pose = true
bones[bone.name] = bone
add_bone.call(root_bone)
for child in root_bone.find_children("*", "BoneAttachment3D"):
add_bone.call(child as BoneAttachment3D)
func _process(delta: float) -> void:
reset_transforms()
handle_turning(delta)
handle_looking_animation(delta)
handle_walking_animation(delta)
func reset_transforms() -> void:
for bone_name in bones:
var bone := bones[bone_name] as BoneAttachment3D
bone.transform = skeleton.get_bone_pose(bone.bone_idx)
camera.transform = camera_default_transform
func handle_turning(delta: float) -> void:
const TURN_BEGIN := 60.0 # Start turning when camera is rotated this much.
const TURN_END := 5.0 # Stop turning when body is this close to camera rotation.
const TURN_SPEED := 6.0
var yaw := camera.current_yaw # Camera yaw relative to player yaw.
if player.is_moving || abs(yaw) > deg_to_rad(TURN_BEGIN):
is_turning = true
if is_turning:
var yaw_delta = sign(yaw) * min(abs(yaw), (abs(yaw) * TURN_SPEED) * delta)
player.rotate_y(yaw_delta)
camera.current_yaw -= yaw_delta
if abs(camera.current_yaw) < deg_to_rad(TURN_END):
is_turning = false
func handle_looking_animation(_delta: float) -> void:
const PITCH_FACTOR_NECK := 0.25
const PITCH_FACTOR_HEAD := 0.35
var pitch := camera.current_pitch
bones["Neck"].rotate_x(-pitch * PITCH_FACTOR_NECK)
bones["Head"].rotate_x(-pitch * PITCH_FACTOR_HEAD)
camera.rotate_x(pitch * (1 - PITCH_FACTOR_NECK - PITCH_FACTOR_HEAD))
const YAW_FACTOR_LOWER_BODY := 0.06
const YAW_FACTOR_UPPER_BODY := 0.18
const YAW_FACTOR_NECK := 0.2
const YAW_FACTOR_HEAD := 0.3
var yaw := camera.current_yaw
bones["LowerBody"].global_rotate(Vector3.UP, yaw * YAW_FACTOR_LOWER_BODY)
bones["UpperBody"].global_rotate(Vector3.UP, yaw * YAW_FACTOR_UPPER_BODY)
bones["Neck"].global_rotate(Vector3.UP, yaw * YAW_FACTOR_NECK)
bones["Head"].global_rotate(Vector3.UP, yaw * YAW_FACTOR_HEAD)
camera.global_rotate(Vector3.UP, yaw * (1 - YAW_FACTOR_LOWER_BODY - YAW_FACTOR_UPPER_BODY - YAW_FACTOR_NECK - YAW_FACTOR_HEAD))
# How much of the "ideal" camera rotation (rather than animation rotation) should be applied.
const CAMERA_FACTOR_IDEAL_PITCH := 1.0 # 0.7
const CAMERA_FACTOR_IDEAL_YAW := 1.0 # 0.8
const CAMERA_FACTOR_IDEAL_ROLL := 1.0 # 0.9
var global_yaw := player.rotation.y + yaw
camera.global_rotation.x = lerp_angle(camera.global_rotation.x, pitch, CAMERA_FACTOR_IDEAL_PITCH)
camera.global_rotation.y = lerp_angle(camera.global_rotation.y, global_yaw, CAMERA_FACTOR_IDEAL_YAW)
camera.global_rotation.z = lerp_angle(camera.global_rotation.z, 0, CAMERA_FACTOR_IDEAL_ROLL)
func handle_walking_animation(delta: float) -> void:
var input := Input.get_vector("move_strafe_left", "move_strafe_right", "move_forward", "move_backward")
var is_on_floor := player.time_since_on_floor < 0.25
var is_moving_forward := input.y <= 0
var walk_state : String
var walk_direction : String
var target_body_yaw : float
if is_on_floor && player.is_moving:
walk_state = "move"
else:
walk_state = "idle"
if is_moving_forward:
walk_direction = "forward"
target_body_yaw = -Vector2.UP.angle_to(input)
else:
walk_direction = "backward"
target_body_yaw = -Vector2.DOWN.angle_to(input)
anim_tree["parameters/walk_state/transition_request"] = walk_state
anim_tree["parameters/walk_direction/transition_request"] = walk_direction
const YAW_FACTOR_LOWER_BODY := 0.25
const YAW_FACTOR_UPPER_BODY := 0.25
const YAW_FACTOR_NECK := 0.50
body_yaw += (target_body_yaw - body_yaw) * delta * 6
bones["Root"].global_rotate(Vector3.UP, body_yaw)
bones["LowerBody"].global_rotate(Vector3.UP, -body_yaw * YAW_FACTOR_LOWER_BODY)
bones["UpperBody"].global_rotate(Vector3.UP, -body_yaw * YAW_FACTOR_UPPER_BODY)
bones["Neck"].global_rotate(Vector3.UP, -body_yaw * YAW_FACTOR_NECK)
static func angle_difference(from: float, to: float) -> float:
return fposmod(to - from + PI, TAU) - PI

@ -0,0 +1,45 @@
class_name Camera
extends Camera3D
@export var mouse_sensitivity := Vector2(0.2, 0.2) # degrees per pixel
@export var joystick_sensitivity := Vector2(200, 200) # degrees per second
@export var pitch_min := -80.0
@export var pitch_max := 80.0
var current_pitch := 0.0
var current_yaw := 0.0
func _unhandled_input(event: InputEvent) -> void:
# When pressing escape and mouse is currently captured, release it.
if event.is_action_pressed("ui_cancel") && is_mouse_captured():
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
var button := event as InputEventMouseButton
if button:
# Grab the mouse when pressing the primary mouse button.
# TODO: Make "primary mouse button" configurable.
if event.button_index == MOUSE_BUTTON_LEFT:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
var mouseMotion := event as InputEventMouseMotion
if mouseMotion && is_mouse_captured():
apply_rotation(-mouseMotion.relative * mouse_sensitivity)
func _process(delta: float) -> void:
# Handle joystick camera controls.
var input := Input.get_vector("look_left", "look_right", "look_up", "look_down")
apply_rotation(-input * joystick_sensitivity * delta)
# Returns whether the mouse is currently captured by the game.
func is_mouse_captured() -> bool:
return Input.mouse_mode == Input.MOUSE_MODE_CAPTURED
# Applies the specified rotation (in degrees) to the camera.
func apply_rotation(delta: Vector2) -> void:
delta = delta / 360 * TAU
current_pitch = clampf(current_pitch + delta.y, deg_to_rad(pitch_min), deg_to_rad(pitch_max))
current_yaw += delta.x

@ -0,0 +1,6 @@
extends Camera3D
func _process(_delta: float) -> void:
var main_viewport := get_tree().root.get_viewport()
var main_camera := main_viewport.get_camera_3d()
global_transform = main_camera.global_transform

@ -0,0 +1,8 @@
extends RigidBody3D
class_name Item
const GRID_SIZE := 0.05
@export var size := Vector3i(1, 4, 1)
@onready var mesh := $MeshInstance3D as MeshInstance3D

@ -0,0 +1,7 @@
class_name PhysicsLayer
enum {
WORLD = 1 << 0,
PLAYER = 1 << 1,
ITEM = 1 << 2,
}

@ -0,0 +1,119 @@
extends Node3D
@export var camera : Camera3D
@export var pickup_distance := 2.0
@onready var world := find_parent("World") as Node3D
var current_item : Item
var is_current_item_held := false
var placement_preview : MeshInstance3D
# TODO: Support holding multiple items.
# TODO: Allow rotation of the item while held.
func _ready() -> void:
pass
func _input(event: InputEvent) -> void:
ensure_current_item_valid()
if !current_item: return
if event.is_action_pressed("interact_pickup"):
if !is_current_item_held:
is_current_item_held = true
# Create a clone of the item's mesh and use it as a placement preview.
placement_preview = current_item.get_node("MeshInstance3D").duplicate(0) as MeshInstance3D
placement_preview.name = "PlacementPreview"
placement_preview.layers = RenderLayer.OUTLINE
placement_preview.top_level = true
add_child(placement_preview)
# Parent item to the pickup controller.
var prev_rot := current_item.global_rotation
current_item.get_parent().remove_child(current_item)
add_child(current_item)
current_item.mesh.layers &= ~RenderLayer.OUTLINE
current_item.position = Vector3.ZERO
current_item.global_rotation = prev_rot
current_item.freeze = true
get_viewport().set_input_as_handled()
elif event.is_action_pressed("interact_place"):
if is_current_item_held:
is_current_item_held = false
# Parent item back to the world.
remove_child(current_item)
world.add_child(current_item)
# Set item's transform to where the placement preview is.
# TODO: If placement preview is not valid, don't allow placing the item.
current_item.global_transform = placement_preview.global_transform
current_item.freeze = false
placement_preview.queue_free()
placement_preview = null
get_viewport().set_input_as_handled()
func _process(_delta: float) -> void:
pass
func _physics_process(_delta: float) -> void:
ensure_current_item_valid()
if is_current_item_held:
# Cast a ray but exlude the current item from being hit.
var ray_result := ray_to_mouse_cursor([ current_item ])
if ray_result:
var pos := ray_result.position as Vector3
var normal := ray_result.normal as Vector3
# Snap rotation to nearest axis.
var global_rot = current_item.global_rotation
global_rot.x = snappedf(global_rot.x, TAU / 4)
global_rot.y = snappedf(global_rot.y, TAU / 4)
global_rot.z = snappedf(global_rot.z, TAU / 4)
placement_preview.global_rotation = global_rot
# Snap the position to the grid.
var half_size := current_item.size * Item.GRID_SIZE / 2
pos += half_size * (normal * placement_preview.global_transform.basis)
pos = pos.snapped(Item.GRID_SIZE * Vector3.ONE)
placement_preview.global_position = pos
else:
# Remove the outline from the previously looked-at item.
if current_item:
current_item.mesh.layers &= ~RenderLayer.OUTLINE
var ray_result := ray_to_mouse_cursor()
# If the ray hit anything and the object hit is an item, set it as current.
if ray_result:
current_item = ray_result.collider as Item
if current_item:
current_item.mesh.layers |= RenderLayer.OUTLINE
func ensure_current_item_valid() -> void:
if !current_item: return
if !is_instance_valid(current_item):
current_item = null
is_current_item_held = false
placement_preview.queue_free()
func ray_to_mouse_cursor(exclude: Array[CollisionObject3D] = []) -> Dictionary:
const COLLIDE_WITH := PhysicsLayer.WORLD | PhysicsLayer.ITEM
var map_to_rid := func(obj: CollisionObject3D) -> RID: return obj.get_rid()
var mouse := get_viewport().get_mouse_position()
var from := camera.project_ray_origin(mouse)
var to := from + camera.project_ray_normal(mouse) * pickup_distance
var query := PhysicsRayQueryParameters3D.create(from, to, COLLIDE_WITH, exclude.map(map_to_rid))
return get_world_3d().direct_space_state.intersect_ray(query)

@ -0,0 +1,123 @@
class_name Player
extends CharacterBody3D
@export var camera: Camera
@export_group("Movement")
@export var move_accel := 6.0
@export var move_max_speed := 4.0
@export var friction_floor := 12.0
@export var friction_air := 2.0
@export var gravity := -12.0
@export_group("Jumping")
@export var jump_velocity := 5.0
@export var jump_early_time := 0.0 # Time (in seconds) after pressing the jump button a jump may occur late.
@export var jump_coyote_time := 0.0 # Time (in seconds) after leaving a jumpable surface when a jump may still occur.
enum MovementModeEnum { DEFAULT, FLYING, NO_CLIP }
var movement_mode := MovementModeEnum.DEFAULT
var is_moving := false
var is_sprinting := false
var time_since_jump_pressed := INF
var time_since_on_floor := INF
func _input(event: InputEvent) -> void:
# Inputs that are valid when the game is focused.
# ===============================================
if event.is_action("move_sprint"):
is_sprinting = event.is_pressed()
get_viewport().set_input_as_handled()
if event.is_action_pressed("move_jump"):
time_since_jump_pressed = 0
get_viewport().set_input_as_handled()
# Cycle movement mode between default, flying and noclip.
if event.is_action_pressed("cycle_movement_mode"):
if (+movement_mode > MovementModeEnum.NO_CLIP):
movement_mode = MovementModeEnum.DEFAULT;
get_viewport().set_input_as_handled()
# Inputs that are valid only when the mouse is captured.
# ======================================================
if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
pass
func _physics_process(delta: float) -> void:
match movement_mode:
MovementModeEnum.DEFAULT:
process_movement_default(delta)
MovementModeEnum.FLYING:
process_movement_flying(delta, false)
MovementModeEnum.NO_CLIP:
process_movement_flying(delta, true)
func process_movement_default(delta: float) -> void:
var input := Input.get_vector("move_strafe_left", "move_strafe_right", "move_forward", "move_backward")
velocity.y += gravity * delta
var hor_vel := Vector3(velocity.x, 0, velocity.z)
var target := basis.rotated(Vector3.UP, camera.current_yaw) * Vector3(input.x, 0, input.y) * move_max_speed
is_moving = target.dot(hor_vel) > 0
var accel: float
if is_moving: accel = move_accel
elif is_on_floor(): accel = friction_floor
else: accel = friction_air
if is_sprinting:
target *= 5
accel *= 5
hor_vel = hor_vel.lerp(target, accel * delta)
velocity = Vector3(hor_vel.x, velocity.y, hor_vel.z)
# TODO: Check if this still applies.
# Sometimes, when pushing into a wall, jumping wasn't working.
# Possibly due to `IsOnFloor` returning `false` for some reason.
# The `JumpEarlyTime` feature seems to avoid this issue, thankfully.
if is_on_floor(): time_since_on_floor = 0
else: time_since_on_floor += delta
if time_since_jump_pressed <= jump_early_time && time_since_on_floor <= jump_coyote_time:
velocity.y = jump_velocity
time_since_jump_pressed = INF
time_since_on_floor = INF
move_and_slide()
func process_movement_flying(delta: float, no_clip: bool) -> void:
var input := Vector3(
Input.get_axis("move_strafe_left", "move_strafe_right"),
Input.get_axis("move_downward", "move_upward"),
Input.get_axis("move_forward", "move_backward"))
velocity *= 1 - friction_air * delta;
var target := camera.global_transform.basis.get_rotation_quaternion() * input * move_max_speed
is_moving = target.dot(velocity) > 0
var accel: float
if is_moving: accel = move_accel
else: accel = friction_air
target *= 4
accel *= 4
if is_sprinting:
target *= 5
accel *= 5
velocity = velocity.lerp(target, accel * delta)
if no_clip: translate(velocity * delta)
else: move_and_slide()

@ -0,0 +1,6 @@
class_name RenderLayer
enum {
DEFAULT = 1 << 0,
OUTLINE = 1 << 1,
}

@ -0,0 +1,24 @@
// Adapted from Leafshade Interactive's outline shader
// explained and shown in https://youtu.be/yh1Kdr37RmI.
shader_type canvas_item;
uniform vec4 line_color : source_color = vec4(1.0);
uniform float line_thickness : hint_range(0.0, 10.0) = 2.0;
void fragment() {
vec2 size = TEXTURE_PIXEL_SIZE * line_thickness;
float outline =
texture(TEXTURE, UV + vec2( size.x, 0)).a +
texture(TEXTURE, UV + vec2(-size.x, 0)).a +
texture(TEXTURE, UV + vec2(0, size.y)).a +
texture(TEXTURE, UV + vec2(0, -size.y)).a +
texture(TEXTURE, UV + vec2( size.x, size.y)).a +
texture(TEXTURE, UV + vec2(-size.x, size.y)).a +
texture(TEXTURE, UV + vec2(-size.x, -size.y)).a +
texture(TEXTURE, UV + vec2( size.x, -size.y)).a;
float alpha = min(outline, 1.0) - texture(TEXTURE, UV).a;
COLOR = vec4(line_color.rgb, line_color.a * alpha);
}
Loading…
Cancel
Save