This commit is contained in:
2025-01-06 22:36:52 +03:00
parent 0629544314
commit d3b899ba93
103 changed files with 1352 additions and 452 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -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<Texture>(_consumable.Icon);
_movementController = GameObject.GetComponent<MovementController>()!;
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<PlayerController>();
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();
}
}

View File

@@ -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<ScoreController>()!;
_timerController = GameObject.GetComponent<TimerController>()!;
_playerController = GameObject.Scene!.FindFirstComponent<PlayerController>()!;
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();
}
}
}
}

View File

@@ -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>()!;
_gameController = EngineUtil.SceneManager.CurrentScene!.FindFirstComponent<GameController>()!;
_healthController = GameObject.GetComponent<HealthController>()!;
_enemyView = GameObject.GetComponent<EnemyView>()!;
_movementController = GameObject.GetComponent<MovementController>()!;
@@ -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);
}
}

View File

@@ -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<Texture>(parEnemyData.Texture);
_box2DRenderer.Texture = EngineUtil.AssetResourceManager.Load<Texture>(parEnemyData.Texture);
}
}

View File

@@ -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<ScoreController>()!;
PlayerController = EngineUtil.SceneManager.CurrentScene!.FindFirstComponent<PlayerController>()!;
_timerController = GameObject.GetComponent<TimerController>()!;
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));
}
}

View File

@@ -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
{

View File

@@ -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();
}

View File

@@ -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
{

View File

@@ -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());
}
}

View File

@@ -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<CollisionManager>();
var collisionManager = EngineUtil.SceneManager.CurrentScene!.FindFirstComponent<CollisionManagerComponent>();
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>();
enemyController?.HealthController.TakeDamage(_weaponController.WeaponData.Damage);
}

View File

@@ -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
{

View File

@@ -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
{

View File

@@ -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);
}

View File

@@ -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
{

View File

@@ -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<Texture>(parWeaponData.Texture);
}
public void UpdateAmmoView(WeaponData parWeaponData)
{
_weaponAmmo.Text = $"Патроны: {parWeaponData.Ammo}/{parWeaponData.MaxAmmo}";
}
}

View File

@@ -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)

View File

@@ -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<Texture>? _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<Texture>(parWeaponData.IdleTexture);
_weaponSprite.Texture = _idleTexture;
_weaponFireAnimation?.Reset();
if (_weaponFireAnimation != null)
{
_weaponFireAnimation.OnFrameChanged -= OnAnimationFrame;
_weaponFireAnimation.OnFinish -= OnAnimationFinish;
}
var frames = parWeaponData.FireAnimation.Select(EngineUtil.AssetResourceManager.Load<Texture>).ToList();
_weaponFireAnimation = new AnimationPlayer<Texture>(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;
}
}

View File

@@ -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);
}
}

View File

@@ -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
{

View File

@@ -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));
}
}

View File

@@ -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<AABBColliderComponent> _colliders = [];
public override void PreUpdate()
public override void PreUpdate(double parDeltaTime)
{
_colliders = GameObject.Scene!.FindAllComponents<AABBColliderComponent>();
_colliders = EngineUtil.SceneManager.CurrentScene!.FindAllComponents<AABBColliderComponent>();
}
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;

View File

@@ -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));
}
}

View File

@@ -1,6 +1,6 @@
using OpenTK.Mathematics;
namespace DoomDeathmatch.Component.Util;
namespace DoomDeathmatch.Component.Physics;
public class GravityComponent : Engine.Scene.Component.Component
{

View File

@@ -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;
}
}

View File

@@ -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<UiComponent>? OnSelect;
private readonly IInputHandler _inputHandler = Engine.Engine.Instance.InputHandler!;
private readonly IInputHandler _inputHandler = EngineUtil.InputHandler;
private readonly List<UiComponent> _children = [];

View File

@@ -1,4 +1,4 @@
using Engine.Scene;
using DoomDeathmatch.Script.UI;
using OpenTK.Mathematics;
namespace DoomDeathmatch.Component.UI;

View File

@@ -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
}
}

View File

@@ -0,0 +1,80 @@
using Engine.Input;
using Engine.Util;
namespace DoomDeathmatch.Component.UI;
public class TextInputComponent : Engine.Scene.Component.Component
{
public event Action<string>? 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<KeyboardButtonCode> _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);
}
}
}
}

View File

@@ -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<UiComponent>? OnClick;
public event Action<UiComponent>? OnMouseOver;
private readonly IInputHandler _inputHandler = Engine.Engine.Instance.InputHandler!;
private readonly IInputHandler _inputHandler = EngineUtil.InputHandler;
public override void Update(double parDeltaTime)
{

View File

@@ -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; }

View File

@@ -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;
}
}

View File

@@ -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>();
healthController?.TakeDamage(Damage);
GameObject.Scene!.Remove(GameObject);
EngineUtil.SceneManager.CurrentScene!.Remove(GameObject);
}
}

View File

@@ -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()

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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"));
}
}

View File

@@ -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 } });

View File

@@ -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<Mesh>($"map/{parMapName}/mesh.obj"),
Albedo = parEngine.AssetResourceManager.Load<Texture>($"map/{parMapName}/texture.png")
Mesh = EngineUtil.AssetResourceManager.Load<Mesh>($"map/{parMapName}/mesh.obj"),
Albedo = EngineUtil.AssetResourceManager.Load<Texture>($"map/{parMapName}/texture.png")
}
]);
var colliders = parEngine.AssetResourceManager.Load<Mesh>($"map/{parMapName}/colliders.obj");
var colliders = EngineUtil.AssetResourceManager.Load<Mesh>($"map/{parMapName}/colliders.obj");
var collidersObject = MeshToColliders(parScene, colliders);
var monsterSpawnPoints = MeshToSpawnPoints(
parEngine.AssetResourceManager.Load<Mesh>($"map/{parMapName}/monster_spawners.obj"));
EngineUtil.AssetResourceManager.Load<Mesh>($"map/{parMapName}/monster_spawners.obj"));
var valuableSpawnPoints = MeshToSpawnPoints(
parEngine.AssetResourceManager.Load<Mesh>($"map/{parMapName}/valuable_spawners.obj"));
EngineUtil.AssetResourceManager.Load<Mesh>($"map/{parMapName}/valuable_spawners.obj"));
parScene.AddChild(mapObject, collidersObject);
var monsterVector3ValueProvider =
new RandomListValueProvider<Vector3>(monsterSpawnPoints.Select(ConstValueProvider<Vector3>.Create));
var monsterSpawnerObject = GameObjectUtil.CreateGameObject(parScene, [
new ObjectSpawnerComponent(
new ObjectSpawner(new WeightedRandomValueProvider<GameObject>(
new List<(int, IValueProvider<GameObject>)>
{
(6, GeneratorValueProvider<GameObject>.Create(() =>
EnemyPrefab.Create(EngineUtil.SceneManager.CurrentScene!, EnemyData.Demon))),
(4,
GeneratorValueProvider<GameObject>.Create(() =>
EnemyPrefab.Create(EngineUtil.SceneManager.CurrentScene!, EnemyData.Imp)))
}),
monsterVector3ValueProvider,
new TickableTimerCondition(2f)))
]
);
parScene.AddChild(parGameControllerObject, monsterSpawnerObject);
var consumableVector3ValueProvider =
new RandomListValueProvider<Vector3>(valuableSpawnPoints.Select(ConstValueProvider<Vector3>.Create));
var consumableSpawnerObject = GameObjectUtil.CreateGameObject(parScene, [
new ObjectSpawnerComponent(
new ObjectSpawner(new WeightedRandomValueProvider<GameObject>(
new List<(int, IValueProvider<GameObject>)>
{
(2, GeneratorValueProvider<GameObject>.Create(() =>
{
var gameController = EngineUtil.SceneManager.CurrentScene!.FindFirstComponent<GameController>()!;
return ConsumablePrefab.Create(EngineUtil.SceneManager.CurrentScene,
new HealthPackConsumable(75),
gameController.PlayerController.Camera.GameObject.Transform
);
})),
(4, GeneratorValueProvider<GameObject>.Create(() =>
{
var gameController = EngineUtil.SceneManager.CurrentScene!.FindFirstComponent<GameController>()!;
return ConsumablePrefab.Create(EngineUtil.SceneManager.CurrentScene,
new WeaponConsumable(WeaponData.Pistol),
gameController.PlayerController.Camera.GameObject.Transform
);
})),
(3, GeneratorValueProvider<GameObject>.Create(() =>
{
var gameController = EngineUtil.SceneManager.CurrentScene!.FindFirstComponent<GameController>()!;
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<GameController>()!.Unpause();
backUi.OnClick += _ =>
EngineUtil.SceneManager.CurrentScene!.FindFirstComponent<GameController>()!.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

View File

@@ -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;
}
}

View File

@@ -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>("texture/fireball.png")
},
new Box2DRenderer { Texture = EngineUtil.AssetResourceManager.Load<Texture>("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" }
}
]
);

View File

@@ -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;
}
}

View File

@@ -0,0 +1,50 @@
using Engine.Scene;
using Engine.Util;
namespace DoomDeathmatch.Script;
public class AnimationPlayer<T>(float parInterval) : IUpdate
{
public List<T> Frames { get; init; } = [];
public bool IsPlaying { get; private set; } = false;
public int NextFrame { get; private set; } = 0;
public event Action<T>? 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;
}
}

View File

@@ -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;
}
}

View File

@@ -1,7 +1,7 @@
using Engine.Scene;
using OpenTK.Mathematics;
namespace DoomDeathmatch.Component.Util.Collision;
namespace DoomDeathmatch.Script.Collision;
public class RaycastResult
{

View File

@@ -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();
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,10 @@
using DoomDeathmatch.Component.MVC;
namespace DoomDeathmatch.Script.Consumable;
public interface IConsumable
{
public string Icon { get; }
public void Consume(PlayerController parPlayerController);
}

View File

@@ -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);
}
}

View File

@@ -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)
{

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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<EnemyController, HealthController, AttackBehavior> parFunc)
: IAttackBehaviorCreator

View File

@@ -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
{

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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
{

View File

@@ -1,6 +1,6 @@
using OpenTK.Mathematics;
namespace DoomDeathmatch.Component.MVC.Model.Enemy.Movement;
namespace DoomDeathmatch.Script.Model.Enemy.Movement;
public interface IMovementBehavior
{

View File

@@ -1,6 +1,6 @@
using OpenTK.Mathematics;
namespace DoomDeathmatch.Component.MVC.Model.Enemy.Movement;
namespace DoomDeathmatch.Script.Model.Enemy.Movement;
public class StandingMovementBehavior : IMovementBehavior
{

View File

@@ -1,4 +1,4 @@
namespace DoomDeathmatch.Component.MVC.Model;
namespace DoomDeathmatch.Script.Model;
public class HealthModel
{

View File

@@ -1,4 +1,4 @@
namespace DoomDeathmatch.Component.MVC.Model;
namespace DoomDeathmatch.Script.Model;
public class ScoreModel
{

View File

@@ -1,6 +1,6 @@
using OpenTK.Mathematics;
namespace DoomDeathmatch.Component.MVC.Model.Weapon;
namespace DoomDeathmatch.Script.Model.Weapon;
public interface IShootPattern
{

View File

@@ -1,6 +1,6 @@
using OpenTK.Mathematics;
namespace DoomDeathmatch.Component.MVC.Model.Weapon;
namespace DoomDeathmatch.Script.Model.Weapon;
public class LineShootPattern : IShootPattern
{

View File

@@ -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
{

View File

@@ -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<string> FireAnimation { get; private init; } = [];
public int Damage { get; private init; }
public int MaxAmmo { get; }
public IShootPattern ShootPattern { get; private init; }

View File

@@ -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
{

View File

@@ -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<GameObject>? OnSpawned;
private readonly IValueProvider<GameObject> _gameObjectProvider;
private readonly IValueProvider<Vector3> _positionProvider;
private readonly ICondition _condition;
public ObjectSpawner(IValueProvider<GameObject> parGameObjectProvider, IValueProvider<Vector3> 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();
}
}

View File

@@ -0,0 +1,9 @@
namespace DoomDeathmatch.Script.Provider;
public class ConstValueProvider<T>(T parValue) : IValueProvider<T>
{
public T GetValue() => parValue;
public static ConstValueProvider<T> Create(T parValue) => new(parValue);
public static implicit operator ConstValueProvider<T>(T parValue) => new(parValue);
}

View File

@@ -0,0 +1,9 @@
namespace DoomDeathmatch.Script.Provider;
public class GeneratorValueProvider<T>(Func<T> parGenerator) : IValueProvider<T>
{
public T GetValue() => parGenerator();
public static GeneratorValueProvider<T> Create(Func<T> parGenerator) => new(parGenerator);
public static implicit operator GeneratorValueProvider<T>(Func<T> parGenerator) => new(parGenerator);
}

View File

@@ -0,0 +1,6 @@
namespace DoomDeathmatch.Script.Provider;
public interface IValueProvider<out T>
{
public T GetValue();
}

View File

@@ -0,0 +1,20 @@
namespace DoomDeathmatch.Script.Provider;
public class RandomListValueProvider<T> : IValueProvider<T>
{
private readonly List<IValueProvider<T>> _providers = [];
private readonly Random _random = new();
public RandomListValueProvider(IEnumerable<IValueProvider<T>> parProviders)
{
foreach (var provider in parProviders)
{
_providers.Add(provider);
}
}
public T GetValue()
{
return _providers[_random.Next(_providers.Count)].GetValue();
}
}

View File

@@ -0,0 +1,35 @@
namespace DoomDeathmatch.Script.Provider;
public class WeightedRandomValueProvider<T> : IValueProvider<T>
{
private readonly List<(int, IValueProvider<T>)> _providers = [];
private readonly Random _random = new();
private readonly int _totalWeight = 0;
public WeightedRandomValueProvider(IEnumerable<(int, IValueProvider<T>)> parProviders)
{
foreach (var (weight, provider) in parProviders)
{
_providers.Add((weight, provider));
_totalWeight += weight;
}
if (_totalWeight <= 0)
throw new InvalidOperationException($"{nameof(WeightedRandomValueProvider<T>)} 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
}
}

View File

@@ -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; }
}

View File

@@ -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<ScoreRow> Rows { get; } = new();
public static ScoreTable LoadOrCreate(string parPath)
{
ScoreTable? table = null;
try
{
using var stream = File.OpenRead(parPath);
table = JsonSerializer.Deserialize<ScoreTable>(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<ScoreTable>
{
public override ScoreTable? Read(
ref Utf8JsonReader parReader,
Type parTypeToConvert,
JsonSerializerOptions? parOptions
)
{
var rows = JsonSerializer.Deserialize<ScoreRow[]>(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);
}
}

View File

@@ -0,0 +1,8 @@
namespace DoomDeathmatch.Script.UI;
public enum Align
{
Left,
Center,
Right
}

View File

@@ -1,4 +1,4 @@
namespace DoomDeathmatch.Component.UI;
namespace DoomDeathmatch.Script.UI;
public enum Anchor
{

View File

@@ -1,4 +1,4 @@
namespace DoomDeathmatch.Component.UI;
namespace DoomDeathmatch.Script.UI;
public enum Orientation
{

View File

@@ -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>("font/doom");
return EngineUtil.AssetResourceManager.Load<Font>("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>("texture/doom_logo.png"),
Texture = EngineUtil.AssetResourceManager.Load<Texture>("texture/doom_logo.png"),
RenderLayer = parRenderLayer ?? RenderLayer.DEFAULT
},
uiComponent = new UiComponent { Container = parContainer, Anchor = Anchor.Center }

View File

@@ -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);
}

View File

@@ -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<Engine, IPresenter>? _presenterFunc;
private Func<Engine, IInputHandler>? _inputHandlerFunc;
@@ -58,6 +59,12 @@ public sealed class EngineBuilder
return this;
}
public EngineBuilder DataFolder(string parDataFolder)
{
_dataFolder = parDataFolder;
return this;
}
public EngineBuilder Presenter(Func<Engine, IPresenter> 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)

View File

@@ -24,7 +24,7 @@ public class RenderLayer : IComparable<RenderLayer>
public int CompareTo(RenderLayer? parOther)
{
return _order.CompareTo(parOther?._order);
return parOther == null ? 1 : _order.CompareTo(parOther._order);
}
public override int GetHashCode()

View File

@@ -63,3 +63,60 @@ public enum KeyboardButtonCode
TotalCount = PageDown + 1
}
public static class KeyboardButtonCodeHelper
{
public static List<KeyboardButtonCode> GetAllPrintableKeys() =>
Enum.GetValues<KeyboardButtonCode>().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'
};
}
}

View File

@@ -14,7 +14,7 @@ public abstract class Component : IUpdate, IRender
{
}
public virtual void PreUpdate()
public virtual void PreUpdate(double parDeltaTime)
{
}

View File

@@ -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<Action> _componentActions = new();
@@ -47,14 +47,14 @@ public sealed class GameObject : IUpdate, IRender
Transform = GetComponent<Transform>()!;
}
public void PreUpdate()
public void PreUpdate(double parDeltaTime)
{
ProcessAddedComponents();
ProcessRemovedComponents();
foreach (var component in _components)
{
component.PreUpdate();
component.PreUpdate(parDeltaTime);
}
}

View File

@@ -39,7 +39,9 @@ public class Scene : IUpdate, IRender
return Hierarchy.Objects
.Where(parGameObject => !parOnlyEnabled || parGameObject.IsEnabled)
.Select(parGameObject => parGameObject.GetComponent<T>())
.Where(parComponent => parComponent != null).ToList()!;
.Where(parComponent => parComponent != null)
.Distinct()
.ToList()!;
}
public T? FindFirstComponent<T>() 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)

View File

@@ -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();
}
}

View File

@@ -28,6 +28,9 @@ public class TickableTimer
if (value > TotalTime)
value = TotalTime;
if (value == _currentTime)
return;
_currentTime = value;
OnUpdate?.Invoke(value);

Some files were not shown because too many files have changed in this diff Show More