SinglePlanarProcess.js

import { AssetManager } from './AssetManager';
import { InputManager } from './InputManager';
import {
  ScriptBase as ExternalScriptBase,
  Context as ExternalContext,
} from './Context';

import { Planar } from '@ud-viz/frame3d';
import {
  Object3D,
  ScriptBase,
  StateInterpolator,
  Context,
} from '@ud-viz/game_shared';
import { RequestAnimationFrameProcess } from '@ud-viz/utils_browser';

/**
 * @classdesc Create a single player game in a {@link Planar}
 */
export class SinglePlanarProcess {
  /**
   *
   * @param {Object3D} gameObject3D - root game object3D of your game
   * @param {Planar} frame3DPlanar - frame3DPlanar where the game is taking place
   * @param {AssetManager} [assetManager] - assetManager of the game {@link AssetManager}
   * @param {InputManager} [inputManager] - input manager of the game {@link InputManager}
   * @param {object} options - single player game planar options
   * @param {Object<string,ScriptBase>=} options.gameScriptClass - custom game scripts class of your object3D
   * @param {Object<string,ExternalScriptBase>=} options.externalGameScriptClass - custom external scripts class of your object3D
   * @param {number=} options.interpolatorDelay - delay between state computed in game process and the ones in external context
   */
  constructor(
    gameObject3D,
    frame3DPlanar,
    assetManager = new AssetManager(),
    inputManager = new InputManager(),
    options = {}
  ) {
    /**
     * game script + collision context
     *
      @type {Context} */
    this.gameContext = new Context(options.gameScriptClass || {}, gameObject3D);

    /**
     * game view
     *
      @type {Planar}  */
    this.frame3DPlanar = frame3DPlanar;

    /**
     * asset manager
     *
      @type {AssetManager} */
    this.assetManager = assetManager;

    /**
     * input manager
     *
      @type {InputManager} */
    this.inputManager = inputManager;

    /**
     * interpolator to smooth comminucation between the two process
     *
      @type {StateInterpolator} */
    this.interpolator = new StateInterpolator(options.interpolatorDelay);

    /**
     * render audio external script context
     *
      @type {ExternalContext} */
    this.externalGameContext = new ExternalContext(
      this.frame3DPlanar,
      assetManager,
      inputManager,
      options.externalGameScriptClass || {},
      { interpolator: this.interpolator }
    );

    /** @type {RequestAnimationFrameProcess} */
    this.process = new RequestAnimationFrameProcess(30);
  }

  dispose() {
    this.frame3DPlanar.dispose();
    this.process.stop();
  }

  /**
   *
   * @returns {Promise} - promise resolving when game has started
   */
  start() {
    return new Promise((resolve) => {
      this.gameContext.load().then(() => {
        // initialize interpolator
        let previousGameState = this.gameContext.toState(false); // false because no need to send game component
        this.interpolator.onFirstState(previousGameState);

        // start process gameContext
        const gameProcess = new RequestAnimationFrameProcess(60);

        // plug inputmanager directly in game process
        this.inputManager.startListening(this.frame3DPlanar.domElement);

        gameProcess.start((dt) => {
          // game loop
          this.gameContext.onCommands(this.inputManager.computeCommands()); // pull commands
          this.gameContext.step(dt); // simulate

          // here we compute a diff with the last game state (we could just send a newState to the interpolator)
          // but this is to test multiplayer (isOutdated is used in diff forcing the update transform in external context)
          // isOutdated is also used to notify external script maybe we should use two boolean ? but no since isOutdated means object3D model has changed anyway
          const newState = this.gameContext.toState(false);
          const stateDiff = newState.sub(previousGameState);
          previousGameState = newState;

          // console.log(stateDiff);

          this.interpolator.onNewDiff(stateDiff); // send new diff of the game to interpolator
        });

        // indicate to the external context how to send command to gamecontext (could be with websocket)
        this.externalGameContext.sendCommandsToGameContext = (cmds) => {
          this.gameContext.onCommands(cmds);
        };

        // step external game context

        // METHOD 1 ITOWNS MAIN LOOP NO SMOOTH RENDERING NO CONTROL DT

        // this.frame3DPlanar.itownsView.addFrameRequester(
        //   itowns.MAIN_LOOP_EVENTS.UPDATE_START,
        //   (dt) => {
        //     this.externalGameContext.step(dt, this.interpolator.computeCurrentStates());
        //   }
        // );

        // METHOD 2 REQUESTANIMATIONFRAME
        this.frame3DPlanar.enableItownsViewRendering(false);
        this.process.start((dt) => {
          // external game loop
          this.externalGameContext.step(
            dt,
            this.interpolator.computeCurrentStates()
          ); // simulate
          this.frame3DPlanar.render();
        });

        // DEBUG PRINT
        this.inputManager.addKeyInput('p', 'keyup', () => {
          console.log('game ', this);
        });

        resolve();
      });
    });
  }
}