diff --git a/DoomDeathmatch/asset/texture/demon.png b/DoomDeathmatch/asset/texture/demon.png index 56b96ca..5e6543f 100644 Binary files a/DoomDeathmatch/asset/texture/demon.png and b/DoomDeathmatch/asset/texture/demon.png differ diff --git a/DoomDeathmatch/asset/texture/health_pack.png b/DoomDeathmatch/asset/texture/health_pack.png new file mode 100644 index 0000000..31763ea Binary files /dev/null and b/DoomDeathmatch/asset/texture/health_pack.png differ diff --git a/DoomDeathmatch/asset/texture/imp.png b/DoomDeathmatch/asset/texture/imp.png index f900685..8bafde7 100644 Binary files a/DoomDeathmatch/asset/texture/imp.png and b/DoomDeathmatch/asset/texture/imp.png differ diff --git a/DoomDeathmatch/asset/texture/pistol/fire1.png b/DoomDeathmatch/asset/texture/pistol/fire1.png new file mode 100644 index 0000000..4023a07 Binary files /dev/null and b/DoomDeathmatch/asset/texture/pistol/fire1.png differ diff --git a/DoomDeathmatch/asset/texture/pistol/fire2.png b/DoomDeathmatch/asset/texture/pistol/fire2.png new file mode 100644 index 0000000..5075cef Binary files /dev/null and b/DoomDeathmatch/asset/texture/pistol/fire2.png differ diff --git a/DoomDeathmatch/asset/texture/pistol/fire3.png b/DoomDeathmatch/asset/texture/pistol/fire3.png new file mode 100644 index 0000000..24cc1ca Binary files /dev/null and b/DoomDeathmatch/asset/texture/pistol/fire3.png differ diff --git a/DoomDeathmatch/asset/texture/pistol/fire4.png b/DoomDeathmatch/asset/texture/pistol/fire4.png new file mode 100644 index 0000000..13d3334 Binary files /dev/null and b/DoomDeathmatch/asset/texture/pistol/fire4.png differ diff --git a/DoomDeathmatch/asset/texture/pistol.png b/DoomDeathmatch/asset/texture/pistol/idle.png similarity index 100% rename from DoomDeathmatch/asset/texture/pistol.png rename to DoomDeathmatch/asset/texture/pistol/idle.png diff --git a/DoomDeathmatch/asset/texture/shotgun/fire1.png b/DoomDeathmatch/asset/texture/shotgun/fire1.png new file mode 100644 index 0000000..de99bf7 Binary files /dev/null and b/DoomDeathmatch/asset/texture/shotgun/fire1.png differ diff --git a/DoomDeathmatch/asset/texture/shotgun/fire2.png b/DoomDeathmatch/asset/texture/shotgun/fire2.png new file mode 100644 index 0000000..7f802af Binary files /dev/null and b/DoomDeathmatch/asset/texture/shotgun/fire2.png differ diff --git a/DoomDeathmatch/asset/texture/shotgun.png b/DoomDeathmatch/asset/texture/shotgun/idle.png similarity index 100% rename from DoomDeathmatch/asset/texture/shotgun.png rename to DoomDeathmatch/asset/texture/shotgun/idle.png diff --git a/DoomDeathmatch/src/Component/ConsumableComponent.cs b/DoomDeathmatch/src/Component/ConsumableComponent.cs new file mode 100644 index 0000000..e7ae114 --- /dev/null +++ b/DoomDeathmatch/src/Component/ConsumableComponent.cs @@ -0,0 +1,78 @@ +using DoomDeathmatch.Component.MVC; +using DoomDeathmatch.Component.Physics.Collision; +using DoomDeathmatch.Script.Consumable; +using Engine.Graphics.Texture; +using Engine.Scene.Component.BuiltIn.Renderer; +using Engine.Util; +using OpenTK.Mathematics; + +namespace DoomDeathmatch.Component; + +public class ConsumableComponent : Engine.Scene.Component.Component +{ + private readonly IConsumable _consumable; + private readonly AABBColliderComponent _collider; + private readonly Box2DRenderer _box2DRenderer; + + private readonly Random _random = new(); + + private MovementController _movementController = null!; + + public ConsumableComponent(IConsumable parConsumable, AABBColliderComponent parCollider, + Box2DRenderer parBox2DRenderer) + { + _consumable = parConsumable; + _collider = parCollider; + _box2DRenderer = parBox2DRenderer; + } + + public override void Awake() + { + _collider.OnCollision += OnCollision; + _box2DRenderer.Texture = EngineUtil.AssetResourceManager.Load(_consumable.Icon); + + _movementController = GameObject.GetComponent()!; + + ArgumentNullException.ThrowIfNull(_movementController); + } + + private void OnCollision(AABBColliderComponent parCollider) + { + if (parCollider.ColliderGroups.Contains("player")) + TryConsume(parCollider); + else if (parCollider.ColliderGroups.Contains("consumable")) + MoveAway(parCollider); + } + + private void TryConsume(AABBColliderComponent parCollider) + { + var playerController = parCollider.GameObject.GetComponent(); + if (playerController == null) + return; + + _consumable.Consume(playerController); + + EngineUtil.SceneManager.CurrentScene!.Remove(GameObject); + } + + private void MoveAway(AABBColliderComponent parCollider) + { + var direction = _collider.GameObject.Transform.GetFullTranslation() - + parCollider.GameObject.Transform.GetFullTranslation(); + + if (direction.LengthSquared <= float.Epsilon) + direction = GetRandomDirection(); + else + direction.Normalize(); + + _movementController.ApplyMovement(direction); + } + + private Vector3 GetRandomDirection() + { + var x = _random.NextSingle() * 2 - 1; + var y = _random.NextSingle() * 2 - 1; + + return new Vector3(x, y, 0).Normalized(); + } +} \ No newline at end of file diff --git a/DoomDeathmatch/src/Component/MVC/Controller/GameController.cs b/DoomDeathmatch/src/Component/MVC/Controller/GameController.cs deleted file mode 100644 index d2eeb64..0000000 --- a/DoomDeathmatch/src/Component/MVC/Controller/GameController.cs +++ /dev/null @@ -1,64 +0,0 @@ -using DoomDeathmatch.Component.UI; -using Engine.Input; - -namespace DoomDeathmatch.Component.MVC.Controller; - -public class GameController : Engine.Scene.Component.Component -{ - public bool IsPaused { get; set; } = false; - - public PlayerController PlayerController => _playerController; - public ScoreController ScoreController => _scoreController; - - private ScoreController _scoreController = null!; - private TimerController _timerController = null!; - private PlayerController _playerController = null!; - - private readonly MenuControllerComponent _menuController; - private readonly IInputHandler _inputHandler = Engine.Engine.Instance.InputHandler!; - - public GameController(MenuControllerComponent parMenuController) - { - _menuController = parMenuController; - } - - public override void Awake() - { - _scoreController = GameObject.GetComponent()!; - _timerController = GameObject.GetComponent()!; - _playerController = GameObject.Scene!.FindFirstComponent()!; - - ArgumentNullException.ThrowIfNull(_scoreController); - ArgumentNullException.ThrowIfNull(_timerController); - ArgumentNullException.ThrowIfNull(_playerController); - } - - public void Unpause() - { - GameObject.Scene!.TimeScale = 1.0f; - IsPaused = false; - _menuController.SelectMenuItem("play"); - } - - public void Pause() - { - GameObject.Scene!.TimeScale = 0.0f; - IsPaused = true; - _menuController.SelectMenuItem("escape"); - } - - public override void Update(double parDeltaTime) - { - if (_inputHandler.IsKeyJustPressed(KeyboardButtonCode.Escape)) - { - if (IsPaused) - { - Unpause(); - } - else - { - Pause(); - } - } - } -} \ No newline at end of file diff --git a/DoomDeathmatch/src/Component/MVC/Controller/EnemyController.cs b/DoomDeathmatch/src/Component/MVC/Enemy/EnemyController.cs similarity index 86% rename from DoomDeathmatch/src/Component/MVC/Controller/EnemyController.cs rename to DoomDeathmatch/src/Component/MVC/Enemy/EnemyController.cs index bcbbc77..34561ce 100644 --- a/DoomDeathmatch/src/Component/MVC/Controller/EnemyController.cs +++ b/DoomDeathmatch/src/Component/MVC/Enemy/EnemyController.cs @@ -1,9 +1,10 @@ -using DoomDeathmatch.Component.MVC.Model.Enemy; -using DoomDeathmatch.Component.MVC.Model.Enemy.Attack; -using DoomDeathmatch.Component.MVC.View; +using DoomDeathmatch.Component.MVC.Health; using DoomDeathmatch.Component.Util; +using DoomDeathmatch.Script.Model.Enemy; +using DoomDeathmatch.Script.Model.Enemy.Attack; +using Engine.Util; -namespace DoomDeathmatch.Component.MVC.Controller; +namespace DoomDeathmatch.Component.MVC.Enemy; public class EnemyController : Engine.Scene.Component.Component { @@ -24,7 +25,7 @@ public class EnemyController : Engine.Scene.Component.Component public override void Awake() { - _gameController = GameObject.Scene!.FindFirstComponent()!; + _gameController = EngineUtil.SceneManager.CurrentScene!.FindFirstComponent()!; _healthController = GameObject.GetComponent()!; _enemyView = GameObject.GetComponent()!; _movementController = GameObject.GetComponent()!; @@ -69,7 +70,7 @@ public class EnemyController : Engine.Scene.Component.Component private void OnDeath() { - GameObject.Scene!.Remove(GameObject); + EngineUtil.SceneManager.CurrentScene!.Remove(GameObject); _gameController.ScoreController.AddScore(_enemyData.BaseScore); } } \ No newline at end of file diff --git a/DoomDeathmatch/src/Component/MVC/View/EnemyView.cs b/DoomDeathmatch/src/Component/MVC/Enemy/EnemyView.cs similarity index 62% rename from DoomDeathmatch/src/Component/MVC/View/EnemyView.cs rename to DoomDeathmatch/src/Component/MVC/Enemy/EnemyView.cs index 81061a5..90594e3 100644 --- a/DoomDeathmatch/src/Component/MVC/View/EnemyView.cs +++ b/DoomDeathmatch/src/Component/MVC/Enemy/EnemyView.cs @@ -1,8 +1,9 @@ -using DoomDeathmatch.Component.MVC.Model.Enemy; +using DoomDeathmatch.Script.Model.Enemy; using Engine.Graphics.Texture; using Engine.Scene.Component.BuiltIn.Renderer; +using Engine.Util; -namespace DoomDeathmatch.Component.MVC.View; +namespace DoomDeathmatch.Component.MVC.Enemy; public class EnemyView : Engine.Scene.Component.Component { @@ -15,6 +16,6 @@ public class EnemyView : Engine.Scene.Component.Component public void UpdateView(EnemyData parEnemyData) { - _box2DRenderer.Texture = Engine.Engine.Instance.AssetResourceManager.Load(parEnemyData.Texture); + _box2DRenderer.Texture = EngineUtil.AssetResourceManager.Load(parEnemyData.Texture); } } \ No newline at end of file diff --git a/DoomDeathmatch/src/Component/MVC/GameController.cs b/DoomDeathmatch/src/Component/MVC/GameController.cs new file mode 100644 index 0000000..944f776 --- /dev/null +++ b/DoomDeathmatch/src/Component/MVC/GameController.cs @@ -0,0 +1,77 @@ +using DoomDeathmatch.Component.MVC.Score; +using DoomDeathmatch.Component.MVC.Timer; +using DoomDeathmatch.Component.UI; +using DoomDeathmatch.Scene.GameOver; +using DoomDeathmatch.Scene.Play; +using Engine.Input; +using Engine.Util; + +namespace DoomDeathmatch.Component.MVC; + +public class GameController : Engine.Scene.Component.Component +{ + public bool IsPaused { get; private set; } = false; + public bool IsGameOver { get; private set; } = false; + + public PlayerController PlayerController { get; private set; } = null!; + public ScoreController ScoreController { get; private set; } = null!; + + private TimerController _timerController = null!; + + private readonly MenuControllerComponent _menuController; + private readonly IInputHandler _inputHandler = EngineUtil.InputHandler; + + public GameController(MenuControllerComponent parMenuController) + { + _menuController = parMenuController; + } + + public override void Awake() + { + ScoreController = GameObject.GetComponent()!; + PlayerController = EngineUtil.SceneManager.CurrentScene!.FindFirstComponent()!; + _timerController = GameObject.GetComponent()!; + + ArgumentNullException.ThrowIfNull(ScoreController); + ArgumentNullException.ThrowIfNull(_timerController); + ArgumentNullException.ThrowIfNull(PlayerController); + + PlayerController.OnDeath += GameOver; + _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)) + { + if (IsPaused) + { + Unpause(); + } + else + { + Pause(); + } + } + } + + private void GameOver() + { + IsGameOver = true; + EngineUtil.SceneManager.TransitionTo(() => GameOverScene.Create(ScoreController.Score)); + } +} \ No newline at end of file diff --git a/DoomDeathmatch/src/Component/MVC/View/EnemyHealthView.cs b/DoomDeathmatch/src/Component/MVC/Health/EnemyHealthView.cs similarity index 79% rename from DoomDeathmatch/src/Component/MVC/View/EnemyHealthView.cs rename to DoomDeathmatch/src/Component/MVC/Health/EnemyHealthView.cs index 658352a..b56e19b 100644 --- a/DoomDeathmatch/src/Component/MVC/View/EnemyHealthView.cs +++ b/DoomDeathmatch/src/Component/MVC/Health/EnemyHealthView.cs @@ -1,7 +1,7 @@ -using DoomDeathmatch.Component.MVC.Model; +using DoomDeathmatch.Script.Model; using Engine.Scene.Component.BuiltIn.Renderer; -namespace DoomDeathmatch.Component.MVC.View; +namespace DoomDeathmatch.Component.MVC.Health; public class EnemyHealthView : HealthView { diff --git a/DoomDeathmatch/src/Component/MVC/Controller/HealthController.cs b/DoomDeathmatch/src/Component/MVC/Health/HealthController.cs similarity index 85% rename from DoomDeathmatch/src/Component/MVC/Controller/HealthController.cs rename to DoomDeathmatch/src/Component/MVC/Health/HealthController.cs index 1f441d3..6854e1d 100644 --- a/DoomDeathmatch/src/Component/MVC/Controller/HealthController.cs +++ b/DoomDeathmatch/src/Component/MVC/Health/HealthController.cs @@ -1,11 +1,11 @@ -using DoomDeathmatch.Component.MVC.Model; -using DoomDeathmatch.Component.MVC.View; +using DoomDeathmatch.Script.Model; -namespace DoomDeathmatch.Component.MVC.Controller; +namespace DoomDeathmatch.Component.MVC.Health; public class HealthController : Engine.Scene.Component.Component { public event Action? OnDeath; + public bool IsAlive => _healthModel.Health > 0; private readonly HealthModel _healthModel; private HealthView? _healthView; @@ -45,7 +45,7 @@ public class HealthController : Engine.Scene.Component.Component private void OnHealthChanged(HealthModel parHealthModel) { - if (parHealthModel.Health <= 0) + if (!IsAlive) { OnDeath?.Invoke(); } diff --git a/DoomDeathmatch/src/Component/MVC/View/HealthView.cs b/DoomDeathmatch/src/Component/MVC/Health/HealthView.cs similarity index 86% rename from DoomDeathmatch/src/Component/MVC/View/HealthView.cs rename to DoomDeathmatch/src/Component/MVC/Health/HealthView.cs index 410a874..b05fa38 100644 --- a/DoomDeathmatch/src/Component/MVC/View/HealthView.cs +++ b/DoomDeathmatch/src/Component/MVC/Health/HealthView.cs @@ -1,7 +1,7 @@ -using DoomDeathmatch.Component.MVC.Model; +using DoomDeathmatch.Script.Model; using Engine.Scene.Component.BuiltIn.Renderer; -namespace DoomDeathmatch.Component.MVC.View; +namespace DoomDeathmatch.Component.MVC.Health; public class HealthView : Engine.Scene.Component.Component { diff --git a/DoomDeathmatch/src/Component/MVC/Controller/MovementController.cs b/DoomDeathmatch/src/Component/MVC/MovementController.cs similarity index 86% rename from DoomDeathmatch/src/Component/MVC/Controller/MovementController.cs rename to DoomDeathmatch/src/Component/MVC/MovementController.cs index ee5a679..fbc34bd 100644 --- a/DoomDeathmatch/src/Component/MVC/Controller/MovementController.cs +++ b/DoomDeathmatch/src/Component/MVC/MovementController.cs @@ -1,7 +1,7 @@ -using DoomDeathmatch.Component.Util; +using DoomDeathmatch.Component.Physics; using OpenTK.Mathematics; -namespace DoomDeathmatch.Component.MVC.Controller; +namespace DoomDeathmatch.Component.MVC; public class MovementController : Engine.Scene.Component.Component { @@ -21,6 +21,6 @@ public class MovementController : Engine.Scene.Component.Component public void ApplyMovement(Vector3 parDirection) { - _rigidbody.AddForce(_dragComponent.Drag * Speed * parDirection); + _rigidbody.AddForce(_dragComponent.Drag * Speed * parDirection.Normalized()); } } \ No newline at end of file diff --git a/DoomDeathmatch/src/Component/MVC/Controller/PlayerController.cs b/DoomDeathmatch/src/Component/MVC/PlayerController.cs similarity index 58% rename from DoomDeathmatch/src/Component/MVC/Controller/PlayerController.cs rename to DoomDeathmatch/src/Component/MVC/PlayerController.cs index 59f1464..eed9590 100644 --- a/DoomDeathmatch/src/Component/MVC/Controller/PlayerController.cs +++ b/DoomDeathmatch/src/Component/MVC/PlayerController.cs @@ -1,21 +1,24 @@ -using DoomDeathmatch.Component.MVC.Model.Weapon; -using DoomDeathmatch.Component.Util; -using DoomDeathmatch.Component.Util.Collision; -using Engine.Graphics.Pipeline; +using DoomDeathmatch.Component.MVC.Enemy; +using DoomDeathmatch.Component.MVC.Health; +using DoomDeathmatch.Component.MVC.Weapon; +using DoomDeathmatch.Component.Physics.Collision; using Engine.Input; using Engine.Scene.Component.BuiltIn; -using Engine.Scene.Component.BuiltIn.Renderer; +using Engine.Util; using OpenTK.Mathematics; -namespace DoomDeathmatch.Component.MVC.Controller; +namespace DoomDeathmatch.Component.MVC; public class PlayerController : Engine.Scene.Component.Component { - public bool IsPaused { get; set; } = false; + public event Action? OnDeath; + public bool IsAlive => _healthController.IsAlive; + public HealthController HealthController => _healthController; + public WeaponController WeaponController => _weaponController; public PerspectiveCamera Camera => _camera; - private readonly IInputHandler _inputHandler = Engine.Engine.Instance.InputHandler!; + private readonly IInputHandler _inputHandler = EngineUtil.InputHandler; private HealthController _healthController = null!; private WeaponController _weaponController = null!; @@ -33,16 +36,15 @@ public class PlayerController : Engine.Scene.Component.Component ArgumentNullException.ThrowIfNull(_healthController); ArgumentNullException.ThrowIfNull(_weaponController); + + _healthController.OnDeath += () => OnDeath?.Invoke(); } public override void Update(double parDeltaTime) { - if (IsPaused) + if (!IsAlive || parDeltaTime == 0) return; - if (_inputHandler.IsKeyJustPressed(KeyboardButtonCode.C)) - _weaponController.AddWeapon(WeaponData.Shotgun); - for (var i = 0; i < 10; i++) { if (KeyboardButtonCode.D1 + i > KeyboardButtonCode.D0) @@ -64,7 +66,7 @@ public class PlayerController : Engine.Scene.Component.Component var forward = (_camera.Forward - position).Normalized(); var right = Vector3.Cross(forward, Vector3.UnitZ).Normalized(); - var collisionManager = GameObject.Scene!.FindFirstComponent(); + var collisionManager = EngineUtil.SceneManager.CurrentScene!.FindFirstComponent(); var offsets = _weaponController.WeaponData.ShootPattern.GetShootPattern(forward, Vector3.UnitZ, right); foreach (var offset in offsets) @@ -73,22 +75,6 @@ public class PlayerController : Engine.Scene.Component.Component if (!collisionManager!.Raycast(position, direction, ["enemy", "wall"], out var result)) continue; - var hitDisplayObject = GameObjectUtil.CreateGameObject(GameObject.Scene, - new Transform { Translation = result.HitPoint, Size = new Vector3(0.1f) }, - [ - new Box2DRenderer { Color = new Vector4(0, 0, 1, 1), RenderLayer = RenderLayer.DEFAULT }, - new BillboardComponent { Target = _camera.GameObject.Transform } - ] - ); - - var hitNormalDisplayObject = GameObjectUtil.CreateGameObject(GameObject.Scene, - new Transform { Translation = result.HitPoint + result.Normal, Size = new Vector3(0.1f) }, - [ - new Box2DRenderer { Color = new Vector4(1, 0, 1, 1), RenderLayer = RenderLayer.DEFAULT }, - new BillboardComponent { Target = _camera.GameObject.Transform } - ] - ); - var enemyController = result.HitObject.GetComponent(); enemyController?.HealthController.TakeDamage(_weaponController.WeaponData.Damage); } diff --git a/DoomDeathmatch/src/Component/MVC/Controller/ScoreController.cs b/DoomDeathmatch/src/Component/MVC/Score/ScoreController.cs similarity index 77% rename from DoomDeathmatch/src/Component/MVC/Controller/ScoreController.cs rename to DoomDeathmatch/src/Component/MVC/Score/ScoreController.cs index e15ad48..5d6bf6a 100644 --- a/DoomDeathmatch/src/Component/MVC/Controller/ScoreController.cs +++ b/DoomDeathmatch/src/Component/MVC/Score/ScoreController.cs @@ -1,7 +1,6 @@ -using DoomDeathmatch.Component.MVC.Model; -using DoomDeathmatch.Component.MVC.View; +using DoomDeathmatch.Script.Model; -namespace DoomDeathmatch.Component.MVC.Controller; +namespace DoomDeathmatch.Component.MVC.Score; public class ScoreController : Engine.Scene.Component.Component { diff --git a/DoomDeathmatch/src/Component/MVC/View/ScoreView.cs b/DoomDeathmatch/src/Component/MVC/Score/ScoreView.cs similarity index 81% rename from DoomDeathmatch/src/Component/MVC/View/ScoreView.cs rename to DoomDeathmatch/src/Component/MVC/Score/ScoreView.cs index 0640aae..63868b4 100644 --- a/DoomDeathmatch/src/Component/MVC/View/ScoreView.cs +++ b/DoomDeathmatch/src/Component/MVC/Score/ScoreView.cs @@ -1,7 +1,7 @@ -using DoomDeathmatch.Component.MVC.Model; +using DoomDeathmatch.Script.Model; using Engine.Scene.Component.BuiltIn.Renderer; -namespace DoomDeathmatch.Component.MVC.View; +namespace DoomDeathmatch.Component.MVC.Score; public class ScoreView : Engine.Scene.Component.Component { diff --git a/DoomDeathmatch/src/Component/MVC/Controller/TimerController.cs b/DoomDeathmatch/src/Component/MVC/Timer/TimerController.cs similarity index 76% rename from DoomDeathmatch/src/Component/MVC/Controller/TimerController.cs rename to DoomDeathmatch/src/Component/MVC/Timer/TimerController.cs index c1440fd..6c4ce5a 100644 --- a/DoomDeathmatch/src/Component/MVC/Controller/TimerController.cs +++ b/DoomDeathmatch/src/Component/MVC/Timer/TimerController.cs @@ -1,11 +1,9 @@ -using DoomDeathmatch.Component.MVC.View; -using Engine.Util; +using Engine.Util; -namespace DoomDeathmatch.Component.MVC.Controller; +namespace DoomDeathmatch.Component.MVC.Timer; public class TimerController : Engine.Scene.Component.Component { - public bool IsPaused { get; set; } = false; public event Action? OnFinished; private readonly TickableTimer _tickableTimer = new(60 + 10); @@ -22,9 +20,6 @@ public class TimerController : Engine.Scene.Component.Component public override void Update(double parDeltaTime) { - if (IsPaused) - return; - _tickableTimer.Update(parDeltaTime); } diff --git a/DoomDeathmatch/src/Component/MVC/View/TimerView.cs b/DoomDeathmatch/src/Component/MVC/Timer/TimerView.cs similarity index 91% rename from DoomDeathmatch/src/Component/MVC/View/TimerView.cs rename to DoomDeathmatch/src/Component/MVC/Timer/TimerView.cs index afc2486..fb7193a 100644 --- a/DoomDeathmatch/src/Component/MVC/View/TimerView.cs +++ b/DoomDeathmatch/src/Component/MVC/Timer/TimerView.cs @@ -1,6 +1,6 @@ using Engine.Scene.Component.BuiltIn.Renderer; -namespace DoomDeathmatch.Component.MVC.View; +namespace DoomDeathmatch.Component.MVC.Timer; public class TimerView : Engine.Scene.Component.Component { diff --git a/DoomDeathmatch/src/Component/MVC/View/WeaponView.cs b/DoomDeathmatch/src/Component/MVC/View/WeaponView.cs deleted file mode 100644 index 14cbbb2..0000000 --- a/DoomDeathmatch/src/Component/MVC/View/WeaponView.cs +++ /dev/null @@ -1,32 +0,0 @@ -using DoomDeathmatch.Component.MVC.Model; -using DoomDeathmatch.Component.MVC.Model.Weapon; -using Engine.Graphics.Texture; -using Engine.Scene.Component.BuiltIn.Renderer; - -namespace DoomDeathmatch.Component.MVC.View; - -public class WeaponView : Engine.Scene.Component.Component -{ - private readonly TextRenderer _weaponName; - private readonly TextRenderer _weaponAmmo; - private readonly Box2DRenderer _weaponSprite; - - public WeaponView(TextRenderer parWeaponName, TextRenderer parWeaponAmmo, Box2DRenderer parWeaponSprite) - { - _weaponName = parWeaponName; - _weaponAmmo = parWeaponAmmo; - _weaponSprite = parWeaponSprite; - } - - public void UpdateView(WeaponData parWeaponData) - { - UpdateAmmoView(parWeaponData); - _weaponName.Text = $"Оружие: {parWeaponData.Name}"; - _weaponSprite.Texture = Engine.Engine.Instance.AssetResourceManager.Load(parWeaponData.Texture); - } - - public void UpdateAmmoView(WeaponData parWeaponData) - { - _weaponAmmo.Text = $"Патроны: {parWeaponData.Ammo}/{parWeaponData.MaxAmmo}"; - } -} \ No newline at end of file diff --git a/DoomDeathmatch/src/Component/MVC/Controller/WeaponController.cs b/DoomDeathmatch/src/Component/MVC/Weapon/WeaponController.cs similarity index 74% rename from DoomDeathmatch/src/Component/MVC/Controller/WeaponController.cs rename to DoomDeathmatch/src/Component/MVC/Weapon/WeaponController.cs index 3164c23..a2e9574 100644 --- a/DoomDeathmatch/src/Component/MVC/Controller/WeaponController.cs +++ b/DoomDeathmatch/src/Component/MVC/Weapon/WeaponController.cs @@ -1,8 +1,7 @@ -using DoomDeathmatch.Component.MVC.Model; -using DoomDeathmatch.Component.MVC.Model.Weapon; -using DoomDeathmatch.Component.MVC.View; +using DoomDeathmatch.Script.Model; +using DoomDeathmatch.Script.Model.Weapon; -namespace DoomDeathmatch.Component.MVC.Controller; +namespace DoomDeathmatch.Component.MVC.Weapon; public class WeaponController : Engine.Scene.Component.Component { @@ -26,9 +25,10 @@ public class WeaponController : Engine.Scene.Component.Component if (_weaponModel.SelectedWeapon.Ammo <= 0) return false; - // _weaponModel.SelectedWeapon.Ammo--; + _weaponModel.SelectedWeapon.Ammo--; OnWeaponShot?.Invoke(_weaponModel.SelectedWeapon); + _weaponView.Fire(); return true; } @@ -46,6 +46,14 @@ public class WeaponController : Engine.Scene.Component.Component _weaponModel.Weapons.Add(parWeaponData); } + public void AddOrMergeWeapon(WeaponData parWeaponData) + { + if (!_weaponModel.Weapons.Contains(parWeaponData)) + _weaponModel.Weapons.Add(parWeaponData); + else + _weaponModel.Weapons.First(parData => parData.Id == parWeaponData.Id).Ammo += parWeaponData.Ammo; + } + public void RemoveWeapon(int parIndex) { if (parIndex <= 0 || parIndex >= _weaponModel.Weapons.Count) @@ -65,6 +73,11 @@ public class WeaponController : Engine.Scene.Component.Component _weaponModel.SelectedWeaponIndex = parIndex; } + // public WeaponData? FindWeapon(string parId) + // { + // return _weaponModel.Weapons.FirstOrDefault(parData => parData.Id == parId); + // } + private void WeaponSelected(WeaponData? parOldWeapon, WeaponData parNewWeapon) { if (parOldWeapon != null) diff --git a/DoomDeathmatch/src/Component/MVC/Weapon/WeaponView.cs b/DoomDeathmatch/src/Component/MVC/Weapon/WeaponView.cs new file mode 100644 index 0000000..0d9c6d2 --- /dev/null +++ b/DoomDeathmatch/src/Component/MVC/Weapon/WeaponView.cs @@ -0,0 +1,71 @@ +using DoomDeathmatch.Script; +using DoomDeathmatch.Script.Model.Weapon; +using Engine.Graphics.Texture; +using Engine.Scene.Component.BuiltIn.Renderer; +using Engine.Util; + +namespace DoomDeathmatch.Component.MVC.Weapon; + +public class WeaponView : Engine.Scene.Component.Component +{ + private readonly TextRenderer _weaponName; + private readonly TextRenderer _weaponAmmo; + private readonly Box2DRenderer _weaponSprite; + + private AnimationPlayer? _weaponFireAnimation; + private Texture? _idleTexture; + + public WeaponView(TextRenderer parWeaponName, TextRenderer parWeaponAmmo, Box2DRenderer parWeaponSprite) + { + _weaponName = parWeaponName; + _weaponAmmo = parWeaponAmmo; + _weaponSprite = parWeaponSprite; + } + + public override void Update(double parDeltaTime) + { + _weaponFireAnimation?.Update(parDeltaTime); + } + + public void UpdateView(WeaponData parWeaponData) + { + UpdateAmmoView(parWeaponData); + _weaponName.Text = $"Оружие: {parWeaponData.Name}"; + + _idleTexture = EngineUtil.AssetResourceManager.Load(parWeaponData.IdleTexture); + _weaponSprite.Texture = _idleTexture; + + _weaponFireAnimation?.Reset(); + if (_weaponFireAnimation != null) + { + _weaponFireAnimation.OnFrameChanged -= OnAnimationFrame; + _weaponFireAnimation.OnFinish -= OnAnimationFinish; + } + + var frames = parWeaponData.FireAnimation.Select(EngineUtil.AssetResourceManager.Load).ToList(); + + _weaponFireAnimation = new AnimationPlayer(parWeaponData.FireAnimationDuration) { Frames = frames }; + _weaponFireAnimation.OnFrameChanged += OnAnimationFrame; + _weaponFireAnimation.OnFinish += OnAnimationFinish; + } + + public void UpdateAmmoView(WeaponData parWeaponData) + { + _weaponAmmo.Text = $"Патроны: {parWeaponData.Ammo}/{parWeaponData.MaxAmmo}"; + } + + public void Fire() + { + _weaponFireAnimation?.Start(); + } + + private void OnAnimationFrame(Texture parFrameTexture) + { + _weaponSprite.Texture = parFrameTexture; + } + + private void OnAnimationFinish() + { + _weaponSprite.Texture = _idleTexture; + } +} \ No newline at end of file diff --git a/DoomDeathmatch/src/Component/ObjectSpawnerComponent.cs b/DoomDeathmatch/src/Component/ObjectSpawnerComponent.cs new file mode 100644 index 0000000..e91b0b3 --- /dev/null +++ b/DoomDeathmatch/src/Component/ObjectSpawnerComponent.cs @@ -0,0 +1,11 @@ +using DoomDeathmatch.Script; + +namespace DoomDeathmatch.Component; + +public class ObjectSpawnerComponent(ObjectSpawner parObjectSpawner) : Engine.Scene.Component.Component +{ + public override void Update(double parDeltaTime) + { + parObjectSpawner.Update(parDeltaTime); + } +} \ No newline at end of file diff --git a/DoomDeathmatch/src/Component/Util/Collision/AABBColliderComponent.cs b/DoomDeathmatch/src/Component/Physics/Collision/AABBColliderComponent.cs similarity index 87% rename from DoomDeathmatch/src/Component/Util/Collision/AABBColliderComponent.cs rename to DoomDeathmatch/src/Component/Physics/Collision/AABBColliderComponent.cs index 0cc11c9..67bb0ec 100644 --- a/DoomDeathmatch/src/Component/Util/Collision/AABBColliderComponent.cs +++ b/DoomDeathmatch/src/Component/Physics/Collision/AABBColliderComponent.cs @@ -1,6 +1,7 @@ -using OpenTK.Mathematics; +using DoomDeathmatch.Script.Collision; +using OpenTK.Mathematics; -namespace DoomDeathmatch.Component.Util.Collision; +namespace DoomDeathmatch.Component.Physics.Collision; public class AABBColliderComponent : Engine.Scene.Component.Component { diff --git a/DoomDeathmatch/src/Component/Util/Collision/ColliderForceFieldComponent.cs b/DoomDeathmatch/src/Component/Physics/Collision/ColliderForceFieldComponent.cs similarity index 81% rename from DoomDeathmatch/src/Component/Util/Collision/ColliderForceFieldComponent.cs rename to DoomDeathmatch/src/Component/Physics/Collision/ColliderForceFieldComponent.cs index df2fb63..e3ca97f 100644 --- a/DoomDeathmatch/src/Component/Util/Collision/ColliderForceFieldComponent.cs +++ b/DoomDeathmatch/src/Component/Physics/Collision/ColliderForceFieldComponent.cs @@ -1,6 +1,6 @@ using OpenTK.Mathematics; -namespace DoomDeathmatch.Component.Util.Collision; +namespace DoomDeathmatch.Component.Physics.Collision; public class ColliderForceFieldComponent : Engine.Scene.Component.Component { @@ -20,9 +20,9 @@ public class ColliderForceFieldComponent : Engine.Scene.Component.Component var normal = _collider.Collider.GetCollisionNormal(parCollider.Collider); var speedAlongNormal = Vector3.Dot(rigidbody.Velocity, normal); - if (speedAlongNormal >= 0) + if (speedAlongNormal > 0) return; - rigidbody.AddVelocity(-normal * speedAlongNormal); + rigidbody.AddVelocity(-normal * (speedAlongNormal * 1.75f)); } } \ No newline at end of file diff --git a/DoomDeathmatch/src/Component/Util/Collision/CollisionManager.cs b/DoomDeathmatch/src/Component/Physics/Collision/CollisionManagerComponent.cs similarity index 87% rename from DoomDeathmatch/src/Component/Util/Collision/CollisionManager.cs rename to DoomDeathmatch/src/Component/Physics/Collision/CollisionManagerComponent.cs index ea9e81b..55c6dbd 100644 --- a/DoomDeathmatch/src/Component/Util/Collision/CollisionManager.cs +++ b/DoomDeathmatch/src/Component/Physics/Collision/CollisionManagerComponent.cs @@ -1,15 +1,17 @@ using System.Diagnostics.CodeAnalysis; +using DoomDeathmatch.Script.Collision; +using Engine.Util; using OpenTK.Mathematics; -namespace DoomDeathmatch.Component.Util.Collision; +namespace DoomDeathmatch.Component.Physics.Collision; -public class CollisionManager : Engine.Scene.Component.Component +public class CollisionManagerComponent : Engine.Scene.Component.Component { private List _colliders = []; - public override void PreUpdate() + public override void PreUpdate(double parDeltaTime) { - _colliders = GameObject.Scene!.FindAllComponents(); + _colliders = EngineUtil.SceneManager.CurrentScene!.FindAllComponents(); } public override void Update(double parDeltaTime) @@ -23,9 +25,9 @@ public class CollisionManager : Engine.Scene.Component.Component var colliderB = _colliders[j]; var canCollideAB = colliderA.ExcludeColliderCollideGroups.Count == 0 || - !colliderA.ExcludeColliderCollideGroups.IsSubsetOf(colliderB.ColliderGroups); + !colliderB.ColliderGroups.Overlaps(colliderA.ExcludeColliderCollideGroups); var canCollideBA = colliderB.ExcludeColliderCollideGroups.Count == 0 || - !colliderB.ExcludeColliderCollideGroups.IsSubsetOf(colliderA.ColliderGroups); + !colliderA.ColliderGroups.Overlaps(colliderB.ExcludeColliderCollideGroups); if (!canCollideAB && !canCollideBA) continue; diff --git a/DoomDeathmatch/src/Component/Util/DragComponent.cs b/DoomDeathmatch/src/Component/Physics/DragComponent.cs similarity index 70% rename from DoomDeathmatch/src/Component/Util/DragComponent.cs rename to DoomDeathmatch/src/Component/Physics/DragComponent.cs index 410473a..9b2889c 100644 --- a/DoomDeathmatch/src/Component/Util/DragComponent.cs +++ b/DoomDeathmatch/src/Component/Physics/DragComponent.cs @@ -1,11 +1,11 @@ using OpenTK.Mathematics; -namespace DoomDeathmatch.Component.Util; +namespace DoomDeathmatch.Component.Physics; public class DragComponent : Engine.Scene.Component.Component { public float Drag { get; set; } = 1f; - public Vector3 Coefficient { get; set; } = Vector3.One; + public Vector3 Multiplier { get; set; } = Vector3.One; private RigidbodyComponent _rigidbody = null!; @@ -18,6 +18,6 @@ public class DragComponent : Engine.Scene.Component.Component public override void Update(double parDeltaTime) { - _rigidbody.AddForce(-Drag * (_rigidbody.Velocity * Coefficient)); + _rigidbody.AddForce(-Drag * (_rigidbody.Velocity * Multiplier)); } } \ No newline at end of file diff --git a/DoomDeathmatch/src/Component/Util/GravityComponent.cs b/DoomDeathmatch/src/Component/Physics/GravityComponent.cs similarity index 96% rename from DoomDeathmatch/src/Component/Util/GravityComponent.cs rename to DoomDeathmatch/src/Component/Physics/GravityComponent.cs index 36bde34..335197d 100644 --- a/DoomDeathmatch/src/Component/Util/GravityComponent.cs +++ b/DoomDeathmatch/src/Component/Physics/GravityComponent.cs @@ -1,6 +1,6 @@ using OpenTK.Mathematics; -namespace DoomDeathmatch.Component.Util; +namespace DoomDeathmatch.Component.Physics; public class GravityComponent : Engine.Scene.Component.Component { diff --git a/DoomDeathmatch/src/Component/Util/RigidbodyComponent.cs b/DoomDeathmatch/src/Component/Physics/RigidbodyComponent.cs similarity index 95% rename from DoomDeathmatch/src/Component/Util/RigidbodyComponent.cs rename to DoomDeathmatch/src/Component/Physics/RigidbodyComponent.cs index 219f5a3..9a6730b 100644 --- a/DoomDeathmatch/src/Component/Util/RigidbodyComponent.cs +++ b/DoomDeathmatch/src/Component/Physics/RigidbodyComponent.cs @@ -1,6 +1,6 @@ using OpenTK.Mathematics; -namespace DoomDeathmatch.Component.Util; +namespace DoomDeathmatch.Component.Physics; public class RigidbodyComponent : Engine.Scene.Component.Component { @@ -34,6 +34,7 @@ public class RigidbodyComponent : Engine.Scene.Component.Component Acceleration = Force / Mass; Velocity += Acceleration * (float)parDeltaTime; GameObject.Transform.Translation += Velocity * (float)parDeltaTime; + Force = Vector3.Zero; } } \ No newline at end of file diff --git a/DoomDeathmatch/src/Component/UI/SelectorComponent.cs b/DoomDeathmatch/src/Component/UI/SelectorComponent.cs index e116a3a..b7c1f73 100644 --- a/DoomDeathmatch/src/Component/UI/SelectorComponent.cs +++ b/DoomDeathmatch/src/Component/UI/SelectorComponent.cs @@ -1,4 +1,5 @@ using Engine.Input; +using Engine.Util; namespace DoomDeathmatch.Component.UI; @@ -11,7 +12,7 @@ public class SelectorComponent : Engine.Scene.Component.Component public event Action? OnSelect; - private readonly IInputHandler _inputHandler = Engine.Engine.Instance.InputHandler!; + private readonly IInputHandler _inputHandler = EngineUtil.InputHandler; private readonly List _children = []; diff --git a/DoomDeathmatch/src/Component/UI/StackComponent.cs b/DoomDeathmatch/src/Component/UI/StackComponent.cs index 9183ee0..7297e8e 100644 --- a/DoomDeathmatch/src/Component/UI/StackComponent.cs +++ b/DoomDeathmatch/src/Component/UI/StackComponent.cs @@ -1,4 +1,4 @@ -using Engine.Scene; +using DoomDeathmatch.Script.UI; using OpenTK.Mathematics; namespace DoomDeathmatch.Component.UI; diff --git a/DoomDeathmatch/src/Component/UI/TextAlignComponent.cs b/DoomDeathmatch/src/Component/UI/TextAlignComponent.cs index 5b79755..29f39b4 100644 --- a/DoomDeathmatch/src/Component/UI/TextAlignComponent.cs +++ b/DoomDeathmatch/src/Component/UI/TextAlignComponent.cs @@ -1,4 +1,5 @@ -using Engine.Scene.Component.BuiltIn.Renderer; +using DoomDeathmatch.Script.UI; +using Engine.Scene.Component.BuiltIn.Renderer; using OpenTK.Mathematics; namespace DoomDeathmatch.Component.UI; @@ -42,11 +43,4 @@ public class TextAlignComponent : Engine.Scene.Component.Component _ => throw new ArgumentOutOfRangeException(nameof(Alignment), Alignment, null) }; } - - public enum Align - { - Left, - Center, - Right - } } \ No newline at end of file diff --git a/DoomDeathmatch/src/Component/UI/TextInputComponent.cs b/DoomDeathmatch/src/Component/UI/TextInputComponent.cs new file mode 100644 index 0000000..550bea4 --- /dev/null +++ b/DoomDeathmatch/src/Component/UI/TextInputComponent.cs @@ -0,0 +1,80 @@ +using Engine.Input; +using Engine.Util; + +namespace DoomDeathmatch.Component.UI; + +public class TextInputComponent : Engine.Scene.Component.Component +{ + public event Action? OnInput; + + public bool IsActive { get; set; } = false; + public string Input { get; private set; } = ""; + + public float InputDelay + { + get => _inputDelay; + set + { + _inputDelay = value; + _inputTimer.TotalTime = value; + } + } + + private readonly IInputHandler _inputHandler = EngineUtil.InputHandler; + + private readonly KeyboardButtonCode[] _acceptedKeys = + KeyboardButtonCodeHelper.GetAllPrintableKeys().Append(KeyboardButtonCode.Backspace).ToArray(); + + private readonly TickableTimer _inputTimer; + private float _inputDelay = 0; + private readonly HashSet _lastKeys = []; + + public TextInputComponent(float parInputDelay = 0.2f) + { + _inputDelay = parInputDelay; + _inputTimer = new TickableTimer(_inputDelay) { CurrentTime = 0 }; + } + + public override void Update(double parDeltaTime) + { + if (!IsActive) + return; + + _inputTimer.Update(parDeltaTime); + + foreach (var key in _acceptedKeys) + { + if (_lastKeys.Contains(key) && !_inputTimer.IsFinished) + continue; + + if (_inputHandler.IsKeyPressed(key)) + { + var invoke = false; + if (key == KeyboardButtonCode.Backspace) + { + if (Input.Length > 0) + { + Input = Input[..^1]; + invoke = true; + } + } + else + { + Input += key.GetChar(); + invoke = true; + } + + if (invoke) + OnInput?.Invoke(Input); + + _lastKeys.Add(key); + + _inputTimer.Reset(); + } + else + { + _lastKeys.Remove(key); + } + } + } +} \ No newline at end of file diff --git a/DoomDeathmatch/src/Component/UI/UiComponent.cs b/DoomDeathmatch/src/Component/UI/UiComponent.cs index 6271d46..562aff8 100644 --- a/DoomDeathmatch/src/Component/UI/UiComponent.cs +++ b/DoomDeathmatch/src/Component/UI/UiComponent.cs @@ -1,5 +1,8 @@ -using Engine.Input; +using DoomDeathmatch.Script.UI; +using Engine.Input; +using Engine.Util; using OpenTK.Mathematics; +using Math = System.Math; namespace DoomDeathmatch.Component.UI; @@ -13,7 +16,7 @@ public class UiComponent : Engine.Scene.Component.Component public event Action? OnClick; public event Action? OnMouseOver; - private readonly IInputHandler _inputHandler = Engine.Engine.Instance.InputHandler!; + private readonly IInputHandler _inputHandler = EngineUtil.InputHandler; public override void Update(double parDeltaTime) { diff --git a/DoomDeathmatch/src/Component/UI/UiContainerComponent.cs b/DoomDeathmatch/src/Component/UI/UiContainerComponent.cs index d8392f7..ad9c6e3 100644 --- a/DoomDeathmatch/src/Component/UI/UiContainerComponent.cs +++ b/DoomDeathmatch/src/Component/UI/UiContainerComponent.cs @@ -1,12 +1,13 @@ using Engine.Input; using Engine.Scene.Component.BuiltIn; +using Engine.Util; using OpenTK.Mathematics; namespace DoomDeathmatch.Component.UI; public class UiContainerComponent : UiComponent { - private readonly IInputHandler _inputHandler = Engine.Engine.Instance.InputHandler!; + private readonly IInputHandler _inputHandler = EngineUtil.InputHandler; public Camera? Camera { get; set; } public Vector3 MousePosition { get; private set; } diff --git a/DoomDeathmatch/src/Component/Util/CopySizeComponent.cs b/DoomDeathmatch/src/Component/Util/CopySizeComponent.cs index 034098c..1d142b3 100644 --- a/DoomDeathmatch/src/Component/Util/CopySizeComponent.cs +++ b/DoomDeathmatch/src/Component/Util/CopySizeComponent.cs @@ -5,7 +5,7 @@ namespace DoomDeathmatch.Component.Util; public class CopySizeComponent : Engine.Scene.Component.Component { - public Vector3 Coefficient { get; set; } = Vector3.One; + public Vector3 Multiplier { get; set; } = Vector3.One; public Transform? Target { get; set; } public override void Update(double parDeltaTime) @@ -13,13 +13,13 @@ public class CopySizeComponent : Engine.Scene.Component.Component if (Target == null) return; - if (Coefficient.X != 0) - GameObject.Transform.Size.X = Target.Size.X * Coefficient.X; + if (Multiplier.X != 0) + GameObject.Transform.Size.X = Target.Size.X * Multiplier.X; - if (Coefficient.Y != 0) - GameObject.Transform.Size.Y = Target.Size.Y * Coefficient.Y; + if (Multiplier.Y != 0) + GameObject.Transform.Size.Y = Target.Size.Y * Multiplier.Y; - if (Coefficient.Z != 0) - GameObject.Transform.Size.Z = Target.Size.Z * Coefficient.Z; + if (Multiplier.Z != 0) + GameObject.Transform.Size.Z = Target.Size.Z * Multiplier.Z; } } \ No newline at end of file diff --git a/DoomDeathmatch/src/Component/Util/FireballComponent.cs b/DoomDeathmatch/src/Component/Util/FireballComponent.cs index 7ca6a59..d7e9129 100644 --- a/DoomDeathmatch/src/Component/Util/FireballComponent.cs +++ b/DoomDeathmatch/src/Component/Util/FireballComponent.cs @@ -1,5 +1,6 @@ -using DoomDeathmatch.Component.MVC.Controller; -using DoomDeathmatch.Component.Util.Collision; +using DoomDeathmatch.Component.MVC.Health; +using DoomDeathmatch.Component.Physics.Collision; +using Engine.Util; namespace DoomDeathmatch.Component.Util; @@ -21,6 +22,6 @@ public class FireballComponent : Engine.Scene.Component.Component var healthController = parCollider.GameObject.GetComponent(); healthController?.TakeDamage(Damage); - GameObject.Scene!.Remove(GameObject); + EngineUtil.SceneManager.CurrentScene!.Remove(GameObject); } } \ No newline at end of file diff --git a/DoomDeathmatch/src/Component/Util/ControllerComponent.cs b/DoomDeathmatch/src/Component/Util/PlayerMovementComponent.cs similarity index 84% rename from DoomDeathmatch/src/Component/Util/ControllerComponent.cs rename to DoomDeathmatch/src/Component/Util/PlayerMovementComponent.cs index 75e51ed..8ac7ee6 100644 --- a/DoomDeathmatch/src/Component/Util/ControllerComponent.cs +++ b/DoomDeathmatch/src/Component/Util/PlayerMovementComponent.cs @@ -1,14 +1,15 @@ -using DoomDeathmatch.Component.MVC.Controller; +using DoomDeathmatch.Component.MVC; using Engine.Input; +using Engine.Util; using OpenTK.Mathematics; namespace DoomDeathmatch.Component.Util; -public class ControllerComponent : Engine.Scene.Component.Component +public class PlayerMovementComponent : Engine.Scene.Component.Component { - public float RotationSpeed { get; set; } = 70.0f; + public float RotationSpeed { get; set; } = 110.0f; - private readonly IInputHandler _inputHandler = Engine.Engine.Instance.InputHandler!; + private readonly IInputHandler _inputHandler = EngineUtil.InputHandler; private MovementController _movementController = null!; public override void Awake() diff --git a/DoomDeathmatch/src/DoomDeathmatch.cs b/DoomDeathmatch/src/DoomDeathmatch.cs index b9e6638..2f01e3b 100644 --- a/DoomDeathmatch/src/DoomDeathmatch.cs +++ b/DoomDeathmatch/src/DoomDeathmatch.cs @@ -1,4 +1,5 @@ using DoomDeathmatch.Scene; +using DoomDeathmatch.Scene.Main; namespace DoomDeathmatch; @@ -6,6 +7,6 @@ public static class DoomDeathmatch { public static void Initialize(Engine.Engine parEngine) { - parEngine.SceneManager.TransitionTo(() => MainScene.Create(parEngine)); + parEngine.SceneManager.TransitionTo(() => MainScene.Create()); } } \ No newline at end of file diff --git a/DoomDeathmatch/src/GameObjectUtil.cs b/DoomDeathmatch/src/GameObjectUtil.cs index 279d0e6..135e998 100644 --- a/DoomDeathmatch/src/GameObjectUtil.cs +++ b/DoomDeathmatch/src/GameObjectUtil.cs @@ -1,7 +1,6 @@ -using DoomDeathmatch.Component.Util.Collision; +using DoomDeathmatch.Component.Physics.Collision; using Engine.Scene; using Engine.Scene.Component.BuiltIn; -using Engine.Scene.Component.BuiltIn.Renderer; using OpenTK.Mathematics; namespace DoomDeathmatch; @@ -67,78 +66,6 @@ public static class GameObjectUtil ] ); - // var color = new Vector4(1, 0, 0, 0.5f); - // var size = parCollider.Collider.Size; - // - // var frontPlaneObject = CreateGameObject(parScene, - // new Transform - // { - // Translation = new Vector3(0, -size.Y / 2, 0), - // Size = new Vector3(size.X, size.Z, 0), - // Rotation = Quaternion.FromAxisAngle(Vector3.UnitX, MathF.PI / 2) - // }, - // [ - // new Box2DRenderer { Color = color } - // ] - // ); - // - // var backPlaneObject = CreateGameObject(parScene, - // new Transform - // { - // Translation = new Vector3(0, size.Y / 2, 0), - // Size = new Vector3(size.X, size.Z, 0), - // Rotation = Quaternion.FromAxisAngle(Vector3.UnitX, -MathF.PI / 2) - // }, - // [ - // new Box2DRenderer { Color = color } - // ] - // ); - // - // var leftPlaneObject = CreateGameObject(parScene, - // new Transform - // { - // Translation = new Vector3(-size.X / 2, 0, 0), - // Size = new Vector3(size.Z, size.Y, 0), - // Rotation = Quaternion.FromAxisAngle(Vector3.UnitY, MathF.PI / 2) - // }, - // [ - // new Box2DRenderer { Color = color } - // ] - // ); - // - // var rightPlaneObject = CreateGameObject(parScene, - // new Transform - // { - // Translation = new Vector3(size.X / 2, 0, 0), - // Size = new Vector3(size.Z, size.Y, 0), - // Rotation = Quaternion.FromAxisAngle(Vector3.UnitY, -MathF.PI / 2) - // }, - // [ - // new Box2DRenderer { Color = color } - // ] - // ); - // - // var topPlaneObject = CreateGameObject(parScene, - // new Transform { Translation = new Vector3(0, 0, size.Z / 2), Size = new Vector3(size.X, size.Y, 0) }, - // [ - // new Box2DRenderer { Color = color } - // ] - // ); - // - // var bottomPlaneObject = CreateGameObject(parScene, - // new Transform { Translation = new Vector3(0, 0, -size.Z / 2), Size = new Vector3(size.X, size.Y, 0) }, - // [ - // new Box2DRenderer { Color = color } - // ] - // ); - // - // parScene.AddChild(forceFieldObject, frontPlaneObject); - // parScene.AddChild(forceFieldObject, backPlaneObject); - // parScene.AddChild(forceFieldObject, leftPlaneObject); - // parScene.AddChild(forceFieldObject, rightPlaneObject); - // parScene.AddChild(forceFieldObject, topPlaneObject); - // parScene.AddChild(forceFieldObject, bottomPlaneObject); - return forceFieldObject; } } \ No newline at end of file diff --git a/DoomDeathmatch/src/Scene/GameOver/GameOverScene.cs b/DoomDeathmatch/src/Scene/GameOver/GameOverScene.cs new file mode 100644 index 0000000..aba74d3 --- /dev/null +++ b/DoomDeathmatch/src/Scene/GameOver/GameOverScene.cs @@ -0,0 +1,71 @@ +using DoomDeathmatch.Component.UI; +using DoomDeathmatch.Scene.Main; +using DoomDeathmatch.Script.Score; +using Engine.Graphics.Pipeline; +using Engine.Input; +using Engine.Util; +using OpenTK.Mathematics; + +namespace DoomDeathmatch.Scene.GameOver; + +public static class GameOverScene +{ + public static Engine.Scene.Scene Create(int parScore) + { + var scene = new Engine.Scene.Scene(); + + var (cameraObject, camera) = UiUtil.CreateOrthographicCamera(scene); + + var (uiContainerObject, uiContainer) = + UiUtil.CreateBackgroundUi(scene, new UiContainerComponent { Camera = camera }); + + var (logoObject, logoUi) = UiUtil.CreateLogoUi(scene, uiContainer); + logoUi.Offset = new Vector2(0, 3f); + scene.AddChild(uiContainerObject, logoObject); + + var (resultUiObject, resultUi, _) = + UiUtil.CreateTextUi(scene, uiContainer, UiUtil.GetDoomFont(), $"Ваш результат: {parScore}"); + + var (nameUiObject, nameUi, (_, nameTextRenderer)) = + UiUtil.CreateTextUi(scene, uiContainer, UiUtil.GetDoomFont(), "Имя: "); + + var nameInputComponent = new TextInputComponent { IsActive = true }; + nameInputComponent.OnInput += parName => + { + nameTextRenderer.Text = $"Имя: {parName}"; + }; + nameUiObject.AddComponent(nameInputComponent); + + var (nextUiObject, nextUi, _) = + UiUtil.CreateTextUi(scene, uiContainer, UiUtil.GetDoomFont(), "Далее"); + nextUi.OnClick += _ => + { + if (string.IsNullOrEmpty(nameInputComponent.Input)) + return; + + SubmitScore(nameInputComponent.Input, parScore); + EngineUtil.SceneManager.TransitionTo(MainScene.Create); + }; + + var (selectorObject, selector) = UiUtil.CreateSelectorUi(scene, + new SelectorComponent { Children = { nextUi }, SelectKey = KeyboardButtonCode.Enter }, RenderLayer.HUD); + + var (stackObject, stack) = UiUtil.CreateStackUi(scene, + new StackComponent { Offset = new Vector2(0, -1f), Container = uiContainer, Children = { resultUi, nextUi } }); + stackObject.Transform.Size.Xy = new Vector2(1f, 6f); + + scene.AddChild(stackObject, resultUiObject); + scene.AddChild(stackObject, nameUiObject); + scene.AddChild(stackObject, nextUiObject); + + + return scene; + } + + private static void SubmitScore(string parUsername, int parScore) + { + var table = ScoreTable.LoadOrCreate(Path.Combine(EngineUtil.DataFolder, "leaders.json")); + table.Rows.Add(new ScoreRow { Name = parUsername, Score = parScore }); + ScoreTable.Save(table, Path.Combine(EngineUtil.DataFolder, "leaders.json")); + } +} \ No newline at end of file diff --git a/DoomDeathmatch/src/Scene/MainScene.cs b/DoomDeathmatch/src/Scene/Main/MainScene.cs similarity index 56% rename from DoomDeathmatch/src/Scene/MainScene.cs rename to DoomDeathmatch/src/Scene/Main/MainScene.cs index 5d2851a..0fa86ca 100644 --- a/DoomDeathmatch/src/Scene/MainScene.cs +++ b/DoomDeathmatch/src/Scene/Main/MainScene.cs @@ -1,13 +1,21 @@ -using DoomDeathmatch.Component.UI; +using System.Text.Json; +using DoomDeathmatch.Component.UI; +using DoomDeathmatch.Component.Util; using DoomDeathmatch.Scene.Play; +using DoomDeathmatch.Script.Score; +using DoomDeathmatch.Script.UI; +using Engine.Graphics.Pipeline; using Engine.Scene; +using Engine.Scene.Component.BuiltIn; +using Engine.Scene.Component.BuiltIn.Renderer; +using Engine.Util; using OpenTK.Mathematics; -namespace DoomDeathmatch.Scene; +namespace DoomDeathmatch.Scene.Main; public static class MainScene { - public static Engine.Scene.Scene Create(Engine.Engine parEngine) + public static Engine.Scene.Scene Create() { var scene = new Engine.Scene.Scene(); @@ -16,7 +24,7 @@ public static class MainScene var (uiContainerObject, uiContainer) = UiUtil.CreateBackgroundUi(scene, new UiContainerComponent { Camera = camera }); - var (logoObject, logoUi) = UiUtil.CreateLogoUi(parEngine, scene, uiContainer); + var (logoObject, logoUi) = UiUtil.CreateLogoUi(scene, uiContainer); logoUi.Offset = new Vector2(0, 3f); scene.AddChild(uiContainerObject, logoObject); @@ -25,14 +33,14 @@ public static class MainScene menuController ]); - var mainMenu = CreateMainMenu(parEngine, scene, uiContainer, menuController); + var mainMenu = CreateMainMenu(scene, uiContainer, menuController); menuController.AddMenuItem("main", mainMenu); - var leadersMenu = CreateLeadersMenu(parEngine, scene, uiContainer, menuController); + var leadersMenu = CreateLeadersMenu(scene, uiContainer, menuController); leadersMenu.IsEnabled = false; menuController.AddMenuItem("leaders", leadersMenu); - var rulesMenu = CreateRulesMenu(parEngine, scene, uiContainer, menuController); + var rulesMenu = CreateRulesMenu(scene, uiContainer, menuController); rulesMenu.IsEnabled = false; menuController.AddMenuItem("rules", rulesMenu); @@ -43,22 +51,22 @@ public static class MainScene return scene; } - private static GameObject CreateMainMenu(Engine.Engine parEngine, Engine.Scene.Scene parScene, + private static GameObject CreateMainMenu(Engine.Scene.Scene parScene, UiContainerComponent parUiContainer, MenuControllerComponent parMenuController) { var parentObject = GameObjectUtil.CreateGameObject(parScene); var (playUiObject, playUi, _) = - UiUtil.CreateTextUi(parScene, parUiContainer, UiUtil.GetDoomFont(parEngine), "Играть"); + UiUtil.CreateTextUi(parScene, parUiContainer, UiUtil.GetDoomFont(), "Играть"); var (leadersUiObject, leadersUi, _) = - UiUtil.CreateTextUi(parScene, parUiContainer, UiUtil.GetDoomFont(parEngine), "Лидеры"); + UiUtil.CreateTextUi(parScene, parUiContainer, UiUtil.GetDoomFont(), "Лидеры"); var (rulesUiObject, rulesUi, _) = - UiUtil.CreateTextUi(parScene, parUiContainer, UiUtil.GetDoomFont(parEngine), "Правила"); + UiUtil.CreateTextUi(parScene, parUiContainer, UiUtil.GetDoomFont(), "Правила"); var (exitUiObject, exitUi, _) = - UiUtil.CreateTextUi(parScene, parUiContainer, UiUtil.GetDoomFont(parEngine), "Выход"); + UiUtil.CreateTextUi(parScene, parUiContainer, UiUtil.GetDoomFont(), "Выход"); var (stackObject, stack) = UiUtil.CreateStackUi(parScene, new StackComponent @@ -67,10 +75,10 @@ public static class MainScene }); stackObject.Transform.Size.Xy = new Vector2(1f, 6f); - playUi.OnClick += _ => parEngine.SceneManager.TransitionTo(() => PlayScene.Create(parEngine)); + playUi.OnClick += _ => EngineUtil.SceneManager.TransitionTo(PlayScene.Create); leadersUi.OnClick += _ => parMenuController.SelectMenuItem("leaders"); rulesUi.OnClick += _ => parMenuController.SelectMenuItem("rules"); - exitUi.OnClick += _ => parEngine.Close(); + exitUi.OnClick += _ => EngineUtil.Close(); var (selectorObject, selector) = UiUtil.CreateSelectorUi(parScene, new SelectorComponent { Children = { playUi, leadersUi, rulesUi, exitUi } }); @@ -86,19 +94,28 @@ public static class MainScene return parentObject; } - private static GameObject CreateLeadersMenu(Engine.Engine parEngine, Engine.Scene.Scene parScene, + private static GameObject CreateLeadersMenu(Engine.Scene.Scene parScene, UiContainerComponent parUiContainer, MenuControllerComponent parMenuController) { var parentObject = GameObjectUtil.CreateGameObject(parScene); var (backUiObject, backUi, _) = UiUtil.CreateTextUi(parScene, parUiContainer, - UiUtil.GetDoomFont(parEngine), "Назад"); + UiUtil.GetDoomFont(), "Назад"); backUi.OnClick += _ => parMenuController.SelectMenuItem("main"); var (stackObject, stack) = UiUtil.CreateStackUi(parScene, - new StackComponent { Offset = new Vector2(0, -1f), Container = parUiContainer, Children = { backUi } }); + new StackComponent { Offset = new Vector2(0, -1.5f), Container = parUiContainer, Children = { } }); stackObject.Transform.Size.Xy = new Vector2(1f, 6f); + var leadersTable = CreateLeadersTable(parScene, parUiContainer, EngineUtil.DataFolder); + foreach (var (rowObject, rowUi) in leadersTable) + { + stack.Children.Add(rowUi); + parScene.AddChild(stackObject, rowObject); + } + + stack.Children.Add(backUi); + var (selectorObject, selector) = UiUtil.CreateSelectorUi(parScene, new SelectorComponent { Children = { backUi } }); parScene.AddChild(parentObject, selectorObject); @@ -108,17 +125,62 @@ public static class MainScene return parentObject; } - private static GameObject CreateRulesMenu(Engine.Engine parEngine, Engine.Scene.Scene parScene, + private static List<(GameObject, UiComponent)> CreateLeadersTable(Engine.Scene.Scene parScene, + UiContainerComponent parUiContainer, string parDataFolder) + { + var leadersTableList = + new List<(GameObject, UiComponent)> { CreateLeadersRow(parScene, parUiContainer, "Имя", "Очки") }; + + var leadersTable = ScoreTable.LoadOrCreate(Path.Combine(parDataFolder, "leaders.json")); + + var rows = 0; + foreach (var row in leadersTable.Rows) + { + leadersTableList.Add(CreateLeadersRow(parScene, parUiContainer, row.Name, row.Score.ToString())); + if (++rows >= 5) + break; + } + + return leadersTableList; + } + + private static (GameObject, UiComponent) CreateLeadersRow(Engine.Scene.Scene parScene, + UiContainerComponent parUiContainer, string parName, string parScore) + { + var uiComponent = new UiContainerComponent { Container = parUiContainer }; + + var rowObject = GameObjectUtil.CreateGameObject(parScene, + new Transform { Size = new Vector3(5, 8, 1) }, [ + uiComponent + ]); + + var (nameUiObject, nameUi, _) = + UiUtil.CreateTextUi(parScene, uiComponent, UiUtil.GetDoomFont(), parName); + nameUi.Anchor = Anchor.CenterLeft; + nameUi.Center = Anchor.Center; + + var (scoreUiObject, scoreUi, _) = + UiUtil.CreateTextUi(parScene, uiComponent, UiUtil.GetDoomFont(), parScore); + scoreUi.Anchor = Anchor.CenterRight; + scoreUi.Center = Anchor.Center; + + parScene.AddChild(rowObject, nameUiObject); + parScene.AddChild(rowObject, scoreUiObject); + + return (rowObject, uiComponent); + } + + private static GameObject CreateRulesMenu(Engine.Scene.Scene parScene, UiContainerComponent parUiContainer, MenuControllerComponent parMenuController) { var parentObject = GameObjectUtil.CreateGameObject(parScene); var (backUiObject, backUi, _) = UiUtil.CreateTextUi(parScene, parUiContainer, - UiUtil.GetDoomFont(parEngine), "Назад"); + UiUtil.GetDoomFont(), "Назад"); backUi.OnClick += _ => parMenuController.SelectMenuItem("main"); var (rulesObject, rulesUi, _) = UiUtil.CreateTextUi(parScene, parUiContainer, - UiUtil.GetDoomFont(parEngine), "Правила"); + UiUtil.GetDoomFont(), "Правила"); var (stackObject, stack) = UiUtil.CreateStackUi(parScene, new StackComponent { Offset = new Vector2(0, -1f), Container = parUiContainer, Children = { rulesUi, backUi } }); diff --git a/DoomDeathmatch/src/Scene/Play/PlayScene.cs b/DoomDeathmatch/src/Scene/Play/PlayScene.cs index 2d018d6..508d3a0 100644 --- a/DoomDeathmatch/src/Scene/Play/PlayScene.cs +++ b/DoomDeathmatch/src/Scene/Play/PlayScene.cs @@ -1,21 +1,35 @@ -using DoomDeathmatch.Component.MVC.Controller; -using DoomDeathmatch.Component.MVC.Model.Enemy; -using DoomDeathmatch.Component.MVC.View; +using DoomDeathmatch.Component; +using DoomDeathmatch.Component.MVC; +using DoomDeathmatch.Component.MVC.Health; +using DoomDeathmatch.Component.MVC.Score; +using DoomDeathmatch.Component.MVC.Timer; +using DoomDeathmatch.Component.MVC.Weapon; +using DoomDeathmatch.Component.Physics.Collision; using DoomDeathmatch.Component.UI; using DoomDeathmatch.Component.Util; -using DoomDeathmatch.Component.Util.Collision; +using DoomDeathmatch.Scene.Main; +using DoomDeathmatch.Scene.Play.Prefab; +using DoomDeathmatch.Script; +using DoomDeathmatch.Script.Collision; +using DoomDeathmatch.Script.Condition; +using DoomDeathmatch.Script.Consumable; +using DoomDeathmatch.Script.Model.Enemy; +using DoomDeathmatch.Script.Model.Weapon; +using DoomDeathmatch.Script.Provider; +using DoomDeathmatch.Script.UI; using Engine.Asset.Mesh; using Engine.Graphics.Pipeline; using Engine.Graphics.Texture; using Engine.Scene; using Engine.Scene.Component.BuiltIn.Renderer; +using Engine.Util; using OpenTK.Mathematics; namespace DoomDeathmatch.Scene.Play; public static class PlayScene { - public static Engine.Scene.Scene Create(Engine.Engine parEngine) + public static Engine.Scene.Scene Create() { var scene = new Engine.Scene.Scene(); @@ -29,41 +43,17 @@ public static class PlayScene ]); var (playUiObject, (playWeaponView, playHealthView, playScoreView, plaTimerView)) = - CreateGameUi(parEngine, scene, uiContainer, menuController); + CreateGameUi(scene, uiContainer, menuController); menuController.AddMenuItem("play", playUiObject); scene.AddChild(uiContainerObject, playUiObject); - var escapeUiObject = CreateEscapeMenu(parEngine, scene, uiContainer, menuController); + var escapeUiObject = CreateEscapeMenu(scene, uiContainer, menuController); escapeUiObject.IsEnabled = false; menuController.AddMenuItem("escape", escapeUiObject); scene.AddChild(uiContainerObject, escapeUiObject); - var (perspectiveCameraObject, perspectiveCamera) = UiUtil.CreatePerspectiveCamera(scene); - perspectiveCameraObject.Transform.Translation.Z = 2; - var playerObject = GameObjectUtil.CreateGameObject(scene, [ - new ControllerComponent(), + PlayerPrefab.Create(scene, playWeaponView, playHealthView); - new MovementController { Speed = 10f }, - new RigidbodyComponent(), - new DragComponent { Drag = 10f, Coefficient = new Vector3(1, 1, 0) }, - - new AABBColliderComponent - { - Collider = new AABBCollider { Size = new Vector3(1, 1, 2) }, - Offset = new Vector3(0, 0f, 1f), - ColliderGroups = { "player" }, - ExcludeColliderCollideGroups = { "player" } - }, - - new PlayerController(perspectiveCamera), - - new WeaponController(), - playWeaponView, - - new HealthController(), - playHealthView, - ]); - playerObject.Transform.Translation.X = -3; var gameControllerObject = GameObjectUtil.CreateGameObject(scene, [ new GameController(menuController), @@ -73,38 +63,100 @@ public static class PlayScene new ScoreController(), playScoreView, - new CollisionManager(), + new CollisionManagerComponent(), ]); - scene.AddChild(playerObject, perspectiveCameraObject); - var mapObject = LoadMap(parEngine, scene, "default"); - var impObject = EnemyObject.CreateEnemy(parEngine, scene, EnemyData.Imp); + var mapObject = LoadMap(scene, gameControllerObject, "default"); return scene; } - private static GameObject LoadMap(Engine.Engine parEngine, Engine.Scene.Scene parScene, string parMapName) + private static GameObject LoadMap(Engine.Scene.Scene parScene, GameObject parGameControllerObject, string parMapName) { var mapObject = GameObjectUtil.CreateGameObject(parScene, [ new MeshRenderer { - Mesh = parEngine.AssetResourceManager.Load($"map/{parMapName}/mesh.obj"), - Albedo = parEngine.AssetResourceManager.Load($"map/{parMapName}/texture.png") + Mesh = EngineUtil.AssetResourceManager.Load($"map/{parMapName}/mesh.obj"), + Albedo = EngineUtil.AssetResourceManager.Load($"map/{parMapName}/texture.png") } ]); - var colliders = parEngine.AssetResourceManager.Load($"map/{parMapName}/colliders.obj"); + var colliders = EngineUtil.AssetResourceManager.Load($"map/{parMapName}/colliders.obj"); var collidersObject = MeshToColliders(parScene, colliders); var monsterSpawnPoints = MeshToSpawnPoints( - parEngine.AssetResourceManager.Load($"map/{parMapName}/monster_spawners.obj")); + EngineUtil.AssetResourceManager.Load($"map/{parMapName}/monster_spawners.obj")); var valuableSpawnPoints = MeshToSpawnPoints( - parEngine.AssetResourceManager.Load($"map/{parMapName}/valuable_spawners.obj")); + EngineUtil.AssetResourceManager.Load($"map/{parMapName}/valuable_spawners.obj")); parScene.AddChild(mapObject, collidersObject); + var monsterVector3ValueProvider = + new RandomListValueProvider(monsterSpawnPoints.Select(ConstValueProvider.Create)); + + var monsterSpawnerObject = GameObjectUtil.CreateGameObject(parScene, [ + new ObjectSpawnerComponent( + new ObjectSpawner(new WeightedRandomValueProvider( + new List<(int, IValueProvider)> + { + (6, GeneratorValueProvider.Create(() => + EnemyPrefab.Create(EngineUtil.SceneManager.CurrentScene!, EnemyData.Demon))), + (4, + GeneratorValueProvider.Create(() => + EnemyPrefab.Create(EngineUtil.SceneManager.CurrentScene!, EnemyData.Imp))) + }), + monsterVector3ValueProvider, + new TickableTimerCondition(2f))) + ] + ); + + parScene.AddChild(parGameControllerObject, monsterSpawnerObject); + + var consumableVector3ValueProvider = + new RandomListValueProvider(valuableSpawnPoints.Select(ConstValueProvider.Create)); + + var consumableSpawnerObject = GameObjectUtil.CreateGameObject(parScene, [ + new ObjectSpawnerComponent( + new ObjectSpawner(new WeightedRandomValueProvider( + new List<(int, IValueProvider)> + { + (2, GeneratorValueProvider.Create(() => + { + var gameController = EngineUtil.SceneManager.CurrentScene!.FindFirstComponent()!; + + return ConsumablePrefab.Create(EngineUtil.SceneManager.CurrentScene, + new HealthPackConsumable(75), + gameController.PlayerController.Camera.GameObject.Transform + ); + })), + (4, GeneratorValueProvider.Create(() => + { + var gameController = EngineUtil.SceneManager.CurrentScene!.FindFirstComponent()!; + + return ConsumablePrefab.Create(EngineUtil.SceneManager.CurrentScene, + new WeaponConsumable(WeaponData.Pistol), + gameController.PlayerController.Camera.GameObject.Transform + ); + })), + (3, GeneratorValueProvider.Create(() => + { + var gameController = EngineUtil.SceneManager.CurrentScene!.FindFirstComponent()!; + + return ConsumablePrefab.Create(EngineUtil.SceneManager.CurrentScene, + new WeaponConsumable(WeaponData.Shotgun), + gameController.PlayerController.Camera.GameObject.Transform + ); + })) + }), + consumableVector3ValueProvider, + new TickableTimerCondition(5f))) + ] + ); + + parScene.AddChild(parGameControllerObject, consumableSpawnerObject); + return mapObject; } @@ -150,7 +202,7 @@ public static class PlayScene return allCollidersObject; } - private static (GameObject, (WeaponView, HealthView, ScoreView, TimerView)) CreateGameUi(Engine.Engine parEngine, + private static (GameObject, (WeaponView, HealthView, ScoreView, TimerView)) CreateGameUi( Engine.Scene.Scene parScene, UiContainerComponent parUiContainer, MenuControllerComponent parMenuController) { @@ -166,7 +218,7 @@ public static class PlayScene }); bottomContainerObject.AddComponent(new CopySizeComponent { - Target = parUiContainer.GameObject.Transform, Coefficient = new Vector3(1, 0, 1) + Target = parUiContainer.GameObject.Transform, Multiplier = new Vector3(1, 0, 1) }); bottomContainerObject.Transform.Size.Y = 1.5f; parScene.AddChild(parentObject, bottomContainerObject); @@ -180,8 +232,8 @@ public static class PlayScene parScene.AddChild(bottomContainerObject, gunObject); var (healthObject, healthUi, (_, healthTextRenderer)) = - UiUtil.CreateTextUi(parScene, bottomContainer, UiUtil.GetDoomFont(parEngine), "Здоровье: 000", - TextAlignComponent.Align.Center, + UiUtil.CreateTextUi(parScene, bottomContainer, UiUtil.GetDoomFont(), "Здоровье: 000", + Align.Center, RenderLayer.HUD); healthObject.Transform.Scale = new Vector3(0.75f); healthUi.Anchor = Anchor.CenterLeft; @@ -189,8 +241,8 @@ public static class PlayScene parScene.AddChild(bottomContainerObject, healthObject); var (ammoObject, ammoUi, (_, ammoTextRenderer)) = - UiUtil.CreateTextUi(parScene, bottomContainer, UiUtil.GetDoomFont(parEngine), "Патроны: 00/00", - TextAlignComponent.Align.Center, + UiUtil.CreateTextUi(parScene, bottomContainer, UiUtil.GetDoomFont(), "Патроны: 00/00", + Align.Center, RenderLayer.HUD); ammoObject.Transform.Scale = new Vector3(0.75f); ammoUi.Anchor = Anchor.TopRight; @@ -198,8 +250,8 @@ public static class PlayScene parScene.AddChild(bottomContainerObject, ammoObject); var (weaponObject, weaponUi, (_, weaponTextRenderer)) = - UiUtil.CreateTextUi(parScene, bottomContainer, UiUtil.GetDoomFont(parEngine), "Оружие: ОРУЖИЕОР", - TextAlignComponent.Align.Center, + UiUtil.CreateTextUi(parScene, bottomContainer, UiUtil.GetDoomFont(), "Оружие: ОРУЖИЕОР", + Align.Center, RenderLayer.HUD); weaponObject.Transform.Scale = new Vector3(0.75f); weaponUi.Anchor = Anchor.BottomRight; @@ -214,20 +266,20 @@ public static class PlayScene new Box2DRenderer { Color = new Vector4(1, 0, 0, 1), RenderLayer = RenderLayer.HUD }); topContainerObject.AddComponent(new CopySizeComponent { - Target = parUiContainer.GameObject.Transform, Coefficient = new Vector3(1, 0, 1) + Target = parUiContainer.GameObject.Transform, Multiplier = new Vector3(1, 0, 1) }); topContainerObject.Transform.Size.Y = 1f; parScene.AddChild(parentObject, topContainerObject); var (timerObject, timerUi, (_, timerTextRenderer)) = - UiUtil.CreateTextUi(parScene, topContainer, UiUtil.GetDoomFont(parEngine), "Время: 00:00", + UiUtil.CreateTextUi(parScene, topContainer, UiUtil.GetDoomFont(), "Время: 00:00", parRenderLayer: RenderLayer.HUD); timerUi.Anchor = Anchor.CenterLeft; timerUi.Center = Anchor.CenterLeft; parScene.AddChild(topContainerObject, timerObject); var (scoreObject, scoreUi, (_, scoreTextRenderer)) = - UiUtil.CreateTextUi(parScene, topContainer, UiUtil.GetDoomFont(parEngine), "Счет: 00000", + UiUtil.CreateTextUi(parScene, topContainer, UiUtil.GetDoomFont(), "Счет: 00000", parRenderLayer: RenderLayer.HUD); scoreUi.Anchor = Anchor.CenterRight; scoreUi.Center = Anchor.CenterRight; @@ -241,7 +293,7 @@ public static class PlayScene return (parentObject, (weaponView, healthView, scoreView, timerView)); } - private static GameObject CreateEscapeMenu(Engine.Engine parEngine, Engine.Scene.Scene parScene, + private static GameObject CreateEscapeMenu(Engine.Scene.Scene parScene, UiContainerComponent parUiContainer, MenuControllerComponent parMenuController) { var parentObject = GameObjectUtil.CreateGameObject(parScene); @@ -251,20 +303,20 @@ public static class PlayScene backgroundObject.AddComponent(new CopySizeComponent { Target = parUiContainer.GameObject.Transform }); parScene.AddChild(parentObject, backgroundObject); - var (logoObject, logoUi) = UiUtil.CreateLogoUi(parEngine, parScene, backgroundUiContainer, RenderLayer.HUD); + var (logoObject, logoUi) = UiUtil.CreateLogoUi(parScene, backgroundUiContainer, RenderLayer.HUD); logoUi.Offset = new Vector2(0, 3f); parScene.AddChild(parentObject, logoObject); var (backUiObject, backUi, _) = UiUtil.CreateTextUi(parScene, backgroundUiContainer, - UiUtil.GetDoomFont(parEngine), "Назад", + UiUtil.GetDoomFont(), "Назад", parRenderLayer: RenderLayer.HUD); - backUi.OnClick += parUiComponent => - parUiComponent.GameObject.Scene!.FindFirstComponent()!.Unpause(); + backUi.OnClick += _ => + EngineUtil.SceneManager.CurrentScene!.FindFirstComponent()!.Unpause(); var (exitUiObject, exitUi, _) = UiUtil.CreateTextUi(parScene, backgroundUiContainer, - UiUtil.GetDoomFont(parEngine), "Выход", + UiUtil.GetDoomFont(), "Выход", parRenderLayer: RenderLayer.HUD); - exitUi.OnClick += _ => parEngine.SceneManager.TransitionTo(() => MainScene.Create(parEngine)); + exitUi.OnClick += _ => EngineUtil.SceneManager.TransitionTo(MainScene.Create); var (stackObject, stack) = UiUtil.CreateStackUi(parScene, new StackComponent diff --git a/DoomDeathmatch/src/Scene/Play/Prefab/ConsumablePrefab.cs b/DoomDeathmatch/src/Scene/Play/Prefab/ConsumablePrefab.cs new file mode 100644 index 0000000..dbdb086 --- /dev/null +++ b/DoomDeathmatch/src/Scene/Play/Prefab/ConsumablePrefab.cs @@ -0,0 +1,51 @@ +using DoomDeathmatch.Component; +using DoomDeathmatch.Component.MVC; +using DoomDeathmatch.Component.Physics; +using DoomDeathmatch.Component.Physics.Collision; +using DoomDeathmatch.Component.Util; +using DoomDeathmatch.Script.Collision; +using DoomDeathmatch.Script.Consumable; +using Engine.Scene; +using Engine.Scene.Component.BuiltIn; +using Engine.Scene.Component.BuiltIn.Renderer; +using OpenTK.Mathematics; + +namespace DoomDeathmatch.Scene.Play.Prefab; + +public static class ConsumablePrefab +{ + public static GameObject Create(Engine.Scene.Scene parScene, + IConsumable parConsumable, Transform? parBillboardTarget = null) + { + var box2DRenderer = new Box2DRenderer(); + var collider = new AABBColliderComponent + { + Collider = new AABBCollider { Size = new Vector3(1f) }, + Offset = new Vector3(0, 0f, 0.5f), + ColliderGroups = { "consumable" }, + ExcludeColliderCollideGroups = { "enemy" } + }; + + var consumableObject = GameObjectUtil.CreateGameObject(parScene, + [ + new ConsumableComponent(parConsumable, collider, box2DRenderer), + new RigidbodyComponent(), + + new DragComponent { Drag = 10f, Multiplier = new Vector3(1, 1, 0) }, + new MovementController { Speed = 10f }, + collider + ] + ); + + var innerObject = GameObjectUtil.CreateGameObject(parScene, + new Transform { Translation = new Vector3(0, 0f, 0.5f), Size = new Vector3(1f) }, [ + box2DRenderer, + new BillboardComponent { Target = parBillboardTarget } + ] + ); + + parScene.AddChild(consumableObject, innerObject); + + return consumableObject; + } +} \ No newline at end of file diff --git a/DoomDeathmatch/src/Scene/Play/EnemyObject.cs b/DoomDeathmatch/src/Scene/Play/Prefab/EnemyPrefab.cs similarity index 50% rename from DoomDeathmatch/src/Scene/Play/EnemyObject.cs rename to DoomDeathmatch/src/Scene/Play/Prefab/EnemyPrefab.cs index 3951b6a..5180163 100644 --- a/DoomDeathmatch/src/Scene/Play/EnemyObject.cs +++ b/DoomDeathmatch/src/Scene/Play/Prefab/EnemyPrefab.cs @@ -1,68 +1,74 @@ -using DoomDeathmatch.Component.MVC.Controller; -using DoomDeathmatch.Component.MVC.Model.Enemy; -using DoomDeathmatch.Component.MVC.View; +using DoomDeathmatch.Component.MVC; +using DoomDeathmatch.Component.MVC.Enemy; +using DoomDeathmatch.Component.MVC.Health; +using DoomDeathmatch.Component.Physics; +using DoomDeathmatch.Component.Physics.Collision; using DoomDeathmatch.Component.UI; using DoomDeathmatch.Component.Util; -using DoomDeathmatch.Component.Util.Collision; +using DoomDeathmatch.Script.Collision; +using DoomDeathmatch.Script.Model.Enemy; +using DoomDeathmatch.Script.UI; using Engine.Graphics.Texture; using Engine.Scene; using Engine.Scene.Component.BuiltIn; using Engine.Scene.Component.BuiltIn.Renderer; +using Engine.Util; using OpenTK.Mathematics; -namespace DoomDeathmatch.Scene.Play; +namespace DoomDeathmatch.Scene.Play.Prefab; -public static class EnemyObject +public static class EnemyPrefab { - public static GameObject CreateEnemy(Engine.Engine parEngine, Engine.Scene.Scene parScene, EnemyData parEnemyData) + public static GameObject Create(Engine.Scene.Scene parScene, EnemyData parEnemyData) { - var enemyHealthTextRenderer = new TextRenderer { Font = UiUtil.GetDoomFont(parEngine), Text = "Здоровье: 000" }; - var displayBox2DRenderer = new Box2DRenderer(); + var enemyHealthTextRenderer = new TextRenderer { Font = UiUtil.GetDoomFont(), Text = "Здоровье: 000" }; + var enemyBox2DRenderer = new Box2DRenderer(); - var demonObject = GameObjectUtil.CreateGameObject(parScene, + var enemyObject = GameObjectUtil.CreateGameObject(parScene, [ new EnemyController(parEnemyData), - new EnemyView(displayBox2DRenderer), + new EnemyView(enemyBox2DRenderer), new HealthController(parEnemyData.BaseHealth), new EnemyHealthView(enemyHealthTextRenderer), new MovementController(), new RigidbodyComponent(), - new DragComponent { Drag = 5f, Coefficient = new Vector3(1, 1, 0) }, + new DragComponent { Drag = 5f, Multiplier = new Vector3(1, 1, 0) }, new AABBColliderComponent { Collider = new AABBCollider { Size = new Vector3(1, 1, 2) }, Offset = new Vector3(0, 0f, 1f), ColliderGroups = { "enemy" }, - ExcludeColliderCollideGroups = { "enemy" } + ExcludeColliderCollideGroups = { "fireball" } }, + new ColliderForceFieldComponent() ]); - var demonHealthContainer = GameObjectUtil.CreateGameObject(parScene, + var enemyHealthContainer = GameObjectUtil.CreateGameObject(parScene, new Transform { Translation = new Vector3(0, 1.25f, 0f), Scale = new Vector3(0.2f) } ); - var demonHealthObject = GameObjectUtil.CreateGameObject(parScene, [ + var enemyHealthObject = GameObjectUtil.CreateGameObject(parScene, [ enemyHealthTextRenderer, - new TextAlignComponent { Alignment = TextAlignComponent.Align.Center } + new TextAlignComponent { Alignment = Align.Center } ] ); - var demonVisualObject = GameObjectUtil.CreateGameObject(parScene, + var enemyVisualObject = GameObjectUtil.CreateGameObject(parScene, new Transform { Translation = new Vector3(0, 0f, 1f), Size = new Vector3(1, 2, 1) }, [ - displayBox2DRenderer, + enemyBox2DRenderer, new BillboardComponent(), ] ); - parScene.AddChild(demonObject, demonVisualObject); - parScene.AddChild(demonVisualObject, demonHealthContainer); - parScene.AddChild(demonHealthContainer, demonHealthObject); + parScene.AddChild(enemyObject, enemyVisualObject); + parScene.AddChild(enemyVisualObject, enemyHealthContainer); + parScene.AddChild(enemyHealthContainer, enemyHealthObject); - return demonObject; + return enemyObject; } - public static GameObject CreateImpFireball(Engine.Scene.Scene parScene, Vector3 parPosition, + public static GameObject CreateFireball(Engine.Scene.Scene parScene, Vector3 parPosition, Vector3 parVelocity, float parDamage, Transform? parBillboardTarget = null) { var rigidbodyComponent = new RigidbodyComponent(); @@ -72,15 +78,14 @@ public static class EnemyObject new Transform { Translation = parPosition, Size = new Vector3(0.5f) }, [ rigidbodyComponent, - new Box2DRenderer - { - Texture = Engine.Engine.Instance.AssetResourceManager.Load("texture/fireball.png") - }, + new Box2DRenderer { Texture = EngineUtil.AssetResourceManager.Load("texture/fireball.png") }, new BillboardComponent { Target = parBillboardTarget }, new FireballComponent { Damage = parDamage }, new AABBColliderComponent { - Collider = new AABBCollider { Size = new Vector3(0.25f) }, ExcludeColliderCollideGroups = { "enemy" } + Collider = new AABBCollider { Size = new Vector3(0.25f) }, + ColliderGroups = { "fireball" }, + ExcludeColliderCollideGroups = { "enemy", "fireball", "consumable" } } ] ); diff --git a/DoomDeathmatch/src/Scene/Play/Prefab/PlayerPrefab.cs b/DoomDeathmatch/src/Scene/Play/Prefab/PlayerPrefab.cs new file mode 100644 index 0000000..e54b849 --- /dev/null +++ b/DoomDeathmatch/src/Scene/Play/Prefab/PlayerPrefab.cs @@ -0,0 +1,47 @@ +using DoomDeathmatch.Component.MVC; +using DoomDeathmatch.Component.MVC.Health; +using DoomDeathmatch.Component.MVC.Weapon; +using DoomDeathmatch.Component.Physics; +using DoomDeathmatch.Component.Physics.Collision; +using DoomDeathmatch.Component.Util; +using DoomDeathmatch.Script.Collision; +using Engine.Scene; +using OpenTK.Mathematics; + +namespace DoomDeathmatch.Scene.Play.Prefab; + +public static class PlayerPrefab +{ + public static GameObject Create(Engine.Scene.Scene parScene, WeaponView parWeaponView, HealthView parHealthView) + { + var (perspectiveCameraObject, perspectiveCamera) = UiUtil.CreatePerspectiveCamera(parScene); + perspectiveCameraObject.Transform.Translation.Z = 2; + var playerObject = GameObjectUtil.CreateGameObject(parScene, [ + new PlayerMovementComponent(), + + new MovementController { Speed = 10f }, + new RigidbodyComponent(), + new DragComponent { Drag = 10f, Multiplier = new Vector3(1, 1, 0) }, + + new AABBColliderComponent + { + Collider = new AABBCollider { Size = new Vector3(1, 1, 2) }, + Offset = new Vector3(0, 0f, 1f), + ColliderGroups = { "player" }, + ExcludeColliderCollideGroups = { "player" } + }, + + new PlayerController(perspectiveCamera), + + new WeaponController(), + parWeaponView, + + new HealthController(), + parHealthView, + ]); + + parScene.AddChild(playerObject, perspectiveCameraObject); + + return playerObject; + } +} \ No newline at end of file diff --git a/DoomDeathmatch/src/Script/AnimationPlayer.cs b/DoomDeathmatch/src/Script/AnimationPlayer.cs new file mode 100644 index 0000000..57896bf --- /dev/null +++ b/DoomDeathmatch/src/Script/AnimationPlayer.cs @@ -0,0 +1,50 @@ +using Engine.Scene; +using Engine.Util; + +namespace DoomDeathmatch.Script; + +public class AnimationPlayer(float parInterval) : IUpdate +{ + public List Frames { get; init; } = []; + public bool IsPlaying { get; private set; } = false; + public int NextFrame { get; private set; } = 0; + + public event Action? OnFrameChanged; + public event Action? OnFinish; + + private readonly TickableTimer _timer = new(parInterval); + + public void Start() + { + Reset(); + IsPlaying = true; + } + + public void Update(double parDeltaTime) + { + if (!IsPlaying) + return; + + _timer.Update(parDeltaTime); + + if (_timer.CurrentTime < _timer.TotalTime * (1.0f - 1.0f / Frames.Count * NextFrame)) + { + OnFrameChanged?.Invoke(Frames[NextFrame]); + NextFrame++; + } + + if (_timer.IsFinished) + { + OnFinish?.Invoke(); + + Reset(); + } + } + + public void Reset() + { + _timer.Reset(); + NextFrame = 0; + IsPlaying = false; + } +} \ No newline at end of file diff --git a/DoomDeathmatch/src/Component/Util/Collision/AABBCollider.cs b/DoomDeathmatch/src/Script/Collision/AABBCollider.cs similarity index 92% rename from DoomDeathmatch/src/Component/Util/Collision/AABBCollider.cs rename to DoomDeathmatch/src/Script/Collision/AABBCollider.cs index 517239b..982fbe6 100644 --- a/DoomDeathmatch/src/Component/Util/Collision/AABBCollider.cs +++ b/DoomDeathmatch/src/Script/Collision/AABBCollider.cs @@ -1,6 +1,6 @@ using OpenTK.Mathematics; -namespace DoomDeathmatch.Component.Util.Collision; +namespace DoomDeathmatch.Script.Collision; public class AABBCollider { @@ -42,12 +42,12 @@ public class AABBCollider var sign = Math.Sign(diff.Y); normal.Y = sign == 0 ? 1 : sign; } - else + else if (penZ < penX && penZ < penY) { var sign = Math.Sign(diff.Z); normal.Z = sign == 0 ? 1 : sign; } - return normal.Normalized(); + return normal; } } \ No newline at end of file diff --git a/DoomDeathmatch/src/Component/Util/Collision/RaycastResult.cs b/DoomDeathmatch/src/Script/Collision/RaycastResult.cs similarity index 82% rename from DoomDeathmatch/src/Component/Util/Collision/RaycastResult.cs rename to DoomDeathmatch/src/Script/Collision/RaycastResult.cs index 6692bca..96bddc5 100644 --- a/DoomDeathmatch/src/Component/Util/Collision/RaycastResult.cs +++ b/DoomDeathmatch/src/Script/Collision/RaycastResult.cs @@ -1,7 +1,7 @@ using Engine.Scene; using OpenTK.Mathematics; -namespace DoomDeathmatch.Component.Util.Collision; +namespace DoomDeathmatch.Script.Collision; public class RaycastResult { diff --git a/DoomDeathmatch/src/Script/Condition/ICondition.cs b/DoomDeathmatch/src/Script/Condition/ICondition.cs new file mode 100644 index 0000000..93e0e5d --- /dev/null +++ b/DoomDeathmatch/src/Script/Condition/ICondition.cs @@ -0,0 +1,12 @@ +using Engine.Scene; + +namespace DoomDeathmatch.Script.Condition; + +public interface ICondition : IUpdate +{ + public event Action? OnTrue; + + public bool IsTrue { get; } + + public void Reset(); +} \ No newline at end of file diff --git a/DoomDeathmatch/src/Script/Condition/TickableTimerCondition.cs b/DoomDeathmatch/src/Script/Condition/TickableTimerCondition.cs new file mode 100644 index 0000000..ef085db --- /dev/null +++ b/DoomDeathmatch/src/Script/Condition/TickableTimerCondition.cs @@ -0,0 +1,27 @@ +using Engine.Util; + +namespace DoomDeathmatch.Script.Condition; + +public class TickableTimerCondition : ICondition +{ + public event Action? OnTrue; + public bool IsTrue => _timer.IsFinished; + + private readonly TickableTimer _timer; + + public TickableTimerCondition(float parInterval) + { + _timer = new TickableTimer(parInterval); + _timer.OnFinished += () => OnTrue?.Invoke(); + } + + public void Update(double parDeltaTime) + { + _timer.Update(parDeltaTime); + } + + public void Reset() + { + _timer.Reset(); + } +} \ No newline at end of file diff --git a/DoomDeathmatch/src/Script/Consumable/HealthPackConsumable.cs b/DoomDeathmatch/src/Script/Consumable/HealthPackConsumable.cs new file mode 100644 index 0000000..f3c6b79 --- /dev/null +++ b/DoomDeathmatch/src/Script/Consumable/HealthPackConsumable.cs @@ -0,0 +1,13 @@ +using DoomDeathmatch.Component.MVC; + +namespace DoomDeathmatch.Script.Consumable; + +public class HealthPackConsumable(float parHealth) : IConsumable +{ + public string Icon => "texture/health_pack.png"; + + public void Consume(PlayerController parPlayerController) + { + parPlayerController.HealthController.Heal(parHealth); + } +} \ No newline at end of file diff --git a/DoomDeathmatch/src/Script/Consumable/IConsumable.cs b/DoomDeathmatch/src/Script/Consumable/IConsumable.cs new file mode 100644 index 0000000..442e11e --- /dev/null +++ b/DoomDeathmatch/src/Script/Consumable/IConsumable.cs @@ -0,0 +1,10 @@ +using DoomDeathmatch.Component.MVC; + +namespace DoomDeathmatch.Script.Consumable; + +public interface IConsumable +{ + public string Icon { get; } + + public void Consume(PlayerController parPlayerController); +} \ No newline at end of file diff --git a/DoomDeathmatch/src/Script/Consumable/WeaponConsumable.cs b/DoomDeathmatch/src/Script/Consumable/WeaponConsumable.cs new file mode 100644 index 0000000..98893f9 --- /dev/null +++ b/DoomDeathmatch/src/Script/Consumable/WeaponConsumable.cs @@ -0,0 +1,14 @@ +using DoomDeathmatch.Component.MVC; +using DoomDeathmatch.Script.Model.Weapon; + +namespace DoomDeathmatch.Script.Consumable; + +public class WeaponConsumable(WeaponData parWeaponData) : IConsumable +{ + public string Icon => parWeaponData.IdleTexture; + + public void Consume(PlayerController parPlayerController) + { + parPlayerController.WeaponController.AddOrMergeWeapon(parWeaponData); + } +} \ No newline at end of file diff --git a/DoomDeathmatch/src/Component/MVC/Model/Enemy/Attack/AttackBehavior.cs b/DoomDeathmatch/src/Script/Model/Enemy/Attack/AttackBehavior.cs similarity index 69% rename from DoomDeathmatch/src/Component/MVC/Model/Enemy/Attack/AttackBehavior.cs rename to DoomDeathmatch/src/Script/Model/Enemy/Attack/AttackBehavior.cs index 02f0de5..8e9d173 100644 --- a/DoomDeathmatch/src/Component/MVC/Model/Enemy/Attack/AttackBehavior.cs +++ b/DoomDeathmatch/src/Script/Model/Enemy/Attack/AttackBehavior.cs @@ -1,6 +1,7 @@ -using DoomDeathmatch.Component.MVC.Controller; +using DoomDeathmatch.Component.MVC.Enemy; +using DoomDeathmatch.Component.MVC.Health; -namespace DoomDeathmatch.Component.MVC.Model.Enemy.Attack; +namespace DoomDeathmatch.Script.Model.Enemy.Attack; public abstract class AttackBehavior(EnemyController parEnemyController, HealthController parHealthController) { diff --git a/DoomDeathmatch/src/Component/MVC/Model/Enemy/Attack/CloseContinuousAttackBehavior.cs b/DoomDeathmatch/src/Script/Model/Enemy/Attack/CloseContinuousAttackBehavior.cs similarity index 81% rename from DoomDeathmatch/src/Component/MVC/Model/Enemy/Attack/CloseContinuousAttackBehavior.cs rename to DoomDeathmatch/src/Script/Model/Enemy/Attack/CloseContinuousAttackBehavior.cs index 8d19fda..932822c 100644 --- a/DoomDeathmatch/src/Component/MVC/Model/Enemy/Attack/CloseContinuousAttackBehavior.cs +++ b/DoomDeathmatch/src/Script/Model/Enemy/Attack/CloseContinuousAttackBehavior.cs @@ -1,6 +1,7 @@ -using DoomDeathmatch.Component.MVC.Controller; +using DoomDeathmatch.Component.MVC.Enemy; +using DoomDeathmatch.Component.MVC.Health; -namespace DoomDeathmatch.Component.MVC.Model.Enemy.Attack; +namespace DoomDeathmatch.Script.Model.Enemy.Attack; public class CloseContinuousAttackBehavior( EnemyController parEnemyController, diff --git a/DoomDeathmatch/src/Component/MVC/Model/Enemy/Attack/CloseCooldownAttackBehavior.cs b/DoomDeathmatch/src/Script/Model/Enemy/Attack/CloseCooldownAttackBehavior.cs similarity index 82% rename from DoomDeathmatch/src/Component/MVC/Model/Enemy/Attack/CloseCooldownAttackBehavior.cs rename to DoomDeathmatch/src/Script/Model/Enemy/Attack/CloseCooldownAttackBehavior.cs index 83d1ef2..cded27a 100644 --- a/DoomDeathmatch/src/Component/MVC/Model/Enemy/Attack/CloseCooldownAttackBehavior.cs +++ b/DoomDeathmatch/src/Script/Model/Enemy/Attack/CloseCooldownAttackBehavior.cs @@ -1,7 +1,7 @@ -using DoomDeathmatch.Component.MVC.Controller; -using Engine.Util; +using DoomDeathmatch.Component.MVC.Enemy; +using DoomDeathmatch.Component.MVC.Health; -namespace DoomDeathmatch.Component.MVC.Model.Enemy.Attack; +namespace DoomDeathmatch.Script.Model.Enemy.Attack; public class CloseCooldownAttackBehavior( EnemyController parEnemyController, diff --git a/DoomDeathmatch/src/Component/MVC/Model/Enemy/Attack/CompositeAttackBehavior.cs b/DoomDeathmatch/src/Script/Model/Enemy/Attack/CompositeAttackBehavior.cs similarity index 75% rename from DoomDeathmatch/src/Component/MVC/Model/Enemy/Attack/CompositeAttackBehavior.cs rename to DoomDeathmatch/src/Script/Model/Enemy/Attack/CompositeAttackBehavior.cs index 36b699d..3a8d6ec 100644 --- a/DoomDeathmatch/src/Component/MVC/Model/Enemy/Attack/CompositeAttackBehavior.cs +++ b/DoomDeathmatch/src/Script/Model/Enemy/Attack/CompositeAttackBehavior.cs @@ -1,6 +1,7 @@ -using DoomDeathmatch.Component.MVC.Controller; +using DoomDeathmatch.Component.MVC.Enemy; +using DoomDeathmatch.Component.MVC.Health; -namespace DoomDeathmatch.Component.MVC.Model.Enemy.Attack; +namespace DoomDeathmatch.Script.Model.Enemy.Attack; public class CompositeAttackBehavior( EnemyController parEnemyController, diff --git a/DoomDeathmatch/src/Component/MVC/Model/Enemy/Attack/CooldownAttackBehavior.cs b/DoomDeathmatch/src/Script/Model/Enemy/Attack/CooldownAttackBehavior.cs similarity index 83% rename from DoomDeathmatch/src/Component/MVC/Model/Enemy/Attack/CooldownAttackBehavior.cs rename to DoomDeathmatch/src/Script/Model/Enemy/Attack/CooldownAttackBehavior.cs index a499931..25afcb9 100644 --- a/DoomDeathmatch/src/Component/MVC/Model/Enemy/Attack/CooldownAttackBehavior.cs +++ b/DoomDeathmatch/src/Script/Model/Enemy/Attack/CooldownAttackBehavior.cs @@ -1,7 +1,8 @@ -using DoomDeathmatch.Component.MVC.Controller; +using DoomDeathmatch.Component.MVC.Enemy; +using DoomDeathmatch.Component.MVC.Health; using Engine.Util; -namespace DoomDeathmatch.Component.MVC.Model.Enemy.Attack; +namespace DoomDeathmatch.Script.Model.Enemy.Attack; public abstract class CooldownAttackBehavior( EnemyController parEnemyController, diff --git a/DoomDeathmatch/src/Component/MVC/Model/Enemy/Attack/FuncAttackBehaviorCreator.cs b/DoomDeathmatch/src/Script/Model/Enemy/Attack/FuncAttackBehaviorCreator.cs similarity index 68% rename from DoomDeathmatch/src/Component/MVC/Model/Enemy/Attack/FuncAttackBehaviorCreator.cs rename to DoomDeathmatch/src/Script/Model/Enemy/Attack/FuncAttackBehaviorCreator.cs index 850535e..26de5b6 100644 --- a/DoomDeathmatch/src/Component/MVC/Model/Enemy/Attack/FuncAttackBehaviorCreator.cs +++ b/DoomDeathmatch/src/Script/Model/Enemy/Attack/FuncAttackBehaviorCreator.cs @@ -1,6 +1,7 @@ -using DoomDeathmatch.Component.MVC.Controller; +using DoomDeathmatch.Component.MVC.Enemy; +using DoomDeathmatch.Component.MVC.Health; -namespace DoomDeathmatch.Component.MVC.Model.Enemy.Attack; +namespace DoomDeathmatch.Script.Model.Enemy.Attack; public class FuncAttackBehaviorCreator(Func parFunc) : IAttackBehaviorCreator diff --git a/DoomDeathmatch/src/Component/MVC/Model/Enemy/Attack/IAttackBehaviorCreator.cs b/DoomDeathmatch/src/Script/Model/Enemy/Attack/IAttackBehaviorCreator.cs similarity index 51% rename from DoomDeathmatch/src/Component/MVC/Model/Enemy/Attack/IAttackBehaviorCreator.cs rename to DoomDeathmatch/src/Script/Model/Enemy/Attack/IAttackBehaviorCreator.cs index daae2a2..81e96f2 100644 --- a/DoomDeathmatch/src/Component/MVC/Model/Enemy/Attack/IAttackBehaviorCreator.cs +++ b/DoomDeathmatch/src/Script/Model/Enemy/Attack/IAttackBehaviorCreator.cs @@ -1,6 +1,7 @@ -using DoomDeathmatch.Component.MVC.Controller; +using DoomDeathmatch.Component.MVC.Enemy; +using DoomDeathmatch.Component.MVC.Health; -namespace DoomDeathmatch.Component.MVC.Model.Enemy.Attack; +namespace DoomDeathmatch.Script.Model.Enemy.Attack; public interface IAttackBehaviorCreator { diff --git a/DoomDeathmatch/src/Component/MVC/Model/Enemy/Attack/ObjectSpawnAttackBehavior.cs b/DoomDeathmatch/src/Script/Model/Enemy/Attack/ObjectSpawnAttackBehavior.cs similarity index 70% rename from DoomDeathmatch/src/Component/MVC/Model/Enemy/Attack/ObjectSpawnAttackBehavior.cs rename to DoomDeathmatch/src/Script/Model/Enemy/Attack/ObjectSpawnAttackBehavior.cs index 50e57b7..cc93840 100644 --- a/DoomDeathmatch/src/Component/MVC/Model/Enemy/Attack/ObjectSpawnAttackBehavior.cs +++ b/DoomDeathmatch/src/Script/Model/Enemy/Attack/ObjectSpawnAttackBehavior.cs @@ -1,7 +1,9 @@ -using DoomDeathmatch.Component.MVC.Controller; +using DoomDeathmatch.Component.MVC.Enemy; +using DoomDeathmatch.Component.MVC.Health; using Engine.Scene; +using Engine.Util; -namespace DoomDeathmatch.Component.MVC.Model.Enemy.Attack; +namespace DoomDeathmatch.Script.Model.Enemy.Attack; public class ObjectSpawnAttackBehavior( EnemyController parEnemyController, @@ -19,7 +21,7 @@ public class ObjectSpawnAttackBehavior( protected override bool ActivateAttack() { var enemyObject = parObjectSpawnFunc(_enemyController, _healthController); - _enemyController.GameObject.Scene!.Add(enemyObject); + EngineUtil.SceneManager.CurrentScene!.Add(enemyObject); return true; } diff --git a/DoomDeathmatch/src/Component/MVC/Model/Enemy/EnemyData.cs b/DoomDeathmatch/src/Script/Model/Enemy/EnemyData.cs similarity index 88% rename from DoomDeathmatch/src/Component/MVC/Model/Enemy/EnemyData.cs rename to DoomDeathmatch/src/Script/Model/Enemy/EnemyData.cs index a5d5450..94728a5 100644 --- a/DoomDeathmatch/src/Component/MVC/Model/Enemy/EnemyData.cs +++ b/DoomDeathmatch/src/Script/Model/Enemy/EnemyData.cs @@ -1,9 +1,10 @@ -using DoomDeathmatch.Component.MVC.Model.Enemy.Attack; -using DoomDeathmatch.Component.MVC.Model.Enemy.Movement; -using DoomDeathmatch.Scene.Play; +using DoomDeathmatch.Scene.Play.Prefab; +using DoomDeathmatch.Script.Model.Enemy.Attack; +using DoomDeathmatch.Script.Model.Enemy.Movement; +using Engine.Util; using OpenTK.Mathematics; -namespace DoomDeathmatch.Component.MVC.Model.Enemy; +namespace DoomDeathmatch.Script.Model.Enemy; public class EnemyData { @@ -45,7 +46,7 @@ public class EnemyData (parHealthController.GameObject.Transform.Translation - parEnemyController.GameObject.Transform.Translation).Normalized(); - var fireballObject = EnemyObject.CreateImpFireball(parEnemyController.GameObject.Scene!, + var fireballObject = EnemyPrefab.CreateFireball(EngineUtil.SceneManager.CurrentScene!, parEnemyController.GameObject.Transform.Translation + new Vector3(0, 0f, 1.75f), direction * 25, 35, diff --git a/DoomDeathmatch/src/Component/MVC/Model/Enemy/Movement/FollowPlayerMovementBehavior.cs b/DoomDeathmatch/src/Script/Model/Enemy/Movement/FollowPlayerMovementBehavior.cs similarity index 84% rename from DoomDeathmatch/src/Component/MVC/Model/Enemy/Movement/FollowPlayerMovementBehavior.cs rename to DoomDeathmatch/src/Script/Model/Enemy/Movement/FollowPlayerMovementBehavior.cs index 9c9631d..d2d9a7e 100644 --- a/DoomDeathmatch/src/Component/MVC/Model/Enemy/Movement/FollowPlayerMovementBehavior.cs +++ b/DoomDeathmatch/src/Script/Model/Enemy/Movement/FollowPlayerMovementBehavior.cs @@ -1,6 +1,6 @@ using OpenTK.Mathematics; -namespace DoomDeathmatch.Component.MVC.Model.Enemy.Movement; +namespace DoomDeathmatch.Script.Model.Enemy.Movement; public class FollowPlayerMovementBehavior(float parRadius) : IMovementBehavior { diff --git a/DoomDeathmatch/src/Component/MVC/Model/Enemy/Movement/IMovementBehavior.cs b/DoomDeathmatch/src/Script/Model/Enemy/Movement/IMovementBehavior.cs similarity index 71% rename from DoomDeathmatch/src/Component/MVC/Model/Enemy/Movement/IMovementBehavior.cs rename to DoomDeathmatch/src/Script/Model/Enemy/Movement/IMovementBehavior.cs index 6e2ca81..41bbde1 100644 --- a/DoomDeathmatch/src/Component/MVC/Model/Enemy/Movement/IMovementBehavior.cs +++ b/DoomDeathmatch/src/Script/Model/Enemy/Movement/IMovementBehavior.cs @@ -1,6 +1,6 @@ using OpenTK.Mathematics; -namespace DoomDeathmatch.Component.MVC.Model.Enemy.Movement; +namespace DoomDeathmatch.Script.Model.Enemy.Movement; public interface IMovementBehavior { diff --git a/DoomDeathmatch/src/Component/MVC/Model/Enemy/Movement/StandingMovementBehavior.cs b/DoomDeathmatch/src/Script/Model/Enemy/Movement/StandingMovementBehavior.cs similarity index 77% rename from DoomDeathmatch/src/Component/MVC/Model/Enemy/Movement/StandingMovementBehavior.cs rename to DoomDeathmatch/src/Script/Model/Enemy/Movement/StandingMovementBehavior.cs index 26de082..cbbeaed 100644 --- a/DoomDeathmatch/src/Component/MVC/Model/Enemy/Movement/StandingMovementBehavior.cs +++ b/DoomDeathmatch/src/Script/Model/Enemy/Movement/StandingMovementBehavior.cs @@ -1,6 +1,6 @@ using OpenTK.Mathematics; -namespace DoomDeathmatch.Component.MVC.Model.Enemy.Movement; +namespace DoomDeathmatch.Script.Model.Enemy.Movement; public class StandingMovementBehavior : IMovementBehavior { diff --git a/DoomDeathmatch/src/Component/MVC/Model/HealthModel.cs b/DoomDeathmatch/src/Script/Model/HealthModel.cs similarity index 93% rename from DoomDeathmatch/src/Component/MVC/Model/HealthModel.cs rename to DoomDeathmatch/src/Script/Model/HealthModel.cs index 8c02b78..13c48f5 100644 --- a/DoomDeathmatch/src/Component/MVC/Model/HealthModel.cs +++ b/DoomDeathmatch/src/Script/Model/HealthModel.cs @@ -1,4 +1,4 @@ -namespace DoomDeathmatch.Component.MVC.Model; +namespace DoomDeathmatch.Script.Model; public class HealthModel { diff --git a/DoomDeathmatch/src/Component/MVC/Model/ScoreModel.cs b/DoomDeathmatch/src/Script/Model/ScoreModel.cs similarity index 82% rename from DoomDeathmatch/src/Component/MVC/Model/ScoreModel.cs rename to DoomDeathmatch/src/Script/Model/ScoreModel.cs index 994d5c3..edfa21d 100644 --- a/DoomDeathmatch/src/Component/MVC/Model/ScoreModel.cs +++ b/DoomDeathmatch/src/Script/Model/ScoreModel.cs @@ -1,4 +1,4 @@ -namespace DoomDeathmatch.Component.MVC.Model; +namespace DoomDeathmatch.Script.Model; public class ScoreModel { diff --git a/DoomDeathmatch/src/Component/MVC/Model/Weapon/IShootPattern.cs b/DoomDeathmatch/src/Script/Model/Weapon/IShootPattern.cs similarity index 75% rename from DoomDeathmatch/src/Component/MVC/Model/Weapon/IShootPattern.cs rename to DoomDeathmatch/src/Script/Model/Weapon/IShootPattern.cs index 85e186f..74dc1c2 100644 --- a/DoomDeathmatch/src/Component/MVC/Model/Weapon/IShootPattern.cs +++ b/DoomDeathmatch/src/Script/Model/Weapon/IShootPattern.cs @@ -1,6 +1,6 @@ using OpenTK.Mathematics; -namespace DoomDeathmatch.Component.MVC.Model.Weapon; +namespace DoomDeathmatch.Script.Model.Weapon; public interface IShootPattern { diff --git a/DoomDeathmatch/src/Component/MVC/Model/Weapon/LineShootPattern.cs b/DoomDeathmatch/src/Script/Model/Weapon/LineShootPattern.cs similarity index 80% rename from DoomDeathmatch/src/Component/MVC/Model/Weapon/LineShootPattern.cs rename to DoomDeathmatch/src/Script/Model/Weapon/LineShootPattern.cs index cf4f77f..8b23a26 100644 --- a/DoomDeathmatch/src/Component/MVC/Model/Weapon/LineShootPattern.cs +++ b/DoomDeathmatch/src/Script/Model/Weapon/LineShootPattern.cs @@ -1,6 +1,6 @@ using OpenTK.Mathematics; -namespace DoomDeathmatch.Component.MVC.Model.Weapon; +namespace DoomDeathmatch.Script.Model.Weapon; public class LineShootPattern : IShootPattern { diff --git a/DoomDeathmatch/src/Component/MVC/Model/Weapon/RandomFlatSpreadShootPattern.cs b/DoomDeathmatch/src/Script/Model/Weapon/RandomFlatSpreadShootPattern.cs similarity index 90% rename from DoomDeathmatch/src/Component/MVC/Model/Weapon/RandomFlatSpreadShootPattern.cs rename to DoomDeathmatch/src/Script/Model/Weapon/RandomFlatSpreadShootPattern.cs index 887aad6..c8daff5 100644 --- a/DoomDeathmatch/src/Component/MVC/Model/Weapon/RandomFlatSpreadShootPattern.cs +++ b/DoomDeathmatch/src/Script/Model/Weapon/RandomFlatSpreadShootPattern.cs @@ -1,6 +1,6 @@ using OpenTK.Mathematics; -namespace DoomDeathmatch.Component.MVC.Model.Weapon; +namespace DoomDeathmatch.Script.Model.Weapon; public class RandomFlatSpreadShootPattern(float parAngle, uint parCount) : IShootPattern { diff --git a/DoomDeathmatch/src/Component/MVC/Model/Weapon/WeaponData.cs b/DoomDeathmatch/src/Script/Model/Weapon/WeaponData.cs similarity index 65% rename from DoomDeathmatch/src/Component/MVC/Model/Weapon/WeaponData.cs rename to DoomDeathmatch/src/Script/Model/Weapon/WeaponData.cs index 78073f7..bb73893 100644 --- a/DoomDeathmatch/src/Component/MVC/Model/Weapon/WeaponData.cs +++ b/DoomDeathmatch/src/Script/Model/Weapon/WeaponData.cs @@ -1,6 +1,6 @@ using OpenTK.Mathematics; -namespace DoomDeathmatch.Component.MVC.Model.Weapon; +namespace DoomDeathmatch.Script.Model.Weapon; public class WeaponData { @@ -9,7 +9,12 @@ public class WeaponData { Id = "pistol", Name = "Пистолет", - Texture = "texture/pistol.png", + IdleTexture = "texture/pistol/idle.png", + FireAnimationDuration = 0.25f, + FireAnimation = + { + "texture/pistol/fire1.png", "texture/pistol/fire2.png", "texture/pistol/fire3.png", "texture/pistol/fire4.png", + }, Damage = 30, ShootPattern = new LineShootPattern() }; @@ -19,14 +24,18 @@ public class WeaponData { Id = "shotgun", Name = "Дробовик", - Texture = "texture/shotgun.png", + IdleTexture = "texture/shotgun/idle.png", + FireAnimationDuration = 0.1f, + FireAnimation = { "texture/shotgun/fire1.png", "texture/shotgun/fire2.png" }, Damage = 5, - ShootPattern = new RandomFlatSpreadShootPattern(MathHelper.DegreesToRadians(20), 40) + ShootPattern = new RandomFlatSpreadShootPattern(MathHelper.DegreesToRadians(10), 40) }; public string Id { get; private init; } = ""; public string Name { get; private init; } = ""; - public string Texture { get; private init; } = ""; + public string IdleTexture { get; private init; } = ""; + public float FireAnimationDuration { get; private init; } = 0; + public List FireAnimation { get; private init; } = []; public int Damage { get; private init; } public int MaxAmmo { get; } public IShootPattern ShootPattern { get; private init; } diff --git a/DoomDeathmatch/src/Component/MVC/Model/WeaponModel.cs b/DoomDeathmatch/src/Script/Model/WeaponModel.cs similarity index 87% rename from DoomDeathmatch/src/Component/MVC/Model/WeaponModel.cs rename to DoomDeathmatch/src/Script/Model/WeaponModel.cs index e8491fc..fd9c4ed 100644 --- a/DoomDeathmatch/src/Component/MVC/Model/WeaponModel.cs +++ b/DoomDeathmatch/src/Script/Model/WeaponModel.cs @@ -1,6 +1,6 @@ -using DoomDeathmatch.Component.MVC.Model.Weapon; +using DoomDeathmatch.Script.Model.Weapon; -namespace DoomDeathmatch.Component.MVC.Model; +namespace DoomDeathmatch.Script.Model; public class WeaponModel { diff --git a/DoomDeathmatch/src/Script/ObjectSpawner.cs b/DoomDeathmatch/src/Script/ObjectSpawner.cs new file mode 100644 index 0000000..d1defbf --- /dev/null +++ b/DoomDeathmatch/src/Script/ObjectSpawner.cs @@ -0,0 +1,40 @@ +using DoomDeathmatch.Script.Condition; +using DoomDeathmatch.Script.Provider; +using Engine.Scene; +using OpenTK.Mathematics; + +namespace DoomDeathmatch.Script; + +public class ObjectSpawner : IUpdate +{ + public event Action? OnSpawned; + + private readonly IValueProvider _gameObjectProvider; + private readonly IValueProvider _positionProvider; + private readonly ICondition _condition; + + public ObjectSpawner(IValueProvider parGameObjectProvider, IValueProvider parPositionProvider, + ICondition parCondition) + { + _gameObjectProvider = parGameObjectProvider; + _positionProvider = parPositionProvider; + _condition = parCondition; + + _condition.OnTrue += Spawn; + } + + public void Update(double parDeltaTime) + { + _condition.Update(parDeltaTime); + } + + private void Spawn() + { + var gameObject = _gameObjectProvider.GetValue(); + var position = _positionProvider.GetValue(); + gameObject.Transform.Translation = position; + + OnSpawned?.Invoke(gameObject); + _condition.Reset(); + } +} \ No newline at end of file diff --git a/DoomDeathmatch/src/Script/Provider/ConstValueProvider.cs b/DoomDeathmatch/src/Script/Provider/ConstValueProvider.cs new file mode 100644 index 0000000..db61517 --- /dev/null +++ b/DoomDeathmatch/src/Script/Provider/ConstValueProvider.cs @@ -0,0 +1,9 @@ +namespace DoomDeathmatch.Script.Provider; + +public class ConstValueProvider(T parValue) : IValueProvider +{ + public T GetValue() => parValue; + + public static ConstValueProvider Create(T parValue) => new(parValue); + public static implicit operator ConstValueProvider(T parValue) => new(parValue); +} \ No newline at end of file diff --git a/DoomDeathmatch/src/Script/Provider/GeneratorValueProvider.cs b/DoomDeathmatch/src/Script/Provider/GeneratorValueProvider.cs new file mode 100644 index 0000000..09dc744 --- /dev/null +++ b/DoomDeathmatch/src/Script/Provider/GeneratorValueProvider.cs @@ -0,0 +1,9 @@ +namespace DoomDeathmatch.Script.Provider; + +public class GeneratorValueProvider(Func parGenerator) : IValueProvider +{ + public T GetValue() => parGenerator(); + + public static GeneratorValueProvider Create(Func parGenerator) => new(parGenerator); + public static implicit operator GeneratorValueProvider(Func parGenerator) => new(parGenerator); +} \ No newline at end of file diff --git a/DoomDeathmatch/src/Script/Provider/IValueProvider.cs b/DoomDeathmatch/src/Script/Provider/IValueProvider.cs new file mode 100644 index 0000000..6e0824b --- /dev/null +++ b/DoomDeathmatch/src/Script/Provider/IValueProvider.cs @@ -0,0 +1,6 @@ +namespace DoomDeathmatch.Script.Provider; + +public interface IValueProvider +{ + public T GetValue(); +} \ No newline at end of file diff --git a/DoomDeathmatch/src/Script/Provider/RandomListValueProvider.cs b/DoomDeathmatch/src/Script/Provider/RandomListValueProvider.cs new file mode 100644 index 0000000..a165112 --- /dev/null +++ b/DoomDeathmatch/src/Script/Provider/RandomListValueProvider.cs @@ -0,0 +1,20 @@ +namespace DoomDeathmatch.Script.Provider; + +public class RandomListValueProvider : IValueProvider +{ + private readonly List> _providers = []; + private readonly Random _random = new(); + + public RandomListValueProvider(IEnumerable> parProviders) + { + foreach (var provider in parProviders) + { + _providers.Add(provider); + } + } + + public T GetValue() + { + return _providers[_random.Next(_providers.Count)].GetValue(); + } +} \ No newline at end of file diff --git a/DoomDeathmatch/src/Script/Provider/WeightedRandomValueProvider.cs b/DoomDeathmatch/src/Script/Provider/WeightedRandomValueProvider.cs new file mode 100644 index 0000000..9568264 --- /dev/null +++ b/DoomDeathmatch/src/Script/Provider/WeightedRandomValueProvider.cs @@ -0,0 +1,35 @@ +namespace DoomDeathmatch.Script.Provider; + +public class WeightedRandomValueProvider : IValueProvider +{ + private readonly List<(int, IValueProvider)> _providers = []; + private readonly Random _random = new(); + private readonly int _totalWeight = 0; + + public WeightedRandomValueProvider(IEnumerable<(int, IValueProvider)> parProviders) + { + foreach (var (weight, provider) in parProviders) + { + _providers.Add((weight, provider)); + _totalWeight += weight; + } + + if (_totalWeight <= 0) + throw new InvalidOperationException($"{nameof(WeightedRandomValueProvider)} is empty"); + } + + public T GetValue() + { + var random = _random.Next(_totalWeight); + + foreach (var (weight, provider) in _providers) + { + if (random < weight) + return provider.GetValue(); + + random -= weight; + } + + return default!; // unreachable + } +} \ No newline at end of file diff --git a/DoomDeathmatch/src/Script/Score/ScoreRow.cs b/DoomDeathmatch/src/Script/Score/ScoreRow.cs new file mode 100644 index 0000000..90d8168 --- /dev/null +++ b/DoomDeathmatch/src/Script/Score/ScoreRow.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace DoomDeathmatch.Script.Score; + +[Serializable] +[JsonSerializable(typeof(ScoreRow))] +public class ScoreRow +{ + [JsonPropertyName("name")] + [JsonInclude] + public string Name { get; init; } = ""; + + [JsonPropertyName("score")] + [JsonInclude] + public int Score { get; init; } +} \ No newline at end of file diff --git a/DoomDeathmatch/src/Script/Score/ScoreTable.cs b/DoomDeathmatch/src/Script/Score/ScoreTable.cs new file mode 100644 index 0000000..f498fef --- /dev/null +++ b/DoomDeathmatch/src/Script/Score/ScoreTable.cs @@ -0,0 +1,71 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace DoomDeathmatch.Script.Score; + +[Serializable] +[JsonSerializable(typeof(ScoreTable))] +public class ScoreTable +{ + private static readonly JsonSerializerOptions OPTIONS = new() { Converters = { new ScoreTableJsonConverter() } }; + + public List Rows { get; } = new(); + + public static ScoreTable LoadOrCreate(string parPath) + { + ScoreTable? table = null; + + try + { + using var stream = File.OpenRead(parPath); + table = JsonSerializer.Deserialize(stream, OPTIONS); + } + catch (Exception) + { + // ignored + } + + if (table != null) + return table; + + table = new ScoreTable(); + ScoreTable.Save(table, parPath); + + return table; + } + + public static void Save(ScoreTable parTable, string parPath) + { + using var stream = File.Create(parPath); + JsonSerializer.Serialize(stream, parTable, OPTIONS); + } +} + +public class ScoreTableJsonConverter : JsonConverter +{ + public override ScoreTable? Read( + ref Utf8JsonReader parReader, + Type parTypeToConvert, + JsonSerializerOptions? parOptions + ) + { + var rows = JsonSerializer.Deserialize(ref parReader, parOptions); + if (rows == null) + return null; + + var scoreTable = new ScoreTable(); + foreach (var row in rows) + { + scoreTable.Rows.Add(row); + } + + scoreTable.Rows.Sort((parX, parY) => parY.Score.CompareTo(parX.Score)); + + return scoreTable; + } + + public override void Write(Utf8JsonWriter parWriter, ScoreTable parValue, JsonSerializerOptions parOptions) + { + JsonSerializer.Serialize(parWriter, parValue.Rows, parOptions); + } +} \ No newline at end of file diff --git a/DoomDeathmatch/src/Script/UI/Align.cs b/DoomDeathmatch/src/Script/UI/Align.cs new file mode 100644 index 0000000..0e22101 --- /dev/null +++ b/DoomDeathmatch/src/Script/UI/Align.cs @@ -0,0 +1,8 @@ +namespace DoomDeathmatch.Script.UI; + +public enum Align +{ + Left, + Center, + Right +} \ No newline at end of file diff --git a/DoomDeathmatch/src/Component/UI/Anchor.cs b/DoomDeathmatch/src/Script/UI/Anchor.cs similarity index 77% rename from DoomDeathmatch/src/Component/UI/Anchor.cs rename to DoomDeathmatch/src/Script/UI/Anchor.cs index c062dfb..619c7b3 100644 --- a/DoomDeathmatch/src/Component/UI/Anchor.cs +++ b/DoomDeathmatch/src/Script/UI/Anchor.cs @@ -1,4 +1,4 @@ -namespace DoomDeathmatch.Component.UI; +namespace DoomDeathmatch.Script.UI; public enum Anchor { diff --git a/DoomDeathmatch/src/Component/UI/Orientation.cs b/DoomDeathmatch/src/Script/UI/Orientation.cs similarity index 55% rename from DoomDeathmatch/src/Component/UI/Orientation.cs rename to DoomDeathmatch/src/Script/UI/Orientation.cs index f425014..cc35fe5 100644 --- a/DoomDeathmatch/src/Component/UI/Orientation.cs +++ b/DoomDeathmatch/src/Script/UI/Orientation.cs @@ -1,4 +1,4 @@ -namespace DoomDeathmatch.Component.UI; +namespace DoomDeathmatch.Script.UI; public enum Orientation { diff --git a/DoomDeathmatch/src/UiUtil.cs b/DoomDeathmatch/src/UiUtil.cs index f0d5184..2cc989c 100644 --- a/DoomDeathmatch/src/UiUtil.cs +++ b/DoomDeathmatch/src/UiUtil.cs @@ -1,20 +1,21 @@ using DoomDeathmatch.Component.UI; +using DoomDeathmatch.Script.UI; using Engine.Asset.Font; using Engine.Graphics.Pipeline; using Engine.Graphics.Texture; -using Engine.Input; using Engine.Scene; using Engine.Scene.Component.BuiltIn; using Engine.Scene.Component.BuiltIn.Renderer; +using Engine.Util; using OpenTK.Mathematics; namespace DoomDeathmatch; public static class UiUtil { - public static Font GetDoomFont(Engine.Engine parEngine) + public static Font GetDoomFont() { - return parEngine.AssetResourceManager.Load("font/doom"); + return EngineUtil.AssetResourceManager.Load("font/doom"); } public static (GameObject, OrthographicCamera) CreateOrthographicCamera(Engine.Scene.Scene parScene, @@ -41,7 +42,7 @@ public static class UiUtil public static (GameObject, UiContainerComponent, (GameObject, TextRenderer)) CreateTextUi(Engine.Scene.Scene parScene, UiContainerComponent parContainer, - Font parFont, string parText, TextAlignComponent.Align parAlign = TextAlignComponent.Align.Center, + Font parFont, string parText, Align parAlign = Align.Center, RenderLayer? parRenderLayer = null) { var size = parFont.Measure(parText); @@ -96,7 +97,7 @@ public static class UiUtil return (uiContainerObject, uiContainer); } - public static (GameObject, UiComponent) CreateLogoUi(Engine.Engine parEngine, Engine.Scene.Scene parScene, + public static (GameObject, UiComponent) CreateLogoUi(Engine.Scene.Scene parScene, UiContainerComponent parContainer, RenderLayer? parRenderLayer = null) { UiComponent uiComponent; @@ -107,7 +108,7 @@ public static class UiUtil }, [ new Box2DRenderer { - Texture = parEngine.AssetResourceManager.Load("texture/doom_logo.png"), + Texture = EngineUtil.AssetResourceManager.Load("texture/doom_logo.png"), RenderLayer = parRenderLayer ?? RenderLayer.DEFAULT }, uiComponent = new UiComponent { Container = parContainer, Anchor = Anchor.Center } diff --git a/Engine/src/Engine.cs b/Engine/src/Engine.cs index 95572b9..5ce27c7 100644 --- a/Engine/src/Engine.cs +++ b/Engine/src/Engine.cs @@ -22,7 +22,7 @@ namespace Engine; public sealed class Engine { - public static Engine Instance { get; private set; } = null!; + internal static Engine Instance { get; private set; } = null!; public Renderer Renderer { get; } public SceneManager SceneManager { get; } = new(); @@ -61,12 +61,16 @@ public sealed class Engine internal ResourceManager EngineResourceManager => _engineResourceManager; public IResourceManager AssetResourceManager => _assetResourceManager; + + public string DataFolder { get; } + private readonly ResourceManager _engineResourceManager; private readonly ResourceManager _assetResourceManager; private Thread? _updateThread; public Engine(int parWidth, int parHeight, bool parHeadless, string parTitle, string parAssetFolder, + string parDataFolder, ILogger parLogger) { if (Instance != null) @@ -95,6 +99,10 @@ public sealed class Engine _logger.Information("Created asset resource manager in {AssetFolder}", parAssetFolder); + DataFolder = parDataFolder; + + _logger.Information("Using data folder {DataFolder}", parDataFolder); + Renderer = new Renderer(this, parWidth, parHeight, settings); Window = new Window(this, Renderer.NativeWindow, parHeadless); } diff --git a/Engine/src/EngineBuilder.cs b/Engine/src/EngineBuilder.cs index ece7219..54081b2 100644 --- a/Engine/src/EngineBuilder.cs +++ b/Engine/src/EngineBuilder.cs @@ -13,7 +13,8 @@ public sealed class EngineBuilder private bool _headless; private int _width = 1; private int _height = 1; - private string _assetFolder = "./"; + private string _assetFolder = "./asset"; + private string _dataFolder = "./data"; private Func? _presenterFunc; private Func? _inputHandlerFunc; @@ -58,6 +59,12 @@ public sealed class EngineBuilder return this; } + public EngineBuilder DataFolder(string parDataFolder) + { + _dataFolder = parDataFolder; + return this; + } + public EngineBuilder Presenter(Func parPresenterFunc) { _presenterFunc = parPresenterFunc; @@ -101,7 +108,7 @@ public sealed class EngineBuilder public Engine Build() { var logger = BuildLogger(); - var engine = new Engine(_width, _height, _headless, _title, _assetFolder, logger); + var engine = new Engine(_width, _height, _headless, _title, _assetFolder, _dataFolder, logger); var presenter = _presenterFunc?.Invoke(engine); if (presenter != null) diff --git a/Engine/src/Graphics/Pipeline/RenderLayer.cs b/Engine/src/Graphics/Pipeline/RenderLayer.cs index a1a29b0..e8c2a3d 100644 --- a/Engine/src/Graphics/Pipeline/RenderLayer.cs +++ b/Engine/src/Graphics/Pipeline/RenderLayer.cs @@ -24,7 +24,7 @@ public class RenderLayer : IComparable public int CompareTo(RenderLayer? parOther) { - return _order.CompareTo(parOther?._order); + return parOther == null ? 1 : _order.CompareTo(parOther._order); } public override int GetHashCode() diff --git a/Engine/src/Input/KeyboardButtonCode.cs b/Engine/src/Input/KeyboardButtonCode.cs index bfdd0d5..811ef32 100644 --- a/Engine/src/Input/KeyboardButtonCode.cs +++ b/Engine/src/Input/KeyboardButtonCode.cs @@ -62,4 +62,61 @@ public enum KeyboardButtonCode PageDown, TotalCount = PageDown + 1 +} + +public static class KeyboardButtonCodeHelper +{ + public static List GetAllPrintableKeys() => + Enum.GetValues().Where(parX => parX.IsPrintableKey()).ToList(); + + public static bool IsPrintableKey(this KeyboardButtonCode parKey) => + parKey is >= KeyboardButtonCode.A and <= KeyboardButtonCode.Z + or >= KeyboardButtonCode.D1 and <= KeyboardButtonCode.D0 or KeyboardButtonCode.Space; + + public static char GetChar(this KeyboardButtonCode parKey) + { + return parKey switch + { + KeyboardButtonCode.A => 'A', + KeyboardButtonCode.B => 'B', + KeyboardButtonCode.C => 'C', + KeyboardButtonCode.D => 'D', + KeyboardButtonCode.E => 'E', + KeyboardButtonCode.F => 'F', + KeyboardButtonCode.G => 'G', + KeyboardButtonCode.H => 'H', + KeyboardButtonCode.I => 'I', + KeyboardButtonCode.J => 'J', + KeyboardButtonCode.K => 'K', + KeyboardButtonCode.L => 'L', + KeyboardButtonCode.M => 'M', + KeyboardButtonCode.N => 'N', + KeyboardButtonCode.O => 'O', + KeyboardButtonCode.P => 'P', + KeyboardButtonCode.Q => 'Q', + KeyboardButtonCode.R => 'R', + KeyboardButtonCode.S => 'S', + KeyboardButtonCode.T => 'T', + KeyboardButtonCode.U => 'U', + KeyboardButtonCode.V => 'V', + KeyboardButtonCode.W => 'W', + KeyboardButtonCode.X => 'X', + KeyboardButtonCode.Y => 'Y', + KeyboardButtonCode.Z => 'Z', + + KeyboardButtonCode.D1 => '1', + KeyboardButtonCode.D2 => '2', + KeyboardButtonCode.D3 => '3', + KeyboardButtonCode.D4 => '4', + KeyboardButtonCode.D5 => '5', + KeyboardButtonCode.D6 => '6', + KeyboardButtonCode.D7 => '7', + KeyboardButtonCode.D8 => '8', + KeyboardButtonCode.D9 => '9', + KeyboardButtonCode.D0 => '0', + + KeyboardButtonCode.Space => ' ', + _ => '\0' + }; + } } \ No newline at end of file diff --git a/Engine/src/Scene/Component/Component.cs b/Engine/src/Scene/Component/Component.cs index a33a6e7..96be6fa 100644 --- a/Engine/src/Scene/Component/Component.cs +++ b/Engine/src/Scene/Component/Component.cs @@ -14,7 +14,7 @@ public abstract class Component : IUpdate, IRender { } - public virtual void PreUpdate() + public virtual void PreUpdate(double parDeltaTime) { } diff --git a/Engine/src/Scene/GameObject.cs b/Engine/src/Scene/GameObject.cs index 91e2472..d866790 100644 --- a/Engine/src/Scene/GameObject.cs +++ b/Engine/src/Scene/GameObject.cs @@ -21,7 +21,7 @@ public sealed class GameObject : IUpdate, IRender public Transform Transform { get; } - public Scene? Scene { get; set; } + internal Scene? Scene { get; set; } private readonly Queue _componentActions = new(); @@ -47,14 +47,14 @@ public sealed class GameObject : IUpdate, IRender Transform = GetComponent()!; } - public void PreUpdate() + public void PreUpdate(double parDeltaTime) { ProcessAddedComponents(); ProcessRemovedComponents(); foreach (var component in _components) { - component.PreUpdate(); + component.PreUpdate(parDeltaTime); } } diff --git a/Engine/src/Scene/Scene.cs b/Engine/src/Scene/Scene.cs index 57339b8..4c88bfb 100644 --- a/Engine/src/Scene/Scene.cs +++ b/Engine/src/Scene/Scene.cs @@ -39,7 +39,9 @@ public class Scene : IUpdate, IRender return Hierarchy.Objects .Where(parGameObject => !parOnlyEnabled || parGameObject.IsEnabled) .Select(parGameObject => parGameObject.GetComponent()) - .Where(parComponent => parComponent != null).ToList()!; + .Where(parComponent => parComponent != null) + .Distinct() + .ToList()!; } public T? FindFirstComponent() where T : Component.Component @@ -61,7 +63,7 @@ public class Scene : IUpdate, IRender foreach (var gameObject in hierarchyObjects) { - gameObject.PreUpdate(); + gameObject.PreUpdate(parDeltaTime * TimeScale); } foreach (var gameObject in hierarchyObjects) diff --git a/Engine/src/Util/EngineUtil.cs b/Engine/src/Util/EngineUtil.cs new file mode 100644 index 0000000..f463d1c --- /dev/null +++ b/Engine/src/Util/EngineUtil.cs @@ -0,0 +1,24 @@ +using Engine.Input; +using Engine.Resource; +using Engine.Scene; + +namespace Engine.Util; + +public static class EngineUtil +{ + public static IInputHandler InputHandler => Engine.Instance.InputHandler!; + public static SceneManager SceneManager => Engine.Instance.SceneManager; + public static IResourceManager AssetResourceManager => Engine.Instance.AssetResourceManager; + public static string DataFolder => Engine.Instance.DataFolder; + + public static void CreateObject(GameObject parGameObject) + { + var scene = Engine.Instance.SceneManager.CurrentScene!; + scene.Add(parGameObject); + } + + public static void Close() + { + Engine.Instance.Close(); + } +} \ No newline at end of file diff --git a/Engine/src/Util/TickableTimer.cs b/Engine/src/Util/TickableTimer.cs index c9c3a06..4bf65f8 100644 --- a/Engine/src/Util/TickableTimer.cs +++ b/Engine/src/Util/TickableTimer.cs @@ -28,6 +28,9 @@ public class TickableTimer if (value > TotalTime) value = TotalTime; + if (value == _currentTime) + return; + _currentTime = value; OnUpdate?.Invoke(value); diff --git a/PresenterConsole/src/Program.cs b/PresenterConsole/src/Program.cs index 137c990..1e1d05b 100644 --- a/PresenterConsole/src/Program.cs +++ b/PresenterConsole/src/Program.cs @@ -12,7 +12,8 @@ internal static class Program .LogLevel(LogEventLevel.Debug) .Presenter(parEngine => new ConsolePresenter(parEngine)) .InputHandler(_ => new ConsoleInputHandler()) - .AssetFolder(Path.GetFullPath("../DoomDeathmatch/asset")) + .AssetFolder(Path.GetFullPath("./asset")) + .DataFolder(Path.GetFullPath("./data")) .Build(); DoomDeathmatch.DoomDeathmatch.Initialize(engine); diff --git a/PresenterNative/src/Program.cs b/PresenterNative/src/Program.cs index 679f361..053eb44 100644 --- a/PresenterNative/src/Program.cs +++ b/PresenterNative/src/Program.cs @@ -18,7 +18,8 @@ internal static class Program .LogLevel(LogEventLevel.Debug) .Presenter(parEngine => parEngine.Window) .InputHandler(parEngine => new WindowInputHandler(parEngine.Window)) - .AssetFolder(Path.GetFullPath("../DoomDeathmatch/asset")) + .AssetFolder(Path.GetFullPath("./asset")) + .DataFolder(Path.GetFullPath("./data")) .Build(); DoomDeathmatch.DoomDeathmatch.Initialize(engine); diff --git a/PresenterWpf/src/App.xaml.cs b/PresenterWpf/src/App.xaml.cs index 103cfaa..042b863 100644 --- a/PresenterWpf/src/App.xaml.cs +++ b/PresenterWpf/src/App.xaml.cs @@ -27,7 +27,8 @@ public partial class App : Application .LogToConsole() .LogToFile(true, "log.txt") .LogLevel(LogEventLevel.Debug) - .AssetFolder(Path.GetFullPath("../DoomDeathmatch/asset")) + .AssetFolder(Path.GetFullPath("./asset")) + .DataFolder(Path.GetFullPath("./data")) .Build(); // Since engine claims current thread for rendering, we need to create a new thread to run WPF