This commit is contained in:
2025-01-07 00:48:37 +03:00
parent 2061eb9347
commit 0df34ef0ec
97 changed files with 866 additions and 764 deletions

View File

@@ -39,16 +39,22 @@ public class ConsumableComponent : Engine.Scene.Component.Component
private void OnCollision(AABBColliderComponent parCollider) private void OnCollision(AABBColliderComponent parCollider)
{ {
if (parCollider.ColliderGroups.Contains("player")) if (parCollider.ColliderGroups.Contains("player"))
{
TryConsume(parCollider); TryConsume(parCollider);
}
else if (parCollider.ColliderGroups.Contains("consumable")) else if (parCollider.ColliderGroups.Contains("consumable"))
{
MoveAway(parCollider); MoveAway(parCollider);
} }
}
private void TryConsume(AABBColliderComponent parCollider) private void TryConsume(AABBColliderComponent parCollider)
{ {
var playerController = parCollider.GameObject.GetComponent<PlayerController>(); var playerController = parCollider.GameObject.GetComponent<PlayerController>();
if (playerController == null) if (playerController == null)
{
return; return;
}
_consumable.Consume(playerController); _consumable.Consume(playerController);
@@ -61,17 +67,21 @@ public class ConsumableComponent : Engine.Scene.Component.Component
parCollider.GameObject.Transform.GetFullTranslation(); parCollider.GameObject.Transform.GetFullTranslation();
if (direction.LengthSquared <= float.Epsilon) if (direction.LengthSquared <= float.Epsilon)
{
direction = GetRandomDirection(); direction = GetRandomDirection();
}
else else
{
direction.Normalize(); direction.Normalize();
}
_movementController.ApplyMovement(direction); _movementController.ApplyMovement(direction);
} }
private Vector3 GetRandomDirection() private Vector3 GetRandomDirection()
{ {
var x = _random.NextSingle() * 2 - 1; var x = (_random.NextSingle() * 2) - 1;
var y = _random.NextSingle() * 2 - 1; var y = (_random.NextSingle() * 2) - 1;
return new Vector3(x, y, 0).Normalized(); return new Vector3(x, y, 0).Normalized();
} }

View File

@@ -8,16 +8,15 @@ namespace DoomDeathmatch.Component.MVC.Enemy;
public class EnemyController : Engine.Scene.Component.Component public class EnemyController : Engine.Scene.Component.Component
{ {
public HealthController HealthController => _healthController; public HealthController HealthController { get; private set; } = null!;
private GameController _gameController = null!;
private HealthController _healthController = null!;
private EnemyView _enemyView = null!;
private MovementController _movementController = null!;
private AttackBehavior _attackBehavior = null!;
private readonly EnemyData _enemyData; private readonly EnemyData _enemyData;
private GameController _gameController = null!;
private MovementController _movementController = null!;
private AttackBehavior _attackBehavior = null!;
private EnemyView _enemyView = null!;
public EnemyController(EnemyData parEnemyData) public EnemyController(EnemyData parEnemyData)
{ {
_enemyData = parEnemyData; _enemyData = parEnemyData;
@@ -26,19 +25,19 @@ public class EnemyController : Engine.Scene.Component.Component
public override void Awake() public override void Awake()
{ {
_gameController = EngineUtil.SceneManager.CurrentScene!.FindFirstComponent<GameController>()!; _gameController = EngineUtil.SceneManager.CurrentScene!.FindFirstComponent<GameController>()!;
_healthController = GameObject.GetComponent<HealthController>()!; HealthController = GameObject.GetComponent<HealthController>()!;
_enemyView = GameObject.GetComponent<EnemyView>()!; _enemyView = GameObject.GetComponent<EnemyView>()!;
_movementController = GameObject.GetComponent<MovementController>()!; _movementController = GameObject.GetComponent<MovementController>()!;
ArgumentNullException.ThrowIfNull(_gameController); ArgumentNullException.ThrowIfNull(_gameController);
ArgumentNullException.ThrowIfNull(_healthController); ArgumentNullException.ThrowIfNull(HealthController);
ArgumentNullException.ThrowIfNull(_enemyView); ArgumentNullException.ThrowIfNull(_enemyView);
ArgumentNullException.ThrowIfNull(_movementController); ArgumentNullException.ThrowIfNull(_movementController);
_attackBehavior = _enemyData.AttackBehaviorCreator.Create(this, _gameController.PlayerController.HealthController); _attackBehavior = _enemyData.AttackBehaviorCreator.Create(this, _gameController.PlayerController.HealthController);
_healthController.SetMaxHealth(_enemyData.BaseHealth); HealthController.SetMaxHealth(_enemyData.BaseHealth);
_healthController.OnDeath += OnDeath; HealthController.OnDeath += OnDeath;
_enemyView.UpdateView(_enemyData); _enemyView.UpdateView(_enemyData);
_movementController.Speed = _enemyData.BaseSpeed; _movementController.Speed = _enemyData.BaseSpeed;
} }
@@ -47,7 +46,9 @@ public class EnemyController : Engine.Scene.Component.Component
{ {
var billboardComponent = GameObject.GetComponentInChildren<BillboardComponent>(); var billboardComponent = GameObject.GetComponentInChildren<BillboardComponent>();
if (billboardComponent == null) if (billboardComponent == null)
{
return; return;
}
billboardComponent.Target = _gameController.PlayerController.Camera.GameObject.Transform; billboardComponent.Target = _gameController.PlayerController.Camera.GameObject.Transform;
} }

View File

@@ -9,16 +9,16 @@ namespace DoomDeathmatch.Component.MVC;
public class GameController : Engine.Scene.Component.Component public class GameController : Engine.Scene.Component.Component
{ {
public bool IsPaused { get; private set; } = false; public bool IsPaused { get; private set; }
public bool IsGameOver { get; private set; } = false; public bool IsGameOver { get; private set; }
public PlayerController PlayerController { get; private set; } = null!; public PlayerController PlayerController { get; private set; } = null!;
public ScoreController ScoreController { 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; private readonly IInputHandler _inputHandler = EngineUtil.InputHandler;
private readonly MenuControllerComponent _menuController;
private TimerController _timerController = null!;
public GameController(MenuControllerComponent parMenuController) public GameController(MenuControllerComponent parMenuController)
{ {

View File

@@ -16,7 +16,9 @@ public class HealthView : Engine.Scene.Component.Component
{ {
var percentage = parHealthModel.Health / parHealthModel.MaxHealth * 100; var percentage = parHealthModel.Health / parHealthModel.MaxHealth * 100;
if (parHealthModel.Health != 0) if (parHealthModel.Health != 0)
{
percentage = Math.Max(1, percentage); percentage = Math.Max(1, percentage);
}
_healthTextRenderer.Text = $"Здоровье: {percentage:000}"; _healthTextRenderer.Text = $"Здоровье: {percentage:000}";
} }

View File

@@ -21,6 +21,6 @@ public class MovementController : Engine.Scene.Component.Component
public void ApplyMovement(Vector3 parDirection) public void ApplyMovement(Vector3 parDirection)
{ {
_rigidbody.AddForce(_dragComponent.Drag * Speed * parDirection.Normalized()); _rigidbody.Force += _dragComponent.Drag * Speed * parDirection.Normalized();
} }
} }

View File

@@ -12,71 +12,77 @@ namespace DoomDeathmatch.Component.MVC;
public class PlayerController : Engine.Scene.Component.Component public class PlayerController : Engine.Scene.Component.Component
{ {
public event Action? OnDeath; public event Action? OnDeath;
public bool IsAlive => _healthController.IsAlive;
public HealthController HealthController => _healthController; public bool IsAlive => HealthController.IsAlive;
public WeaponController WeaponController => _weaponController; public HealthController HealthController { get; private set; } = null!;
public PerspectiveCamera Camera => _camera; public WeaponController WeaponController { get; private set; } = null!;
public PerspectiveCamera Camera { get; }
private readonly IInputHandler _inputHandler = EngineUtil.InputHandler; private readonly IInputHandler _inputHandler = EngineUtil.InputHandler;
private HealthController _healthController = null!;
private WeaponController _weaponController = null!;
private readonly PerspectiveCamera _camera;
public PlayerController(PerspectiveCamera parCamera) public PlayerController(PerspectiveCamera parCamera)
{ {
_camera = parCamera; Camera = parCamera;
} }
public override void Awake() public override void Awake()
{ {
_healthController = GameObject.GetComponent<HealthController>()!; HealthController = GameObject.GetComponent<HealthController>()!;
_weaponController = GameObject.GetComponent<WeaponController>()!; WeaponController = GameObject.GetComponent<WeaponController>()!;
ArgumentNullException.ThrowIfNull(_healthController); ArgumentNullException.ThrowIfNull(HealthController);
ArgumentNullException.ThrowIfNull(_weaponController); ArgumentNullException.ThrowIfNull(WeaponController);
_healthController.OnDeath += () => OnDeath?.Invoke(); HealthController.OnDeath += () => OnDeath?.Invoke();
} }
public override void Update(double parDeltaTime) public override void Update(double parDeltaTime)
{ {
if (!IsAlive || parDeltaTime == 0) if (!IsAlive || parDeltaTime == 0)
{
return; return;
}
for (var i = 0; i < 10; i++) for (var i = 0; i < 10; i++)
{ {
if (KeyboardButtonCode.D1 + i > KeyboardButtonCode.D0) if (KeyboardButtonCode.D1 + i > KeyboardButtonCode.D0)
{
break; break;
}
if (!_inputHandler.IsKeyJustPressed(KeyboardButtonCode.D1 + i)) if (!_inputHandler.IsKeyJustPressed(KeyboardButtonCode.D1 + i))
{
continue; continue;
}
_weaponController.SelectWeapon(i); WeaponController.SelectWeapon(i);
break; break;
} }
if (_inputHandler.IsKeyJustPressed(KeyboardButtonCode.Space)) if (_inputHandler.IsKeyJustPressed(KeyboardButtonCode.Space))
{ {
if (!_weaponController.TryShoot()) if (!WeaponController.TryShoot())
{
return; return;
}
var position = _camera.GameObject.Transform.GetFullTranslation(); var position = Camera.GameObject.Transform.GetFullTranslation();
var forward = (_camera.Forward - position).Normalized(); var forward = (Camera.Forward - position).Normalized();
var right = Vector3.Cross(forward, Vector3.UnitZ).Normalized(); var right = Vector3.Cross(forward, Vector3.UnitZ).Normalized();
var collisionManager = EngineUtil.SceneManager.CurrentScene!.FindFirstComponent<CollisionManagerComponent>(); var collisionManager = EngineUtil.SceneManager.CurrentScene!.FindFirstComponent<CollisionManagerComponent>();
var offsets = _weaponController.WeaponData.ShootPattern.GetShootPattern(forward, Vector3.UnitZ, right); var offsets = WeaponController.WeaponData.ShootPattern.GetShootPattern(forward, Vector3.UnitZ, right);
foreach (var offset in offsets) foreach (var offset in offsets)
{ {
var direction = forward + offset; var direction = forward + offset;
if (!collisionManager!.Raycast(position, direction, ["enemy", "wall"], out var result)) if (!collisionManager!.Raycast(position, direction, ["enemy", "wall"], out var result))
{
continue; continue;
}
var enemyController = result.HitObject.GetComponent<EnemyController>(); var enemyController = result.HitObject.GetComponent<EnemyController>();
enemyController?.HealthController.TakeDamage(_weaponController.WeaponData.Damage); enemyController?.HealthController.TakeDamage(WeaponController.WeaponData.Damage);
} }
} }
} }

View File

@@ -7,6 +7,7 @@ public class TimerController : Engine.Scene.Component.Component
public event Action? OnFinished; public event Action? OnFinished;
private readonly TickableTimer _tickableTimer = new(60 + 10); private readonly TickableTimer _tickableTimer = new(60 + 10);
private TimerView _timerView = null!; private TimerView _timerView = null!;
public override void Awake() public override void Awake()

View File

@@ -23,7 +23,9 @@ public class WeaponController : Engine.Scene.Component.Component
public bool TryShoot() public bool TryShoot()
{ {
if (_weaponModel.SelectedWeapon.Ammo <= 0) if (_weaponModel.SelectedWeapon.Ammo <= 0)
{
return false; return false;
}
_weaponModel.SelectedWeapon.Ammo--; _weaponModel.SelectedWeapon.Ammo--;
@@ -41,7 +43,9 @@ public class WeaponController : Engine.Scene.Component.Component
public void AddWeapon(WeaponData parWeaponData) public void AddWeapon(WeaponData parWeaponData)
{ {
if (_weaponModel.Weapons.Contains(parWeaponData)) if (_weaponModel.Weapons.Contains(parWeaponData))
{
return; return;
}
_weaponModel.Weapons.Add(parWeaponData); _weaponModel.Weapons.Add(parWeaponData);
} }
@@ -49,15 +53,21 @@ public class WeaponController : Engine.Scene.Component.Component
public void AddOrMergeWeapon(WeaponData parWeaponData) public void AddOrMergeWeapon(WeaponData parWeaponData)
{ {
if (!_weaponModel.Weapons.Contains(parWeaponData)) if (!_weaponModel.Weapons.Contains(parWeaponData))
{
_weaponModel.Weapons.Add(parWeaponData); _weaponModel.Weapons.Add(parWeaponData);
}
else else
{
_weaponModel.Weapons.First(parData => parData.Id == parWeaponData.Id).Ammo += parWeaponData.Ammo; _weaponModel.Weapons.First(parData => parData.Id == parWeaponData.Id).Ammo += parWeaponData.Ammo;
} }
}
public void RemoveWeapon(int parIndex) public void RemoveWeapon(int parIndex)
{ {
if (parIndex <= 0 || parIndex >= _weaponModel.Weapons.Count) if (parIndex <= 0 || parIndex >= _weaponModel.Weapons.Count)
{
return; return;
}
var newSelectedIndex = parIndex >= _weaponModel.SelectedWeaponIndex ? _weaponModel.SelectedWeaponIndex : 0; var newSelectedIndex = parIndex >= _weaponModel.SelectedWeaponIndex ? _weaponModel.SelectedWeaponIndex : 0;
@@ -68,20 +78,19 @@ public class WeaponController : Engine.Scene.Component.Component
public void SelectWeapon(int parIndex) public void SelectWeapon(int parIndex)
{ {
if (parIndex >= _weaponModel.Weapons.Count) if (parIndex >= _weaponModel.Weapons.Count)
{
return; return;
}
_weaponModel.SelectedWeaponIndex = parIndex; _weaponModel.SelectedWeaponIndex = parIndex;
} }
// public WeaponData? FindWeapon(string parId)
// {
// return _weaponModel.Weapons.FirstOrDefault(parData => parData.Id == parId);
// }
private void WeaponSelected(WeaponData? parOldWeapon, WeaponData parNewWeapon) private void WeaponSelected(WeaponData? parOldWeapon, WeaponData parNewWeapon)
{ {
if (parOldWeapon != null) if (parOldWeapon != null)
{
parOldWeapon.OnAmmoChanged -= _weaponView.UpdateAmmoView; parOldWeapon.OnAmmoChanged -= _weaponView.UpdateAmmoView;
}
parNewWeapon.OnAmmoChanged += _weaponView.UpdateAmmoView; parNewWeapon.OnAmmoChanged += _weaponView.UpdateAmmoView;
_weaponView.UpdateView(parNewWeapon); _weaponView.UpdateView(parNewWeapon);

View File

@@ -12,9 +12,10 @@ public class WeaponView : Engine.Scene.Component.Component
private readonly TextRenderer _weaponAmmo; private readonly TextRenderer _weaponAmmo;
private readonly Box2DRenderer _weaponSprite; private readonly Box2DRenderer _weaponSprite;
private AnimationPlayer<Texture>? _weaponFireAnimation;
private Texture? _idleTexture; private Texture? _idleTexture;
private AnimationPlayer<Texture>? _weaponFireAnimation;
public WeaponView(TextRenderer parWeaponName, TextRenderer parWeaponAmmo, Box2DRenderer parWeaponSprite) public WeaponView(TextRenderer parWeaponName, TextRenderer parWeaponAmmo, Box2DRenderer parWeaponSprite)
{ {
_weaponName = parWeaponName; _weaponName = parWeaponName;

View File

@@ -16,13 +16,17 @@ public class ColliderForceFieldComponent : Engine.Scene.Component.Component
{ {
var rigidbody = parCollider.GameObject.GetComponent<RigidbodyComponent>(); var rigidbody = parCollider.GameObject.GetComponent<RigidbodyComponent>();
if (rigidbody == null) if (rigidbody == null)
{
return; return;
}
var normal = _collider.Collider.GetCollisionNormal(parCollider.Collider); var normal = _collider.Collider.GetCollisionNormal(parCollider.Collider);
var speedAlongNormal = Vector3.Dot(rigidbody.Velocity, normal); var speedAlongNormal = Vector3.Dot(rigidbody.Velocity, normal);
if (speedAlongNormal > 0) if (speedAlongNormal > 0)
{
return; return;
}
rigidbody.AddVelocity(-normal * (speedAlongNormal * 1.75f)); rigidbody.Velocity -= normal * (speedAlongNormal * 1.75f);
} }
} }

View File

@@ -30,19 +30,27 @@ public class CollisionManagerComponent : Engine.Scene.Component.Component
!colliderA.ColliderGroups.Overlaps(colliderB.ExcludeColliderCollideGroups); !colliderA.ColliderGroups.Overlaps(colliderB.ExcludeColliderCollideGroups);
if (!canCollideAB && !canCollideBA) if (!canCollideAB && !canCollideBA)
{
continue; continue;
}
if (!colliderA.Collider.Intersects(colliderB.Collider)) if (!colliderA.Collider.Intersects(colliderB.Collider))
{
continue; continue;
}
if (canCollideAB) if (canCollideAB)
{
colliderA.CollideWith(colliderB); colliderA.CollideWith(colliderB);
}
if (canCollideBA) if (canCollideBA)
{
colliderB.CollideWith(colliderA); colliderB.CollideWith(colliderA);
} }
} }
} }
}
public bool Raycast(Vector3 parStart, Vector3 parDirection, HashSet<string> parColliderGroups, public bool Raycast(Vector3 parStart, Vector3 parDirection, HashSet<string> parColliderGroups,
[MaybeNullWhen(false)] out RaycastResult parResult) [MaybeNullWhen(false)] out RaycastResult parResult)
@@ -56,14 +64,20 @@ public class CollisionManagerComponent : Engine.Scene.Component.Component
foreach (var collider in _colliders) foreach (var collider in _colliders)
{ {
if (!collider.ColliderGroups.Overlaps(parColliderGroups)) if (!collider.ColliderGroups.Overlaps(parColliderGroups))
{
continue; continue;
}
if (!RaycastAABB(start, direction, collider.Collider, out var hitPoint, out var normal)) if (!RaycastAABB(start, direction, collider.Collider, out var hitPoint, out var normal))
{
continue; continue;
}
var distance = (start - hitPoint).Length; var distance = (start - hitPoint).Length;
if (distance > closestDistance) if (distance > closestDistance)
{
continue; continue;
}
closestDistance = distance; closestDistance = distance;
@@ -128,36 +142,46 @@ public class CollisionManagerComponent : Engine.Scene.Component.Component
for (var i = 0; i < 3; i++) for (var i = 0; i < 3; i++)
{ {
if (quadrant[i] != MIDDLE && parDirection[i] != 0.0f) if (quadrant[i] != MIDDLE && parDirection[i] != 0.0f)
{
maxT[i] = (candidatePlane[i] - parOrigin[i]) / parDirection[i]; maxT[i] = (candidatePlane[i] - parOrigin[i]) / parDirection[i];
}
else else
{
maxT[i] = -1.0f; maxT[i] = -1.0f;
} }
}
// Get largest of the maxT's for final choice of intersection // Get largest of the maxT's for final choice of intersection
var whichPlane = 0; var whichPlane = 0;
for (var i = 1; i < 3; i++) for (var i = 1; i < 3; i++)
{ {
if (maxT[whichPlane] < maxT[i]) if (maxT[whichPlane] < maxT[i])
{
whichPlane = i; whichPlane = i;
} }
}
// Check final candidate actually inside box // Check final candidate actually inside box
if (maxT[whichPlane] < 0.0f) if (maxT[whichPlane] < 0.0f)
{
return false; return false;
}
for (var i = 0; i < 3; i++) for (var i = 0; i < 3; i++)
{ {
if (whichPlane != i) if (whichPlane != i)
{ {
parHitPoint[i] = parOrigin[i] + maxT[whichPlane] * parDirection[i]; parHitPoint[i] = parOrigin[i] + (maxT[whichPlane] * parDirection[i]);
if (parHitPoint[i] < minB[i] || parHitPoint[i] > maxB[i]) if (parHitPoint[i] < minB[i] || parHitPoint[i] > maxB[i])
{
return false; return false;
} }
}
else else
{ {
parHitPoint[i] = candidatePlane[i]; parHitPoint[i] = candidatePlane[i];
// Calculate normal for the intersection plane // Calculate normal for the intersection plane
parHitNormal[i] = (quadrant[i] == LEFT) ? -1.0f : 1.0f; parHitNormal[i] = quadrant[i] == LEFT ? -1.0f : 1.0f;
} }
} }

View File

@@ -18,6 +18,6 @@ public class DragComponent : Engine.Scene.Component.Component
public override void Update(double parDeltaTime) public override void Update(double parDeltaTime)
{ {
_rigidbody.AddForce(-Drag * (_rigidbody.Velocity * Multiplier)); _rigidbody.Force -= Drag * (_rigidbody.Velocity * Multiplier);
} }
} }

View File

@@ -4,6 +4,8 @@ namespace DoomDeathmatch.Component.Physics;
public class GravityComponent : Engine.Scene.Component.Component public class GravityComponent : Engine.Scene.Component.Component
{ {
public bool IsInAir { get; private set; }
public float Strength { get; set; } = 10.0f; public float Strength { get; set; } = 10.0f;
public Vector3 Direction public Vector3 Direction
@@ -14,7 +16,6 @@ public class GravityComponent : Engine.Scene.Component.Component
public float Floor { get; set; } = 5.0f; public float Floor { get; set; } = 5.0f;
public bool IsInAir { get; private set; } = false;
private RigidbodyComponent _rigidbody = null!; private RigidbodyComponent _rigidbody = null!;
private Vector3 _direction = -Vector3.UnitZ; private Vector3 _direction = -Vector3.UnitZ;
@@ -34,13 +35,15 @@ public class GravityComponent : Engine.Scene.Component.Component
IsInAir = false; IsInAir = false;
var velocityAlongDirection = Vector3.Dot(_rigidbody.Velocity, Direction); var velocityAlongDirection = Vector3.Dot(_rigidbody.Velocity, Direction);
if (velocityAlongDirection > 0) if (velocityAlongDirection > 0)
_rigidbody.AddVelocity(-velocityAlongDirection * Direction); {
_rigidbody.Velocity -= velocityAlongDirection * Direction;
}
return; return;
} }
IsInAir = true; IsInAir = true;
var gravity = Strength * Direction; var gravity = Strength * Direction;
_rigidbody.AddForce(gravity); _rigidbody.Force += gravity;
} }
} }

View File

@@ -4,35 +4,50 @@ namespace DoomDeathmatch.Component.Physics;
public class RigidbodyComponent : Engine.Scene.Component.Component public class RigidbodyComponent : Engine.Scene.Component.Component
{ {
public Vector3 Velocity { get; private set; } = Vector3.Zero;
public Vector3 Acceleration { get; private set; } = Vector3.Zero;
public Vector3 Force { get; private set; } = Vector3.Zero;
public float Mass { get; set; } = 1.0f; public float Mass { get; set; } = 1.0f;
public bool IsStatic { get; set; } = false; public bool IsStatic { get; set; } = false;
public void AddForce(Vector3 parForce) public Vector3 Force
{
get => _force;
set
{ {
if (IsStatic) if (IsStatic)
{
return; return;
Force += parForce;
} }
public void AddVelocity(Vector3 parVelocity) _force = value;
}
}
public Vector3 Velocity
{
get => _velocity;
set
{ {
if (IsStatic) if (IsStatic)
{
return; return;
Velocity += parVelocity;
} }
_velocity = value;
}
}
private Vector3 _force = Vector3.Zero;
private Vector3 _velocity = Vector3.Zero;
private Vector3 _acceleration = Vector3.Zero;
public override void Update(double parDeltaTime) public override void Update(double parDeltaTime)
{ {
if (IsStatic) if (IsStatic)
{
return; return;
}
Acceleration = Force / Mass; _acceleration = Force / Mass;
Velocity += Acceleration * (float)parDeltaTime; Velocity += _acceleration * (float)parDeltaTime;
GameObject.Transform.Translation += Velocity * (float)parDeltaTime; GameObject.Transform.Translation += Velocity * (float)parDeltaTime;
Force = Vector3.Zero; Force = Vector3.Zero;

View File

@@ -5,18 +5,16 @@ namespace DoomDeathmatch.Component.UI;
public class SelectorComponent : Engine.Scene.Component.Component public class SelectorComponent : Engine.Scene.Component.Component
{ {
public List<UiComponent> Children => _children; public event Action<UiComponent>? OnSelect;
public KeyboardButtonCode SelectKey { get; set; } = KeyboardButtonCode.Space;
public List<UiComponent> Children { get; } = [];
public KeyboardButtonCode NextKey { get; set; } = KeyboardButtonCode.Down; public KeyboardButtonCode NextKey { get; set; } = KeyboardButtonCode.Down;
public KeyboardButtonCode PrevKey { get; set; } = KeyboardButtonCode.Up; public KeyboardButtonCode PrevKey { get; set; } = KeyboardButtonCode.Up;
public KeyboardButtonCode SelectKey { get; set; } = KeyboardButtonCode.Space;
public event Action<UiComponent>? OnSelect;
private readonly IInputHandler _inputHandler = EngineUtil.InputHandler; private readonly IInputHandler _inputHandler = EngineUtil.InputHandler;
private readonly List<UiComponent> _children = []; private int _selectedIndex;
private int _selectedIndex = 0;
public override void Start() public override void Start()
{ {
@@ -31,13 +29,17 @@ public class SelectorComponent : Engine.Scene.Component.Component
public override void Update(double parDeltaTime) public override void Update(double parDeltaTime)
{ {
if (_inputHandler.IsKeyJustPressed(SelectKey)) if (_inputHandler.IsKeyJustPressed(SelectKey))
_children[_selectedIndex].InvokeClick(); {
Children[_selectedIndex].InvokeClick();
}
if (_inputHandler.IsKeyJustPressed(NextKey)) if (_inputHandler.IsKeyJustPressed(NextKey))
{ {
_selectedIndex++; _selectedIndex++;
if (_selectedIndex >= Children.Count) if (_selectedIndex >= Children.Count)
{
_selectedIndex = 0; _selectedIndex = 0;
}
SelectionChanged(); SelectionChanged();
} }
@@ -46,7 +48,9 @@ public class SelectorComponent : Engine.Scene.Component.Component
{ {
_selectedIndex--; _selectedIndex--;
if (_selectedIndex < 0) if (_selectedIndex < 0)
{
_selectedIndex = Children.Count - 1; _selectedIndex = Children.Count - 1;
}
SelectionChanged(); SelectionChanged();
} }
@@ -77,10 +81,14 @@ public class SelectorComponent : Engine.Scene.Component.Component
{ {
var index = Children.IndexOf(parComponent); var index = Children.IndexOf(parComponent);
if (index < 0) if (index < 0)
{
index = 0; index = 0;
}
if (index == _selectedIndex) if (index == _selectedIndex)
{
return; return;
}
_selectedIndex = index; _selectedIndex = index;

View File

@@ -5,25 +5,23 @@ namespace DoomDeathmatch.Component.UI;
public class StackComponent : UiContainerComponent public class StackComponent : UiContainerComponent
{ {
public List<UiComponent> Children => _children; public List<UiComponent> Children { get; } = [];
public Orientation Orientation { get; set; } = Orientation.Vertical; public Orientation Orientation { get; set; } = Orientation.Vertical;
private readonly List<UiComponent> _children = [];
public override void Update(double parDeltaTime) public override void Update(double parDeltaTime)
{ {
base.Update(parDeltaTime); base.Update(parDeltaTime);
var size = GameObject.Transform.Size.Xy; var size = GameObject.Transform.Size.Xy;
var count = _children.Count; var count = Children.Count;
size *= Orientation == Orientation.Horizontal ? new Vector2(1, 0) : new Vector2(0, 1); size *= Orientation == Orientation.Horizontal ? new Vector2(1, 0) : new Vector2(0, 1);
var offset = new Vector2(-size.X / 2 + size.X / count / 2, -size.Y / 2 + size.Y / count / 2); var offset = new Vector2((-size.X / 2) + (size.X / count / 2), (-size.Y / 2) + (size.Y / count / 2));
for (var i = count - 1; i >= 0; i--) for (var i = count - 1; i >= 0; i--)
{ {
var child = _children[i]; var child = Children[i];
child.Offset = offset; child.Offset = offset;
offset += size / count; offset += size / count;

View File

@@ -9,7 +9,6 @@ public class TextAlignComponent : Engine.Scene.Component.Component
public Align Alignment { get; set; } = Align.Left; public Align Alignment { get; set; } = Align.Left;
private TextRenderer _textRenderer = null!; private TextRenderer _textRenderer = null!;
private string? _cachedText; private string? _cachedText;
public override void Awake() public override void Awake()
@@ -20,10 +19,14 @@ public class TextAlignComponent : Engine.Scene.Component.Component
public override void Update(double parDeltaTime) public override void Update(double parDeltaTime)
{ {
if (_textRenderer.Text == null) if (_textRenderer.Text == null)
{
return; return;
}
if (_cachedText == _textRenderer.Text) if (_cachedText == _textRenderer.Text)
{
return; return;
}
_cachedText = _textRenderer.Text; _cachedText = _textRenderer.Text;
var font = _textRenderer.Font; var font = _textRenderer.Font;

View File

@@ -12,40 +12,37 @@ public class TextInputComponent : Engine.Scene.Component.Component
public float InputDelay public float InputDelay
{ {
get => _inputDelay; get => (float)_inputTimer.TotalTime;
set set => _inputTimer.TotalTime = value;
{
_inputDelay = value;
_inputTimer.TotalTime = value;
} }
}
private readonly IInputHandler _inputHandler = EngineUtil.InputHandler;
private readonly KeyboardButtonCode[] _acceptedKeys = private readonly KeyboardButtonCode[] _acceptedKeys =
KeyboardButtonCodeHelper.GetAllPrintableKeys().Append(KeyboardButtonCode.Backspace).ToArray(); KeyboardButtonCodeHelper.GetAllPrintableKeys().Append(KeyboardButtonCode.Backspace).ToArray();
private readonly IInputHandler _inputHandler = EngineUtil.InputHandler;
private readonly TickableTimer _inputTimer; private readonly TickableTimer _inputTimer;
private float _inputDelay = 0;
private readonly HashSet<KeyboardButtonCode> _lastKeys = []; private readonly HashSet<KeyboardButtonCode> _lastKeys = [];
public TextInputComponent(float parInputDelay = 0.2f) public TextInputComponent(float parInputDelay = 0.2f)
{ {
_inputDelay = parInputDelay; _inputTimer = new TickableTimer(parInputDelay) { CurrentTime = 0 };
_inputTimer = new TickableTimer(_inputDelay) { CurrentTime = 0 };
} }
public override void Update(double parDeltaTime) public override void Update(double parDeltaTime)
{ {
if (!IsActive) if (!IsActive)
{
return; return;
}
_inputTimer.Update(parDeltaTime); _inputTimer.Update(parDeltaTime);
foreach (var key in _acceptedKeys) foreach (var key in _acceptedKeys)
{ {
if (_lastKeys.Contains(key) && !_inputTimer.IsFinished) if (_lastKeys.Contains(key) && !_inputTimer.IsFinished)
{
continue; continue;
}
if (_inputHandler.IsKeyPressed(key)) if (_inputHandler.IsKeyPressed(key))
{ {
@@ -69,7 +66,9 @@ public class TextInputComponent : Engine.Scene.Component.Component
} }
if (invoke) if (invoke)
{
OnInput?.Invoke(Input); OnInput?.Invoke(Input);
}
_lastKeys.Add(key); _lastKeys.Add(key);

View File

@@ -8,14 +8,14 @@ namespace DoomDeathmatch.Component.UI;
public class UiComponent : Engine.Scene.Component.Component public class UiComponent : Engine.Scene.Component.Component
{ {
public UiContainerComponent? Container { get; set; }
public Anchor Anchor { get; set; } = Anchor.Center;
public Vector2 Offset { get; set; } = Vector2.Zero;
public Anchor Center { get; set; } = Anchor.Center;
public event Action<UiComponent>? OnClick; public event Action<UiComponent>? OnClick;
public event Action<UiComponent>? OnMouseOver; public event Action<UiComponent>? OnMouseOver;
public UiContainerComponent? Container { get; set; }
public Anchor Center { get; set; } = Anchor.Center;
public Anchor Anchor { get; set; } = Anchor.Center;
public Vector2 Offset { get; set; } = Vector2.Zero;
private readonly IInputHandler _inputHandler = EngineUtil.InputHandler; private readonly IInputHandler _inputHandler = EngineUtil.InputHandler;
public override void Update(double parDeltaTime) public override void Update(double parDeltaTime)

View File

@@ -7,11 +7,11 @@ namespace DoomDeathmatch.Component.UI;
public class UiContainerComponent : UiComponent public class UiContainerComponent : UiComponent
{ {
private readonly IInputHandler _inputHandler = EngineUtil.InputHandler;
public Camera? Camera { get; set; } public Camera? Camera { get; set; }
public Vector3 MousePosition { get; private set; } public Vector3 MousePosition { get; private set; }
private readonly IInputHandler _inputHandler = EngineUtil.InputHandler;
public override void Update(double parDeltaTime) public override void Update(double parDeltaTime)
{ {
base.Update(parDeltaTime); base.Update(parDeltaTime);

View File

@@ -21,11 +21,15 @@ public class BillboardComponent : Engine.Scene.Component.Component
var forward = targetPosition - currentPosition; var forward = targetPosition - currentPosition;
forward -= Vector3.Dot(forward, Up) * Up; forward -= Vector3.Dot(forward, Up) * Up;
if (forward.LengthSquared > 0) if (forward.LengthSquared > 0)
{
forward.Normalize(); forward.Normalize();
}
var right = Vector3.Cross(Up, forward); var right = Vector3.Cross(Up, forward);
if (right.LengthSquared > 0) if (right.LengthSquared > 0)
{
right.Normalize(); right.Normalize();
}
var recalculatedUp = Vector3.Cross(forward, right).Normalized(); var recalculatedUp = Vector3.Cross(forward, right).Normalized();

View File

@@ -5,21 +5,29 @@ namespace DoomDeathmatch.Component.Util;
public class CopySizeComponent : Engine.Scene.Component.Component public class CopySizeComponent : Engine.Scene.Component.Component
{ {
public Vector3 Multiplier { get; set; } = Vector3.One;
public Transform? Target { get; set; } public Transform? Target { get; set; }
public Vector3 Multiplier { get; set; } = Vector3.One;
public override void Update(double parDeltaTime) public override void Update(double parDeltaTime)
{ {
if (Target == null) if (Target == null)
{
return; return;
}
if (Multiplier.X != 0) if (Multiplier.X != 0)
{
GameObject.Transform.Size.X = Target.Size.X * Multiplier.X; GameObject.Transform.Size.X = Target.Size.X * Multiplier.X;
}
if (Multiplier.Y != 0) if (Multiplier.Y != 0)
{
GameObject.Transform.Size.Y = Target.Size.Y * Multiplier.Y; GameObject.Transform.Size.Y = Target.Size.Y * Multiplier.Y;
}
if (Multiplier.Z != 0) if (Multiplier.Z != 0)
{
GameObject.Transform.Size.Z = Target.Size.Z * Multiplier.Z; GameObject.Transform.Size.Z = Target.Size.Z * Multiplier.Z;
} }
} }
}

View File

@@ -10,6 +10,7 @@ public class PlayerMovementComponent : Engine.Scene.Component.Component
public float RotationSpeed { get; set; } = 110.0f; public float RotationSpeed { get; set; } = 110.0f;
private readonly IInputHandler _inputHandler = EngineUtil.InputHandler; private readonly IInputHandler _inputHandler = EngineUtil.InputHandler;
private MovementController _movementController = null!; private MovementController _movementController = null!;
public override void Awake() public override void Awake()
@@ -25,18 +26,34 @@ public class PlayerMovementComponent : Engine.Scene.Component.Component
var rotation = 0.0f; var rotation = 0.0f;
if (_inputHandler.IsKeyPressed(KeyboardButtonCode.W)) if (_inputHandler.IsKeyPressed(KeyboardButtonCode.W))
{
movement.Y += 1; movement.Y += 1;
}
if (_inputHandler.IsKeyPressed(KeyboardButtonCode.S)) if (_inputHandler.IsKeyPressed(KeyboardButtonCode.S))
{
movement.Y -= 1; movement.Y -= 1;
}
if (_inputHandler.IsKeyPressed(KeyboardButtonCode.D)) if (_inputHandler.IsKeyPressed(KeyboardButtonCode.D))
{
movement.X += 1; movement.X += 1;
}
if (_inputHandler.IsKeyPressed(KeyboardButtonCode.A)) if (_inputHandler.IsKeyPressed(KeyboardButtonCode.A))
{
movement.X -= 1; movement.X -= 1;
}
if (_inputHandler.IsKeyPressed(KeyboardButtonCode.Q)) if (_inputHandler.IsKeyPressed(KeyboardButtonCode.Q))
{
rotation += RotationSpeed; rotation += RotationSpeed;
}
if (_inputHandler.IsKeyPressed(KeyboardButtonCode.E)) if (_inputHandler.IsKeyPressed(KeyboardButtonCode.E))
{
rotation -= RotationSpeed; rotation -= RotationSpeed;
}
if (movement.LengthSquared > 0) if (movement.LengthSquared > 0)
{ {

View File

@@ -41,7 +41,9 @@ public static class GameOverScene
nextUi.OnClick += _ => nextUi.OnClick += _ =>
{ {
if (string.IsNullOrEmpty(nameInputComponent.Input)) if (string.IsNullOrEmpty(nameInputComponent.Input))
{
return; return;
}
SubmitScore(nameInputComponent.Input, parScore); SubmitScore(nameInputComponent.Input, parScore);
EngineUtil.SceneManager.TransitionTo(MainScene.Create); EngineUtil.SceneManager.TransitionTo(MainScene.Create);

View File

@@ -100,7 +100,7 @@ public static class MainScene
backUi.OnClick += _ => parMenuController.SelectMenuItem("main"); backUi.OnClick += _ => parMenuController.SelectMenuItem("main");
var (stackObject, stack) = UiUtil.CreateStackUi(parScene, var (stackObject, stack) = UiUtil.CreateStackUi(parScene,
new StackComponent { Offset = new Vector2(0, -1.5f), Container = parUiContainer, Children = { } }); new StackComponent { Offset = new Vector2(0, -1.5f), Container = parUiContainer });
stackObject.Transform.Size.Xy = new Vector2(1f, 6f); stackObject.Transform.Size.Xy = new Vector2(1f, 6f);
var leadersTable = CreateLeadersTable(parScene, parUiContainer, EngineUtil.DataFolder); var leadersTable = CreateLeadersTable(parScene, parUiContainer, EngineUtil.DataFolder);
@@ -134,8 +134,10 @@ public static class MainScene
{ {
leadersTableList.Add(CreateLeadersRow(parScene, parUiContainer, row.Name, row.Score.ToString())); leadersTableList.Add(CreateLeadersRow(parScene, parUiContainer, row.Name, row.Score.ToString()));
if (++rows >= 5) if (++rows >= 5)
{
break; break;
} }
}
return leadersTableList; return leadersTableList;
} }

View File

@@ -63,7 +63,7 @@ public static class PlayScene
new ScoreController(), new ScoreController(),
playScoreView, playScoreView,
new CollisionManagerComponent(), new CollisionManagerComponent()
]); ]);
@@ -186,7 +186,7 @@ public static class PlayScene
} }
var size = max - min; var size = max - min;
var offset = min + size / 2; var offset = min + (size / 2);
var colliderObject = GameObjectUtil.CreateColliderForceField( var colliderObject = GameObjectUtil.CreateColliderForceField(
parScene, parScene,

View File

@@ -57,7 +57,7 @@ public static class EnemyPrefab
var enemyVisualObject = GameObjectUtil.CreateGameObject(parScene, var enemyVisualObject = GameObjectUtil.CreateGameObject(parScene,
new Transform { Translation = new Vector3(0, 0f, 1f), Size = new Vector3(1, 2, 1) }, [ new Transform { Translation = new Vector3(0, 0f, 1f), Size = new Vector3(1, 2, 1) }, [
enemyBox2DRenderer, enemyBox2DRenderer,
new BillboardComponent(), new BillboardComponent()
] ]
); );
@@ -72,7 +72,7 @@ public static class EnemyPrefab
Vector3 parVelocity, float parDamage, Transform? parBillboardTarget = null) Vector3 parVelocity, float parDamage, Transform? parBillboardTarget = null)
{ {
var rigidbodyComponent = new RigidbodyComponent(); var rigidbodyComponent = new RigidbodyComponent();
rigidbodyComponent.AddVelocity(parVelocity); rigidbodyComponent.Velocity += parVelocity;
var fireballObject = GameObjectUtil.CreateGameObject(parScene, var fireballObject = GameObjectUtil.CreateGameObject(parScene,
new Transform { Translation = parPosition, Size = new Vector3(0.5f) }, new Transform { Translation = parPosition, Size = new Vector3(0.5f) },

View File

@@ -37,7 +37,7 @@ public static class PlayerPrefab
parWeaponView, parWeaponView,
new HealthController(), new HealthController(),
parHealthView, parHealthView
]); ]);
parScene.AddChild(playerObject, perspectiveCameraObject); parScene.AddChild(playerObject, perspectiveCameraObject);

View File

@@ -5,29 +5,25 @@ namespace DoomDeathmatch.Script;
public class AnimationPlayer<T>(float parInterval) : IUpdate 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; public event Action? OnFinish;
public event Action<T>? OnFrameChanged;
public bool IsPlaying { get; private set; }
public List<T> Frames { get; init; } = [];
public int NextFrame { get; private set; }
private readonly TickableTimer _timer = new(parInterval); private readonly TickableTimer _timer = new(parInterval);
public void Start()
{
Reset();
IsPlaying = true;
}
public void Update(double parDeltaTime) public void Update(double parDeltaTime)
{ {
if (!IsPlaying) if (!IsPlaying)
{
return; return;
}
_timer.Update(parDeltaTime); _timer.Update(parDeltaTime);
if (_timer.CurrentTime < _timer.TotalTime * (1.0f - 1.0f / Frames.Count * NextFrame)) if (_timer.CurrentTime < _timer.TotalTime * (1.0f - (1.0f / Frames.Count * NextFrame)))
{ {
OnFrameChanged?.Invoke(Frames[NextFrame]); OnFrameChanged?.Invoke(Frames[NextFrame]);
NextFrame++; NextFrame++;
@@ -41,6 +37,12 @@ public class AnimationPlayer<T>(float parInterval) : IUpdate
} }
} }
public void Start()
{
Reset();
IsPlaying = true;
}
public void Reset() public void Reset()
{ {
_timer.Reset(); _timer.Reset();

View File

@@ -4,11 +4,11 @@ namespace DoomDeathmatch.Script.Collision;
public class AABBCollider public class AABBCollider
{ {
public Vector3 Size { get; set; }
public Vector3 Position { get; set; } public Vector3 Position { get; set; }
public Vector3 Size { get; set; }
public Vector3 Max => Position + Size / 2; public Vector3 Min => Position - (Size / 2);
public Vector3 Min => Position - Size / 2; public Vector3 Max => Position + (Size / 2);
public bool Intersects(AABBCollider parCollider) public bool Intersects(AABBCollider parCollider)
{ {
@@ -27,9 +27,9 @@ public class AABBCollider
var diff = parOther.Position - Position; var diff = parOther.Position - Position;
// Calculate penetration depths for each axis // Calculate penetration depths for each axis
var penX = (Size.X / 2 + parOther.Size.X / 2) - Math.Abs(diff.X); var penX = (Size.X / 2) + (parOther.Size.X / 2) - Math.Abs(diff.X);
var penY = (Size.Y / 2 + parOther.Size.Y / 2) - Math.Abs(diff.Y); var penY = (Size.Y / 2) + (parOther.Size.Y / 2) - Math.Abs(diff.Y);
var penZ = (Size.Z / 2 + parOther.Size.Z / 2) - Math.Abs(diff.Z); var penZ = (Size.Z / 2) + (parOther.Size.Z / 2) - Math.Abs(diff.Z);
// Use the axis with the smallest penetration // Use the axis with the smallest penetration
if (penX < penY && penX < penZ) if (penX < penY && penX < penZ)

View File

@@ -8,5 +8,6 @@ public class RaycastResult
public float Distance { get; init; } public float Distance { get; init; }
public Vector3 HitPoint { get; init; } public Vector3 HitPoint { get; init; }
public Vector3 Normal { get; init; } public Vector3 Normal { get; init; }
public GameObject HitObject { get; init; } public GameObject HitObject { get; init; }
} }

View File

@@ -5,6 +5,7 @@ namespace DoomDeathmatch.Script.Condition;
public class TickableTimerCondition : ICondition public class TickableTimerCondition : ICondition
{ {
public event Action? OnTrue; public event Action? OnTrue;
public bool IsTrue => _timer.IsFinished; public bool IsTrue => _timer.IsFinished;
private readonly TickableTimer _timer; private readonly TickableTimer _timer;

View File

@@ -19,7 +19,9 @@ public abstract class CooldownAttackBehavior(
if (CanAttack()) if (CanAttack())
{ {
if (!_tickableTimer.IsFinished) if (!_tickableTimer.IsFinished)
{
return false; return false;
}
var result = ActivateAttack(); var result = ActivateAttack();

View File

@@ -63,10 +63,13 @@ public class EnemyData
public string Id { get; private init; } = ""; public string Id { get; private init; } = "";
public string Name { get; private init; } = ""; public string Name { get; private init; } = "";
public string Texture { get; private init; } = ""; public string Texture { get; private init; } = "";
public float BaseHealth { get; private init; } public float BaseHealth { get; private init; }
public int BaseScore { get; private init; } public int BaseScore { get; private init; }
public float BaseSpeed { get; private init; } public float BaseSpeed { get; private init; }
public IMovementBehavior MovementBehavior { get; private init; } public IMovementBehavior MovementBehavior { get; private init; }
public IAttackBehaviorCreator AttackBehaviorCreator { get; private init; } public IAttackBehaviorCreator AttackBehaviorCreator { get; private init; }

View File

@@ -7,6 +7,6 @@ public class FollowPlayerMovementBehavior(float parRadius) : IMovementBehavior
public Vector3 GetNextPosition(Vector3 parPosition, Vector3 parPlayerPosition) public Vector3 GetNextPosition(Vector3 parPosition, Vector3 parPlayerPosition)
{ {
var direction = (parPosition - parPlayerPosition).Normalized(); var direction = (parPosition - parPlayerPosition).Normalized();
return parPlayerPosition + parRadius * direction; return parPlayerPosition + (parRadius * direction);
} }
} }

View File

@@ -2,6 +2,24 @@
public class HealthModel public class HealthModel
{ {
public event Action<HealthModel>? HealthChanged;
public float Health
{
get => _health;
set
{
value = Math.Clamp(value, 0, MaxHealth);
if (Math.Abs(_health - value) < float.Epsilon)
{
return;
}
_health = value;
HealthChanged?.Invoke(this);
}
}
public float MaxHealth public float MaxHealth
{ {
get => _maxHealth; get => _maxHealth;
@@ -13,22 +31,6 @@ public class HealthModel
} }
} }
public float Health
{
get => _health;
set
{
value = Math.Clamp(value, 0, MaxHealth);
if (Math.Abs(_health - value) < float.Epsilon)
return;
_health = value;
HealthChanged?.Invoke(this);
}
}
public event Action<HealthModel>? HealthChanged;
private float _health; private float _health;
private float _maxHealth; private float _maxHealth;

View File

@@ -10,7 +10,7 @@ public class RandomFlatSpreadShootPattern(float parAngle, uint parCount) : IShoo
{ {
for (var i = 0; i < parCount; i++) for (var i = 0; i < parCount; i++)
{ {
var angle = parAngle * ((float)_random.NextDouble() * 2 - 1); var angle = parAngle * (((float)_random.NextDouble() * 2) - 1);
var delta = MathF.Tan(angle); var delta = MathF.Tan(angle);
var offset = parRight * delta; var offset = parRight * delta;

View File

@@ -13,7 +13,7 @@ public class WeaponData
FireAnimationDuration = 0.25f, FireAnimationDuration = 0.25f,
FireAnimation = FireAnimation =
{ {
"texture/pistol/fire1.png", "texture/pistol/fire2.png", "texture/pistol/fire3.png", "texture/pistol/fire4.png", "texture/pistol/fire1.png", "texture/pistol/fire2.png", "texture/pistol/fire3.png", "texture/pistol/fire4.png"
}, },
Damage = 30, Damage = 30,
ShootPattern = new LineShootPattern() ShootPattern = new LineShootPattern()
@@ -31,14 +31,11 @@ public class WeaponData
ShootPattern = new RandomFlatSpreadShootPattern(MathHelper.DegreesToRadians(10), 40) ShootPattern = new RandomFlatSpreadShootPattern(MathHelper.DegreesToRadians(10), 40)
}; };
public event Action<WeaponData>? OnAmmoChanged;
public string Id { get; private init; } = ""; public string Id { get; private init; } = "";
public string Name { get; private init; } = ""; public string Name { 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; }
public int Ammo public int Ammo
{ {
@@ -46,19 +43,34 @@ public class WeaponData
set set
{ {
if (value < 0) if (value < 0)
{
value = 0; value = 0;
}
if (value > MaxAmmo) if (value > MaxAmmo)
{
value = MaxAmmo; value = MaxAmmo;
}
if (_ammo == value) if (_ammo == value)
{
return; return;
}
_ammo = value; _ammo = value;
OnAmmoChanged?.Invoke(this); OnAmmoChanged?.Invoke(this);
} }
} }
public event Action<WeaponData>? OnAmmoChanged; public int MaxAmmo { get; }
public int Damage { get; private init; }
public string IdleTexture { get; private init; } = "";
public float FireAnimationDuration { get; private init; }
public List<string> FireAnimation { get; } = [];
public IShootPattern ShootPattern { get; private init; }
private int _ammo; private int _ammo;

View File

@@ -7,7 +7,6 @@ public class WeaponModel
public event Action<WeaponData, WeaponData>? OnWeaponSelected; public event Action<WeaponData, WeaponData>? OnWeaponSelected;
public IList<WeaponData> Weapons => _weapons; public IList<WeaponData> Weapons => _weapons;
public WeaponData SelectedWeapon => _weapons[_selectedWeaponIndex]; public WeaponData SelectedWeapon => _weapons[_selectedWeaponIndex];
public int SelectedWeaponIndex public int SelectedWeaponIndex
@@ -18,7 +17,9 @@ public class WeaponModel
value = Math.Clamp(value, 0, _weapons.Count - 1); value = Math.Clamp(value, 0, _weapons.Count - 1);
if (_selectedWeaponIndex == value) if (_selectedWeaponIndex == value)
{
return; return;
}
var oldSelectedWeapon = SelectedWeapon; var oldSelectedWeapon = SelectedWeapon;
_selectedWeaponIndex = value; _selectedWeaponIndex = value;
@@ -27,5 +28,6 @@ public class WeaponModel
} }
private readonly List<WeaponData> _weapons = [WeaponData.Pistol]; private readonly List<WeaponData> _weapons = [WeaponData.Pistol];
private int _selectedWeaponIndex = 0;
private int _selectedWeaponIndex;
} }

View File

@@ -4,7 +4,7 @@ public class WeightedRandomValueProvider<T> : IValueProvider<T>
{ {
private readonly List<(int, IValueProvider<T>)> _providers = []; private readonly List<(int, IValueProvider<T>)> _providers = [];
private readonly Random _random = new(); private readonly Random _random = new();
private readonly int _totalWeight = 0; private readonly int _totalWeight;
public WeightedRandomValueProvider(IEnumerable<(int, IValueProvider<T>)> parProviders) public WeightedRandomValueProvider(IEnumerable<(int, IValueProvider<T>)> parProviders)
{ {
@@ -15,8 +15,10 @@ public class WeightedRandomValueProvider<T> : IValueProvider<T>
} }
if (_totalWeight <= 0) if (_totalWeight <= 0)
{
throw new InvalidOperationException($"{nameof(WeightedRandomValueProvider<T>)} is empty"); throw new InvalidOperationException($"{nameof(WeightedRandomValueProvider<T>)} is empty");
} }
}
public T GetValue() public T GetValue()
{ {
@@ -25,7 +27,9 @@ public class WeightedRandomValueProvider<T> : IValueProvider<T>
foreach (var (weight, provider) in _providers) foreach (var (weight, provider) in _providers)
{ {
if (random < weight) if (random < weight)
{
return provider.GetValue(); return provider.GetValue();
}
random -= weight; random -= weight;
} }

View File

@@ -7,10 +7,10 @@ namespace DoomDeathmatch.Script.Score;
[JsonSerializable(typeof(ScoreTable))] [JsonSerializable(typeof(ScoreTable))]
public class ScoreTable public class ScoreTable
{ {
private static readonly JsonSerializerOptions OPTIONS = new() { Converters = { new ScoreTableJsonConverter() } };
public List<ScoreRow> Rows { get; } = new(); public List<ScoreRow> Rows { get; } = new();
private static readonly JsonSerializerOptions OPTIONS = new() { Converters = { new ScoreTableJsonConverter() } };
public static ScoreTable LoadOrCreate(string parPath) public static ScoreTable LoadOrCreate(string parPath)
{ {
ScoreTable? table = null; ScoreTable? table = null;
@@ -26,10 +26,12 @@ public class ScoreTable
} }
if (table != null) if (table != null)
{
return table; return table;
}
table = new ScoreTable(); table = new ScoreTable();
ScoreTable.Save(table, parPath); Save(table, parPath);
return table; return table;
} }
@@ -51,7 +53,9 @@ public class ScoreTableJsonConverter : JsonConverter<ScoreTable>
{ {
var rows = JsonSerializer.Deserialize<ScoreRow[]>(ref parReader, parOptions); var rows = JsonSerializer.Deserialize<ScoreRow[]>(ref parReader, parOptions);
if (rows == null) if (rows == null)
{
return null; return null;
}
var scoreTable = new ScoreTable(); var scoreTable = new ScoreTable();
foreach (var row in rows) foreach (var row in rows)

View File

@@ -138,7 +138,9 @@ public static class UiUtil
StackComponent parStackComponent, RenderLayer? parRenderLayer = null) StackComponent parStackComponent, RenderLayer? parRenderLayer = null)
{ {
foreach (var child in parStackComponent.Children) foreach (var child in parStackComponent.Children)
{
child.Container = parStackComponent; child.Container = parStackComponent;
}
var stackObject = new GameObject { Transform = { Translation = new Vector3(0, 0, -1) } }; var stackObject = new GameObject { Transform = { Translation = new Vector3(0, 0, -1) } };
stackObject.AddComponent(parStackComponent); stackObject.AddComponent(parStackComponent);

View File

@@ -43,6 +43,7 @@
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>
<DependentUpon>ShaderResource.resx</DependentUpon> <DependentUpon>ShaderResource.resx</DependentUpon>
</Compile> </Compile>
<Compile Remove="src\Resource\ResourceHandle.cs" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -6,24 +6,20 @@ namespace Engine.Asset.Font;
public class Font public class Font
{ {
public StaticTexture AtlasTexture => _atlasTexture; public StaticTexture AtlasTexture { get; }
public Metadata.Metadata Metadata => _metadata; public Metadata.Metadata Metadata { get; }
public Vector2 UnitRange => _unitRange; public Vector2 UnitRange { get; }
private readonly StaticTexture _atlasTexture;
private readonly Metadata.Metadata _metadata;
private readonly Dictionary<int, Glyph> _glyphs = new(); private readonly Dictionary<int, Glyph> _glyphs = new();
private readonly Dictionary<int, GlyphData> _glyphData = new(); private readonly Dictionary<int, GlyphData> _glyphData = new();
private readonly Dictionary<(int, int), Kerning> _kernings = new(); private readonly Dictionary<(int, int), Kerning> _kernings = new();
private readonly Vector2 _unitRange;
public Font(StaticTexture parAtlasTexture, Metadata.Metadata parMetadata) public Font(StaticTexture parAtlasTexture, Metadata.Metadata parMetadata)
{ {
_atlasTexture = parAtlasTexture; AtlasTexture = parAtlasTexture;
_metadata = parMetadata; Metadata = parMetadata;
_unitRange = new Vector2(_metadata.Atlas.DistanceRange / _metadata.Atlas.Width, UnitRange = new Vector2(Metadata.Atlas.DistanceRange / Metadata.Atlas.Width,
_metadata.Atlas.DistanceRange / _metadata.Atlas.Height); Metadata.Atlas.DistanceRange / Metadata.Atlas.Height);
LoadGlyphs(); LoadGlyphs();
LoadKernings(); LoadKernings();
@@ -59,7 +55,7 @@ public class Font
private void LoadGlyphs() private void LoadGlyphs()
{ {
foreach (var glyph in _metadata.Glyphs) foreach (var glyph in Metadata.Glyphs)
{ {
_glyphs.Add(glyph.Unicode, glyph); _glyphs.Add(glyph.Unicode, glyph);
@@ -68,13 +64,13 @@ public class Font
continue; continue;
} }
_glyphData.Add(glyph.Unicode, new GlyphData(_metadata, glyph)); _glyphData.Add(glyph.Unicode, new GlyphData(Metadata, glyph));
} }
} }
private void LoadKernings() private void LoadKernings()
{ {
foreach (var kerning in _metadata.Kerning) foreach (var kerning in Metadata.Kerning)
{ {
_kernings.Add((kerning.Unicode1, kerning.Unicode2), kerning); _kernings.Add((kerning.Unicode1, kerning.Unicode2), kerning);
} }
@@ -93,7 +89,7 @@ public class Font
new Vector2(parGlyph.PlaneBounds!.Left, parGlyph.PlaneBounds.Bottom), new Vector2(parGlyph.PlaneBounds!.Left, parGlyph.PlaneBounds.Bottom),
new Vector2(parGlyph.PlaneBounds.Left, parGlyph.PlaneBounds.Top), new Vector2(parGlyph.PlaneBounds.Left, parGlyph.PlaneBounds.Top),
new Vector2(parGlyph.PlaneBounds.Right, parGlyph.PlaneBounds.Bottom), new Vector2(parGlyph.PlaneBounds.Right, parGlyph.PlaneBounds.Bottom),
new Vector2(parGlyph.PlaneBounds.Right, parGlyph.PlaneBounds.Top), new Vector2(parGlyph.PlaneBounds.Right, parGlyph.PlaneBounds.Top)
]; ];
UVs = UVs =
@@ -101,8 +97,7 @@ public class Font
new Vector2(parGlyph.AtlasBounds!.Left, parGlyph.AtlasBounds.Bottom) / size, new Vector2(parGlyph.AtlasBounds!.Left, parGlyph.AtlasBounds.Bottom) / size,
new Vector2(parGlyph.AtlasBounds.Left, parGlyph.AtlasBounds.Top) / size, new Vector2(parGlyph.AtlasBounds.Left, parGlyph.AtlasBounds.Top) / size,
new Vector2(parGlyph.AtlasBounds.Right, parGlyph.AtlasBounds.Bottom) / size, new Vector2(parGlyph.AtlasBounds.Right, parGlyph.AtlasBounds.Bottom) / size,
new Vector2(parGlyph.AtlasBounds.Right, parGlyph.AtlasBounds.Top) / size
new Vector2(parGlyph.AtlasBounds.Right, parGlyph.AtlasBounds.Top) / size,
]; ];
} }
} }

View File

@@ -5,8 +5,8 @@ namespace Engine.Asset.Font;
public class FontIterator : IEnumerable<FontIterator.NextGlyphData> public class FontIterator : IEnumerable<FontIterator.NextGlyphData>
{ {
public float MaxWidth => _maxWidth; public float MaxWidth { get; private set; }
public float MaxHeight => _maxHeight; public float MaxHeight { get; private set; }
private readonly Font _font; private readonly Font _font;
private readonly string _text; private readonly string _text;
@@ -17,44 +17,12 @@ public class FontIterator : IEnumerable<FontIterator.NextGlyphData>
private Vector2 _cursor = Vector2.Zero; private Vector2 _cursor = Vector2.Zero;
private Vector2 _kerning = Vector2.Zero; private Vector2 _kerning = Vector2.Zero;
private float _maxWidth;
private float _maxHeight;
public FontIterator(Font parFont, string parText) public FontIterator(Font parFont, string parText)
{ {
_font = parFont; _font = parFont;
_text = parText; _text = parText;
} }
private static bool IsLineBreak(int parCodepoint)
{
return parCodepoint == '\n';
}
private static bool IsTab(int parCodepoint)
{
return parCodepoint == '\t';
}
private void Tab()
{
var spaceGlyph = _font.GetGlyph(' ');
if (spaceGlyph == null)
return;
var missingSpaces = 4 - _lineCharCount % 4;
_cursor.X += missingSpaces * spaceGlyph.Advance;
}
private void LineBreak()
{
_kerning = Vector2.Zero;
_cursor.X = 0;
_cursor.Y += _font.Metadata.Metrics.LineHeight;
_lineCharCount = 0;
_previousCodepoint = -1;
}
public IEnumerator<NextGlyphData> GetEnumerator() public IEnumerator<NextGlyphData> GetEnumerator()
{ {
while (_currentIndex < _text.Length) while (_currentIndex < _text.Length)
@@ -97,11 +65,13 @@ public class FontIterator : IEnumerable<FontIterator.NextGlyphData>
var glyphData = _font.GetGlyphData(glyph.Unicode); var glyphData = _font.GetGlyphData(glyph.Unicode);
_maxWidth = Math.Max(_maxWidth, _cursor.X + glyph.Advance); MaxWidth = Math.Max(MaxWidth, _cursor.X + glyph.Advance);
_maxHeight = Math.Max(_maxHeight, _cursor.Y + _font.Metadata.Metrics.Ascender); MaxHeight = Math.Max(MaxHeight, _cursor.Y + _font.Metadata.Metrics.Ascender);
if (glyphData != null) if (glyphData != null)
{
yield return new NextGlyphData(glyphData.Positions, glyphData.UVs, (_cursor + _kerning) * new Vector2(1, -1)); yield return new NextGlyphData(glyphData.Positions, glyphData.UVs, (_cursor + _kerning) * new Vector2(1, -1));
}
_cursor.X += glyph.Advance; _cursor.X += glyph.Advance;
_lineCharCount++; _lineCharCount++;
@@ -115,5 +85,36 @@ public class FontIterator : IEnumerable<FontIterator.NextGlyphData>
return GetEnumerator(); return GetEnumerator();
} }
private static bool IsLineBreak(int parCodepoint)
{
return parCodepoint == '\n';
}
private static bool IsTab(int parCodepoint)
{
return parCodepoint == '\t';
}
private void Tab()
{
var spaceGlyph = _font.GetGlyph(' ');
if (spaceGlyph == null)
{
return;
}
var missingSpaces = 4 - (_lineCharCount % 4);
_cursor.X += missingSpaces * spaceGlyph.Advance;
}
private void LineBreak()
{
_kerning = Vector2.Zero;
_cursor.X = 0;
_cursor.Y += _font.Metadata.Metrics.LineHeight;
_lineCharCount = 0;
_previousCodepoint = -1;
}
public record NextGlyphData(Vector2[] Positions, Vector2[] UVs, Vector2 Offset); public record NextGlyphData(Vector2[] Positions, Vector2[] UVs, Vector2 Offset);
} }

View File

@@ -9,8 +9,6 @@ public class Image<T> where T : struct, IPixel
public int Height { get; } public int Height { get; }
public T[,] Pixels { get; } public T[,] Pixels { get; }
public T this[int parY, int parX] => Pixels[parY, parX];
public Image(int parWidth, int parHeight) public Image(int parWidth, int parHeight)
{ {
Width = parWidth; Width = parWidth;
@@ -18,6 +16,8 @@ public class Image<T> where T : struct, IPixel
Pixels = new T[parHeight, parWidth]; Pixels = new T[parHeight, parWidth];
} }
public T this[int parY, int parX] => Pixels[parY, parX];
public DynamicTexture ToDynamicTexture() public DynamicTexture ToDynamicTexture()
{ {
var texture = DynamicTexture.Create<T>(Width, Height); var texture = DynamicTexture.Create<T>(Width, Height);

View File

@@ -7,15 +7,15 @@ public class ObjMeshLoader : IMeshLoader
{ {
private static readonly ObjMeshLoader INSTANCE = new(); private static readonly ObjMeshLoader INSTANCE = new();
private ObjMeshLoader()
{
}
public static Mesh Load(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default) public static Mesh Load(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default)
{ {
return INSTANCE.LoadMesh(parReader, parParameters); return INSTANCE.LoadMesh(parReader, parParameters);
} }
private ObjMeshLoader()
{
}
public Mesh LoadMesh(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default) public Mesh LoadMesh(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default)
{ {
var mesh = new Mesh(); var mesh = new Mesh();

View File

@@ -7,15 +7,15 @@ public class StlMeshLoader : IMeshLoader
{ {
private static readonly StlMeshLoader INSTANCE = new(); private static readonly StlMeshLoader INSTANCE = new();
private StlMeshLoader()
{
}
public static Mesh Load(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default) public static Mesh Load(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default)
{ {
return INSTANCE.LoadMesh(parReader, parParameters); return INSTANCE.LoadMesh(parReader, parParameters);
} }
private StlMeshLoader()
{
}
public Mesh LoadMesh(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default) public Mesh LoadMesh(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default)
{ {
var mesh = new Mesh(); var mesh = new Mesh();

View File

@@ -6,14 +6,14 @@ namespace Engine.Asset.Mesh;
public class Mesh public class Mesh
{ {
public IReadOnlyList<Vertex> Vertices => _vertices;
public IReadOnlyList<uint> Indices => _indices; public IReadOnlyList<uint> Indices => _indices;
public IReadOnlyList<Vertex> Vertices => _vertices;
internal IList<Vertex> VerticesInternal => _vertices;
internal IList<uint> IndicesInternal => _indices; internal IList<uint> IndicesInternal => _indices;
internal IList<Vertex> VerticesInternal => _vertices;
private readonly List<Vertex> _vertices = [];
private readonly List<uint> _indices = []; private readonly List<uint> _indices = [];
private readonly List<Vertex> _vertices = [];
public override int GetHashCode() public override int GetHashCode()
{ {
@@ -23,9 +23,7 @@ public class Mesh
public record struct Vertex : IVertex public record struct Vertex : IVertex
{ {
[Vertex(VertexAttribType.Float, 3)] public Vector3 _position; [Vertex(VertexAttribType.Float, 3)] public Vector3 _position;
[Vertex(VertexAttribType.Float, 3)] public Vector3 _normal; [Vertex(VertexAttribType.Float, 3)] public Vector3 _normal;
[Vertex(VertexAttribType.Float, 2)] public Vector2 _uv; [Vertex(VertexAttribType.Float, 2)] public Vector2 _uv;
} }
} }

View File

@@ -22,12 +22,14 @@ namespace Engine;
public sealed class Engine public sealed class Engine
{ {
internal static Engine Instance { get; private set; } = null!; public IInputHandler? InputHandler { get; internal set; }
public Renderer Renderer { get; }
public SceneManager SceneManager { get; } = new(); public SceneManager SceneManager { get; } = new();
public IResourceManager AssetResourceManager { get; }
public string DataFolder { get; }
internal Window Window { get; } internal static Engine Instance { get; private set; } = null!;
internal ResourceManager EngineResourceManager { get; }
internal Renderer Renderer { get; }
internal IPresenter? Presenter internal IPresenter? Presenter
{ {
@@ -48,25 +50,12 @@ public sealed class Engine
} }
} }
public IInputHandler? InputHandler internal Window Window { get; }
{
get => _inputHandler;
internal set => _inputHandler = value;
}
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly object _sceneLock = new();
private IInputHandler? _inputHandler;
private IPresenter? _presenter; private IPresenter? _presenter;
internal ResourceManager EngineResourceManager => _engineResourceManager;
public IResourceManager AssetResourceManager => _assetResourceManager;
public string DataFolder { get; }
private readonly ResourceManager _engineResourceManager;
private readonly ResourceManager _assetResourceManager;
private Thread? _updateThread; private Thread? _updateThread;
public Engine(int parWidth, int parHeight, bool parHeadless, string parTitle, string parAssetFolder, public Engine(int parWidth, int parHeight, bool parHeadless, string parTitle, string parAssetFolder,
@@ -94,8 +83,8 @@ public sealed class Engine
StencilBits = 0 StencilBits = 0
}; };
_engineResourceManager = CreateEngineResourceManager(); EngineResourceManager = CreateEngineResourceManager();
_assetResourceManager = CreateAssetResourceManager(parAssetFolder); AssetResourceManager = CreateAssetResourceManager(parAssetFolder);
_logger.Information("Created asset resource manager in {AssetFolder}", parAssetFolder); _logger.Information("Created asset resource manager in {AssetFolder}", parAssetFolder);
@@ -139,8 +128,6 @@ public sealed class Engine
parResourceManager.RegisterLoader<Font>(new FontLoader()); parResourceManager.RegisterLoader<Font>(new FontLoader());
} }
private readonly object _sceneLock = new();
public void Run() public void Run()
{ {
_updateThread = new Thread(RunUpdate) { Name = "UpdateThread" }; _updateThread = new Thread(RunUpdate) { Name = "UpdateThread" };
@@ -227,7 +214,9 @@ public sealed class Engine
private void PresenterResize(ResizeEventArgs parEventArgs) private void PresenterResize(ResizeEventArgs parEventArgs)
{ {
if (parEventArgs.Width == 0 || parEventArgs.Height == 0) if (parEventArgs.Width == 0 || parEventArgs.Height == 0)
{
return; return;
}
Renderer.Resize(parEventArgs.Width, parEventArgs.Height); Renderer.Resize(parEventArgs.Width, parEventArgs.Height);
} }

View File

@@ -16,8 +16,8 @@ public sealed class EngineBuilder
private string _assetFolder = "./asset"; private string _assetFolder = "./asset";
private string _dataFolder = "./data"; private string _dataFolder = "./data";
private Func<Engine, IPresenter>? _presenterFunc;
private Func<Engine, IInputHandler>? _inputHandlerFunc; private Func<Engine, IInputHandler>? _inputHandlerFunc;
private Func<Engine, IPresenter>? _presenterFunc;
// Logging // Logging
private bool _logToConsole; private bool _logToConsole;
@@ -65,24 +65,18 @@ public sealed class EngineBuilder
return this; return this;
} }
public EngineBuilder Presenter(Func<Engine, IPresenter> parPresenterFunc)
{
_presenterFunc = parPresenterFunc;
return this;
}
public EngineBuilder InputHandler(IInputHandler parInputHandler)
{
_inputHandlerFunc = _ => parInputHandler;
return this;
}
public EngineBuilder InputHandler(Func<Engine, IInputHandler> parInputHandlerFunc) public EngineBuilder InputHandler(Func<Engine, IInputHandler> parInputHandlerFunc)
{ {
_inputHandlerFunc = parInputHandlerFunc; _inputHandlerFunc = parInputHandlerFunc;
return this; return this;
} }
public EngineBuilder Presenter(Func<Engine, IPresenter> parPresenterFunc)
{
_presenterFunc = parPresenterFunc;
return this;
}
public EngineBuilder LogToConsole(bool parLogToConsole = true) public EngineBuilder LogToConsole(bool parLogToConsole = true)
{ {
_logToConsole = parLogToConsole; _logToConsole = parLogToConsole;
@@ -92,7 +86,9 @@ public sealed class EngineBuilder
public EngineBuilder LogToFile(bool parLogToFile = true, string? parLogFilePath = null) public EngineBuilder LogToFile(bool parLogToFile = true, string? parLogFilePath = null)
{ {
if (parLogToFile && parLogFilePath == null) if (parLogToFile && parLogFilePath == null)
{
throw new ArgumentNullException(nameof(parLogFilePath)); throw new ArgumentNullException(nameof(parLogFilePath));
}
_logToFile = parLogToFile; _logToFile = parLogToFile;
_logFilePath = parLogFilePath; _logFilePath = parLogFilePath;

View File

@@ -8,7 +8,7 @@ namespace Engine.Graphics.Buffer;
public class VertexArray : OpenGlObject public class VertexArray : OpenGlObject
{ {
private int _enabledAttribs = 0; private int _enabledAttributes;
public VertexArray() public VertexArray()
{ {
@@ -41,9 +41,9 @@ public class VertexArray : OpenGlObject
var attribute = field.GetCustomAttribute<VertexAttribute>()!; var attribute = field.GetCustomAttribute<VertexAttribute>()!;
var offset = Marshal.OffsetOf<T>(field.Name).ToInt32(); var offset = Marshal.OffsetOf<T>(field.Name).ToInt32();
SetupAttribute(attribute, _enabledAttribs, offset, parBindingIndex); SetupAttribute(attribute, _enabledAttributes, offset, parBindingIndex);
_enabledAttribs += attribute.RepeatCount; _enabledAttributes += attribute.RepeatCount;
} }
GL.VertexArrayBindingDivisor(Handle, parBindingIndex, parDivisor); GL.VertexArrayBindingDivisor(Handle, parBindingIndex, parDivisor);

View File

@@ -1,8 +1,8 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Evergine.Bindings.RenderDoc;
using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.OpenGL;
using Serilog; using Serilog;
using Serilog.Events; using Serilog.Events;
using Evergine.Bindings.RenderDoc;
namespace Engine.Graphics; namespace Engine.Graphics;

View File

@@ -32,6 +32,7 @@ public class Framebuffer : OpenGlObject
internal Texture.Texture? TextureInternal => GetAttachment<DynamicTexture>(FramebufferAttachment.ColorAttachment0); internal Texture.Texture? TextureInternal => GetAttachment<DynamicTexture>(FramebufferAttachment.ColorAttachment0);
private readonly IDictionary<FramebufferAttachment, IFramebufferAttachment> _attachments; private readonly IDictionary<FramebufferAttachment, IFramebufferAttachment> _attachments;
private int _width; private int _width;
private int _height; private int _height;

View File

@@ -13,13 +13,13 @@ public class GenericRenderer : IRenderer
public AnyMeshRenderer AnyMeshRenderer => _anyMeshRenderer ??= new AnyMeshRenderer(_engine, 1024); public AnyMeshRenderer AnyMeshRenderer => _anyMeshRenderer ??= new AnyMeshRenderer(_engine, 1024);
public TextRenderer TextRenderer => _textRenderer ??= new TextRenderer(_engine, 1024 * 8); public TextRenderer TextRenderer => _textRenderer ??= new TextRenderer(_engine, 1024 * 8);
internal readonly Framebuffer.Framebuffer _framebuffer;
private readonly Engine _engine;
private QuadRenderer? _quadRenderer; private QuadRenderer? _quadRenderer;
private AnyMeshRenderer? _anyMeshRenderer; private AnyMeshRenderer? _anyMeshRenderer;
private TextRenderer? _textRenderer; private TextRenderer? _textRenderer;
private readonly Engine _engine;
internal readonly Framebuffer.Framebuffer _framebuffer;
private bool _frameStarted; private bool _frameStarted;
public GenericRenderer(Engine parEngine, int parWidth, int parHeight) public GenericRenderer(Engine parEngine, int parWidth, int parHeight)
@@ -40,7 +40,9 @@ public class GenericRenderer : IRenderer
public void EndFrame(in Matrix4 parProjectionMatrix, in Matrix4 parViewMatrix) public void EndFrame(in Matrix4 parProjectionMatrix, in Matrix4 parViewMatrix)
{ {
if (!_frameStarted) if (!_frameStarted)
{
throw new InvalidOperationException("Frame not started"); throw new InvalidOperationException("Frame not started");
}
_framebuffer.Bind(); _framebuffer.Bind();

View File

@@ -6,12 +6,12 @@ namespace Engine.Graphics;
public interface IPresenter : IUpdate, IRender public interface IPresenter : IUpdate, IRender
{ {
bool IsExiting { get; } public event Action<ResizeEventArgs> Resize;
int Width { get; }
int Height { get; }
event Action<ResizeEventArgs> Resize; public int Width { get; }
public int Height { get; }
public bool IsExiting { get; }
void Present(IConstTexture parTexture); public void Present(IConstTexture parTexture);
void Exit(); public void Exit();
} }

View File

@@ -5,10 +5,10 @@ public class RenderLayer : IComparable<RenderLayer>
public static readonly RenderLayer DEFAULT = new("default", 0); public static readonly RenderLayer DEFAULT = new("default", 0);
public static readonly RenderLayer OVERLAY = new("overlay", 1); public static readonly RenderLayer OVERLAY = new("overlay", 1);
public static readonly RenderLayer HUD = new("hud", 2); public static readonly RenderLayer HUD = new("hud", 2);
public static readonly IReadOnlyList<RenderLayer> ALL = new List<RenderLayer> { DEFAULT, OVERLAY, HUD }.AsReadOnly(); public static readonly IReadOnlyList<RenderLayer> ALL = new List<RenderLayer> { DEFAULT, OVERLAY, HUD }.AsReadOnly();
public string Name { get; } public string Name { get; }
private readonly int _order; private readonly int _order;
private RenderLayer(string parName, int parOrder) private RenderLayer(string parName, int parOrder)
@@ -17,16 +17,16 @@ public class RenderLayer : IComparable<RenderLayer>
_order = parOrder; _order = parOrder;
} }
public override string ToString()
{
return Name;
}
public int CompareTo(RenderLayer? parOther) public int CompareTo(RenderLayer? parOther)
{ {
return parOther == null ? 1 : _order.CompareTo(parOther._order); return parOther == null ? 1 : _order.CompareTo(parOther._order);
} }
public override string ToString()
{
return Name;
}
public override int GetHashCode() public override int GetHashCode()
{ {
return Name.GetHashCode(); return Name.GetHashCode();

View File

@@ -1,6 +0,0 @@
namespace Engine.Graphics.Pipeline;
public class RenderPipeline
{
}

View File

@@ -10,18 +10,18 @@ public abstract class InstancedRenderer<C, I>
where C : struct, IVertex where C : struct, IVertex
where I : struct, IVertex where I : struct, IVertex
{ {
protected readonly int _instanceCount;
protected readonly I[] _instanceVertices;
protected int _queuedInstanceCount;
private readonly PrimitiveType _primitiveType;
private readonly Program _program;
private readonly IndexBuffer _indexBuffer; private readonly IndexBuffer _indexBuffer;
private readonly VertexBuffer<C> _commonVertexBuffer; private readonly VertexBuffer<C> _commonVertexBuffer;
private readonly VertexBuffer<I> _instanceVertexBuffer; private readonly VertexBuffer<I> _instanceVertexBuffer;
private readonly VertexArray _vertexArray; private readonly VertexArray _vertexArray;
protected readonly int _instanceCount;
protected int _queuedInstanceCount;
protected readonly I[] _instanceVertices;
private readonly PrimitiveType _primitiveType;
private readonly Program _program;
protected InstancedRenderer(PrimitiveType parPrimitiveType, int parInstanceCount, protected InstancedRenderer(PrimitiveType parPrimitiveType, int parInstanceCount,
uint[] parIndexBuffer, C[] parInstanceBuffer, uint[] parIndexBuffer, C[] parInstanceBuffer,
Program parProgram) Program parProgram)
@@ -39,7 +39,7 @@ public abstract class InstancedRenderer<C, I>
_vertexArray = new VertexArray(); _vertexArray = new VertexArray();
_vertexArray.BindIndexBuffer(_indexBuffer); _vertexArray.BindIndexBuffer(_indexBuffer);
_vertexArray.BindVertexBuffer(_commonVertexBuffer, 0, 0); _vertexArray.BindVertexBuffer(_commonVertexBuffer);
_vertexArray.BindVertexBuffer(_instanceVertexBuffer, 1, 1); _vertexArray.BindVertexBuffer(_instanceVertexBuffer, 1, 1);
} }
@@ -51,7 +51,10 @@ public abstract class InstancedRenderer<C, I>
} }
if (DataChanged()) if (DataChanged())
{
_instanceVertexBuffer.UploadData(_instanceVertices, _queuedInstanceCount); _instanceVertexBuffer.UploadData(_instanceVertices, _queuedInstanceCount);
}
_vertexArray.Bind(); _vertexArray.Bind();
_program.Bind(); _program.Bind();

View File

@@ -46,8 +46,10 @@ public class AnyMeshRenderer(Engine parEngine, int parMaxInstanceCount)
foreach (var mesh in meshes) foreach (var mesh in meshes)
{ {
if (!_frameMeshes.Contains(mesh)) if (!_frameMeshes.Contains(mesh))
{
_meshRenderers.Remove(mesh); _meshRenderers.Remove(mesh);
} }
}
_frameMeshes.Clear(); _frameMeshes.Clear();
} }

View File

@@ -8,7 +8,6 @@ namespace Engine.Graphics.Render.Quad;
public class QuadRenderer : InstancedRenderer<QuadCommonVertex, QuadInstanceVertex> public class QuadRenderer : InstancedRenderer<QuadCommonVertex, QuadInstanceVertex>
{ {
private readonly TextureUnitMap _textureUnitMap = new(16); private readonly TextureUnitMap _textureUnitMap = new(16);
private readonly int[] _textureUnitIndices = new int[16]; private readonly int[] _textureUnitIndices = new int[16];
private int _frameHash; private int _frameHash;
@@ -19,7 +18,7 @@ public class QuadRenderer : InstancedRenderer<QuadCommonVertex, QuadInstanceVert
new QuadCommonVertex { _position = new Vector3(-0.5f, -0.5f, 0), _uv = new Vector2(0, 0) }, new QuadCommonVertex { _position = new Vector3(-0.5f, -0.5f, 0), _uv = new Vector2(0, 0) },
new QuadCommonVertex { _position = new Vector3(-0.5f, 0.5f, 0), _uv = new Vector2(0, 1) }, new QuadCommonVertex { _position = new Vector3(-0.5f, 0.5f, 0), _uv = new Vector2(0, 1) },
new QuadCommonVertex { _position = new Vector3(0.5f, -0.5f, 0), _uv = new Vector2(1, 0) }, new QuadCommonVertex { _position = new Vector3(0.5f, -0.5f, 0), _uv = new Vector2(1, 0) },
new QuadCommonVertex { _position = new Vector3(0.5f, 0.5f, 0), _uv = new Vector2(1, 1) }, new QuadCommonVertex { _position = new Vector3(0.5f, 0.5f, 0), _uv = new Vector2(1, 1) }
], ],
parEngine.EngineResourceManager.Load<Program>("shader/quad")) parEngine.EngineResourceManager.Load<Program>("shader/quad"))
{ {

View File

@@ -9,41 +9,34 @@ namespace Engine.Graphics.Render.Text;
public class TextRenderer public class TextRenderer
{ {
private readonly Program _program;
private readonly IndexBuffer _indexBuffer; private readonly IndexBuffer _indexBuffer;
private readonly VertexBuffer<GlyphVertex> _glyphVertexBuffer;
private readonly VertexArray _vertexArray; private readonly VertexArray _vertexArray;
private readonly VertexBuffer<GlyphVertex> _glyphVertexBuffer;
// private readonly VertexBuffer<GlyphCommonVertex> _glyphCommonVertexBuffer;
private readonly Program _program;
private readonly int _characterCount; private readonly int _characterCount;
private int _queuedCharacterCount; private readonly GlyphVertex[] _glyphVertices;
private readonly TextureUnitMap _textureUnitMap = new(16); private readonly TextureUnitMap _textureUnitMap = new(16);
private readonly int[] _textureUnitIndices = new int[16]; private readonly int[] _textureUnitIndices = new int[16];
private readonly GlyphVertex[] _glyphVertices; private int _queuedCharacterCount;
// private readonly GlyphCommonVertex[] _glyphCommonVertices;
public TextRenderer(Engine parEngine, int parCharacterCount) public TextRenderer(Engine parEngine, int parCharacterCount)
{ {
_characterCount = parCharacterCount; _characterCount = parCharacterCount;
_glyphVertices = new GlyphVertex[parCharacterCount * 4]; _glyphVertices = new GlyphVertex[parCharacterCount * 4];
// _glyphCommonVertices = new GlyphCommonVertex[parCharacterCount];
_program = parEngine.EngineResourceManager.Load<Program>("shader/text"); _program = parEngine.EngineResourceManager.Load<Program>("shader/text");
_indexBuffer = new IndexBuffer(CreateIndices(_characterCount)); _indexBuffer = new IndexBuffer(CreateIndices(_characterCount));
_glyphVertexBuffer = new VertexBuffer<GlyphVertex>(_characterCount * 4, _glyphVertexBuffer = new VertexBuffer<GlyphVertex>(_characterCount * 4,
BufferStorageFlags.DynamicStorageBit); BufferStorageFlags.DynamicStorageBit);
// _glyphCommonVertexBuffer = new VertexBuffer<GlyphCommonVertex>(_characterCount,
// BufferStorageFlags.DynamicStorageBit);
_vertexArray = new VertexArray(); _vertexArray = new VertexArray();
_vertexArray.BindIndexBuffer(_indexBuffer); _vertexArray.BindIndexBuffer(_indexBuffer);
_vertexArray.BindVertexBuffer(_glyphVertexBuffer, 0, 0); _vertexArray.BindVertexBuffer(_glyphVertexBuffer);
// _vertexArray.BindVertexBuffer(_glyphCommonVertexBuffer, 1, 1);
} }
public void Commit(Font parFont, string parText, Vector4 parColor, in Matrix4 parModelMatrix) public void Commit(Font parFont, string parText, Vector4 parColor, in Matrix4 parModelMatrix)
@@ -61,12 +54,12 @@ public class TextRenderer
{ {
for (var i = 0; i < 4; i++) for (var i = 0; i < 4; i++)
{ {
_glyphVertices[_queuedCharacterCount * 4 + i]._position = glyphData.Positions[i] + glyphData.Offset; _glyphVertices[(_queuedCharacterCount * 4) + i]._position = glyphData.Positions[i] + glyphData.Offset;
_glyphVertices[_queuedCharacterCount * 4 + i]._uv = glyphData.UVs[i]; _glyphVertices[(_queuedCharacterCount * 4) + i]._uv = glyphData.UVs[i];
_glyphVertices[_queuedCharacterCount * 4 + i]._color = parColor; _glyphVertices[(_queuedCharacterCount * 4) + i]._color = parColor;
_glyphVertices[_queuedCharacterCount * 4 + i]._atlasId = textureId; _glyphVertices[(_queuedCharacterCount * 4) + i]._atlasId = textureId;
_glyphVertices[_queuedCharacterCount * 4 + i]._unitRange = parFont.UnitRange; _glyphVertices[(_queuedCharacterCount * 4) + i]._unitRange = parFont.UnitRange;
_glyphVertices[_queuedCharacterCount * 4 + i]._modelMatrix = parModelMatrix; _glyphVertices[(_queuedCharacterCount * 4) + i]._modelMatrix = parModelMatrix;
} }
_queuedCharacterCount++; _queuedCharacterCount++;
@@ -81,7 +74,6 @@ public class TextRenderer
} }
_glyphVertexBuffer.UploadData(_glyphVertices, _queuedCharacterCount * 4); _glyphVertexBuffer.UploadData(_glyphVertices, _queuedCharacterCount * 4);
// _glyphCommonVertexBuffer.UploadData(_glyphCommonVertices, _queuedCharacterCount);
_vertexArray.Bind(); _vertexArray.Bind();
_program.Bind(); _program.Bind();

View File

@@ -9,31 +9,18 @@ namespace Engine.Graphics;
public class Renderer public class Renderer
{ {
internal Framebuffer.Framebuffer RenderFramebuffer => _framebuffer; public int ViewportWidth => RenderFramebuffer.Width;
internal Texture.Texture RenderTexture => _framebuffer.TextureInternal!; public int ViewportHeight => RenderFramebuffer.Height;
private QuadRenderer QuadRenderer { get; }
public int ViewportWidth => _framebuffer.Width;
public int ViewportHeight => _framebuffer.Height;
private readonly SortedDictionary<RenderLayer, GenericRenderer> _renderers = new();
public GenericRenderer this[RenderLayer parRenderLayer]
{
get
{
if (_renderers.TryGetValue(parRenderLayer, out var renderer))
return renderer;
throw new InvalidOperationException($"Renderer for layer {parRenderLayer} not found");
}
}
internal Framebuffer.Framebuffer RenderFramebuffer { get; }
internal Texture.Texture RenderTexture => RenderFramebuffer.TextureInternal!;
internal NativeWindow NativeWindow { get; } internal NativeWindow NativeWindow { get; }
private readonly Framebuffer.Framebuffer _framebuffer; private readonly SortedDictionary<RenderLayer, GenericRenderer> _renderers = new();
private readonly Thread _renderThread; private readonly Thread _renderThread;
private QuadRenderer QuadRenderer { get; }
private readonly Queue<Action> _scheduleActions = new(); private readonly Queue<Action> _scheduleActions = new();
public Renderer(Engine parEngine, int parWidth, int parHeight, NativeWindowSettings parSettings) public Renderer(Engine parEngine, int parWidth, int parHeight, NativeWindowSettings parSettings)
@@ -49,7 +36,7 @@ public class Renderer
InitializeOpenGl(parWidth, parHeight); InitializeOpenGl(parWidth, parHeight);
_framebuffer = Framebuffer.Framebuffer.Builder(parWidth, parHeight) RenderFramebuffer = Framebuffer.Framebuffer.Builder(parWidth, parHeight)
.AddColorAttachment<Rgba8>() .AddColorAttachment<Rgba8>()
.Build(); .Build();
@@ -61,6 +48,19 @@ public class Renderer
} }
} }
public GenericRenderer this[RenderLayer parRenderLayer]
{
get
{
if (_renderers.TryGetValue(parRenderLayer, out var renderer))
{
return renderer;
}
throw new InvalidOperationException($"Renderer for layer {parRenderLayer} not found");
}
}
private void InitializeOpenGl(int parWidth, int parHeight) private void InitializeOpenGl(int parWidth, int parHeight)
{ {
#if DEBUG #if DEBUG
@@ -129,15 +129,16 @@ public class Renderer
foreach (var (renderLayer, renderer) in _renderers) foreach (var (renderLayer, renderer) in _renderers)
{ {
if (!parMatrices.TryGetValue(renderLayer, out var matrices)) if (!parMatrices.TryGetValue(renderLayer, out var matrices))
{
renderer.EndFrame(Matrix4.Identity, Matrix4.Identity); renderer.EndFrame(Matrix4.Identity, Matrix4.Identity);
}
else else
{
renderer.EndFrame(in matrices.Item1, in matrices.Item2); renderer.EndFrame(in matrices.Item1, in matrices.Item2);
} }
}
_framebuffer.Bind(); RenderFramebuffer.Bind();
// GL.Disable(EnableCap.DepthTest);
// GL.Disable(EnableCap.CullFace);
GL.ClearColor(0.0f, 0.0f, 0.0f, 1.0f); GL.ClearColor(0.0f, 0.0f, 0.0f, 1.0f);
GL.Clear(ClearBufferMask.ColorBufferBit); GL.Clear(ClearBufferMask.ColorBufferBit);
@@ -145,22 +146,21 @@ public class Renderer
foreach (var (renderLayer, renderer) in _renderers) foreach (var (renderLayer, renderer) in _renderers)
{ {
if (!parMatrices.ContainsKey(renderLayer)) if (!parMatrices.ContainsKey(renderLayer))
{
continue; continue;
}
QuadRenderer.Commit(Matrix4.CreateScale(2f, -2f, 1f), Vector4.One, renderer._framebuffer.TextureInternal); QuadRenderer.Commit(Matrix4.CreateScale(2f, -2f, 1f), Vector4.One, renderer._framebuffer.TextureInternal);
QuadRenderer.Render(Matrix4.Identity, Matrix4.Identity); QuadRenderer.Render(Matrix4.Identity, Matrix4.Identity);
QuadRenderer.Reset(); QuadRenderer.Reset();
} }
// GL.Enable(EnableCap.DepthTest); RenderFramebuffer.Unbind();
// GL.Enable(EnableCap.CullFace);
_framebuffer.Unbind();
} }
internal void Resize(int parWidth, int parHeight) internal void Resize(int parWidth, int parHeight)
{ {
_framebuffer.Resize(parWidth, parHeight); RenderFramebuffer.Resize(parWidth, parHeight);
GL.Viewport(0, 0, parWidth, parHeight); GL.Viewport(0, 0, parWidth, parHeight);
foreach (var renderer in _renderers.Values) foreach (var renderer in _renderers.Values)

View File

@@ -1,12 +1,10 @@
using Engine.Asset; using Engine.Asset;
using Engine.Graphics.Pixel; using Engine.Graphics.Pixel;
using OpenTK.Mathematics;
namespace Engine.Graphics.Texture; namespace Engine.Graphics.Texture;
public interface IConstTexture public interface IConstTexture
{ {
public Vector2i Size { get; }
public int Width { get; } public int Width { get; }
public int Height { get; } public int Height { get; }

View File

@@ -1,15 +1,12 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Engine.Graphics.Pixel; using Engine.Graphics.Pixel;
using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
using Serilog; using Serilog;
namespace Engine.Graphics.Texture; namespace Engine.Graphics.Texture;
public abstract class Texture : OpenGlObject, ITexture public abstract class Texture : OpenGlObject, ITexture
{ {
public Vector2i Size => new(Width, Height);
public int Width public int Width
{ {
get => _width; get => _width;

View File

@@ -66,12 +66,16 @@ public enum KeyboardButtonCode
public static class KeyboardButtonCodeHelper public static class KeyboardButtonCodeHelper
{ {
public static List<KeyboardButtonCode> GetAllPrintableKeys() => public static List<KeyboardButtonCode> GetAllPrintableKeys()
Enum.GetValues<KeyboardButtonCode>().Where(parX => parX.IsPrintableKey()).ToList(); {
return Enum.GetValues<KeyboardButtonCode>().Where(parX => parX.IsPrintableKey()).ToList();
}
public static bool IsPrintableKey(this KeyboardButtonCode parKey) => public static bool IsPrintableKey(this KeyboardButtonCode parKey)
parKey is >= KeyboardButtonCode.A and <= KeyboardButtonCode.Z {
return parKey is >= KeyboardButtonCode.A and <= KeyboardButtonCode.Z
or >= KeyboardButtonCode.D1 and <= KeyboardButtonCode.D0 or KeyboardButtonCode.Space; or >= KeyboardButtonCode.D1 and <= KeyboardButtonCode.D0 or KeyboardButtonCode.Space;
}
public static char GetChar(this KeyboardButtonCode parKey) public static char GetChar(this KeyboardButtonCode parKey)
{ {

View File

@@ -9,11 +9,10 @@ public class WindowInputHandler(Window parWindow) : IInputHandler
public CultureInfo CurrentInputLanguage => new(1033); public CultureInfo CurrentInputLanguage => new(1033);
public Vector2 MousePosition => parWindow.NativeWindow.MouseState.Position; public Vector2 MousePosition => parWindow.NativeWindow.MouseState.Position;
private KeyboardState _previousKeyboardState = parWindow.NativeWindow.KeyboardState.GetSnapshot();
private KeyboardState _keyboardState = parWindow.NativeWindow.KeyboardState.GetSnapshot(); private KeyboardState _keyboardState = parWindow.NativeWindow.KeyboardState.GetSnapshot();
private KeyboardState _previousKeyboardState = parWindow.NativeWindow.KeyboardState.GetSnapshot();
private MouseState _previousMouseState = parWindow.NativeWindow.MouseState.GetSnapshot();
private MouseState _mouseState = parWindow.NativeWindow.MouseState.GetSnapshot(); private MouseState _mouseState = parWindow.NativeWindow.MouseState.GetSnapshot();
private MouseState _previousMouseState = parWindow.NativeWindow.MouseState.GetSnapshot();
public void Update(double parDeltaTime) public void Update(double parDeltaTime)
{ {

View File

@@ -1,5 +1,6 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Engine.Graphics.Pixel; using Engine.Graphics.Pixel;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
@@ -15,10 +16,10 @@ public class ImageLoader : IResourceLoader
internal static Asset.Image<Rgba8> Load(Stream parStream) internal static Asset.Image<Rgba8> Load(Stream parStream)
{ {
var sharpImage = SixLabors.ImageSharp.Image.Load<Rgba32>(parStream); var sharpImage = Image.Load<Rgba32>(parStream);
if (sharpImage == null) if (sharpImage == null)
{ {
throw new InvalidOperationException($"Failed to load image from stream"); throw new InvalidOperationException("Failed to load image from stream");
} }
sharpImage.Mutate(parImageContext => parImageContext.Flip(FlipMode.Vertical)); sharpImage.Mutate(parImageContext => parImageContext.Flip(FlipMode.Vertical));

View File

@@ -6,9 +6,6 @@ namespace Engine.Resource.Loader;
public partial class ProgramLoader : IResourceLoader public partial class ProgramLoader : IResourceLoader
{ {
[GeneratedRegex(@"^//\s+#type\s+(?<type>[a-z]+)$", RegexOptions.Compiled)]
private static partial Regex TypeRegex();
public object Load(string parPath, IResourceStreamProvider parStreamProvider) public object Load(string parPath, IResourceStreamProvider parStreamProvider)
{ {
var textReader = new StreamReader(parStreamProvider.GetStream(parPath)); var textReader = new StreamReader(parStreamProvider.GetStream(parPath));
@@ -38,4 +35,7 @@ public partial class ProgramLoader : IResourceLoader
return new Program(vertexSource.ToString(), fragmentSource.ToString()); return new Program(vertexSource.ToString(), fragmentSource.ToString());
} }
[GeneratedRegex(@"^//\s+#type\s+(?<type>[a-z]+)$", RegexOptions.Compiled)]
private static partial Regex TypeRegex();
} }

View File

@@ -1,36 +0,0 @@
namespace Engine.Resource;
public class ResourceHandle<T>
{
public T? Value
{
get
{
if (_isLoaded)
{
return _value!;
}
if (!_task.IsCompleted)
{
return _defaultValue;
}
_value = _task.Result;
_isLoaded = true;
return _value!;
}
}
private readonly T? _defaultValue;
private readonly Task<T> _task;
private T? _value;
private bool _isLoaded;
public ResourceHandle(Task<T> parTask, T? parDefaultValue)
{
_defaultValue = parDefaultValue;
_task = parTask;
}
}

View File

@@ -2,20 +2,14 @@
public class ResourceManager : IResourceManager public class ResourceManager : IResourceManager
{ {
internal IResourceStreamProvider StreamProvider => _streamProvider; internal IResourceStreamProvider StreamProvider { get; }
private readonly IResourceStreamProvider _streamProvider;
private readonly Dictionary<Type, IResourceLoader> _loaders = new(); private readonly Dictionary<Type, IResourceLoader> _loaders = new();
private readonly Dictionary<Type, ResourceStorage> _storages = new(); private readonly Dictionary<Type, ResourceStorage> _storages = new();
public ResourceManager(IResourceStreamProvider parStreamProvider) public ResourceManager(IResourceStreamProvider parStreamProvider)
{ {
_streamProvider = parStreamProvider; StreamProvider = parStreamProvider;
}
internal void RegisterLoader<T>(IResourceLoader parLoader) where T : class
{
_loaders.Add(typeof(T), parLoader);
} }
public T Load<T>(string parPath) where T : class public T Load<T>(string parPath) where T : class
@@ -37,13 +31,18 @@ public class ResourceManager : IResourceManager
throw new InvalidOperationException($"No loader found for type {typeof(T)}"); throw new InvalidOperationException($"No loader found for type {typeof(T)}");
} }
var resource = loader.Load(parPath, _streamProvider); var resource = loader.Load(parPath, StreamProvider);
storage.Add(parPath, resource); storage.Add(parPath, resource);
return (T)resource; return (T)resource;
} }
internal void RegisterLoader<T>(IResourceLoader parLoader) where T : class
{
_loaders.Add(typeof(T), parLoader);
}
internal void Reset() internal void Reset()
{ {
_storages.Clear(); _storages.Clear();

View File

@@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> id="root"
xmlns="">
<xsd:element name="root" msdata:IsDataSet="true"> <xsd:element name="root" msdata:IsDataSet="true">
</xsd:element> </xsd:element>

View File

@@ -11,13 +11,18 @@ public class OrthographicCamera(
) )
: Camera(parNearPlane, parFarPlane) : Camera(parNearPlane, parFarPlane)
{ {
public Axis FixedAxis { get; set; } = parAxis; public enum Axis
public float Size { get; set; } = parSize; {
public bool UseScreenSize { get; set; } = false; X,
Y
public override Matrix4 View => GameObject.Transform.TransformMatrix.Inverted(); }
public override Matrix4 Projection => GetProjectionMatrix(); public override Matrix4 Projection => GetProjectionMatrix();
public override Matrix4 View => GameObject.Transform.TransformMatrix.Inverted();
public float Size { get; set; } = parSize;
public bool UseScreenSize { get; set; } = false;
public Axis FixedAxis { get; set; } = parAxis;
private Matrix4 GetProjectionMatrix() private Matrix4 GetProjectionMatrix()
{ {
@@ -27,7 +32,7 @@ public class OrthographicCamera(
public override Vector3 ScreenToWorld(Vector2 parScreenPosition) public override Vector3 ScreenToWorld(Vector2 parScreenPosition)
{ {
var normalized = parScreenPosition / ScreenSize - new Vector2(0.5f); var normalized = (parScreenPosition / ScreenSize) - new Vector2(0.5f);
normalized.X *= 2; normalized.X *= 2;
normalized.Y *= -2; normalized.Y *= -2;
@@ -70,10 +75,4 @@ public class OrthographicCamera(
} }
} }
} }
public enum Axis
{
X,
Y
}
} }

View File

@@ -10,9 +10,6 @@ public class PerspectiveCamera(
) )
: Camera(parNearPlane, parFarPlane) : Camera(parNearPlane, parFarPlane)
{ {
public float FieldOfView { get; set; } = parFieldOfView;
public Vector3 Forward => new Vector4(0, 1, 0, 1).MulProject(GameObject.Transform.TransformMatrix).Xyz;
public override Matrix4 View public override Matrix4 View
{ {
get get
@@ -30,9 +27,12 @@ public class PerspectiveCamera(
Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(FieldOfView), AspectRatio, Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(FieldOfView), AspectRatio,
NearPlane, FarPlane); NearPlane, FarPlane);
public Vector3 Forward => new Vector4(0, 1, 0, 1).MulProject(GameObject.Transform.TransformMatrix).Xyz;
public float FieldOfView { get; set; } = parFieldOfView;
public override Vector3 ScreenToWorld(Vector2 parScreenPosition) public override Vector3 ScreenToWorld(Vector2 parScreenPosition)
{ {
var normalized = parScreenPosition / ScreenSize - new Vector2(0.5f); var normalized = (parScreenPosition / ScreenSize) - new Vector2(0.5f);
normalized.X *= 2; normalized.X *= 2;
normalized.Y *= -2; normalized.Y *= -2;

View File

@@ -7,7 +7,7 @@ namespace Engine.Scene.Component.BuiltIn.Renderer;
public class Box2DRenderer : Component public class Box2DRenderer : Component
{ {
public ref Vector4 Color => ref _color; public ref Vector4 Color => ref _color;
public Texture? Texture { get; set; } = null; public Texture? Texture { get; set; }
public RenderLayer RenderLayer { get; set; } = RenderLayer.DEFAULT; public RenderLayer RenderLayer { get; set; } = RenderLayer.DEFAULT;
private Vector4 _color = Vector4.One; private Vector4 _color = Vector4.One;

View File

@@ -7,9 +7,8 @@ namespace Engine.Scene.Component.BuiltIn.Renderer;
public class TextRenderer : Component public class TextRenderer : Component
{ {
public Font Font { get; set; } = null!; public Font Font { get; set; } = null!;
public string? Text { get; set; }
public ref Vector4 Color => ref _color; public ref Vector4 Color => ref _color;
public string? Text { get; set; }
public RenderLayer RenderLayer { get; set; } = RenderLayer.DEFAULT; public RenderLayer RenderLayer { get; set; } = RenderLayer.DEFAULT;
private Vector4 _color = Vector4.One; private Vector4 _color = Vector4.One;
@@ -17,7 +16,9 @@ public class TextRenderer : Component
public override void Render() public override void Render()
{ {
if (Text == null) if (Text == null)
{
return; return;
}
Engine.Instance.Renderer[RenderLayer].TextRenderer Engine.Instance.Renderer[RenderLayer].TextRenderer
.Commit(Font, Text, Color, GameObject.Transform.FullTransformMatrix); .Commit(Font, Text, Color, GameObject.Transform.FullTransformMatrix);

View File

@@ -4,24 +4,18 @@ namespace Engine.Scene.Component.BuiltIn;
public class Transform : Component public class Transform : Component
{ {
private Vector3 _translation = Vector3.Zero;
private Quaternion _rotation = Quaternion.Identity;
private Vector3 _scale = Vector3.One;
private Vector3 _size = Vector3.One;
public ref Vector3 Translation => ref _translation;
public ref Quaternion Rotation => ref _rotation;
public ref Vector3 Scale => ref _scale;
public ref Vector3 Size => ref _size; public ref Vector3 Size => ref _size;
public ref Vector3 Scale => ref _scale;
public ref Quaternion Rotation => ref _rotation;
public ref Vector3 Translation => ref _translation;
public Matrix4 FullTransformMatrix => Matrix4.CreateScale(Size) * TransformMatrix;
public Matrix4 TransformMatrix => LocalTransformMatrix * ParentTransformMatrix;
public Matrix4 LocalTransformMatrix => Matrix4.CreateScale(Scale) * public Matrix4 LocalTransformMatrix => Matrix4.CreateScale(Scale) *
Matrix4.CreateFromQuaternion(Rotation) * Matrix4.CreateFromQuaternion(Rotation) *
Matrix4.CreateTranslation(Translation); Matrix4.CreateTranslation(Translation);
public Matrix4 TransformMatrix => LocalTransformMatrix * ParentTransformMatrix;
public Matrix4 FullTransformMatrix => Matrix4.CreateScale(Size) * TransformMatrix;
private Matrix4 ParentTransformMatrix private Matrix4 ParentTransformMatrix
{ {
get get
@@ -31,6 +25,11 @@ public class Transform : Component
} }
} }
private Vector3 _size = Vector3.One;
private Vector3 _scale = Vector3.One;
private Quaternion _rotation = Quaternion.Identity;
private Vector3 _translation = Vector3.Zero;
public Vector3 GetFullTranslation() public Vector3 GetFullTranslation()
{ {
return FullTransformMatrix.ExtractTranslation(); return FullTransformMatrix.ExtractTranslation();
@@ -43,15 +42,4 @@ public class Transform : Component
return clone; return clone;
} }
public float SquaredDistanceTo(Transform parTransform)
{
var translation = GetFullTranslation();
var otherTranslation = parTransform.GetFullTranslation();
var difference = translation - otherTranslation;
var squaredDistance = difference.LengthSquared;
return squaredDistance;
}
} }

View File

@@ -14,23 +14,22 @@ public sealed class GameObject : IUpdate, IRender
set => _nextIsSelfEnabled = value; set => _nextIsSelfEnabled = value;
} }
private bool IsSelfEnabled { get; set; } = true;
private bool _prevIsSelfEnabled = true;
private bool _nextIsSelfEnabled = true;
private bool IsParentEnabled => Scene?.Hierarchy.GetParent(this)?.IsEnabled ?? true;
public Transform Transform { get; } public Transform Transform { get; }
internal Scene? Scene { get; set; } internal Scene? Scene { get; set; }
private readonly Queue<Action> _componentActions = new(); private bool IsParentEnabled => Scene?.Hierarchy.GetParent(this)?.IsEnabled ?? true;
private bool IsSelfEnabled { get; set; } = true;
private readonly List<Component.Component> _components = [];
private readonly HashSet<Type> _addedComponentTypes = [];
private readonly HashSet<Component.Component> _addedComponents = []; private readonly HashSet<Component.Component> _addedComponents = [];
private readonly HashSet<Type> _addedComponentTypes = [];
private readonly Queue<Action> _componentActions = new();
private readonly List<Component.Component> _components = [];
private readonly HashSet<Component.Component> _removedComponents = []; private readonly HashSet<Component.Component> _removedComponents = [];
private bool _nextIsSelfEnabled = true;
private bool _prevIsSelfEnabled = true;
public GameObject() public GameObject()
{ {
AddComponent<Transform>(); AddComponent<Transform>();
@@ -58,34 +57,14 @@ public sealed class GameObject : IUpdate, IRender
} }
} }
private void ProcessAddedComponents()
{
foreach (var component in _addedComponents)
{
component.Awake();
component.Start();
}
_addedComponents.Clear();
}
private void ProcessRemovedComponents()
{
foreach (var component in _removedComponents)
{
component.Destroy();
component.GameObject = null!;
}
_removedComponents.Clear();
}
public void Update(double parDeltaTime) public void Update(double parDeltaTime)
{ {
if (!IsEnabled) if (!IsEnabled)
{ {
if (!_prevIsSelfEnabled) if (!_prevIsSelfEnabled)
{
return; return;
}
foreach (var component in _components) foreach (var component in _components)
{ {
@@ -115,7 +94,9 @@ public sealed class GameObject : IUpdate, IRender
public void Render() public void Render()
{ {
if (!IsEnabled) if (!IsEnabled)
{
return; return;
}
foreach (var component in _components) foreach (var component in _components)
{ {
@@ -134,13 +115,17 @@ public sealed class GameObject : IUpdate, IRender
public T? GetComponent<T>() where T : Component.Component public T? GetComponent<T>() where T : Component.Component
{ {
if (!HasComponent<T>()) if (!HasComponent<T>())
{
return null; return null;
}
foreach (var component in _components) foreach (var component in _components)
{ {
if (component is T result) if (component is T result)
{
return result; return result;
} }
}
return null; return null;
} }
@@ -149,7 +134,9 @@ public sealed class GameObject : IUpdate, IRender
{ {
var component = GetComponent<T>(); var component = GetComponent<T>();
if (component != null) if (component != null)
{
return component; return component;
}
component = GetComponentInChildren<T>(); component = GetComponentInChildren<T>();
return component; return component;
@@ -163,12 +150,16 @@ public sealed class GameObject : IUpdate, IRender
{ {
var component = child.GetComponent<T>(); var component = child.GetComponent<T>();
if (component != null) if (component != null)
{
return component; return component;
}
var childComponent = child.GetComponentInChildren<T>(); var childComponent = child.GetComponentInChildren<T>();
if (childComponent != null) if (childComponent != null)
{
return childComponent; return childComponent;
} }
}
return null; return null;
} }
@@ -257,6 +248,28 @@ public sealed class GameObject : IUpdate, IRender
} }
} }
private void ProcessAddedComponents()
{
foreach (var component in _addedComponents)
{
component.Awake();
component.Start();
}
_addedComponents.Clear();
}
private void ProcessRemovedComponents()
{
foreach (var component in _removedComponents)
{
component.Destroy();
component.GameObject = null!;
}
_removedComponents.Clear();
}
public override string ToString() public override string ToString()
{ {
return Id.ToString(); return Id.ToString();

View File

@@ -10,7 +10,6 @@ public class Hierarchy<T>
private readonly Dictionary<NullableObject<T>, IList<T>> _childrenLookup = new(); private readonly Dictionary<NullableObject<T>, IList<T>> _childrenLookup = new();
private readonly Dictionary<T, T?> _parentLookup = new(); private readonly Dictionary<T, T?> _parentLookup = new();
private readonly ConcurrentQueue<Action> _hierarchyActions = new(); private readonly ConcurrentQueue<Action> _hierarchyActions = new();
public Hierarchy() public Hierarchy()
@@ -51,7 +50,9 @@ public class Hierarchy<T>
_hierarchyActions.Enqueue(() => _hierarchyActions.Enqueue(() =>
{ {
if (!Contains(parObj)) if (!Contains(parObj))
{
return; return;
}
var parent = GetParent(parObj); var parent = GetParent(parObj);
_childrenLookup[parent].Remove(parObj); _childrenLookup[parent].Remove(parObj);

View File

@@ -7,49 +7,14 @@ namespace Engine.Scene;
public class Scene : IUpdate, IRender public class Scene : IUpdate, IRender
{ {
public bool IsPlaying { get; private set; } public bool IsPlaying { get; private set; }
public IReadOnlyDictionary<RenderLayer, ICamera> Cameras => _cameras;
public float TimeScale { get; set; } = 1.0f; public float TimeScale { get; set; } = 1.0f;
private readonly Dictionary<RenderLayer, ICamera> _cameras = new();
internal Hierarchy<GameObject> Hierarchy { get; } = new(); internal Hierarchy<GameObject> Hierarchy { get; } = new();
internal IReadOnlyDictionary<RenderLayer, ICamera> Cameras => _cameras;
private readonly Dictionary<RenderLayer, ICamera> _cameras = new();
private readonly Queue<Action> _sceneActions = []; private readonly Queue<Action> _sceneActions = [];
internal void Enter()
{
if (IsPlaying)
{
throw new InvalidOperationException("Scene is already playing");
}
ProcessChanges();
var allCameras = FindAllComponents<Camera>();
foreach (var camera in allCameras)
{
_cameras.Add(camera.RenderLayer, camera);
}
IsPlaying = true;
}
public List<T> FindAllComponents<T>(bool parOnlyEnabled = true) where T : Component.Component
{
return Hierarchy.Objects
.Where(parGameObject => !parOnlyEnabled || parGameObject.IsEnabled)
.Select(parGameObject => parGameObject.GetComponent<T>())
.Where(parComponent => parComponent != null)
.Distinct()
.ToList()!;
}
public T? FindFirstComponent<T>() where T : Component.Component
{
return Hierarchy.Objects.Select(parGameObject => parGameObject.GetComponent<T>())
.FirstOrDefault(parComponent => parComponent != null);
}
public void Update(double parDeltaTime) public void Update(double parDeltaTime)
{ {
if (!IsPlaying) if (!IsPlaying)
@@ -85,19 +50,20 @@ public class Scene : IUpdate, IRender
} }
} }
internal void Exit() public List<T> FindAllComponents<T>(bool parOnlyEnabled = true) where T : Component.Component
{ {
if (!IsPlaying) return Hierarchy.Objects
{ .Where(parGameObject => !parOnlyEnabled || parGameObject.IsEnabled)
throw new InvalidOperationException("Scene is not playing"); .Select(parGameObject => parGameObject.GetComponent<T>())
.Where(parComponent => parComponent != null)
.Distinct()
.ToList()!;
} }
foreach (var gameObject in Hierarchy.Objects) public T? FindFirstComponent<T>() where T : Component.Component
{ {
gameObject.Destroy(); return Hierarchy.Objects.Select(parGameObject => parGameObject.GetComponent<T>())
} .FirstOrDefault(parComponent => parComponent != null);
IsPlaying = false;
} }
public void Add(GameObject parGameObject) public void Add(GameObject parGameObject)
@@ -136,6 +102,39 @@ public class Scene : IUpdate, IRender
return parRecursive ? Hierarchy.GetAllChildren(parParent) : Hierarchy.GetChildren(parParent); return parRecursive ? Hierarchy.GetAllChildren(parParent) : Hierarchy.GetChildren(parParent);
} }
internal void Enter()
{
if (IsPlaying)
{
throw new InvalidOperationException("Scene is already playing");
}
ProcessChanges();
var allCameras = FindAllComponents<Camera>();
foreach (var camera in allCameras)
{
_cameras.Add(camera.RenderLayer, camera);
}
IsPlaying = true;
}
internal void Exit()
{
if (!IsPlaying)
{
throw new InvalidOperationException("Scene is not playing");
}
foreach (var gameObject in Hierarchy.Objects)
{
gameObject.Destroy();
}
IsPlaying = false;
}
private void ProcessChanges() private void ProcessChanges()
{ {
Hierarchy.ProcessChanges(); Hierarchy.ProcessChanges();

View File

@@ -2,32 +2,33 @@
public class SceneManager : IUpdate, IRender public class SceneManager : IUpdate, IRender
{ {
public Scene? CurrentScene => _currentScene; public Scene? CurrentScene { get; private set; }
private Scene? _currentScene;
private Func<Scene>? _nextScene; private Func<Scene>? _nextScene;
public void TransitionTo(Func<Scene>? parScene)
{
_nextScene = parScene;
}
public void Update(double parDeltaTime) public void Update(double parDeltaTime)
{ {
if (_nextScene != null) if (_nextScene != null)
{ {
_currentScene?.Exit(); CurrentScene?.Exit();
_currentScene = _nextScene(); CurrentScene = _nextScene();
_nextScene = null; _nextScene = null;
_currentScene.Enter(); CurrentScene.Enter();
} }
if (parDeltaTime != 0) if (parDeltaTime != 0)
_currentScene?.Update(parDeltaTime); {
CurrentScene?.Update(parDeltaTime);
}
} }
public void Render() public void Render()
{ {
_currentScene?.Render(); CurrentScene?.Render();
}
public void TransitionTo(Func<Scene>? parScene)
{
_nextScene = parScene;
} }
} }

View File

@@ -2,21 +2,10 @@
public class TickableTimer public class TickableTimer
{ {
public event Action? OnFinished;
public event Action<double>? OnUpdate; public event Action<double>? OnUpdate;
public event Action? OnFinished;
public double TotalTime public bool IsFinished => _currentTime <= 0;
{
get => _totalTime;
set
{
if (value <= 0)
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value);
_totalTime = value;
CurrentTime = value;
}
}
public double CurrentTime public double CurrentTime
{ {
@@ -24,30 +13,54 @@ public class TickableTimer
set set
{ {
if (value < 0) if (value < 0)
{
value = 0; value = 0;
}
if (value > TotalTime) if (value > TotalTime)
{
value = TotalTime; value = TotalTime;
}
if (value == _currentTime) if (value == _currentTime)
{
return; return;
}
_currentTime = value; _currentTime = value;
OnUpdate?.Invoke(value); OnUpdate?.Invoke(value);
if (IsFinished) if (IsFinished)
{
OnFinished?.Invoke(); OnFinished?.Invoke();
} }
} }
}
public bool IsFinished => _currentTime <= 0; public double TotalTime
{
get => _totalTime;
set
{
if (value <= 0)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value);
}
_totalTime = value;
CurrentTime = value;
}
}
private double _totalTime;
private double _currentTime; private double _currentTime;
private double _totalTime;
public TickableTimer(double parTotalTime) public TickableTimer(double parTotalTime)
{ {
if (parTotalTime <= 0) if (parTotalTime <= 0)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(parTotalTime); ArgumentOutOfRangeException.ThrowIfNegativeOrZero(parTotalTime);
}
_totalTime = parTotalTime; _totalTime = parTotalTime;
_currentTime = parTotalTime; _currentTime = parTotalTime;

View File

@@ -9,34 +9,34 @@ namespace Engine;
public class Window : IPresenter public class Window : IPresenter
{ {
public bool IsExiting => _window.IsExiting;
public int Width { get; private set; }
public int Height { get; private set; }
public event Action<ResizeEventArgs>? Resize; public event Action<ResizeEventArgs>? Resize;
internal NativeWindow NativeWindow => _window; public int Width { get; private set; }
public int Height { get; private set; }
public bool IsExiting => NativeWindow.IsExiting;
internal NativeWindow NativeWindow { get; }
private readonly Engine _engine; private readonly Engine _engine;
private readonly NativeWindow _window;
private readonly bool _headless; private readonly bool _headless;
public Window(Engine parEngine, NativeWindow parWindow, bool parHeadless) public Window(Engine parEngine, NativeWindow parWindow, bool parHeadless)
{ {
_engine = parEngine; _engine = parEngine;
_window = parWindow; NativeWindow = parWindow;
_headless = parHeadless; _headless = parHeadless;
(Width, Height) = _window.ClientSize; (Width, Height) = NativeWindow.ClientSize;
_window.MakeCurrent(); NativeWindow.MakeCurrent();
_window.Resize += parArgs => NativeWindow.Resize += parArgs =>
{ {
Width = parArgs.Width; Width = parArgs.Width;
Height = parArgs.Height; Height = parArgs.Height;
Resize?.Invoke(parArgs); Resize?.Invoke(parArgs);
}; };
_window.VSync = VSyncMode.On; NativeWindow.VSync = VSyncMode.On;
} }
public void Update(double parDeltaTime) public void Update(double parDeltaTime)
@@ -50,9 +50,9 @@ public class Window : IPresenter
return; return;
} }
_window.NewInputFrame(); NativeWindow.NewInputFrame();
NativeWindow.ProcessWindowEvents(false); NativeWindow.ProcessWindowEvents(false);
_window.SwapBuffers(); NativeWindow.SwapBuffers();
} }
public void Present(IConstTexture parTexture) public void Present(IConstTexture parTexture)
@@ -72,7 +72,7 @@ public class Window : IPresenter
public void Exit() public void Exit()
{ {
_window.Close(); NativeWindow.Close();
} }
} }

View File

@@ -5,16 +5,16 @@ namespace PresenterConsole;
public sealed class ConsoleFastOutput : IDisposable public sealed class ConsoleFastOutput : IDisposable
{ {
private const uint GENERIC_WRITE = 0x40000000;
private const int FILE_SHARE_WRITE = 0x2; private const int FILE_SHARE_WRITE = 0x2;
private const uint GENERIC_WRITE = 0x40000000;
private readonly SafeFileHandle _handle;
private readonly WindowsFFI.Coord _bufferCoord = new(0, 0); private readonly WindowsFFI.Coord _bufferCoord = new(0, 0);
private readonly SafeFileHandle _handle;
private int _width;
private int _height;
private WindowsFFI.CharInfo[] _buffer; private WindowsFFI.CharInfo[] _buffer;
private WindowsFFI.Coord _bufferSize; private WindowsFFI.Coord _bufferSize;
private int _height;
private int _width;
public ConsoleFastOutput(int parWidth, int parHeight) public ConsoleFastOutput(int parWidth, int parHeight)
@@ -26,7 +26,9 @@ public sealed class ConsoleFastOutput : IDisposable
IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero); IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
if (_handle.IsInvalid) if (_handle.IsInvalid)
{
throw new InvalidOperationException("Failed to open console handle"); throw new InvalidOperationException("Failed to open console handle");
}
Resize(parWidth, parHeight); Resize(parWidth, parHeight);
} }
@@ -34,7 +36,7 @@ public sealed class ConsoleFastOutput : IDisposable
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteChar(char parCharacter, int parX, int parY, ConsoleColor parForeground, ConsoleColor parBackground) public void WriteChar(char parCharacter, int parX, int parY, ConsoleColor parForeground, ConsoleColor parBackground)
{ {
var index = parX + parY * _width; var index = parX + (parY * _width);
ref var charInfo = ref _buffer[index]; ref var charInfo = ref _buffer[index];
charInfo.Char.UnicodeChar = parCharacter; charInfo.Char.UnicodeChar = parCharacter;
charInfo.Attributes = (short)((ushort)parForeground | ((ushort)parBackground << 4)); charInfo.Attributes = (short)((ushort)parForeground | ((ushort)parBackground << 4));
@@ -53,7 +55,9 @@ public sealed class ConsoleFastOutput : IDisposable
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(parHeight); ArgumentOutOfRangeException.ThrowIfNegativeOrZero(parHeight);
if (parWidth == _width && parHeight == _height) if (parWidth == _width && parHeight == _height)
{
return; return;
}
_width = parWidth; _width = parWidth;
_height = parHeight; _height = parHeight;

View File

@@ -7,7 +7,6 @@ namespace PresenterConsole;
public class ConsoleInputHandler : IInputHandler public class ConsoleInputHandler : IInputHandler
{ {
public CultureInfo CurrentInputLanguage => WindowsFFI.GetCurrentKeyboardLayout(); public CultureInfo CurrentInputLanguage => WindowsFFI.GetCurrentKeyboardLayout();
public Vector2 MousePosition => Vector2.Zero; public Vector2 MousePosition => Vector2.Zero;
private readonly bool[] _currentKeys = new bool[256]; private readonly bool[] _currentKeys = new bool[256];
@@ -46,17 +45,20 @@ public class ConsoleInputHandler : IInputHandler
return _currentKeys[consoleKey] && !_previousKeys[consoleKey]; return _currentKeys[consoleKey] && !_previousKeys[consoleKey];
} }
private static int ConvertToConsoleKey(MouseButtonCode parMouseButtonCode) => private static int ConvertToConsoleKey(MouseButtonCode parMouseButtonCode)
parMouseButtonCode switch {
return parMouseButtonCode switch
{ {
MouseButtonCode.Left => 0x01, MouseButtonCode.Left => 0x01,
MouseButtonCode.Right => 0x02, MouseButtonCode.Right => 0x02,
MouseButtonCode.Middle => 0x04, MouseButtonCode.Middle => 0x04,
_ => throw new ArgumentOutOfRangeException(nameof(parMouseButtonCode), parMouseButtonCode, null) _ => throw new ArgumentOutOfRangeException(nameof(parMouseButtonCode), parMouseButtonCode, null)
}; };
}
private static int ConvertToConsoleKey(KeyboardButtonCode parKeyboardButtonCode) => private static int ConvertToConsoleKey(KeyboardButtonCode parKeyboardButtonCode)
parKeyboardButtonCode switch {
return parKeyboardButtonCode switch
{ {
KeyboardButtonCode.A => 0x41, KeyboardButtonCode.A => 0x41,
KeyboardButtonCode.B => 0x42, KeyboardButtonCode.B => 0x42,
@@ -115,3 +117,4 @@ public class ConsoleInputHandler : IInputHandler
_ => throw new ArgumentOutOfRangeException(nameof(parKeyboardButtonCode), parKeyboardButtonCode, null) _ => throw new ArgumentOutOfRangeException(nameof(parKeyboardButtonCode), parKeyboardButtonCode, null)
}; };
} }
}

View File

@@ -14,31 +14,30 @@ namespace PresenterConsole;
public class ConsolePresenter : IPresenter public class ConsolePresenter : IPresenter
{ {
public bool IsExiting { get; private set; }
public int Width { get; private set; } = 2;
public int Height { get; private set; } = 1;
public event Action<ResizeEventArgs>? Resize; public event Action<ResizeEventArgs>? Resize;
private readonly Engine.Engine _engine; public int Width { get; private set; } = 2;
public int Height { get; private set; } = 1;
public bool IsExiting { get; private set; }
private readonly Framebuffer _framebuffer; private static readonly char[] LIGHTMAP = " .,:;=*#%@".Reverse().ToArray();
private readonly Engine.Graphics.Shader.Program _asciiProgram;
private Image<AsciiPixel>? _asciiImage;
private readonly IndexBuffer _indexBuffer;
private readonly VertexBuffer<AsciiVertex> _vertexBuffer;
private readonly VertexArray _vertexArray;
private readonly ConsoleFastOutput _consoleOutput; private readonly ConsoleFastOutput _consoleOutput;
private static readonly char[] LIGHTMAP = " .,:;=*#%@".Reverse().ToArray();
private readonly Engine.Graphics.Shader.Program _asciiProgram;
private readonly Framebuffer _framebuffer;
private readonly IndexBuffer _indexBuffer;
private readonly VertexArray _vertexArray;
private readonly VertexBuffer<AsciiVertex> _vertexBuffer;
private Image<AsciiPixel>? _asciiImage;
public ConsolePresenter(Engine.Engine parEngine) public ConsolePresenter(Engine.Engine parEngine)
{ {
_engine = parEngine; ((MemoryResourceStreamProvider)parEngine.EngineResourceManager.StreamProvider).AddResource("shader/ascii",
((MemoryResourceStreamProvider)_engine.EngineResourceManager.StreamProvider).AddResource("shader/ascii",
Encoding.UTF8.GetBytes(ShaderResource.Ascii)); Encoding.UTF8.GetBytes(ShaderResource.Ascii));
_asciiProgram = _engine.EngineResourceManager.Load<Engine.Graphics.Shader.Program>("shader/ascii"); _asciiProgram = parEngine.EngineResourceManager.Load<Engine.Graphics.Shader.Program>("shader/ascii");
_framebuffer = Framebuffer.Builder(Width / 2, Height) _framebuffer = Framebuffer.Builder(Width / 2, Height)
.AddColorAttachment<AsciiPixel>() .AddColorAttachment<AsciiPixel>()
@@ -77,36 +76,21 @@ public class ConsolePresenter : IPresenter
_framebuffer.Unbind(); _framebuffer.Unbind();
// GL.Viewport(0, 0, Width / 2 * 4, Height * 4);
var asciiTexture = _framebuffer.TextureInternal; var asciiTexture = _framebuffer.TextureInternal;
if (asciiTexture == null) if (asciiTexture == null)
{
throw new InvalidOperationException("Framebuffer texture is null"); throw new InvalidOperationException("Framebuffer texture is null");
}
if (_asciiImage == null || asciiTexture.Width != _asciiImage.Width || asciiTexture.Height != _asciiImage.Height) if (_asciiImage == null || asciiTexture.Width != _asciiImage.Width || asciiTexture.Height != _asciiImage.Height)
{
_asciiImage = new Image<AsciiPixel>(asciiTexture.Width, asciiTexture.Height); _asciiImage = new Image<AsciiPixel>(asciiTexture.Width, asciiTexture.Height);
}
asciiTexture.ReadPixels(_asciiImage); asciiTexture.ReadPixels(_asciiImage);
DrawImage(_asciiImage); DrawImage(_asciiImage);
} }
private void DrawImage(Image<AsciiPixel> parImage)
{
for (var y = 0; y < parImage.Height; y++)
{
for (var x = 0; x < parImage.Width; x++)
{
var pixel = parImage[y, x];
var lightnessIndex = (byte)(pixel.Luminance / 255.0f * (LIGHTMAP.Length - 1));
var colorIndex = (ConsoleColor)(pixel.Color / 255.0f * 15.0f);
_consoleOutput.WriteChar(LIGHTMAP[lightnessIndex], 2 * x, y, 0, colorIndex);
_consoleOutput.WriteChar(LIGHTMAP[lightnessIndex], 2 * x + 1, y, 0, colorIndex);
}
}
_consoleOutput.Flush();
}
public void Update(double parDeltaTime) public void Update(double parDeltaTime)
{ {
} }
@@ -131,4 +115,21 @@ public class ConsolePresenter : IPresenter
{ {
IsExiting = true; IsExiting = true;
} }
private void DrawImage(Image<AsciiPixel> parImage)
{
for (var y = 0; y < parImage.Height; y++)
{
for (var x = 0; x < parImage.Width; x++)
{
var pixel = parImage[y, x];
var lightnessIndex = (byte)(pixel.Luminance / 255.0f * (LIGHTMAP.Length - 1));
var colorIndex = (ConsoleColor)(pixel.Color / 255.0f * 15.0f);
_consoleOutput.WriteChar(LIGHTMAP[lightnessIndex], 2 * x, y, 0, colorIndex);
_consoleOutput.WriteChar(LIGHTMAP[lightnessIndex], (2 * x) + 1, y, 0, colorIndex);
}
}
_consoleOutput.Flush();
}
} }

View File

@@ -1,4 +1,5 @@
using Serilog.Events; using Engine;
using Serilog.Events;
namespace PresenterConsole; namespace PresenterConsole;
@@ -6,7 +7,7 @@ internal static class Program
{ {
public static void Main(string[] parArgs) public static void Main(string[] parArgs)
{ {
var engine = new Engine.EngineBuilder() var engine = new EngineBuilder()
.Headless() .Headless()
.LogToFile(true, "log.txt") .LogToFile(true, "log.txt")
.LogLevel(LogEventLevel.Debug) .LogLevel(LogEventLevel.Debug)

View File

@@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> id="root"
xmlns="">
<xsd:element name="root" msdata:IsDataSet="true"> <xsd:element name="root" msdata:IsDataSet="true">
</xsd:element> </xsd:element>

View File

@@ -1,5 +1,4 @@
<Application x:Class="PresenterWpf.App" <Application x:Class="PresenterWpf.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:local="clr-namespace:PresenterWpf">
</Application> </Application>

View File

@@ -1,6 +1,7 @@
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Windows; using System.Windows;
using System.Windows.Threading;
using Engine; using Engine;
using Engine.Graphics; using Engine.Graphics;
using Engine.Graphics.Texture; using Engine.Graphics.Texture;
@@ -35,11 +36,11 @@ public partial class App : Application
// Since engine claims current thread for rendering, we need to create a new thread to run WPF // Since engine claims current thread for rendering, we need to create a new thread to run WPF
var thread = new Thread(() => var thread = new Thread(() =>
{ {
var window = new MainWindow(engine); var window = new MainWindow();
presenter.Presenter = window; presenter.Presenter = window;
inputHandler.InputHandler = new WpfInputHandler(window); inputHandler.InputHandler = new WpfInputHandler(window);
window.Show(); window.Show();
System.Windows.Threading.Dispatcher.Run(); Dispatcher.Run();
}); });
thread.SetApartmentState(ApartmentState.STA); thread.SetApartmentState(ApartmentState.STA);
@@ -56,50 +57,41 @@ public partial class App : Application
private class InputHandlerWrapper : IInputHandler private class InputHandlerWrapper : IInputHandler
{ {
public CultureInfo CurrentInputLanguage => _inputHandler?.CurrentInputLanguage ?? new CultureInfo(1033); public IInputHandler? InputHandler { get; set; }
public Vector2 MousePosition => _inputHandler?.MousePosition ?? Vector2.Zero; public CultureInfo CurrentInputLanguage => InputHandler?.CurrentInputLanguage ?? new CultureInfo(1033);
private IInputHandler? _inputHandler; public Vector2 MousePosition => InputHandler?.MousePosition ?? Vector2.Zero;
public IInputHandler? InputHandler
{
get => _inputHandler;
set
{
_inputHandler = value;
}
}
public bool IsKeyPressed(KeyboardButtonCode parKeyboardButtonCode) public bool IsKeyPressed(KeyboardButtonCode parKeyboardButtonCode)
{ {
return _inputHandler?.IsKeyPressed(parKeyboardButtonCode) ?? false; return InputHandler?.IsKeyPressed(parKeyboardButtonCode) ?? false;
} }
public bool IsKeyJustPressed(KeyboardButtonCode parKeyboardButtonCode) public bool IsKeyJustPressed(KeyboardButtonCode parKeyboardButtonCode)
{ {
return _inputHandler?.IsKeyJustPressed(parKeyboardButtonCode) ?? false; return InputHandler?.IsKeyJustPressed(parKeyboardButtonCode) ?? false;
} }
public bool IsMouseButtonPressed(MouseButtonCode parButtonCode) public bool IsMouseButtonPressed(MouseButtonCode parButtonCode)
{ {
return _inputHandler?.IsMouseButtonPressed(parButtonCode) ?? false; return InputHandler?.IsMouseButtonPressed(parButtonCode) ?? false;
} }
public bool IsMouseButtonJustPressed(MouseButtonCode parButtonCode) public bool IsMouseButtonJustPressed(MouseButtonCode parButtonCode)
{ {
return _inputHandler?.IsMouseButtonJustPressed(parButtonCode) ?? false; return InputHandler?.IsMouseButtonJustPressed(parButtonCode) ?? false;
} }
public void Update(double parDeltaTime) public void Update(double parDeltaTime)
{ {
_inputHandler?.Update(parDeltaTime); InputHandler?.Update(parDeltaTime);
} }
} }
private class PresenterWrapper : IPresenter private class PresenterWrapper : IPresenter
{ {
private IPresenter? _presenter; public event Action<ResizeEventArgs>? Resize;
public IPresenter? Presenter public IPresenter? Presenter
{ {
@@ -120,10 +112,11 @@ public partial class App : Application
} }
} }
public bool IsExiting => Presenter?.IsExiting ?? false;
public int Width => Presenter?.Width ?? 0; public int Width => Presenter?.Width ?? 0;
public int Height => Presenter?.Height ?? 0; public int Height => Presenter?.Height ?? 0;
public event Action<ResizeEventArgs>? Resize; public bool IsExiting => Presenter?.IsExiting ?? false;
private IPresenter? _presenter;
public void Present(IConstTexture parTexture) public void Present(IConstTexture parTexture)
{ {
@@ -140,14 +133,14 @@ public partial class App : Application
Presenter?.Render(); Presenter?.Render();
} }
private void PresenterResize(ResizeEventArgs e)
{
Resize?.Invoke(e);
}
public void Exit() public void Exit()
{ {
Presenter?.Exit(); Presenter?.Exit();
} }
private void PresenterResize(ResizeEventArgs e)
{
Resize?.Invoke(e);
}
} }
} }

View File

@@ -3,7 +3,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:PresenterWpf"
mc:Ignorable="d" mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" Closing="MainWindow_OnClosing"> Title="MainWindow" Height="450" Width="800" Closing="MainWindow_OnClosing">
<Grid> <Grid>

View File

@@ -12,23 +12,19 @@ namespace PresenterWpf;
public partial class MainWindow : Window, IPresenter public partial class MainWindow : Window, IPresenter
{ {
public bool IsExiting { get; private set; }
public new int Width { get; private set; }
public new int Height { get; private set; }
private bool _scheduledResize;
public event Action<ResizeEventArgs>? Resize; public event Action<ResizeEventArgs>? Resize;
private readonly Engine.Engine _engine; public new int Width { get; private set; }
private Image<Rgb8>? _image; public new int Height { get; private set; }
private WriteableBitmap? _bitmap; public bool IsExiting { get; private set; }
public MainWindow(Engine.Engine parEngine) private WriteableBitmap? _bitmap;
private Image<Rgb8>? _image;
private bool _scheduledResize;
public MainWindow()
{ {
InitializeComponent(); InitializeComponent();
_engine = parEngine;
} }
public void Update(double parDeltaTime) public void Update(double parDeltaTime)

View File

@@ -9,9 +9,7 @@ namespace PresenterWpf;
public class WpfInputHandler : IInputHandler public class WpfInputHandler : IInputHandler
{ {
public CultureInfo CurrentInputLanguage { get; private set; } public CultureInfo CurrentInputLanguage { get; private set; }
public Vector2 MousePosition => _mousePosition; public Vector2 MousePosition { get; private set; } = Vector2.Zero;
private readonly Window _window;
private readonly bool[] _actualKeys = new bool[(int)KeyboardButtonCode.TotalCount]; private readonly bool[] _actualKeys = new bool[(int)KeyboardButtonCode.TotalCount];
private readonly bool[] _currentKeys = new bool[(int)KeyboardButtonCode.TotalCount]; private readonly bool[] _currentKeys = new bool[(int)KeyboardButtonCode.TotalCount];
@@ -21,8 +19,7 @@ public class WpfInputHandler : IInputHandler
private readonly bool[] _currentMouseButtons = new bool[(int)MouseButtonCode.TotalCount]; private readonly bool[] _currentMouseButtons = new bool[(int)MouseButtonCode.TotalCount];
private readonly bool[] _previousMouseButtons = new bool[(int)MouseButtonCode.TotalCount]; private readonly bool[] _previousMouseButtons = new bool[(int)MouseButtonCode.TotalCount];
private readonly Window _window;
private Vector2 _mousePosition = Vector2.Zero;
public WpfInputHandler(Window parWindow) public WpfInputHandler(Window parWindow)
{ {
@@ -35,6 +32,14 @@ public class WpfInputHandler : IInputHandler
_window.MouseMove += Window_MouseMove; _window.MouseMove += Window_MouseMove;
} }
~WpfInputHandler()
{
_window.PreviewKeyDown -= Window_PreviewKeyDown;
_window.PreviewKeyUp -= Window_PreviewKeyUp;
_window.PreviewMouseDown -= Window_PreviewMouseDown;
_window.PreviewMouseUp -= Window_PreviewMouseUp;
}
public void Update(double parDeltaTime) public void Update(double parDeltaTime)
{ {
_window.Dispatcher.Invoke(() => _window.Dispatcher.Invoke(() =>
@@ -55,6 +60,30 @@ public class WpfInputHandler : IInputHandler
} }
} }
public bool IsKeyPressed(KeyboardButtonCode parKeyboardButtonCode)
{
var keyCode = (int)parKeyboardButtonCode;
return _currentKeys[keyCode];
}
public bool IsKeyJustPressed(KeyboardButtonCode parKeyboardButtonCode)
{
var keyCode = (int)parKeyboardButtonCode;
return _currentKeys[keyCode] && !_previousKeys[keyCode];
}
public bool IsMouseButtonPressed(MouseButtonCode parButtonCode)
{
var buttonCode = (int)parButtonCode;
return _currentMouseButtons[buttonCode];
}
public bool IsMouseButtonJustPressed(MouseButtonCode parButtonCode)
{
var buttonCode = (int)parButtonCode;
return _currentMouseButtons[buttonCode] && !_previousMouseButtons[buttonCode];
}
private void Window_PreviewKeyDown(object parSender, KeyEventArgs parEventArgs) private void Window_PreviewKeyDown(object parSender, KeyEventArgs parEventArgs)
{ {
var keyCode = ConvertToKeyboardButtonCode(parEventArgs.Key); var keyCode = ConvertToKeyboardButtonCode(parEventArgs.Key);
@@ -94,34 +123,12 @@ public class WpfInputHandler : IInputHandler
private void Window_MouseMove(object parSender, MouseEventArgs parEventArgs) private void Window_MouseMove(object parSender, MouseEventArgs parEventArgs)
{ {
var position = parEventArgs.GetPosition(null); var position = parEventArgs.GetPosition(null);
_mousePosition = new Vector2((float)position.X, (float)position.Y); MousePosition = new Vector2((float)position.X, (float)position.Y);
} }
public bool IsKeyPressed(KeyboardButtonCode parKeyboardButtonCode) private static int ConvertToKeyboardButtonCode(Key parKey)
{ {
var keyCode = (int)parKeyboardButtonCode; return parKey switch
return _currentKeys[keyCode];
}
public bool IsKeyJustPressed(KeyboardButtonCode parKeyboardButtonCode)
{
var keyCode = (int)parKeyboardButtonCode;
return _currentKeys[keyCode] && !_previousKeys[keyCode];
}
public bool IsMouseButtonPressed(MouseButtonCode parButtonCode)
{
var buttonCode = (int)parButtonCode;
return _currentMouseButtons[buttonCode];
}
public bool IsMouseButtonJustPressed(MouseButtonCode parButtonCode)
{
var buttonCode = (int)parButtonCode;
return _currentMouseButtons[buttonCode] && !_previousMouseButtons[buttonCode];
}
private static int ConvertToKeyboardButtonCode(Key parKey) => parKey switch
{ {
Key.A => (int)KeyboardButtonCode.A, Key.A => (int)KeyboardButtonCode.A,
Key.B => (int)KeyboardButtonCode.B, Key.B => (int)KeyboardButtonCode.B,
@@ -182,21 +189,16 @@ public class WpfInputHandler : IInputHandler
Key.D9 => (int)KeyboardButtonCode.D9, Key.D9 => (int)KeyboardButtonCode.D9,
_ => -1 _ => -1
}; };
}
private static int ConvertToMouseButtonCode(MouseButton parButton) => private static int ConvertToMouseButtonCode(MouseButton parButton)
parButton switch {
return parButton switch
{ {
MouseButton.Left => (int)MouseButtonCode.Left, MouseButton.Left => (int)MouseButtonCode.Left,
MouseButton.Right => (int)MouseButtonCode.Right, MouseButton.Right => (int)MouseButtonCode.Right,
MouseButton.Middle => (int)MouseButtonCode.Middle, MouseButton.Middle => (int)MouseButtonCode.Middle,
_ => -1 _ => -1
}; };
~WpfInputHandler()
{
_window.PreviewKeyDown -= Window_PreviewKeyDown;
_window.PreviewKeyUp -= Window_PreviewKeyUp;
_window.PreviewMouseDown -= Window_PreviewMouseDown;
_window.PreviewMouseUp -= Window_PreviewMouseUp;
} }
} }