Add initial resource and presenter classes for game entities and effects

This commit is contained in:
2025-10-13 12:11:50 +02:00
commit ad3e631d8c
75 changed files with 1423 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
using System.Collections.Generic;
using GameCore.ECS;
using Godot;
namespace CryptonymThunder.Code.Autoloads;
public partial class PresenterRegistry : Node
{
private readonly Dictionary<ulong, Entity> _instanceIdToEntity = new();
public void RegisterPresenter(Node presenterNode, Entity entity)
{
_instanceIdToEntity[presenterNode.GetInstanceId()] = entity;
}
public void UnregisterPresenter(Node presenterNode)
{
_instanceIdToEntity.Remove(presenterNode.GetInstanceId());
}
public bool TryGetEntity(ulong instanceId, out Entity entity)
{
return _instanceIdToEntity.TryGetValue(instanceId, out entity);
}
}

View File

@@ -0,0 +1 @@
uid://5twln2ckxjj8

View File

@@ -0,0 +1,17 @@
using GameCoreMath = GameCore.Math.Vector3;
using GodotMath = Godot.Vector3;
namespace CryptonymThunder.Code.Extensions;
public static class Vector3Extensions
{
public static GodotMath ToGodot(this GameCoreMath v)
{
return new GodotMath(v.X, v.Y, v.Z);
}
public static GameCoreMath ToGameCore(this GodotMath v)
{
return new GameCoreMath(v.X, v.Y, v.Z);
}
}

View File

@@ -0,0 +1 @@
uid://dqwjwnmn7x6ny

View File

@@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using System.Linq;
using CryptonymThunder.Code.Resources;
using GameCore.Attributes;
using GameCore.Combat;
using GameCore.ECS.Interfaces;
using GameCore.Input;
using GameCore.Movement;
using GameCore.Physics;
using GameCore.Player;
using Godot;
namespace CryptonymThunder.Code.Factories;
public class ComponentFactory
{
private readonly Dictionary<Type, Func<Resource, IComponent>> _factories = new();
private readonly EffectFactory _effectFactory = new();
public ComponentFactory()
{
Register<AttributeComponentResource>(CreateAttributeComponent);
Register<WeaponComponentResource>(CreateWeaponComponent);
Register<ProjectileComponentResource>(CreateProjectileComponent);
Register<PositionComponentResource>(_ => new PositionComponent());
Register<VelocityComponentResource>(_ => new VelocityComponent());
Register<InputStateComponentResource>(_ => new InputStateComponent());
Register<PlayerComponentResource>(_ => new PlayerComponent());
Register<RotationComponentResource>(_ => new RotationComponent());
Register<CharacterStateComponentResource>(_ => new CharacterStateComponent());
}
public IComponent Create(Resource resource)
{
return _factories.TryGetValue(resource.GetType(), out var factory)
? factory(resource)
: null;
}
private void Register<TResource>(Func<TResource, IComponent> factory) where TResource : Resource
{
_factories[typeof(TResource)] = res => factory((TResource)res);
}
private static AttributeComponent CreateAttributeComponent(AttributeComponentResource resource)
{
var component = new AttributeComponent();
foreach (var (attribute, value) in resource.BaseValues)
{
component.SetBaseValue(attribute, value);
}
return component;
}
private WeaponComponent CreateWeaponComponent(WeaponComponentResource resource)
{
if (resource.WeaponData == null) return null;
var weaponData = resource.WeaponData;
var onFireEffects = weaponData.OnFireEffects.Select(_effectFactory.Create).ToList();
var onHitEffects = weaponData.OnHitEffects.Select(_effectFactory.Create).ToList();
return new WeaponComponent
{
FireRate = resource.WeaponData.FireRate,
OnFireEffects = onFireEffects,
OnHitEffects = onHitEffects,
};
}
private ProjectileComponent CreateProjectileComponent(ProjectileComponentResource resource)
{
return new ProjectileComponent
{
Lifetime = resource.Lifetime,
};
}
}

View File

@@ -0,0 +1 @@
uid://cwf0khkssee8b

View File

@@ -0,0 +1,24 @@
using System;
using System.Runtime.InteropServices.ComTypes;
using CryptonymThunder.Code.Resources;
using CryptonymThunder.Code.Resources.Effects;
using GameCore.Combat.Effects;
using GameCore.Combat.Interfaces;
namespace CryptonymThunder.Code.Factories;
public class EffectFactory
{
public IEffect Create(EffectResource resource)
{
return resource switch
{
FireProjectileEffectResource fire =>
new BulkProjectileEffect(fire.ProjectileArchetypeId, fire.Count, fire.SpreadAngle, fire.ProjectileSpeed),
DamageEffectResource damage => new DamageEffect(damage.Amount),
HitscanEffectResource hitscan => new HitscanEffect(hitscan.Range),
_ => throw new ArgumentOutOfRangeException(nameof(resource),
$"Effect type {resource.GetType().Name} not recognized")
};
}
}

View File

@@ -0,0 +1 @@
uid://d1n7re7y0g6q4

View File

@@ -0,0 +1,99 @@
using System.Collections.Generic;
using CryptonymThunder.Code.Autoloads;
using CryptonymThunder.Code.Resources;
using GameCore.ECS;
using GameCore.ECS.Interfaces;
using GameCore.Movement;
using GameCore.Physics;
using Godot;
using Godot.Collections;
namespace CryptonymThunder.Code.Factories;
public class PresenterFactory(
World world,
ComponentFactory componentFactory,
PresenterRegistry presenterRegistry,
Node sceneRoot)
{
private readonly World _world = world;
private readonly ComponentFactory _componentFactory = componentFactory;
private PresenterRegistry _presenterRegistry = presenterRegistry;
private readonly Node _sceneRoot = sceneRoot;
public PresenterData CreateEntityFromArchetype(EntityArchetype archetype, GameCore.Math.Vector3? position = null,
GameCore.Math.Vector3? rotation = null, GameCore.Math.Vector3? initialVelocity = null)
{
var entity = _world.CreateEntity();
foreach (var resource in archetype.ComponentResources)
{
var component = _componentFactory.Create(resource);
if (component != null) _world.AddComponent(entity, component);
}
var presenterNode = archetype.Scene.Instantiate<Node>();
if (presenterNode is not IEntityPresenter presenter)
{
GD.PrintErr($"Archetype scene '{archetype.ResourcePath}' root does not implement IEntityPresenter!");
presenterNode.QueueFree();
return default;
}
presenter.CoreEntity = entity;
var components = InitializePresenterComponents(presenterNode, entity);
if (position.HasValue && _world.GetComponent<PositionComponent>(entity) is { } posComp)
posComp.Position = position.Value;
if (rotation.HasValue && _world.GetComponent<RotationComponent>(entity) is { } rotComp)
rotComp.Rotation = rotation.Value;
if (initialVelocity.HasValue && _world.GetComponent<VelocityComponent>(entity) is { } velComp)
velComp.DesiredVelocity = initialVelocity.Value;
_sceneRoot.AddChild(presenterNode);
return new PresenterData(entity, presenter, components);
}
public PresenterData RegisterSceneEntity(Node presenterNode, Array<Resource> componentResources)
{
var entity = _world.CreateEntity();
foreach (var resource in componentResources)
{
var component = _componentFactory.Create(resource);
if (component != null) _world.AddComponent(entity, component);
}
if (presenterNode is not IEntityPresenter presenter)
{
GD.PrintErr($"Scene node '{presenterNode.Name}' does not implement IEntityPresenter!");
return default;
}
presenter.CoreEntity = entity;
var components = InitializePresenterComponents(presenterNode, entity);
return new PresenterData(entity, presenter, components);
}
private List<IPresenterComponent> InitializePresenterComponents(Node presenterNode, Entity entity)
{
_presenterRegistry.RegisterPresenter(presenterNode, entity);
var components = new List<IPresenterComponent>();
foreach (var child in presenterNode.GetChildren(true))
{
if (child is IPresenterComponent pComponent)
{
pComponent.Initialize(entity, _world);
components.Add(pComponent);
}
}
if (presenterNode is IPresenterComponent rootComponent)
{
rootComponent.Initialize(entity, _world);
components.Add(rootComponent);
}
return components;
}
}

View File

@@ -0,0 +1 @@
uid://balunj6u4ptjq

View File

@@ -0,0 +1,58 @@
using CryptonymThunder.Code.Extensions;
using GameCore.ECS;
using GameCore.ECS.Interfaces;
using GameCore.Input;
using GameCore.Movement;
using Godot;
namespace CryptonymThunder.Code.Presenters;
[GlobalClass]
public partial class CameraPresenterComponent : Node3D, IPresenterComponent
{
private Entity _coreEntity;
private World _world;
private Camera3D _camera;
private RotationComponent _rotationComponent;
private bool _cursorLocked = true;
public void Initialize(Entity coreEntity, World world)
{
_coreEntity = coreEntity;
_world = world;
_rotationComponent = _world.GetComponent<RotationComponent>(_coreEntity);
_camera = GetNode<Camera3D>("Camera3D");
if (_cursorLocked) Input.MouseMode = Input.MouseModeEnum.Captured;
}
public void SyncToPresentation(float delta)
{
if (_rotationComponent == null || _camera == null) return;
var coreRotation = _rotationComponent.Rotation;
_camera.Rotation = new Vector3(coreRotation.X, 0f, 0f);
}
public void SyncToCore(float delta)
{
}
public override void _Input(InputEvent @event)
{
if (Input.IsActionJustPressed("ui_cancel"))
{
if (_cursorLocked)
{
Input.MouseMode = Input.MouseModeEnum.Visible;
_cursorLocked = false;
}
else
{
Input.MouseMode = Input.MouseModeEnum.Captured;
_cursorLocked = true;
}
}
}
}

View File

@@ -0,0 +1 @@
uid://crx03e8buoni3

View File

@@ -0,0 +1,66 @@
using CryptonymThunder.Code.Extensions;
using GameCore.ECS;
using GameCore.ECS.Interfaces;
using GameCore.Input;
using GameCore.Movement;
using GameCore.Physics;
using Godot;
namespace CryptonymThunder.Code.Presenters;
[GlobalClass]
public partial class CharacterBody3DPresenter : CharacterBody3D, IEntityPresenter, IPresenterComponent
{
private World _world;
private VelocityComponent _velocity;
private PositionComponent _position;
private RotationComponent _rotation;
private CharacterStateComponent _characterState;
private InputStateComponent _inputState;
private Camera3D _camera;
public Entity CoreEntity { get; set; }
public void Initialize(Entity coreEntity, World world)
{
CoreEntity = coreEntity;
_world = world;
_velocity = _world.GetComponent<VelocityComponent>(CoreEntity);
_position = _world.GetComponent<PositionComponent>(CoreEntity);
_rotation = _world.GetComponent<RotationComponent>(CoreEntity);
_inputState = _world.GetComponent<InputStateComponent>(CoreEntity);
_characterState = _world.GetComponent<CharacterStateComponent>(CoreEntity);
_camera = GetNode<Camera3D>("CameraPivot/Camera3D");
}
public void SyncToPresentation(float delta)
{
if (_rotation != null)
{
var coreRotation = _rotation.Rotation;
Rotation = new Vector3(Rotation.X, coreRotation.Y, Rotation.Z);
}
if (_velocity == null) return;
var godotVelocity = Velocity;
godotVelocity.X = _velocity.DesiredVelocity.X;
godotVelocity.Y = _velocity.DesiredVelocity.Y;
godotVelocity.Z = _velocity.DesiredVelocity.Z;
Velocity = godotVelocity;
MoveAndSlide();
}
public void SyncToCore(float delta)
{
if (_position != null) _position.Position = GlobalPosition.ToGameCore();
if (_velocity != null) _velocity.ActualVelocity = Velocity.ToGameCore();
if (_characterState != null) _characterState.IsOnFloor = IsOnFloor();
if (_inputState != null && _camera != null)
{
_inputState.MuzzlePosition = _camera.GlobalPosition.ToGameCore();
_inputState.MuzzleDirection = (-_camera.GlobalTransform.Basis.Z).ToGameCore();
}
}
}

View File

@@ -0,0 +1 @@
uid://hkiny1ftv4r7

View File

@@ -0,0 +1,11 @@
using GameCore.ECS;
using GameCore.ECS.Interfaces;
using Godot;
namespace CryptonymThunder.Code.Presenters;
[GlobalClass]
public partial class EntityPresenter : Node3D, IEntityPresenter
{
public Entity CoreEntity { get; set; }
}

View File

@@ -0,0 +1 @@
uid://cb7vaw6xqjs1i

View File

@@ -0,0 +1,157 @@
using System.Collections.Generic;
using CryptonymThunder.Code.Autoloads;
using CryptonymThunder.Code.Factories;
using CryptonymThunder.Code.Resources;
using CryptonymThunder.Code.Services;
using GameCore.Attributes;
using GameCore.Combat;
using GameCore.Config;
using GameCore.ECS;
using GameCore.ECS.Interfaces;
using GameCore.Events;
using GameCore.Input;
using GameCore.Movement;
using Godot;
namespace CryptonymThunder.Code.Presenters;
[GlobalClass]
public partial class GamePresenter : Node
{
[Export] private ArchetypeDatabase ArchetypesDatabase { get; set; }
[Export] private EntityArchetype PlayerArchetype { get; set; }
[Export] private SimulationConfigResource SimulationConfig { get; set; }
private World _world;
private PresenterRegistry _presenterRegistry;
private GodotInputService _inputService;
private GodotWorldQuery _worldQuery;
private PresenterFactory _presenterFactory;
private readonly Dictionary<int, List<IPresenterComponent>> _presenterComponents = new();
private readonly Dictionary<int, IEntityPresenter> _presenters = new();
public override void _Ready()
{
_presenterRegistry = GetNode<PresenterRegistry>("/root/PresenterRegistry");
_inputService = new GodotInputService();
_worldQuery = new GodotWorldQuery(this);
var simConfig = new SimulationConfig();
if (SimulationConfig != null)
{
simConfig.GravityStrength = SimulationConfig.GravityStrength;
}
_world = new World(_inputService, _worldQuery, simConfig);
_presenterFactory = new PresenterFactory(_world, new ComponentFactory(), _presenterRegistry, this);
_world.RegisterSystem(new PlayerInputSystem());
_world.RegisterSystem(new RotationSystem());
_world.RegisterSystem(new GroundMovementSystem());
_world.RegisterSystem(new GravitySystem());
_world.RegisterSystem(new JumpSystem());
_world.RegisterSystem(new AttributeSystem());
_world.RegisterSystem(new WeaponSystem());
_world.RegisterSystem(new ProjectileSystem());
_world.RegisterSystem(new ProjectileInitializationSystem(_world));
_world.RegisterSystem(new DamageSystem(_world));
_world.RegisterSystem(new ProjectileCleanupSystem());
_world.RegisterSystem(new DestructionSystem());
_world.Subscribe<EntityDiedEvent>(OnEntityDied);
_world.Subscribe<SpawnEntityEvent>(OnSpawnEntity);
RegisterAllSceneEntities();
var playerData = _presenterFactory.CreateEntityFromArchetype(PlayerArchetype);
_presenters.Add(playerData.Entity.Id, playerData.Presenter);
_presenterComponents.Add(playerData.Entity.Id, playerData.Components);
}
public override void _Input(InputEvent @event)
{
_inputService?.HandleInputEvent(@event);
}
public override void _PhysicsProcess(double delta)
{
if (_presenters.Count == 0) return;
_inputService.Update();
_world.Update((float)delta);
_world.ProcessEvents();
foreach (var componentList in _presenterComponents.Values)
{
foreach (var component in componentList)
{
component.SyncToPresentation((float)delta);
}
}
foreach (var componentList in _presenterComponents.Values)
{
foreach (var component in componentList)
{
component.SyncToCore((float)delta);
}
}
_inputService.LateUpdate();
}
private void OnSpawnEntity(SpawnEntityEvent @event)
{
if (ArchetypesDatabase.Archetypes.TryGetValue(@event.ArchetypeId, out var archetype))
{
var presenterData = _presenterFactory.CreateEntityFromArchetype(archetype, @event.Position, @event.Rotation, @event.InitialVelocity);
_presenters.Add(presenterData.Entity.Id, presenterData.Presenter);
_presenterComponents.Add(presenterData.Entity.Id, presenterData.Components);
_world.PublishEvent(new EntitySpawnedEvent(presenterData.Entity, @event.Owner, @event.ArchetypeId));
}
}
private void OnEntityDied(EntityDiedEvent e)
{
if (_presenters.Remove(e.Entity.Id, out var presenter))
{
_presenterRegistry.UnregisterPresenter(presenter as Node);
_presenterComponents.Remove(e.Entity.Id);
(presenter as Node)?.QueueFree();
}
}
private void RegisterAllSceneEntities()
{
var sceneEntities = GetTree().GetNodesInGroup("SceneEntities");
foreach (var node in sceneEntities)
{
if (node is SceneEntity sceneEntity)
{
var parentNode = sceneEntity.GetParent();
if (parentNode != null)
{
var presenterData = _presenterFactory.RegisterSceneEntity(parentNode, sceneEntity.ComponentResources);
_presenters.Add(presenterData.Entity.Id, presenterData.Presenter);
_presenterComponents.Add(presenterData.Entity.Id, presenterData.Components);
}
}
}
}
public Node GetPresenterNode(Entity entity)
{
if (_presenters.TryGetValue(entity.Id, out var presenter))
{
return presenter as Node;
}
return null;
}
}

View File

@@ -0,0 +1 @@
uid://cfpm5p102f65x

View File

@@ -0,0 +1,10 @@
using Godot;
using Godot.Collections;
namespace CryptonymThunder.Code.Presenters;
[GlobalClass]
public partial class SceneEntity : Node
{
[Export] public Array<Resource> ComponentResources { get; set; } = [];
}

View File

@@ -0,0 +1 @@
uid://b6x8llipvutqs

View File

@@ -0,0 +1,31 @@
using CryptonymThunder.Code.Extensions;
using GameCore.ECS;
using GameCore.ECS.Interfaces;
using GameCore.Physics;
using Godot;
namespace CryptonymThunder.Code.Presenters;
[GlobalClass]
public partial class TransformPresenterComponent : Node3D, IPresenterComponent, IEntityPresenter
{
private PositionComponent _position;
public Entity CoreEntity { get; set; }
public void Initialize(Entity coreEntity, World world)
{
CoreEntity = coreEntity;
_position = world.GetComponent<PositionComponent>(coreEntity);
}
public void SyncToPresentation(float delta)
{
if (_position != null) GlobalPosition = _position.Position.ToGodot();
}
public void SyncToCore(float delta)
{
}
}

View File

@@ -0,0 +1 @@
uid://dl2awoy0hyruh

View File

@@ -0,0 +1,10 @@
using Godot;
using Godot.Collections;
namespace CryptonymThunder.Code.Resources;
[GlobalClass]
public partial class ArchetypeDatabase : Resource
{
[Export] public Dictionary<string, EntityArchetype> Archetypes { get; set; } = new();
}

View File

@@ -0,0 +1 @@
uid://df10ksb6f0pjt

View File

@@ -0,0 +1,10 @@
using Godot;
using Attribute = GameCore.Attributes.Attribute;
namespace CryptonymThunder.Code.Resources;
[GlobalClass]
public partial class AttributeComponentResource : Resource
{
[Export] public Godot.Collections.Dictionary<Attribute, float> BaseValues { get; set; } = new();
}

View File

@@ -0,0 +1 @@
uid://dc7wq2ij5kwj5

View File

@@ -0,0 +1,9 @@
using Godot;
namespace CryptonymThunder.Code.Resources;
[GlobalClass]
public partial class CharacterStateComponentResource : Resource
{
}

View File

@@ -0,0 +1 @@
uid://cdpbn8eiypfbd

View File

@@ -0,0 +1,9 @@
using Godot;
namespace CryptonymThunder.Code.Resources;
[GlobalClass]
public partial class EffectResource : Resource
{
}

View File

@@ -0,0 +1 @@
uid://bdo6vrtrsr0an

View File

@@ -0,0 +1,9 @@
using Godot;
namespace CryptonymThunder.Code.Resources.Effects;
[GlobalClass]
public partial class DamageEffectResource : EffectResource
{
[Export] public float Amount { get; set; } = 10f;
}

View File

@@ -0,0 +1 @@
uid://btv24gsw1p850

View File

@@ -0,0 +1,12 @@
using Godot;
namespace CryptonymThunder.Code.Resources.Effects;
[GlobalClass]
public partial class FireProjectileEffectResource : EffectResource
{
[Export] public string ProjectileArchetypeId { get; set; }
[Export(PropertyHint.Range, "1,50,1")] public int Count { get; set; } = 1;
[Export(PropertyHint.Range, "0,90,0.1")] public float SpreadAngle { get; set; } = 0f;
[Export] public float ProjectileSpeed { get; set; } = 30f;
}

View File

@@ -0,0 +1 @@
uid://cht6trljihvle

View File

@@ -0,0 +1,9 @@
using Godot;
namespace CryptonymThunder.Code.Resources.Effects;
[GlobalClass]
public partial class HitscanEffectResource : EffectResource
{
[Export] public float Range { get; set; } = 100f;
}

View File

@@ -0,0 +1 @@
uid://chc0d0rjcbl65

View File

@@ -0,0 +1,11 @@
using Godot;
using Godot.Collections;
namespace CryptonymThunder.Code.Resources;
[GlobalClass]
public partial class EntityArchetype : Resource
{
[Export] public PackedScene Scene { get; set; }
[Export] public Array<Resource> ComponentResources { get; set; } = [];
}

View File

@@ -0,0 +1 @@
uid://coe688e2jkyjq

View File

@@ -0,0 +1,9 @@
using Godot;
namespace CryptonymThunder.Code.Resources;
[GlobalClass]
public partial class InputStateComponentResource : Resource
{
}

View File

@@ -0,0 +1 @@
uid://y4cbuh2wxigy

View File

@@ -0,0 +1,9 @@
using Godot;
namespace CryptonymThunder.Code.Resources;
[GlobalClass]
public partial class PlayerComponentResource : Resource
{
}

View File

@@ -0,0 +1 @@
uid://p4vr80n70vkt

View File

@@ -0,0 +1,9 @@
using Godot;
namespace CryptonymThunder.Code.Resources;
[GlobalClass]
public partial class PositionComponentResource : Resource
{
}

View File

@@ -0,0 +1 @@
uid://df5wmj1jp2oy5

View File

@@ -0,0 +1,10 @@
using Godot;
namespace CryptonymThunder.Code.Resources;
[GlobalClass]
public partial class ProjectileComponentResource : Resource
{
[Export] public float Speed { get; set; } = 50.0f;
[Export] public float Lifetime { get; set; } = 5.0f;
}

View File

@@ -0,0 +1 @@
uid://ddryrwjq33832

View File

@@ -0,0 +1,9 @@
using Godot;
namespace CryptonymThunder.Code.Resources;
[GlobalClass]
public partial class RotationComponentResource : Resource
{
}

View File

@@ -0,0 +1 @@
uid://blm62f85g7icn

View File

@@ -0,0 +1,9 @@
using Godot;
namespace CryptonymThunder.Code.Resources;
[GlobalClass]
public partial class SimulationConfigResource : Resource
{
[Export] public float GravityStrength { get; set; } = 9.81f;
}

View File

@@ -0,0 +1 @@
uid://uearpvfk21ym

View File

@@ -0,0 +1,9 @@
using Godot;
namespace CryptonymThunder.Code.Resources;
[GlobalClass]
public partial class VelocityComponentResource : Resource
{
}

View File

@@ -0,0 +1 @@
uid://t4j1urlupxxv

View File

@@ -0,0 +1,9 @@
using Godot;
namespace CryptonymThunder.Code.Resources;
[GlobalClass]
public partial class WeaponComponentResource : Resource
{
[Export] public WeaponResource WeaponData { get; set; }
}

View File

@@ -0,0 +1 @@
uid://bp7mufswr41w6

View File

@@ -0,0 +1,14 @@
using GameCore.Combat;
using Godot;
namespace CryptonymThunder.Code.Resources;
[GlobalClass]
public partial class WeaponResource : Resource
{
[Export] public float FireRate { get; set; } = 1.0f;
[ExportGroup("Effects")]
[Export] public Godot.Collections.Array<EffectResource> OnFireEffects { get; set; } = [];
[Export] public Godot.Collections.Array<EffectResource> OnHitEffects { get; set; } = [];
}

View File

@@ -0,0 +1 @@
uid://hf0iut8o8do3

View File

@@ -0,0 +1,47 @@
using GameCore.Input.Interfaces;
using Godot;
using GameCoreMath = GameCore.Math.Vector3;
namespace CryptonymThunder.Code.Services;
public class GodotInputService : IInputService
{
private Vector2 _mouseDeltaAccumulator = Vector2.Zero;
private const float MouseSensitivity = 0.002f;
public bool IsFiring { get; private set; }
public bool IsInteracting { get; private set; }
public bool IsJumping { get; private set; }
public GameCoreMath MoveDirection { get; private set; }
public GameCoreMath LookDirection { get; private set; }
public void HandleInputEvent(InputEvent e)
{
if (e is InputEventMouseMotion mouseMotion)
{
_mouseDeltaAccumulator += mouseMotion.Relative;
}
}
public void Update()
{
var godotInputVector = Input.GetVector("left", "right", "forward", "backward");
MoveDirection = new GameCoreMath(godotInputVector.X, 0f, godotInputVector.Y);
LookDirection = new GameCoreMath(
-_mouseDeltaAccumulator.Y * MouseSensitivity,
-_mouseDeltaAccumulator.X * MouseSensitivity,
0f);
_mouseDeltaAccumulator = Vector2.Zero;
IsJumping = Input.IsActionJustPressed("jump");
IsFiring = Input.IsActionPressed("fire");
IsInteracting = Input.IsActionPressed("interact");
}
public void LateUpdate()
{
LookDirection = GameCoreMath.Zero;
}
}

View File

@@ -0,0 +1 @@
uid://dp63r0vs7vydp

View File

@@ -0,0 +1,59 @@
using System;
using CryptonymThunder.Code.Autoloads;
using CryptonymThunder.Code.Extensions;
using CryptonymThunder.Code.Presenters;
using GameCore.ECS;
using GameCore.ECS.Interfaces;
using Godot;
using GodotWorld = Godot.World3D;
using Vector3 = GameCore.Math.Vector3;
namespace CryptonymThunder.Code.Services;
public class GodotWorldQuery(GamePresenter ownerNode) : IWorldQuery
{
private readonly PresenterRegistry _presenterRegistry = ownerNode.GetNode<PresenterRegistry>("/root/PresenterRegistry");
private readonly GodotWorld _godotWorld = ((SceneTree)Engine.GetMainLoop()).Root.World3D;
public HitResult Raycast(Vector3 from, Vector3 to, Entity? ownerToExclude = null)
{
var spaceState = _godotWorld.DirectSpaceState;
var query = PhysicsRayQueryParameters3D.Create(from.ToGodot(), to.ToGodot());
query.CollisionMask = 1;
query.CollideWithBodies = true;
query.CollideWithAreas = false;
if (ownerToExclude.HasValue)
{
var ownerPresenter = ownerNode.GetPresenterNode(ownerToExclude.Value);
if (ownerPresenter != null)
{
query.Exclude = [((PhysicsBody3D)ownerPresenter).GetRid()];
}
}
var result = spaceState.IntersectRay(query);
if (result.Count > 0 && result["collider"].Obj is Node3D collider)
{
if (_presenterRegistry.TryGetEntity(collider.GetInstanceId(), out var hitEntity))
{
return new HitResult
{
DidHit = true,
HitEntity = hitEntity
};
}
}
return new HitResult { DidHit = false };
}
public Vector3 RotateVectorByYaw(Vector3 vector, float yawRadians)
{
var rotatedX = vector.X * (float)Math.Cos(yawRadians) + vector.Z * (float)Math.Sin(yawRadians);
var rotatedZ = -vector.X * (float)Math.Sin(yawRadians) + vector.Z * (float)Math.Cos(yawRadians);
return new Vector3(rotatedX, vector.Y, rotatedZ);
}
}

View File

@@ -0,0 +1 @@
uid://l1tfuwnumj0m