@colyseus/schema
Version:
Binary state serializer with delta encoding for games
160 lines • 7.38 kB
JavaScript
;
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Movement = exports.VelocityInputController = void 0;
const becsy_1 = require("@lastolivegames/becsy");
const readline_1 = require("readline");
const symbols_1 = require("./types/symbols");
const EncodeOperation_1 = require("./encoder/EncodeOperation");
const DecodeOperation_1 = require("./decoder/DecodeOperation");
const spec_1 = require("./encoding/spec");
const Metadata_1 = require("./Metadata");
let Position = class Position {
};
__decorate([
becsy_1.field.float64
], Position.prototype, "x", void 0);
__decorate([
becsy_1.field.float64
], Position.prototype, "y", void 0);
Position = __decorate([
becsy_1.component
], Position);
let Velocity = class Velocity {
};
__decorate([
becsy_1.field.float64
], Velocity.prototype, "vx", void 0);
__decorate([
becsy_1.field.float64
], Velocity.prototype, "vy", void 0);
Velocity = __decorate([
becsy_1.component
], Velocity);
let VelocityInputController = class VelocityInputController extends becsy_1.System {
constructor() {
super(...arguments);
// Every system can define any number of queries whose results will be available in the `execute`
// method. In this case, we're asking for all entities that currently have a Velocity component,
// and declare that we'll be writing to those components.
this.movables = this.query(q => q.current.with(Velocity).write);
// Here we'll store all keys that are currently pressed. This is not specific to ECS but it's a
// common pattern to glue together event-driven (DOM) and timing-driven (ECS) processes.
this.keysPressed = new Set();
}
// Every system can provide an `initialize` method that will be called once as the world is being
// set up. We'll use it to register our DOM event handlers.
initialize() {
// Listen for keypress events
const timeouts = {};
process.stdin.on('keypress', (str, key) => {
if (key.ctrl && key.name === 'c') {
// If Ctrl+C is pressed, exit the program
rl.close();
}
else {
// Print the key pressed
this.keysPressed.add(key.name);
if (timeouts[key.name]) {
clearTimeout(timeouts[key.name]);
}
timeouts[key.name] = setTimeout(() => {
this.keysPressed.delete(key.name);
delete timeouts[key.name];
}, 100);
}
});
}
// Every system can (and probably should) provide an `execute` method that implements its logic.
// It will be invoked once per frame in our demo, so at 60fps it's called 60 times per second.
execute() {
// We loop through the query results of the movables query we defined above.
for (const movable of this.movables.current) {
// This is how we access the data stored in the Velocity component of our movable entity.
// We must specify whether we intend to only `read` the data or also to `write` it. We'll
// only be allowed to `write` to component types that we reserved as such in our queries.
const velocity = movable.write(Velocity);
if (this.keysPressed.has('up'))
velocity.vy = -100; // in pixels per second
else if (this.keysPressed.has('down'))
velocity.vy = 100;
else
velocity.vy = 0;
if (this.keysPressed.has('left'))
velocity.vx = -100;
else if (this.keysPressed.has('right'))
velocity.vx = 100;
else
velocity.vx = 0;
}
}
};
exports.VelocityInputController = VelocityInputController;
exports.VelocityInputController = VelocityInputController = __decorate([
becsy_1.system
], VelocityInputController);
let Movement = class Movement extends becsy_1.System {
constructor() {
super(...arguments);
this.movables = this.query(q => q.current.with(Velocity).and.with(Position).write);
}
execute() {
for (const movable of this.movables.current) {
// We retrive both velocity (to read) and position (to write) from our entities.
const velocity = movable.read(Velocity);
const position = movable.write(Position);
// In the execute method, a system has access to `this.delta`, which is the delta time between
// the current frame and the previous one. This allows us to calculate a stable movement
// regardless of the intervals between our frames. For more on that see
// https://drewcampbell92.medium.com/understanding-delta-time-b53bf4781a03.
position.x += this.delta * velocity.vx;
position.y += this.delta * velocity.vy;
console.log("x:", position.x, "y:", position.y);
}
}
};
exports.Movement = Movement;
exports.Movement = Movement = __decorate([
becsy_1.system
], Movement);
Metadata_1.Metadata.setFields(becsy_1.World, {
x: "number",
y: "number",
z: "number",
});
becsy_1.World[symbols_1.$encoder] = EncodeOperation_1.encodeSchemaOperation;
becsy_1.World[symbols_1.$decoder] = DecodeOperation_1.decodeSchemaOperation;
becsy_1.World[symbols_1.$track] = function (changeTree, index, operation = spec_1.OPERATION.ADD) {
changeTree.change(index, operation);
};
async function main() {
// We can now create the world that all our entities and their components will live in. All system
// and component classes tagged with `@system` and `@component` will be automatically added to the
// world's `defs`, and in this case we don't need to add any other types manually.
const world = await becsy_1.World.create();
// Now we create the entity that will represent our object and add the components it will need.
// Each component type can be optionally followed by an object with initial field values.
world.createEntity(Position, Velocity);
// Finally, we set up our game loop. The `run` function will be executed once per frame.
async function run() {
// Execute the world, which will call the `execute` method of all systems in sequence. The call
// is asynchronous and we _must_ await its result, otherwise errors won't be reported properly.
await world.execute();
}
setInterval(() => run(), 1000 / 30);
}
main();
// Create an interface for reading input
const rl = readline_1.default.createInterface({
input: process.stdin,
output: process.stdout
});
// Start listening for input
process.stdin.setRawMode(true);
process.stdin.resume();
//# sourceMappingURL=ecs.js.map