add docs for game
This commit is contained in:
@@ -8,16 +8,42 @@ using OpenTK.Mathematics;
|
||||
|
||||
namespace DoomDeathmatch.Component;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a consumable item in the game, handling interaction with players and other objects.
|
||||
/// </summary>
|
||||
public class ConsumableComponent : Engine.Scene.Component.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The consumable logic tied to this component.
|
||||
/// </summary>
|
||||
private readonly IConsumable _consumable;
|
||||
|
||||
/// <summary>
|
||||
/// The collider used for detecting interactions with other objects.
|
||||
/// </summary>
|
||||
private readonly AABBColliderComponent _collider;
|
||||
|
||||
/// <summary>
|
||||
/// The 2D box renderer used to display the consumable's icon.
|
||||
/// </summary>
|
||||
private readonly Box2DRenderer _box2DRenderer;
|
||||
|
||||
/// <summary>
|
||||
/// Provides random values for movement adjustments.
|
||||
/// </summary>
|
||||
private readonly Random _random = new();
|
||||
|
||||
/// <summary>
|
||||
/// Controls the movement of the consumable object.
|
||||
/// </summary>
|
||||
private MovementController _movementController = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConsumableComponent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parConsumable">The consumable logic associated with this component.</param>
|
||||
/// <param name="parCollider">The collider for detecting interactions.</param>
|
||||
/// <param name="parBox2DRenderer">The renderer for displaying the consumable's visual representation.</param>
|
||||
public ConsumableComponent(IConsumable parConsumable, AABBColliderComponent parCollider,
|
||||
Box2DRenderer parBox2DRenderer)
|
||||
{
|
||||
@@ -36,6 +62,10 @@ public class ConsumableComponent : Engine.Scene.Component.Component
|
||||
ArgumentNullException.ThrowIfNull(_movementController);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles collision events with other objects.
|
||||
/// </summary>
|
||||
/// <param name="parCollider">The collider of the object that this collided with.</param>
|
||||
private void OnCollision(AABBColliderComponent parCollider)
|
||||
{
|
||||
if (parCollider.ColliderGroups.Contains("player"))
|
||||
@@ -48,6 +78,10 @@ public class ConsumableComponent : Engine.Scene.Component.Component
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to consume the consumable if it collided with a player.
|
||||
/// </summary>
|
||||
/// <param name="parCollider">The player's collider component.</param>
|
||||
private void TryConsume(AABBColliderComponent parCollider)
|
||||
{
|
||||
var playerController = parCollider.GameObject.GetComponent<PlayerController>();
|
||||
@@ -61,6 +95,10 @@ public class ConsumableComponent : Engine.Scene.Component.Component
|
||||
EngineUtil.SceneManager.CurrentScene!.Remove(GameObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the consumable away if it collides with another consumable.
|
||||
/// </summary>
|
||||
/// <param name="parCollider">The collider of the other consumable.</param>
|
||||
private void MoveAway(AABBColliderComponent parCollider)
|
||||
{
|
||||
var direction = _collider.GameObject.Transform.GetFullTranslation() -
|
||||
@@ -78,6 +116,10 @@ public class ConsumableComponent : Engine.Scene.Component.Component
|
||||
_movementController.ApplyMovement(direction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random direction vector for movement.
|
||||
/// </summary>
|
||||
/// <returns>A normalized random direction vector.</returns>
|
||||
private Vector3 GetRandomDirection()
|
||||
{
|
||||
var x = (_random.NextSingle() * 2) - 1;
|
||||
|
||||
@@ -2,21 +2,50 @@
|
||||
using DoomDeathmatch.Component.Util;
|
||||
using DoomDeathmatch.Script.Model.Enemy;
|
||||
using DoomDeathmatch.Script.Model.Enemy.Attack;
|
||||
using DoomDeathmatch.Script.MVC;
|
||||
using Engine.Util;
|
||||
|
||||
namespace DoomDeathmatch.Component.MVC.Enemy;
|
||||
|
||||
/// <summary>
|
||||
/// Controls enemy behavior, including movement, health, and attacks.
|
||||
/// </summary>
|
||||
public class EnemyController : Engine.Scene.Component.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages the health of the enemy.
|
||||
/// </summary>
|
||||
public HealthController HealthController { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Data associated with the enemy, including stats and behaviors.
|
||||
/// </summary>
|
||||
private readonly EnemyData _enemyData;
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the game controller.
|
||||
/// </summary>
|
||||
private GameController _gameController = null!;
|
||||
private MovementController _movementController = null!;
|
||||
private AttackBehavior _attackBehavior = null!;
|
||||
private EnemyView _enemyView = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Controls the movement of the enemy.
|
||||
/// </summary>
|
||||
private MovementController _movementController = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Handles the enemy's attack behavior.
|
||||
/// </summary>
|
||||
private AttackBehavior _attackBehavior = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Handles visual representation of the enemy.
|
||||
/// </summary>
|
||||
private IView<EnemyData> _enemyView = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EnemyController"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parEnemyData">Data for the enemy, including stats and behaviors.</param>
|
||||
public EnemyController(EnemyData parEnemyData)
|
||||
{
|
||||
_enemyData = parEnemyData;
|
||||
@@ -26,7 +55,7 @@ public class EnemyController : Engine.Scene.Component.Component
|
||||
{
|
||||
_gameController = EngineUtil.SceneManager.CurrentScene!.FindFirstComponent<GameController>()!;
|
||||
HealthController = GameObject.GetComponent<HealthController>()!;
|
||||
_enemyView = GameObject.GetComponent<EnemyView>()!;
|
||||
_enemyView = GameObject.GetComponent<IView<EnemyData>>()!;
|
||||
_movementController = GameObject.GetComponent<MovementController>()!;
|
||||
|
||||
ArgumentNullException.ThrowIfNull(_gameController);
|
||||
@@ -69,6 +98,9 @@ public class EnemyController : Engine.Scene.Component.Component
|
||||
_attackBehavior.Attack(parDeltaTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the death of the enemy by removing it from the scene and updating the score.
|
||||
/// </summary>
|
||||
private void OnDeath()
|
||||
{
|
||||
EngineUtil.SceneManager.CurrentScene!.Remove(GameObject);
|
||||
|
||||
@@ -1,19 +1,31 @@
|
||||
using DoomDeathmatch.Script.Model.Enemy;
|
||||
using DoomDeathmatch.Script.MVC;
|
||||
using Engine.Graphics.Texture;
|
||||
using Engine.Scene.Component.BuiltIn.Renderer;
|
||||
using Engine.Util;
|
||||
|
||||
namespace DoomDeathmatch.Component.MVC.Enemy;
|
||||
|
||||
public class EnemyView : Engine.Scene.Component.Component
|
||||
/// <summary>
|
||||
/// View component for displaying the enemy.
|
||||
/// </summary>
|
||||
public class EnemyView : Engine.Scene.Component.Component, IView<EnemyData>
|
||||
{
|
||||
/// <summary>
|
||||
/// The box2d renderer used to display the enemy.
|
||||
/// </summary>
|
||||
private readonly Box2DRenderer _box2DRenderer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EnemyView"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parBox2DRenderer">Box2d renderer for displaying the enemy.</param>
|
||||
public EnemyView(Box2DRenderer parBox2DRenderer)
|
||||
{
|
||||
_box2DRenderer = parBox2DRenderer;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UpdateView(EnemyData parEnemyData)
|
||||
{
|
||||
_box2DRenderer.Texture = EngineUtil.AssetResourceManager.Load<Texture>(parEnemyData.Texture);
|
||||
|
||||
@@ -7,19 +7,50 @@ using Engine.Util;
|
||||
|
||||
namespace DoomDeathmatch.Component.MVC;
|
||||
|
||||
/// <summary>
|
||||
/// Controls the main game logic, including pausing, unpausing, and handling game over states.
|
||||
/// </summary>
|
||||
public class GameController : Engine.Scene.Component.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates whether the game is currently paused.
|
||||
/// </summary>
|
||||
public bool IsPaused { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the game is over.
|
||||
/// </summary>
|
||||
public bool IsGameOver { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The player controller managing the player's state and actions.
|
||||
/// </summary>
|
||||
public PlayerController PlayerController { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The score controller tracking the player's score.
|
||||
/// </summary>
|
||||
public ScoreController ScoreController { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The input handler for handling keyboard and mouse input.
|
||||
/// </summary>
|
||||
private readonly IInputHandler _inputHandler = EngineUtil.InputHandler;
|
||||
|
||||
/// <summary>
|
||||
/// The menu controller for handling the main menu.
|
||||
/// </summary>
|
||||
private readonly MenuControllerComponent _menuController;
|
||||
|
||||
/// <summary>
|
||||
/// The timer controller for managing the game timer.
|
||||
/// </summary>
|
||||
private TimerController _timerController = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameController"/> class with a specified menu controller.
|
||||
/// </summary>
|
||||
/// <param name="parMenuController">The menu controller used for menu navigation during the game.</param>
|
||||
public GameController(MenuControllerComponent parMenuController)
|
||||
{
|
||||
_menuController = parMenuController;
|
||||
@@ -39,20 +70,6 @@ public class GameController : Engine.Scene.Component.Component
|
||||
_timerController.OnFinished += GameOver;
|
||||
}
|
||||
|
||||
public void Unpause()
|
||||
{
|
||||
EngineUtil.SceneManager.CurrentScene!.TimeScale = 1.0f;
|
||||
IsPaused = false;
|
||||
_menuController.SelectMenuItem("play");
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
EngineUtil.SceneManager.CurrentScene!.TimeScale = 0.0f;
|
||||
IsPaused = true;
|
||||
_menuController.SelectMenuItem("escape");
|
||||
}
|
||||
|
||||
public override void Update(double parDeltaTime)
|
||||
{
|
||||
if (_inputHandler.IsKeyJustPressed(KeyboardButtonCode.Escape))
|
||||
@@ -68,6 +85,29 @@ public class GameController : Engine.Scene.Component.Component
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pauses the game.
|
||||
/// </summary>
|
||||
public void Pause()
|
||||
{
|
||||
EngineUtil.SceneManager.CurrentScene!.TimeScale = 0.0f;
|
||||
IsPaused = true;
|
||||
_menuController.SelectMenuItem("escape");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resumes the game from a paused state.
|
||||
/// </summary>
|
||||
public void Unpause()
|
||||
{
|
||||
EngineUtil.SceneManager.CurrentScene!.TimeScale = 1.0f;
|
||||
IsPaused = false;
|
||||
_menuController.SelectMenuItem("play");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the game over state, transitioning to the Game Over scene.
|
||||
/// </summary>
|
||||
private void GameOver()
|
||||
{
|
||||
IsGameOver = true;
|
||||
|
||||
@@ -1,15 +1,30 @@
|
||||
using DoomDeathmatch.Script.Model;
|
||||
using DoomDeathmatch.Script.MVC;
|
||||
using Engine.Scene.Component.BuiltIn.Renderer;
|
||||
|
||||
namespace DoomDeathmatch.Component.MVC.Health;
|
||||
|
||||
public class EnemyHealthView : HealthView
|
||||
/// <summary>
|
||||
/// View component for displaying the health status of an enemy.
|
||||
/// </summary>
|
||||
public class EnemyHealthView : Engine.Scene.Component.Component, IView<HealthModel>
|
||||
{
|
||||
public EnemyHealthView(TextRenderer parHealthTextRenderer) : base(parHealthTextRenderer)
|
||||
/// <summary>
|
||||
/// The text renderer used to display the health.
|
||||
/// </summary>
|
||||
private readonly TextRenderer _healthTextRenderer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EnemyHealthView"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parHealthTextRenderer">Text renderer for displaying the health status text.</param>
|
||||
public EnemyHealthView(TextRenderer parHealthTextRenderer)
|
||||
{
|
||||
_healthTextRenderer = parHealthTextRenderer;
|
||||
}
|
||||
|
||||
public override void UpdateView(HealthModel parHealthModel)
|
||||
/// <inheritdoc/>
|
||||
public void UpdateView(HealthModel parHealthModel)
|
||||
{
|
||||
_healthTextRenderer.Text = $"Здоровье: {parHealthModel.Health:000}";
|
||||
}
|
||||
|
||||
@@ -1,15 +1,37 @@
|
||||
using DoomDeathmatch.Script.Model;
|
||||
using DoomDeathmatch.Script.MVC;
|
||||
|
||||
namespace DoomDeathmatch.Component.MVC.Health;
|
||||
|
||||
/// <summary>
|
||||
/// Manages health-related logic, including damage, healing, and health changes.
|
||||
/// </summary>
|
||||
public class HealthController : Engine.Scene.Component.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Triggered when the entity dies.
|
||||
/// </summary>
|
||||
public event Action? OnDeath;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the entity is alive.
|
||||
/// </summary>
|
||||
public bool IsAlive => _healthModel.Health > 0;
|
||||
|
||||
/// <summary>
|
||||
/// The health model containing the current and maximum health.
|
||||
/// </summary>
|
||||
private readonly HealthModel _healthModel;
|
||||
private HealthView? _healthView;
|
||||
|
||||
/// <summary>
|
||||
/// The view responsible for displaying health information.
|
||||
/// </summary>
|
||||
private IView<HealthModel>? _healthView;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HealthController"/> class with an optional starting health value.
|
||||
/// </summary>
|
||||
/// <param name="parHealth">Initial health value. Defaults to 100.</param>
|
||||
public HealthController(float parHealth = 100)
|
||||
{
|
||||
_healthModel = new HealthModel(parHealth);
|
||||
@@ -18,7 +40,7 @@ public class HealthController : Engine.Scene.Component.Component
|
||||
public override void Awake()
|
||||
{
|
||||
_healthModel.HealthChanged += OnHealthChanged;
|
||||
_healthView = GameObject.GetComponent<HealthView>();
|
||||
_healthView = GameObject.GetComponent<IView<HealthModel>>();
|
||||
|
||||
if (_healthView != null)
|
||||
{
|
||||
@@ -27,22 +49,38 @@ public class HealthController : Engine.Scene.Component.Component
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the maximum health and restores the entity's health to the maximum value.
|
||||
/// </summary>
|
||||
/// <param name="parMaxHealth">The maximum health value.</param>
|
||||
public void SetMaxHealth(float parMaxHealth)
|
||||
{
|
||||
_healthModel.MaxHealth = parMaxHealth;
|
||||
_healthModel.Health = parMaxHealth;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reduces the entity's health by the specified damage value.
|
||||
/// </summary>
|
||||
/// <param name="parDamage">The amount of damage to apply.</param>
|
||||
public void TakeDamage(float parDamage)
|
||||
{
|
||||
_healthModel.Health -= parDamage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increases the entity's health by the specified heal value.
|
||||
/// </summary>
|
||||
/// <param name="parHeal">The amount of healing to apply.</param>
|
||||
public void Heal(float parHeal)
|
||||
{
|
||||
_healthModel.Health += parHeal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles health changes and triggers death logic if health reaches zero.
|
||||
/// </summary>
|
||||
/// <param name="parHealthModel">The updated health model.</param>
|
||||
private void OnHealthChanged(HealthModel parHealthModel)
|
||||
{
|
||||
if (!IsAlive)
|
||||
|
||||
@@ -1,17 +1,29 @@
|
||||
using DoomDeathmatch.Script.Model;
|
||||
using DoomDeathmatch.Script.MVC;
|
||||
using Engine.Scene.Component.BuiltIn.Renderer;
|
||||
|
||||
namespace DoomDeathmatch.Component.MVC.Health;
|
||||
|
||||
public class HealthView : Engine.Scene.Component.Component
|
||||
/// <summary>
|
||||
/// View component for displaying the health status.
|
||||
/// </summary>
|
||||
public class HealthView : Engine.Scene.Component.Component, IView<HealthModel>
|
||||
{
|
||||
protected readonly TextRenderer _healthTextRenderer;
|
||||
/// <summary>
|
||||
/// The text renderer used to display the health percentage.
|
||||
/// </summary>
|
||||
private readonly TextRenderer _healthTextRenderer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HealthView"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parHealthTextRenderer">Text renderer for displaying the health status text.</param>
|
||||
public HealthView(TextRenderer parHealthTextRenderer)
|
||||
{
|
||||
_healthTextRenderer = parHealthTextRenderer;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void UpdateView(HealthModel parHealthModel)
|
||||
{
|
||||
var percentage = parHealthModel.Health / parHealthModel.MaxHealth * 100;
|
||||
|
||||
@@ -3,11 +3,24 @@ using OpenTK.Mathematics;
|
||||
|
||||
namespace DoomDeathmatch.Component.MVC;
|
||||
|
||||
/// <summary>
|
||||
/// Controls the movement logic for a game object, applying movement forces to the rigidbody.
|
||||
/// </summary>
|
||||
public class MovementController : Engine.Scene.Component.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The movement speed.
|
||||
/// </summary>
|
||||
public float Speed { get; set; } = 10.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The rigidbody component for the game object.
|
||||
/// </summary>
|
||||
private RigidbodyComponent _rigidbody = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The drag component for the game object.
|
||||
/// </summary>
|
||||
private DragComponent _dragComponent = null!;
|
||||
|
||||
public override void Awake()
|
||||
@@ -19,6 +32,10 @@ public class MovementController : Engine.Scene.Component.Component
|
||||
ArgumentNullException.ThrowIfNull(_dragComponent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a directional movement force to the object.
|
||||
/// </summary>
|
||||
/// <param name="parDirection">The direction of movement.</param>
|
||||
public void ApplyMovement(Vector3 parDirection)
|
||||
{
|
||||
_rigidbody.Force += _dragComponent.Drag * Speed * parDirection.Normalized();
|
||||
|
||||
@@ -9,17 +9,45 @@ using OpenTK.Mathematics;
|
||||
|
||||
namespace DoomDeathmatch.Component.MVC;
|
||||
|
||||
/// <summary>
|
||||
/// Controls the player's actions and state, including health, weapons, and input handling.
|
||||
/// </summary>
|
||||
public class PlayerController : Engine.Scene.Component.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Event triggered when the player dies.
|
||||
/// </summary>
|
||||
public event Action? OnDeath;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the player is alive.
|
||||
/// </summary>
|
||||
public bool IsAlive => HealthController.IsAlive;
|
||||
|
||||
/// <summary>
|
||||
/// The health controller managing the player's health.
|
||||
/// </summary>
|
||||
public HealthController HealthController { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The weapon controller managing the player's weapons.
|
||||
/// </summary>
|
||||
public WeaponController WeaponController { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The player's camera providing a perspective view.
|
||||
/// </summary>
|
||||
public PerspectiveCamera Camera { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The input handler for handling keyboard and mouse input.
|
||||
/// </summary>
|
||||
private readonly IInputHandler _inputHandler = EngineUtil.InputHandler;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PlayerController"/> class with a specified camera.
|
||||
/// </summary>
|
||||
/// <param name="parCamera">The player's perspective camera.</param>
|
||||
public PlayerController(PerspectiveCamera parCamera)
|
||||
{
|
||||
Camera = parCamera;
|
||||
@@ -66,24 +94,32 @@ public class PlayerController : Engine.Scene.Component.Component
|
||||
return;
|
||||
}
|
||||
|
||||
var position = Camera.GameObject.Transform.GetFullTranslation();
|
||||
var forward = (Camera.Forward - position).Normalized();
|
||||
var right = Vector3.Cross(forward, Vector3.UnitZ).Normalized();
|
||||
HandleShootingRaycast();
|
||||
}
|
||||
}
|
||||
|
||||
var collisionManager = EngineUtil.SceneManager.CurrentScene!.FindFirstComponent<CollisionManagerComponent>();
|
||||
/// <summary>
|
||||
/// Handles the shooting raycast for the player.
|
||||
/// </summary>
|
||||
private void HandleShootingRaycast()
|
||||
{
|
||||
var position = Camera.GameObject.Transform.GetFullTranslation();
|
||||
var forward = (Camera.Forward - position).Normalized();
|
||||
var right = Vector3.Cross(forward, Vector3.UnitZ).Normalized();
|
||||
|
||||
var offsets = WeaponController.WeaponData.ShootPattern.GetShootPattern(forward, Vector3.UnitZ, right);
|
||||
foreach (var offset in offsets)
|
||||
var collisionManager = EngineUtil.SceneManager.CurrentScene!.FindFirstComponent<CollisionManagerComponent>();
|
||||
|
||||
var offsets = WeaponController.WeaponData.ShootPattern.GetShootPattern(forward, Vector3.UnitZ, right);
|
||||
foreach (var offset in offsets)
|
||||
{
|
||||
var direction = forward + offset;
|
||||
if (!collisionManager!.Raycast(position, direction, ["enemy", "wall"], out var result))
|
||||
{
|
||||
var direction = forward + offset;
|
||||
if (!collisionManager!.Raycast(position, direction, ["enemy", "wall"], out var result))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var enemyController = result.HitObject.GetComponent<EnemyController>();
|
||||
enemyController?.HealthController.TakeDamage(WeaponController.WeaponData.Damage);
|
||||
continue;
|
||||
}
|
||||
|
||||
var enemyController = result.HitObject.GetComponent<EnemyController>();
|
||||
enemyController?.HealthController.TakeDamage(WeaponController.WeaponData.Damage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,39 @@
|
||||
using DoomDeathmatch.Script.Model;
|
||||
using DoomDeathmatch.Script.MVC;
|
||||
|
||||
namespace DoomDeathmatch.Component.MVC.Score;
|
||||
|
||||
/// <summary>
|
||||
/// Manages the player's score, including updating and displaying it.
|
||||
/// </summary>
|
||||
public class ScoreController : Engine.Scene.Component.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Current score value.
|
||||
/// </summary>
|
||||
public int Score => _scoreModel.Score;
|
||||
|
||||
/// <summary>
|
||||
/// The score model containing the current score.
|
||||
/// </summary>
|
||||
private readonly ScoreModel _scoreModel = new();
|
||||
private ScoreView _scoreView = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The view responsible for displaying score information.
|
||||
/// </summary>
|
||||
private IView<ScoreModel> _scoreView = null!;
|
||||
|
||||
public override void Awake()
|
||||
{
|
||||
_scoreView = GameObject.GetComponent<ScoreView>()!;
|
||||
_scoreView = GameObject.GetComponent<IView<ScoreModel>>()!;
|
||||
_scoreView.UpdateView(_scoreModel);
|
||||
_scoreModel.ScoreChanged += _scoreView.UpdateView;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified value to the current score.
|
||||
/// </summary>
|
||||
/// <param name="parScore">The score value to add.</param>
|
||||
public void AddScore(int parScore)
|
||||
{
|
||||
_scoreModel.Score += parScore;
|
||||
|
||||
@@ -1,17 +1,29 @@
|
||||
using DoomDeathmatch.Script.Model;
|
||||
using DoomDeathmatch.Script.MVC;
|
||||
using Engine.Scene.Component.BuiltIn.Renderer;
|
||||
|
||||
namespace DoomDeathmatch.Component.MVC.Score;
|
||||
|
||||
public class ScoreView : Engine.Scene.Component.Component
|
||||
/// <summary>
|
||||
/// View component for displaying the score.
|
||||
/// </summary>
|
||||
public class ScoreView : Engine.Scene.Component.Component, IView<ScoreModel>
|
||||
{
|
||||
/// <summary>
|
||||
/// The text renderer used to display the score.
|
||||
/// </summary>
|
||||
private readonly TextRenderer _scoreTextRenderer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ScoreView"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parScoreTextRenderer">Text renderer for displaying the score text.</param>
|
||||
public ScoreView(TextRenderer parScoreTextRenderer)
|
||||
{
|
||||
_scoreTextRenderer = parScoreTextRenderer;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UpdateView(ScoreModel parScoreModel)
|
||||
{
|
||||
_scoreTextRenderer.Text = $"Счет: {parScoreModel.Score:00000}";
|
||||
|
||||
@@ -1,19 +1,41 @@
|
||||
using Engine.Util;
|
||||
using DoomDeathmatch.Script.MVC;
|
||||
using Engine.Util;
|
||||
|
||||
namespace DoomDeathmatch.Component.MVC.Timer;
|
||||
|
||||
/// <summary>
|
||||
/// Controls the timer functionality, including updates and view synchronization.
|
||||
/// </summary>
|
||||
public class TimerController : Engine.Scene.Component.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Triggered when the timer finishes.
|
||||
/// </summary>
|
||||
public event Action? OnFinished;
|
||||
|
||||
private readonly TickableTimer _tickableTimer = new(60 + 10);
|
||||
/// <summary>
|
||||
/// Internal timer that tracks the current countdown time.
|
||||
/// </summary>
|
||||
private readonly TickableTimer _tickableTimer;
|
||||
|
||||
private TimerView _timerView = null!;
|
||||
/// <summary>
|
||||
/// View responsible for displaying the timer's current state.
|
||||
/// </summary>
|
||||
private IView<TickableTimer> _timerView = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TimerController"/> class with an optional starting time.
|
||||
/// </summary>
|
||||
/// <param name="parTime">The initial time value. Defaults to 60 seconds.</param>
|
||||
public TimerController(float parTime = 60)
|
||||
{
|
||||
_tickableTimer = new TickableTimer(parTime);
|
||||
}
|
||||
|
||||
public override void Awake()
|
||||
{
|
||||
_timerView = GameObject.GetComponent<TimerView>()!;
|
||||
_timerView.UpdateView(_tickableTimer.CurrentTime);
|
||||
_timerView = GameObject.GetComponent<IView<TickableTimer>>()!;
|
||||
_timerView.UpdateView(_tickableTimer);
|
||||
|
||||
_tickableTimer.OnUpdate += OnTimeChanged;
|
||||
_tickableTimer.OnFinished += OnFinished;
|
||||
@@ -24,8 +46,12 @@ public class TimerController : Engine.Scene.Component.Component
|
||||
_tickableTimer.Update(parDeltaTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the timer view with the new time value.
|
||||
/// </summary>
|
||||
/// <param name="parTime">The updated time value.</param>
|
||||
private void OnTimeChanged(double parTime)
|
||||
{
|
||||
_timerView.UpdateView(parTime);
|
||||
_timerView.UpdateView(_tickableTimer);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,34 @@
|
||||
using Engine.Scene.Component.BuiltIn.Renderer;
|
||||
using DoomDeathmatch.Script.MVC;
|
||||
using Engine.Scene.Component.BuiltIn.Renderer;
|
||||
using Engine.Util;
|
||||
|
||||
namespace DoomDeathmatch.Component.MVC.Timer;
|
||||
|
||||
public class TimerView : Engine.Scene.Component.Component
|
||||
/// <summary>
|
||||
/// View component for displaying the timer.
|
||||
/// </summary>
|
||||
public class TimerView : Engine.Scene.Component.Component, IView<TickableTimer>
|
||||
{
|
||||
/// <summary>
|
||||
/// The text renderer used to display the timer.
|
||||
/// </summary>
|
||||
private readonly TextRenderer _timerTextRenderer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TimerView"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parTimerTextRenderer">Text renderer for displaying the timer text.</param>
|
||||
public TimerView(TextRenderer parTimerTextRenderer)
|
||||
{
|
||||
_timerTextRenderer = parTimerTextRenderer;
|
||||
}
|
||||
|
||||
public void UpdateView(double parTime)
|
||||
/// <inheritdoc/>
|
||||
public void UpdateView(TickableTimer parTime)
|
||||
{
|
||||
var seconds = Math.Floor(parTime) % 60;
|
||||
var minutes = Math.Floor(parTime / 60) % 60;
|
||||
var time = parTime.CurrentTime;
|
||||
var seconds = Math.Floor(time) % 60;
|
||||
var minutes = Math.Floor(time / 60) % 60;
|
||||
|
||||
_timerTextRenderer.Text = $"Время: {minutes:00}:{seconds:00}";
|
||||
}
|
||||
|
||||
17
DoomDeathmatch/src/Component/MVC/Weapon/AmmoData.cs
Normal file
17
DoomDeathmatch/src/Component/MVC/Weapon/AmmoData.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace DoomDeathmatch.Component.MVC.Weapon;
|
||||
|
||||
/// <summary>
|
||||
/// Data for ammo information.
|
||||
/// </summary>
|
||||
public struct AmmoData
|
||||
{
|
||||
/// <summary>
|
||||
/// The maximum amount of ammo.
|
||||
/// </summary>
|
||||
public int MaxAmmo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current amount of ammo.
|
||||
/// </summary>
|
||||
public int Ammo { get; set; }
|
||||
}
|
||||
15
DoomDeathmatch/src/Component/MVC/Weapon/IWeaponView.cs
Normal file
15
DoomDeathmatch/src/Component/MVC/Weapon/IWeaponView.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using DoomDeathmatch.Script.Model.Weapon;
|
||||
using DoomDeathmatch.Script.MVC;
|
||||
|
||||
namespace DoomDeathmatch.Component.MVC.Weapon;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for weapon view components.
|
||||
/// </summary>
|
||||
public interface IWeaponView : IView<WeaponData>, IView<AmmoData>
|
||||
{
|
||||
/// <summary>
|
||||
/// Starts the weapon fire animation.
|
||||
/// </summary>
|
||||
public void PlayFireAnimation();
|
||||
}
|
||||
@@ -3,23 +3,48 @@ using DoomDeathmatch.Script.Model.Weapon;
|
||||
|
||||
namespace DoomDeathmatch.Component.MVC.Weapon;
|
||||
|
||||
/// <summary>
|
||||
/// Manages weapon functionality, including shooting, reloading, and weapon selection.
|
||||
/// </summary>
|
||||
public class WeaponController : Engine.Scene.Component.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Triggered when a weapon is fired.
|
||||
/// </summary>
|
||||
public event Action<WeaponData>? OnWeaponShot;
|
||||
|
||||
/// <summary>
|
||||
/// The currently selected weapon's data.
|
||||
/// </summary>
|
||||
public WeaponData WeaponData => _weaponModel.SelectedWeapon;
|
||||
|
||||
/// <summary>
|
||||
/// The internal weapon model containing weapon-related data.
|
||||
/// </summary>
|
||||
private readonly WeaponModel _weaponModel = new();
|
||||
private WeaponView _weaponView = null!;
|
||||
|
||||
/// <summary>
|
||||
/// View responsible for displaying weapon information and animations.
|
||||
/// </summary>
|
||||
private IWeaponView _weaponView = null!;
|
||||
|
||||
public override void Awake()
|
||||
{
|
||||
_weaponView = GameObject.GetComponent<WeaponView>()!;
|
||||
_weaponView = GameObject.GetComponent<IWeaponView>()!;
|
||||
_weaponView.UpdateView(_weaponModel.SelectedWeapon);
|
||||
_weaponView.UpdateView(new AmmoData
|
||||
{
|
||||
Ammo = _weaponModel.SelectedWeapon.Ammo, MaxAmmo = _weaponModel.SelectedWeapon.MaxAmmo
|
||||
});
|
||||
|
||||
_weaponModel.OnWeaponSelected += WeaponSelected;
|
||||
WeaponSelected(null, _weaponModel.SelectedWeapon);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to fire the currently selected weapon.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if the weapon is fired successfully; otherwise, <c>false</c>.</returns>
|
||||
public bool TryShoot()
|
||||
{
|
||||
if (_weaponModel.SelectedWeapon.Ammo <= 0)
|
||||
@@ -30,16 +55,23 @@ public class WeaponController : Engine.Scene.Component.Component
|
||||
_weaponModel.SelectedWeapon.Ammo--;
|
||||
|
||||
OnWeaponShot?.Invoke(_weaponModel.SelectedWeapon);
|
||||
_weaponView.Fire();
|
||||
_weaponView.PlayFireAnimation();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reloads the currently selected weapon, restoring its ammo to the maximum value.
|
||||
/// </summary>
|
||||
public void Reload()
|
||||
{
|
||||
_weaponModel.SelectedWeapon.Ammo = _weaponModel.SelectedWeapon.MaxAmmo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new weapon to the weapon model if it does not already exist.
|
||||
/// </summary>
|
||||
/// <param name="parWeaponData">The weapon data to add.</param>
|
||||
public void AddWeapon(WeaponData parWeaponData)
|
||||
{
|
||||
if (_weaponModel.Weapons.Contains(parWeaponData))
|
||||
@@ -50,6 +82,10 @@ public class WeaponController : Engine.Scene.Component.Component
|
||||
_weaponModel.Weapons.Add(parWeaponData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a weapon to the model or merges its ammo with an existing weapon of the same type.
|
||||
/// </summary>
|
||||
/// <param name="parWeaponData">The weapon data to add or merge.</param>
|
||||
public void AddOrMergeWeapon(WeaponData parWeaponData)
|
||||
{
|
||||
if (!_weaponModel.Weapons.Contains(parWeaponData))
|
||||
@@ -62,6 +98,10 @@ public class WeaponController : Engine.Scene.Component.Component
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a weapon from the model by its index.
|
||||
/// </summary>
|
||||
/// <param name="parIndex">The index of the weapon to remove.</param>
|
||||
public void RemoveWeapon(int parIndex)
|
||||
{
|
||||
if (parIndex <= 0 || parIndex >= _weaponModel.Weapons.Count)
|
||||
@@ -75,6 +115,10 @@ public class WeaponController : Engine.Scene.Component.Component
|
||||
_weaponModel.Weapons.RemoveAt(parIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects a weapon from the model by its index.
|
||||
/// </summary>
|
||||
/// <param name="parIndex">The index of the weapon to select.</param>
|
||||
public void SelectWeapon(int parIndex)
|
||||
{
|
||||
if (parIndex >= _weaponModel.Weapons.Count)
|
||||
@@ -85,14 +129,26 @@ public class WeaponController : Engine.Scene.Component.Component
|
||||
_weaponModel.SelectedWeaponIndex = parIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the view when the selected weapon changes.
|
||||
/// </summary>
|
||||
/// <param name="parOldWeapon">The previously selected weapon.</param>
|
||||
/// <param name="parNewWeapon">The newly selected weapon.</param>
|
||||
private void WeaponSelected(WeaponData? parOldWeapon, WeaponData parNewWeapon)
|
||||
{
|
||||
if (parOldWeapon != null)
|
||||
{
|
||||
parOldWeapon.OnAmmoChanged -= _weaponView.UpdateAmmoView;
|
||||
parOldWeapon.OnAmmoChanged -= AmmoChanged;
|
||||
}
|
||||
|
||||
parNewWeapon.OnAmmoChanged += _weaponView.UpdateAmmoView;
|
||||
parNewWeapon.OnAmmoChanged += AmmoChanged;
|
||||
_weaponView.UpdateView(parNewWeapon);
|
||||
}
|
||||
|
||||
private void AmmoChanged(WeaponData parWeapon)
|
||||
{
|
||||
var ammoData = new AmmoData { Ammo = parWeapon.Ammo, MaxAmmo = parWeapon.MaxAmmo };
|
||||
|
||||
_weaponView.UpdateView(ammoData);
|
||||
}
|
||||
}
|
||||
@@ -6,16 +6,42 @@ using Engine.Util;
|
||||
|
||||
namespace DoomDeathmatch.Component.MVC.Weapon;
|
||||
|
||||
public class WeaponView : Engine.Scene.Component.Component
|
||||
/// <summary>
|
||||
/// View component for displaying weapon information such as name, ammo, and sprite.
|
||||
/// </summary>
|
||||
public class WeaponView : Engine.Scene.Component.Component, IWeaponView
|
||||
{
|
||||
/// <summary>
|
||||
/// The text renderer used to display the weapon name.
|
||||
/// </summary>
|
||||
private readonly TextRenderer _weaponName;
|
||||
|
||||
/// <summary>
|
||||
/// The text renderer used to display the weapon ammo.
|
||||
/// </summary>
|
||||
private readonly TextRenderer _weaponAmmo;
|
||||
|
||||
/// <summary>
|
||||
/// The box2d renderer used to display the weapon sprite.
|
||||
/// </summary>
|
||||
private readonly Box2DRenderer _weaponSprite;
|
||||
|
||||
/// <summary>
|
||||
/// The texture used for the weapon sprite when it is idle.
|
||||
/// </summary>
|
||||
private Texture? _idleTexture;
|
||||
|
||||
/// <summary>
|
||||
/// The animation player used to display the weapon sprite when it is firing.
|
||||
/// </summary>
|
||||
private AnimationPlayer<Texture>? _weaponFireAnimation;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WeaponView"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parWeaponName">Text renderer for displaying the weapon name.</param>
|
||||
/// <param name="parWeaponAmmo">Text renderer for displaying the ammo count.</param>
|
||||
/// <param name="parWeaponSprite">Renderer for displaying the weapon sprite.</param>
|
||||
public WeaponView(TextRenderer parWeaponName, TextRenderer parWeaponAmmo, Box2DRenderer parWeaponSprite)
|
||||
{
|
||||
_weaponName = parWeaponName;
|
||||
@@ -28,9 +54,9 @@ public class WeaponView : Engine.Scene.Component.Component
|
||||
_weaponFireAnimation?.Update(parDeltaTime);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UpdateView(WeaponData parWeaponData)
|
||||
{
|
||||
UpdateAmmoView(parWeaponData);
|
||||
_weaponName.Text = $"Оружие: {parWeaponData.Name}";
|
||||
|
||||
_idleTexture = EngineUtil.AssetResourceManager.Load<Texture>(parWeaponData.IdleTexture);
|
||||
@@ -50,21 +76,30 @@ public class WeaponView : Engine.Scene.Component.Component
|
||||
_weaponFireAnimation.OnFinish += OnAnimationFinish;
|
||||
}
|
||||
|
||||
public void UpdateAmmoView(WeaponData parWeaponData)
|
||||
/// <inheritdoc/>
|
||||
public void UpdateView(AmmoData parAmmoData)
|
||||
{
|
||||
_weaponAmmo.Text = $"Патроны: {parWeaponData.Ammo}/{parWeaponData.MaxAmmo}";
|
||||
_weaponAmmo.Text = $"Патроны: {parAmmoData.Ammo}/{parAmmoData.MaxAmmo}";
|
||||
}
|
||||
|
||||
public void Fire()
|
||||
/// <inheritdoc/>
|
||||
public void PlayFireAnimation()
|
||||
{
|
||||
_weaponFireAnimation?.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the animation frame change event.
|
||||
/// </summary>
|
||||
/// <param name="parFrameTexture">The current frame texture.</param>
|
||||
private void OnAnimationFrame(Texture parFrameTexture)
|
||||
{
|
||||
_weaponSprite.Texture = parFrameTexture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the animation finish event.
|
||||
/// </summary>
|
||||
private void OnAnimationFinish()
|
||||
{
|
||||
_weaponSprite.Texture = _idleTexture;
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
namespace DoomDeathmatch.Component;
|
||||
|
||||
/// <summary>
|
||||
/// Component for spawning objects using <see cref="ObjectSpawner"/>.
|
||||
/// </summary>
|
||||
public class ObjectSpawnerComponent(ObjectSpawner parObjectSpawner) : Engine.Scene.Component.Component
|
||||
{
|
||||
public override void Update(double parDeltaTime)
|
||||
|
||||
@@ -3,16 +3,44 @@ using OpenTK.Mathematics;
|
||||
|
||||
namespace DoomDeathmatch.Component.Physics.Collision;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an axis-aligned bounding box (AABB) collider component used for collision detection.
|
||||
/// </summary>
|
||||
public class AABBColliderComponent : Engine.Scene.Component.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Triggered when a collision occurs with another <see cref="AABBColliderComponent"/>.
|
||||
/// </summary>
|
||||
public event Action<AABBColliderComponent>? OnCollision;
|
||||
|
||||
/// <summary>
|
||||
/// The collider defining the bounds of this component.
|
||||
/// </summary>
|
||||
public AABBCollider Collider { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Offset applied to the collider position relative to the parent object.
|
||||
/// </summary>
|
||||
public Vector3 Offset { get; set; } = Vector3.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// A set of groups that this collider belongs to.
|
||||
/// </summary>
|
||||
public ISet<string> ColliderGroups => _colliderGroups;
|
||||
|
||||
/// <summary>
|
||||
/// A set of groups that this collider will not collide with.
|
||||
/// </summary>
|
||||
public ISet<string> ExcludeColliderCollideGroups => _excludeColliderCollideGroups;
|
||||
|
||||
/// <summary>
|
||||
/// A set of groups that this collider belongs to.
|
||||
/// </summary>
|
||||
private readonly HashSet<string> _colliderGroups = ["default"];
|
||||
|
||||
/// <summary>
|
||||
/// A set of groups that this collider will not collide with.
|
||||
/// </summary>
|
||||
private readonly HashSet<string> _excludeColliderCollideGroups = [];
|
||||
|
||||
public override void Update(double parDeltaTime)
|
||||
@@ -20,13 +48,12 @@ public class AABBColliderComponent : Engine.Scene.Component.Component
|
||||
Collider.Position = GameObject.Transform.GetFullTranslation() + Offset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the collision logic for this collider with the specified collider.
|
||||
/// </summary>
|
||||
/// <param name="parCollider">The collider with which this component collided.</param>
|
||||
public void CollideWith(AABBColliderComponent parCollider)
|
||||
{
|
||||
OnCollision?.Invoke(parCollider);
|
||||
}
|
||||
|
||||
public bool InColliderGroup(string parGroup)
|
||||
{
|
||||
return ColliderGroups.Contains(parGroup);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
namespace DoomDeathmatch.Component.Physics.Collision;
|
||||
|
||||
/// <summary>
|
||||
/// A force field component that applies collision-based velocity changes to rigid bodies.
|
||||
/// </summary>
|
||||
public class ColliderForceFieldComponent : Engine.Scene.Component.Component
|
||||
{
|
||||
private AABBColliderComponent _collider = null!;
|
||||
@@ -12,6 +15,10 @@ public class ColliderForceFieldComponent : Engine.Scene.Component.Component
|
||||
_collider.OnCollision += OnCollision;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles collision events and modifies the velocity of colliding rigid bodies.
|
||||
/// </summary>
|
||||
/// <param name="parCollider">The collider involved in the collision.</param>
|
||||
private void OnCollision(AABBColliderComponent parCollider)
|
||||
{
|
||||
var rigidbody = parCollider.GameObject.GetComponent<RigidbodyComponent>();
|
||||
@@ -27,6 +34,6 @@ public class ColliderForceFieldComponent : Engine.Scene.Component.Component
|
||||
return;
|
||||
}
|
||||
|
||||
rigidbody.Velocity -= normal * (speedAlongNormal * 1.75f);
|
||||
rigidbody.Velocity -= normal * speedAlongNormal;
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,14 @@ using OpenTK.Mathematics;
|
||||
|
||||
namespace DoomDeathmatch.Component.Physics.Collision;
|
||||
|
||||
/// <summary>
|
||||
/// Manages collisions between AABB colliders in the current scene.
|
||||
/// </summary>
|
||||
public class CollisionManagerComponent : Engine.Scene.Component.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// A list of colliders in the current scene.
|
||||
/// </summary>
|
||||
private List<AABBColliderComponent> _colliders = [];
|
||||
|
||||
public override void PreUpdate(double parDeltaTime)
|
||||
@@ -24,34 +30,19 @@ public class CollisionManagerComponent : Engine.Scene.Component.Component
|
||||
{
|
||||
var colliderB = _colliders[j];
|
||||
|
||||
var canCollideAB = colliderA.ExcludeColliderCollideGroups.Count == 0 ||
|
||||
!colliderB.ColliderGroups.Overlaps(colliderA.ExcludeColliderCollideGroups);
|
||||
var canCollideBA = colliderB.ExcludeColliderCollideGroups.Count == 0 ||
|
||||
!colliderA.ColliderGroups.Overlaps(colliderB.ExcludeColliderCollideGroups);
|
||||
|
||||
if (!canCollideAB && !canCollideBA)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!colliderA.Collider.Intersects(colliderB.Collider))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (canCollideAB)
|
||||
{
|
||||
colliderA.CollideWith(colliderB);
|
||||
}
|
||||
|
||||
if (canCollideBA)
|
||||
{
|
||||
colliderB.CollideWith(colliderA);
|
||||
}
|
||||
PerformCollisionCheck(colliderA, colliderB);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a raycast and finds the closest collider intersected by the ray.
|
||||
/// </summary>
|
||||
/// <param name="parStart">The starting point of the ray.</param>
|
||||
/// <param name="parDirection">The direction of the ray.</param>
|
||||
/// <param name="parColliderGroups">The collider groups to consider for the raycast.</param>
|
||||
/// <param name="parResult">The result of the raycast, if an intersection occurs.</param>
|
||||
/// <returns><c>true</c> if an intersection occurs; otherwise, <c>false</c>.</returns>
|
||||
public bool Raycast(Vector3 parStart, Vector3 parDirection, HashSet<string> parColliderGroups,
|
||||
[MaybeNullWhen(false)] out RaycastResult parResult)
|
||||
{
|
||||
@@ -90,6 +81,48 @@ public class CollisionManagerComponent : Engine.Scene.Component.Component
|
||||
return parResult != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a collision check between two colliders and invokes the appropriate collision events.
|
||||
/// </summary>
|
||||
/// <param name="parColliderA">The first collider.</param>
|
||||
/// <param name="parColliderB">The second collider.</param>
|
||||
private static void PerformCollisionCheck(AABBColliderComponent parColliderA, AABBColliderComponent parColliderB)
|
||||
{
|
||||
var canCollideAB = parColliderA.ExcludeColliderCollideGroups.Count == 0 ||
|
||||
!parColliderB.ColliderGroups.Overlaps(parColliderA.ExcludeColliderCollideGroups);
|
||||
var canCollideBA = parColliderB.ExcludeColliderCollideGroups.Count == 0 ||
|
||||
!parColliderA.ColliderGroups.Overlaps(parColliderB.ExcludeColliderCollideGroups);
|
||||
|
||||
if (!canCollideAB && !canCollideBA)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!parColliderA.Collider.Intersects(parColliderB.Collider))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (canCollideAB)
|
||||
{
|
||||
parColliderA.CollideWith(parColliderB);
|
||||
}
|
||||
|
||||
if (canCollideBA)
|
||||
{
|
||||
parColliderB.CollideWith(parColliderA);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks for intersection between a ray and an AABB collider.
|
||||
/// </summary>
|
||||
/// <param name="parOrigin">The origin of the ray.</param>
|
||||
/// <param name="parDirection">The direction of the ray.</param>
|
||||
/// <param name="parCollider">The collider to test against.</param>
|
||||
/// <param name="parHitPoint">The intersection point, if the ray intersects.</param>
|
||||
/// <param name="parHitNormal">The normal of the intersection surface.</param>
|
||||
/// <returns><c>true</c> if the ray intersects the collider; otherwise, <c>false</c>.</returns>
|
||||
private static bool RaycastAABB(Vector3 parOrigin, Vector3 parDirection, AABBCollider parCollider,
|
||||
out Vector3 parHitPoint,
|
||||
out Vector3 parHitNormal)
|
||||
|
||||
@@ -2,11 +2,24 @@
|
||||
|
||||
namespace DoomDeathmatch.Component.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// Applies drag to a rigidbody to simulate resistance to motion.
|
||||
/// </summary>
|
||||
public class DragComponent : Engine.Scene.Component.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The drag coefficient applied to the velocity of the rigidbody.
|
||||
/// </summary>
|
||||
public float Drag { get; set; } = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// A multiplier applied to each axis of the velocity when calculating drag.
|
||||
/// </summary>
|
||||
public Vector3 Multiplier { get; set; } = Vector3.One;
|
||||
|
||||
/// <summary>
|
||||
/// The rigidbody to apply drag to.
|
||||
/// </summary>
|
||||
private RigidbodyComponent _rigidbody = null!;
|
||||
|
||||
public override void Awake()
|
||||
|
||||
@@ -2,24 +2,45 @@
|
||||
|
||||
namespace DoomDeathmatch.Component.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// Simulates the effect of gravity on a rigidbody.
|
||||
/// </summary>
|
||||
public class GravityComponent : Engine.Scene.Component.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates whether the object is currently in the air.
|
||||
/// </summary>
|
||||
public bool IsInAir { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The strength of the gravitational force.
|
||||
/// </summary>
|
||||
public float Strength { get; set; } = 10.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The direction of the gravitational force.
|
||||
/// </summary>
|
||||
public Vector3 Direction
|
||||
{
|
||||
get => _direction;
|
||||
set => _direction = value.Normalized();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The height at which gravity stops affecting the object.
|
||||
/// </summary>
|
||||
public float Floor { get; set; } = 5.0f;
|
||||
|
||||
|
||||
private RigidbodyComponent _rigidbody = null!;
|
||||
/// <summary>
|
||||
/// The direction of the gravitational force.
|
||||
/// </summary>
|
||||
private Vector3 _direction = -Vector3.UnitZ;
|
||||
|
||||
/// <summary>
|
||||
/// The rigidbody to apply gravity to.
|
||||
/// </summary>
|
||||
private RigidbodyComponent _rigidbody = null!;
|
||||
|
||||
public override void Awake()
|
||||
{
|
||||
_rigidbody = GameObject.GetComponent<RigidbodyComponent>()!;
|
||||
|
||||
@@ -2,11 +2,24 @@
|
||||
|
||||
namespace DoomDeathmatch.Component.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a rigidbody component that simulates physics behavior such as force, velocity, and acceleration.
|
||||
/// </summary>
|
||||
public class RigidbodyComponent : Engine.Scene.Component.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The mass of the rigidbody.
|
||||
/// </summary>
|
||||
public float Mass { get; set; } = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the rigidbody is static and unaffected by forces.
|
||||
/// </summary>
|
||||
public bool IsStatic { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// The force currently applied to the rigidbody.
|
||||
/// </summary>
|
||||
public Vector3 Force
|
||||
{
|
||||
get => _force;
|
||||
@@ -21,6 +34,9 @@ public class RigidbodyComponent : Engine.Scene.Component.Component
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The velocity of the rigidbody.
|
||||
/// </summary>
|
||||
public Vector3 Velocity
|
||||
{
|
||||
get => _velocity;
|
||||
@@ -35,11 +51,22 @@ public class RigidbodyComponent : Engine.Scene.Component.Component
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The force currently applied to the rigidbody.
|
||||
/// </summary>
|
||||
private Vector3 _force = Vector3.Zero;
|
||||
private Vector3 _velocity = Vector3.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// The acceleration of the rigidbody.
|
||||
/// </summary>
|
||||
private Vector3 _acceleration = Vector3.Zero;
|
||||
|
||||
public override void Update(double parDeltaTime)
|
||||
/// <summary>
|
||||
/// The velocity of the rigidbody.
|
||||
/// </summary>
|
||||
private Vector3 _velocity = Vector3.Zero;
|
||||
|
||||
public override void PostUpdate(double parDeltaTime)
|
||||
{
|
||||
if (IsStatic)
|
||||
{
|
||||
|
||||
@@ -2,20 +2,39 @@
|
||||
|
||||
namespace DoomDeathmatch.Component.UI;
|
||||
|
||||
/// <summary>
|
||||
/// A component responsible for managing and selecting menu items in a UI.
|
||||
/// </summary>
|
||||
public class MenuControllerComponent : Engine.Scene.Component.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// A dictionary of menu items, mapping their names to their game objects.
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, GameObject> _menuItems = new();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new menu item to the menu.
|
||||
/// </summary>
|
||||
/// <param name="parName">The name of the menu item.</param>
|
||||
/// <param name="parGameObject">The game object representing the menu item.</param>
|
||||
public void AddMenuItem(string parName, GameObject parGameObject)
|
||||
{
|
||||
_menuItems.Add(parName, parGameObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a menu item from the menu by name.
|
||||
/// </summary>
|
||||
/// <param name="parName">The name of the menu item to remove.</param>
|
||||
public void RemoveMenuItem(string parName)
|
||||
{
|
||||
_menuItems.Remove(parName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects a menu item by name, enabling only the selected item.
|
||||
/// </summary>
|
||||
/// <param name="parName">The name of the menu item to select.</param>
|
||||
public void SelectMenuItem(string parName)
|
||||
{
|
||||
foreach (var (name, menuItem) in _menuItems)
|
||||
|
||||
@@ -3,17 +3,44 @@ using Engine.Util;
|
||||
|
||||
namespace DoomDeathmatch.Component.UI;
|
||||
|
||||
/// <summary>
|
||||
/// A component that manages selection of UI components using keyboard inputs.
|
||||
/// </summary>
|
||||
public class SelectorComponent : Engine.Scene.Component.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Event triggered when a UI component is selected.
|
||||
/// </summary>
|
||||
public event Action<UiComponent>? OnSelect;
|
||||
|
||||
/// <summary>
|
||||
/// The list of selectable UI components.
|
||||
/// </summary>
|
||||
public List<UiComponent> Children { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The key used to navigate to the next item in the list.
|
||||
/// </summary>
|
||||
public KeyboardButtonCode NextKey { get; set; } = KeyboardButtonCode.Down;
|
||||
|
||||
/// <summary>
|
||||
/// The key used to navigate to the previous item in the list.
|
||||
/// </summary>
|
||||
public KeyboardButtonCode PrevKey { get; set; } = KeyboardButtonCode.Up;
|
||||
|
||||
/// <summary>
|
||||
/// The key used to select the currently selected item.
|
||||
/// </summary>
|
||||
public KeyboardButtonCode SelectKey { get; set; } = KeyboardButtonCode.Space;
|
||||
|
||||
/// <summary>
|
||||
/// The input handler used to check for keyboard input.
|
||||
/// </summary>
|
||||
private readonly IInputHandler _inputHandler = EngineUtil.InputHandler;
|
||||
|
||||
/// <summary>
|
||||
/// The index of the currently selected item.
|
||||
/// </summary>
|
||||
private int _selectedIndex;
|
||||
|
||||
public override void Start()
|
||||
@@ -58,11 +85,17 @@ public class SelectorComponent : Engine.Scene.Component.Component
|
||||
UpdatePosition();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggers the <see cref="OnSelect"/> event for the currently selected child.
|
||||
/// </summary>
|
||||
private void SelectionChanged()
|
||||
{
|
||||
OnSelect?.Invoke(Children[_selectedIndex]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the position of the selector based on the selected child component.
|
||||
/// </summary>
|
||||
private void UpdatePosition()
|
||||
{
|
||||
var child = Children[_selectedIndex];
|
||||
@@ -77,6 +110,10 @@ public class SelectorComponent : Engine.Scene.Component.Component
|
||||
GameObject.Transform.Size.Y = scale.Y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects a UI component when the mouse hovers over it.
|
||||
/// </summary>
|
||||
/// <param name="parComponent">The UI component to select.</param>
|
||||
private void Select(UiComponent parComponent)
|
||||
{
|
||||
var index = Children.IndexOf(parComponent);
|
||||
|
||||
@@ -3,9 +3,19 @@ using OpenTK.Mathematics;
|
||||
|
||||
namespace DoomDeathmatch.Component.UI;
|
||||
|
||||
/// <summary>
|
||||
/// A UI container that arranges its child components in a stack, either horizontally or vertically.
|
||||
/// </summary>
|
||||
public class StackComponent : UiContainerComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// The child components contained in this stack.
|
||||
/// </summary>
|
||||
public List<UiComponent> Children { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The orientation of the stack, which determines whether the components are arranged horizontally or vertically.
|
||||
/// </summary>
|
||||
public Orientation Orientation { get; set; } = Orientation.Vertical;
|
||||
|
||||
public override void Update(double parDeltaTime)
|
||||
|
||||
@@ -4,11 +4,24 @@ using OpenTK.Mathematics;
|
||||
|
||||
namespace DoomDeathmatch.Component.UI;
|
||||
|
||||
/// <summary>
|
||||
/// A component that handles text alignment for UI elements that use text rendering.
|
||||
/// </summary>
|
||||
public class TextAlignComponent : Engine.Scene.Component.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The alignment of the text within its bounding box.
|
||||
/// </summary>
|
||||
public Align Alignment { get; set; } = Align.Left;
|
||||
|
||||
/// <summary>
|
||||
/// The text renderer used to measure the text size.
|
||||
/// </summary>
|
||||
private TextRenderer _textRenderer = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The cached text to avoid unnecessary measurements.
|
||||
/// </summary>
|
||||
private string? _cachedText;
|
||||
|
||||
public override void Awake()
|
||||
@@ -36,6 +49,11 @@ public class TextAlignComponent : Engine.Scene.Component.Component
|
||||
GameObject.Transform.Translation.Xy = offset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the offset for the text alignment based on the given size.
|
||||
/// </summary>
|
||||
/// <param name="parSize">The size of the text.</param>
|
||||
/// <returns>The calculated offset.</returns>
|
||||
public Vector2 GetOffset(Vector2 parSize)
|
||||
{
|
||||
return Alignment switch
|
||||
|
||||
@@ -3,26 +3,60 @@ using Engine.Util;
|
||||
|
||||
namespace DoomDeathmatch.Component.UI;
|
||||
|
||||
/// <summary>
|
||||
/// A component that handles text input from the user, such as typing and backspacing.
|
||||
/// </summary>
|
||||
public class TextInputComponent : Engine.Scene.Component.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Event triggered when there is new input.
|
||||
/// </summary>
|
||||
public event Action<string>? OnInput;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the input component is active and accepting input.
|
||||
/// </summary>
|
||||
public bool IsActive { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// The current input string the user has typed.
|
||||
/// </summary>
|
||||
public string Input { get; private set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// The time delay before input is registered after a key is pressed.
|
||||
/// </summary>
|
||||
public float InputDelay
|
||||
{
|
||||
get => (float)_inputTimer.TotalTime;
|
||||
set => _inputTimer.TotalTime = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The keys that are accepted as input.
|
||||
/// </summary>
|
||||
private readonly KeyboardButtonCode[] _acceptedKeys =
|
||||
KeyboardButtonCodeHelper.GetAllPrintableKeys().Append(KeyboardButtonCode.Backspace).ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// The input handler used to check for keyboard input.
|
||||
/// </summary>
|
||||
private readonly IInputHandler _inputHandler = EngineUtil.InputHandler;
|
||||
|
||||
/// <summary>
|
||||
/// The timer used to delay input.
|
||||
/// </summary>
|
||||
private readonly TickableTimer _inputTimer;
|
||||
|
||||
/// <summary>
|
||||
/// The keys that were last pressed.
|
||||
/// </summary>
|
||||
private readonly HashSet<KeyboardButtonCode> _lastKeys = [];
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TextInputComponent"/> class with an optional input delay.
|
||||
/// </summary>
|
||||
/// <param name="parInputDelay">The input delay before registering the next key press.</param>
|
||||
public TextInputComponent(float parInputDelay = 0.2f)
|
||||
{
|
||||
_inputTimer = new TickableTimer(parInputDelay) { CurrentTime = 0 };
|
||||
|
||||
@@ -6,16 +6,44 @@ using Math = System.Math;
|
||||
|
||||
namespace DoomDeathmatch.Component.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a UI element that can interact with user input.
|
||||
/// </summary>
|
||||
public class UiComponent : Engine.Scene.Component.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Invoked when this UI component is clicked.
|
||||
/// </summary>
|
||||
public event Action<UiComponent>? OnClick;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the mouse is hovering over this UI component.
|
||||
/// </summary>
|
||||
public event Action<UiComponent>? OnMouseOver;
|
||||
|
||||
/// <summary>
|
||||
/// The parent container of this UI component.
|
||||
/// </summary>
|
||||
public UiContainerComponent? Container { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The alignment of this component's center within its container.
|
||||
/// </summary>
|
||||
public Anchor Center { get; set; } = Anchor.Center;
|
||||
|
||||
/// <summary>
|
||||
/// The anchor point of this component relative to its container.
|
||||
/// </summary>
|
||||
public Anchor Anchor { get; set; } = Anchor.Center;
|
||||
|
||||
/// <summary>
|
||||
/// The positional offset of this component relative to its anchor point.
|
||||
/// </summary>
|
||||
public Vector2 Offset { get; set; } = Vector2.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// The input handler used to check for mouse input.
|
||||
/// </summary>
|
||||
private readonly IInputHandler _inputHandler = EngineUtil.InputHandler;
|
||||
|
||||
public override void Update(double parDeltaTime)
|
||||
@@ -26,8 +54,8 @@ public class UiComponent : Engine.Scene.Component.Component
|
||||
}
|
||||
|
||||
var size = GameObject.Transform.Size * GameObject.Transform.Scale;
|
||||
GameObject.Transform.Translation.Xy = GetAnchorPosition(Container.GameObject.Transform.Size.Xy, Anchor) + Offset -
|
||||
GetAnchorPosition(size.Xy, Center);
|
||||
GameObject.Transform.Translation.Xy = Anchor.GetPosition(Container.GameObject.Transform.Size.Xy) + Offset -
|
||||
Center.GetPosition(size.Xy);
|
||||
|
||||
var transformMatrix = GameObject.Transform.FullTransformMatrix;
|
||||
var actualSize = transformMatrix.ExtractScale();
|
||||
@@ -46,30 +74,11 @@ public class UiComponent : Engine.Scene.Component.Component
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manually triggers the click event for this component.
|
||||
/// </summary>
|
||||
public void InvokeClick()
|
||||
{
|
||||
OnClick?.Invoke(this);
|
||||
}
|
||||
|
||||
private static Vector2 GetAnchorPosition(Vector2 parSize, Anchor parAnchor)
|
||||
{
|
||||
return parSize * GetAnchorRatio(parAnchor);
|
||||
}
|
||||
|
||||
private static Vector2 GetAnchorRatio(Anchor parAnchor)
|
||||
{
|
||||
return parAnchor switch
|
||||
{
|
||||
Anchor.TopLeft => new Vector2(-0.5f, 0.5f),
|
||||
Anchor.TopCenter => new Vector2(0, 0.5f),
|
||||
Anchor.TopRight => new Vector2(0.5f, 0.5f),
|
||||
Anchor.CenterLeft => new Vector2(-0.5f, 0),
|
||||
Anchor.Center => new Vector2(0, 0),
|
||||
Anchor.CenterRight => new Vector2(0.5f, 0),
|
||||
Anchor.BottomLeft => new Vector2(-0.5f, -0.5f),
|
||||
Anchor.BottomCenter => new Vector2(0, -0.5f),
|
||||
Anchor.BottomRight => new Vector2(0.5f, -0.5f),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(parAnchor), parAnchor, null)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,24 @@ using OpenTK.Mathematics;
|
||||
|
||||
namespace DoomDeathmatch.Component.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a container for UI components, responsible for handling mouse position and camera interactions.
|
||||
/// </summary>
|
||||
public class UiContainerComponent : UiComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// The camera used to calculate the mouse position in world space.
|
||||
/// </summary>
|
||||
public Camera? Camera { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current mouse position in world coordinates.
|
||||
/// </summary>
|
||||
public Vector3 MousePosition { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The input handler used to check for mouse input.
|
||||
/// </summary>
|
||||
private readonly IInputHandler _inputHandler = EngineUtil.InputHandler;
|
||||
|
||||
public override void Update(double parDeltaTime)
|
||||
|
||||
@@ -3,9 +3,19 @@ using OpenTK.Mathematics;
|
||||
|
||||
namespace DoomDeathmatch.Component.Util;
|
||||
|
||||
/// <summary>
|
||||
/// A component that aligns the orientation of an object to always face a target, similar to a billboard.
|
||||
/// </summary>
|
||||
public class BillboardComponent : Engine.Scene.Component.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The target transform that the object will face.
|
||||
/// </summary>
|
||||
public Transform? Target { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The upward direction used as a reference for recalculating orientation.
|
||||
/// </summary>
|
||||
public Vector3 Up { get; set; } = Vector3.UnitZ;
|
||||
|
||||
public override void Update(double parDeltaTime)
|
||||
@@ -17,15 +27,28 @@ public class BillboardComponent : Engine.Scene.Component.Component
|
||||
|
||||
var targetPosition = Target.GetFullTranslation();
|
||||
var currentPosition = GameObject.Transform.GetFullTranslation();
|
||||
var rotationMatrix = CalculateRotationMatrix(currentPosition, targetPosition, Up);
|
||||
|
||||
var forward = targetPosition - currentPosition;
|
||||
forward -= Vector3.Dot(forward, Up) * Up;
|
||||
GameObject.Transform.Rotation = Quaternion.FromMatrix(rotationMatrix);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the rotation matrix required to align the object to face the target.
|
||||
/// </summary>
|
||||
/// <param name="parCurrentPosition">The current position of the object.</param>
|
||||
/// <param name="parTargetPosition">The target position the object should face.</param>
|
||||
/// <param name="parUp">The upward direction used for orientation adjustment.</param>
|
||||
/// <returns>A <see cref="Matrix3"/> representing the required rotation.</returns>
|
||||
private static Matrix3 CalculateRotationMatrix(Vector3 parCurrentPosition, Vector3 parTargetPosition, Vector3 parUp)
|
||||
{
|
||||
var forward = parTargetPosition - parCurrentPosition;
|
||||
forward -= Vector3.Dot(forward, parUp) * parUp;
|
||||
if (forward.LengthSquared > 0)
|
||||
{
|
||||
forward.Normalize();
|
||||
}
|
||||
|
||||
var right = Vector3.Cross(Up, forward);
|
||||
var right = Vector3.Cross(parUp, forward);
|
||||
if (right.LengthSquared > 0)
|
||||
{
|
||||
right.Normalize();
|
||||
@@ -39,6 +62,6 @@ public class BillboardComponent : Engine.Scene.Component.Component
|
||||
right.Z, recalculatedUp.Z, forward.Z
|
||||
);
|
||||
|
||||
GameObject.Transform.Rotation = Quaternion.FromMatrix(rotationMatrix);
|
||||
return rotationMatrix;
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,19 @@ using OpenTK.Mathematics;
|
||||
|
||||
namespace DoomDeathmatch.Component.Util;
|
||||
|
||||
/// <summary>
|
||||
/// A component that synchronizes the size of an object with a target object's size, applying a multiplier.
|
||||
/// </summary>
|
||||
public class CopySizeComponent : Engine.Scene.Component.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The target transform whose size is copied.
|
||||
/// </summary>
|
||||
public Transform? Target { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The multiplier applied to the target's size when copying.
|
||||
/// </summary>
|
||||
public Vector3 Multiplier { get; set; } = Vector3.One;
|
||||
|
||||
public override void Update(double parDeltaTime)
|
||||
|
||||
@@ -4,8 +4,14 @@ using Engine.Util;
|
||||
|
||||
namespace DoomDeathmatch.Component.Util;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a fireball that deals damage on collision.
|
||||
/// </summary>
|
||||
public class FireballComponent : Engine.Scene.Component.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The amount of damage the fireball inflicts on collision.
|
||||
/// </summary>
|
||||
public float Damage { get; set; }
|
||||
|
||||
public override void Awake()
|
||||
@@ -17,6 +23,10 @@ public class FireballComponent : Engine.Scene.Component.Component
|
||||
collider.OnCollision += OnCollision;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles collision logic, dealing damage to colliding objects.
|
||||
/// </summary>
|
||||
/// <param name="parCollider">The collider of the object this fireball collided with.</param>
|
||||
private void OnCollision(AABBColliderComponent parCollider)
|
||||
{
|
||||
var healthController = parCollider.GameObject.GetComponent<HealthController>();
|
||||
|
||||
@@ -5,12 +5,24 @@ using OpenTK.Mathematics;
|
||||
|
||||
namespace DoomDeathmatch.Component.Util;
|
||||
|
||||
/// <summary>
|
||||
/// Handles player movement based on input and applies rotation to the player object.
|
||||
/// </summary>
|
||||
public class PlayerMovementComponent : Engine.Scene.Component.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The speed at which the player rotates.
|
||||
/// </summary>
|
||||
public float RotationSpeed { get; set; } = 110.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Handles input from the player.
|
||||
/// </summary>
|
||||
private readonly IInputHandler _inputHandler = EngineUtil.InputHandler;
|
||||
|
||||
/// <summary>
|
||||
/// Controls the movement logic of the player.
|
||||
/// </summary>
|
||||
private MovementController _movementController = null!;
|
||||
|
||||
public override void Awake()
|
||||
|
||||
@@ -57,7 +57,7 @@ public static class PlayScene
|
||||
var gameControllerObject = GameObjectUtil.CreateGameObject(scene, [
|
||||
new GameController(menuController),
|
||||
|
||||
new TimerController(),
|
||||
new TimerController(5 * 60),
|
||||
plaTimerView,
|
||||
|
||||
new ScoreController(),
|
||||
@@ -88,11 +88,10 @@ public static class PlayScene
|
||||
var monsterSpawnPoints = MeshToSpawnPoints(
|
||||
EngineUtil.AssetResourceManager.Load<Mesh>($"map/{parMapName}/monster_spawners.obj"));
|
||||
|
||||
var valuableSpawnPoints = MeshToSpawnPoints(
|
||||
EngineUtil.AssetResourceManager.Load<Mesh>($"map/{parMapName}/valuable_spawners.obj"));
|
||||
|
||||
parScene.AddChild(mapObject, collidersObject);
|
||||
|
||||
#region Monster Objects
|
||||
|
||||
var monsterVector3ValueProvider =
|
||||
new RandomListValueProvider<Vector3>(monsterSpawnPoints.Select(ConstValueProvider<Vector3>.Create));
|
||||
|
||||
@@ -114,6 +113,12 @@ public static class PlayScene
|
||||
|
||||
parScene.AddChild(parGameControllerObject, monsterSpawnerObject);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Consumable Objects
|
||||
|
||||
var valuableSpawnPoints = MeshToSpawnPoints(
|
||||
EngineUtil.AssetResourceManager.Load<Mesh>($"map/{parMapName}/valuable_spawners.obj"));
|
||||
var consumableVector3ValueProvider =
|
||||
new RandomListValueProvider<Vector3>(valuableSpawnPoints.Select(ConstValueProvider<Vector3>.Create));
|
||||
|
||||
@@ -157,6 +162,8 @@ public static class PlayScene
|
||||
|
||||
parScene.AddChild(parGameControllerObject, consumableSpawnerObject);
|
||||
|
||||
#endregion
|
||||
|
||||
return mapObject;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,17 +3,55 @@ using Engine.Util;
|
||||
|
||||
namespace DoomDeathmatch.Script;
|
||||
|
||||
public class AnimationPlayer<T>(float parInterval) : IUpdate
|
||||
/// <summary>
|
||||
/// Handles the playback of animations consisting of frames of type <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the animation frames.</typeparam>
|
||||
public class AnimationPlayer<T> : IUpdate
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when the animation finishes playing.
|
||||
/// </summary>
|
||||
public event Action? OnFinish;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the animation advances to a new frame.
|
||||
/// </summary>
|
||||
public event Action<T>? OnFrameChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the animation is currently playing.
|
||||
/// </summary>
|
||||
public bool IsPlaying { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of frames in the animation.
|
||||
/// </summary>
|
||||
public List<T> Frames { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The index of the next frame to be displayed.
|
||||
/// </summary>
|
||||
public int NextFrame { get; private set; }
|
||||
|
||||
private readonly TickableTimer _timer = new(parInterval);
|
||||
/// <summary>
|
||||
/// The timer used to control frame intervals.
|
||||
/// </summary>
|
||||
private readonly TickableTimer _timer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AnimationPlayer{T}"/> class with a specified frame interval.
|
||||
/// </summary>
|
||||
/// <param name="parInterval">The total time in seconds for whole animation.</param>
|
||||
public AnimationPlayer(float parInterval)
|
||||
{
|
||||
_timer = new TickableTimer(parInterval);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the animation's playback state based on the elapsed time.
|
||||
/// </summary>
|
||||
/// <param name="parDeltaTime">The time in seconds since the last update.</param>
|
||||
public void Update(double parDeltaTime)
|
||||
{
|
||||
if (!IsPlaying)
|
||||
@@ -37,12 +75,18 @@ public class AnimationPlayer<T>(float parInterval) : IUpdate
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the animation playback from the beginning.
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
Reset();
|
||||
IsPlaying = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the animation to its initial state.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
_timer.Reset();
|
||||
|
||||
@@ -2,14 +2,36 @@
|
||||
|
||||
namespace DoomDeathmatch.Script.Collision;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Axis-Aligned Bounding Box (AABB) collider for 3D collision detection.
|
||||
/// </summary>
|
||||
public class AABBCollider
|
||||
{
|
||||
/// <summary>
|
||||
/// The position of the collider's center in 3D space.
|
||||
/// </summary>
|
||||
public Vector3 Position { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The size (width, height, depth) of the collider.
|
||||
/// </summary>
|
||||
public Vector3 Size { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The minimum point (corner) of the collider in 3D space.
|
||||
/// </summary>
|
||||
public Vector3 Min => Position - (Size / 2);
|
||||
|
||||
/// <summary>
|
||||
/// The maximum point (corner) of the collider in 3D space.
|
||||
/// </summary>
|
||||
public Vector3 Max => Position + (Size / 2);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if this collider intersects with another AABB collider.
|
||||
/// </summary>
|
||||
/// <param name="parCollider">The other collider to check for intersection.</param>
|
||||
/// <returns><see langword="true"/> if the colliders intersect; otherwise, <see langword="false"/>.</returns>
|
||||
public bool Intersects(AABBCollider parCollider)
|
||||
{
|
||||
var max = Max;
|
||||
@@ -21,6 +43,14 @@ public class AABBCollider
|
||||
max.Z >= otherMin.Z && min.Z <= otherMax.Z;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the collision normal between this collider and another collider.
|
||||
/// </summary>
|
||||
/// <param name="parOther">The other collider involved in the collision.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Vector3"/> representing the normal of the collision surface.
|
||||
/// This indicates the direction of the collision resolution.
|
||||
/// </returns>
|
||||
public Vector3 GetCollisionNormal(AABBCollider parOther)
|
||||
{
|
||||
var normal = Vector3.Zero;
|
||||
|
||||
@@ -3,11 +3,28 @@ using OpenTK.Mathematics;
|
||||
|
||||
namespace DoomDeathmatch.Script.Collision;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the result of a raycast in 3D space.
|
||||
/// </summary>
|
||||
public class RaycastResult
|
||||
{
|
||||
/// <summary>
|
||||
/// The distance from the ray's origin to the hit point.
|
||||
/// </summary>
|
||||
public float Distance { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The point in 3D space where the ray hit an object.
|
||||
/// </summary>
|
||||
public Vector3 HitPoint { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The normal vector at the surface of the hit object.
|
||||
/// </summary>
|
||||
public Vector3 Normal { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The game object that was hit by the ray.
|
||||
/// </summary>
|
||||
public GameObject HitObject { get; init; }
|
||||
}
|
||||
@@ -2,11 +2,23 @@
|
||||
|
||||
namespace DoomDeathmatch.Script.Condition;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a condition that can be updated and triggers an event when it becomes true.
|
||||
/// </summary>
|
||||
public interface ICondition : IUpdate
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when the condition evaluates to true.
|
||||
/// </summary>
|
||||
public event Action? OnTrue;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the condition is currently true.
|
||||
/// </summary>
|
||||
public bool IsTrue { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Resets the condition to its initial state.
|
||||
/// </summary>
|
||||
public void Reset();
|
||||
}
|
||||
@@ -2,25 +2,49 @@
|
||||
|
||||
namespace DoomDeathmatch.Script.Condition;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a condition based on a timer that can be updated over time.
|
||||
/// The condition becomes true when the timer finishes.
|
||||
/// </summary>
|
||||
public class TickableTimerCondition : ICondition
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when the timer finishes, causing the condition to become true.
|
||||
/// </summary>
|
||||
public event Action? OnTrue;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the timer has finished, making the condition true.
|
||||
/// </summary>
|
||||
public bool IsTrue => _timer.IsFinished;
|
||||
|
||||
/// <summary>
|
||||
/// The timer used to evaluate the condition's state.
|
||||
/// </summary>
|
||||
private readonly TickableTimer _timer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TickableTimerCondition"/> class with a specified interval.
|
||||
/// </summary>
|
||||
/// <param name="parInterval">The interval in seconds for the timer.</param>
|
||||
public TickableTimerCondition(float parInterval)
|
||||
{
|
||||
_timer = new TickableTimer(parInterval);
|
||||
_timer.OnFinished += () => OnTrue?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the timer with the elapsed time, progressing the condition's state.
|
||||
/// </summary>
|
||||
/// <param name="parDeltaTime">The time in seconds since the last update.</param>
|
||||
public void Update(double parDeltaTime)
|
||||
{
|
||||
_timer.Update(parDeltaTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the timer and the condition to their initial state.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
_timer.Reset();
|
||||
|
||||
@@ -2,12 +2,32 @@
|
||||
|
||||
namespace DoomDeathmatch.Script.Consumable;
|
||||
|
||||
public class HealthPackConsumable(float parHealth) : IConsumable
|
||||
/// <summary>
|
||||
/// Represents a health pack consumable that restores health to the player.
|
||||
/// </summary>
|
||||
/// <param name="parHealth">The amount of health restored by the consumable.</param>
|
||||
public class HealthPackConsumable : IConsumable
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public string Icon => "texture/health_pack.png";
|
||||
|
||||
/// <summary>
|
||||
/// The amount of health restored by the consumable.
|
||||
/// </summary>
|
||||
private readonly float _health;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HealthPackConsumable"/> class with a specified health restoration value.
|
||||
/// </summary>
|
||||
/// <param name="parHealth">The amount of health this consumable restores.</param>
|
||||
public HealthPackConsumable(float parHealth)
|
||||
{
|
||||
_health = parHealth;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Consume(PlayerController parPlayerController)
|
||||
{
|
||||
parPlayerController.HealthController.Heal(parHealth);
|
||||
parPlayerController.HealthController.Heal(_health);
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,21 @@
|
||||
|
||||
namespace DoomDeathmatch.Script.Consumable;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a consumable item in the game.
|
||||
/// Consumables can be used by a player to apply specific effects, such as healing or adding a weapon.
|
||||
/// </summary>
|
||||
public interface IConsumable
|
||||
{
|
||||
/// <summary>
|
||||
/// The icon path associated with the consumable.
|
||||
/// Used for displaying the consumable in the user interface.
|
||||
/// </summary>
|
||||
public string Icon { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Consumes the item and applies its effect to the specified player.
|
||||
/// </summary>
|
||||
/// <param name="parPlayerController">The player controller to apply the consumable effect to.</param>
|
||||
public void Consume(PlayerController parPlayerController);
|
||||
}
|
||||
@@ -3,12 +3,31 @@ using DoomDeathmatch.Script.Model.Weapon;
|
||||
|
||||
namespace DoomDeathmatch.Script.Consumable;
|
||||
|
||||
public class WeaponConsumable(WeaponData parWeaponData) : IConsumable
|
||||
/// <summary>
|
||||
/// Represents a weapon consumable that grants weapon or reloads ammo to the player.
|
||||
/// </summary>
|
||||
public class WeaponConsumable : IConsumable
|
||||
{
|
||||
public string Icon => parWeaponData.IdleTexture;
|
||||
/// <inheritdoc/>
|
||||
public string Icon => _weaponData.IdleTexture;
|
||||
|
||||
/// <summary>
|
||||
/// The weapon data associated with this consumable.
|
||||
/// </summary>
|
||||
private readonly WeaponData _weaponData;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WeaponConsumable"/> class with the specified weapon data.
|
||||
/// </summary>
|
||||
/// <param name="parWeaponData">The weapon data associated with this consumable.</param>
|
||||
public WeaponConsumable(WeaponData parWeaponData)
|
||||
{
|
||||
_weaponData = parWeaponData;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Consume(PlayerController parPlayerController)
|
||||
{
|
||||
parPlayerController.WeaponController.AddOrMergeWeapon(parWeaponData);
|
||||
parPlayerController.WeaponController.AddOrMergeWeapon(_weaponData);
|
||||
}
|
||||
}
|
||||
14
DoomDeathmatch/src/Script/MVC/IView.cs
Normal file
14
DoomDeathmatch/src/Script/MVC/IView.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace DoomDeathmatch.Script.MVC;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for view components.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the model to display.</typeparam>
|
||||
public interface IView<in T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Updates the view based on the specified model.
|
||||
/// </summary>
|
||||
/// <param name="parModel">The model to display.</param>
|
||||
public void UpdateView(T parModel);
|
||||
}
|
||||
@@ -3,10 +3,36 @@ using DoomDeathmatch.Component.MVC.Health;
|
||||
|
||||
namespace DoomDeathmatch.Script.Model.Enemy.Attack;
|
||||
|
||||
public abstract class AttackBehavior(EnemyController parEnemyController, HealthController parHealthController)
|
||||
/// <summary>
|
||||
/// Represents the base class for defining enemy attack behaviors.
|
||||
/// </summary>
|
||||
public abstract class AttackBehavior
|
||||
{
|
||||
protected readonly EnemyController _enemyController = parEnemyController;
|
||||
protected readonly HealthController _healthController = parHealthController;
|
||||
/// <summary>
|
||||
/// The controller for the enemy performing the attack.
|
||||
/// </summary>
|
||||
protected readonly EnemyController _enemyController;
|
||||
|
||||
/// <summary>
|
||||
/// The controller for the health of the target being attacked.
|
||||
/// </summary>
|
||||
protected readonly HealthController _healthController;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AttackBehavior"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parEnemyController">The enemy controller.</param>
|
||||
/// <param name="parHealthController">The health controller of the target.</param>
|
||||
protected AttackBehavior(EnemyController parEnemyController, HealthController parHealthController)
|
||||
{
|
||||
_enemyController = parEnemyController;
|
||||
_healthController = parHealthController;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the attack behavior.
|
||||
/// </summary>
|
||||
/// <param name="parDeltaTime">The time elapsed since the last update.</param>
|
||||
/// <returns>True if the attack was successful; otherwise, false.</returns>
|
||||
public abstract bool Attack(double parDeltaTime);
|
||||
}
|
||||
@@ -3,22 +3,47 @@ using DoomDeathmatch.Component.MVC.Health;
|
||||
|
||||
namespace DoomDeathmatch.Script.Model.Enemy.Attack;
|
||||
|
||||
public class CloseContinuousAttackBehavior(
|
||||
EnemyController parEnemyController,
|
||||
HealthController parHealthController,
|
||||
float parRadius,
|
||||
float parDamage)
|
||||
: AttackBehavior(parEnemyController, parHealthController)
|
||||
/// <summary>
|
||||
/// Represents a behavior where the enemy continuously attacks when in close proximity.
|
||||
/// </summary>
|
||||
public class CloseContinuousAttackBehavior : AttackBehavior
|
||||
{
|
||||
/// <summary>
|
||||
/// The attack radius within which the attack is triggered.
|
||||
/// </summary>
|
||||
private readonly float _radius;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of damage dealt per second.
|
||||
/// </summary>
|
||||
private readonly float _damage;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CloseContinuousAttackBehavior"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parEnemyController">The enemy controller.</param>
|
||||
/// <param name="parHealthController">The health controller of the target.</param>
|
||||
/// <param name="parRadius">The attack radius.</param>
|
||||
/// <param name="parDamage">The damage dealt per second.</param>
|
||||
public CloseContinuousAttackBehavior(EnemyController parEnemyController,
|
||||
HealthController parHealthController,
|
||||
float parRadius,
|
||||
float parDamage) : base(parEnemyController, parHealthController)
|
||||
{
|
||||
_radius = parRadius;
|
||||
_damage = parDamage;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Attack(double parDeltaTime)
|
||||
{
|
||||
var distanceSquared =
|
||||
(_enemyController.GameObject.Transform.Translation - _healthController.GameObject.Transform.Translation)
|
||||
.LengthSquared;
|
||||
|
||||
if (distanceSquared <= parRadius * parRadius)
|
||||
if (distanceSquared <= _radius * _radius)
|
||||
{
|
||||
_healthController.TakeDamage(parDamage * (float)parDeltaTime);
|
||||
_healthController.TakeDamage(_damage * (float)parDeltaTime);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,26 +3,53 @@ using DoomDeathmatch.Component.MVC.Health;
|
||||
|
||||
namespace DoomDeathmatch.Script.Model.Enemy.Attack;
|
||||
|
||||
public class CloseCooldownAttackBehavior(
|
||||
EnemyController parEnemyController,
|
||||
HealthController parHealthController,
|
||||
float parRadius,
|
||||
float parCooldown,
|
||||
float parDamage)
|
||||
: CooldownAttackBehavior(parEnemyController, parHealthController, parCooldown)
|
||||
/// <summary>
|
||||
/// Represents a behavior where the enemy performs a cooldown-based close-range attack.
|
||||
/// </summary>
|
||||
public class CloseCooldownAttackBehavior : CooldownAttackBehavior
|
||||
{
|
||||
/// <summary>
|
||||
/// The attack radius within which the attack is triggered.
|
||||
/// </summary>
|
||||
private readonly float _radius;
|
||||
|
||||
/// <summary>
|
||||
/// The damage dealt by the attack.
|
||||
/// </summary>
|
||||
private readonly float _damage;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CloseCooldownAttackBehavior"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parEnemyController">The enemy controller.</param>
|
||||
/// <param name="parHealthController">The health controller of the target.</param>
|
||||
/// <param name="parRadius">The attack radius.</param>
|
||||
/// <param name="parCooldown">The cooldown duration.</param>
|
||||
/// <param name="parDamage">The damage dealt by the attack.</param>
|
||||
public CloseCooldownAttackBehavior(EnemyController parEnemyController,
|
||||
HealthController parHealthController,
|
||||
float parRadius,
|
||||
float parCooldown,
|
||||
float parDamage) : base(parEnemyController, parHealthController, parCooldown)
|
||||
{
|
||||
_radius = parRadius;
|
||||
_damage = parDamage;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool CanAttack()
|
||||
{
|
||||
var distanceSquared =
|
||||
(_enemyController.GameObject.Transform.Translation - _healthController.GameObject.Transform.Translation)
|
||||
.LengthSquared;
|
||||
|
||||
return distanceSquared <= parRadius * parRadius;
|
||||
return distanceSquared <= _radius * _radius;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool ActivateAttack()
|
||||
{
|
||||
_healthController.TakeDamage(parDamage);
|
||||
_healthController.TakeDamage(_damage);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3,17 +3,35 @@ using DoomDeathmatch.Component.MVC.Health;
|
||||
|
||||
namespace DoomDeathmatch.Script.Model.Enemy.Attack;
|
||||
|
||||
public class CompositeAttackBehavior(
|
||||
EnemyController parEnemyController,
|
||||
HealthController parHealthController,
|
||||
List<AttackBehavior> parBehaviors)
|
||||
: AttackBehavior(parEnemyController, parHealthController)
|
||||
/// <summary>
|
||||
/// Represents a composite attack behavior combining multiple attack behaviors.
|
||||
/// </summary>
|
||||
public class CompositeAttackBehavior : AttackBehavior
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of individual attack behaviors.
|
||||
/// </summary>
|
||||
private readonly List<AttackBehavior> _behaviors;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CompositeAttackBehavior"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parEnemyController">The enemy controller.</param>
|
||||
/// <param name="parHealthController">The health controller of the target.</param>
|
||||
/// <param name="parBehaviors">The collection of attack behaviors.</param>
|
||||
public CompositeAttackBehavior(EnemyController parEnemyController,
|
||||
HealthController parHealthController,
|
||||
IEnumerable<AttackBehavior> parBehaviors) : base(parEnemyController, parHealthController)
|
||||
{
|
||||
_behaviors = parBehaviors.ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Attack(double parDeltaTime)
|
||||
{
|
||||
var result = false;
|
||||
|
||||
foreach (var behavior in parBehaviors)
|
||||
foreach (var behavior in _behaviors)
|
||||
{
|
||||
result |= behavior.Attack(parDeltaTime);
|
||||
}
|
||||
|
||||
@@ -4,14 +4,30 @@ using Engine.Util;
|
||||
|
||||
namespace DoomDeathmatch.Script.Model.Enemy.Attack;
|
||||
|
||||
public abstract class CooldownAttackBehavior(
|
||||
EnemyController parEnemyController,
|
||||
HealthController parHealthController,
|
||||
float parCooldown)
|
||||
: AttackBehavior(parEnemyController, parHealthController)
|
||||
/// <summary>
|
||||
/// Represents a base class for attacks with cooldown functionality.
|
||||
/// </summary>
|
||||
public abstract class CooldownAttackBehavior : AttackBehavior
|
||||
{
|
||||
private readonly TickableTimer _tickableTimer = new(parCooldown);
|
||||
/// <summary>
|
||||
/// Timer that tracks the cooldown duration between attacks.
|
||||
/// </summary>
|
||||
private readonly TickableTimer _tickableTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CooldownAttackBehavior"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parEnemyController">The enemy controller.</param>
|
||||
/// <param name="parHealthController">The health controller of the target.</param>
|
||||
/// <param name="parCooldown">The cooldown duration.</param>
|
||||
protected CooldownAttackBehavior(EnemyController parEnemyController,
|
||||
HealthController parHealthController,
|
||||
float parCooldown) : base(parEnemyController, parHealthController)
|
||||
{
|
||||
_tickableTimer = new TickableTimer(parCooldown);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override bool Attack(double parDeltaTime)
|
||||
{
|
||||
_tickableTimer.Update(parDeltaTime);
|
||||
@@ -33,6 +49,15 @@ public abstract class CooldownAttackBehavior(
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the attack can be performed.
|
||||
/// </summary>
|
||||
/// <returns>True if the attack can be performed; otherwise, false.</returns>
|
||||
protected abstract bool CanAttack();
|
||||
|
||||
/// <summary>
|
||||
/// Executes the attack logic.
|
||||
/// </summary>
|
||||
/// <returns>True if the attack was successful; otherwise, false.</returns>
|
||||
protected abstract bool ActivateAttack();
|
||||
}
|
||||
@@ -3,11 +3,31 @@ using DoomDeathmatch.Component.MVC.Health;
|
||||
|
||||
namespace DoomDeathmatch.Script.Model.Enemy.Attack;
|
||||
|
||||
public class FuncAttackBehaviorCreator(Func<EnemyController, HealthController, AttackBehavior> parFunc)
|
||||
: IAttackBehaviorCreator
|
||||
/// <summary>
|
||||
/// A creator that uses a function to generate instances of <see cref="AttackBehavior"/>.
|
||||
/// </summary>
|
||||
public class FuncAttackBehaviorCreator : IAttackBehaviorCreator
|
||||
{
|
||||
/// <summary>
|
||||
/// The function used to create <see cref="AttackBehavior"/> instances.
|
||||
/// </summary>
|
||||
private readonly Func<EnemyController, HealthController, AttackBehavior> _func;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FuncAttackBehaviorCreator"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parFunc">
|
||||
/// A function that takes an <see cref="EnemyController"/> and <see cref="HealthController"/>
|
||||
/// and returns a new instance of <see cref="AttackBehavior"/>.
|
||||
/// </param>
|
||||
public FuncAttackBehaviorCreator(Func<EnemyController, HealthController, AttackBehavior> parFunc)
|
||||
{
|
||||
_func = parFunc;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AttackBehavior Create(EnemyController parEnemyController, HealthController parHealthController)
|
||||
{
|
||||
return parFunc(parEnemyController, parHealthController);
|
||||
return _func(parEnemyController, parHealthController);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,16 @@ using DoomDeathmatch.Component.MVC.Health;
|
||||
|
||||
namespace DoomDeathmatch.Script.Model.Enemy.Attack;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for creating instances of <see cref="AttackBehavior"/>.
|
||||
/// </summary>
|
||||
public interface IAttackBehaviorCreator
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of an <see cref="AttackBehavior"/>.
|
||||
/// </summary>
|
||||
/// <param name="parEnemyController">The enemy controller for the behavior.</param>
|
||||
/// <param name="parHealthController">The health controller for the target.</param>
|
||||
/// <returns>An instance of <see cref="AttackBehavior"/>.</returns>
|
||||
public AttackBehavior Create(EnemyController parEnemyController, HealthController parHealthController);
|
||||
}
|
||||
@@ -5,22 +5,42 @@ using Engine.Util;
|
||||
|
||||
namespace DoomDeathmatch.Script.Model.Enemy.Attack;
|
||||
|
||||
public class ObjectSpawnAttackBehavior(
|
||||
EnemyController parEnemyController,
|
||||
HealthController parHealthController,
|
||||
float parCooldown,
|
||||
Func<EnemyController, HealthController, GameObject> parObjectSpawnFunc
|
||||
)
|
||||
: CooldownAttackBehavior(parEnemyController, parHealthController, parCooldown)
|
||||
/// <summary>
|
||||
/// Represents a behavior where an object is spawned as part of the attack.
|
||||
/// </summary>
|
||||
public class ObjectSpawnAttackBehavior : CooldownAttackBehavior
|
||||
{
|
||||
/// <summary>
|
||||
/// The function used to spawn an object based on the enemy and target controllers.
|
||||
/// </summary>
|
||||
private readonly Func<EnemyController, HealthController, GameObject> _objectSpawnFunc;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObjectSpawnAttackBehavior"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parEnemyController">The enemy controller.</param>
|
||||
/// <param name="parHealthController">The health controller of the target.</param>
|
||||
/// <param name="parCooldown">The cooldown duration between spawns.</param>
|
||||
/// <param name="parObjectSpawnFunc">The function used to create the spawned object.</param>
|
||||
public ObjectSpawnAttackBehavior(EnemyController parEnemyController,
|
||||
HealthController parHealthController,
|
||||
float parCooldown,
|
||||
Func<EnemyController, HealthController, GameObject> parObjectSpawnFunc) : base(parEnemyController,
|
||||
parHealthController, parCooldown)
|
||||
{
|
||||
_objectSpawnFunc = parObjectSpawnFunc;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool CanAttack()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool ActivateAttack()
|
||||
{
|
||||
var enemyObject = parObjectSpawnFunc(_enemyController, _healthController);
|
||||
var enemyObject = _objectSpawnFunc(_enemyController, _healthController);
|
||||
EngineUtil.SceneManager.CurrentScene!.Add(enemyObject);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -6,8 +6,14 @@ using OpenTK.Mathematics;
|
||||
|
||||
namespace DoomDeathmatch.Script.Model.Enemy;
|
||||
|
||||
/// <summary>
|
||||
/// Represents data associated with an enemy type, including its stats, behavior, and appearance.
|
||||
/// </summary>
|
||||
public class EnemyData
|
||||
{
|
||||
/// <summary>
|
||||
/// Data for the "Demon" enemy type.
|
||||
/// </summary>
|
||||
public static EnemyData Demon =>
|
||||
new()
|
||||
{
|
||||
@@ -24,6 +30,9 @@ public class EnemyData
|
||||
)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Data for the "Imp" enemy type.
|
||||
/// </summary>
|
||||
public static EnemyData Imp =>
|
||||
new()
|
||||
{
|
||||
@@ -61,25 +70,58 @@ public class EnemyData
|
||||
)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Unique identifier for the enemy type.
|
||||
/// </summary>
|
||||
public string Id { get; private init; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Display name for the enemy type.
|
||||
/// </summary>
|
||||
public string Name { get; private init; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Path to the texture file used to render the enemy.
|
||||
/// </summary>
|
||||
public string Texture { get; private init; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// The base health of the enemy.
|
||||
/// </summary>
|
||||
public float BaseHealth { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// The base score awarded for defeating this enemy.
|
||||
/// </summary>
|
||||
public int BaseScore { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// The base speed of the enemy.
|
||||
/// </summary>
|
||||
public float BaseSpeed { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// The movement behavior defining how the enemy moves.
|
||||
/// </summary>
|
||||
public IMovementBehavior MovementBehavior { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// The creator for the enemy's attack behavior.
|
||||
/// </summary>
|
||||
public IAttackBehaviorCreator AttackBehaviorCreator { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// Private constructor to prevent direct instantiation. Use the predefined static properties instead.
|
||||
/// </summary>
|
||||
private EnemyData() { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? parObj)
|
||||
{
|
||||
return parObj is EnemyData enemyData && Id == enemyData.Id;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Id);
|
||||
|
||||
@@ -2,11 +2,34 @@
|
||||
|
||||
namespace DoomDeathmatch.Script.Model.Enemy.Movement;
|
||||
|
||||
public class FollowPlayerMovementBehavior(float parRadius) : IMovementBehavior
|
||||
/// <summary>
|
||||
/// Movement behavior where the enemy follows the player while maintaining a specific radius.
|
||||
/// </summary>
|
||||
public class FollowPlayerMovementBehavior : IMovementBehavior
|
||||
{
|
||||
/// <summary>
|
||||
/// The radius to maintain between the enemy and the player.
|
||||
/// </summary>
|
||||
private readonly float _radius;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FollowPlayerMovementBehavior"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parRadius">The radius to maintain between the enemy and the player.</param>
|
||||
public FollowPlayerMovementBehavior(float parRadius)
|
||||
{
|
||||
_radius = parRadius;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the next position for the enemy, keeping it at the specified radius from the player.
|
||||
/// </summary>
|
||||
/// <param name="parPosition">The current position of the enemy.</param>
|
||||
/// <param name="parPlayerPosition">The current position of the player.</param>
|
||||
/// <returns>The next position of the enemy, maintaining the specified radius from the player.</returns>
|
||||
public Vector3 GetNextPosition(Vector3 parPosition, Vector3 parPlayerPosition)
|
||||
{
|
||||
var direction = (parPosition - parPlayerPosition).Normalized();
|
||||
return parPlayerPosition + (parRadius * direction);
|
||||
return parPlayerPosition + (_radius * direction);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,16 @@
|
||||
|
||||
namespace DoomDeathmatch.Script.Model.Enemy.Movement;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for defining enemy movement behavior.
|
||||
/// </summary>
|
||||
public interface IMovementBehavior
|
||||
{
|
||||
/// <summary>
|
||||
/// Calculates the next position for the enemy based on the current position and the player's position.
|
||||
/// </summary>
|
||||
/// <param name="parPosition">The current position of the enemy.</param>
|
||||
/// <param name="parPlayerPosition">The current position of the player.</param>
|
||||
/// <returns>The next position of the enemy.</returns>
|
||||
public Vector3 GetNextPosition(Vector3 parPosition, Vector3 parPlayerPosition);
|
||||
}
|
||||
@@ -2,8 +2,17 @@
|
||||
|
||||
namespace DoomDeathmatch.Script.Model.Enemy.Movement;
|
||||
|
||||
/// <summary>
|
||||
/// Movement behavior where the enemy remains stationary.
|
||||
/// </summary>
|
||||
public class StandingMovementBehavior : IMovementBehavior
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the current position of the enemy without any change, as the enemy does not move.
|
||||
/// </summary>
|
||||
/// <param name="parPosition">The current position of the enemy.</param>
|
||||
/// <param name="parPlayerPosition">The current position of the player (ignored).</param>
|
||||
/// <returns>The current position of the enemy.</returns>
|
||||
public Vector3 GetNextPosition(Vector3 parPosition, Vector3 parPlayerPosition)
|
||||
{
|
||||
return parPosition;
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
namespace DoomDeathmatch.Script.Model;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the health state of a player or entity in the game.
|
||||
/// Provides functionality to track and modify health, and notify listeners when health changes.
|
||||
/// </summary>
|
||||
public class HealthModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when the health value changes.
|
||||
/// </summary>
|
||||
public event Action<HealthModel>? HealthChanged;
|
||||
|
||||
/// <summary>
|
||||
/// The current health value of the entity.
|
||||
/// Health is clamped between 0 and <see cref="MaxHealth"/>.
|
||||
/// </summary>
|
||||
public float Health
|
||||
{
|
||||
get => _health;
|
||||
@@ -20,6 +31,10 @@ public class HealthModel
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The maximum health value of the entity.
|
||||
/// When modified, the current health is clamped between 0 and the new <see cref="MaxHealth"/>.
|
||||
/// </summary>
|
||||
public float MaxHealth
|
||||
{
|
||||
get => _maxHealth;
|
||||
@@ -31,9 +46,21 @@ public class HealthModel
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current health value of the entity.
|
||||
/// </summary>
|
||||
private float _health;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum health value of the entity.
|
||||
/// </summary>
|
||||
private float _maxHealth;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HealthModel"/> class with a specified maximum health.
|
||||
/// Sets the initial health to the maximum health.
|
||||
/// </summary>
|
||||
/// <param name="parMaxHealth">The maximum health value.</param>
|
||||
public HealthModel(float parMaxHealth)
|
||||
{
|
||||
MaxHealth = parMaxHealth;
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
namespace DoomDeathmatch.Script.Model;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the score state of a player or entity in the game.
|
||||
/// Provides functionality to track and modify the score, and notify listeners when the score changes.
|
||||
/// </summary>
|
||||
public class ScoreModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when the score value changes.
|
||||
/// </summary>
|
||||
public event Action<ScoreModel>? ScoreChanged;
|
||||
|
||||
/// <summary>
|
||||
/// The current score of the player or entity.
|
||||
/// The score cannot be negative.
|
||||
/// </summary>
|
||||
public int Score
|
||||
{
|
||||
get => _score;
|
||||
@@ -14,5 +25,8 @@ public class ScoreModel
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current score of the player or entity.
|
||||
/// </summary>
|
||||
private int _score;
|
||||
}
|
||||
@@ -2,7 +2,17 @@
|
||||
|
||||
namespace DoomDeathmatch.Script.Model.Weapon;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a shooting pattern for a weapon.
|
||||
/// </summary>
|
||||
public interface IShootPattern
|
||||
{
|
||||
/// <summary>
|
||||
/// Computes the shooting pattern based on directional vectors.
|
||||
/// </summary>
|
||||
/// <param name="parForward">The forward direction of the weapon.</param>
|
||||
/// <param name="parUp">The upward direction of the weapon.</param>
|
||||
/// <param name="parRight">The rightward direction of the weapon.</param>
|
||||
/// <returns>A collection of directional offsets representing the shoot pattern.</returns>
|
||||
public IEnumerable<Vector3> GetShootPattern(Vector3 parForward, Vector3 parUp, Vector3 parRight);
|
||||
}
|
||||
@@ -2,8 +2,18 @@
|
||||
|
||||
namespace DoomDeathmatch.Script.Model.Weapon;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a simple linear shooting pattern.
|
||||
/// </summary>
|
||||
public class LineShootPattern : IShootPattern
|
||||
{
|
||||
/// <summary>
|
||||
/// Computes a linear shooting pattern, which always shoots straight ahead.
|
||||
/// </summary>
|
||||
/// <param name="parForward">The forward direction of the weapon.</param>
|
||||
/// <param name="parUp">The upward direction of the weapon.</param>
|
||||
/// <param name="parRight">The rightward direction of the weapon.</param>
|
||||
/// <returns>A collection containing a single zero vector, indicating no offset.</returns>
|
||||
public IEnumerable<Vector3> GetShootPattern(Vector3 parForward, Vector3 parUp, Vector3 parRight)
|
||||
{
|
||||
return [Vector3.Zero];
|
||||
|
||||
@@ -2,15 +2,49 @@
|
||||
|
||||
namespace DoomDeathmatch.Script.Model.Weapon;
|
||||
|
||||
public class RandomFlatSpreadShootPattern(float parAngle, uint parCount) : IShootPattern
|
||||
/// <summary>
|
||||
/// Represents a shooting pattern with a random flat spread.
|
||||
/// </summary>
|
||||
public class RandomFlatSpreadShootPattern : IShootPattern
|
||||
{
|
||||
/// <summary>
|
||||
/// The random number generator for calculating spread angles.
|
||||
/// </summary>
|
||||
private readonly Random _random = new();
|
||||
|
||||
/// <summary>
|
||||
/// The maximum angle of spread in radians.
|
||||
/// </summary>
|
||||
private readonly float _angle;
|
||||
|
||||
/// <summary>
|
||||
/// The number of projectiles in the spread.
|
||||
/// </summary>
|
||||
private readonly uint _count;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RandomFlatSpreadShootPattern"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parAngle">The maximum angle of spread in radians.</param>
|
||||
/// <param name="parCount">The number of projectiles in the spread.</param>
|
||||
public RandomFlatSpreadShootPattern(float parAngle, uint parCount)
|
||||
{
|
||||
_angle = parAngle;
|
||||
_count = parCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes a random flat spread shooting pattern.
|
||||
/// </summary>
|
||||
/// <param name="parForward">The forward direction of the weapon.</param>
|
||||
/// <param name="parUp">The upward direction of the weapon.</param>
|
||||
/// <param name="parRight">The rightward direction of the weapon.</param>
|
||||
/// <returns>A collection of directional offsets representing the spread pattern.</returns>
|
||||
public IEnumerable<Vector3> GetShootPattern(Vector3 parForward, Vector3 parUp, Vector3 parRight)
|
||||
{
|
||||
for (var i = 0; i < parCount; i++)
|
||||
for (var i = 0; i < _count; i++)
|
||||
{
|
||||
var angle = parAngle * (((float)_random.NextDouble() * 2) - 1);
|
||||
var angle = _angle * (((float)_random.NextDouble() * 2) - 1);
|
||||
var delta = MathF.Tan(angle);
|
||||
|
||||
var offset = parRight * delta;
|
||||
|
||||
@@ -2,8 +2,14 @@
|
||||
|
||||
namespace DoomDeathmatch.Script.Model.Weapon;
|
||||
|
||||
/// <summary>
|
||||
/// Represents data associated with a weapon, including its stats, animations, and shooting pattern.
|
||||
/// </summary>
|
||||
public class WeaponData
|
||||
{
|
||||
/// <summary>
|
||||
/// Data for the "Pistol" weapon.
|
||||
/// </summary>
|
||||
public static WeaponData Pistol =>
|
||||
new(30)
|
||||
{
|
||||
@@ -19,6 +25,9 @@ public class WeaponData
|
||||
ShootPattern = new LineShootPattern()
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Data for the "Shotgun" weapon.
|
||||
/// </summary>
|
||||
public static WeaponData Shotgun =>
|
||||
new(10)
|
||||
{
|
||||
@@ -31,12 +40,24 @@ public class WeaponData
|
||||
ShootPattern = new RandomFlatSpreadShootPattern(MathHelper.DegreesToRadians(10), 40)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Triggered when the ammo of the weapon changes.
|
||||
/// </summary>
|
||||
public event Action<WeaponData>? OnAmmoChanged;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A unique identifier for the weapon.
|
||||
/// </summary>
|
||||
public string Id { get; private init; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// The display name of the weapon.
|
||||
/// </summary>
|
||||
public string Name { get; private init; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// The current ammo count of the weapon. Updates trigger the <see cref="OnAmmoChanged"/> event.
|
||||
/// </summary>
|
||||
public int Ammo
|
||||
{
|
||||
get => _ammo;
|
||||
@@ -62,29 +83,58 @@ public class WeaponData
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The maximum ammo capacity of the weapon.
|
||||
/// </summary>
|
||||
public int MaxAmmo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The base damage dealt by the weapon.
|
||||
/// </summary>
|
||||
public int Damage { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// The path to the texture used when the weapon is idle.
|
||||
/// </summary>
|
||||
public string IdleTexture { get; private init; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// The duration of the weapon's firing animation, in seconds.
|
||||
/// </summary>
|
||||
public float FireAnimationDuration { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of textures used for the weapon's firing animation.
|
||||
/// </summary>
|
||||
public List<string> FireAnimation { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The shooting pattern associated with the weapon.
|
||||
/// </summary>
|
||||
public IShootPattern ShootPattern { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
///The current ammo count of the weapon.
|
||||
/// </summary>
|
||||
private int _ammo;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WeaponData"/> class with the specified maximum ammo.
|
||||
/// </summary>
|
||||
/// <param name="parMaxAmmo">The maximum ammo capacity for the weapon.</param>
|
||||
private WeaponData(int parMaxAmmo)
|
||||
{
|
||||
MaxAmmo = parMaxAmmo;
|
||||
Ammo = MaxAmmo;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? parObj)
|
||||
{
|
||||
return parObj is WeaponData weaponModel && Id == weaponModel.Id;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Id);
|
||||
|
||||
@@ -2,13 +2,31 @@
|
||||
|
||||
namespace DoomDeathmatch.Script.Model;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the weapon state of a player or entity in the game.
|
||||
/// Provides functionality to track available weapons, the selected weapon, and notify listeners when the selected weapon changes.
|
||||
/// </summary>
|
||||
public class WeaponModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when the selected weapon changes.
|
||||
/// </summary>
|
||||
public event Action<WeaponData, WeaponData>? OnWeaponSelected;
|
||||
|
||||
/// <summary>
|
||||
/// The list of available weapons for the player or entity.
|
||||
/// </summary>
|
||||
public IList<WeaponData> Weapons => _weapons;
|
||||
|
||||
/// <summary>
|
||||
/// The currently selected weapon.
|
||||
/// </summary>
|
||||
public WeaponData SelectedWeapon => _weapons[_selectedWeaponIndex];
|
||||
|
||||
/// <summary>
|
||||
/// The index of the currently selected weapon in the weapons list.
|
||||
/// When modified, triggers the <see cref="OnWeaponSelected"/> event with the old and new selected weapons.
|
||||
/// </summary>
|
||||
public int SelectedWeaponIndex
|
||||
{
|
||||
get => _selectedWeaponIndex;
|
||||
@@ -27,7 +45,13 @@ public class WeaponModel
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The list of available weapons for the player or entity.
|
||||
/// </summary>
|
||||
private readonly List<WeaponData> _weapons = [WeaponData.Pistol];
|
||||
|
||||
/// <summary>
|
||||
/// The index of the currently selected weapon in the weapons list.
|
||||
/// </summary>
|
||||
private int _selectedWeaponIndex;
|
||||
}
|
||||
@@ -5,14 +5,37 @@ using OpenTK.Mathematics;
|
||||
|
||||
namespace DoomDeathmatch.Script;
|
||||
|
||||
/// <summary>
|
||||
/// Handles the spawning of game objects based on a condition and provided position and object data.
|
||||
/// </summary>
|
||||
public class ObjectSpawner : IUpdate
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when a new game object is spawned.
|
||||
/// </summary>
|
||||
public event Action<GameObject>? OnSpawned;
|
||||
|
||||
/// <summary>
|
||||
/// Provides the game object to spawn.
|
||||
/// </summary>
|
||||
private readonly IValueProvider<GameObject> _gameObjectProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Provides the position where the game object will be spawned.
|
||||
/// </summary>
|
||||
private readonly IValueProvider<Vector3> _positionProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Determines when the spawner should spawn a new object.
|
||||
/// </summary>
|
||||
private readonly ICondition _condition;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObjectSpawner"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parGameObjectProvider">The provider for the game object to spawn.</param>
|
||||
/// <param name="parPositionProvider">The provider for the position of the spawned object.</param>
|
||||
/// <param name="parCondition">The condition that triggers the spawning.</param>
|
||||
public ObjectSpawner(IValueProvider<GameObject> parGameObjectProvider, IValueProvider<Vector3> parPositionProvider,
|
||||
ICondition parCondition)
|
||||
{
|
||||
@@ -23,11 +46,18 @@ public class ObjectSpawner : IUpdate
|
||||
_condition.OnTrue += Spawn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the state of the spawner, evaluating the condition to determine if a new object should spawn.
|
||||
/// </summary>
|
||||
/// <param name="parDeltaTime">The time elapsed since the last update, in seconds.</param>
|
||||
public void Update(double parDeltaTime)
|
||||
{
|
||||
_condition.Update(parDeltaTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns a new game object at the position provided by the position provider.
|
||||
/// </summary>
|
||||
private void Spawn()
|
||||
{
|
||||
var gameObject = _gameObjectProvider.GetValue();
|
||||
|
||||
@@ -1,9 +1,38 @@
|
||||
namespace DoomDeathmatch.Script.Provider;
|
||||
|
||||
public class ConstValueProvider<T>(T parValue) : IValueProvider<T>
|
||||
/// <summary>
|
||||
/// Provides a constant value of type <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value provided.</typeparam>
|
||||
public class ConstValueProvider<T> : IValueProvider<T>
|
||||
{
|
||||
public T GetValue() => parValue;
|
||||
/// <summary>
|
||||
/// The value to provide.
|
||||
/// </summary>
|
||||
private readonly T _value;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ConstValueProvider{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="parValue">The value to store.</param>
|
||||
public ConstValueProvider(T parValue)
|
||||
{
|
||||
_value = parValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetValue() => _value;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="ConstValueProvider{T}"/> with the specified value.
|
||||
/// </summary>
|
||||
/// <param name="parValue">The constant value to provide.</param>
|
||||
/// <returns>A new instance of <see cref="ConstValueProvider{T}"/>.</returns>
|
||||
public static ConstValueProvider<T> Create(T parValue) => new(parValue);
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts a value of type <typeparamref name="T"/> to a <see cref="ConstValueProvider{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="parValue">The value to convert.</param>
|
||||
public static implicit operator ConstValueProvider<T>(T parValue) => new(parValue);
|
||||
}
|
||||
@@ -1,9 +1,38 @@
|
||||
namespace DoomDeathmatch.Script.Provider;
|
||||
|
||||
public class GeneratorValueProvider<T>(Func<T> parGenerator) : IValueProvider<T>
|
||||
/// <summary>
|
||||
/// Provides a value generated by a function.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value provided.</typeparam>
|
||||
public class GeneratorValueProvider<T> : IValueProvider<T>
|
||||
{
|
||||
public T GetValue() => parGenerator();
|
||||
/// <summary>
|
||||
/// A function used to generate values.
|
||||
/// </summary>
|
||||
private readonly Func<T> _generator;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="GeneratorValueProvider{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="parGenerator">The function used to generate values.</param>
|
||||
public GeneratorValueProvider(Func<T> parGenerator)
|
||||
{
|
||||
_generator = parGenerator;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetValue() => _generator();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="GeneratorValueProvider{T}"/> with the specified generator function.
|
||||
/// </summary>
|
||||
/// <param name="parGenerator">The function that generates values.</param>
|
||||
/// <returns>A new instance of <see cref="GeneratorValueProvider{T}"/>.</returns>
|
||||
public static GeneratorValueProvider<T> Create(Func<T> parGenerator) => new(parGenerator);
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts a function of type <see cref="Func{T}"/> to a <see cref="GeneratorValueProvider{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="parGenerator">The function to convert.</param>
|
||||
public static implicit operator GeneratorValueProvider<T>(Func<T> parGenerator) => new(parGenerator);
|
||||
}
|
||||
@@ -1,6 +1,14 @@
|
||||
namespace DoomDeathmatch.Script.Provider;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a provider that supplies a value of type <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value provided.</typeparam>
|
||||
public interface IValueProvider<out T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves the value provided by this instance.
|
||||
/// </summary>
|
||||
/// <returns>The value of type <typeparamref name="T"/>.</returns>
|
||||
public T GetValue();
|
||||
}
|
||||
@@ -1,10 +1,25 @@
|
||||
namespace DoomDeathmatch.Script.Provider;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a value randomly selected from a list of value providers.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value provided.</typeparam>
|
||||
public class RandomListValueProvider<T> : IValueProvider<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of value providers to choose from.
|
||||
/// </summary>
|
||||
private readonly List<IValueProvider<T>> _providers = [];
|
||||
|
||||
/// <summary>
|
||||
/// The random number generator for selecting providers.
|
||||
/// </summary>
|
||||
private readonly Random _random = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="RandomListValueProvider{T}"/> with the specified providers.
|
||||
/// </summary>
|
||||
/// <param name="parProviders">The collection of value providers to select from.</param>
|
||||
public RandomListValueProvider(IEnumerable<IValueProvider<T>> parProviders)
|
||||
{
|
||||
foreach (var provider in parProviders)
|
||||
@@ -13,6 +28,7 @@ public class RandomListValueProvider<T> : IValueProvider<T>
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetValue()
|
||||
{
|
||||
return _providers[_random.Next(_providers.Count)].GetValue();
|
||||
|
||||
@@ -1,11 +1,31 @@
|
||||
namespace DoomDeathmatch.Script.Provider;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a value randomly selected from weighted value providers.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value provided.</typeparam>
|
||||
public class WeightedRandomValueProvider<T> : IValueProvider<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of weighted value providers.
|
||||
/// </summary>
|
||||
private readonly List<(int, IValueProvider<T>)> _providers = [];
|
||||
|
||||
/// <summary>
|
||||
/// The random number generator used to select a provider.
|
||||
/// </summary>
|
||||
private readonly Random _random = new();
|
||||
|
||||
/// <summary>
|
||||
/// The total weight of all providers.
|
||||
/// </summary>
|
||||
private readonly int _totalWeight;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="WeightedRandomValueProvider{T}"/> with the specified providers.
|
||||
/// </summary>
|
||||
/// <param name="parProviders">The collection of weighted value providers.</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown if the total weight is less than or equal to 0.</exception>
|
||||
public WeightedRandomValueProvider(IEnumerable<(int, IValueProvider<T>)> parProviders)
|
||||
{
|
||||
foreach (var (weight, provider) in parProviders)
|
||||
@@ -20,6 +40,7 @@ public class WeightedRandomValueProvider<T> : IValueProvider<T>
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetValue()
|
||||
{
|
||||
var random = _random.Next(_totalWeight);
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace DoomDeathmatch.Script.Score;
|
||||
[JsonSerializable(typeof(ScoreTable))]
|
||||
public class ScoreTable
|
||||
{
|
||||
public List<ScoreRow> Rows { get; } = new();
|
||||
public List<ScoreRow> Rows { get; } = [];
|
||||
|
||||
private static readonly JsonSerializerOptions OPTIONS = new() { Converters = { new ScoreTableJsonConverter() } };
|
||||
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
namespace DoomDeathmatch.Script.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies horizontal alignment options.
|
||||
/// </summary>
|
||||
public enum Align
|
||||
{
|
||||
/// <summary>
|
||||
/// Align content to the left.
|
||||
/// </summary>
|
||||
Left,
|
||||
|
||||
/// <summary>
|
||||
/// Align content to the center.
|
||||
/// </summary>
|
||||
Center,
|
||||
|
||||
/// <summary>
|
||||
/// Align content to the right.
|
||||
/// </summary>
|
||||
Right
|
||||
}
|
||||
@@ -1,14 +1,90 @@
|
||||
namespace DoomDeathmatch.Script.UI;
|
||||
using OpenTK.Mathematics;
|
||||
|
||||
namespace DoomDeathmatch.Script.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies anchoring options for positioning elements.
|
||||
/// </summary>
|
||||
public enum Anchor
|
||||
{
|
||||
/// <summary>
|
||||
/// Anchor to the top-left corner.
|
||||
/// </summary>
|
||||
TopLeft,
|
||||
|
||||
/// <summary>
|
||||
/// Anchor to the top-center.
|
||||
/// </summary>
|
||||
TopCenter,
|
||||
|
||||
/// <summary>
|
||||
/// Anchor to the top-right corner.
|
||||
/// </summary>
|
||||
TopRight,
|
||||
|
||||
/// <summary>
|
||||
/// Anchor to the center-left.
|
||||
/// </summary>
|
||||
CenterLeft,
|
||||
|
||||
/// <summary>
|
||||
/// Anchor to the center.
|
||||
/// </summary>
|
||||
Center,
|
||||
|
||||
/// <summary>
|
||||
/// Anchor to the center-right.
|
||||
/// </summary>
|
||||
CenterRight,
|
||||
|
||||
/// <summary>
|
||||
/// Anchor to the bottom-left corner.
|
||||
/// </summary>
|
||||
BottomLeft,
|
||||
|
||||
/// <summary>
|
||||
/// Anchor to the bottom-center.
|
||||
/// </summary>
|
||||
BottomCenter,
|
||||
|
||||
/// <summary>
|
||||
/// Anchor to the bottom-right corner.
|
||||
/// </summary>
|
||||
BottomRight
|
||||
}
|
||||
|
||||
public static class AnchorHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves the ratio of an anchor.
|
||||
/// </summary>
|
||||
/// <param name="parAnchor">The anchor to retrieve the ratio of.</param>
|
||||
/// <returns>The ratio of the anchor.</returns>
|
||||
public static Vector2 GetRatio(this Anchor parAnchor)
|
||||
{
|
||||
return parAnchor switch
|
||||
{
|
||||
Anchor.TopLeft => new Vector2(-0.5f, 0.5f),
|
||||
Anchor.TopCenter => new Vector2(0, 0.5f),
|
||||
Anchor.TopRight => new Vector2(0.5f, 0.5f),
|
||||
Anchor.CenterLeft => new Vector2(-0.5f, 0),
|
||||
Anchor.Center => new Vector2(0, 0),
|
||||
Anchor.CenterRight => new Vector2(0.5f, 0),
|
||||
Anchor.BottomLeft => new Vector2(-0.5f, -0.5f),
|
||||
Anchor.BottomCenter => new Vector2(0, -0.5f),
|
||||
Anchor.BottomRight => new Vector2(0.5f, -0.5f),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(parAnchor), parAnchor, null)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the position of an anchor relative to a size.
|
||||
/// </summary>
|
||||
/// <param name="parAnchor">The anchor to retrieve the position of.</param>
|
||||
/// <param name="parSize">The size to use for calculations.</param>
|
||||
/// <returns>The position of the anchor relative to the size.</returns>
|
||||
public static Vector2 GetPosition(this Anchor parAnchor, Vector2 parSize)
|
||||
{
|
||||
return parSize * parAnchor.GetRatio();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,17 @@
|
||||
namespace DoomDeathmatch.Script.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the orientation of UI elements.
|
||||
/// </summary>
|
||||
public enum Orientation
|
||||
{
|
||||
/// <summary>
|
||||
/// Arrange elements horizontally.
|
||||
/// </summary>
|
||||
Horizontal,
|
||||
|
||||
/// <summary>
|
||||
/// Arrange elements vertically.
|
||||
/// </summary>
|
||||
Vertical
|
||||
}
|
||||
@@ -17,7 +17,7 @@ public partial class ProgramLoader : IResourceLoader
|
||||
/// <returns>The loaded shader program.</returns>
|
||||
public object Load(string parPath, IResourceStreamProvider parStreamProvider)
|
||||
{
|
||||
var textReader = new StreamReader(parStreamProvider.GetStream(parPath));
|
||||
using var textReader = new StreamReader(parStreamProvider.GetStream(parPath));
|
||||
|
||||
var vertexSource = new StringBuilder();
|
||||
var fragmentSource = new StringBuilder();
|
||||
|
||||
@@ -45,6 +45,14 @@ public abstract class Component : IUpdate, IRender
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after the main update loop.
|
||||
/// </summary>
|
||||
/// <param name="parDeltaTime">Time elapsed since the last frame.</param>
|
||||
public virtual void PostUpdate(double parDeltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called during the main render loop.
|
||||
/// </summary>
|
||||
|
||||
@@ -151,6 +151,18 @@ public sealed class GameObject : IUpdate, IRender
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs post-update operations for all components.
|
||||
/// </summary>
|
||||
/// <param name="parDeltaTime">The time delta since the last update.</param>
|
||||
public void PostUpdate(double parDeltaTime)
|
||||
{
|
||||
foreach (var component in _components)
|
||||
{
|
||||
component.PostUpdate(parDeltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Render()
|
||||
{
|
||||
@@ -181,9 +193,9 @@ public sealed class GameObject : IUpdate, IRender
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the component to retrieve.</typeparam>
|
||||
/// <returns>The component if found, otherwise null.</returns>
|
||||
public T? GetComponent<T>() where T : Component.Component
|
||||
public T? GetComponent<T>() where T : class
|
||||
{
|
||||
if (!HasComponent<T>())
|
||||
if (!typeof(T).IsInterface && !HasComponent<T>())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -204,7 +216,7 @@ public sealed class GameObject : IUpdate, IRender
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the component to retrieve.</typeparam>
|
||||
/// <returns>The component if found, otherwise null.</returns>
|
||||
public T? GetComponentAny<T>() where T : Component.Component
|
||||
public T? GetComponentAny<T>() where T : class
|
||||
{
|
||||
var component = GetComponent<T>();
|
||||
if (component != null)
|
||||
@@ -221,7 +233,7 @@ public sealed class GameObject : IUpdate, IRender
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the component to retrieve.</typeparam>
|
||||
/// <returns>The component if found, otherwise null.</returns>
|
||||
public T? GetComponentInChildren<T>() where T : Component.Component
|
||||
public T? GetComponentInChildren<T>() where T : class
|
||||
{
|
||||
var children = Scene!.Hierarchy.GetChildren(this);
|
||||
|
||||
@@ -334,7 +346,7 @@ public sealed class GameObject : IUpdate, IRender
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the component to check for.</typeparam>
|
||||
/// <returns>True if the component exists, otherwise false.</returns>
|
||||
public bool HasComponent<T>() where T : Component.Component
|
||||
public bool HasComponent<T>()
|
||||
{
|
||||
var baseType = typeof(T).GetComponentBaseType();
|
||||
return _addedComponentTypes.Contains(baseType);
|
||||
|
||||
@@ -61,6 +61,11 @@ public class Scene : IUpdate, IRender
|
||||
{
|
||||
gameObject.Update(parDeltaTime * TimeScale);
|
||||
}
|
||||
|
||||
foreach (var gameObject in hierarchyObjects)
|
||||
{
|
||||
gameObject.PostUpdate(parDeltaTime * TimeScale);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Engine.Util;
|
||||
/// <summary>
|
||||
/// Contains mathematical utility methods.
|
||||
/// </summary>
|
||||
public static class Math
|
||||
public static class MathUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// Multiplies a <see cref="Vector4"/> by a <see cref="Matrix4"/> and performs projective division.
|
||||
@@ -14,10 +14,16 @@ namespace PresenterConsole;
|
||||
|
||||
public class ConsolePresenter : IPresenter
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public event Action<ResizeEventArgs>? OnResize;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Width { get; private set; } = 2;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Height { get; private set; } = 1;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsExiting { get; private set; }
|
||||
|
||||
private static readonly char[] LIGHTMAP = " .,:;=*#%@".Reverse().ToArray();
|
||||
@@ -27,8 +33,8 @@ public class ConsolePresenter : IPresenter
|
||||
private readonly Engine.Graphics.Shader.Program _asciiProgram;
|
||||
private readonly Framebuffer _framebuffer;
|
||||
private readonly IndexBuffer _indexBuffer;
|
||||
private readonly VertexArray _vertexArray;
|
||||
private readonly VertexBuffer<AsciiVertex> _vertexBuffer;
|
||||
private readonly VertexArray _vertexArray;
|
||||
|
||||
private Image<AsciiPixel>? _asciiImage;
|
||||
|
||||
@@ -62,8 +68,6 @@ public class ConsolePresenter : IPresenter
|
||||
{
|
||||
var openglTexture = (Texture)parTexture;
|
||||
|
||||
// GL.Viewport(0, 0, Width / 2, Height);
|
||||
|
||||
_framebuffer.Bind();
|
||||
|
||||
openglTexture.BindUnit();
|
||||
|
||||
@@ -8,38 +8,101 @@ using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace PresenterConsole;
|
||||
|
||||
/// <summary>
|
||||
/// Provides Windows FFI (Foreign Function Interface) functionality to interact with the Windows API
|
||||
/// for various operations such as file handling, console operations, keyboard state management, and window handling.
|
||||
/// </summary>
|
||||
public static partial class WindowsFFI
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a coordinate with X and Y values, typically used for positioning or grid-like structures.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Coord(short parX, short parY)
|
||||
{
|
||||
/// <summary>
|
||||
/// The X coordinate.
|
||||
/// </summary>
|
||||
public short X = parX;
|
||||
|
||||
/// <summary>
|
||||
/// The Y coordinate.
|
||||
/// </summary>
|
||||
public short Y = parY;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A union structure that can hold either a Unicode or an ASCII character at the same memory location.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct CharUnion
|
||||
{
|
||||
/// <summary>
|
||||
/// The Unicode character value.
|
||||
/// </summary>
|
||||
[FieldOffset(0)] public char UnicodeChar;
|
||||
|
||||
/// <summary>
|
||||
/// The ASCII character value.
|
||||
/// </summary>
|
||||
[FieldOffset(0)] public byte AsciiChar;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a character and its associated attributes in the console output, including the character's color.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct CharInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The character.
|
||||
/// </summary>
|
||||
[FieldOffset(0)] public CharUnion Char;
|
||||
|
||||
/// <summary>
|
||||
/// The attributes associated with the character (e.g., color).
|
||||
/// </summary>
|
||||
[FieldOffset(2)] public short Attributes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a rectangle in terms of its left, top, right, and bottom coordinates, used for defining the bounds of a console screen area.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct SmallRect
|
||||
{
|
||||
/// <summary>
|
||||
/// The left coordinate of the rectangle.
|
||||
/// </summary>
|
||||
public short Left;
|
||||
|
||||
/// <summary>
|
||||
/// The top coordinate of the rectangle.
|
||||
/// </summary>
|
||||
public short Top;
|
||||
|
||||
/// <summary>
|
||||
/// The right coordinate of the rectangle.
|
||||
/// </summary>
|
||||
public short Right;
|
||||
|
||||
/// <summary>
|
||||
/// The bottom coordinate of the rectangle.
|
||||
/// </summary>
|
||||
public short Bottom;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses the kernel32.dll's CreateFileW function to open or create a file with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="parFileName">The name of the file to open or create.</param>
|
||||
/// <param name="parFileAccess">The requested access to the file.</param>
|
||||
/// <param name="parFileShare">The sharing mode for the file.</param>
|
||||
/// <param name="parSecurityAttributes">Security attributes for the file.</param>
|
||||
/// <param name="parCreationDisposition">Action to take when opening the file.</param>
|
||||
/// <param name="parFlags">File-specific flags.</param>
|
||||
/// <param name="parTemplate">An optional template file.</param>
|
||||
/// <returns>A handle to the opened or created file.</returns>
|
||||
[LibraryImport("kernel32.dll", SetLastError = true, EntryPoint = "CreateFileW")]
|
||||
public static partial SafeFileHandle CreateFile(
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string parFileName,
|
||||
@@ -51,6 +114,15 @@ public static partial class WindowsFFI
|
||||
IntPtr parTemplate
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Uses the kernel32.dll's WriteConsoleOutputW function to write characters to the console output at the specified coordinates.
|
||||
/// </summary>
|
||||
/// <param name="parHConsoleOutput">A handle to the console output.</param>
|
||||
/// <param name="parLpBuffer">The buffer containing the characters and their attributes to be written.</param>
|
||||
/// <param name="parDwBufferSize">The size of the buffer.</param>
|
||||
/// <param name="parDwBufferCoord">The coordinates of the starting position in the console buffer.</param>
|
||||
/// <param name="parLpWriteRegion">The region of the console buffer to write to.</param>
|
||||
/// <returns>True if the operation was successful, otherwise false.</returns>
|
||||
[LibraryImport("kernel32.dll", SetLastError = true, EntryPoint = "WriteConsoleOutputW")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool WriteConsoleOutput(
|
||||
@@ -62,32 +134,61 @@ public static partial class WindowsFFI
|
||||
ref SmallRect parLpWriteRegion
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Uses the user32.dll's GetKeyboardState function to retrieve the state of the keyboard keys.
|
||||
/// </summary>
|
||||
/// <param name="parKeyboardState">An array to receive the current state of the keyboard keys.</param>
|
||||
/// <returns>True if the operation was successful, otherwise false.</returns>
|
||||
[LibraryImport("user32.dll", SetLastError = true, EntryPoint = "GetKeyboardState")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool GetKeyboardState(byte[] parKeyboardState);
|
||||
|
||||
/// <summary>
|
||||
/// Uses the user32.dll's GetAsyncKeyState function to retrieve the state of a specific key.
|
||||
/// </summary>
|
||||
/// <param name="parKeyCode">The key code for the key whose state is to be retrieved.</param>
|
||||
/// <returns>A short value representing the key's state.</returns>
|
||||
[LibraryImport("user32.dll", SetLastError = true, EntryPoint = "GetAsyncKeyState")]
|
||||
public static partial short GetAsyncKeyState(int parKeyCode);
|
||||
|
||||
/// <summary>
|
||||
/// Uses the user32.dll's GetForegroundWindow function to retrieve the handle of the foreground window.
|
||||
/// </summary>
|
||||
/// <returns>The handle of the foreground window.</returns>
|
||||
[LibraryImport("user32.dll", SetLastError = true, EntryPoint = "GetForegroundWindow")]
|
||||
public static partial IntPtr GetForegroundWindow();
|
||||
|
||||
/// <summary>
|
||||
/// Uses the user32.dll's GetWindowThreadProcessId function to retrieve the process ID of the thread that owns a specified window.
|
||||
/// </summary>
|
||||
/// <param name="parHwnd">The handle to the window.</param>
|
||||
/// <param name="parLpdwProcessId">An optional pointer to receive the process ID of the window's thread.</param>
|
||||
/// <returns>The thread ID of the window.</returns>
|
||||
[LibraryImport("user32.dll", SetLastError = true, EntryPoint = "GetWindowThreadProcessId")]
|
||||
public static partial uint GetWindowThreadProcessId(IntPtr parHwnd, IntPtr parLpdwProcessId);
|
||||
|
||||
/// <summary>
|
||||
/// Uses the user32.dll's GetKeyboardLayout function to retrieve the input locale identifier for the current thread.
|
||||
/// </summary>
|
||||
/// <param name="parThreadId">The ID of the thread for which to retrieve the keyboard layout.</param>
|
||||
/// <returns>The handle to the keyboard layout for the thread.</returns>
|
||||
[LibraryImport("user32.dll", SetLastError = true, EntryPoint = "GetKeyboardLayout")]
|
||||
public static partial IntPtr GetKeyboardLayout(uint parThreadId);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the current keyboard layout for the foreground window's process. Assumes English (1033) if an error occurs.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="CultureInfo"/> object representing the current keyboard layout.</returns>
|
||||
public static CultureInfo GetCurrentKeyboardLayout()
|
||||
{
|
||||
try
|
||||
{
|
||||
IntPtr foregroundWindow = GetForegroundWindow();
|
||||
uint foregroundProcess = GetWindowThreadProcessId(foregroundWindow, IntPtr.Zero);
|
||||
int keyboardLayout = GetKeyboardLayout(foregroundProcess).ToInt32() & 0xFFFF;
|
||||
var foregroundWindow = GetForegroundWindow();
|
||||
var foregroundProcess = GetWindowThreadProcessId(foregroundWindow, IntPtr.Zero);
|
||||
var keyboardLayout = GetKeyboardLayout(foregroundProcess).ToInt32() & 0xFFFF;
|
||||
return new CultureInfo(keyboardLayout);
|
||||
}
|
||||
catch (Exception _)
|
||||
catch (Exception)
|
||||
{
|
||||
return new CultureInfo(1033); // Assume English if something went wrong.
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user