-
Notifications
You must be signed in to change notification settings - Fork 0
Server side
World layer contains game world state as single data structure. Its responsibility is to process and update it according to events that come from Event layer.
After World layer processed all events, world state snapshot goes to Presentational layer.
Except event processing, World layer can have recurring tasks to update world state with time. Those tasks can run and stop depending on world state.
This layer can be local for single-player games and remote for multiplayer.
World layer passes only updates to presentation layer, so it compares new state value after applying all changes with old state.
-
Scene entities organization World layer organized game entities by Entity-Component-System pattern
-
World persistence We can save game state server-side
-
Server-side aspects
- AI
- Physics
- User Input reactions
World layer keeps world state in a single structure. Structure is Record with Entities, each Entity is a set of Components handled by Systems.
Systems perform changes of world state. They are pure functions, that take world state and any needed extra data, and returns list of updated components. Those functions can be closured by container functions to provide additional data or perform side effects.
Systems usually describe rules of how game world changes. Those rules are done as javascript logic.
The idea is to replace systems with separate rules that can be applied to the game. It will make game logic composable and easily extencible since game > development will mean just describing those world rules.
It can lead to performance problems if each rule is handled separately, since they can use same components but iterate over them separately. Also, if there are too many rules, pipeline can become crowded.
To solve this, we can group those rules as RuleSets, which are basically array of rules that handle same data and on it called as small pipeline. It will allow to handle grouped rules in one iteration and organize them semantically.
The main issue here is that rules can be interrelated and need each other's results
Example:
- Some rule needs to be applied only after collision rule detected collision.
This can be solved several ways:
- Allow rules to pass events downstream. This can lead to problem when two rules need to pass some state to each other, but it should be solved another way around since it's most likely design issue. More to it, it's general for any pipeline since it's improssible to determine execution order (which > is bad by default)
- Allow rules to manage extra Components/Entities that can be used by them to pass data to each other.
// Container function is impure and can
function bulidDamageSystem(
// Additional system configuration can be passed to build function
additionalConfig: { enableLogging?: boolean }
) {
// Creating local cache
let cache = {};
// Create systemImplementationFunction that will be used by WorldLayer
return (requestedCompoents, relatedEvents) => {
const result = damageSystemImpl(
{ cache },
requestedCompoents,
relatedEvents
);
// Updating cache if changed
if (updatedAdditionalArguments?.cache) {
cache = updatedAdditionalArguments.cache;
}
return result.updatedComponents;
}
}
// Implementation function is pure and only depends on it's arguments
function damageSystemImpl(
// System implementation can have additional arguments or settings
customArguments: {
cache: any
}
// Additional engine parametes
engineParameters: {
engineTime: number,
}
// Components are provided by World layer depending on what components are listed
// in system dependency list
requestedComponents: {
health: Component[],
weapon: Component[]
},
// Events are provided by World layer depending on what event types system subscribed to
relatedEvents: Event[]
) {
const attackEvents = relatedEvents.filter(event => event.type === ATTACK_EVENT_TYPE);
// Handle all attack events
...
return {
updatedComponents,
updatedAddutionalArguments
}
}
const engine = new ServerEngine({
networkServer: new NetworkServer(),
systemsPipeline: [
buildDamageSystem({ enableLogging: true }),
]
});
Since in multiplayer users can see different parts of the world as well as have their own view of it, we need to frame (filter) world state before passing it to client. This is where framing logic comes into play.
It passes world state with client-related information to framing logic, so it can remove all private, restricted or excessive information before it goes to client-side.
- Home - About Engine
- Comparison with UE
- Approach to documentation
- Architecture
- Implementation
- Development
- Example Game Entities