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();
});
});
}
}