Skip to content

Instantly share code, notes, and snippets.

@chientrm
Last active December 9, 2023 03:07
Show Gist options
  • Save chientrm/a0ae90aede69975f1ffd6cb7ad2c229b to your computer and use it in GitHub Desktop.
Save chientrm/a0ae90aede69975f1ffd6cb7ad2c229b to your computer and use it in GitHub Desktop.
Reconcile glitch
import type { PlayerController } from '@chientrm/game';
import type { GameScene } from './scene';
export class Player {
ship: Phaser.GameObjects.Sprite;
flame: Phaser.GameObjects.Sprite;
controller: PlayerController;
anim: string;
constructor(scene: GameScene, controller: PlayerController, slot: number) {
this.controller = controller;
this.ship = scene.add.sprite(controller.schema.x, 550, 'Ships');
this.ship.scale = 4;
this.anim = 'middle';
this.ship.play(`${slot}-${this.anim}`);
this.flame = scene.add.sprite(controller.schema.x, 550 + 7 * 4, 'Misc');
this.flame.scale = 4;
this.flame.play('flame');
controller.onUpdate((state) => {
this.ship.x = this.flame.x = state.x;
if (this.anim !== state.anim) {
this.anim = state.anim;
this.ship.play(`${slot}-${this.anim}`);
}
});
}
}
import { ToJSON } from '@colyseus/schema/lib/types/HelperTypes';
import { CLIENT_TIMESTEP, MAP_WIDTH, SHIP_VX } from '../constants';
import { Anim, InputPayload, PlayerSchema } from '../schemas/player';
type PlayerState = ToJSON<PlayerSchema>;
type Callback = (state: PlayerState) => void;
export class PlayerController {
callback?: Callback;
inputPayloads: InputPayload[];
momentum: Anim;
sessionId: string;
schema: PlayerSchema;
constructor(schema: PlayerSchema, sessionId?: string) {
this.schema = schema;
this.sessionId = sessionId;
this.inputPayloads = [];
this.shootTick = 0;
schema.onChange(() => {
if (sessionId === schema.sessionId) {
this.reconcile(schema);
} else {
this.momentum = schema.anim;
this.update(() => {});
}
});
}
update(f: (state: PlayerState) => void) {
const state = this.schema.clone().toJSON();
f(state);
if (
state.x !== this.schema.x ||
state.anim !== this.schema.anim ||
state.tick !== this.schema.tick
) {
this.schema.x = state.x;
this.schema.anim = state.anim;
this.schema.tick = state.tick;
if (this.callback) this.callback(state);
}
}
tick(dt: number): void {
this.update((state) => {
if (this.sessionId) {
if (this.momentum === 'left') {
this.step(state, { left: true, right: false, tick: 0 });
} else if (this.momentum === 'right') {
this.step(state, { left: false, right: true, tick: 0 });
}
} else {
let inputPayload: InputPayload;
while ((inputPayload = this.inputPayloads.shift()))
this.step(state, inputPayload);
}
});
}
step(state: PlayerState, inputPayload: InputPayload) {
let anim = state.anim;
let x = state.x;
const { left, right, tick } = inputPayload;
if (left) {
anim = 'left';
x = Math.max(0, x - SHIP_VX * CLIENT_TIMESTEP);
} else if (right) {
anim = 'right';
x = Math.min(MAP_WIDTH, x + SHIP_VX * CLIENT_TIMESTEP);
} else {
anim = 'middle';
}
state.x = x;
state.anim = anim;
state.tick = tick;
}
onUpdate(callback: Callback) {
this.callback = callback;
}
reconcile(schema: PlayerSchema) {
while (
this.inputPayloads.length > 0 &&
this.inputPayloads[0].tick <= schema.tick
) {
this.inputPayloads.shift();
}
this.update((state) => {
this.inputPayloads.forEach((inputPayload) =>
this.step(state, inputPayload)
);
});
}
input(inputPayload: InputPayload) {
this.inputPayloads.push(inputPayload);
if (this.sessionId) {
this.update((state) => this.step(state, inputPayload));
}
}
}
import { Schema, type } from '@colyseus/schema';
import { MAP_WIDTH } from '../constants';
export class InputPayload {
left: boolean;
right: boolean;
tick: number;
}
export type Anim = 'middle' | 'left' | 'right';
export class PlayerSchema extends Schema {
@type('number') x = Math.random() * MAP_WIDTH;
@type('number') tick = 0;
@type('string') sessionId?: string;
@type('string') anim: Anim = 'middle';
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment