UNPKG

@rtsdk/lance-topia

Version:

A Node.js based real-time multiplayer game server

258 lines (221 loc) 10.2 kB
import GameObject from './GameObject'; import BaseTypes from './BaseTypes'; import TwoVector from './TwoVector'; import MathUtils from '../lib/MathUtils'; /** * The PhysicalObject2D is the base class for physical game objects in 2D Physics */ class PhysicalObject2D extends GameObject { /** * The netScheme is a dictionary of attributes in this game * object. The attributes listed in the netScheme are those exact * attributes which will be serialized and sent from the server * to each client on every server update. * The netScheme member is implemented as a getter. * * You may choose not to implement this method, in which * case your object only transmits the default attributes * which are already part of {@link PhysicalObject2D}. * But if you choose to add more attributes, make sure * the return value includes the netScheme of the super class. * * @memberof PhysicalObject2D * @member {Object} netScheme * @example * static get netScheme() { * return Object.assign({ * mojo: { type: BaseTypes.TYPES.UINT8 }, * }, super.netScheme); * } */ static get netScheme() { return Object.assign({ mass: { type: BaseTypes.TYPES.FLOAT32 }, position: { type: BaseTypes.TYPES.CLASSINSTANCE }, angle: { type: BaseTypes.TYPES.FLOAT32 }, velocity: { type: BaseTypes.TYPES.CLASSINSTANCE }, angularVelocity: { type: BaseTypes.TYPES.FLOAT32 } }, super.netScheme); } /** * Creates an instance of a physical object. * Override to provide starting values for position, velocity, angle and angular velocity. * NOTE: all subclasses of this class must comply with this constructor signature. * This is required because the engine will create temporary instances when * syncs arrive on the clients. * @param {GameEngine} gameEngine - the gameEngine this object will be used in * @param {Object} options - options for the new object. See {@link GameObject} * @param {Object} props - properties to be set in the new object * @param {TwoVector} props.position - position vector * @param {TwoVector} props.velocity - velocity vector * @param {Number} props.angle - orientation angle * @param {Number} props.mass - the mass * @param {Number} props.angularVelocity - angular velocity */ constructor(gameEngine, options, props) { super(gameEngine, options, props); this.bendingIncrements = 0; // set default position, velocity and quaternion this.position = new TwoVector(0, 0); this.velocity = new TwoVector(0, 0); this.angle = 0; this.angularVelocity = 0; this.mass = 0; // use values if provided props = props || {}; if (props.position) this.position.copy(props.position); if (props.velocity) this.velocity.copy(props.velocity); if (props.angle) this.angle = props.angle; if (props.angularVelocity) this.angularVelocity = props.angularVelocity; if (props.mass) this.mass = props.mass; this.class = PhysicalObject2D; } /** * 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 */ onAddToWorld() {} /** * Formatted textual description of the dynamic object. * The output of this method is used to describe each instance in the traces, * which significantly helps in debugging. * * @return {String} description - a string describing the PhysicalObject2D */ toString() { let p = this.position.toString(); let v = this.velocity.toString(); let a = this.angle; let av = this.angularVelocity; return `phyObj2D[${this.id}] player${this.playerId} Pos=${p} Vel=${v} Ang=${a} AVel=${av}`; } /** * Each object class can define its own bending overrides. * return an object which can include attributes: position, velocity, * angle, and angularVelocity. In each case, you can specify a min value, max * value, and a percent value. * * @return {Object} bending - an object with bending paramters */ get bending() { return { // example: // position: { percent: 0.8, min: 0.0, max: 4.0 }, // velocity: { percent: 0.4, min: 0.0 }, // angularVelocity: { percent: 0.0 }, // angleLocal: { percent: 0.0 } }; } // display object's physical attributes as a string // for debugging purposes mostly bendingToString() { if (this.bendingIncrements) return `ΔPos=${this.bendingPositionDelta} ΔVel=${this.bendingVelocityDelta} ΔAngle=${this.bendingAngleDelta} increments=${this.bendingIncrements}`; return 'no bending'; } // derive and save the bending increment parameters: // - bendingPositionDelta // - bendingVelocityDelta // - bendingAVDelta // - bendingAngleDelta // these can later be used to "bend" incrementally from the state described // by "original" to the state described by "self" bendToCurrent(original, percent, worldSettings, isLocal, increments) { let bending = { increments, percent }; // if the object has defined a bending multiples for this object, use them let positionBending = Object.assign({}, bending, this.bending.position); let velocityBending = Object.assign({}, bending, this.bending.velocity); let angleBending = Object.assign({}, bending, this.bending.angle); let avBending = Object.assign({}, bending, this.bending.angularVelocity); // check for local object overrides to bendingTarget if (isLocal) { Object.assign(positionBending, this.bending.positionLocal); Object.assign(velocityBending, this.bending.velocityLocal); Object.assign(angleBending, this.bending.angleLocal); Object.assign(avBending, this.bending.angularVelocityLocal); } // get the incremental delta position & velocity this.incrementScale = percent / increments; this.bendingPositionDelta = original.position.getBendingDelta(this.position, positionBending); this.bendingVelocityDelta = original.velocity.getBendingDelta(this.velocity, velocityBending); // get the incremental angular-velocity this.bendingAVDelta = (this.angularVelocity - original.angularVelocity) * this.incrementScale * avBending.percent; // get the incremental angle correction this.bendingAngleDelta = MathUtils.interpolateDeltaWithWrapping(original.angle, this.angle, angleBending.percent, 0, 2 * Math.PI) / increments; this.bendingTarget = (new this.constructor()); this.bendingTarget.syncTo(this); // revert to original this.position.copy(original.position); this.angle = original.angle; this.angularVelocity = original.angularVelocity; this.velocity.copy(original.velocity); this.bendingIncrements = increments; this.bendingOptions = bending; this.refreshToPhysics(); } syncTo(other, options) { super.syncTo(other); this.position.copy(other.position); this.angle = other.angle; this.angularVelocity = other.angularVelocity; if (!options || !options.keepVelocity) { this.velocity.copy(other.velocity); } if (this.physicsObj) this.refreshToPhysics(); } // update position, angle, angular velocity, and velocity from new physical state. refreshFromPhysics() { this.copyVector(this.physicsObj.position, this.position); this.copyVector(this.physicsObj.velocity, this.velocity); this.angle = this.physicsObj.angle; this.angularVelocity = this.physicsObj.angularVelocity; } // generic vector copy. We need this because different // physics engines have different implementations. // TODO: Better implementation: the physics engine implementor // should define copyFromLanceVector and copyToLanceVector copyVector(source, target) { let sourceVec = source; if (typeof source[0] === 'number' && typeof source[1] === 'number') sourceVec = { x: source[0], y: source[1] }; if (typeof target.copy === 'function') { target.copy(sourceVec); } else if (target instanceof Float32Array) { target[0] = sourceVec.x; target[1] = sourceVec.y; } else { target.x = sourceVec.x; target.y = sourceVec.y; } } // update position, angle, angular velocity, and velocity from new game state. refreshToPhysics() { this.copyVector(this.position, this.physicsObj.position); this.copyVector(this.velocity, this.physicsObj.velocity); this.physicsObj.angle = this.angle; this.physicsObj.angularVelocity = this.angularVelocity; } // apply one increment of bending applyIncrementalBending(stepDesc) { if (this.bendingIncrements === 0) return; let timeFactor = 1; if (stepDesc && stepDesc.dt) timeFactor = stepDesc.dt / (1000 / 60); const posDelta = this.bendingPositionDelta.clone().multiplyScalar(timeFactor); const velDelta = this.bendingVelocityDelta.clone().multiplyScalar(timeFactor); this.position.add(posDelta); this.velocity.add(velDelta); this.angularVelocity += (this.bendingAVDelta * timeFactor); this.angle += (this.bendingAngleDelta * timeFactor); this.bendingIncrements--; } // interpolate implementation interpolate(nextObj, percent) { // slerp to target position this.position.lerp(nextObj.position, percent); this.angle = MathUtils.interpolateDeltaWithWrapping(this.angle, nextObj.angle, percent, 0, 2 * Math.PI); } } export default PhysicalObject2D;