update to new roxy version :3

This commit is contained in:
Rowan 2024-12-05 00:51:24 -06:00
parent 154de78758
commit a6bd688c7e
2 changed files with 60 additions and 6 deletions

View file

@ -130,7 +130,7 @@ There is still a timing issue with this approach however. Events can be raised a
Depending on the engine and implementation, there may be a few options for solving this. Rather than using `OnTick`, the damage can be handled in `OnLateTick` which runs after all `OnTick` systems have been processed (Unity's version of these methods are `Update` and `LateUpdate`). In Unity the `DamageEvent` singleton script could have its order explicitly modified in the settings or with the `DefaultExecutionOrder` attribute. In Godot, this would likely be resolved by moving the `DamageEvent` node lower in the tree since nodes are processed from top to bottom while resolving children first.
An alternative solution would be to identify the behavior which raises code events and isolate it into its own singleton. Doing this would allow it to easily be ordered before the damage handling system. We'll revisit this idea later.
An alternative solution would be to identify the behavior which raises events and isolate it into its own singleton. Doing this would allow it to easily be ordered before the damage handling system. We'll revisit this idea later.
## Legion-oriented programming
Managing a game state as it grows in size and complexity is *difficult*. It's not uncommon for a game to have dozens, hundreds, or thousands of active entities at a time. In many cases, it makes sense to decouple separate-but-related behaviors into their own systems to make them easier to manage.
@ -179,12 +179,63 @@ class Healthbar : UIElement {
This will work for many cases but it's unfortunate it needs to manage subscriptions to two events in order to get the updates it needs. If we ever needed to add more than one player that renders a healthbar, this solution would no longer be sufficient. We could circumvent this by making health updates dispatched globally but that wold come at the cost of checking *every* health update just to find a player. This solution would be much more appealing, however, if every entity with health had a healthbar. One happy medium would be to create a more specific global event for player health updates. `Healthbar` can then simply register to that event without needing to concern itself with the particulars of spawning.
With all this event-driven programming comes another downside: events are difficult to debug. Events are opaque, short-lived, and have unpredictable interactions by design. When an architecture relies heavily on events, it becomes increasingly important to have outstanding documentation. It's an unfortunate sacrifice to be made in exchange for the highly decoupled nature of events. Some game engines have made attempts to offer better insight into event connections but it's often only a mild remedy.
With all this event-driven programming comes another downside: events are opaque and difficult to debug. When an architecture relies heavily on events, it becomes increasingly important to have outstanding documentation. It's an unfortunate sacrifice to be made in exchange for the highly decoupled nature of events. Some game engines have made attempts to offer better insight into event connections but it's often only a mild remedy.
In order to better query the state of an event-driven application, [Martin Fowler's Event Sourcing post](https://martinfowler.com/eaaDev/EventSourcing.html) provides some additional respite. It's worth reading if you're working with many events in your game. Essentially, tracking the source of an event *in* the newly raised event allows subscribers to walk the chain of events in reverse to determine additional context.
## Enter ECS
These were just a few pain points of game architecture that I've come across when making games. I've tried different solutions each time to varying levels of success. While I don't think there exists a one-size-fits-all solution to every architectural decision in video games, I *do* believe that reframing how we think about our architectural goals can make some problems diminish or even disappear. This is especially helpful if the affected problems are persistent.
These were just a few pain points of game architecture that I've come across when making games. I've tried different solutions each time to varying levels of success. While I don't think there exists a one-size-fits-all solution to every architectural decision in video games, I *do* believe that reframing how we think about our architectural goals can make some problems diminish or even disappear. This is especially helpful if the affected problems are persistent across multiple projects.
Entity-Component-System (ECS) is a data-oriented approach that has resolved many of the above issues for me. It comes with its own architectural challenges, especially since the pattern has been rapidly evolving due to its recent explosion of popularity.
There are many guides attempting to explain ECS in a simple terms. This can be a bit challenging since the approach may run counter to the fundamental understanding of game architecture for many new readers. Additionally, there are different types and implementations of ECS which sometimes pollute the overall message. [Sander Mertens](https://ajmmertens.medium.com/), the author of [FLECS](https://github.com/SanderMertens/flecs), has contributed a substantial amount to the development and education of ECS. Their [FAQ](https://github.com/SanderMertens/ecs-faq) is a valuable resource to have. I'm going to try to provide a high level explanation of ECS but I recommend looking to other resources if this doesn't make sense.
### Entities
An entity is an identifier for an object. It has no data, properties, or behaviors by itself. It is simply a marker for something that exists. In some implementations of ECS, this could be as simple as an unsigned integer. These identifiers are the primary way of fetching *Component* data.
### Components
Components hold data and belong to an entity. Some implementations have limitations on what kind of data can be housed but conceptually it can be anything. A player's level, their position in the world, their current health, and the current input state of a gamepad would all be stored in a component.
Components are often stored contiguously in memory (such as in an array). The entity is used to fetch data from that container.
### An example
Let's pause for a moment to consider the relationship between entities and components. In the simplest possible ECS library, we could implement these concepts in the following way.
```cs
// this is our component definition
struct Health {
public double Current;
public double Max;
}
Health[] health = new Health[100]; // this is our component container
int player = 0; // this is our entity
double currentHealth = health[player].current; // accessing our component data
Console.WriteLine($"The player's current health is {currentHealth}");
```
I would like to restate that the actual interface will depend on the ECS library and the implementation decisions they've made. Different implementations have different tradeoffs and limitations that may change the internal representation of an entity or component.
If we extend our example just a little bit to include multiple components, we would end up with multiple containers (arrays) too. This has an interesting implication in that it allows us to visualize our data in a more intuitive way: a table.
| Entity | Name | Health.Current | Health.Max |
| ------ | -------- | -------------- | ---------- |
| 0 | "Player" | 25 | 50 |
| 1 | "Kain" | 100 | 100 |
| 2 | "Raziel" | 5 | 75 |
| 3 | "Janos" | 0 | 1000 |
Our entity is the row ID and each successive column is its associated component data.
### Systems
A system represents a behavior. This is where game and application logic lives. Systems receive a list of entities and iterate over that list to perform work on their component data. They may also create and destroy entities or attach and remove components.
## Issues with ECS
Mental modeling is hard. Complex queries are hard. ECS is hard.

View file

@ -1,10 +1,13 @@
{
"name": "kitsucafe-redux",
"version": "1.0.0",
"main": "index.js",
"config": {
"content": "content",
"dist": "dist"
},
"scripts": {
"copy:static": "ln -sfn $(realpath static) dist/static 2>/dev/null",
"build": "roxy_cli content dist && npm run copy:static",
"copy:static": "ln -sfn $(realpath static) ${npm_package_config_dist}/static 2>/dev/null",
"build": "roxy $npm_package_config_content $npm_package_config_dist && npm run copy:static",
"watch": "./watch",
"serve": "wrangler pages dev --live-reload",
"dev": "parallel -u -j3 \"npm run\" ::: build watch serve",