import { EventEmitter } from "events" ; import * as THREE from "three" ; import ActorTree from "./ActorTree" ; import Actor from "./Actor" ; import ActorComponent from "./ActorComponent" ; import Input from "./Input" ; import Audio from "./Audio" ; import Camera from "./components/Camera" ; export default class GameInstance extends EventEmitter { framesPerSecond = 60 ; ratio : number ; layers : string [ ] = [ "Default" ] ; tree = new ActorTree ( ) ; cachedActors : Actor [ ] = [ ] ; renderComponents : Camera [ ] = [ ] ; componentsToBeStarted : ActorComponent [ ] = [ ] ; componentsToBeDestroyed : ActorComponent [ ] = [ ] ; actorsToBeDestroyed : Actor [ ] = [ ] ; input : Input ; audio = new Audio ( ) ; threeRenderer : THREE . WebGLRenderer ; threeScene = new THREE . Scene ( ) ; debug : boolean ; skipRendering = false ; constructor ( canvas , options = { } ) { super ( ) ; this . debug = options . debug === true ; this . input = new Input ( canvas , { enableOnExit : options . enableOnExit } ) ; if ( options . layers != null ) this . layers = options . layers ; try { this . threeRenderer = new THREE . WebGLRenderer ( { canvas , precision : "mediump" , alpha : false , antialias : false , stencil : true } ) ; } catch ( e ) { return ; } this . threeRenderer . setSize ( 0 , 0 , false ) ; this . threeRenderer . autoClearColor = false ; this . threeScene . autoUpdate = false ; } tick ( accumulatedTime , callback ? ) { const updateInterval = 1 / this . framesPerSecond * 1000 ; const maxAccumulatedUpdates = 5 ; const maxAccumulatedTime = maxAccumulatedUpdates * updateInterval ; if ( accumulatedTime > maxAccumulatedTime ) accumulatedTime = maxAccumulatedTime ; let updates = 0 ; while ( accumulatedTime >= updateInterval ) { this . update ( ) ; if ( callback != null ) callback ( ) ; if ( this . input . exited ) break ; accumulatedTime -= updateInterval ; updates ++ ; } return { updates , timeLeft : accumulatedTime } ; } update ( ) { this . input . update ( ) ; this . cachedActors . length = 0 ; this . tree . walkTopDown ( ( actor ) => { this . cachedActors . push ( actor ) ; return true ; } ) ; let index = 0 ; while ( index < this . componentsToBeStarted . length ) { const component = this . componentsToBeStarted [ index ] ; if ( this . cachedActors . indexOf ( component . actor ) === - 1 ) { index ++ ; continue ; } component . start ( ) ; this . componentsToBeStarted . splice ( index , 1 ) ; } for ( const pluginName in SupEngine . earlyUpdateFunctions ) SupEngine . earlyUpdateFunctions [ pluginName ] ( this ) ; this . cachedActors . forEach ( ( actor ) => { actor . update ( ) ; } ) ; this . componentsToBeDestroyed . forEach ( ( component ) => { this . _doComponentDestruction ( component ) ; } ) ; this . componentsToBeDestroyed . length = 0 ; this . actorsToBeDestroyed . forEach ( ( actor ) => { this . _doActorDestruction ( actor ) ; } ) ; this . actorsToBeDestroyed . length = 0 ; if ( this . input . exited ) { this . threeRenderer . clear ( ) ; return ; } if ( this . skipRendering ) { this . skipRendering = false ; this . update ( ) ; return ; } } setRatio ( ratio ? ) { this . ratio = ratio ; if ( this . ratio != null ) { this . threeRenderer . domElement . style . margin = "auto" ; this . threeRenderer . domElement . style . flex = "none" ; } else { this . threeRenderer . domElement . style . margin = "0" ; this . threeRenderer . domElement . style . flex = "1" ; } this . resizeRenderer ( ) ; } resizeRenderer ( ) { let width ; let height ; if ( this . ratio != null ) { if ( document . body . clientWidth / document . body . clientHeight > this . ratio ) { height = document . body . clientHeight ; width = Math . min ( document . body . clientWidth , height * this . ratio ) ; } else { width = document . body . clientWidth ; height = Math . min ( document . body . clientHeight , width / this . ratio ) ; } } else { width = this . threeRenderer . domElement . clientWidth ; height = this . threeRenderer . domElement . clientHeight ; } if ( this . threeRenderer . domElement . width !== width || this . threeRenderer . domElement . height !== height ) { this . threeRenderer . setSize ( width , height , false ) ; this . emit ( "resize" , { width , height } ) ; } } setActiveLayer ( layer ) { for ( const cachedActor of this . cachedActors ) cachedActor . setActiveLayer ( layer ) ; } draw ( ) { this . resizeRenderer ( ) ; this . threeRenderer . clear ( ) ; this . renderComponents . sort ( ( a , b ) => { let order = ( a . depth - b . depth ) ; if ( order === 0 ) order = this . cachedActors . indexOf ( a . actor ) - this . cachedActors . indexOf ( b . actor ) ; return order ; } ) ; for ( const renderComponent of this . renderComponents ) renderComponent . render ( ) ; } clear ( ) { this . threeRenderer . clear ( ) ; } destroyComponent ( component ) { if ( component . pendingForDestruction ) return ; this . componentsToBeDestroyed . push ( component ) ; component . pendingForDestruction = true ; const index = this . componentsToBeStarted . indexOf ( component ) ; if ( index !== - 1 ) this . componentsToBeStarted . splice ( index , 1 ) ; } destroyActor ( actor ) { if ( actor . pendingForDestruction ) return ; this . actorsToBeDestroyed . push ( actor ) ; actor . _markDestructionPending ( ) ; } destroyAllActors ( ) { this . tree . walkTopDown ( ( actor ) => { this . destroyActor ( actor ) ; return true ; } ) ; this . skipRendering = true ; } _doComponentDestruction ( component ) { component . _destroy ( ) ; } _doActorDestruction ( actor ) { while ( actor . children . length > 0 ) this . _doActorDestruction ( actor . children [ 0 ] ) ; const cachedIndex = this . cachedActors . indexOf ( actor ) ; if ( cachedIndex !== - 1 ) this . cachedActors . splice ( cachedIndex , 1 ) ; actor . _destroy ( ) ; } }