updated draft

This commit is contained in:
Rowan 2024-12-01 18:10:40 -06:00
parent 1383c86fcb
commit 0df53c5cef

View file

@ -48,15 +48,13 @@ interface ICollectable {
}
interface IDamaging {
void ApplyDamage(float value) {
// ...
void ApplyDamage(IDamageable damageable, float value) {
damageable.ReceiveDamage(this, value);
}
}
interface IDamageable {
void ReceiveDamage(float value) {
// ...
}
void ReceiveDamage(IDamaging source, float value);
}
class SpikedShield: ICollectable, IDamaging, IDamageable {}
@ -66,7 +64,50 @@ We can use this approach to refactor our existing items and systems to derive/ov
Engines like Unity and Godot use variations of the [Entity-Component (EC)](https://gameprogrammingpatterns.com/component.html) pattern (similar to but distinct from Entity-Component-System (ECS)). These patterns favor composition over inheritance by allowing developers to isolate behaviors into discrete components that can be applied to entities. In the Spiked Shield example, a developer could make a "Damage Source" and "Damage Target" component and add both to the item. In essence, this is the same as the interface-based approach.
Unfortunately, these patterns suffer a much bigger issue that is much more difficult to solve.
Unfortunately, these patterns suffer issues that are much more difficult to solve.
## In the event of my demise
Due to the nature of video games, important events may need to be handled at any moment. For example, a player may've dealt a fatal blow to a boss enemy on the same frame that they received fatal damage. In which order should this damage be processed? Depending on the handling order, this is likely the difference between clearing a potentially difficult boss battle and needing to do it again.
In my experience, this would likely be handled by a traditional event system where the order is difficult to predict. This isn't to make the claim that syncronous event systems are *unpredictable*, but ensuring that a given event will be handled in a way that is predictable to the designer/developer without additional abstractions is difficult.
It's often useful to explicitly define the processing order of certain interactions and events. To solve this problem, we could implement a priority [event queue](https://gameprogrammingpatterns.com/event-queue.html).
```cs
public class DamageEventArgs : EventArgs {
public readonly IDamaging Source;
public readonly IDamageable Target;
public readonly float Value;
}
public delegate void DamageEventHandler(object sender, DamageEventArgs args);
class DamageEventQueue {
private PriorityQueue<DamageEventArgs, int> queue = new();
private void OnHandleDamageEvent(object sender, DamageEventArgs args) {
var priority = args.Source switch {
Player => 2,
Enemy => 1,
_ => 0
};
queue.Enqueue(args, priority);
}
private void OnTick(float deltaTime) {
while(queue.TryDequeue(out var event, out int priority)) {
event.Source.ApplyDamage(event.Target, event.Value);
}
}
}
DamageEvent.Invoke(this, new DamageEventArgs(bossEnemy, player, 10));
DamageEvent.Invoke(this, new DamageEventArgs(player, bossEnemy, 10));
```
There's a lot of issues with this code that I'm going to pretend were deliberate decisions for brevity. The point of this example is to show that we can ingest events and sort them arbitrarily based on the requirements of our games. We've made a step in the right direction by identifying a need to explicitly order these events. Even if this implementation isn't ideal, it properly encodes the requirements of the design ("player damage should be processed before all other types").
## In search of loot
Managing game state is difficult. As a game grows, so too does the amount of amount of objects active at any given time. Dozens, hundreds, or even thousands of entities each with their own behaviors, goals, and concerns that interact with each other in different ways. This explosion in complexity can be exceedingly hard to manage even in games that are small in scope. Games, even at their simplest, tend to be *highly* complex.
@ -78,10 +119,21 @@ ECS solves many of the problems presented by object oriented programming pattern
Mental modeling is hard. Complex queries are hard. ECS is hard.
## A Solution?
something something game state as a database, expressive querying
## Going forward
continued evolution of ecs which can broadly respond to specific use cases and needs
## What I'm looking for
### What I'm looking for
* constructive feedback and discussion about the general direction i'm putting forward
* potential implementations and data structures that allow ecs to perform as outlined
* potential pitfalls that ecs may not be able to answer
### Not on topic but will still accept
* improvements and specific optimizations to my prototype
### What I'm not looking for
* criticisms about my specific prototype implementation
{% endblock article %}