import {create as createClient} from '@avocado/client'; import {EntityList} from '@avocado/entity'; import {ActionRegistry} from '@avocado/input'; import {Container, Renderer} from '@avocado/graphics'; import {World} from '@avocado/physics/matter/world'; import {StateSynchronizer} from '@avocado/state'; const renderer = new Renderer([640, 360]); const stage = new Container(); const entityList = new EntityList(); entityList.world = new World(); const stateSynchronizer = new StateSynchronizer({ entityList, }); let selfEntity; function hasSelfEntity() { return selfEntity && 'string' !== typeof selfEntity; } entityList.on('entityAdded', (entity) => { // Add to graphics. if ('container' in entity) { stage.addChild(entity.container); } // Set self entity. if ('string' === typeof selfEntity) { const mappedUuid = entityList.mappedUuid(selfEntity); if (entity.instanceUuid === mappedUuid) { selfEntity = entity; } } else { // Only self entity should be controllable. entity.removeTrait('controllable'); } }); entityList.on('entityRemoved', (entity) => { // Remove from graphics. if ('container' in entity) { stage.removeChild(entity.container); } }); // Accept input. const actionRegistry = new ActionRegistry(); actionRegistry.mapKeysToActions({ 'w': 'MoveUp', 'a': 'MoveLeft', 's': 'MoveDown', 'd': 'MoveRight', }); actionRegistry.listen(); let actionState = actionRegistry.state(); // Create the socket connection. const socket = createClient(window.location.href); // Create the graphics canvas. const appNode = document.querySelector('.app'); appNode.appendChild(renderer.element); // Input messages. const messageHandle = setInterval(() => { if (actionState !== actionRegistry.state()) { actionState = actionRegistry.state(); socket.send({ type: 'input', payload: actionState.toJS() }); } }, 1000 / 60); // Prediction. let lastTime = performance.now(); const predictionHandle = setInterval(() => { const now = performance.now(); const elapsed = (now - lastTime) / 1000; lastTime = now; if (hasSelfEntity()) { selfEntity.inputState = actionState.toJS(); } entityList.tick(elapsed); }, 1000 / 80); // State updates. let dirty = false; function onMessage({type, payload}) { switch (type) { case 'state-update': if (payload.selfEntity) { selfEntity = payload.selfEntity; } stateSynchronizer.acceptStateChange(payload); dirty = true; break; default: } } socket.on('message', onMessage); // Render. function render() { stage.tick(); if (!dirty) { return; } renderer.render(stage); dirty = false; } const renderHandle = setInterval(render, 1000 / 60); // Hot reloading. if (module.hot) { module.hot.accept((error) => { console.error(error); }); module.hot.dispose(() => { entityList.destroy(); appNode.removeChild(renderer.element); stage.destroy(); renderer.destroy(); }); }