Compare commits
5
Commits
8c6286a95b
...
c95233b9f9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c95233b9f9 | ||
|
|
470e70d51b | ||
|
|
926b703ace | ||
|
|
c702d7b2ab | ||
|
|
ef6b34bcbd |
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<Project Sdk="Godot.NET.Sdk/4.7.1">
|
<Project Sdk="Godot.NET.Sdk/4.7.1">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||||
<RootNamespace>Game</RootNamespace>
|
<RootNamespace>Game</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
+29
-1
@@ -11,7 +11,7 @@ config_version=5
|
|||||||
[application]
|
[application]
|
||||||
|
|
||||||
config/name="Night Shift"
|
config/name="Night Shift"
|
||||||
run/main_scene="uid://p766ipnev5hb"
|
run/main_scene="uid://c44pljehe0qun"
|
||||||
config/features=PackedStringArray("4.7", "C#", "Forward Plus")
|
config/features=PackedStringArray("4.7", "C#", "Forward Plus")
|
||||||
config/icon="res://icon.svg"
|
config/icon="res://icon.svg"
|
||||||
|
|
||||||
@@ -29,6 +29,34 @@ project/assembly_name="NightShift"
|
|||||||
naming/scene_name_casing=1
|
naming/scene_name_casing=1
|
||||||
naming/script_name_casing=1
|
naming/script_name_casing=1
|
||||||
|
|
||||||
|
[input]
|
||||||
|
|
||||||
|
move_forward={
|
||||||
|
"deadzone": 0.2,
|
||||||
|
"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,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
move_back={
|
||||||
|
"deadzone": 0.2,
|
||||||
|
"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,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
move_left={
|
||||||
|
"deadzone": 0.2,
|
||||||
|
"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,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
move_right={
|
||||||
|
"deadzone": 0.2,
|
||||||
|
"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,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
jump={
|
||||||
|
"deadzone": 0.2,
|
||||||
|
"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,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
[physics]
|
[physics]
|
||||||
|
|
||||||
3d/physics_engine="Jolt Physics"
|
3d/physics_engine="Jolt Physics"
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
using Godot;
|
|
||||||
|
|
||||||
namespace Game.Bootstrap;
|
|
||||||
|
|
||||||
public partial class GameBootstrap : Node
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
using Game.Features.Player.Domain;
|
|
||||||
using Godot;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Game.Features.Player.Godot;
|
|
||||||
|
|
||||||
public partial class PlayerController : Node3D
|
|
||||||
{
|
|
||||||
[Export] public CharacterBody3D Body;
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
using Game.Shared.Domain;
|
using Game.Shared.Domain;
|
||||||
|
|
||||||
namespace Game.Features.Player.Domain;
|
namespace Game.Features.Players.Domain;
|
||||||
|
|
||||||
public class PlayerData
|
public class Player
|
||||||
{
|
{
|
||||||
public EntityId Id { get; private set; }
|
public EntityId Id { get; private set; }
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Game.Features.Players.Domain;
|
||||||
|
|
||||||
|
public sealed class PlayerLook(float mouseSensitivity, float maxLookAngleDegrees)
|
||||||
|
{
|
||||||
|
public float MouseSensitivity { get; } = mouseSensitivity;
|
||||||
|
public float MaxLookAngleDegrees { get; } = maxLookAngleDegrees;
|
||||||
|
public float Pitch { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Yaw rotation (radians) to apply for this mouse movement.
|
||||||
|
/// </summary>
|
||||||
|
public float CalculateYawDelta(float mouseDeltaX) => -mouseDeltaX * MouseSensitivity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates <see cref="Pitch"/> for this mouse movement.
|
||||||
|
/// </summary>
|
||||||
|
public void UpdatePitch(float mouseDeltaY)
|
||||||
|
{
|
||||||
|
float pitch = Pitch - mouseDeltaY * MouseSensitivity;
|
||||||
|
Pitch = Mathf.Clamp(pitch, Mathf.DegToRad(-MaxLookAngleDegrees), Mathf.DegToRad(MaxLookAngleDegrees));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dke8w2hp71p1g
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Game.Features.Players.Domain;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Per-frame input for movement calculation.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Right">World-space right direction of the body.</param>
|
||||||
|
/// <param name="Back">World-space back direction of the body.</param>
|
||||||
|
/// <param name="Direction">Movement input, X = left/right, Y = forward/back.</param>
|
||||||
|
/// <param name="IsOnFloor">Whether the body is currently grounded.</param>
|
||||||
|
/// <param name="JumpPressed">Whether jump was just pressed this frame.</param>
|
||||||
|
/// <param name="Delta">Physics frame delta time in seconds.</param>
|
||||||
|
public readonly record struct MovementInput(
|
||||||
|
Vector3 Right,
|
||||||
|
Vector3 Back,
|
||||||
|
Vector2 Direction,
|
||||||
|
bool IsOnFloor,
|
||||||
|
bool JumpPressed,
|
||||||
|
float Delta
|
||||||
|
);
|
||||||
|
|
||||||
|
public sealed class PlayerMovement(float moveSpeed, float jumpVelocity, float gravity)
|
||||||
|
{
|
||||||
|
public float MoveSpeed { get; } = moveSpeed;
|
||||||
|
public float JumpVelocity { get; } = jumpVelocity;
|
||||||
|
public float Gravity { get; } = gravity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates next-frame velocity from input and ground state.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="currentVelocity">Body's velocity before this frame.</param>
|
||||||
|
/// <param name="input">Per-frame movement input.</param>
|
||||||
|
public Vector3 CalculateVelocity(Vector3 currentVelocity, MovementInput input)
|
||||||
|
{
|
||||||
|
Vector3 velocity = currentVelocity;
|
||||||
|
|
||||||
|
if (!input.IsOnFloor)
|
||||||
|
{
|
||||||
|
velocity.Y -= Gravity * input.Delta;
|
||||||
|
}
|
||||||
|
else if (input.JumpPressed)
|
||||||
|
{
|
||||||
|
velocity.Y = JumpVelocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 moveDirection = input.Right * input.Direction.X + input.Back * input.Direction.Y;
|
||||||
|
Vector3 moveDirectionNormalized = moveDirection.Normalized();
|
||||||
|
velocity.X = moveDirectionNormalized.X * MoveSpeed;
|
||||||
|
velocity.Z = moveDirectionNormalized.Z * MoveSpeed;
|
||||||
|
|
||||||
|
return velocity;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://chnyf8efw3xw2
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Game.Features.Players.Domain;
|
||||||
|
using Game.Godot.Extensions;
|
||||||
|
using Game.Godot.Mappings;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Game.Features.Players.Godot;
|
||||||
|
|
||||||
|
public partial class PlayerController : Node3D
|
||||||
|
{
|
||||||
|
[Export] public CharacterBody3D Body;
|
||||||
|
[Export] public Camera3D Camera;
|
||||||
|
|
||||||
|
[Export] public float MoveSpeed = 4.0f;
|
||||||
|
[Export] public float JumpVelocity = 4.5f;
|
||||||
|
[Export] public float MouseSensitivity = 0.003f;
|
||||||
|
[Export] public float MaxLookAngleDegrees = 89.0f;
|
||||||
|
|
||||||
|
private PlayerMovement _movement;
|
||||||
|
private PlayerLook _look;
|
||||||
|
|
||||||
|
private static bool IsMouseCaptured() => Input.MouseMode == Input.MouseModeEnum.Captured;
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
this.AssertRequiredExports();
|
||||||
|
|
||||||
|
float gravity = (float)ProjectSettings.GetSetting(ProjectSetting.Gravity.SettingPath());
|
||||||
|
_movement = new PlayerMovement(MoveSpeed, JumpVelocity, gravity);
|
||||||
|
_look = new PlayerLook(MouseSensitivity, MaxLookAngleDegrees);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _UnhandledInput(InputEvent @event)
|
||||||
|
{
|
||||||
|
if (@event is not InputEventMouseMotion mouseMotion || !IsMouseCaptured()) return;
|
||||||
|
|
||||||
|
Body.RotateY(_look.CalculateYawDelta(mouseMotion.Relative.X));
|
||||||
|
_look.UpdatePitch(mouseMotion.Relative.Y);
|
||||||
|
Camera.Rotation = new Vector3(_look.Pitch, 0f, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _PhysicsProcess(double delta)
|
||||||
|
{
|
||||||
|
Vector2 inputDirection = Input.GetVector(
|
||||||
|
InputAction.MoveLeft.ActionName(),
|
||||||
|
InputAction.MoveRight.ActionName(),
|
||||||
|
InputAction.MoveForward.ActionName(),
|
||||||
|
InputAction.MoveBack.ActionName()
|
||||||
|
);
|
||||||
|
|
||||||
|
MovementInput input = new(
|
||||||
|
Right: Body.Transform.Basis.X,
|
||||||
|
Back: Body.Transform.Basis.Z,
|
||||||
|
Direction: inputDirection,
|
||||||
|
IsOnFloor: Body.IsOnFloor(),
|
||||||
|
JumpPressed: Input.IsActionJustPressed(InputAction.Jump.ActionName()),
|
||||||
|
Delta: (float)delta
|
||||||
|
);
|
||||||
|
|
||||||
|
Body.Velocity = _movement.CalculateVelocity(Body.Velocity, input);
|
||||||
|
Body.MoveAndSlide();
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
-16
@@ -1,8 +1,11 @@
|
|||||||
[gd_scene format=4 uid="uid://bka1dmgdi5btd"]
|
[gd_scene format=4 uid="uid://bka1dmgdi5btd"]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://mjm7ghkibnxa" path="res://src/Features/Player/Godot/PlayerController.cs" id="1_gkqfc"]
|
[ext_resource type="Script" uid="uid://mjm7ghkibnxa" path="res://src/Features/Players/Godot/PlayerController.cs" id="1_gkqfc"]
|
||||||
[ext_resource type="Texture2D" uid="uid://bymgknsy145ap" path="res://assets/models/simple-character-psx_character_256.png" id="2_tduai"]
|
[ext_resource type="Texture2D" uid="uid://bymgknsy145ap" path="res://assets/models/simple-character-psx_character_256.png" id="2_tduai"]
|
||||||
|
|
||||||
|
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_130ks"]
|
||||||
|
radius = 0.25
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_sgsg4"]
|
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_sgsg4"]
|
||||||
resource_name = "character_256"
|
resource_name = "character_256"
|
||||||
cull_mode = 2
|
cull_mode = 2
|
||||||
@@ -287,16 +290,21 @@ _surfaces = [{
|
|||||||
}]
|
}]
|
||||||
blend_shape_mode = 0
|
blend_shape_mode = 0
|
||||||
|
|
||||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_130ks"]
|
[node name="Player" type="Node3D" unique_id=26823676 node_paths=PackedStringArray("Body", "Camera")]
|
||||||
radius = 0.25
|
|
||||||
|
|
||||||
[node name="Player" type="Node3D" unique_id=26823676 node_paths=PackedStringArray("characterBody3D")]
|
|
||||||
script = ExtResource("1_gkqfc")
|
script = ExtResource("1_gkqfc")
|
||||||
characterBody3D = NodePath("Mesh/CharacterBody3D")
|
Body = NodePath("CharacterBody3D")
|
||||||
|
Camera = NodePath("CharacterBody3D/Camera3D")
|
||||||
|
|
||||||
[node name="Mesh" type="Node3D" parent="." unique_id=388629767]
|
[node name="CharacterBody3D" type="CharacterBody3D" parent="." unique_id=1327672780]
|
||||||
|
|
||||||
[node name="Skeleton3D" type="Skeleton3D" parent="Mesh" unique_id=183161254]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="CharacterBody3D" unique_id=1006347064]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
||||||
|
shape = SubResource("CapsuleShape3D_130ks")
|
||||||
|
|
||||||
|
[node name="Mesh" type="Node3D" parent="CharacterBody3D" unique_id=388629767]
|
||||||
|
transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, 0, 0, 0)
|
||||||
|
|
||||||
|
[node name="Skeleton3D" type="Skeleton3D" parent="CharacterBody3D/Mesh" unique_id=183161254]
|
||||||
transform = Transform3D(0.1225099, 0, 0, 0, 0.1225099, 0, 0, 0, 0.1225099, 0, 0, 0)
|
transform = Transform3D(0.1225099, 0, 0, 0, 0.1225099, 0, 0, 0, 0.1225099, 0, 0, 0)
|
||||||
bones/0/name = "spine"
|
bones/0/name = "spine"
|
||||||
bones/0/parent = -1
|
bones/0/parent = -1
|
||||||
@@ -747,23 +755,22 @@ bones/63/position = Vector3(0.24846391, -0.054724697, 0.53722215)
|
|||||||
bones/63/rotation = Quaternion(-0.689515, 0.6895178, -0.15673877, -0.15673946)
|
bones/63/rotation = Quaternion(-0.689515, 0.6895178, -0.15673877, -0.15673946)
|
||||||
bones/63/scale = Vector3(1, 1, 1)
|
bones/63/scale = Vector3(1, 1, 1)
|
||||||
|
|
||||||
[node name="arms" type="MeshInstance3D" parent="Mesh/Skeleton3D" unique_id=1995581269]
|
[node name="arms" type="MeshInstance3D" parent="CharacterBody3D/Mesh/Skeleton3D" unique_id=1995581269]
|
||||||
mesh = SubResource("ArrayMesh_wr7hk")
|
mesh = SubResource("ArrayMesh_wr7hk")
|
||||||
skin = SubResource("Skin_dmu40")
|
skin = SubResource("Skin_dmu40")
|
||||||
skeleton = NodePath("..")
|
skeleton = NodePath("..")
|
||||||
|
|
||||||
[node name="body" type="MeshInstance3D" parent="Mesh/Skeleton3D" unique_id=2033799835]
|
[node name="body" type="MeshInstance3D" parent="CharacterBody3D/Mesh/Skeleton3D" unique_id=2033799835]
|
||||||
mesh = SubResource("ArrayMesh_5ti08")
|
mesh = SubResource("ArrayMesh_5ti08")
|
||||||
skin = SubResource("Skin_dmu40")
|
skin = SubResource("Skin_dmu40")
|
||||||
skeleton = NodePath("..")
|
skeleton = NodePath("..")
|
||||||
|
|
||||||
[node name="head" type="MeshInstance3D" parent="Mesh/Skeleton3D" unique_id=1273139092]
|
[node name="head" type="MeshInstance3D" parent="CharacterBody3D/Mesh/Skeleton3D" unique_id=1273139092]
|
||||||
|
layers = 2
|
||||||
mesh = SubResource("ArrayMesh_31tkm")
|
mesh = SubResource("ArrayMesh_31tkm")
|
||||||
skin = SubResource("Skin_dmu40")
|
skin = SubResource("Skin_dmu40")
|
||||||
skeleton = NodePath("..")
|
skeleton = NodePath("..")
|
||||||
|
|
||||||
[node name="CharacterBody3D" type="CharacterBody3D" parent="Mesh" unique_id=1327672780]
|
[node name="Camera3D" type="Camera3D" parent="CharacterBody3D" unique_id=718409984]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.817, 0)
|
||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="Mesh/CharacterBody3D" unique_id=1006347064]
|
cull_mask = 1048573
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
|
||||||
shape = SubResource("CapsuleShape3D_130ks")
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using Game.Godot.Mappings;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Game.Godot.Bootstrap;
|
||||||
|
|
||||||
|
public partial class GameBootstrap : Node
|
||||||
|
{
|
||||||
|
private static void ToggleMouseCapture()
|
||||||
|
{
|
||||||
|
Input.MouseMode = Input.MouseMode == Input.MouseModeEnum.Captured
|
||||||
|
? Input.MouseModeEnum.Visible
|
||||||
|
: Input.MouseModeEnum.Captured;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
Input.MouseMode = Input.MouseModeEnum.Captured;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _UnhandledInput(InputEvent @event)
|
||||||
|
{
|
||||||
|
if (@event.IsActionPressed(InputAction.Cancel.ActionName()))
|
||||||
|
{
|
||||||
|
ToggleMouseCapture();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Game.Godot.Exceptions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Thrown when a node's [Export]ed Node reference is unassigned.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class MissingRequiredExportException(string nodeName, IReadOnlyList<string> missingExports)
|
||||||
|
: Exception(BuildMessage(nodeName, missingExports))
|
||||||
|
{
|
||||||
|
public string NodeName { get; } = nodeName;
|
||||||
|
public IReadOnlyList<string> MissingExports { get; } = missingExports;
|
||||||
|
|
||||||
|
private static string BuildMessage(string nodeName, IReadOnlyList<string> missingExports)
|
||||||
|
=> $"{nodeName}: missing required exports:\n- {string.Join("\n- ", missingExports)}\nAssign them in the editor Inspector.";
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://diqdaysdyp3jf
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Game.Godot.Exceptions;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Game.Godot.Extensions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates that a node's [Export] fields/properties pointing to other nodes are actually assigned.
|
||||||
|
/// </summary>
|
||||||
|
public static class NodeExportValidationExtensions
|
||||||
|
{
|
||||||
|
// Tells which members to include when scanning for [Export] fields/properties
|
||||||
|
private const BindingFlags MemberFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
|
||||||
|
|
||||||
|
private static void DisableProcessing(Node node)
|
||||||
|
{
|
||||||
|
node.SetProcess(false);
|
||||||
|
node.SetPhysicsProcess(false);
|
||||||
|
node.SetProcessInput(false);
|
||||||
|
node.SetProcessUnhandledInput(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string[] GetMissingExportWarnings(Node node)
|
||||||
|
{
|
||||||
|
Type nodeType = node.GetType();
|
||||||
|
|
||||||
|
IEnumerable<string> missingFields = nodeType
|
||||||
|
.GetFields(MemberFlags)
|
||||||
|
.Where(field => field.GetCustomAttribute<ExportAttribute>() is not null)
|
||||||
|
.Where(field => typeof(Node).IsAssignableFrom(field.FieldType))
|
||||||
|
.Where(field => field.GetValue(node) is null)
|
||||||
|
.Select(field => $"{field.Name} ({field.FieldType.Name}) is not assigned.");
|
||||||
|
|
||||||
|
IEnumerable<string> missingProperties = nodeType
|
||||||
|
.GetProperties(MemberFlags)
|
||||||
|
.Where(property => property.GetCustomAttribute<ExportAttribute>() is not null)
|
||||||
|
.Where(property => typeof(Node).IsAssignableFrom(property.PropertyType))
|
||||||
|
.Where(property => property.GetValue(node) is null)
|
||||||
|
.Select(property => $"{property.Name} ({property.PropertyType.Name}) is not assigned.");
|
||||||
|
|
||||||
|
return missingFields.Concat(missingProperties).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Throws if any [Export]ed Node field on this node is unassigned.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">Node to check.</param>
|
||||||
|
/// <exception cref="MissingRequiredExportException">One or more exports are missing.</exception>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// public override void _Ready()
|
||||||
|
/// {
|
||||||
|
/// this.AssertRequiredExports();
|
||||||
|
/// // rest of initialization
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
public static void AssertRequiredExports(this Node node)
|
||||||
|
{
|
||||||
|
List<string> missing = GetMissingExportWarnings(node).ToList();
|
||||||
|
if (missing.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DisableProcessing(node);
|
||||||
|
|
||||||
|
throw new MissingRequiredExportException(node.Name, missing);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://d3twapljpp1ln
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Game.Godot.Mappings;
|
||||||
|
|
||||||
|
public enum InputAction
|
||||||
|
{
|
||||||
|
MoveLeft,
|
||||||
|
MoveRight,
|
||||||
|
MoveForward,
|
||||||
|
MoveBack,
|
||||||
|
Jump,
|
||||||
|
Cancel
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class InputActionExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Godot Input Map action name for this action.
|
||||||
|
/// </summary>
|
||||||
|
public static string ActionName(this InputAction action) => action switch
|
||||||
|
{
|
||||||
|
InputAction.MoveLeft => "move_left",
|
||||||
|
InputAction.MoveRight => "move_right",
|
||||||
|
InputAction.MoveForward => "move_forward",
|
||||||
|
InputAction.MoveBack => "move_back",
|
||||||
|
InputAction.Jump => "jump",
|
||||||
|
InputAction.Cancel => "ui_cancel",
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(action), action, null)
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bwrbj8sdi6tqe
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Game.Godot.Mappings;
|
||||||
|
|
||||||
|
public enum ProjectSetting
|
||||||
|
{
|
||||||
|
Gravity
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ProjectSettingExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ProjectSettings key for this setting.
|
||||||
|
/// </summary>
|
||||||
|
public static string SettingPath(this ProjectSetting setting) => setting switch
|
||||||
|
{
|
||||||
|
ProjectSetting.Gravity => "physics/3d/default_gravity",
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(setting), setting, null)
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://c6kb654ebimt6
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
[ext_resource type="Texture2D" uid="uid://b5i827vmueyei" path="res://assets/textures/skyboxes/skybox_emx_19.png" id="1_g4n7o"]
|
[ext_resource type="Texture2D" uid="uid://b5i827vmueyei" path="res://assets/textures/skyboxes/skybox_emx_19.png" id="1_g4n7o"]
|
||||||
[ext_resource type="PackedScene" uid="uid://curuqmbndymi6" path="res://src/Scenes/FloorCeilingHr42.tscn" id="2_1kpcd"]
|
[ext_resource type="PackedScene" uid="uid://curuqmbndymi6" path="res://src/Scenes/FloorCeilingHr42.tscn" id="2_1kpcd"]
|
||||||
[ext_resource type="PackedScene" uid="uid://bka1dmgdi5btd" path="res://src/Features/Player/Godot/PlayerNode.tscn" id="2_mvtit"]
|
[ext_resource type="PackedScene" uid="uid://bka1dmgdi5btd" path="res://src/Features/Player/Godot/PlayerNode.tscn" id="2_mvtit"]
|
||||||
|
[ext_resource type="Script" uid="uid://drctu5y21ey2y" path="res://src/Godot/Bootstrap/GameBootstrap.cs" id="3_bootstrap"]
|
||||||
|
|
||||||
[sub_resource type="PanoramaSkyMaterial" id="PanoramaSkyMaterial_epykj"]
|
[sub_resource type="PanoramaSkyMaterial" id="PanoramaSkyMaterial_epykj"]
|
||||||
panorama = ExtResource("1_g4n7o")
|
panorama = ExtResource("1_g4n7o")
|
||||||
@@ -16,6 +17,7 @@ sky = SubResource("Sky_1kpcd")
|
|||||||
glow_enabled = true
|
glow_enabled = true
|
||||||
|
|
||||||
[node name="GameWorld" type="Node" unique_id=222644234]
|
[node name="GameWorld" type="Node" unique_id=222644234]
|
||||||
|
script = ExtResource("3_bootstrap")
|
||||||
|
|
||||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1669211403]
|
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1669211403]
|
||||||
transform = Transform3D(-0.8660254, -0.43301278, 0.25, 0, 0.49999997, 0.86602545, -0.50000006, 0.75, -0.43301266, 0, 0, 0)
|
transform = Transform3D(-0.8660254, -0.43301278, 0.25, 0, 0.49999997, 0.86602545, -0.50000006, 0.75, -0.43301266, 0, 0, 0)
|
||||||
|
|||||||
Reference in New Issue
Block a user