SingleBaseProcess.js

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

import { Base } 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 Base}
 */
export class SingleBaseProcess {
  /**
   *
   * @param {Object3D} gameObject3D - root game object3D of your game
   * @param {Base} frame3DBase - frame3DBase 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,
    frame3DBase,
    assetManager,
    inputManager,
    options = {}
  ) {
    /**
     * game script + collision context
     *
      @type {Context} */
    this.gameContext = new Context(options.gameScriptClass || {}, gameObject3D);

    /**
     * game view
     *
      @type {Base}  */
    this.frame3DBase = frame3DBase;

    /**
     * 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.frame3DBase,
      assetManager,
      inputManager,
      options.externalGameScriptClass || {},
      { interpolator: this.interpolator }
    );

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

  dispose() {
    this.frame3DBase.dispose();
    this.inputManager.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.frame3DBase.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
        this.process.start((dt) => {
          // external game loop
          this.externalGameContext.step(
            dt,
            this.interpolator.computeCurrentStates()
          ); // simulate
          this.frame3DBase.render();
        });

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

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