Add MeleeEffect and related services for combat interactions

This commit is contained in:
2025-10-30 03:24:44 +01:00
parent bb31c9a39c
commit f65277e6b4
9 changed files with 145 additions and 5 deletions

View File

@@ -0,0 +1,29 @@
using GameCore.Combat.Interfaces;
using GameCore.Input;
namespace GameCore.Combat.Effects;
public class MeleeEffect(float range, float radius) : IEffect
{
public void Execute(EffectContext context)
{
var input = context.World.GetComponent<InputStateComponent>(context.Owner);
var weapon = context.World.GetComponent<WeaponComponent>(context.Owner);
if (input == null || weapon == null) return;
var checkPosition = input.MuzzlePosition + input.MuzzleDirection * range;
var hits = context.World.WorldQuery.OverlapSphere(checkPosition, radius, context.Owner);
foreach (var hitEntity in hits)
{
var hitContext = new EffectContext
{
World = context.World,
Owner = context.Owner,
Target = hitEntity
};
foreach (var effect in weapon.OnHitEffects) effect.Execute(hitContext);
}
}
}

View File

@@ -6,4 +6,5 @@ public interface IWorldQuery
{
HitResult Raycast(Vector3 from, Vector3 to, Entity? ownerToExclude = null);
Vector3 RotateVectorByYaw(Vector3 vector, float yawRadians);
IEnumerable<Entity> OverlapSphere(Vector3 position, float radius, Entity? ownerToExclude = null);
}

View File

@@ -5,6 +5,8 @@ using GameCore.Events.Interfaces;
using GameCore.Input.Interfaces;
using GameCore.Interaction;
using GameCore.Logging.Interfaces;
using GameCore.Services;
using GameCore.State;
namespace GameCore.ECS;
@@ -13,18 +15,37 @@ namespace GameCore.ECS;
/// It manages all entities, components, and systems, and orchestrates the main game loop.
/// This class is the primary point of interaction for the Engine/Presentation layer.
/// </summary>
public class World(IInputService inputService, IWorldQuery worldQuery, SimulationConfig config, ILogger logger)
public class World
{
private readonly Dictionary<int, List<IComponent>> _componentsByEntityId = new();
private readonly EventBus _eventBus = new();
private readonly List<ISystem> _systems = [];
public readonly IAudioService AudioService;
public readonly SimulationConfig Config = config;
public readonly IInputService InputService = inputService;
public readonly ILogger Logger = logger;
public readonly IWorldQuery WorldQuery = worldQuery;
public readonly SimulationConfig Config;
public readonly IGameStateService GameState;
public readonly IInputService InputService;
public readonly ILogger Logger;
public readonly IParticleService ParticleService;
public readonly IWorldQuery WorldQuery;
private int _nextEntityId;
public World(IInputService inputService,
IWorldQuery worldQuery,
SimulationConfig config,
ILogger logger,
IAudioService audioService,
IParticleService particleService)
{
AudioService = audioService;
Config = config;
InputService = inputService;
Logger = logger;
ParticleService = particleService;
WorldQuery = worldQuery;
GameState = new GameStateService(this);
}
/// <summary>
/// Creates a new, unique entity.
/// </summary>

View File

@@ -0,0 +1,9 @@
using GameCore.Events.Interfaces;
using GameCore.State;
namespace GameCore.Events;
public readonly struct GameStatusChangedEvent(GameStatus newStatus) : IEvent
{
public readonly GameStatus NewStatus = newStatus;
}

View File

@@ -0,0 +1,9 @@
using GameCore.Math;
namespace GameCore.Services;
public interface IAudioService
{
void Play(string soundId);
void PlayAtPoint(string soundId, Vector3 position);
}

View File

@@ -0,0 +1,9 @@
using GameCore.Math;
namespace GameCore.Services;
public interface IParticleService
{
void Trigger(string particleId, Vector3 position);
void Trigger(string particleId, Vector3 position, Vector3 normal);
}

View File

@@ -0,0 +1,40 @@
using GameCore.ECS;
using GameCore.Events;
namespace GameCore.State;
public class GameStateService(World world) : IGameStateService
{
private readonly HashSet<string> _unlockedLevelIds = [];
private readonly World _world = world;
public GameStatus CurrentStatus { get; private set; } = GameStatus.InMainMenu;
public string CurrentLevelId { get; private set; }
public IReadOnlyCollection<string> UnlockedLevelIds => _unlockedLevelIds;
public void SetStatus(GameStatus newStatus)
{
if (CurrentStatus == newStatus) return;
CurrentStatus = newStatus;
_world.PublishEvent(new GameStatusChangedEvent(newStatus));
}
public void StartGame(string startingLevelId)
{
CurrentLevelId = startingLevelId;
_unlockedLevelIds.Add(startingLevelId);
SetStatus(GameStatus.InGame);
}
public void UnlockLevel(string levelId)
{
_unlockedLevelIds.Add(levelId);
}
public void ResetProgress()
{
_unlockedLevelIds.Clear();
CurrentLevelId = null!;
SetStatus(GameStatus.InMainMenu);
}
}

View File

@@ -0,0 +1,9 @@
namespace GameCore.State;
public enum GameStatus
{
InMainMenu,
InGame,
Paused,
GameOver
}

View File

@@ -0,0 +1,13 @@
namespace GameCore.State;
public interface IGameStateService
{
GameStatus CurrentStatus { get; }
string CurrentLevelId { get; }
IReadOnlyCollection<string> UnlockedLevelIds { get; }
void SetStatus(GameStatus newStatus);
void StartGame(string startingLevelId);
void UnlockLevel(string levelId);
void ResetProgress();
}