UNPKG

wordpress-plugin

Version:

Boilerplate WordPress Plugin structure to support development of JavaScript frameworks: React, Vue, Vanilla, TypeScript, supporting Vite, NextJS, Nuxt, Gatsby, and JSX/TSX Components (like MUI), CSS (like Tailwind + Unity), data APIs (like REST and GraphQ

1,252 lines (1,076 loc) 38 kB
window.onload = function(e){ const game = new Game(); window.game = game;//For debugging only } class Game{ constructor(){ if ( ! Detector.webgl ) Detector.addGetWebGLMessage(); const pluginName = postdata.plugin_name; const pluginVersion = postdata.plugin_version; const pluginURL = postdata.plugin_url; const themeURI = postdata.theme_uri; const restURL = postdata.rest_url; const worldID = postdata.world_id; console.log("-----------------------"); console.log("pluginName-------------"); console.log(pluginName, pluginVersion); console.log("-----------------------"); this.modes = Object.freeze({ NONE: Symbol("none"), PRELOAD: Symbol("preload"), INITIALISING: Symbol("initialising"), CREATING_LEVEL: Symbol("creating_level"), ACTIVE: Symbol("active"), GAMEOVER: Symbol("gameover") }); this.mode = this.modes.NONE; this.container; this.player = { }; this.stats; this.controls; this.camera; this.scene; this.renderer; this.cellSize = 16; this.interactive = false; this.levelIndex = 0; this._hints = 0; this.score = 0; this.debug = false; this.debugPhysics = false; this.cameraFade = 0.05; this.mute = false; this.collect = []; this.messages = { text:[ "Welcome to LostTreasure", "GOOD LUCK!" ], index:0 } if (localStorage && !this.debug){ //const levelIndex = Number(localStorage.getItem('levelIndex')); //if (levelIndex!=undefined) this.levelIndex = levelIndex; } this.container = document.createElement( 'div' ); this.container.style.height = '100%'; document.body.appendChild( this.container ); const sfxExt = SFX.supportsAudioType('mp3') ? 'mp3' : 'ogg'; const game = this; this.anims = ["ascend-stairs", "gather-objects", "look-around", "push-button", "run"]; this.tweens = []; this.assetsPath = `${pluginURL}assets/`; const options = { assets:[ `${this.assetsPath}sfx/gliss.${sfxExt}`, `${this.assetsPath}sfx/factory.${sfxExt}`, `${this.assetsPath}sfx/button.${sfxExt}`, `${this.assetsPath}sfx/door.${sfxExt}`, `${this.assetsPath}sfx/fan.${sfxExt}`, `${this.assetsPath}fbx/environment.fbx`, `${this.assetsPath}fbx/girl-walk.fbx`, `${this.assetsPath}fbx/usb.fbx`, ], oncomplete: function(){ game.init(); game.animate(); } } this.anims.forEach( function(anim){ options.assets.push(`${game.assetsPath}fbx/${anim}.fbx`)}); this.mode = this.modes.PRELOAD; document.getElementById("camera-btn").onclick = function(){ game.switchCamera(); }; document.getElementById("briefcase-btn").onclick = function(){ game.toggleBriefcase(); }; document.getElementById("action-btn").onclick = function(){ game.contextAction(); }; document.getElementById("sfx-btn").onclick = function(){ game.toggleSound(); }; this.actionBtn = document.getElementById("action-btn"); this.clock = new THREE.Clock(); //this.init(); //this.animate(); const preloader = new Preloader(options); } toggleBriefcase(){ const briefcase = document.getElementById("briefcase"); const open = (briefcase.style.opacity > 0); if (open){ briefcase.style.opacity = "0"; }else{ briefcase.style.opacity = "1"; } } toggleSound(){ this.mute = !this.mute; const btn = document.getElementById('sfx-btn'); if (this.mute){ for(let prop in this.sfx){ let sfx = this.sfx[prop]; if (sfx instanceof SFX) sfx.stop(); } btn.innerHTML = '<i class="fas fa-volume-off"></i>'; }else{ this.sfx.factory.play this.sfx.fan.play(); btn.innerHTML = '<i class="fas fa-volume-up"></i>'; } } contextAction(){ console.log('contextAction called ' + JSON.stringify(this.onAction)); if (this.onAction !== undefined){ if (this.onAction.action!=undefined){ this.action = this.onAction.action; } } const game = this; if (this.onAction.mode !== undefined){ switch(this.onAction.mode){ case 'open-doors': this.sfx.door.play(); this.sfx.button.play(); const door = this.doors[this.onAction.index]; const left = door.doors[0]; const right = door.doors[1]; this.cameraTarget = { position:left.position.clone(), target:left.position.clone() }; this.cameraTarget.position.y += 150; this.cameraTarget.position.x -= 950; //target, channel, endValue, duration, oncomplete, easing="inOutQuad"){ this.tweens.push( new Tween(left.position, "z", left.position.z - 240, 2, function(){ game.tweens.splice(game.tweens.indexOf(this), 1); })); this.tweens.push( new Tween(right.position, "z", right.position.z + 240, 2, function(){ game.tweens.splice(game.tweens.indexOf(this), 1); delete game.cameraTarget; const door = game.doors[this.onAction.index]; const left = door.doors[0]; const right = door.doors[1]; const leftProxy = door.proxy[0]; const rightProxy = door.proxy[1]; leftProxy.position = left.position.clone(); rightProxy.position = right.position.clone(); })) break; case 'collect': this.activeCamera = this.player.cameras.collect; this.collect[this.onAction.index].visible = false; if (this.collected==undefined) this.collected = []; this.collected.push(this.onAction.index); document.getElementById("briefcase").children[0].children[0].children[this.onAction.index].children[0].src = this.onAction.src; break; } } } switchCamera(fade=0.05){ const cams = Object.keys(this.player.cameras); cams.splice(cams.indexOf('active'), 1); let index; for(let prop in this.player.cameras){ if (this.player.cameras[prop]==this.player.cameras.active){ index = cams.indexOf(prop) + 1; if (index>=cams.length) index = 0; this.player.cameras.active = this.player.cameras[cams[index]]; break; } } this.cameraFade = fade; } initSfx(){ this.sfx = {}; this.sfx.context = new (window.AudioContext || window.webkitAudioContext)(); const list = ['gliss','door','factory','button','fan']; const game = this; list.forEach(function(item){ game.sfx[item] = new SFX({ context: game.sfx.context, src:{mp3:`${game.assetsPath}sfx/${item}.mp3`, ogg:`${game.assetsPath}sfx/${item}.ogg`}, loop: (item=='factory' || item=='fan'), autoplay: (item=='factory'|| item=='fan'), volume: 0.3 }); }) } set activeCamera(object){ this.player.cameras.active = object; } init() { this.mode = this.modes.INITIALISING; this.camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 2000 ); let col = 0x605050; this.scene = new THREE.Scene(); this.scene.background = new THREE.Color( col ); this.scene.fog = new THREE.Fog( col, 500, 1500 ); let light = new THREE.HemisphereLight( 0xffffff, 0x444444 ); light.position.set( 0, 200, 0 ); this.scene.add( light ); light = new THREE.DirectionalLight( 0xffffff ); light.position.set( 0, 200, 100 ); light.castShadow = true; light.shadow.mapSize.width = 2048; light.shadow.mapSize.height = 2048; light.shadow.camera.top = 3000; light.shadow.camera.bottom = -3000; light.shadow.camera.left = -3000; light.shadow.camera.right = 3000; light.shadow.camera.far = 3000; this.scene.add( light ); // ground var mesh = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2000, 2000 ), new THREE.MeshPhongMaterial( { color: 0x999999, depthWrite: false } ) ); mesh.rotation.x = - Math.PI / 2; //mesh.position.y = -100; mesh.receiveShadow = true; //this.scene.add( mesh ); var grid = new THREE.GridHelper( 2000, 40, 0x000000, 0x000000 ); //grid.position.y = -100; grid.material.opacity = 0.2; grid.material.transparent = true; //this.scene.add( grid ); // model const loader = new THREE.FBXLoader(); const game = this; loader.load( `${this.assetsPath}fbx/girl-walk.fbx`, function ( object ) { object.mixer = new THREE.AnimationMixer( object ); object.mixer.addEventListener('finished', function(e){ game.action = 'look-around'; if (game.player.cameras.active == game.player.cameras.collect){ game.activeCamera = game.player.cameras.back; game.toggleBriefcase(); } }) object.castShadow = true; game.player.mixer = object.mixer; game.player.root = object.mixer.getRoot(); object.name = "Character"; object.traverse( function ( child ) { if ( child.isMesh ) { child.castShadow = true; child.receiveShadow = true; } } ); game.scene.add(object); game.player.object = object; game.player.walk = object.animations[0]; game.joystick = new JoyStick({ onMove: game.playerControl, game: game }); game.createCameras(); game.loadEnvironment(loader); }, null, this.onError ); this.renderer = new THREE.WebGLRenderer( { antialias: true } ); this.renderer.setPixelRatio( window.devicePixelRatio ); this.renderer.setSize( window.innerWidth, window.innerHeight ); this.renderer.shadowMap.enabled = true; this.renderer.shadowMap.type = THREE.PCFSoftShadowMap; // default THREE.PCFShadowMap this.renderer.shadowMapDebug = true; this.container.appendChild( this.renderer.domElement ); window.addEventListener( 'resize', function(){ game.onWindowResize(); }, false ); // stats if (this.debug){ this.stats = new Stats(); this.container.appendChild( this.stats.dom ); } this.initSfx(); } loadUSB(loader){ const game = this; loader.load( `${this.assetsPath}fbx/usb.fbx`, function ( object ) { game.scene.add(object); const scale = 0.2; object.scale.set(scale, scale, scale); object.name = "usb"; object.position.set(-416, 0.8, -472); object.castShadow = true; game.collect.push(object); object.traverse( function ( child ) { if ( child.isMesh ) { child.castShadow = true; child.receiveShadow = true; } } ); game.loadNextAnim(loader); }, null, this.onError ); } loadEnvironment(loader){ const game = this; loader.load( `${this.assetsPath}fbx/environment.fbx`, function ( object ) { game.scene.add(object); game.doors = []; game.fans = []; object.receiveShadow = true; object.scale.set(0.8, 0.8, 0.8); object.name = "Environment"; let door = { trigger:null, proxy:[], doors:[]}; object.traverse( function ( child ) { if ( child.isMesh ) { if (child.name.includes('main')){ child.castShadow = true; child.receiveShadow = true; }else if (child.name.includes('mentproxy')){ child.material.visible = false; game.environmentProxy = child; }else if (child.name.includes('door-proxy')){ child.material.visible = false; door.proxy.push(child); checkDoor(); }else if (child.name.includes('door')){ door.doors.push(child); checkDoor() }else if (child.name.includes('fan')){ game.fans.push(child); } }else{ if (child.name.includes('Door-null')){ door.trigger = child; checkDoor(); } } function checkDoor(){ if (door.trigger!==null && door.proxy.length==2 && door.doors.length==2){ game.doors.push(Object.assign({}, door)); door = { trigger:null, proxy:[], doors:[]}; } } } ); game.loadUSB(loader); }, null, this.onError ); } createDummyEnvironment(){ const env = new THREE.Group(); env.name = "Environment"; this.scene.add(env); const geometry = new THREE.BoxBufferGeometry( 150, 150, 150 ); const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); for(let x=-1000; x<1000; x+=300){ for(let z=-1000; z<1000; z+=300){ const block = new THREE.Mesh(geometry, material); block.position.set(x, 75, z); env.add(block); } } this.environmentProxy = env; } playerControl(forward, turn){ //console.log(`playerControl(${forward}), ${turn}`); turn = -turn; if (forward==0 && turn==0){ delete this.player.move; }else{ this.player.move = { forward, turn }; } if (forward>0){ if (this.player.action!='walk'&& this.player.action!='run') this.action = 'walk'; }else if (forward<-0.2){ if (this.player.action!='walk') this.action = 'walk'; }else{ if (this.player.action=="walk" || this.player.action=='run') this.action = 'look-around'; } } createCameras(){ const front = new THREE.Object3D(); front.position.set(112, 100, 200); front.parent = this.player.object; const back = new THREE.Object3D(); back.position.set(0, 100, -250); back.parent = this.player.object; const wide = new THREE.Object3D(); wide.position.set(178, 139, 465); wide.parent = this.player.object; const overhead = new THREE.Object3D(); overhead.position.set(0, 400, 0); overhead.parent = this.player.object; const collect = new THREE.Object3D(); collect.position.set(40, 82, 94); collect.parent = this.player.object; this.player.cameras = { front, back, wide, overhead, collect }; game.activeCamera = this.player.cameras.wide; game.cameraFade = 1; setTimeout( function(){ game.activeCamera = game.player.cameras.back; game.cameraFade = 0.01; setTimeout(function(){ game.cameraFade = 0.1; }, 1500); }, 2000) } loadNextAnim(loader){ let anim = this.anims.pop(); const game = this; loader.load( `${this.assetsPath}fbx/${anim}.fbx`, function( object ){ game.player[anim] = object.animations[0]; if (anim=='push-button'){ game.player[anim].loop = false; } if (game.anims.length>0){ game.loadNextAnim(loader); }else{ delete game.anims; game.action = "look-around"; game.initPlayerPosition(); game.mode = game.modes.ACTIVE; const overlay = document.getElementById("overlay"); overlay.classList.add("fade-in"); overlay.addEventListener("animationend", function(evt){ evt.target.style.display = 'none'; }, false); } }, null, this.onError); } initPlayerPosition(){ //cast down const dir = new THREE.Vector3(0,-1,0); const pos = this.player.object.position.clone(); pos.y += 200; const raycaster = new THREE.Raycaster(pos, dir); const gravity = 30; const box = this.environmentProxy; const intersect = raycaster.intersectObject(box); if (intersect.length>0){ this.player.object.position.y = pos.y - intersect[0].distance; } } getMousePosition(clientX, clientY){ const pos = new THREE.Vector2(); pos.x = (clientX / this.renderer.domElement.clientWidth) * 2 - 1; pos.y = -(clientY / this.renderer.domElement.clientHeight) * 2 + 1; return pos; } showMessage(msg, fontSize=20, onOK=null){ const txt = document.getElementById('message_text'); txt.innerHTML = msg; txt.style.fontSize = fontSize + 'px'; const btn = document.getElementById('message_ok'); const panel = document.getElementById('message'); const game = this; if (onOK!=null){ btn.onclick = function(){ panel.style.display = 'none'; onOK.call(game); } }else{ btn.onclick = function(){ panel.style.display = 'none'; } } panel.style.display = 'flex'; } loadJSON(name, callback) { var xobj = new XMLHttpRequest(); xobj.overrideMimeType("application/json"); xobj.open('GET', `${name}.json`, true); // Replace 'my_data' with the path to your file xobj.onreadystatechange = function () { if (xobj.readyState == 4 && xobj.status == "200") { // Required use of an anonymous callback as .open will NOT return a value but simply returns undefined in asynchronous mode callback(xobj.responseText); } }; xobj.send(null); } onWindowResize() { this.camera.aspect = window.innerWidth / window.innerHeight; this.camera.updateProjectionMatrix(); this.renderer.setSize( window.innerWidth, window.innerHeight ); } set action(name){ if (this.player.action==name) return; const anim = this.player[name]; const action = this.player.mixer.clipAction( anim, this.player.root ); this.player.mixer.stopAllAction(); this.player.action = name; action.timeScale = (name=='walk' && this.player.move!=undefined && this.player.move.forward<0) ? -0.3 : 1; action.time = 0; action.fadeIn(0.5); if (name=='push-button' || name=='gather-objects') action.loop = THREE.LoopOnce; action.play(); this.player.actionTime = Date.now(); } movePlayer(dt){ const pos = this.player.object.position.clone(); pos.y += 60; let dir = new THREE.Vector3(); this.player.object.getWorldDirection(dir); if (this.player.move.forward<0) dir.negate(); let raycaster = new THREE.Raycaster(pos, dir); let blocked = false; const box = this.environmentProxy; if (this.environmentProxy!=undefined){ const intersect = raycaster.intersectObject(box); if (intersect.length>0){ if (intersect[0].distance<50) blocked = true; } } if (!blocked){ if (this.player.move.forward>0){ const speed = (this.player.action=='run') ? 200 : 100; this.player.object.translateZ(dt*speed); }else{ this.player.object.translateZ(-dt*30); } } if (this.environmentProxy!=undefined){ //cast left dir.set(-1,0,0); dir.applyMatrix4(this.player.object.matrix); dir.normalize(); raycaster = new THREE.Raycaster(pos, dir); let intersect = raycaster.intersectObject(box); if (intersect.length>0){ if (intersect[0].distance<50) this.player.object.translateX(50-intersect[0].distance); } //cast right dir.set(1,0,0); dir.applyMatrix4(this.player.object.matrix); dir.normalize(); raycaster = new THREE.Raycaster(pos, dir); intersect = raycaster.intersectObject(box); if (intersect.length>0){ if (intersect[0].distance<50) this.player.object.translateX(intersect[0].distance-50); } //cast down dir.set(0,-1,0); pos.y += 200; raycaster = new THREE.Raycaster(pos, dir); const gravity = 30; intersect = raycaster.intersectObject(box); if (intersect.length>0){ const targetY = pos.y - intersect[0].distance; if (targetY > this.player.object.position.y){ //Going up this.player.object.position.y = 0.8 * this.player.object.position.y + 0.2 * targetY; this.player.velocityY = 0; }else if (targetY < this.player.object.position.y){ //Falling if (this.player.velocityY==undefined) this.player.velocityY = 0; this.player.velocityY += dt * gravity; this.player.object.position.y -= this.player.velocityY; if (this.player.object.position.y < targetY){ this.player.velocityY = 0; this.player.object.position.y = targetY; } } } } } animate() { const game = this; const dt = this.clock.getDelta(); requestAnimationFrame( function(){ game.animate(); } ); if (this.tweens.length>0){ this.tweens.forEach(function(tween){ tween.update(dt); }); } if (this.player.mixer!=undefined && this.mode==this.modes.ACTIVE){ this.player.mixer.update(dt); } if (this.player.action=='walk'){ const elapsedTime = Date.now() - this.player.actionTime; if (elapsedTime>1000 && this.player.move.forward>0) this.action = 'run'; } if (this.player.move!=undefined){ if (this.player.move.forward!=0) this.movePlayer(dt); this.player.object.rotateY(this.player.move.turn*dt); } if (this.player.cameras!=undefined && this.player.cameras.active!=undefined){ this.camera.position.lerp(this.player.cameras.active.getWorldPosition(new THREE.Vector3()), this.cameraFade); let pos; if (this.cameraTarget!=undefined){ this.camera.position.copy(this.cameraTarget.position); pos = this.cameraTarget.target; }else{ pos = this.player.object.position.clone(); pos.y += 60; } this.camera.lookAt(pos); } this.actionBtn.style = 'display:none;'; let trigger = false; if (this.doors !== undefined){ this.doors.forEach(function(door){ if (game.player.object.position.distanceTo(door.trigger.position)<100){ game.actionBtn.style = 'display:block;'; game.onAction = { action:'push-button', mode:'open-doors', index:0 }; trigger = true; } }); } if (this.collect !== undefined && !trigger){ this.collect.forEach(function(object){ if (object.visible && game.player.object.position.distanceTo(object.position)<100){ game.actionBtn.style = 'display:block;'; game.onAction = { action:'gather-objects', mode:'collect', index:0, src:"usb.jpg" }; trigger = true; } }); } if (!trigger) delete this.onAction; if (this.fans !== undefined){ let vol = 0; this.fans.forEach(function(fan){ const dist = fan.position.distanceTo(game.player.object.position); const tmpVol = 1 - dist/1000; if (tmpVol>vol) vol = tmpVol; fan.rotateZ(dt); }); this.sfx.fan.volume = vol; } this.renderer.render( this.scene, this.camera ); if (this.stats!=undefined) this.stats.update(); } onError(error){ const msg = console.error(JSON.stringify(error)); console.error(error.message); } } class Easing{ // t: current time, b: begInnIng value, c: change In value, d: duration constructor(start, end, duration, startTime=0, type='linear'){ this.b = start; this.c = end - start; this.d = duration; this.type = type; this.startTime = startTime; } value(time){ this.t = time - this.startTime; return this[this.type](); } linear(){ return this.c*(this.t/this.d) + this.b; } inQuad() { return this.c*(this.t/=this.d)*this.t + this.b; } outQuad() { return -this.c*(this.t/=this.d)*(this.t-2) + this.b; } inOutQuad() { if ((this.t/=this.d/2) < 1) return this.c/2*this.t*this.t + this.b; return -this.c/2 * ((--this.t)*(this.t-2) - 1) + this.b; } projectile(){ let c = this.c; let b = this.b; let t = this.t; this.t *= 2; let result; let func; if (this.t<this.d){ result = this.outQuad(); func = "outQuad"; }else{ this.t -= this.d; this.b += c; this.c = -c; result = this.inQuad(); func = "inQuad"; } console.log("projectile: " + result.toFixed(2) + " time:" + this.t.toFixed(2) + " func:" + func); this.b = b; this.c = c; this.t = t; return result; } inCubic() { return this.c*(this.t/=this.d)*this.t*this.t + this.b; } outCubic() { return this.c*((this.t=this.t/this.d-1)*this.t*this.t + 1) + this.b; } inOutCubic() { if ((this.t/=this.d/2) < 1) return this.c/2*this.t*this.t*this.t + this.b; return this.c/2*((this.t-=2)*this.t*this.t + 2) + this.b; } inQuart() { return this.c*(this.t/=this.d)*this.t*this.t*this.t + this.b; } outQuart() { return -this.c * ((this.t=this.t/this.d-1)*this.t*this.t*this.t - 1) + this.b; } inOutQuart() { if ((this.t/=this.d/2) < 1) return this.c/2*this.t*this.t*this.t*this.t + this.b; return -this.c/2 * ((this.t-=2)*this.t*this.t*this.t - 2) + this.b; } inQuint() { return this.c*(this.t/=this.d)*this.t*this.t*this.t*this.t + this.b; } outQuint() { return this.c*((this.t=this.t/this.d-1)*this.t*this.t*this.t*this.t + 1) + this.b; } inOutQuint() { if ((this.t/=this.d/2) < 1) return this.c/2*this.t*this.t*this.t*this.t*this.t + this.b; return this.c/2*((this.t-=2)*this.t*this.t*this.t*this.t + 2) + this.b; } inSine() { return -this.c * Math.cos(this.t/this.d * (Math.PI/2)) + this.c + this.b; } outSine() { return this.c * Math.sin(this.t/this.d * (Math.PI/2)) + this.b; } inOutSine() { return -this.c/2 * (Math.cos(Math.PI*this.t/this.d) - 1) + this.b; } inExpo() { return (this.t==0) ? this.b : this.c * Math.pow(2, 10 * (this.t/this.d - 1)) + this.b; } outExpo() { return (this.t==this.d) ? this.b+this.c : this.c * (-Math.pow(2, -10 * this.t/this.d) + 1) + this.b; } inOutExpo() { if (this.t==0) return this.b; if (this.t==this.d) return this.b+this.c; if ((this.t/=this.d/2) < 1) return this.c/2 * Math.pow(2, 10 * (this.t - 1)) + this.b; return this.c/2 * (-Math.pow(2, -10 * --this.t) + 2) + this.b; } inCirc() { return -this.c * (Math.sqrt(1 - (this.t/=this.d)*this.t) - 1) + this.b; } outCirc() { return this.c * Math.sqrt(1 - (this.t=this.t/this.d-1)*this.t) + this.b; } inOutCirc() { if ((this.t/=this.d/2) < 1) return -this.c/2 * (Math.sqrt(1 - this.t*this.t) - 1) + this.b; return this.c/2 * (Math.sqrt(1 - (this.t-=2)*this.t) + 1) + this.b; } inElastic() { let s=1.70158, p=0, a=this.c; if (this.t==0) return this.b; if ((this.t/=this.d)==1) return this.b+this.c; if (!p) p=this.d*.3; if (a < Math.abs(this.c)) { a=this.c; let s=p/4; } else{ let s = p/(2*Math.PI) * Math.asin (this.c/a) }; return -(a*Math.pow(2,10*(this.t-=1)) * Math.sin( (this.t*this.d-s)*(2*Math.PI)/p )) + this.b; } outElastic() { let s=1.70158, p=0, a=this.c; if (this.t==0) return this.b; if ((this.t/=this.d)==1) return this.b+this.c; if (!p) p=this.d*.3; if (a < Math.abs(this.c)) { a=this.c; let s=p/4; } else{ let s = p/(2*Math.PI) * Math.asin (this.c/a) }; return a*Math.pow(2,-10*this.t) * Math.sin( (this.t*this.d-s)*(2*Math.PI)/p ) + this.c + this.b; } inOutElastic() { let s=1.70158, p=0, a=this.c; if (this.t==0) return this.b; if ((this.t/=this.d/2)==2) return this.b+this.c; if (!p) p=this.d*(.3*1.5); if (a < Math.abs(this.c)) { a=this.c; let s=p/4; } else{ let s = p/(2*Math.PI) * Math.asin (this.c/a) }; if (this.t < 1) return -.5*(a*Math.pow(2,10*(this.t-=1)) * Math.sin( (this.t*this.d-s)*(2*Math.PI)/p )) + this.b; return a*Math.pow(2,-10*(this.t-=1)) * Math.sin( (this.t*this.d-s)*(2*Math.PI)/p )*.5 + this.c + this.b; } inBack() { let s = 1.70158; return this.c*(this.t/=this.d)*this.t*((s+1)*this.t - s) + this.b; } outBack() { let s = 1.70158; return this.c*((this.t=this.t/this.d-1)*this.t*((s+1)*this.t + s) + 1) + this.b; } inOutBack() { let s = 1.70158; if ((this.t/=this.d/2) < 1) return this.c/2*(this.t*this.t*(((s*=(1.525))+1)*this.t - s)) + this.b; return this.c/2*((this.t-=2)*this.t*(((s*=(1.525))+1)*this.t + s) + 2) + this.b; } inBounce(t=this.t, b=this.b) { return this.c - this.outBounce (this.d-t, 0) + b; } outBounce(t=this.t, b=this.b) { if ((t/=this.d) < (1/2.75)) { return this.c*(7.5625*t*t) + b; } else if (t < (2/2.75)) { return this.c*(7.5625*(t-=(1.5/2.75))*t + .75) + b; } else if (t < (2.5/2.75)) { return this.c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b; } else { return this.c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b; } } inOutBounce() { if (this.t < this.d/2) return this.inBounce (this.t*2, 0) * .5 + this.b; return this.outBounce (this.t*2-this.d, 0) * .5 + this.c*.5 + this.b; } } class Tween{ constructor(target, channel, endValue, duration, oncomplete, easing="inOutQuad"){ this.target = target; this.channel = channel; this.oncomplete = oncomplete; this.endValue = endValue; this.duration = duration; this.currentTime = 0; this.finished = false; //constructor(start, end, duration, startTime=0, type='linear') this.easing = new Easing(target[channel], endValue, duration, 0, easing); } update(dt){ if (this.finished) return; this.currentTime += dt; if (this.currentTime>=this.duration){ this.target[this.channel] = this.endValue; if (this.oncomplete) this.oncomplete(); this.finished = true; }else{ this.target[this.channel] = this.easing.value(this.currentTime); } } } class SFX{ constructor(options){ this.context = options.context; const volume = (options.volume!=undefined) ? options.volume : 1.0; this.gainNode = this.context.createGain(); this.gainNode.gain.setValueAtTime(volume, this.context.currentTime); this.gainNode.connect(this.context.destination); this._loop = (options.loop==undefined) ? false : options.loop; this.fadeDuration = (options.fadeDuration==undefined) ? 0.5 : options.fadeDuration; this.autoplay = (options.autoplay==undefined) ? false : options.autoplay; this.buffer = null; let codec; for(let prop in options.src){ if (SFX.supportsAudioType(prop)){ codec = prop; break; } } if (codec!=undefined){ this.url = options.src[codec]; this.load(this.url); }else{ console.warn("Browser does not support any of the supplied audio files"); } } static supportsAudioType(type) { let audio; // Allow user to create shortcuts, i.e. just "mp3" let formats = { mp3: 'audio/mpeg', wav: 'audio/wav', aif: 'audio/x-aiff', ogg: 'audio/ogg' }; if(!audio) audio = document.createElement('audio'); return audio.canPlayType(formats[type] || type); } load(url) { // Load buffer asynchronously const request = new XMLHttpRequest(); request.open("GET", url, true); request.responseType = "arraybuffer"; const sfx = this; request.onload = function() { // Asynchronously decode the audio file data in request.response sfx.context.decodeAudioData( request.response, function(buffer) { if (!buffer) { console.error('error decoding file data: ' + sfx.url); return; } sfx.buffer = buffer; if (sfx.autoplay) sfx.play(); }, function(error) { console.error('decodeAudioData error', error); } ); } request.onerror = function() { console.error('SFX Loader: XHR error'); } request.send(); } set loop(value){ this._loop = value; if (this.source!=undefined) this.source.loop = value; } play(){ if (this.buffer==null) return; if (this.source!=undefined) this.source.stop(); this.source = this.context.createBufferSource(); this.source.loop = this._loop; this.source.buffer = this.buffer; this.source.connect(this.gainNode); this.source.start(0); } set volume(value){ this._volume = value; this.gainNode.gain.setTargetAtTime(value, this.context.currentTime + this.fadeDuration, 0); } pause(){ if (this.source==undefined) return; this.source.stop(); } stop(){ if (this.source==undefined) return; this.source.stop(); delete this.source; } } class JoyStick{ constructor(options){ const circle = document.createElement("div"); circle.style.cssText = "position:absolute; bottom:35px; width:80px; height:80px; background:rgba(126, 126, 126, 0.5); border:#fff solid medium; border-radius:50%; left:50%; transform:translateX(-50%);"; const thumb = document.createElement("div"); thumb.style.cssText = "position: absolute; left: 20px; top: 20px; width: 40px; height: 40px; border-radius: 50%; background: #fff;"; circle.appendChild(thumb); document.body.appendChild(circle); this.domElement = thumb; this.maxRadius = options.maxRadius || 40; this.maxRadiusSquared = this.maxRadius * this.maxRadius; this.onMove = options.onMove; this.game = options.game; this.origin = { left:this.domElement.offsetLeft, top:this.domElement.offsetTop }; if (this.domElement!=undefined){ const joystick = this; if ('ontouchstart' in window){ this.domElement.addEventListener('touchstart', function(evt){ joystick.tap(evt); }); }else{ this.domElement.addEventListener('mousedown', function(evt){ joystick.tap(evt); }); } } } getMousePosition(evt){ let clientX = evt.targetTouches ? evt.targetTouches[0].pageX : evt.clientX; let clientY = evt.targetTouches ? evt.targetTouches[0].pageY : evt.clientY; return { x:clientX, y:clientY }; } tap(evt){ evt = evt || window.event; // get the mouse cursor position at startup: this.offset = this.getMousePosition(evt); const joystick = this; if ('ontouchstart' in window){ document.ontouchmove = function(evt){ joystick.move(evt); }; document.ontouchend = function(evt){ joystick.up(evt); }; }else{ document.onmousemove = function(evt){ joystick.move(evt); }; document.onmouseup = function(evt){ joystick.up(evt); }; } } move(evt){ evt = evt || window.event; const mouse = this.getMousePosition(evt); // calculate the new cursor position: let left = mouse.x - this.offset.x; let top = mouse.y - this.offset.y; //this.offset = mouse; const sqMag = left*left + top*top; if (sqMag>this.maxRadiusSquared){ //Only use sqrt if essential const magnitude = Math.sqrt(sqMag); left /= magnitude; top /= magnitude; left *= this.maxRadius; top *= this.maxRadius; } // set the element's new position: this.domElement.style.top = `${top + this.domElement.clientHeight/2}px`; this.domElement.style.left = `${left + this.domElement.clientWidth/2}px`; const forward = -(top - this.origin.top + this.domElement.clientHeight/2)/this.maxRadius; const turn = (left - this.origin.left + this.domElement.clientWidth/2)/this.maxRadius; if (this.onMove!=undefined) this.onMove.call(this.game, forward, turn); } up(evt){ if ('ontouchstart' in window){ document.ontouchmove = null; document.touchend = null; }else{ document.onmousemove = null; document.onmouseup = null; } this.domElement.style.top = `${this.origin.top}px`; this.domElement.style.left = `${this.origin.left}px`; this.onMove.call(this.game, 0, 0); } } class Preloader{ constructor(options){ this.assets = {}; for(let asset of options.assets){ this.assets[asset] = { loaded:0, complete:false }; this.load(asset); } this.container = options.container; if (options.onprogress==undefined){ this.onprogress = onprogress; this.domElement = document.createElement("div"); this.domElement.style.position = 'absolute'; this.domElement.style.top = '0'; this.domElement.style.left = '0'; this.domElement.style.width = '100%'; this.domElement.style.height = '100%'; this.domElement.style.background = '#000'; this.domElement.style.opacity = '0.7'; this.domElement.style.display = 'flex'; this.domElement.style.alignItems = 'center'; this.domElement.style.justifyContent = 'center'; this.domElement.style.zIndex = '1111'; const barBase = document.createElement("div"); barBase.style.background = '#aaa'; barBase.style.width = '50%'; barBase.style.minWidth = '250px'; barBase.style.borderRadius = '10px'; barBase.style.height = '15px'; this.domElement.appendChild(barBase); const bar = document.createElement("div"); bar.style.background = '#2a2'; bar.style.width = '50%'; bar.style.borderRadius = '10px'; bar.style.height = '100%'; bar.style.width = '0'; barBase.appendChild(bar); this.progressBar = bar; if (this.container!=undefined){ this.container.appendChild(this.domElement); }else{ document.body.appendChild(this.domElement); } }else{ this.onprogress = options.onprogress; } this.oncomplete = options.oncomplete; const loader = this; function onprogress(delta){ const progress = delta*100; loader.progressBar.style.width = `${progress}%`; } } checkCompleted(){ for(let prop in this.assets){ const asset = this.assets[prop]; if (!asset.complete) return false; } return true; } get progress(){ let total = 0; let loaded = 0; for(let prop in this.assets){ const asset = this.assets[prop]; if (asset.total == undefined){ loaded = 0; break; } loaded += asset.loaded; total += asset.total; } return loaded/total; } load(url){ const loader = this; var xobj = new XMLHttpRequest(); xobj.overrideMimeType("application/json"); xobj.open('GET', url, true); xobj.onreadystatechange = function () { if (xobj.readyState == 4 && xobj.status == "200") { loader.assets[url].complete = true; if (loader.checkCompleted()){ if (loader.domElement!=undefined){ if (loader.container!=undefined){ loader.container.removeChild(loader.domElement); }else{ document.body.removeChild(loader.domElement); } } loader.oncomplete(); } } }; xobj.onprogress = function(e){ const asset = loader.assets[url]; asset.loaded = e.loaded; asset.total = e.total; loader.onprogress(loader.progress); } xobj.send(null); } }