UNPKG

@rtsdk/lance-topia

Version:

A Node.js based real-time multiplayer game server

206 lines (179 loc) 7.29 kB
import Serializable from './Serializable'; import BaseTypes from './BaseTypes'; /** * GameObject is the base class of all game objects. * It is created only for the purpose of clearly defining the game * object interface. * Game developers will use one of the subclasses such as DynamicObject, * or PhysicalObject. */ class GameObject extends Serializable { static get netScheme() { return { id: { type: BaseTypes.TYPES.INT32 }, playerId: { type: BaseTypes.TYPES.INT16 } }; } /** * Creates an instance of a game object. * @param {GameEngine} gameEngine - the gameEngine this object will be used in * @param {Object} options - options for instantiation of the GameObject * @param {Number} id - if set, the new instantiated object will be set to this id instead of being generated a new one. Use with caution! * @param {Object} props - additional properties for creation * @param {Number} props.playerId - the playerId value of the player who owns this object */ constructor(gameEngine, options, props) { super(); /** * The gameEngine this object will be used in * @member {GameEngine} */ this.gameEngine = gameEngine; /** * ID of this object's instance. * There are three cases of instance creation which can occur: * 1. In the normal case, the constructor is asked to assign an ID which is unique * across the entire game world, including the server and all the clients. * 2. In extrapolation mode, the client may have an object instance which does not * yet exist on the server, these objects are known as shadow objects. Their IDs must * be allocated from a different range. * 3. Also, temporary objects are created on the client side each time a sync is received. * These are used for interpolation purposes and as bending targets of position, velocity, * angular velocity, and orientation. In this case the id will be set to null. * @member {Number} */ this.id = null; if (options && 'id' in options) this.id = options.id; else if (this.gameEngine) this.id = this.gameEngine.world.getNewId(); /** * playerId of player who created this object * @member {Number} */ this.playerId = (props && props.playerId) ? props.playerId : 0; this.components = {}; } /** * Called after the object is added to to the game world. * This is the right place to add renderer sub-objects, physics sub-objects * and any other resources that should be created * @param {GameEngine} gameEngine the game engine */ onAddToWorld(gameEngine) {} /** * Called after the object is removed from game-world. * This is where renderer sub-objects and any other resources should be freed * @param {GameEngine} gameEngine the game engine */ onRemoveFromWorld(gameEngine) {} /** * Formatted textual description of the game object. * @return {String} description - a string description */ toString() { return `game-object[${this.id}]`; } /** * Formatted textual description of the game object's current bending properties. * @return {String} description - a string description */ bendingToString() { return 'no bending'; } saveState(other) { this.savedCopy = (new this.constructor(this.gameEngine, { id: null })); this.savedCopy.syncTo(other ? other : this); } /** * Bending is defined as the amount of error correction that will be applied * on the client side to a given object's physical attributes, incrementally, * by the time the next server broadcast is expected to arrive. * * When this percentage is 0.0, the client always ignores the server object's value. * When this percentage is 1.0, the server object's attributes will be applied in full. * * The GameObject bending attribute is implemented as a getter, and can provide * distinct values for position, velocity, angle, and angularVelocity. * And in each case, you can also provide overrides for local objects, * these attributes will be called, respectively, positionLocal, velocityLocal, * angleLocal, angularVelocityLocal. * * @example * get bending() { * return { * position: { percent: 1.0, min: 0.0 }, * velocity: { percent: 0.0, min: 0.0 }, * angularVelocity: { percent: 0.0 }, * angleLocal: { percent: 1.0 } * } * }; * * @memberof GameObject * @member {Object} bending */ get bending() { return { position: { percent: 1.0, min: 0.0 }, velocity: { percent: 0.0, min: 0.0 }, angularVelocity: { percent: 0.0 }, angleLocal: { percent: 1.0 } }; } // TODO: // rather than pass worldSettings on each bend, they could // be passed in on the constructor just once. bendToCurrentState(bending, worldSettings, isLocal, bendingIncrements) { if (this.savedCopy) { this.bendToCurrent(this.savedCopy, bending, worldSettings, isLocal, bendingIncrements); } this.savedCopy = null; } bendToCurrent(original, bending, worldSettings, isLocal, bendingIncrements) { } /** * synchronize this object to the state of an other object, by copying all the netscheme variables. * This is used by the synchronizer to create temporary objects, and must be implemented by all sub-classes as well. * @param {GameObject} other the other object to synchronize to */ syncTo(other) { super.syncTo(other); this.playerId = other.playerId; } // copy physical attributes to physics sub-object refreshToPhysics() {} // copy physical attributes from physics sub-object refreshFromPhysics() {} // apply a single bending increment applyIncrementalBending() { } // clean up resources destroy() {} addComponent(componentInstance) { componentInstance.parentObject = this; this.components[componentInstance.constructor.name] = componentInstance; // a gameEngine might not exist if this class is instantiated by the serializer if (this.gameEngine) { this.gameEngine.emit('componentAdded', this, componentInstance); } } removeComponent(componentName) { // todo cleanup of the component ? delete this.components[componentName]; // a gameEngine might not exist if this class is instantiated by the serializer if (this.gameEngine) { this.gameEngine.emit('componentRemoved', this, componentName); } } /** * Check whether this game object has a certain component * @param {Object} componentClass the comp * @return {Boolean} true if the gameObject contains this component */ hasComponent(componentClass) { return componentClass.name in this.components; } getComponent(componentClass) { return this.components[componentClass.name]; } } export default GameObject;