├── LICENSE ├── index.html ├── index.js ├── lib ├── game │ ├── entities │ │ ├── debug-sectors.js │ │ ├── enemy-blob.js │ │ ├── grenade-pickup.js │ │ ├── health-pickup.js │ │ ├── particle.js │ │ ├── player.js │ │ └── void.js │ ├── hud.js │ ├── levels │ │ └── base1.js │ ├── main.js │ ├── title.js │ └── weapons │ │ ├── base.js │ │ └── grenade-launcher.js └── plugins │ ├── gamepad.js │ ├── mouse-delta.js │ ├── touch-button.js │ ├── touch-field.js │ └── twopointfive │ ├── debug.js │ ├── entity.js │ ├── font.js │ ├── game.js │ ├── gl-matrix.js │ ├── hud.js │ ├── image.js │ ├── loader.js │ ├── namespace.js │ ├── renderer │ ├── ortho-camera.js │ ├── perspective-camera.js │ ├── program.js │ ├── quad.js │ ├── renderer.js │ └── stereo-renderer.js │ ├── system.js │ └── world │ ├── culled-sectors.js │ ├── light-map.js │ ├── map.js │ ├── tile.js │ └── wall-map.js ├── media ├── blob-gib.png ├── blob-spawn.png ├── blob.png ├── explosion.png ├── fredoka-one.font.png ├── grenade-launcher.png ├── grenade-pickup.png ├── grenade.png ├── health-icon.png ├── health.png ├── hud-blood-low.png ├── loading-block.png ├── rotate.png ├── sounds │ ├── blob-gib.m4a │ ├── blob-gib.ogg │ ├── empty-click.m4a │ ├── empty-click.ogg │ ├── explosion.m4a │ ├── explosion.ogg │ ├── grenade-bounce.m4a │ ├── grenade-bounce.ogg │ ├── grenade-launcher.m4a │ ├── grenade-launcher.ogg │ ├── health-pickup.m4a │ ├── health-pickup.ogg │ ├── hurt1.m4a │ ├── hurt1.ogg │ ├── hurt2.m4a │ ├── hurt2.ogg │ ├── hurt3.m4a │ └── hurt3.ogg ├── tiles │ ├── basic-tiles-64.png │ └── lights-64.png └── title.png ├── readme.md └── styles.css /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Dominic Szablewski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TwoPointFive for Impact 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |

18 | Demo Game using the 19 | TwoPointFive Plugin 20 | for 21 | Impact 22 |

23 |
24 | 25 |
26 | 27 | 28 |
29 |
30 | Rotate to Landscape to play! 31 |
32 | 33 |
34 |
35 | 36 | 37 |
38 |
39 | 40 | 41 | – requires the Chrome WebVR Build 42 |
43 |
44 |
45 | 46 |
47 |
48 |

49 | Your browser does not support WebGL. Try 50 | Chrome or 51 | Firefox. 52 |

53 |

54 | Mobile Safari has WebGL support in iOS 8 55 |

56 |
57 |
58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | ejecta.include('lib/impact/impact.js'); 3 | ejecta.include('lib/game/main.js'); 4 | -------------------------------------------------------------------------------- /lib/game/entities/debug-sectors.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'game.entities.debug-sectors' 3 | ) 4 | .requires( 5 | 'plugins.twopointfive.entity', 6 | 'plugins.twopointfive.world.culled-sectors' 7 | ) 8 | .defines(function(){ 9 | 10 | // This Entity can be placed in weltmeister to visualize the sector boundaries 11 | // in the level. It does not have any purpose in the game. 12 | 13 | EntityDebugSectors = ig.Entity.extend({ 14 | type: ig.Entity.TYPE.NONE, 15 | checkAgainst: ig.Entity.TYPE.NONE, 16 | collides: ig.Entity.COLLIDES.NEVER, 17 | 18 | size: {x: 16, y: 16}, 19 | sectorSize: 4, 20 | 21 | _wmDrawBox: true, 22 | _wmBoxColor: '#fff', 23 | 24 | generate: function() { 25 | var floorMap = null; 26 | for( var i = 0; i < ig.editor.layers.length; i++ ) { 27 | if( ig.editor.layers[i].name == 'floor' ) { 28 | floorMap = ig.editor.layers[i]; 29 | break; 30 | } 31 | } 32 | if( floorMap ) { 33 | console 34 | this.culledSectors = new tpf.CulledSectors( floorMap, [], this.sectorSize ); 35 | } 36 | }, 37 | 38 | draw: function() { 39 | if( !ig.global.wm ) { return; } 40 | 41 | // Did the sector size change? Regenerate! 42 | if( !this.culledSectors || this.culledSectors.sectorSize != this.sectorSize ) { 43 | this.generate(); 44 | return; 45 | } 46 | 47 | // Draw all portals 48 | for( var y in this.culledSectors.sectors ) { 49 | for( var x in this.culledSectors.sectors[y] ) { 50 | var s = this.culledSectors.sectors[y][x]; 51 | for( var i = 0; i < s.portals.length; i++ ) { 52 | var p = s.portals[i]; 53 | this.drawLine('#f4f', p.x1, p.y1, p.x2, p.y2); 54 | } 55 | } 56 | } 57 | }, 58 | 59 | drawLine: function( color, sx, sy, dx, dy ) { 60 | ig.system.context.strokeStyle = color; 61 | ig.system.context.lineWidth = 1.0; 62 | 63 | ig.system.context.beginPath(); 64 | ig.system.context.moveTo( 65 | ig.system.getDrawPos(sx - ig.game.screen.x), 66 | ig.system.getDrawPos(sy - ig.game.screen.y) 67 | ); 68 | ig.system.context.lineTo( 69 | ig.system.getDrawPos(dx - ig.game.screen.x), 70 | ig.system.getDrawPos(dy - ig.game.screen.y) 71 | ); 72 | ig.system.context.stroke(); 73 | ig.system.context.closePath(); 74 | } 75 | }); 76 | 77 | 78 | }); 79 | -------------------------------------------------------------------------------- /lib/game/entities/enemy-blob.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'game.entities.enemy-blob' 3 | ) 4 | .requires( 5 | 'plugins.twopointfive.entity', 6 | 'game.entities.particle' 7 | ) 8 | .defines(function(){ 9 | 10 | 11 | 12 | EntityEnemyBlobSpawner = tpf.Entity.extend({ 13 | size: {x: 16, y: 16}, 14 | scale: 0.5, 15 | 16 | dynamicLight: true, 17 | _wmBoxColor: '#ff0000', 18 | 19 | angle: 0, 20 | 21 | animSheet: new ig.AnimationSheet( 'media/blob-spawn.png', 64, 128 ), 22 | 23 | init: function( x, y, settings ) { 24 | this.parent( x, y, settings ); 25 | this.addAnim( 'idle', 1, [0] ); 26 | this.addAnim( 'spawn', 0.05, [0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,14,15,16,17,18,19,20,21] ); 27 | }, 28 | 29 | update: function() { 30 | if( this.currentAnim == this.anims.idle ) { 31 | if( this.manhattenDistanceTo(ig.game.player) < 512 ) { 32 | this.currentAnim = this.anims.spawn.rewind(); 33 | } 34 | else { 35 | return; 36 | } 37 | } 38 | 39 | this.parent(); 40 | 41 | // Spawn anim finished? Spawn the Blob and kill the spawner. 42 | if( this.currentAnim == this.anims.spawn && this.currentAnim.loopCount ) { 43 | ig.game.spawnEntity(EntityEnemyBlob, this.pos.x, this.pos.y); 44 | this.kill(); 45 | } 46 | }, 47 | 48 | manhattenDistanceTo: function( other ) { 49 | // This is a tiny bit faster than .distanceTo() and we don't need the precision 50 | return Math.abs(other.pos.x - this.pos.x) + Math.abs(other.pos.y - this.pos.y); 51 | } 52 | }); 53 | 54 | 55 | EntityEnemyBlob = tpf.Entity.extend({ 56 | type: ig.Entity.TYPE.B, 57 | checkAgainst: ig.Entity.TYPE.A, 58 | collides: ig.Entity.COLLIDES.ACTIVE, 59 | 60 | size: {x: 16, y: 16}, 61 | friction: {x: 100, y: 100}, 62 | scale: 0.5, 63 | 64 | health: 10, 65 | damage: 10, 66 | 67 | dynamicLight: true, 68 | _wmBoxColor: '#ff0000', 69 | 70 | angle: 0, 71 | speed: 80, 72 | injump: false, 73 | 74 | didHurtPlayer: false, 75 | seenPlayer: false, 76 | 77 | 78 | animSheet: new ig.AnimationSheet( 'media/blob.png', 64, 64 ), 79 | 80 | init: function( x, y, settings ) { 81 | this.parent( x, y, settings ); 82 | var crawFrameTime = 0.04 + Math.random() * 0.02; 83 | 84 | this.addAnim( 'crawl', 0.04, [0,1,2,3,4,5,4,3,2,1] ); 85 | this.currentAnim.gotoRandomFrame(); 86 | 87 | this.hurtTimer = new ig.Timer(); 88 | }, 89 | 90 | 91 | update: function() { 92 | this.angle = this.angleTo( ig.game.player ); 93 | 94 | this.vel.x = Math.cos(this.angle) * this.speed; 95 | this.vel.y = Math.sin(this.angle) * this.speed; 96 | 97 | if( ig.game.dead ) { 98 | // Move away from the player if he's dead 99 | this.vel.x *= -1; 100 | this.vel.y *= -1; 101 | } 102 | 103 | this.parent(); 104 | }, 105 | 106 | kill: function() { 107 | var cx = this.pos.x + this.size.x/2; 108 | var cy = this.pos.y + this.size.y/2; 109 | for( var i = 0; i < 20; i++ ) { 110 | ig.game.spawnEntity( EntityEnemyBlobGib, cx, cy ); 111 | } 112 | ig.game.blobKillCount++; 113 | this.parent(); 114 | }, 115 | 116 | check: function( other ) { 117 | if( this.hurtTimer.delta() < 0 ) { 118 | // Player already hurt during this attack move? 119 | return; 120 | } 121 | 122 | // Only hurt the player once a second 123 | this.hurtTimer.set(1); 124 | 125 | 126 | this.vel.x = -this.vel.x; 127 | this.vel.y = -this.vel.y; 128 | other.receiveDamage( this.damage, this ); 129 | } 130 | }); 131 | 132 | 133 | 134 | EntityEnemyBlobGib = EntityParticle.extend({ 135 | vpos: 0, 136 | scale: 0.5, 137 | initialVel: {x:120, y: 120, z: 2.5}, 138 | friction: {x: 10, y: 10}, 139 | 140 | lifetime: 2, 141 | 142 | animSheet: new ig.AnimationSheet( 'media/blob-gib.png', 16, 16 ), 143 | 144 | init: function( x, y, settings ) { 145 | this.addAnim( 'idle', 5, [0,1,2,3,4,5,6,7,8,9,10,11] ); 146 | this.parent( x, y, settings ); 147 | } 148 | }); 149 | 150 | 151 | }); -------------------------------------------------------------------------------- /lib/game/entities/grenade-pickup.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'game.entities.grenade-pickup' 3 | ) 4 | .requires( 5 | 'plugins.twopointfive.entity', 6 | 'game.weapons.grenade-launcher' 7 | ) 8 | .defines(function(){ 9 | 10 | EntityGrenadePickup = tpf.Entity.extend({ 11 | checkAgainst: ig.Entity.TYPE.A, 12 | 13 | size: {x: 16, y: 16}, 14 | vpos: 0.5, 15 | scale: 0.5, 16 | amount: 8, 17 | 18 | dynamicLight: true, 19 | _wmBoxColor: '#55ff00', 20 | 21 | animSheet: new ig.AnimationSheet( 'media/grenade-pickup.png', 32, 32 ), 22 | pickupSound: new ig.Sound( 'media/sounds/health-pickup.*' ), 23 | bounceTimer: null, 24 | 25 | init: function( x, y, settings ) { 26 | this.parent( x, y, settings ); 27 | this.addAnim( 'idle', 10, [0] ); 28 | }, 29 | 30 | update: function() { 31 | this.parent(); 32 | }, 33 | 34 | check: function( other ) { 35 | other.giveAmmo(WeaponGrenadeLauncher, this.amount); 36 | this.pickupSound.play(); 37 | this.kill(); 38 | } 39 | }); 40 | 41 | }); -------------------------------------------------------------------------------- /lib/game/entities/health-pickup.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'game.entities.health-pickup' 3 | ) 4 | .requires( 5 | 'plugins.twopointfive.entity' 6 | ) 7 | .defines(function(){ 8 | 9 | EntityHealthPickup = tpf.Entity.extend({ 10 | checkAgainst: ig.Entity.TYPE.A, 11 | 12 | size: {x: 16, y: 16}, 13 | vpos: 0.5, 14 | scale: 0.5, 15 | amount: 25, 16 | gravityFactor: 0, 17 | 18 | dynamicLight: true, 19 | _wmBoxColor: '#55ff00', 20 | 21 | animSheet: new ig.AnimationSheet( 'media/health.png', 32, 32 ), 22 | pickupSound: new ig.Sound( 'media/sounds/health-pickup.*' ), 23 | bounceTimer: null, 24 | 25 | init: function( x, y, settings ) { 26 | this.parent( x, y, settings ); 27 | this.addAnim( 'idle', 10, [0] ); 28 | this.bounceTimer = new ig.Timer(); 29 | }, 30 | 31 | update: function() { 32 | // Give the item an Arcade-like bounce animation 33 | this.pos.z = (Math.cos(this.bounceTimer.delta()*3)+1) * 3; 34 | this.parent(); 35 | }, 36 | 37 | check: function( other ) { 38 | if( other.giveHealth(this.amount) ) { 39 | this.pickupSound.play(); 40 | this.kill(); 41 | } 42 | } 43 | }); 44 | 45 | }); -------------------------------------------------------------------------------- /lib/game/entities/particle.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'game.entities.particle' 3 | ) 4 | .requires( 5 | 'plugins.twopointfive.entity', 6 | 'impact.entity-pool' 7 | ) 8 | .defines(function(){ 9 | 10 | 11 | 12 | EntityParticle = tpf.Entity.extend({ 13 | size: {x: 1, y: 1}, 14 | offset: {x: 0, y: 0}, 15 | minBounceVelocity: 0, 16 | 17 | lifetime: 5, 18 | fadetime: 1, 19 | bounciness: 0.6, 20 | friction: {x:20, y: 0}, 21 | 22 | initialVel: {x:1, y: 1, z: 1}, 23 | 24 | init: function( x, y, settings ) { 25 | this.parent( x, y, settings ); 26 | this.currentAnim.gotoRandomFrame(); 27 | this.setPosition(); 28 | }, 29 | 30 | reset: function( x, y, settings ) { 31 | this.parent(x, y, settings); 32 | this.setPosition(); 33 | }, 34 | 35 | setPosition: function() { 36 | this.vel.x = (Math.random() * 2 - 1) * this.initialVel.x; 37 | this.vel.y = (Math.random() * 2 - 1) * this.initialVel.y; 38 | this.vel.z = (Math.random() * 2 - 1) * this.initialVel.z; 39 | 40 | this.idleTimer = new ig.Timer(); 41 | }, 42 | 43 | update: function() { 44 | var delta = this.idleTimer.delta(); 45 | if( delta > this.lifetime ) { 46 | this.kill(); 47 | return; 48 | } 49 | 50 | this.tile.quad.setAlpha( 51 | delta.map(this.lifetime - this.fadetime, this.lifetime,1, 0).limit(0,1) 52 | ); 53 | 54 | this.parent(); 55 | } 56 | }); 57 | 58 | ig.EntityPool.enableFor(EntityParticle); 59 | 60 | 61 | }); -------------------------------------------------------------------------------- /lib/game/entities/player.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'game.entities.player' 3 | ) 4 | .requires( 5 | 'plugins.twopointfive.entity', 6 | 'plugins.mouse-delta', 7 | 'game.weapons.grenade-launcher' 8 | ) 9 | .defines(function(){ 10 | 11 | EntityPlayer = tpf.Entity.extend({ 12 | type: ig.Entity.TYPE.A, 13 | collides: ig.Entity.COLLIDES.PASSIVE, 14 | 15 | size: {x: 32, y: 32}, 16 | 17 | angle: 0, 18 | internalAngle: 0, 19 | turnSpeed: (120).toRad(), 20 | moveSpeed: 192, 21 | bob: 0, 22 | bobSpeed: 0.1, 23 | bobHeight: 0.8, 24 | 25 | health: 100, 26 | maxHealth: 100, 27 | 28 | weapons: [], 29 | 30 | currentWeapon: null, 31 | currentWeaponIndex: -1, 32 | delayedWeaponSwitchIndex: -1, 33 | 34 | currentLightColor: {r:1, g:1, b:1, a:1}, 35 | 36 | god: false, 37 | 38 | hurtSounds: [ 39 | new ig.Sound('media/sounds/hurt1.*'), 40 | new ig.Sound('media/sounds/hurt2.*'), 41 | new ig.Sound('media/sounds/hurt3.*') 42 | ], 43 | 44 | init: function( x, y, settings ) { 45 | this.parent( x, y, settings ); 46 | this.internalAngle = this.angle; 47 | ig.game.player = this; 48 | }, 49 | 50 | ready: function() { 51 | var cx = this.pos.x + this.size.x/2, 52 | cy = this.pos.y + this.size.y/2; 53 | ig.system.camera.position[0] = cx; 54 | ig.system.camera.position[2] = cy; 55 | 56 | 57 | this.giveWeapon( WeaponGrenadeLauncher, 16 ); 58 | }, 59 | 60 | update: function() { 61 | 62 | // Move 63 | var dx = 0, 64 | dy = 0; 65 | 66 | if( ig.input.state('forward') ) { 67 | dy = 1; 68 | } 69 | else if( ig.input.state('back') ) { 70 | dy = -1; 71 | } 72 | 73 | // Turn viewpoint with mouse? 74 | if( ig.system.isFullscreen || ig.system.hasMouseLock ) { 75 | this.internalAngle -= ig.input.mouseDelta.x / 400; 76 | } 77 | 78 | // Turn with keys 79 | if( ig.input.state('left') ) { 80 | this.internalAngle += this.turnSpeed * ig.system.tick; 81 | } 82 | else if( ig.input.state('right') ) { 83 | this.internalAngle -= this.turnSpeed * ig.system.tick; 84 | } 85 | 86 | // Sidestep 87 | if( ig.input.state('stepleft') ) { 88 | dx = 1; 89 | } 90 | else if( ig.input.state('stepright') ) { 91 | dx = -1; 92 | } 93 | 94 | // Touch controls 95 | if( ig.game.touchFieldMove ) { 96 | var fi = ig.game.touchFieldMove.input; 97 | dx = -(fi.x/60).limit(-1, 1); 98 | dy = -(fi.y/60).limit(-1, 1); 99 | } 100 | if( ig.game.touchFieldTurn ) { 101 | var fi = ig.game.touchFieldTurn.input; 102 | this.internalAngle += fi.dx/100; 103 | } 104 | 105 | // Gamepad input 106 | if( ig.input.gamepad ) { 107 | var stickThreshold = 0.2; 108 | if( Math.abs(ig.input.gamepad.axes[2]) > stickThreshold ) { 109 | this.internalAngle -= ig.input.gamepad.axes[2] * this.turnSpeed * ig.system.tick; 110 | } 111 | if( Math.abs(ig.input.gamepad.axes[0]) > stickThreshold ) { 112 | dx = -ig.input.gamepad.axes[0]; 113 | } 114 | if( Math.abs(ig.input.gamepad.axes[1]) > stickThreshold ) { 115 | dy = -ig.input.gamepad.axes[1]; 116 | } 117 | } 118 | 119 | 120 | var running = ig.input.state('run') || ig.ua.mobile; 121 | var speed = this.moveSpeed; 122 | 123 | 124 | // If we have a head tracker connected, add its rotation to our own; 125 | // It's a bit of a hack to have this here, but we want to change the 126 | // aim direction of the player with the head movement as well. 127 | var trackerRotation = [0,0,0]; 128 | var trackerPosition = [0,0,0]; 129 | if( ig.system.renderer instanceof tpf.StereoRenderer ) { 130 | var state = ig.system.renderer.getHMDState(); 131 | trackerRotation = state.rotation; 132 | trackerPosition = state.position; 133 | } 134 | 135 | this.angle = this.internalAngle + trackerRotation[0]; 136 | 137 | 138 | // Normalize movement vector 139 | if( Math.abs(dx) + Math.abs(dy) > 1 ) { 140 | dx *= Math.SQRT1_2; 141 | dy *= Math.SQRT1_2; 142 | } 143 | 144 | // Set the desired velocity based on our angle and which keys are 145 | // pressed 146 | this.vel.x = -Math.sin(this.angle) * dy * this.moveSpeed 147 | -Math.sin(this.angle+Math.PI/2) * dx * this.moveSpeed; 148 | 149 | this.vel.y = -Math.cos(this.angle) * dy * this.moveSpeed 150 | -Math.cos(this.angle+Math.PI/2) * dx * this.moveSpeed; 151 | 152 | 153 | 154 | // Shoot 155 | if( 156 | this.currentWeapon && 157 | ( ig.input.state('shoot') || (!ig.ua.mobile && ig.input.state('click')) ) 158 | ) { 159 | // Calculate the spawn position for projectiles 160 | var sx = this.pos.x+this.size.x/2 -Math.sin(this.angle) * 3; 161 | sy = this.pos.y+this.size.y/2 -Math.cos(this.angle) * 3; 162 | 163 | if( !this.currentWeapon.depleted() ) { 164 | this.currentWeapon.trigger( sx, sy, this.angle ); 165 | } 166 | else { 167 | // find the first weapon that has ammo 168 | this.switchToNextNonEmptyWeapon(); 169 | } 170 | } 171 | 172 | // Change Weapon; be careful to only switch after the shoot button was released 173 | if( this.delayedWeaponSwitchIndex >= 0 ) { 174 | this.switchWeapon( this.delayedWeaponSwitchIndex ); 175 | } 176 | 177 | if( ig.input.pressed('weaponNext') && this.weapons.length > 1 ) { 178 | this.switchWeapon( (this.currentWeaponIndex + 1) % this.weapons.length ); 179 | } 180 | else if( ig.input.pressed('weaponPrev') && this.weapons.length > 1 ) { 181 | var index = (this.currentWeaponIndex == 0) 182 | ? this.weapons.length - 1 183 | : this.currentWeaponIndex - 1; 184 | this.switchWeapon( index ); 185 | } 186 | 187 | 188 | // Calculate new position based on velocity; update sector and light etc... 189 | this.parent(); 190 | 191 | 192 | // Calculate bobbing 193 | this.bob += ig.system.tick * this.bobSpeed * Math.min(Math.abs(dx) + Math.abs(dy),1) * speed; 194 | var bobOffset = Math.sin(this.bob) * this.bobHeight; 195 | 196 | if( this.currentWeapon ) { 197 | this.currentWeapon.bobOffset = Math.sin(this.bob+Math.PI/2) * this.bobHeight * 4; 198 | this.currentWeapon.update(); 199 | } 200 | 201 | // Update camera position and view angle 202 | var cx = this.pos.x + this.size.x/2, 203 | cy = this.pos.y + this.size.y/2; 204 | 205 | ig.system.camera.setRotation( trackerRotation[2], trackerRotation[1], this.angle ); 206 | 207 | // If we have a head tracker connected, we may to adjust the position a bit 208 | if( ig.system.renderer instanceof tpf.StereoRenderer ) { 209 | var tt = trackerPosition; 210 | var a = this.internalAngle; 211 | var ttx = tt[0] * Math.cos(-a) - tt[2] * Math.sin(-a); 212 | var tty = tt[0] * Math.sin(-a) + tt[2] * Math.cos(-a); 213 | ig.system.camera.setPosition( cx + ttx, cy + tty, tt[1] ); 214 | } 215 | else { 216 | ig.system.camera.setPosition( cx, cy, bobOffset ); 217 | } 218 | }, 219 | 220 | receiveDamage: function( amount, from ) { 221 | if( this.god || this._killed ) { 222 | return; 223 | } 224 | 225 | // Figure out where the damage came from and show the damage indicator 226 | // accordingly on the HUD 227 | var a = (this.angle + this.angleTo(from)) % (Math.PI*2); 228 | a += a < 0 ? Math.PI : -Math.PI; 229 | 230 | var xedge = ig.game.hud.width/2; 231 | var ypos = a < 0 ? ig.game.hud.height/2 : 0; 232 | var xpos = Math.abs(a).map( 0, Math.PI, -xedge, xedge ); 233 | 234 | ig.game.hud.showDamageIndicator( xpos, ypos, 1 ); 235 | 236 | this.hurtSounds.random().play(); 237 | this.parent( amount, from ); 238 | }, 239 | 240 | kill: function() { 241 | ig.game.hud.showMessage('You are Dead!', tpf.Hud.TIME.PERMANENT); 242 | ig.game.showDeathAnim(); 243 | this.parent(); 244 | }, 245 | 246 | giveWeapon: function( weaponClass, ammo ) { 247 | // Do we have this weapon already? Add ammo! 248 | var index = -1; 249 | for( var i = 0; i < this.weapons.length; i++ ) { 250 | var w = this.weapons[i]; 251 | if( w instanceof weaponClass ) { 252 | index = i; 253 | w.giveAmmo( ammo ); 254 | } 255 | } 256 | 257 | // New weapon? 258 | if( index === -1 ) { 259 | this.weapons.push( new weaponClass(ammo) ); 260 | index = this.weapons.length - 1; 261 | } 262 | 263 | this.switchWeapon( index ); 264 | }, 265 | 266 | giveAmmo: function( weaponClass, ammo ) { 267 | for( var i = 0; i < this.weapons.length; i++ ) { 268 | var w = this.weapons[i]; 269 | if( w instanceof weaponClass ) { 270 | w.giveAmmo( ammo ); 271 | } 272 | } 273 | }, 274 | 275 | giveHealth: function( amount ) { 276 | if( this.health >= this.maxHealth ) { 277 | return false; 278 | } 279 | 280 | this.health = Math.min(this.health + amount, this.maxHealth); 281 | return true; 282 | }, 283 | 284 | switchWeapon: function( index ) { 285 | if( this.currentWeapon ) { 286 | if( this.currentWeapon.shootTimer.delta() < 0 ) { 287 | this.delayedWeaponSwitchIndex = index; 288 | return; 289 | } 290 | } 291 | 292 | this.delayedWeaponSwitchIndex = -1; 293 | this.currentWeaponIndex = index; 294 | this.currentWeapon = this.weapons[index]; 295 | 296 | if( this.currentWeapon.ammoIcon ) { 297 | this.currentWeapon.ammoIcon.setPosition( 298 | 215, 299 | ig.game.hud.height-this.currentWeapon.ammoIcon.tileHeight-6 300 | ); 301 | } 302 | 303 | // Make sure the lighting for the weapon is updated 304 | this.currentWeapon.setLight( this.currentLightColor ); 305 | }, 306 | 307 | switchToNextNonEmptyWeapon: function() { 308 | for( var i = this.currentWeaponIndex+1; i < this.weapons.length; i++ ) { 309 | if( !this.weapons[i].depleted() ) { 310 | this.switchWeapon(i); 311 | this.currentWeapon.shootTimer.set(0.5); 312 | return; 313 | } 314 | } 315 | 316 | for( var i = 0; i < this.currentWeaponIndex; i++ ) { 317 | if( !this.weapons[i].depleted() ) { 318 | this.switchWeapon(i); 319 | this.currentWeapon.shootTimer.set(0.5); 320 | return; 321 | } 322 | } 323 | }, 324 | 325 | setLight: function( color ) { 326 | this.currentLightColor = color; 327 | if( this.currentWeapon ) { 328 | this.currentWeapon.setLight( color ); 329 | } 330 | } 331 | }); 332 | 333 | }); -------------------------------------------------------------------------------- /lib/game/entities/void.js: -------------------------------------------------------------------------------- 1 | /* 2 | This entity does nothing but just sits there. It can be used as a target 3 | for other entities, such as movers. 4 | */ 5 | 6 | ig.module( 7 | 'game.entities.void' 8 | ) 9 | .requires( 10 | 'impact.entity' 11 | ) 12 | .defines(function(){ 13 | 14 | EntityVoid = ig.Entity.extend({ 15 | _wmDrawBox: true, 16 | _wmBoxColor: 'rgba(128, 28, 230, 0.7)', 17 | 18 | size: {x: 32, y: 32}, 19 | 20 | update: function(){} 21 | }); 22 | 23 | }); -------------------------------------------------------------------------------- /lib/game/hud.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'game.hud' 3 | ) 4 | .requires( 5 | 'plugins.twopointfive.hud' 6 | ) 7 | .defines(function(){ 8 | 9 | MyHud = tpf.Hud.extend({ 10 | 11 | font: new tpf.Font( 'media/fredoka-one.font.png' ), 12 | 13 | healthIconImage: new ig.Image( 'media/health-icon.png' ), 14 | damageIndicatorImage: new ig.Image( 'media/hud-blood-low.png' ), 15 | healthIcon: null, 16 | 17 | keys: [], 18 | 19 | showControlsTimer: null, 20 | 21 | init: function( width, height, showControls ) { 22 | this.parent(width, height); 23 | 24 | this.healthIcon = new tpf.HudTile( this.healthIconImage, 0, 32, 32 ); 25 | this.healthIcon.setPosition( 96, this.height-this.healthIcon.tileHeight-4 ); 26 | }, 27 | 28 | draw: function( player, weapon ) { 29 | this.prepare(); 30 | 31 | if( weapon ) { 32 | weapon.draw(); 33 | 34 | if( weapon.ammoIcon ) { 35 | weapon.ammoIcon.draw(); 36 | this.font.draw( weapon.ammo, 210, this.height - this.font.height + 1, ig.Font.ALIGN.RIGHT ); 37 | } 38 | } 39 | 40 | this.healthIcon.draw(); 41 | this.font.draw( player.health, 90, this.height - this.font.height + 1, ig.Font.ALIGN.RIGHT ); 42 | 43 | this.font.draw( 'Kills: ' +ig.game.blobKillCount, 32, 8 ); 44 | 45 | // Draw the current message (showMessage(text)) and the damage indicator 46 | this.drawDefault(); 47 | } 48 | }); 49 | 50 | 51 | }); -------------------------------------------------------------------------------- /lib/game/levels/base1.js: -------------------------------------------------------------------------------- 1 | ig.module( 'game.levels.base1' ) 2 | .requires( 'impact.image','game.entities.player','game.entities.void' ) 3 | .defines(function(){ 4 | LevelBase1=/*JSON[*/{ 5 | "entities": [ 6 | { 7 | "type": "EntityPlayer", 8 | "x": 1010, 9 | "y": 818 10 | }, 11 | { 12 | "type": "EntityVoid", 13 | "x": 1144, 14 | "y": 692, 15 | "settings": { 16 | "fogColor": "0x041928", 17 | "fogNear": 128, 18 | "fogFar": 512, 19 | "name": "info" 20 | } 21 | } 22 | ], 23 | "layer": [ 24 | { 25 | "name": "floor", 26 | "width": 32, 27 | "height": 32, 28 | "linkWithCollision": false, 29 | "visible": 0, 30 | "tilesetName": "media/tiles/basic-tiles-64.png", 31 | "repeat": false, 32 | "preRender": false, 33 | "distance": "1", 34 | "tilesize": 64, 35 | "foreground": false, 36 | "data": [ 37 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 38 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 39 | [0,0,0,0,0,26,26,0,26,26,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 40 | [0,0,0,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 41 | [0,0,0,2,2,17,17,2,17,17,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 42 | [0,0,26,2,17,0,0,17,0,0,17,2,2,10,19,19,19,18,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 43 | [0,0,26,2,17,0,0,17,0,0,17,2,2,10,19,19,19,18,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 44 | [0,0,0,2,2,17,17,2,17,17,2,2,0,0,0,19,19,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 45 | [0,0,0,2,2,2,2,2,2,2,2,2,0,0,0,19,19,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 46 | [0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,19,19,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 47 | [0,0,0,0,0,0,10,10,10,0,0,0,0,0,0,19,19,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 48 | [0,0,0,0,0,0,12,9,9,0,0,0,0,0,0,10,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 49 | [0,0,0,0,0,0,9,4,9,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,2,2,2,2,2,2,0,0], 50 | [0,0,0,0,0,0,9,9,4,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,2,2,9,9,2,2,0,0], 51 | [0,0,0,0,0,0,3,9,4,9,4,9,10,2,2,0,0,2,2,10,19,19,19,10,2,9,0,0,9,2,0,0], 52 | [0,0,0,0,0,0,3,9,12,9,4,12,10,2,2,0,0,2,2,10,19,19,19,10,2,9,0,0,9,2,0,0], 53 | [0,0,0,0,0,0,12,4,9,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,2,2,9,9,2,2,0,0], 54 | [0,0,0,0,0,0,9,9,9,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,2,2,2,2,2,2,0,0], 55 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,10,0,0,0,0,0,0,0,0,0,10,10,0,0,0,0], 56 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,3,3,1,1,4,1,1,0,0,0,0], 57 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1,1,1,1,1,1,0,0,0,0], 58 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,3,3,1,1,1,0,0,0,0,0,0,0,0,0], 59 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0], 60 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0], 61 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 62 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 63 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 64 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 65 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 66 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 67 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 68 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] 69 | ] 70 | }, 71 | { 72 | "name": "collision", 73 | "width": 32, 74 | "height": 32, 75 | "linkWithCollision": false, 76 | "visible": 0, 77 | "tilesetName": "", 78 | "repeat": false, 79 | "preRender": false, 80 | "distance": 1, 81 | "tilesize": 64, 82 | "foreground": false, 83 | "data": [ 84 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 85 | [0,0,0,0,0,1,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 86 | [0,0,0,1,1,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 87 | [0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 88 | [0,0,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 89 | [0,1,0,0,0,1,1,0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0], 90 | [0,1,0,0,0,1,1,0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0], 91 | [0,0,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 92 | [0,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 93 | [0,0,0,1,1,1,0,0,0,1,1,1,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 94 | [0,0,0,0,0,1,0,0,0,1,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 95 | [0,0,0,0,0,1,0,0,0,1,0,0,0,1,1,0,0,1,1,0,0,0,0,0,1,1,1,1,1,1,0,0], 96 | [0,0,0,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,1,0], 97 | [0,0,0,0,0,1,0,0,0,1,1,1,1,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,1,0], 98 | [0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,1,0,0,1,0], 99 | [0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,1,0,0,1,0], 100 | [0,0,0,0,0,1,0,0,0,1,1,1,1,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,1,0], 101 | [0,0,0,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,1,0], 102 | [0,0,0,0,0,0,1,1,1,0,0,0,0,1,1,0,0,1,1,0,0,1,1,1,1,1,0,0,1,1,0,0], 103 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,1,0,0,0,0,0,0,0,1,0,0,0], 104 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,1,1,1,0,0,0,0,0,0,0,1,0,0,0], 105 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0], 106 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0], 107 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0], 108 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0], 109 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 110 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 111 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 112 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 113 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 114 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 115 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] 116 | ] 117 | }, 118 | { 119 | "name": "walls", 120 | "width": 32, 121 | "height": 32, 122 | "linkWithCollision": true, 123 | "visible": 0, 124 | "tilesetName": "media/tiles/basic-tiles-64.png", 125 | "repeat": false, 126 | "preRender": false, 127 | "distance": "1", 128 | "tilesize": 64, 129 | "foreground": false, 130 | "data": [ 131 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 132 | [0,0,0,0,0,15,15,0,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 133 | [0,0,0,29,16,0,0,30,0,0,24,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 134 | [0,0,22,0,0,0,0,0,0,0,0,0,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 135 | [0,0,23,0,0,0,0,0,0,0,0,0,22,13,13,31,31,13,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 136 | [0,15,0,0,0,14,14,0,14,14,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0], 137 | [0,15,0,0,0,14,14,0,14,14,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0], 138 | [0,0,30,0,0,0,0,0,0,0,0,0,22,13,31,0,0,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 139 | [0,0,16,0,0,0,0,0,0,0,0,0,13,0,31,0,0,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 140 | [0,0,0,16,22,22,0,0,0,22,30,29,0,0,31,0,0,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 141 | [0,0,0,0,0,7,0,0,0,7,0,0,0,0,31,0,0,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 142 | [0,0,0,0,0,6,0,0,0,16,0,0,0,7,22,0,0,22,7,0,0,0,0,0,22,30,22,29,16,22,0,0], 143 | [0,0,0,0,0,6,0,0,0,16,0,0,7,0,0,0,0,0,0,7,0,0,0,13,0,0,0,0,0,0,16,0], 144 | [0,0,0,0,0,7,0,0,0,22,24,24,22,0,0,0,0,0,0,22,24,24,24,23,0,0,0,0,0,0,30,0], 145 | [0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,14,14,0,0,0,0,0,0,0,0,0,14,14,0,0,15,0], 146 | [0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,14,14,0,0,0,0,0,0,0,0,0,14,14,0,0,15,0], 147 | [0,0,0,0,0,22,0,0,0,22,24,24,22,0,0,0,0,0,0,22,24,24,24,23,0,0,0,0,0,0,22,0], 148 | [0,0,0,0,0,7,0,0,0,7,0,0,7,0,0,0,0,0,0,7,0,0,0,13,0,0,0,0,0,0,13,0], 149 | [0,0,0,0,0,0,7,15,7,0,0,0,0,7,22,0,0,22,7,0,0,8,8,29,13,13,0,0,13,29,0,0], 150 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,0,0,24,0,0,4,0,0,0,0,0,0,0,29,0,0,0], 151 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,5,8,8,5,0,0,0,0,0,0,0,13,0,0,0], 152 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,29,0,0,0,0,0,0,0,0,5,13,29,13,22,0,0,0,0], 153 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,0,0,0,0,0,0,0,0,13,0,0,0,0,0,0,0,0], 154 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,5,0,0,0,5,13,16,0,0,0,0,0,0,0,0,0], 155 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,30,13,0,0,0,0,0,0,0,0,0,0,0,0], 156 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 157 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 158 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 159 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 160 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 161 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 162 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] 163 | ] 164 | }, 165 | { 166 | "name": "ceiling", 167 | "width": 32, 168 | "height": 32, 169 | "linkWithCollision": false, 170 | "visible": 1, 171 | "tilesetName": "media/tiles/basic-tiles-64.png", 172 | "repeat": false, 173 | "preRender": false, 174 | "distance": "1", 175 | "tilesize": 64, 176 | "foreground": false, 177 | "data": [ 178 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 179 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 180 | [0,0,0,0,0,2,2,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 181 | [0,0,0,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 182 | [0,0,0,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 183 | [0,0,2,2,2,0,0,2,0,0,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 184 | [0,0,2,2,2,0,0,2,0,0,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 185 | [0,0,0,2,2,2,2,2,2,2,2,2,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 186 | [0,0,0,2,2,2,2,2,2,2,2,2,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 187 | [0,0,0,0,0,0,9,9,9,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 188 | [0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 189 | [0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 190 | [0,0,0,0,0,0,2,2,2,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,2,2,2,2,2,2,0,0], 191 | [0,0,0,0,0,0,2,2,2,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,2,2,2,2,2,2,0,0], 192 | [0,0,0,0,0,0,2,2,2,2,2,2,3,2,2,0,0,2,2,3,2,2,2,9,2,2,0,0,2,2,0,0], 193 | [0,0,0,0,0,0,2,2,2,2,2,2,3,2,2,0,0,2,2,3,2,2,2,9,2,2,0,0,2,2,0,0], 194 | [0,0,0,0,0,0,2,2,2,0,0,0,0,2,2,2,2,2,2,0,0,0,0,2,2,2,2,2,2,2,0,0], 195 | [0,0,0,0,0,0,2,2,2,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,2,2,2,2,2,2,0,0], 196 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0], 197 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,2,2,2,2,2,2,2,0,0,0,0], 198 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,2,2,2,2,2,2,2,0,0,0,0], 199 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0], 200 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0], 201 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0], 202 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 203 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 204 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 205 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 206 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 207 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 208 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 209 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] 210 | ] 211 | }, 212 | { 213 | "name": "light", 214 | "width": 32, 215 | "height": 32, 216 | "linkWithCollision": false, 217 | "visible": 0, 218 | "tilesetName": "media/tiles/lights-64.png", 219 | "repeat": false, 220 | "preRender": false, 221 | "distance": "1", 222 | "tilesize": 64, 223 | "foreground": false, 224 | "data": [ 225 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 226 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 227 | [0,0,0,0,0,119,119,0,119,119,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 228 | [0,0,0,135,119,103,103,119,103,103,119,135,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 229 | [0,0,0,119,103,87,87,103,87,87,103,119,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 230 | [0,0,119,103,87,0,0,87,0,0,87,103,119,135,131,115,99,83,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 231 | [0,0,119,103,87,0,0,87,0,0,87,103,119,135,131,115,99,83,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 232 | [0,0,0,119,103,87,87,103,87,87,103,119,0,0,0,131,131,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 233 | [0,0,0,135,119,103,103,119,103,103,119,135,0,0,0,147,147,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 234 | [0,0,0,0,0,0,119,135,119,0,0,0,0,0,0,147,147,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 235 | [0,0,0,0,0,0,135,151,135,0,0,0,0,0,0,131,131,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 236 | [0,0,0,0,0,0,131,147,151,0,0,0,0,0,0,115,115,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 237 | [0,0,0,0,0,0,115,131,147,0,0,0,0,131,115,99,99,115,131,0,0,0,0,0,140,124,108,108,124,140,0,0], 238 | [0,0,0,0,0,0,99,115,131,0,0,0,0,115,99,83,83,99,115,0,0,0,0,0,124,108,92,92,108,124,0,0], 239 | [0,0,0,0,0,0,83,99,115,131,147,131,115,99,83,0,0,83,99,115,131,147,156,140,108,92,0,0,92,108,0,0], 240 | [0,0,0,0,0,0,83,99,115,131,147,131,115,99,83,0,0,83,99,115,131,147,156,140,108,92,0,0,92,108,0,0], 241 | [0,0,0,0,0,0,99,115,131,0,0,0,0,115,99,83,83,99,115,0,0,0,0,0,124,108,92,92,108,124,0,0], 242 | [0,0,0,0,0,0,115,131,147,0,0,0,0,131,115,99,99,115,131,0,0,0,0,0,140,124,108,108,124,140,0,0], 243 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,115,115,0,0,0,0,0,0,0,0,0,124,124,0,0,0,0], 244 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,131,131,0,0,0,0,85,85,101,117,133,140,140,0,0,0,0], 245 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,149,149,0,0,0,0,101,101,117,133,149,156,156,0,0,0,0], 246 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,133,117,101,85,85,101,117,133,0,0,0,0,0,0,0,0,0], 247 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,149,133,117,101,101,117,133,149,0,0,0,0,0,0,0,0,0], 248 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,133,117,117,0,0,0,0,0,0,0,0,0,0,0,0], 249 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 250 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 251 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 252 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 253 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 254 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 255 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 256 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] 257 | ] 258 | } 259 | ] 260 | }/*]JSON*/; 261 | LevelBase1Resources=[new ig.Image('media/tiles/basic-tiles-64.png'), new ig.Image('media/tiles/basic-tiles-64.png'), new ig.Image('media/tiles/basic-tiles-64.png'), new ig.Image('media/tiles/lights-64.png')]; 262 | }); -------------------------------------------------------------------------------- /lib/game/main.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'game.main' 3 | ) 4 | .requires( 5 | 'impact.game', 6 | 'impact.font', 7 | 8 | 'plugins.twopointfive.game', 9 | 10 | 'plugins.touch-button', 11 | 'plugins.touch-field', 12 | 'plugins.gamepad', 13 | 14 | 'game.levels.base1', 15 | 'game.entities.enemy-blob', 16 | 17 | 'game.entities.grenade-pickup', 18 | 'game.entities.health-pickup', 19 | 20 | 21 | 'game.title', 22 | 'game.hud' 23 | 24 | // ,'plugins.twopointfive.debug' 25 | ) 26 | .defines(function(){ "use strict"; 27 | 28 | 29 | var MyGame = tpf.Game.extend({ 30 | sectorSize: 4, 31 | hud: null, 32 | 33 | dead: false, 34 | menu: null, 35 | 36 | touchButtons: null, 37 | touchFieldMove: null, 38 | touchFieldTurn: null, 39 | 40 | gravity: 4, 41 | 42 | blobSpawnWaitInitial: 2, 43 | blobSpawnWaitCurrent: 2, 44 | blobSpawnWaitDiv: 1.01, 45 | blobKillCount: 0, 46 | blobSpawnTimer: null, 47 | 48 | powerupSpawnWait: 8, 49 | powerupSpawnTimer: null, 50 | 51 | init: function() { 52 | // Setup HTML Checkboxes and mouse lock on click 53 | if( !ig.ua.mobile ) { 54 | ig.$('#requestFullscreen').addEventListener('click', function( ev ) { 55 | ig.system.requestFullscreen(); 56 | ev.preventDefault(); 57 | this.blur(); 58 | return false; 59 | },false); 60 | 61 | ig.$('#riftStereoMode').addEventListener('change', function( ev ) { 62 | ig.system.setStereoMode(ev.target.checked); 63 | ev.preventDefault(); 64 | this.blur(); 65 | return false; 66 | },false); 67 | 68 | if( ig.$('#riftStereoMode').checked ) { 69 | ig.system.setStereoMode(true); 70 | } 71 | 72 | ig.system.canvas.addEventListener('click', function(){ 73 | ig.system.requestMouseLock(); 74 | }); 75 | } 76 | 77 | // Setup Controls 78 | ig.input.bind( ig.KEY.MOUSE1, 'click' ); 79 | if( ig.ua.mobile ) { 80 | this.setupTouchControls(); 81 | } 82 | else { 83 | this.setupDesktopControls(); 84 | } 85 | 86 | 87 | this.setTitle(); 88 | }, 89 | 90 | setTitle: function() { 91 | this.menu = new MyTitle(); 92 | }, 93 | 94 | setGame: function() { 95 | this.menu = null; 96 | this.dead = false; 97 | this.hud = new MyHud( 640, 480 ); 98 | 99 | this.blobKillCount = 0; 100 | this.blobSpawnWaitInitial = this.blobSpawnWaitInitial; 101 | this.blobSpawnTimer = new ig.Timer(this.blobSpawnWaitInitial); 102 | this.powerupSpawnTimer = new ig.Timer(this.powerupSpawnWait); 103 | 104 | // Load the last level we've been in or the default Base1 105 | this.loadLevel( this.lastLevel || LevelBase1 ); 106 | }, 107 | 108 | setupDesktopControls: function() { 109 | // Setup keyboard & mouse controls 110 | ig.input.bind( ig.KEY.UP_ARROW, 'forward' ); 111 | ig.input.bind( ig.KEY.LEFT_ARROW, 'left' ); 112 | ig.input.bind( ig.KEY.DOWN_ARROW, 'back' ); 113 | ig.input.bind( ig.KEY.RIGHT_ARROW, 'right' ); 114 | 115 | ig.input.bind( ig.KEY.C, 'shoot' ); 116 | ig.input.bind( ig.KEY.ENTER, 'shoot' ); 117 | ig.input.bind( ig.KEY.X, 'run' ); 118 | ig.input.bind( ig.KEY.V, 'weaponNext' ); 119 | 120 | ig.input.bind( ig.KEY.ESC, 'pause' ); 121 | 122 | ig.input.bind( ig.KEY.W, 'forward' ); 123 | ig.input.bind( ig.KEY.A, 'stepleft' ); 124 | ig.input.bind( ig.KEY.S, 'back' ); 125 | ig.input.bind( ig.KEY.D, 'stepright' ); 126 | 127 | ig.input.bind( ig.KEY.SHIFT, 'run' ); 128 | ig.input.bind( ig.KEY.CTRL, 'shoot' ); 129 | 130 | ig.input.bind( ig.KEY.MOUSE2, 'run' ); 131 | ig.input.bind( ig.KEY.MWHEEL_UP, 'weaponNext' ); 132 | ig.input.bind( ig.KEY.MWHEEL_DOWN, 'weaponPrev' ); 133 | 134 | // Setup Gamepad 135 | ig.input.bind( ig.GAMEPAD.PAD_TOP, 'forward' ); 136 | ig.input.bind( ig.GAMEPAD.PAD_LEFT, 'left' ); 137 | ig.input.bind( ig.GAMEPAD.PAD_BOTTOM, 'back' ); 138 | ig.input.bind( ig.GAMEPAD.PAD_RIGHT, 'right' ); 139 | 140 | ig.input.bind( ig.GAMEPAD.RIGHT_SHOULDER_BOTTOM, 'shoot' ); 141 | ig.input.bind( ig.GAMEPAD.LEFT_SHOULDER_BOTTOM, 'run' ); 142 | ig.input.bind( ig.GAMEPAD.FACE_1, 'shoot' ); 143 | ig.input.bind( ig.GAMEPAD.FACE_4, 'reset-tracking' ); 144 | ig.input.bind( ig.GAMEPAD.FACE_3, 'weaponNext' ); 145 | ig.input.bind( ig.GAMEPAD.FACE_2, 'weaponPrev' ); 146 | }, 147 | 148 | setupTouchControls: function() { 149 | if( this.touchButtons ) { this.touchButtons.remove(); } 150 | if( this.touchFieldMove ) { this.touchFieldMove.remove(); } 151 | if( this.touchFieldTurn ) { this.touchFieldTurn.remove(); } 152 | 153 | // Touch buttons are anchored to either the left or right and top or bottom 154 | // edge of the screen. 155 | this.touchButtons = new ig.TouchButtonCollection([ 156 | new ig.TouchButton( 'shoot', {right: 0, bottom: 0}, ig.system.width/2, ig.system.height/4 ) 157 | ]); 158 | this.touchButtons.align(); 159 | 160 | this.touchFieldMove = new ig.TouchField(0, 0, ig.system.width/2, ig.system.height); 161 | this.touchFieldTurn = new ig.TouchField(ig.system.width/2, 0, ig.system.width/2, ig.system.height/4*3); 162 | }, 163 | 164 | loadLevel: function( data ) { 165 | this.lastLevel = data; 166 | this.clearColor = null; 167 | 168 | // Find the info entity 169 | var info = null; 170 | for( var i = 0; i < data.entities.length; i++ ) { 171 | if( data.entities[i].settings && data.entities[i].settings.name == 'info' ) { 172 | info = data.entities[i].settings; 173 | } 174 | } 175 | 176 | // Use the sector size specified in the info entity or default (4) 177 | this.sectorSize = (info && info.sectorSize) || 4; 178 | 179 | // Load the map 180 | this.parent( data ); 181 | 182 | // Set the fog and fog color (never use fog on mobile) 183 | if( info && typeof info.fogColor !== 'undefined' && !ig.ua.mobile ) { 184 | ig.system.renderer.setFog( parseInt(info.fogColor), info.fogNear, info.fogFar ); 185 | } 186 | else { 187 | ig.system.renderer.setFog( false ); 188 | } 189 | 190 | // Remember the floor map, so we know where we can spawn entities 191 | this.floorMap = this.getMapByName('floor'); 192 | }, 193 | 194 | 195 | update: function() { 196 | // Reset tracking position for WebVR on button press 197 | if( ig.input.pressed('reset-tracking') && ig.system.renderer instanceof tpf.StereoRenderer ) { 198 | ig.system.renderer.reset(); 199 | } 200 | 201 | if( this.menu ) { 202 | // If we have a menu don't update anything else 203 | this.menu.update(); 204 | return; 205 | } 206 | 207 | if( this.dead ) { 208 | // Wait for keypress if we are dead 209 | if( ig.input.released('shoot') || (!ig.ua.mobile && ig.input.released('click')) ) { 210 | this.setTitle(); 211 | } 212 | } 213 | else { 214 | // Is it time to spawn another Blob? 215 | if( this.blobSpawnTimer.delta() > 0 ) { 216 | this.spawnBlob(); 217 | } 218 | if( this.powerupSpawnTimer.delta() > 0 ) { 219 | this.spawnPowerup(); 220 | } 221 | } 222 | 223 | // Update all entities and backgroundMaps 224 | this.parent(); 225 | 226 | // Roll the death animation; just move the camera down a bit. 227 | if( this.deathAnimTimer ) { 228 | var delta = this.deathAnimTimer.delta(); 229 | if( delta < 0 ) { 230 | ig.system.camera.position[1] = delta.map( 231 | -this.deathAnimTimer.target, 0, 232 | 0, -ig.game.collisionMap.tilesize / 4 233 | ); 234 | } 235 | else { 236 | this.deathAnimTimer = null; 237 | this.dead = true; 238 | } 239 | } 240 | }, 241 | 242 | spawnBlob: function() { 243 | var spawnPos = null, 244 | playerPos = this.player.pos; 245 | 246 | // Try a few times to find a spawn position that's not too close 247 | // to the player 248 | for( var i = 0; i < 10; i++ ) { 249 | spawnPos = this.getRandomSpawnPos(); 250 | if( Math.abs(spawnPos.x - playerPos.x) + Math.abs(spawnPos.y - playerPos.y) > 256 ) { 251 | // Far enough; all good! 252 | break; 253 | } 254 | } 255 | this.spawnEntity(EntityEnemyBlobSpawner, spawnPos.x, spawnPos.y); 256 | 257 | this.blobSpawnWaitCurrent /= this.blobSpawnWaitDiv; 258 | this.blobSpawnTimer.set( Math.max(this.blobSpawnWaitCurrent, 0.5) ); 259 | }, 260 | 261 | spawnPowerup: function() { 262 | // 1/3 chance for health, 2/3 chance for grenades 263 | var powerups = [EntityHealthPickup, EntityGrenadePickup, EntityGrenadePickup]; 264 | var entityClass = powerups.random(); 265 | 266 | var pos = this.getRandomSpawnPos(); 267 | this.spawnEntity(entityClass, pos.x, pos.y); 268 | 269 | this.powerupSpawnTimer.reset(); 270 | }, 271 | 272 | getRandomSpawnPos: function() { 273 | // This randomly probes the floor map and stops at the first tile 274 | // that is set. If the floor map is empty, this results in an 275 | // endless loop, so... better have a floor map in your level! 276 | var ts = this.floorMap.tilesize; 277 | while( true ) { 278 | var x = ((Math.random() * this.floorMap.width)|0) * ts + ts/2, 279 | y = ((Math.random() * this.floorMap.height)|0) * ts + ts/2; 280 | 281 | if( this.floorMap.getTile(x, y) ) { 282 | return { x: x, y:y }; 283 | } 284 | } 285 | }, 286 | 287 | showDeathAnim: function() { 288 | this.deathAnimTimer = new ig.Timer( 1 ); 289 | }, 290 | 291 | drawWorld: function() { 292 | this.parent(); 293 | }, 294 | 295 | drawHud: function() { 296 | ig.system.renderer.hudFreelook = false; 297 | if( this.player ) { 298 | ig.game.hud.draw(this.player, this.player.currentWeapon); 299 | } 300 | 301 | if( this.menu ) { 302 | ig.system.renderer.hudFreelook = true; 303 | this.menu.draw(); 304 | } 305 | } 306 | }); 307 | 308 | 309 | document.body.className = 310 | (ig.System.hasWebGL() ? 'webgl' : 'no-webgl') + ' ' + 311 | (ig.ua.mobile ? 'mobile' : 'desktop'); 312 | 313 | 314 | var width = 640; 315 | var height = 480; 316 | 317 | if( window.Ejecta ) { 318 | var canvas = ig.$('#canvas'); 319 | width = window.innerWidth; 320 | height = window.innerHeight; 321 | 322 | canvas.style.width = window.innerWidth + 'px'; 323 | canvas.style.height = window.innerHeight + 'px'; 324 | } 325 | else if( ig.ua.mobile ) { 326 | ig.$('#game').className = 'mobile'; 327 | var canvas = ig.$('#canvas'); 328 | 329 | // Listen to the window's 'resize' event and set the canvas' size each time 330 | // it changes. 331 | // Wait 16ms, because iOS might report the wrong window size immediately 332 | // after rotation. 333 | window.addEventListener('resize', function(){ setTimeout(function(){ 334 | if( ig.system ) { ig.system.resize( window.innerWidth, window.innerHeight ); } 335 | if( ig.game ) { ig.game.setupTouchControls(); } 336 | }, 16); }, false); 337 | 338 | width = window.innerWidth; 339 | height = window.innerHeight; 340 | } 341 | 342 | 343 | ig.Sound.use = [ig.Sound.FORMAT.OGG, ig.Sound.FORMAT.M4A]; 344 | 345 | // Test WebGL support and init 346 | if( ig.System.hasWebGL() ) { 347 | ig.main( '#canvas', MyGame, 60, width, height, 1, tpf.Loader ); 348 | } 349 | else { 350 | ig.$('#game').style.display = 'none'; 351 | ig.$('#no-webgl').style.display = 'block'; 352 | } 353 | 354 | }); 355 | -------------------------------------------------------------------------------- /lib/game/title.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'game.title' 3 | ) 4 | .requires( 5 | 'plugins.twopointfive.font', 6 | 'plugins.twopointfive.world.tile' 7 | ) 8 | .defines(function(){ 9 | 10 | MyTitle = ig.Class.extend({ 11 | camera: null, 12 | fadeScreen: null, 13 | 14 | width: 640, 15 | height: 480, 16 | 17 | font: new tpf.Font( 'media/fredoka-one.font.png' ), 18 | 19 | titleImage: new ig.Image( 'media/title.png' ), 20 | title: null, 21 | background: null, 22 | timer: null, 23 | 24 | init: function() { 25 | // Create the tile for the title image 26 | this.title = new tpf.HudTile( this.titleImage, 0, this.titleImage.width, this.titleImage.height); 27 | this.title.setPosition(0, 64); 28 | 29 | // Create an empty quad with a dark blue color as the background 30 | this.background = new tpf.Quad(this.width, this.height); 31 | this.background.setPosition(this.width/2, this.height/2,0) 32 | this.background.setColor({r:0.16, g:0.3, b:0.5}); 33 | 34 | 35 | this.camera = new tpf.OrthoCamera(this.width, this.height); 36 | this.timer = new ig.Timer(); 37 | }, 38 | 39 | update: function() { 40 | if( ig.input.released('shoot') || ig.input.released('click') ) { 41 | ig.game.setGame(); 42 | } 43 | }, 44 | 45 | draw: function() { 46 | ig.system.renderer.setCamera(this.camera); 47 | ig.system.renderer.pushQuad(this.background); 48 | this.title.draw(); 49 | 50 | var message = ig.ua.mobile 51 | ? 'Tap to Start' 52 | : 'Click to Start'; 53 | var alpha = (Math.sin(this.timer.delta()*4)+1)*0.5; 54 | this.font.draw(message, this.width/2, 350, ig.Font.ALIGN.CENTER, alpha); 55 | } 56 | }); 57 | 58 | }); 59 | -------------------------------------------------------------------------------- /lib/game/weapons/base.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'game.weapons.base' 3 | ) 4 | .requires( 5 | 'plugins.twopointfive.world.tile', 6 | 'impact.animation' 7 | ) 8 | .defines(function(){ 9 | 10 | Weapon = ig.Class.extend({ 11 | 12 | offset: {x: 0, y: 48}, 13 | offsetAngle: 0, 14 | projectileOffset: 0, 15 | pos: {x: 0, y: 0}, 16 | bobOffset: 0, 17 | 18 | anim: null, 19 | tile: null, 20 | 21 | ammo: 0, 22 | maxAmmo: 100, 23 | anims: [], 24 | 25 | cooldown: 1, 26 | shootTimer: null, 27 | ammoIcon: null, 28 | 29 | currentQuadColor: {r: 1, g: 1, b:1}, 30 | flashQuadColor: {r: 1, g: 1, b:1}, 31 | unsetFlashTimer: null, 32 | 33 | init: function( ammo ) { 34 | this.ammo = ammo || 0; 35 | 36 | this.tile = new tpf.HudTile( 37 | this.animSheet.image, 0, 38 | this.animSheet.width, 39 | this.animSheet.height 40 | ); 41 | 42 | this.pos.x = ig.game.hud.width/2 - this.animSheet.width/2 - this.offset.x; 43 | this.pos.y = ig.game.hud.height - this.offset.y; 44 | 45 | this.shootTimer = new ig.Timer(); 46 | this.tile.setPosition( this.pos.x, this.pos.y + this.bobOffset ); 47 | }, 48 | 49 | 50 | addAnim: function( name, frameTime, sequence, stop ) { 51 | if( !this.animSheet ) { 52 | throw( 'No animSheet to add the animation '+name+' to.' ); 53 | } 54 | var a = new ig.Animation( this.animSheet, frameTime, sequence, stop ); 55 | this.anims[name] = a; 56 | if( !this.currentAnim ) { 57 | this.currentAnim = a; 58 | } 59 | 60 | return a; 61 | }, 62 | 63 | 64 | trigger: function( x, y, angle ) { 65 | if( this.ammo > 0 && this.shootTimer.delta() > 0 ) { 66 | this.shootTimer.set( this.cooldown ); 67 | this.ammo--; 68 | 69 | var offsetAngle = angle - Math.PI/2; 70 | var sx = x -Math.sin(offsetAngle) * this.projectileOffset, 71 | sy = y -Math.cos(offsetAngle) * this.projectileOffset; 72 | 73 | this.shoot( sx, sy, angle + this.offsetAngle ); 74 | } 75 | }, 76 | 77 | depleted: function() { 78 | return (this.shootTimer.delta() > 0 && this.ammo <= 0); 79 | }, 80 | 81 | 82 | giveAmmo: function( ammo ) { 83 | this.ammo = Math.min( this.maxAmmo, this.ammo + ammo); 84 | }, 85 | 86 | 87 | shoot: function( x, y, angle ) { 88 | // Not implemented in the base class 89 | }, 90 | 91 | 92 | setLight: function( color ) { 93 | this.currentQuadColor = color; 94 | 95 | if( !this.tile ) { return; } 96 | this.tile.quad.setColor(color); 97 | }, 98 | 99 | flash: function(duration) { 100 | if( !this.tile ) { return; } 101 | this.tile.quad.setColor(this.flashQuadColor); 102 | this.unsetFlashTimer = new ig.Timer(duration); 103 | }, 104 | 105 | update: function() { 106 | this.currentAnim.update(); 107 | this.tile.setTile( this.currentAnim.tile ); 108 | 109 | this.tile.setPosition( this.pos.x, this.pos.y + this.bobOffset ); 110 | 111 | if( this.unsetFlashTimer && this.unsetFlashTimer.delta() > 0 ) { 112 | this.setLight(this.currentQuadColor); 113 | this.unsetFlashTimer = null; 114 | } 115 | }, 116 | 117 | draw: function() { 118 | this.tile.draw(); 119 | } 120 | }); 121 | 122 | 123 | }); -------------------------------------------------------------------------------- /lib/game/weapons/grenade-launcher.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'game.weapons.grenade-launcher' 3 | ) 4 | .requires( 5 | 'game.weapons.base', 6 | 'plugins.twopointfive.entity', 7 | 'impact.entity-pool' 8 | ) 9 | .defines(function(){ 10 | 11 | WeaponGrenadeLauncher = Weapon.extend({ 12 | offset: {x: 0, y: 128}, 13 | // projectileOffset: -8, 14 | 15 | maxAmmo: 80, 16 | 17 | cooldown: 0.5, 18 | 19 | animSheet: new ig.AnimationSheet( 'media/grenade-launcher.png', 180, 134), 20 | shootSound: new ig.Sound( 'media/sounds/grenade-launcher.*' ), 21 | emptySound: new ig.Sound( 'media/sounds/empty-click.*' ), 22 | ammoIconImage: new ig.Image( 'media/grenade.png' ), 23 | ammoIcon: null, 24 | 25 | init: function( ammo ) { 26 | this.parent( ammo ); 27 | this.addAnim( 'idle', 100, [0] ); 28 | this.addAnim( 'shoot', 0.1, [1,0], true ); 29 | 30 | this.ammoIcon = new tpf.HudTile( this.ammoIconImage, 0, 32, 32); 31 | this.ammoIcon.setPosition( 200, 460) 32 | this.shootSound.volume = 0.8; 33 | }, 34 | 35 | depleted: function() { 36 | if( this.shootTimer.delta() > 0 && this.ammo <= 0 ) { 37 | this.shootTimer.set( this.cooldown ); 38 | this.emptySound.play(); 39 | return true; 40 | } 41 | else { 42 | return false 43 | } 44 | }, 45 | 46 | shoot: function( x, y, angle ) { 47 | ig.game.spawnEntity(EntityGrenade, x, y, {angle: angle} ); 48 | this.currentAnim = this.anims.shoot.rewind(); 49 | this.shootSound.play(); 50 | 51 | this.flash(0.2); 52 | } 53 | }); 54 | 55 | 56 | EntityGrenade = tpf.Entity.extend({ 57 | checkAgainst: ig.Entity.TYPE.B, 58 | collides: ig.Entity.COLLIDES.NEVER, 59 | 60 | size: {x: 8, y: 8}, 61 | speed: 440, 62 | scale: 0.25, 63 | 64 | bounciness: 0.8, 65 | minBounceVelocity: 0.5, 66 | 67 | blastSettings: {radius: 100, damage: 100}, 68 | explosionParticles: 20, 69 | explosionRadius: 60, 70 | 71 | animSheet: new ig.AnimationSheet( 'media/grenade.png', 32, 32 ), 72 | explodeSound: new ig.Sound( 'media/sounds/explosion.*' ), 73 | bounceSound: new ig.Sound( 'media/sounds/grenade-bounce.*' ), 74 | dynamicLight: true, 75 | 76 | 77 | init: function( x, y, settings ) { 78 | this.parent( x-this.size.x/2, y-this.size.y/2, settings ); // center on spawn pos 79 | this.addAnim( 'idle', 1, [0] ); 80 | this.bounceSound.volume = 0.6; 81 | this.explodeSound.volume = 0.9; 82 | 83 | this.vel.x = -Math.sin(this.angle) * this.speed; 84 | this.vel.y = -Math.cos(this.angle) * this.speed; 85 | this.vel.z = 1.2; 86 | this.pos.z = 12; 87 | }, 88 | 89 | reset: function( x, y, settings ) { 90 | this.parent(x,y,settings); 91 | this.vel.x = -Math.sin(this.angle) * this.speed; 92 | this.vel.y = -Math.cos(this.angle) * this.speed; 93 | this.vel.z = 1.2; 94 | this.pos.z = 12; 95 | this.currentAnim = this.anims.idle.rewind(); 96 | }, 97 | 98 | update: function() { 99 | if( this.currentAnim.loopCount > 0 ) { 100 | this.kill(); 101 | return; 102 | } 103 | 104 | var zvel = this.vel.z; 105 | 106 | this.parent(); 107 | 108 | // If the z-velocity did invert in the parent update, we bounced 109 | // of the ground - play the bounce sound! 110 | if( zvel < 0 && this.vel.z > 0 ) { 111 | this.bounceSound.play(); 112 | } 113 | }, 114 | 115 | check: function( other ) { 116 | this.kill(); 117 | }, 118 | 119 | handleMovementTrace: function( res ) { 120 | if( res.collision.x || res.collision.y ) { 121 | this.bounceSound.play(); 122 | } 123 | this.parent(res); 124 | }, 125 | 126 | kill: function() { 127 | for( var i = 0; i < this.explosionParticles; i++ ) { 128 | var x = this.pos.x 129 | + Math.random() * this.explosionRadius * 2 130 | - this.explosionRadius; 131 | var y = this.pos.y 132 | + Math.random() * this.explosionRadius * 2 133 | - this.explosionRadius; 134 | ig.game.spawnEntity(EntityGrenadeExplosion, x, y ); 135 | } 136 | 137 | ig.game.spawnEntity(EntityBlastRadius, this.pos.x, this.pos.y, this.blastSettings ); 138 | this.explodeSound.play(); 139 | this.parent(); 140 | } 141 | }); 142 | 143 | ig.EntityPool.enableFor(EntityGrenade); 144 | 145 | 146 | // This invisible entity will spawn and immediately die a frame later. It will 147 | // give every other entity it touches a bit of damage. 148 | EntityBlastRadius = ig.Entity.extend({ 149 | frame: 0, 150 | radius: 8, 151 | damage: 20, 152 | checkAgainst: ig.Entity.TYPE.B, 153 | 154 | init: function( x, y, settings ) { 155 | var offset = settings.radius || this.radius; 156 | this.size.x = this.size.y = offset * 2; 157 | this.parent( x - offset, y - offset, settings ); 158 | }, 159 | 160 | update: function() { 161 | if( this.frame == 2 ) { 162 | this.kill(); 163 | } 164 | this.frame++; 165 | }, 166 | 167 | draw: function() {}, 168 | 169 | check: function( other ) { 170 | if( this.frame != 1 ) { return; } 171 | 172 | var f = 1 - (this.distanceTo(other) / this.radius); // normalize to 0..1 range 173 | if( f > 0 ) { 174 | var damage = Math.ceil( Math.sqrt(f) * this.damage); 175 | other.receiveDamage( damage, this ); 176 | } 177 | } 178 | }); 179 | 180 | 181 | EntityGrenadeExplosion = tpf.Entity.extend({ 182 | size: {x: 0, y: 0}, 183 | vpos: 2, 184 | scale: 1, 185 | 186 | gravityFactor: 0, 187 | 188 | animSheet: new ig.AnimationSheet( 'media/explosion.png', 32, 32 ), 189 | 190 | init: function( x, y, settings ) { 191 | var frameTime = Math.random() * 0.1 + 0.03; 192 | this.addAnim( 'idle', frameTime, [0,1,2,3], true ); 193 | this.parent( x, y, settings ); 194 | 195 | this.pos.z = Math.random() * 20; 196 | }, 197 | 198 | reset: function(x,y,settings) { 199 | this.currentAnim.rewind(); 200 | this.parent(x,y,settings); 201 | }, 202 | 203 | update: function() { 204 | this.parent(); 205 | if( this.currentAnim.loopCount ) { 206 | this.kill(); 207 | } 208 | } 209 | }); 210 | 211 | ig.EntityPool.enableFor(EntityGrenadeExplosion); 212 | 213 | }); -------------------------------------------------------------------------------- /lib/plugins/gamepad.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'plugins.gamepad' 3 | ) 4 | .requires( 5 | 'impact.input', 6 | 'impact.game' 7 | ) 8 | .defines(function(){ 9 | 10 | // Assign some values to the Gamepad buttons. We use an offset of 256 11 | // here so we don't collide with the keyboard buttons when binding. 12 | ig.GAMEPAD_BUTTON_OFFSET = 256; 13 | ig.GAMEPAD = { 14 | FACE_1: ig.GAMEPAD_BUTTON_OFFSET + 0, // A 15 | FACE_2: ig.GAMEPAD_BUTTON_OFFSET + 1, // Y 16 | FACE_3: ig.GAMEPAD_BUTTON_OFFSET + 2, // B 17 | FACE_4: ig.GAMEPAD_BUTTON_OFFSET + 3, // X 18 | LEFT_SHOULDER: ig.GAMEPAD_BUTTON_OFFSET + 4, 19 | RIGHT_SHOULDER: ig.GAMEPAD_BUTTON_OFFSET + 5, 20 | LEFT_SHOULDER_BOTTOM: ig.GAMEPAD_BUTTON_OFFSET + 6, 21 | RIGHT_SHOULDER_BOTTOM: ig.GAMEPAD_BUTTON_OFFSET + 7, 22 | SELECT: ig.GAMEPAD_BUTTON_OFFSET + 8, 23 | START: ig.GAMEPAD_BUTTON_OFFSET + 9, 24 | LEFT_ANALOGUE_STICK: ig.GAMEPAD_BUTTON_OFFSET + 10, 25 | RIGHT_ANALOGUE_STICK: ig.GAMEPAD_BUTTON_OFFSET + 11, 26 | PAD_TOP: ig.GAMEPAD_BUTTON_OFFSET + 12, 27 | PAD_BOTTOM: ig.GAMEPAD_BUTTON_OFFSET + 13, 28 | PAD_LEFT: ig.GAMEPAD_BUTTON_OFFSET + 14, 29 | PAD_RIGHT: ig.GAMEPAD_BUTTON_OFFSET + 15 30 | }; 31 | 32 | 33 | ig.normalizeVendorAttribute(navigator, 'getGamepads'); 34 | 35 | if( !navigator.getGamepads ) { 36 | // No Gamepad support; nothing to do here 37 | return; 38 | } 39 | 40 | ig.Input.inject({ 41 | gamepad: null, 42 | lastButtons: {}, 43 | hasButtonObject: !!window.GamepadButton, 44 | 45 | getFirstGamepadSnapshot: function() { 46 | var gamepads = navigator.getGamepads(); 47 | for( var i = 0; i < gamepads.length; i++ ) { 48 | if( gamepads[i] ) { 49 | return gamepads[i]; 50 | } 51 | } 52 | return null; 53 | }, 54 | 55 | pollGamepad: function() { 56 | this.gamepad = this.getFirstGamepadSnapshot(); 57 | if( !this.gamepad ) { 58 | // No gamepad snapshot? 59 | return; 60 | } 61 | 62 | // Iterate over all buttons, see if they're bound and check 63 | // for their state 64 | for( var b = 0; b < this.gamepad.buttons.length; b++ ) { 65 | var action = this.bindings[b+ig.GAMEPAD_BUTTON_OFFSET]; 66 | var currentState = false; 67 | 68 | // Is the button bound to an action? 69 | if( action ) { 70 | var button = this.gamepad.buttons[b]; 71 | currentState = (typeof button.pressed !== 'undefined') 72 | ? button.pressed // W3C Standard 73 | : button; // Current Chrome version 74 | 75 | var prevState = this.lastButtons[b]; 76 | 77 | // Was not pressed, but is now? 78 | if( !prevState && currentState ) { 79 | this.actions[action] = true; 80 | this.presses[action] = true; 81 | } 82 | // Was pressed, but is no more? 83 | else if( prevState && !currentState ) { 84 | this.delayedKeyup[action] = true; 85 | } 86 | } 87 | 88 | this.lastButtons[b] = currentState; 89 | } 90 | } 91 | }); 92 | 93 | // Always poll gamepad before each frame 94 | ig.Game.inject({ 95 | run: function() { 96 | ig.input.pollGamepad(); 97 | this.parent(); 98 | } 99 | }) 100 | 101 | }); -------------------------------------------------------------------------------- /lib/plugins/mouse-delta.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'plugins.mouse-delta' 3 | ) 4 | .requires( 5 | 'impact.input' 6 | ) 7 | .defines(function(){ 8 | 9 | ig.Input.inject({ 10 | mouseDelta: {x: 0, y: 0}, 11 | 12 | mousemove: function( event ) { 13 | var oldX = this.mouse.x; 14 | var oldY = this.mouse.y; 15 | 16 | this.parent( event ); 17 | 18 | // Needed because mousemove() is also called for click events 19 | if( event.type == 'mousemove' ) { 20 | this.mouseDelta.x += 21 | event.movementX || 22 | event.mozMovementX || 23 | event.webkitMovementX || 24 | this.mouse.x - oldX; 25 | 26 | this.mouseDelta.y += 27 | event.movementY || 28 | event.mozMovementY || 29 | event.webkitMovementY || 30 | this.mouse.y - oldY; 31 | } 32 | }, 33 | 34 | clearPressed: function() { 35 | this.parent(); 36 | 37 | this.mouseDelta.x = 0; 38 | this.mouseDelta.y = 0; 39 | } 40 | }) 41 | 42 | }); -------------------------------------------------------------------------------- /lib/plugins/touch-button.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'plugins.touch-button' 3 | ) 4 | .requires( 5 | 'impact.system', 6 | 'impact.input', 7 | 'impact.image' 8 | ) 9 | .defines(function(){ "use strict"; 10 | 11 | 12 | ig.TouchButton = ig.Class.extend({ 13 | action: 'undefined', 14 | image: null, 15 | tile: 0, 16 | pos: {x: 0, y: 0}, 17 | size: {x: 0, y: 0}, 18 | area: {x1: 0, y1:0, x2: 0, y2:0}, 19 | 20 | pressed: false, 21 | touchId: 0, 22 | anchor: null, 23 | 24 | init: function( action, anchor, width, height, image, tile ) { 25 | this.action = action; 26 | this.anchor = anchor; 27 | this.size = {x: width, y: height}; 28 | 29 | this.image = image || null; 30 | this.tile = tile || 0; 31 | }, 32 | 33 | align: function( w, h ) { 34 | if( 'left' in this.anchor ) { 35 | this.pos.x = this.anchor.left; 36 | } 37 | else if( 'right' in this.anchor ) { 38 | this.pos.x = w - this.anchor.right - this.size.x; 39 | } 40 | if( 'top' in this.anchor ) { 41 | this.pos.y = this.anchor.top; 42 | } 43 | else if( 'bottom' in this.anchor ) { 44 | this.pos.y = h - this.anchor.bottom - this.size.y; 45 | } 46 | 47 | var internalWidth = parseInt(ig.system.canvas.offsetWidth) || ig.system.realWidth; 48 | var s = ig.system.scale * (internalWidth / ig.system.realWidth); 49 | this.area = { 50 | x1: this.pos.x * s, y1: this.pos.y * s, 51 | x2: (this.pos.x + this.size.x) * s, y2: (this.pos.y + this.size.y) *s}; 52 | }, 53 | 54 | touchStart: function( ev ) { 55 | if( this.pressed ) { return; } 56 | 57 | var pos = {left: 0, top: 0}; 58 | if( ig.system.canvas.getBoundingClientRect ) { 59 | pos = ig.system.canvas.getBoundingClientRect(); 60 | } 61 | 62 | for( var i = 0; i < ev.touches.length; i++ ) { 63 | var touch = ev.touches[i]; 64 | if( this.checkStart(touch.identifier, touch.clientX - pos.left, touch.clientY - pos.top) ) { 65 | return; 66 | } 67 | } 68 | }, 69 | 70 | touchEnd: function( ev ) { 71 | if( !this.pressed ) { return; } 72 | 73 | for( var i = 0; i < ev.changedTouches.length; i++ ) { 74 | if( this.checkEnd(ev.changedTouches[i].identifier) ) { 75 | return; 76 | } 77 | } 78 | }, 79 | 80 | touchStartMS: function( ev ) { 81 | if( this.pressed ) { return; } 82 | 83 | var pos = {left: 0, top: 0}; 84 | if( ig.system.canvas.getBoundingClientRect ) { 85 | pos = ig.system.canvas.getBoundingClientRect(); 86 | } 87 | 88 | this.checkStart(ev.pointerId, ev.clientX - pos.left, ev.clientY - pos.top); 89 | }, 90 | 91 | touchEndMS: function( ev ) { 92 | if( !this.pressed ) { return; } 93 | 94 | this.checkEnd(ev.pointerId); 95 | }, 96 | 97 | checkStart: function( id, x, y ) { 98 | if( 99 | x > this.area.x1 && x < this.area.x2 && 100 | y > this.area.y1 && y < this.area.y2 101 | ) { 102 | this.pressed = true; 103 | this.touchId = id; 104 | 105 | ig.input.actions[this.action] = true; 106 | if( !ig.input.locks[this.action] ) { 107 | ig.input.presses[this.action] = true; 108 | ig.input.locks[this.action] = true; 109 | } 110 | return true; 111 | } 112 | 113 | return false; 114 | }, 115 | 116 | checkEnd: function( id ) { 117 | if( id === this.touchId ) { 118 | this.pressed = false; 119 | this.touchId = 0; 120 | ig.input.delayedKeyup[this.action] = true; 121 | return true; 122 | } 123 | 124 | return false; 125 | }, 126 | 127 | draw: function() { 128 | if( this.image ) { 129 | this.image.drawTile( this.pos.x, this.pos.y, this.tile, this.size.x, this.size.y ); 130 | } 131 | } 132 | }); 133 | 134 | 135 | 136 | ig.TouchButtonCollection = ig.Class.extend({ 137 | buttons: [], 138 | 139 | touchStartBound: null, 140 | touchEndBound: null, 141 | touchStartMSBound: null, 142 | touchEndMSBound: null, 143 | 144 | init: function( buttons ) { 145 | this.buttons = buttons; 146 | 147 | this.touchStartBound = this.touchStart.bind(this); 148 | this.touchEndBound = this.touchEnd.bind(this); 149 | 150 | this.touchStartMSBound = this.touchStartMS.bind(this); 151 | this.touchEndMSBound = this.touchEndMS.bind(this); 152 | 153 | ig.system.canvas.addEventListener('touchstart', this.touchStartBound, false); 154 | ig.system.canvas.addEventListener('touchend', this.touchEndBound, false); 155 | 156 | ig.system.canvas.addEventListener('MSPointerDown', this.touchStartMSBound, false); 157 | ig.system.canvas.addEventListener('MSPointerUp', this.touchStartMSBound, false); 158 | document.body.style.msTouchAction = 'none'; 159 | }, 160 | 161 | remove: function() { 162 | ig.system.canvas.removeEventListener('touchstart', this.touchStartBound, false); 163 | ig.system.canvas.removeEventListener('touchend', this.touchEndBound, false); 164 | 165 | ig.system.canvas.removeEventListener('MSPointerDown', this.touchStartMSBound, false); 166 | ig.system.canvas.removeEventListener('MSPointerUp', this.touchStartMSBound, false); 167 | }, 168 | 169 | touchStart: function(ev) { 170 | ev.preventDefault(); 171 | 172 | for( var i = 0; i < this.buttons.length; i++ ) { 173 | this.buttons[i].touchStart( ev ); 174 | } 175 | }, 176 | 177 | touchEnd: function(ev) { 178 | ev.preventDefault(); 179 | 180 | for( var i = 0; i < this.buttons.length; i++ ) { 181 | this.buttons[i].touchEnd( ev ); 182 | } 183 | }, 184 | 185 | touchStartMS: function(ev) { 186 | ev.preventDefault(); 187 | 188 | for( var i = 0; i < this.buttons.length; i++ ) { 189 | this.buttons[i].touchStartMS( ev ); 190 | } 191 | }, 192 | 193 | touchEndMS: function(ev) { 194 | ev.preventDefault(); 195 | 196 | for( var i = 0; i < this.buttons.length; i++ ) { 197 | this.buttons[i].touchEndMS( ev ); 198 | } 199 | }, 200 | 201 | align: function() { 202 | var w = ig.system.width || window.innerWidth; 203 | var h = ig.system.height || window.innerHeight; 204 | 205 | for( var i = 0; i < this.buttons.length; i++ ) { 206 | this.buttons[i].align( w, h ); 207 | } 208 | }, 209 | 210 | draw: function() { 211 | for( var i = 0; i < this.buttons.length; i++ ) { 212 | this.buttons[i].draw(); 213 | } 214 | } 215 | }); 216 | 217 | 218 | }); 219 | -------------------------------------------------------------------------------- /lib/plugins/touch-field.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'plugins.touch-field' 3 | ) 4 | .requires( 5 | 'impact.system' 6 | ) 7 | .defines(function(){ 8 | 9 | ig.TouchField = ig.Class.extend({ 10 | pos: {x: 0, y: 0}, 11 | size: {x: 0, y: 0}, 12 | 13 | input: {x: 0, y: 0, dx: 0, dy: 0}, 14 | pressed: false, 15 | 16 | angle: 0, 17 | amount: 0, 18 | 19 | _touchId: null, 20 | _startPos: {x: 0, y: 0}, 21 | touched: false, 22 | 23 | touchStartBound: null, 24 | touchMoveBound: null, 25 | touchEndBound: null, 26 | 27 | init: function( x, y, width, height ) { 28 | this.pos = {x: x, y: y}; 29 | this.size = {x: width, y: height}; 30 | 31 | this.touchStartBound = this.touchStart.bind(this); 32 | this.touchMoveBound = this.touchMove.bind(this); 33 | this.touchEndBound = this.touchEnd.bind(this); 34 | 35 | ig.system.canvas.addEventListener( 'touchstart', this.touchStartBound, false ); 36 | ig.system.canvas.addEventListener( 'touchmove', this.touchMoveBound, false ); 37 | ig.system.canvas.addEventListener( 'touchend', this.touchEndBound, false ); 38 | }, 39 | 40 | remove: function() { 41 | ig.system.canvas.removeEventListener( 'touchstart', this.touchStartBound, false ); 42 | ig.system.canvas.removeEventListener( 'touchmove', this.touchMoveBound, false ); 43 | ig.system.canvas.removeEventListener( 'touchend', this.touchEndBound, false ); 44 | }, 45 | 46 | touchStart: function( ev ) { 47 | ev.preventDefault(); 48 | 49 | if( this.pressed ) { return; } 50 | for( var i = 0; i < ev.touches.length; i++ ) { 51 | var touch = ev.touches[i]; 52 | 53 | var x = touch.pageX; 54 | var y = touch.pageY; 55 | 56 | if( 57 | x > this.pos.x && x < this.pos.x + this.size.x && 58 | y > this.pos.y && y < this.pos.y + this.size.y 59 | ) { 60 | this.pressed = true; 61 | this.touched = true; 62 | this.input.dx = 0; 63 | this.input.dy = 0; 64 | this._touchId = touch.identifier; 65 | this._startPos.x = x; 66 | this._startPos.y = y; 67 | return; 68 | } 69 | } 70 | }, 71 | 72 | touchMove: function( ev ) { 73 | ev.preventDefault(); 74 | 75 | for( var i = 0; i < ev.changedTouches.length; i++ ) { 76 | if( ev.changedTouches[i].identifier == this._touchId ) { 77 | this._moved( ev.changedTouches[i] ); 78 | return; 79 | } 80 | } 81 | }, 82 | 83 | _moved: function( touch ) { 84 | var nx = touch.pageX - this._startPos.x; 85 | var ny = touch.pageY - this._startPos.y; 86 | this.input.dx = this.input.x - nx; 87 | this.input.dy = this.input.y - ny; 88 | this.input.x = nx; 89 | this.input.y = ny; 90 | }, 91 | 92 | touchEnd: function( ev ) { 93 | ev.preventDefault(); 94 | 95 | for( var i = 0; i < ev.changedTouches.length; i++ ) { 96 | if( ev.changedTouches[i].identifier == this._touchId ) { 97 | this.pressed = false; 98 | this.input.x = 0; 99 | this.input.dx = 0; 100 | this.input.y = 0; 101 | this.input.dy = 0; 102 | this._touchId = null; 103 | return; 104 | } 105 | } 106 | } 107 | }); 108 | 109 | 110 | }); -------------------------------------------------------------------------------- /lib/plugins/twopointfive/debug.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'plugins.twopointfive.debug' 3 | ) 4 | .requires( 5 | 'impact.debug.menu', 6 | 'impact.debug.graph-panel', 7 | 'impact.debug.entities-panel', 8 | 9 | 'plugins.twopointfive.game', 10 | 'plugins.twopointfive.world.culled-sectors' 11 | ) 12 | .defines(function(){ "use strict"; 13 | 14 | tpf.Game.inject({ 15 | draw: function() { 16 | ig.graph.beginClock('draw'); 17 | this.parent(); 18 | ig.graph.endClock('draw'); 19 | 20 | ig.Image.drawCount = ig.system.renderer.drawCalls; 21 | ig.debug.showNumber( 'quads', ig.system.renderer.quadCount ); 22 | 23 | if( ig.game.culledSectors ) { 24 | ig.debug.showNumber( 'sectors', ig.game.culledSectors.sectorsTraversed); 25 | } 26 | } 27 | }); 28 | 29 | 30 | tpf.CulledSectors.inject({ 31 | drawEntities: function(visibleSectors) { 32 | if( tpf.CulledSectors._debugDrawEntities ) { 33 | this.parent(visibleSectors); 34 | } 35 | } 36 | }); 37 | tpf.CulledSectors._debugDrawEntities = true; 38 | 39 | ig.debug.addPanel({ 40 | type: ig.DebugPanel, 41 | name: 'tpf', 42 | label: 'TwoPointFive', 43 | options: [ 44 | { 45 | name: 'Wireframe Rendering', 46 | object: { 47 | get wireframe() { return ig.system && ig.system.renderer && ig.system.renderer.wireframe; }, 48 | set wireframe(v) { ig.system.renderer.wireframe = v; } 49 | }, 50 | property: 'wireframe' 51 | }, 52 | { 53 | name: 'Draw Entities', 54 | object: tpf.CulledSectors, 55 | property: '_debugDrawEntities' 56 | } 57 | ] 58 | }); 59 | 60 | 61 | }); -------------------------------------------------------------------------------- /lib/plugins/twopointfive/entity.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'plugins.twopointfive.entity' 3 | ) 4 | .requires( 5 | 'impact.entity', 6 | 7 | 'plugins.twopointfive.namespace', 8 | 'plugins.twopointfive.world.tile' 9 | ) 10 | .defines(function(){ "use strict"; 11 | 12 | 13 | tpf.Entity = ig.Entity.extend({ 14 | tile: null, 15 | scale: 0.25, 16 | 17 | pos: {x: 0, y: 0, z: 0}, 18 | vel: {x: 0, y: 0, z: 0}, 19 | accel: {x: 0, y: 0, z: 0}, 20 | maxVel: {x: 10000, y: 10000, z: 10000}, 21 | 22 | dynamicLight: true, 23 | 24 | _wmDrawBox: true, 25 | _wmBoxColor: '#ff5500', 26 | 27 | 28 | 29 | rotateToView: true, 30 | 31 | __tilePosX: -1, 32 | __tilePosY: -1, 33 | __sectorX: null, 34 | __sectorY: null, 35 | 36 | init: function( x, y, settings ) { 37 | this.parent( x, y, settings ); 38 | 39 | if( ig.global.wm ) { 40 | return; 41 | } 42 | 43 | if( this.animSheet ) { 44 | this.tile = new tpf.Tile( 45 | this.animSheet.image, 0, 46 | this.animSheet.width, this.animSheet.height, 47 | this.scale 48 | ); 49 | 50 | this.updateQuad(); 51 | } 52 | 53 | ig.game.culledSectors.moveEntity(this); 54 | }, 55 | 56 | reset: function( x, y, settings ) { 57 | this.parent( x, y, settings ); 58 | ig.game.culledSectors.moveEntity(this); 59 | this.updateQuad(); 60 | }, 61 | 62 | kill: function() { 63 | this.parent(); 64 | this.remove(); 65 | }, 66 | 67 | handleMovementTrace: function( res ) { 68 | // Impact's handleMovementTrace may omit the z position, 69 | // so remember it here and re-set it afterwards 70 | var z = this.pos.z; 71 | this.parent(res); 72 | this.pos.z = z; 73 | }, 74 | 75 | remove: function() { 76 | ig.game.culledSectors.removeEntity(this); 77 | }, 78 | 79 | updateQuad: function() { 80 | if( this.tile && this.currentAnim ) { 81 | this.tile.setTile( this.currentAnim.tile ); 82 | 83 | var tpos = this.tile.quad.position; 84 | tpos[0] = this.pos.x + this.size.x/2; 85 | tpos[2] = this.pos.y + this.size.y/2; 86 | tpos[1] = this.pos.z 87 | - ig.game.collisionMap.tilesize / 2 88 | + (this.animSheet.height * this.scale) / 2; 89 | 90 | if( this.rotateToView ) { 91 | this.tile.quad.rotation[1] = ig.system.camera.rotation[1]; 92 | } 93 | this.tile.quad._dirty = true; 94 | } 95 | 96 | var lm = ig.game.lightMap; 97 | if( this.dynamicLight && lm ) { 98 | var ntx = Math.floor( (this.pos.x+this.size.x/2) / lm.tilesize), 99 | nty = Math.floor( (this.pos.y+this.size.y/2) / lm.tilesize); 100 | 101 | if( ntx !== this.__tilePosX || nty !== this.__tilePosY ) { 102 | this.__tilePosX = ntx; 103 | this.__tilePosY = nty; 104 | this.setLight( lm.getLight(ntx, nty) ); 105 | } 106 | } 107 | 108 | if( 109 | this.tile && !this._killed && 110 | (this.pos.x != this.last.x || this.pos.y != this.last.y) 111 | ) { 112 | ig.game.culledSectors.moveEntity(this); 113 | } 114 | }, 115 | 116 | canSee: function( other ) { 117 | // Trace a line to the player to check if we have a line of sight 118 | var sx = this.pos.x+this.size.x/2, 119 | sy = this.pos.y+this.size.y/2; 120 | var res = ig.game.collisionMap.trace( 121 | sx, sy, 122 | other.pos.x+other.size.x/2 - sx, other.pos.y+other.size.y/2 - sy, 123 | 1, 1 124 | ); 125 | 126 | return ( !res.collision.x && !res.collision.y ); 127 | }, 128 | 129 | update: function() { 130 | this.last.x = this.pos.x; 131 | this.last.y = this.pos.y; 132 | 133 | this.vel.z -= ig.game.gravity * ig.system.tick * this.gravityFactor; 134 | 135 | this.vel.x = this.getNewVelocity( this.vel.x, this.accel.x, this.friction.x, this.maxVel.x ); 136 | this.vel.y = this.getNewVelocity( this.vel.y, this.accel.y, this.friction.y, this.maxVel.y ); 137 | this.vel.z = this.getNewVelocity( this.vel.z, this.accel.z, 0, this.maxVel.z ); 138 | 139 | // movement & collision 140 | var mx = this.vel.x * ig.system.tick; 141 | var my = this.vel.y * ig.system.tick; 142 | var res = ig.game.collisionMap.trace( 143 | this.pos.x, this.pos.y, mx, my, this.size.x, this.size.y 144 | ); 145 | this.handleMovementTrace( res ); 146 | 147 | // handle the z-axis collision with the floor 148 | this.pos.z += this.vel.z; 149 | if( this.pos.z < 0 ) { 150 | if( this.bounciness > 0 && Math.abs(this.vel.z) > this.minBounceVelocity ) { 151 | this.vel.z *= -this.bounciness; 152 | } 153 | else { 154 | this.vel.z = 0; 155 | } 156 | this.pos.z = 0; 157 | } 158 | 159 | if( this.currentAnim ) { 160 | this.currentAnim.update(); 161 | } 162 | 163 | this.updateQuad(); 164 | }, 165 | 166 | setLight: function( color ) { 167 | if( !this.tile ) { return; } 168 | this.tile.quad.setColor(color); 169 | }, 170 | 171 | draw: function() { 172 | if( ig.global.wm ) { 173 | return; 174 | } 175 | else if( this.tile ) { 176 | this.tile.draw(); 177 | } 178 | } 179 | }); 180 | 181 | }); -------------------------------------------------------------------------------- /lib/plugins/twopointfive/font.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'plugins.twopointfive.font' 3 | ) 4 | .requires( 5 | 'impact.font', 6 | 7 | 'plugins.twopointfive.namespace', 8 | 'plugins.twopointfive.renderer.quad' 9 | ) 10 | .defines(function(){ "use strict"; 11 | 12 | 13 | tpf.Font = ig.Font.extend({ 14 | _quads: [], 15 | _glAlpha: 1, 16 | 17 | draw: function( text, x, y, align, alpha ) { 18 | this._glAlpha = typeof(alpha) != 'undefined' ? alpha : 1; 19 | this.parent(text, x, y, align); 20 | }, 21 | 22 | _drawChar: function( c, targetX, targetY ) { 23 | if( !this.loaded || c < 0 || c >= this.indices.length ) { return 0; } 24 | 25 | var charX = this.indices[c]; 26 | var charY = 0; 27 | var charWidth = this.widthMap[c]; 28 | var charHeight = (this.height-2); 29 | 30 | var q = this._quads[c]; 31 | q.setAlpha(this._glAlpha); 32 | q.setPosition(targetX + charWidth/2, targetY + charHeight/2, 0); 33 | ig.system.renderer.pushQuad(q); 34 | 35 | return charWidth + this.letterSpacing; 36 | }, 37 | 38 | 39 | onload: function( event ) { 40 | this.parent(event); 41 | 42 | var charHeight = this.height-2; 43 | for( var i = 0; i < this.indices.length; i++ ) { 44 | var index = this.indices[i]; 45 | var charWidth = this.widthMap[i]; 46 | 47 | var q = new tpf.Quad(charWidth, charHeight, this.texture); 48 | q.setUV( 49 | index / this.data.width, 0, 50 | (index + charWidth) / this.data.width, charHeight / this.data.height 51 | ); 52 | 53 | this._quads.push(q); 54 | } 55 | } 56 | }); 57 | 58 | }); -------------------------------------------------------------------------------- /lib/plugins/twopointfive/game.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'plugins.twopointfive.game' 3 | ) 4 | .requires( 5 | 'impact.game', 6 | 7 | 'plugins.twopointfive.namespace', 8 | 9 | 'plugins.twopointfive.world.map', 10 | 'plugins.twopointfive.world.wall-map', 11 | 'plugins.twopointfive.world.light-map', 12 | 'plugins.twopointfive.world.culled-sectors', 13 | 14 | 'plugins.twopointfive.entity', 15 | 'plugins.twopointfive.font', 16 | 'plugins.twopointfive.image', 17 | 'plugins.twopointfive.loader', 18 | 'plugins.twopointfive.system' 19 | ) 20 | .defines(function(){ "use strict"; 21 | 22 | 23 | tpf.Game = ig.Game.extend({ 24 | 25 | culledSectors: null, 26 | sectorSize: 4, 27 | clearColor: null, 28 | 29 | clearLevel: function() { 30 | for( var i = 0; i < this.entities.length; i++ ) { 31 | if( this.entities[i] instanceof tpf.Entity ) { 32 | this.entities[i].remove(); 33 | } 34 | } 35 | this.entities = []; 36 | this.namedEntities = {}; 37 | 38 | this.culledSectors = null; 39 | this.collisionMap = ig.CollisionMap.staticNoCollision; 40 | this.backgroundMaps = []; 41 | 42 | this.lightMap = null; 43 | }, 44 | 45 | 46 | loadLevel: function( data ) { 47 | this.clearLevel(); 48 | 49 | // Map Layer 50 | for( var i = 0; i < data.layer.length; i++ ) { 51 | var ld = data.layer[i]; 52 | if( ld.name == 'collision' ) { 53 | this.collisionMap = new ig.CollisionMap(ld.tilesize, ld.data ); 54 | } 55 | else if( ld.name == 'light' ) { 56 | this.lightMap = new tpf.LightMap( ld.tilesize, ld.data, ld.tilesetName ); 57 | } 58 | else if( ld.name == 'walls' || ld.name == 'floor' || ld.name == 'ceiling' ) { 59 | var MapClass = ld.name == 'walls' ? tpf.WallMap : tpf.Map; 60 | var anims = this.backgroundAnims[ld.tilesetName] || {}; 61 | var newMap = new MapClass( ld.tilesize, ld.data, ld.tilesetName, ld.name, anims ); 62 | newMap.name = ld.name; 63 | this.backgroundMaps.push( newMap ); 64 | } 65 | } 66 | 67 | 68 | // Erase all faces from the wall map that are not connected to a floor 69 | var floorMap = this.getMapByName('floor'); 70 | var wallMap = this.getMapByName('walls'); 71 | 72 | if( floorMap && wallMap ) { 73 | wallMap.eraseDisconnectedWalls( floorMap ); 74 | } 75 | 76 | 77 | // Apply lightmap on all background maps if we have one 78 | if( this.lightMap ) { 79 | for( var i = 0; i < this.backgroundMaps.length; i++ ) { 80 | this.backgroundMaps[i].applyLightMap( this.lightMap ); 81 | } 82 | } 83 | 84 | // Create the culled sector map, using the floor map as a guide to where the player 85 | // can travel. Add the geometry from all background maps 86 | this.culledSectors = new tpf.CulledSectors( floorMap, this.backgroundMaps, this.sectorSize ); 87 | 88 | 89 | for( var i = 0; i < data.entities.length; i++ ) { 90 | var ent = data.entities[i]; 91 | this.spawnEntity( ent.type, ent.x, ent.y, ent.settings ); 92 | } 93 | 94 | // Call post-init ready function on all entities 95 | for( var i = 0; i < this.entities.length; i++ ) { 96 | this.entities[i].ready(); 97 | } 98 | }, 99 | 100 | draw: function() { 101 | ig.system.renderer.render(this.drawCallback.bind(this)); 102 | }, 103 | 104 | drawCallback: function(renderer) { 105 | if( this.clearColor ) { 106 | var c = this.clearColor; 107 | ig.system.renderer.gl.clearColor(c[0],c[1],c[2],1); 108 | } 109 | ig.system.renderer.clear(!!this.clearColor, true); 110 | 111 | this.drawWorld(); 112 | 113 | var fog = ig.system.renderer.fog; 114 | ig.system.renderer.setFog(false); 115 | this.drawHud(); 116 | if( fog ) { ig.system.renderer.setFog( fog.color, fog.near, fog.far ); } 117 | }, 118 | 119 | drawWorld: function() { 120 | if( !this.culledSectors ) { 121 | return; 122 | } 123 | 124 | 125 | ig.system.renderer.setCamera(ig.system.camera); 126 | 127 | // Update culled sectors 128 | var 129 | cx = ig.system.camera.position[0], 130 | cy = ig.system.camera.position[2], 131 | cullAngle = -ig.system.camera.rotation[1]-Math.PI/2, 132 | fov = ig.system.horizontalFov().toRad(); 133 | 134 | this.culledSectors.draw(cx, cy, cullAngle, fov); 135 | }, 136 | 137 | drawHud: function() {} 138 | }); 139 | 140 | 141 | }); -------------------------------------------------------------------------------- /lib/plugins/twopointfive/gl-matrix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview gl-matrix - High performance matrix and vector operations 3 | * @author Brandon Jones 4 | * @author Colin MacKenzie IV 5 | * @version 2.2.0 6 | */ 7 | /* Copyright (c) 2013, Brandon Jones, Colin MacKenzie IV. All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without modification, 10 | are permitted provided that the following conditions are met: 11 | 12 | * Redistributions of source code must retain the above copyright notice, this 13 | list of conditions and the following disclaimer. 14 | * Redistributions in binary form must reproduce the above copyright notice, 15 | this list of conditions and the following disclaimer in the documentation 16 | and/or other materials provided with the distribution. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ 28 | (function(e){"use strict";var t={};typeof exports=="undefined"?typeof define=="function"&&typeof define.amd=="object"&&define.amd?(t.exports={},define(function(){return t.exports})):t.exports=typeof window!="undefined"?window:e:t.exports=exports,function(e){if(!t)var t=1e-6;if(!n)var n=typeof Float32Array!="undefined"?Float32Array:Array;if(!r)var r=Math.random;var i={};i.setMatrixArrayType=function(e){n=e},typeof e!="undefined"&&(e.glMatrix=i);var s={};s.create=function(){var e=new n(2);return e[0]=0,e[1]=0,e},s.clone=function(e){var t=new n(2);return t[0]=e[0],t[1]=e[1],t},s.fromValues=function(e,t){var r=new n(2);return r[0]=e,r[1]=t,r},s.copy=function(e,t){return e[0]=t[0],e[1]=t[1],e},s.set=function(e,t,n){return e[0]=t,e[1]=n,e},s.add=function(e,t,n){return e[0]=t[0]+n[0],e[1]=t[1]+n[1],e},s.subtract=function(e,t,n){return e[0]=t[0]-n[0],e[1]=t[1]-n[1],e},s.sub=s.subtract,s.multiply=function(e,t,n){return e[0]=t[0]*n[0],e[1]=t[1]*n[1],e},s.mul=s.multiply,s.divide=function(e,t,n){return e[0]=t[0]/n[0],e[1]=t[1]/n[1],e},s.div=s.divide,s.min=function(e,t,n){return e[0]=Math.min(t[0],n[0]),e[1]=Math.min(t[1],n[1]),e},s.max=function(e,t,n){return e[0]=Math.max(t[0],n[0]),e[1]=Math.max(t[1],n[1]),e},s.scale=function(e,t,n){return e[0]=t[0]*n,e[1]=t[1]*n,e},s.scaleAndAdd=function(e,t,n,r){return e[0]=t[0]+n[0]*r,e[1]=t[1]+n[1]*r,e},s.distance=function(e,t){var n=t[0]-e[0],r=t[1]-e[1];return Math.sqrt(n*n+r*r)},s.dist=s.distance,s.squaredDistance=function(e,t){var n=t[0]-e[0],r=t[1]-e[1];return n*n+r*r},s.sqrDist=s.squaredDistance,s.length=function(e){var t=e[0],n=e[1];return Math.sqrt(t*t+n*n)},s.len=s.length,s.squaredLength=function(e){var t=e[0],n=e[1];return t*t+n*n},s.sqrLen=s.squaredLength,s.negate=function(e,t){return e[0]=-t[0],e[1]=-t[1],e},s.normalize=function(e,t){var n=t[0],r=t[1],i=n*n+r*r;return i>0&&(i=1/Math.sqrt(i),e[0]=t[0]*i,e[1]=t[1]*i),e},s.dot=function(e,t){return e[0]*t[0]+e[1]*t[1]},s.cross=function(e,t,n){var r=t[0]*n[1]-t[1]*n[0];return e[0]=e[1]=0,e[2]=r,e},s.lerp=function(e,t,n,r){var i=t[0],s=t[1];return e[0]=i+r*(n[0]-i),e[1]=s+r*(n[1]-s),e},s.random=function(e,t){t=t||1;var n=r()*2*Math.PI;return e[0]=Math.cos(n)*t,e[1]=Math.sin(n)*t,e},s.transformMat2=function(e,t,n){var r=t[0],i=t[1];return e[0]=n[0]*r+n[2]*i,e[1]=n[1]*r+n[3]*i,e},s.transformMat2d=function(e,t,n){var r=t[0],i=t[1];return e[0]=n[0]*r+n[2]*i+n[4],e[1]=n[1]*r+n[3]*i+n[5],e},s.transformMat3=function(e,t,n){var r=t[0],i=t[1];return e[0]=n[0]*r+n[3]*i+n[6],e[1]=n[1]*r+n[4]*i+n[7],e},s.transformMat4=function(e,t,n){var r=t[0],i=t[1];return e[0]=n[0]*r+n[4]*i+n[12],e[1]=n[1]*r+n[5]*i+n[13],e},s.forEach=function(){var e=s.create();return function(t,n,r,i,s,o){var u,a;n||(n=2),r||(r=0),i?a=Math.min(i*n+r,t.length):a=t.length;for(u=r;u0&&(s=1/Math.sqrt(s),e[0]=t[0]*s,e[1]=t[1]*s,e[2]=t[2]*s),e},o.dot=function(e,t){return e[0]*t[0]+e[1]*t[1]+e[2]*t[2]},o.cross=function(e,t,n){var r=t[0],i=t[1],s=t[2],o=n[0],u=n[1],a=n[2];return e[0]=i*a-s*u,e[1]=s*o-r*a,e[2]=r*u-i*o,e},o.lerp=function(e,t,n,r){var i=t[0],s=t[1],o=t[2];return e[0]=i+r*(n[0]-i),e[1]=s+r*(n[1]-s),e[2]=o+r*(n[2]-o),e},o.random=function(e,t){t=t||1;var n=r()*2*Math.PI,i=r()*2-1,s=Math.sqrt(1-i*i)*t;return e[0]=Math.cos(n)*s,e[1]=Math.sin(n)*s,e[2]=i*t,e},o.transformMat4=function(e,t,n){var r=t[0],i=t[1],s=t[2];return e[0]=n[0]*r+n[4]*i+n[8]*s+n[12],e[1]=n[1]*r+n[5]*i+n[9]*s+n[13],e[2]=n[2]*r+n[6]*i+n[10]*s+n[14],e},o.transformMat3=function(e,t,n){var r=t[0],i=t[1],s=t[2];return e[0]=r*n[0]+i*n[3]+s*n[6],e[1]=r*n[1]+i*n[4]+s*n[7],e[2]=r*n[2]+i*n[5]+s*n[8],e},o.transformQuat=function(e,t,n){var r=t[0],i=t[1],s=t[2],o=n[0],u=n[1],a=n[2],f=n[3],l=f*r+u*s-a*i,c=f*i+a*r-o*s,h=f*s+o*i-u*r,p=-o*r-u*i-a*s;return e[0]=l*f+p*-o+c*-a-h*-u,e[1]=c*f+p*-u+h*-o-l*-a,e[2]=h*f+p*-a+l*-u-c*-o,e},o.forEach=function(){var e=o.create();return function(t,n,r,i,s,o){var u,a;n||(n=3),r||(r=0),i?a=Math.min(i*n+r,t.length):a=t.length;for(u=r;u0&&(o=1/Math.sqrt(o),e[0]=t[0]*o,e[1]=t[1]*o,e[2]=t[2]*o,e[3]=t[3]*o),e},u.dot=function(e,t){return e[0]*t[0]+e[1]*t[1]+e[2]*t[2]+e[3]*t[3]},u.lerp=function(e,t,n,r){var i=t[0],s=t[1],o=t[2],u=t[3];return e[0]=i+r*(n[0]-i),e[1]=s+r*(n[1]-s),e[2]=o+r*(n[2]-o),e[3]=u+r*(n[3]-u),e},u.random=function(e,t){return t=t||1,e[0]=r(),e[1]=r(),e[2]=r(),e[3]=r(),u.normalize(e,e),u.scale(e,e,t),e},u.transformMat4=function(e,t,n){var r=t[0],i=t[1],s=t[2],o=t[3];return e[0]=n[0]*r+n[4]*i+n[8]*s+n[12]*o,e[1]=n[1]*r+n[5]*i+n[9]*s+n[13]*o,e[2]=n[2]*r+n[6]*i+n[10]*s+n[14]*o,e[3]=n[3]*r+n[7]*i+n[11]*s+n[15]*o,e},u.transformQuat=function(e,t,n){var r=t[0],i=t[1],s=t[2],o=n[0],u=n[1],a=n[2],f=n[3],l=f*r+u*s-a*i,c=f*i+a*r-o*s,h=f*s+o*i-u*r,p=-o*r-u*i-a*s;return e[0]=l*f+p*-o+c*-a-h*-u,e[1]=c*f+p*-u+h*-o-l*-a,e[2]=h*f+p*-a+l*-u-c*-o,e},u.forEach=function(){var e=u.create();return function(t,n,r,i,s,o){var u,a;n||(n=4),r||(r=0),i?a=Math.min(i*n+r,t.length):a=t.length;for(u=r;u.999999?(r[0]=0,r[1]=0,r[2]=0,r[3]=1,r):(o.cross(e,i,s),r[0]=e[0],r[1]=e[1],r[2]=e[2],r[3]=1+u,h.normalize(r,r))}}(),h.setAxes=function(){var e=l.create();return function(t,n,r,i){return e[0]=r[0],e[3]=r[1],e[6]=r[2],e[1]=i[0],e[4]=i[1],e[7]=i[2],e[2]=n[0],e[5]=n[1],e[8]=n[2],h.normalize(t,h.fromMat3(t,e))}}(),h.clone=u.clone,h.fromValues=u.fromValues,h.copy=u.copy,h.set=u.set,h.identity=function(e){return e[0]=0,e[1]=0,e[2]=0,e[3]=1,e},h.setAxisAngle=function(e,t,n){n*=.5;var r=Math.sin(n);return e[0]=r*t[0],e[1]=r*t[1],e[2]=r*t[2],e[3]=Math.cos(n),e},h.add=u.add,h.multiply=function(e,t,n){var r=t[0],i=t[1],s=t[2],o=t[3],u=n[0],a=n[1],f=n[2],l=n[3];return e[0]=r*l+o*u+i*f-s*a,e[1]=i*l+o*a+s*u-r*f,e[2]=s*l+o*f+r*a-i*u,e[3]=o*l-r*u-i*a-s*f,e},h.mul=h.multiply,h.scale=u.scale,h.rotateX=function(e,t,n){n*=.5;var r=t[0],i=t[1],s=t[2],o=t[3],u=Math.sin(n),a=Math.cos(n);return e[0]=r*a+o*u,e[1]=i*a+s*u,e[2]=s*a-i*u,e[3]=o*a-r*u,e},h.rotateY=function(e,t,n){n*=.5;var r=t[0],i=t[1],s=t[2],o=t[3],u=Math.sin(n),a=Math.cos(n);return e[0]=r*a-s*u,e[1]=i*a+o*u,e[2]=s*a+r*u,e[3]=o*a-i*u,e},h.rotateZ=function(e,t,n){n*=.5;var r=t[0],i=t[1],s=t[2],o=t[3],u=Math.sin(n),a=Math.cos(n);return e[0]=r*a+i*u,e[1]=i*a-r*u,e[2]=s*a+o*u,e[3]=o*a-s*u,e},h.calculateW=function(e,t){var n=t[0],r=t[1],i=t[2];return e[0]=n,e[1]=r,e[2]=i,e[3]=-Math.sqrt(Math.abs(1-n*n-r*r-i*i)),e},h.dot=u.dot,h.lerp=u.lerp,h.slerp=function(e,t,n,r){var i=t[0],s=t[1],o=t[2],u=t[3],a=n[0],f=n[1],l=n[2],c=n[3],h,p,d,v,m;return p=i*a+s*f+o*l+u*c,p<0&&(p=-p,a=-a,f=-f,l=-l,c=-c),1-p>1e-6?(h=Math.acos(p),d=Math.sin(h),v=Math.sin((1-r)*h)/d,m=Math.sin(r*h)/d):(v=1-r,m=r),e[0]=v*i+m*a,e[1]=v*s+m*f,e[2]=v*o+m*l,e[3]=v*u+m*c,e},h.invert=function(e,t){var n=t[0],r=t[1],i=t[2],s=t[3],o=n*n+r*r+i*i+s*s,u=o?1/o:0;return e[0]=-n*u,e[1]=-r*u,e[2]=-i*u,e[3]=s*u,e},h.conjugate=function(e,t){return e[0]=-t[0],e[1]=-t[1],e[2]=-t[2],e[3]=t[3],e},h.length=u.length,h.len=h.length,h.squaredLength=u.squaredLength,h.sqrLen=h.squaredLength,h.normalize=u.normalize,h.fromMat3=function(){var e=typeof Int8Array!="undefined"?new Int8Array([1,2,0]):[1,2,0];return function(t,n){var r=n[0]+n[4]+n[8],i;if(r>0)i=Math.sqrt(r+1),t[3]=.5*i,i=.5/i,t[0]=(n[7]-n[5])*i,t[1]=(n[2]-n[6])*i,t[2]=(n[3]-n[1])*i;else{var s=0;n[4]>n[0]&&(s=1),n[8]>n[s*3+s]&&(s=2);var o=e[s],u=e[o];i=Math.sqrt(n[s*3+s]-n[o*3+o]-n[u*3+u]+1),t[s]=.5*i,i=.5/i,t[3]=(n[u*3+o]-n[o*3+u])*i,t[o]=(n[o*3+s]+n[s*3+o])*i,t[u]=(n[u*3+s]+n[s*3+u])*i}return t}}(),h.str=function(e){return"quat("+e[0]+", "+e[1]+", "+e[2]+", "+e[3]+")"},typeof e!="undefined"&&(e.quat=h)}(t.exports)})(this); 29 | 30 | 31 | // Dummy Module definition for Impact 32 | ig.module('plugins.twopointfive.gl-matrix').defines(function(){}); 33 | -------------------------------------------------------------------------------- /lib/plugins/twopointfive/hud.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'plugins.twopointfive.hud' 3 | ) 4 | .requires( 5 | 'plugins.twopointfive.font', 6 | 'plugins.twopointfive.world.tile' 7 | ) 8 | .defines(function(){ 9 | 10 | tpf.Hud = ig.Class.extend({ 11 | width: 320, 12 | height: 240, 13 | 14 | font: null, 15 | 16 | damageIndicatorImage: null, 17 | damageIndicator: null, 18 | damageTimer: null, 19 | 20 | fadeScreen: null, 21 | 22 | message: null, 23 | messageTimer: null, 24 | 25 | fadeToWhite: 0, 26 | 27 | debug: true, 28 | 29 | init: function( width, height ) { 30 | this.width = width; 31 | this.height = height; 32 | 33 | this.font.letterSpacing = -2; 34 | 35 | this.camera = new tpf.OrthoCamera( width, height ); 36 | 37 | this.fadeScreen = new tpf.Quad(width, height); 38 | this.fadeScreen.setPosition(width/2,height/2,0) 39 | this.fadeScreen.setColor({r:255, g:255, b:255}); 40 | 41 | if( this.damageIndicatorImage ) { 42 | this.damageIndicator = new tpf.HudTile( this.damageIndicatorImage, 0, 160, 120); 43 | this.damageIndicator.setPosition( 0, 0 ); 44 | } 45 | }, 46 | 47 | showMessage: function( text, time ) { 48 | if( text ) { 49 | if( time !== -1 ) { 50 | this.messageTimer = new ig.Timer( tpf.Hud.TIME.DEFAULT || time ); 51 | } 52 | this.message = text; 53 | } 54 | else { 55 | this.messageTimer = null; 56 | this.message = null; 57 | } 58 | }, 59 | 60 | showDamageIndicator: function( x, y, initialAlpha ) { 61 | if( this.damageIndicator ) { 62 | this.damageIndicator.setPosition( x, y ); 63 | this.damageTimer = new ig.Timer( initialAlpha ); 64 | } 65 | }, 66 | 67 | prepare: function() { 68 | ig.system.renderer.setCamera(this.camera); 69 | }, 70 | 71 | drawDefault: function() { 72 | if( this.messageTimer && this.messageTimer.delta() > 0 ) { 73 | this.showMessage( null ); 74 | } 75 | 76 | if( this.message && this.font ) { 77 | this.font.draw(this.message, this.width/2, this.height/3, ig.Font.ALIGN.CENTER); 78 | } 79 | 80 | if( this.damageTimer ) { 81 | var delta = this.damageTimer.delta(); 82 | if( delta < 0 ) { 83 | this.damageIndicator.setAlpha( -delta ); 84 | this.damageIndicator.draw(); 85 | } 86 | else { 87 | this.damageTimer = null; 88 | } 89 | } 90 | 91 | if( this.fadeToWhite > 0 ) { 92 | this.fadeScreen.setAlpha( this.fadeToWhite ); 93 | ig.system.renderer.pushQuad(this.fadeScreen); 94 | } 95 | }, 96 | 97 | draw: function() { 98 | this.prepare(); 99 | this.drawDefault(); 100 | } 101 | }); 102 | 103 | tpf.Hud.TIME = { 104 | DEFAULT: 2, 105 | PERMANENT: -1 106 | }; 107 | 108 | }); -------------------------------------------------------------------------------- /lib/plugins/twopointfive/image.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'plugins.twopointfive.image' 3 | ) 4 | .requires( 5 | 'impact.image', 6 | 7 | 'plugins.twopointfive.namespace', 8 | 'plugins.twopointfive.renderer.renderer' 9 | ) 10 | .defines(function(){ "use strict"; 11 | 12 | 13 | ig.Image.inject({ 14 | texture: null, 15 | 16 | seamsExpanded: false, 17 | textureWidth: 0, 18 | textureHeight: 0, 19 | 20 | onload: function( event ) { 21 | this.texture = ig.system.renderer.loadTexture(this.data); 22 | this.textureWidth = this.data.width; 23 | this.textureHeight = this.data.height; 24 | this.parent(event); 25 | }, 26 | 27 | expandSeams: function(tilesize) { 28 | if( this.seamsExpanded ) { return; } 29 | this.seamsExpanded = true; 30 | 31 | 32 | var tw = (this.width / tilesize)|0, 33 | th = (this.height / tilesize)|0; 34 | 35 | this.textureWidth = this.width + tw * 2 - 2; 36 | this.textureHeight = this.height + th * 2 - 2; 37 | 38 | var expandedCanvas = ig.$new('canvas'); 39 | expandedCanvas.width = this.textureWidth; 40 | expandedCanvas.height = this.textureHeight; 41 | var ctx = expandedCanvas.getContext('2d'); 42 | ig.System.SCALE.CRISP(expandedCanvas, ctx); 43 | 44 | for( var y = 0, dy = -1; y < th; y++, dy += (tilesize+2) ) { 45 | for( var x = 0, dx = -1; x < tw; x++, dx += (tilesize+2) ) { 46 | 47 | // Left edge 48 | if( dx > 0 ) { 49 | ctx.drawImage(this.data, x*tilesize, y*tilesize, 1, tilesize, dx, dy+1, 1, tilesize); 50 | } 51 | 52 | // Right edge 53 | if( dx < this.width - tilesize ) { 54 | ctx.drawImage(this.data, (x+1)*tilesize-1, y*tilesize, 1, tilesize, dx+tilesize+1, dy+1, 1, tilesize); 55 | } 56 | 57 | // Top edge, draw expanded first to cover the corners 58 | if( dy > 0 ) { 59 | ctx.drawImage(this.data, x*tilesize, y*tilesize, tilesize, 1, dx, dy, tilesize+2, 1); 60 | ctx.drawImage(this.data, x*tilesize, y*tilesize, tilesize, 1, dx+1, dy, tilesize, 1); 61 | } 62 | 63 | // Bottom edge, draw expanded first to cover the corners 64 | if( dy < this.height - tilesize ) { 65 | ctx.drawImage(this.data, x*tilesize, (y+1)*tilesize-1, tilesize, 1, dx, dy+tilesize+1, tilesize+2, 1); 66 | ctx.drawImage(this.data, x*tilesize, (y+1)*tilesize-1, tilesize, 1, dx+1, dy+tilesize+1, tilesize, 1); 67 | } 68 | 69 | // Tile 70 | ctx.drawImage(this.data, x*tilesize, y*tilesize, tilesize, tilesize, dx+1, dy+1, tilesize, tilesize); 71 | } 72 | } 73 | 74 | // Replace texture with the expanded version 75 | this.texture = ig.system.renderer.loadTexture(expandedCanvas); 76 | } 77 | }); 78 | 79 | }); 80 | -------------------------------------------------------------------------------- /lib/plugins/twopointfive/loader.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'plugins.twopointfive.loader' 3 | ) 4 | .requires( 5 | 'impact.loader', 6 | 7 | 'plugins.twopointfive.namespace', 8 | 'plugins.twopointfive.renderer.renderer' 9 | ) 10 | .defines(function(){ "use strict"; 11 | 12 | 13 | tpf.Loader = ig.Loader.extend({ 14 | rotation: 0, 15 | 16 | blockFaces: [], 17 | loadingBar: null, 18 | loadingBarBackground: null, 19 | 20 | barSize: {x: 16, y: 0.1}, 21 | 22 | load: function() { 23 | var that = this; 24 | this.blockImage = new ig.Image('media/loading-block.png'); 25 | this.blockImage.load( function(){ 26 | if( !that._intervalId ) { 27 | ig.Loader.prototype.load.call(that); 28 | } 29 | }); 30 | }, 31 | 32 | createGeometry: function() { 33 | this.loadingBarBackground = new tpf.Quad(this.barSize.x, this.barSize.y); 34 | this.loadingBarBackground.setPosition(0, -8, 0); 35 | this.loadingBarBackground.setColor({r: 0.1, g: 0.1, b: 0.1}); 36 | 37 | this.loadingBar = new tpf.Quad(this.barSize.x, this.barSize.y); 38 | this.loadingBar.setPosition(0, -8, 0); 39 | this.loadingBar.setColor({r: 1, g: 1, b: 1}); 40 | 41 | this.blockFaces[0] = new tpf.Tile(this.blockImage, 0, 64, 64, 0.125); 42 | this.blockFaces[0].quad.setPosition(0, 0, -4); 43 | 44 | this.blockFaces[1] = new tpf.Tile(this.blockImage, 0, 64, 64, 0.125); 45 | this.blockFaces[1].quad.setPosition(4, 0, 0); 46 | this.blockFaces[1].quad.setRotation(0, -Math.PI/2, 0); 47 | 48 | this.blockFaces[2] = new tpf.Tile(this.blockImage, 0, 64, 64, 0.125); 49 | this.blockFaces[2].quad.setPosition(0, 0, 4); 50 | 51 | this.blockFaces[3] = new tpf.Tile(this.blockImage, 0, 64, 64, 0.125); 52 | this.blockFaces[3].quad.setPosition(-4, 0, 0); 53 | this.blockFaces[3].quad.setRotation(0, Math.PI/2, 0); 54 | }, 55 | 56 | draw: function() { 57 | if( !this.loadingBar ) { 58 | this.createGeometry(); 59 | } 60 | 61 | this.rotation += 0.2 * this.status * this.status; 62 | ig.system.renderer.render(this.renderCallback.bind(this)); 63 | }, 64 | 65 | renderCallback: function() { 66 | var renderer = ig.system.renderer; 67 | var camera = ig.system.camera; 68 | renderer.clear( true, true, true ); 69 | 70 | // Rotate camera around the center block 71 | camera.position[0] = Math.cos(this.rotation) * 20; 72 | camera.position[2] = Math.sin(this.rotation) * 20; 73 | camera.rotation[1] = -this.rotation + Math.PI/2; 74 | renderer.setCamera(camera); 75 | 76 | for( var i = 0; i < this.blockFaces.length; i++ ) { 77 | var c = (Math.sin(this.rotation - (i+1.2) * Math.PI/2)+1)/2; 78 | this.blockFaces[i].quad.setColor({r:c,g:c,b:c}); 79 | this.blockFaces[i].draw(); 80 | } 81 | 82 | // Reset camera to stationary position for the loading bar 83 | camera.position[0] = 0; 84 | camera.position[2] = 20; 85 | camera.rotation[1] = 0; 86 | renderer.setCamera(camera); 87 | 88 | this.loadingBar.setPosition(-(1-this.status)*this.barSize.x/2, -8, 0); 89 | this.loadingBar.setSize(this.status*this.barSize.x, this.barSize.y); 90 | renderer.pushQuad(this.loadingBar); 91 | renderer.pushQuad(this.loadingBarBackground); 92 | } 93 | }); 94 | 95 | 96 | }); -------------------------------------------------------------------------------- /lib/plugins/twopointfive/namespace.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'plugins.twopointfive.namespace' 3 | ) 4 | .requires( 5 | 'plugins.twopointfive.gl-matrix' 6 | ) 7 | .defines(function(){ "use strict"; 8 | 9 | // Create the main 'tpf' namespace, used by all other modules of this plugin 10 | window.tpf = window.tpf || {}; 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /lib/plugins/twopointfive/renderer/ortho-camera.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'plugins.twopointfive.renderer.ortho-camera' 3 | ) 4 | .requires( 5 | 'plugins.twopointfive.namespace' 6 | ) 7 | .defines(function(){ "use strict"; 8 | 9 | 10 | tpf.OrthoCamera = ig.Class.extend({ 11 | _projection: null, 12 | _view: null, 13 | aspect: 1, 14 | depthTest: false, 15 | 16 | init: function( width, height ) { 17 | this._projection = mat4.create(); 18 | this._view = mat4.create(); 19 | mat4.ortho(this._projection, 0, width, height, 0, -1000, 1000); 20 | 21 | this.aspect = width/height; 22 | this.width = width; 23 | this.height = height; 24 | }, 25 | 26 | projection: function() { 27 | return this._projection; 28 | }, 29 | 30 | view: function() { 31 | return this._view; 32 | } 33 | }); 34 | 35 | }); 36 | -------------------------------------------------------------------------------- /lib/plugins/twopointfive/renderer/perspective-camera.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'plugins.twopointfive.renderer.perspective-camera' 3 | ) 4 | .requires( 5 | 'plugins.twopointfive.namespace' 6 | ) 7 | .defines(function(){ "use strict"; 8 | 9 | 10 | tpf.PerspectiveCamera = ig.Class.extend({ 11 | _projection: null, 12 | _view: null, 13 | 14 | position: null, 15 | rotation: null, 16 | aspect: 1, 17 | depthTest: true, 18 | 19 | init: function( fov, aspect, near, far ) { 20 | this._projection = mat4.create(); 21 | this._view = mat4.create(); 22 | this.position = vec3.create(); 23 | this.rotation = vec3.create(); 24 | 25 | mat4.perspective(this._projection, fov.toRad(), aspect, near, far); 26 | this.aspect = aspect; 27 | }, 28 | 29 | setRotation: function( x, y, z ) { 30 | this.rotation[0] = x; 31 | this.rotation[1] = z; 32 | this.rotation[2] = y; 33 | }, 34 | 35 | setPosition: function( x, y, z ) { 36 | this.position[0] = x; 37 | this.position[1] = z; 38 | this.position[2] = y; 39 | }, 40 | 41 | projection: function() { 42 | return this._projection; 43 | }, 44 | 45 | view: function() { 46 | var m = this._view; 47 | var rot = this.rotation; 48 | 49 | mat4.identity(m); 50 | 51 | if( rot[2] ) { mat4.rotateZ(m, m, -rot[2]); } 52 | if( rot[0] ) { mat4.rotateX(m, m, -rot[0]); } 53 | if( rot[1] ) { mat4.rotateY(m, m, -rot[1]); } 54 | 55 | mat4.translate(m, m, [-this.position[0], -this.position[1], -this.position[2]]); 56 | 57 | return m; 58 | } 59 | }); 60 | 61 | }); 62 | -------------------------------------------------------------------------------- /lib/plugins/twopointfive/renderer/program.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'plugins.twopointfive.renderer.program' 3 | ) 4 | .requires( 5 | 'plugins.twopointfive.namespace' 6 | ) 7 | .defines(function(){ "use strict"; 8 | 9 | 10 | tpf.Program = ig.Class.extend({ 11 | uniform: {}, 12 | attribute: {}, 13 | 14 | init: function( gl, vertexSource, fragmentSource ) { 15 | var vsh = this.compile(gl, vertexSource, gl.VERTEX_SHADER); 16 | var fsh = this.compile(gl, fragmentSource, gl.FRAGMENT_SHADER); 17 | 18 | this.program = gl.createProgram(); 19 | gl.attachShader(this.program, vsh); 20 | gl.attachShader(this.program, fsh); 21 | gl.linkProgram(this.program); 22 | 23 | if( !gl.getProgramParameter(this.program, gl.LINK_STATUS) ) { 24 | console.log(gl.getProgramInfoLog(this.program)); 25 | } 26 | 27 | gl.useProgram(this.program); 28 | 29 | // Collect attributes 30 | this._collect(vertexSource, 'attribute', this.attribute); 31 | for( var a in this.attribute ) { 32 | this.attribute[a] = gl.getAttribLocation(this.program, a); 33 | } 34 | 35 | // Collect uniforms 36 | this._collect(vertexSource, 'uniform', this.uniform); 37 | this._collect(fragmentSource, 'uniform', this.uniform); 38 | for( var u in this.uniform ) { 39 | this.uniform[u] = gl.getUniformLocation(this.program, u); 40 | } 41 | }, 42 | 43 | compile: function( gl, source, type ) { 44 | var shader = gl.createShader(type); 45 | gl.shaderSource(shader, source); 46 | gl.compileShader(shader); 47 | 48 | if( !gl.getShaderParameter(shader, gl.COMPILE_STATUS) ) { 49 | console.log(gl.getShaderInfoLog(shader)); 50 | return null; 51 | } 52 | return shader; 53 | }, 54 | 55 | _collect: function( source, prefix, collection ) { 56 | var r = new RegExp('\\b' + prefix + ' \\w+ (\\w+)', 'ig'); 57 | source.replace(r, function(match, name) { 58 | collection[name] = 0; 59 | return match; 60 | }); 61 | } 62 | }); 63 | 64 | 65 | }); 66 | -------------------------------------------------------------------------------- /lib/plugins/twopointfive/renderer/quad.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'plugins.twopointfive.renderer.quad' 3 | ) 4 | .requires( 5 | 'plugins.twopointfive.namespace' 6 | ) 7 | .defines(function(){ "use strict"; 8 | 9 | // The tpf.Quad is the heart of TwoPointFive. Everything that's drawn 10 | // on the screen is drawn through a Quad or, like the tpf.TileMesh, 11 | // is generated from Quads. 12 | 13 | // A Quad has 6 vertices, each with an an x, y, z position, 14 | // u, v texture coordinates and rgba colors. 15 | 16 | // Each Quad comes with its own 54 element Float32Array: 17 | 18 | // struct vert { 19 | // float x, y, z; // 0, 1, 2 20 | // float u, v; // 3, 4 21 | // float r, g, b, a; // 5, 6, 7, 8 22 | // } 23 | // vert size = 9 * 6 verts = 54 float elements = 216 bytes 24 | 25 | // A Quad has no means to draw itself. Instead, it can only copy itself 26 | // into another buffer. TwoPointFive's Renderer maintains a large buffer 27 | // to collect all tiles with the same texture and finally draw them. 28 | 29 | tpf.Quad = function( width, height, texture ) { 30 | this.texture = texture || null; 31 | this.width = width || 1; 32 | this.height = height || 1; 33 | this.color = {r:1, g:1, b:1, a:1}; 34 | 35 | this.position = vec3.create(); 36 | this.rotation = vec3.create(); 37 | 38 | this._dirty = true; 39 | this._verts = new Float32Array(tpf.Quad.SIZE); 40 | 41 | // vec3 views into the _verts array; needed by gl-matrix 42 | this._vertsPos = [ 43 | this._verts.subarray(0 * 9, 0 * 9 + 3), 44 | this._verts.subarray(1 * 9, 1 * 9 + 3), 45 | this._verts.subarray(2 * 9, 2 * 9 + 3), 46 | this._verts.subarray(3 * 9, 3 * 9 + 3), 47 | this._verts.subarray(4 * 9, 4 * 9 + 3), 48 | this._verts.subarray(5 * 9, 5 * 9 + 3), 49 | ]; 50 | 51 | this._recalcPositions = function() { 52 | var v = this._verts; 53 | var vp = this._vertsPos; 54 | var rot = this.rotation; 55 | var m = mat4.identity(tpf.Quad._workMatrix); 56 | 57 | var sx2 = this.width/2, 58 | sy2 = this.height/2; 59 | 60 | vp[0][0] = -sx2; vp[0][1] = -sy2; vp[0][2] = 0; // top left 61 | vp[1][0] = sx2; vp[1][1] = -sy2; vp[1][2] = 0; // top right 62 | vp[2][0] = -sx2; vp[2][1] = sy2; vp[2][2] = 0; // bottom left 63 | 64 | vp[3][0] = sx2; vp[3][1] = sy2; vp[3][2] = 0; // bottom right 65 | // vp[4] = vp[2] = bottom left; set after transform 66 | // vp[5] = vp[1] = top right; set after transform 67 | 68 | mat4.translate(m, m, this.position); 69 | if( rot[0] ) { mat4.rotateX(m, m, rot[0]); } 70 | if( rot[1] ) { mat4.rotateY(m, m, rot[1]); } 71 | if( rot[2] ) { mat4.rotateZ(m, m, rot[2]); } 72 | 73 | vec3.transformMat4(vp[0], vp[0], m); 74 | vec3.transformMat4(vp[1], vp[1], m); 75 | vec3.transformMat4(vp[2], vp[2], m); 76 | vec3.transformMat4(vp[3], vp[3], m); 77 | 78 | vp[4].set(vp[2]); 79 | vp[5].set(vp[1]); 80 | }; 81 | 82 | this.setSize = function( width, height ) { 83 | this.width = width; 84 | this.height = height; 85 | this._dirty = true; 86 | }; 87 | 88 | this.setPosition = function( x, y, z ) { 89 | this.position[0] = x; 90 | this.position[1] = y; 91 | this.position[2] = z; 92 | this._dirty = true; 93 | }; 94 | 95 | this.setRotation = function( x, y, z ) { 96 | this.rotation[0] = x; 97 | this.rotation[1] = y; 98 | this.rotation[2] = z; 99 | this._dirty = true; 100 | }; 101 | 102 | this.setUV = function( x1, y1, x2, y2 ) { 103 | var v = this._verts; 104 | v[3] = x1; v[4] = y1; // top left 105 | v[12] = x2; v[13] = y1; // top right 106 | v[21] = x1; v[22] = y2; // bottom left 107 | 108 | v[30] = x2; v[31] = y2; // bottom right 109 | v[39] = x1; v[40] = y2; // bottom left 110 | v[48] = x2; v[49] = y1; // top right 111 | }; 112 | this.setUV(0,0,1,1); 113 | 114 | this.setColor = function( c ) { 115 | this.color.r = c.r; 116 | this.color.g = c.g; 117 | this.color.b = c.b; 118 | var v = this._verts; 119 | 120 | v[5] = c.r; v[6] = c.g; v[7] = c.b; // top left 121 | v[14] = c.r; v[15] = c.g; v[16] = c.b; // top right 122 | v[23] = c.r; v[24] = c.g; v[25] = c.b; // bottom left 123 | 124 | v[32] = c.r; v[33] = c.g; v[34] = c.b; // bottom right 125 | v[41] = c.r; v[42] = c.g; v[43] = c.b; // bottom left 126 | v[50] = c.r; v[51] = c.g; v[52] = c.b; // top right 127 | }; 128 | this.setColor(this.color); 129 | 130 | this.setAlpha = function( a ) { 131 | var v = this._verts; 132 | this.color.a = a; 133 | 134 | v[8] = a; // top left 135 | v[17] = a; // top right 136 | v[26] = a; // bottom left 137 | 138 | v[35] = a; // bottom right 139 | v[44] = a; // bottom left 140 | v[53] = a; // top right 141 | }; 142 | this.setAlpha(this.color.a); 143 | 144 | this.copyToBuffer = function( buffer, index ) { 145 | if( this._dirty ) { 146 | this._recalcPositions(); 147 | this._dirty = false; 148 | } 149 | buffer.set(this._verts, index); 150 | }; 151 | }; 152 | 153 | // This class method is essentially the same as the setUV() instance method, 154 | // but takes a buffer and offset instead of operating on the instance's 155 | // vertices directly. 156 | 157 | // This is used by tpf.TileMeshes to directly update UV coordinates for animted 158 | // world tiles. 159 | 160 | tpf.Quad.setUVInBuffer = function(buffer, offset, x1, y1, x2, y2) { 161 | var b = offset * tpf.Quad.SIZE; 162 | var v = buffer; 163 | 164 | v[b+3] = x1; v[b+4] = y1; // top left 165 | v[b+12] = x2; v[b+13] = y1; // top right 166 | v[b+21] = x1; v[b+22] = y2; // bottom left 167 | 168 | v[b+30] = x2; v[b+31] = y2; // bottom right 169 | v[b+39] = x1; v[b+40] = y2; // bottom left 170 | v[b+48] = x2; v[b+49] = y1; // top right 171 | }; 172 | 173 | tpf.Quad.VERTEX_SIZE = 9; 174 | tpf.Quad.VERTICES = 6; 175 | tpf.Quad.SIZE = tpf.Quad.VERTEX_SIZE * tpf.Quad.VERTICES; 176 | 177 | if( !ig.global.wm ) { 178 | tpf.Quad._workMatrix = mat4.create(); 179 | } 180 | 181 | }); 182 | -------------------------------------------------------------------------------- /lib/plugins/twopointfive/renderer/renderer.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'plugins.twopointfive.renderer.renderer' 3 | ) 4 | .requires( 5 | 'plugins.twopointfive.namespace', 6 | 'plugins.twopointfive.renderer.quad', 7 | 'plugins.twopointfive.renderer.program' 8 | ) 9 | .defines(function(){ "use strict"; 10 | 11 | 12 | tpf.Renderer = ig.Class.extend({ 13 | bufferSize: 64, // 64 Quads 14 | buffer: null, 15 | texture: null, 16 | bufferIndex: 0, 17 | gl: null, 18 | drawCalls: 0, 19 | _currentDrawCalls: 0, 20 | _currentQuadCount: 0, 21 | 22 | depthTest: true, 23 | wireframe: false, 24 | 25 | fog: null, 26 | fullscreenFlags: {}, 27 | 28 | init: function( canvas ) { 29 | this.canvas = canvas; 30 | var webglOptions = { 31 | alpha: false, 32 | premultipliedAlpha: false, 33 | antialias: false, 34 | stencil: false, 35 | preserveDrawingBuffer: true 36 | }; 37 | 38 | this.gl = canvas.getContext( 'webgl', webglOptions); 39 | if( !this.gl ) { 40 | this.gl = canvas.getContext( 'experimental-webgl', webglOptions); 41 | } 42 | 43 | this.setSize( canvas.width, canvas.height ); 44 | 45 | this.programDefault = new tpf.Program( this.gl, tpf.Renderer.Shaders.Vertex, tpf.Renderer.Shaders.Fragment ); 46 | this.programFog = new tpf.Program( this.gl, tpf.Renderer.Shaders.Vertex, tpf.Renderer.Shaders.FragmentWithFog ); 47 | this.program = this.programDefault; 48 | 49 | this.buffer = new Float32Array( this.bufferSize * tpf.Quad.SIZE ); 50 | this.glBuffer = this.gl.createBuffer(); 51 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.glBuffer); 52 | 53 | this.prepare(); 54 | this.whiteTexture = this.loadTexture(new Uint8Array([0xff,0xff,0xff,0xff]),1,1); 55 | this.setProgram( this.programDefault ); 56 | }, 57 | 58 | setFog: function( color, near, far ) { 59 | if( color === false || typeof color === 'undefined' ) { 60 | this.setProgram( this.programDefault, true ); 61 | this.fog = null; 62 | } 63 | else { 64 | this.setProgram( this.programFog, true ); 65 | 66 | this.fog = { 67 | color: color, 68 | near: near, 69 | far: far 70 | }; 71 | 72 | var c1 = ((color & 0xff0000) >> 16)/255, 73 | c2 = ((color & 0x00ff00) >> 8)/255, 74 | c3 = ((color & 0x0000ff) >> 0)/255; 75 | 76 | this.gl.uniform3f(this.program.uniform.fogColor, c1, c2, c3); 77 | this.gl.uniform1f(this.program.uniform.fogNear, near); 78 | this.gl.uniform1f(this.program.uniform.fogFar, far); 79 | } 80 | }, 81 | 82 | setSize: function( width, height ) { 83 | this.width = width; 84 | this.height = height; 85 | this.gl.viewport(0, 0, this.width, this.height); 86 | }, 87 | 88 | loadTexture: function( img, width, height ) { 89 | var texture = this.gl.createTexture(); 90 | 91 | this.gl.bindTexture(this.gl.TEXTURE_2D, texture); 92 | 93 | if( img instanceof Uint8Array && width && height ) { 94 | this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, width, height, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, img); 95 | } 96 | else { 97 | this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, img); 98 | } 99 | 100 | this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST); 101 | this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR); 102 | 103 | this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE); 104 | this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE); 105 | 106 | this.gl.bindTexture(this.gl.TEXTURE_2D, null); 107 | this.texture = null; 108 | return texture; 109 | }, 110 | 111 | clear: function( color, depth, stencil ) { 112 | this.gl.clear( 113 | (color ? this.gl.COLOR_BUFFER_BIT : 0) | 114 | (depth ? this.gl.DEPTH_BUFFER_BIT : 0) | 115 | (stencil ? this.gl.STENCIL_BUFFER_BIT : 0) 116 | ); 117 | }, 118 | 119 | prepare: function() { 120 | this.gl.enable(this.gl.DEPTH_TEST); 121 | this.gl.enable(this.gl.BLEND); 122 | this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA); 123 | 124 | this.gl.useProgram(this.program.program); 125 | this.gl.clearColor(0,0,0,1); 126 | 127 | var floatSize = Float32Array.BYTES_PER_ELEMENT; 128 | var vertSize = floatSize * tpf.Quad.VERTEX_SIZE 129 | 130 | this.gl.enableVertexAttribArray(this.program.attribute.pos); 131 | this.gl.vertexAttribPointer(this.program.attribute.pos, 3, this.gl.FLOAT, false, vertSize, 0 * floatSize); 132 | 133 | this.gl.enableVertexAttribArray(this.program.attribute.uv); 134 | this.gl.vertexAttribPointer(this.program.attribute.uv, 2, this.gl.FLOAT, false, vertSize, 3 * floatSize); 135 | 136 | this.gl.enableVertexAttribArray(this.program.attribute.color); 137 | this.gl.vertexAttribPointer(this.program.attribute.color, 4, this.gl.FLOAT, false, vertSize, 5 * floatSize); 138 | }, 139 | 140 | flush: function() { 141 | if( this.bufferIndex == 0 ) { return; } 142 | 143 | this._currentDrawCalls++; 144 | this._currentQuadCount += this.bufferIndex; 145 | this.gl.bufferData(this.gl.ARRAY_BUFFER, this.buffer, this.gl.DYNAMIC_DRAW); 146 | this.gl.drawArrays(this.gl.TRIANGLES, 0, this.bufferIndex * tpf.Quad.VERTICES); 147 | this.bufferIndex = 0; 148 | }, 149 | 150 | render: function( callback ) { 151 | if( this.wireframe ) { 152 | this.clear(true,true,true) 153 | } 154 | 155 | callback(this); 156 | 157 | this.flush(); 158 | this.drawCalls = this._currentDrawCalls; 159 | this.quadCount = this._currentQuadCount; 160 | this._currentDrawCalls = 0; 161 | this._currentQuadCount = 0; 162 | }, 163 | 164 | setCamera: function( camera ) { 165 | this.flush(); 166 | this.gl.uniformMatrix4fv(this.program.uniform.projection, false, camera.projection()); 167 | this.gl.uniformMatrix4fv(this.program.uniform.view, false, camera.view()); 168 | 169 | if( camera.depthTest != this.depthTest) { 170 | this.depthTest = camera.depthTest; 171 | if( this.depthTest ) { 172 | this.gl.enable(this.gl.DEPTH_TEST); 173 | } 174 | else { 175 | this.gl.disable(this.gl.DEPTH_TEST); 176 | } 177 | } 178 | }, 179 | 180 | setTexture: function(texture) { 181 | texture = texture || this.whiteTexture; 182 | if( texture == this.texture ) { 183 | return; 184 | } 185 | 186 | this.flush(); 187 | this.texture = texture; 188 | this.gl.bindTexture(this.gl.TEXTURE_2D, texture); 189 | }, 190 | 191 | setProgram: function(program, force) { 192 | if( program == this.program && !force ) { 193 | return; 194 | } 195 | 196 | this.flush(); 197 | this.program = program; 198 | this.gl.useProgram(this.program.program); 199 | }, 200 | 201 | pushQuad: function(quad) { 202 | this.setTexture(quad.texture); 203 | if( this.bufferIndex + 1 >= this.bufferSize ) { 204 | this.flush(); 205 | } 206 | 207 | quad.copyToBuffer( this.buffer, this.bufferIndex * tpf.Quad.SIZE ); 208 | this.bufferIndex++; 209 | }, 210 | 211 | pushMesh: function(mesh) { 212 | // Meshes are drawn immediately; flush out all previous quads 213 | this.flush(); 214 | 215 | this._currentDrawCalls++; 216 | this._currentQuadCount += mesh.length; 217 | this.setTexture(mesh.texture); 218 | 219 | this.gl.bufferData(this.gl.ARRAY_BUFFER, mesh.buffer, this.gl.DYNAMIC_DRAW); 220 | 221 | var polygonMode = this.wireframe ? this.gl.LINES : this.gl.TRIANGLES; 222 | this.gl.drawArrays(polygonMode, 0, mesh.length * tpf.Quad.VERTICES); 223 | } 224 | }); 225 | 226 | tpf.Renderer.Shaders = { 227 | Vertex: [ 228 | "precision highp float;", 229 | 230 | "attribute vec3 pos;", 231 | "attribute vec2 uv;", 232 | "attribute vec4 color;", 233 | 234 | "varying vec4 vColor;", 235 | "varying vec2 vUv;", 236 | 237 | "uniform mat4 view;", 238 | "uniform mat4 projection;", 239 | 240 | "void main(void) {", 241 | "vColor = color;", 242 | "vUv = uv;", 243 | "gl_Position = projection * view * vec4(pos, 1.0);", 244 | 245 | "}" 246 | ].join('\n'), 247 | 248 | Fragment: [ 249 | "precision highp float;", 250 | 251 | "varying vec4 vColor;", 252 | "varying vec2 vUv;", 253 | 254 | "uniform sampler2D texture;", 255 | 256 | "void main(void) {", 257 | "vec4 tex = texture2D(texture, vUv);", 258 | "if( tex.a < 0.8 ) discard;", 259 | "gl_FragColor = tex * vColor;", 260 | "}" 261 | ].join('\n'), 262 | 263 | FragmentWithFog: [ 264 | "precision highp float;", 265 | 266 | "varying vec4 vColor;", 267 | "varying vec2 vUv;", 268 | 269 | "uniform sampler2D texture;", 270 | 271 | "uniform vec3 fogColor;", 272 | "uniform float fogNear;", 273 | "uniform float fogFar;", 274 | 275 | "void main(void) {", 276 | "float depth = gl_FragCoord.z / gl_FragCoord.w;", 277 | "float fogFactor = smoothstep( fogFar, fogNear, depth );", 278 | "fogFactor = 1.0 - clamp( fogFactor, 0.2, 1.0);", 279 | 280 | "vec4 tex = texture2D(texture, vUv);", 281 | "if( tex.a < 0.8 ) discard;", 282 | "gl_FragColor = tex * vColor;", 283 | "gl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor.rgb, fogFactor);", 284 | "}" 285 | ].join('\n') 286 | }; 287 | 288 | 289 | }); 290 | -------------------------------------------------------------------------------- /lib/plugins/twopointfive/renderer/stereo-renderer.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'plugins.twopointfive.renderer.stereo-renderer' 3 | ) 4 | .requires( 5 | 'plugins.twopointfive.namespace', 6 | 'plugins.twopointfive.renderer.renderer' 7 | ) 8 | .defines(function(){ "use strict"; 9 | 10 | tpf.StereoRenderer = tpf.Renderer.extend({ 11 | eyes: { 12 | left: {offset: 0, projection: null, viewport: {}, quad: null}, 13 | right: {offset: 0, projection: null, viewport: {}, quad: null} 14 | }, 15 | 16 | sensorDevice: null, 17 | hmdDevice: null, 18 | vrMode: false, 19 | currentEye: null, 20 | 21 | worldScale: (32/1.80), 22 | viewportFov: (110).toRad(), 23 | hudFreelook: false, 24 | hudDistance: 250, 25 | 26 | init: function( canvas, worldScale ) { 27 | this.parent(canvas); 28 | 29 | this.worldScale = worldScale || this.worldScale; 30 | 31 | if( navigator.getVRDevices ) { 32 | navigator.getVRDevices().then(this.enumerateVRDevices.bind(this)); 33 | } else if (navigator.mozGetVRDevices) { 34 | navigator.mozGetVRDevices(this.enumerateVRDevices.bind(this)); 35 | } else { 36 | alert('No WebVR support!'); 37 | } 38 | }, 39 | 40 | enumerateVRDevices: function(devices) { 41 | // First find an HMD device 42 | for( var i = 0; i < devices.length; i++ ) { 43 | if( devices[i] instanceof HMDVRDevice ) { 44 | this.hmdDevice = devices[i]; 45 | 46 | this.eyes.left.offset = this.hmdDevice.getEyeTranslation("left"); 47 | this.eyes.right.offset = this.hmdDevice.getEyeTranslation("right"); 48 | break; 49 | } 50 | } 51 | 52 | // Next find a sensor that matches the HMD hardwareUnitId 53 | for( var i = 0; i < devices.length; i++ ) { 54 | if( 55 | devices[i] instanceof PositionSensorVRDevice && 56 | (!this.hmdDevice || devices[i].hardwareUnitId == this.hmdDevice.hardwareUnitId) 57 | ) { 58 | this.sensorDevice = devices[i]; 59 | break; 60 | } 61 | } 62 | 63 | this.fullscreenFlags = { vrDisplay: this.hmdDevice }; 64 | ig.system.requestFullscreen(); 65 | this.setFov(); 66 | }, 67 | 68 | setFov: function() { 69 | if( !this.hmdDevice ) { return; } 70 | 71 | var fovLeft, fovRight; 72 | 73 | if( 'getRecommendedEyeRenderRect' in this.hmdDevice ) { 74 | var leftEyeViewport = this.hmdDevice.getRecommendedEyeRenderRect("left"); 75 | var rightEyeViewport = this.hmdDevice.getRecommendedEyeRenderRect("right"); 76 | var renderTargetWidth = leftEyeViewport.width + rightEyeViewport.width; 77 | var renderTargetHeight = Math.max(leftEyeViewport.height, rightEyeViewport.height); 78 | } 79 | 80 | this.setSize( renderTargetWidth, renderTargetHeight ); 81 | 82 | if ('getCurrentEyeFieldOfView' in this.hmdDevice) { 83 | fovLeft = this.hmdDevice.getCurrentEyeFieldOfView("left"); 84 | fovRight = this.hmdDevice.getCurrentEyeFieldOfView("right"); 85 | } else { 86 | fovLeft = this.hmdDevice.getRecommendedEyeFieldOfView("left"); 87 | fovRight = this.hmdDevice.getRecommendedEyeFieldOfView("right"); 88 | } 89 | 90 | this.eyes.left.projection = this.perspectiveMatrixFromVRFieldOfView(fovLeft, 0.1, 1000); 91 | this.eyes.right.projection = this.perspectiveMatrixFromVRFieldOfView(fovRight, 0.1, 1000); 92 | }, 93 | 94 | perspectiveMatrixFromVRFieldOfView: function(fov, zNear, zFar) { 95 | var out = mat4.create(); 96 | var upTan, downTan, leftTan, rightTan; 97 | if (fov == null) { 98 | // If no FOV is given plug in some dummy values 99 | upTan = Math.tan(50 * Math.PI/180.0); 100 | downTan = Math.tan(50 * Math.PI/180.0); 101 | leftTan = Math.tan(45 * Math.PI/180.0); 102 | rightTan = Math.tan(45 * Math.PI/180.0); 103 | } else { 104 | upTan = Math.tan(fov.upDegrees * Math.PI/180.0); 105 | downTan = Math.tan(fov.downDegrees * Math.PI/180.0); 106 | leftTan = Math.tan(fov.leftDegrees * Math.PI/180.0); 107 | rightTan = Math.tan(fov.rightDegrees * Math.PI/180.0); 108 | } 109 | 110 | var xScale = 2.0 / (leftTan + rightTan); 111 | var yScale = 2.0 / (upTan + downTan); 112 | 113 | out[0] = xScale; 114 | out[4] = 0.0; 115 | out[8] = -((leftTan - rightTan) * xScale * 0.5); 116 | out[12] = 0.0; 117 | 118 | out[1] = 0.0; 119 | out[5] = yScale; 120 | out[9] = ((upTan - downTan) * yScale * 0.5); 121 | out[13] = 0.0; 122 | 123 | out[2] = 0.0; 124 | out[6] = 0.0; 125 | out[10] = zFar / (zNear - zFar); 126 | out[14] = (zFar * zNear) / (zNear - zFar); 127 | 128 | out[3] = 0.0; 129 | out[7] = 0.0; 130 | out[11] = -1.0; 131 | out[15] = 0.0; 132 | 133 | return out; 134 | }, 135 | 136 | setSize: function( width, height ) { 137 | this.canvas.width = width; 138 | this.canvas.height = height; 139 | 140 | this.parent(width, height); 141 | 142 | var width2 = width/2; 143 | 144 | this.eyes.left.viewport = {x:0, y:0, width:width2, height:height}; 145 | this.eyes.right.viewport = {x:width2, y:0, width:width2, height:height}; 146 | }, 147 | 148 | 149 | _renderSceneForEye: function( eye, sceneCallback ) { 150 | this.gl.viewport(eye.viewport.x, eye.viewport.y, eye.viewport.width, eye.viewport.height); 151 | this.currentEye = eye; // needed to override setCamera 152 | sceneCallback(this); 153 | this.flush(); 154 | }, 155 | 156 | render: function( sceneCallback ) { 157 | if( !this.hmdDevice ) { 158 | return this.parent(sceneCallback); 159 | } 160 | 161 | 162 | if( this.wireframe ) { 163 | this.clear(true,true,true) 164 | } 165 | 166 | this._renderSceneForEye(this.eyes.left, sceneCallback); 167 | this._renderSceneForEye(this.eyes.right, sceneCallback); 168 | 169 | this.drawCalls = this._currentDrawCalls; 170 | this.quadCount = this._currentQuadCount; 171 | this._currentDrawCalls = 0; 172 | this._currentQuadCount = 0; 173 | }, 174 | 175 | setCamera: function( camera ) { 176 | this.flush(); 177 | 178 | var projection = camera.projection(); 179 | var view = camera.view(); 180 | 181 | // Transform Perspective Camera to current eye coordinates 182 | if( camera instanceof tpf.PerspectiveCamera ) { 183 | var tt = vec3.fromValues( 184 | Math.cos(camera.rotation[1]) * -this.currentEye.offset.x * this.worldScale, 185 | 0, 186 | Math.sin(camera.rotation[1]) * this.currentEye.offset.x * this.worldScale 187 | ); 188 | mat4.translate( view, view, tt ); 189 | projection = this.currentEye.projection; 190 | } 191 | 192 | // Cheap hack to transform HUD into center of the screen 193 | else if( camera instanceof tpf.OrthoCamera ) { 194 | var ts = this.getHMDState(); 195 | projection = this.currentEye.projection; 196 | 197 | var tt = vec3.fromValues( 198 | -camera.width * 0.5, 199 | camera.height * 0.5, 200 | -this.hudDistance 201 | ); 202 | var inv = vec3.fromValues(1,-1,1); 203 | view = mat4.create(); 204 | 205 | 206 | view = mat4.rotateX( view, view, -ts.rotation[2]); 207 | view = mat4.rotateZ( view, view, -ts.rotation[1]); 208 | if( this.hudFreelook ) { 209 | view = mat4.rotateY( view, view, -ts.rotation[0]); 210 | } 211 | else { 212 | this.HMDRotation += this.lastHMDRotation - ts.rotation[0]; 213 | this.HMDRotation *= 0.95; 214 | view = mat4.rotateY( view, view, this.HMDRotation); 215 | this.lastHMDRotation = ts.rotation[0]; 216 | } 217 | 218 | view = mat4.translate( view, view, tt); 219 | view = mat4.scale( view, view, inv); 220 | } 221 | 222 | this.gl.uniformMatrix4fv(this.program.uniform.projection, false, projection); 223 | this.gl.uniformMatrix4fv(this.program.uniform.view, false, view); 224 | 225 | if( camera.depthTest != this.depthTest) { 226 | this.depthTest = camera.depthTest; 227 | if( this.depthTest ) { 228 | this.gl.enable(this.gl.DEPTH_TEST); 229 | } 230 | else { 231 | this.gl.disable(this.gl.DEPTH_TEST); 232 | } 233 | } 234 | }, 235 | HMDRotation: 0, 236 | lastHMDRotation: 0, 237 | 238 | reset: function() { 239 | if( this.sensorDevice ) { 240 | this.sensorDevice.zeroSensor(); 241 | } 242 | }, 243 | 244 | getHMDState: function() { 245 | var state = { 246 | position: [0,0,0], 247 | rotation: [0,0,0] 248 | }; 249 | 250 | if( !this.sensorDevice ) { 251 | return state; 252 | } 253 | 254 | var s = this.sensorDevice.getState(); 255 | state.position = [ 256 | s.position.x * this.worldScale, 257 | s.position.y * this.worldScale, 258 | s.position.z * this.worldScale 259 | ]; 260 | 261 | // Quaternion to xyz rotation 262 | var q = s.orientation; 263 | var sqw = q.w*q.w; 264 | var sqx = q.x*q.x; 265 | var sqy = q.y*q.y; 266 | var sqz = q.z*q.z; 267 | var unit = sqx + sqy + sqz + sqw; // if normalised is one, otherwise is correction factor 268 | var test = q.x*q.y + q.z*q.w; 269 | if( test > 0.499*unit ) { // singularity at north pole 270 | state.rotation[0] = 2 * Math.atan2(q.x,q.w); 271 | state.rotation[1] = Math.PI/2; 272 | state.rotation[2] = 0; 273 | } 274 | else if( test < -0.499*unit ) { // singularity at south pole 275 | state.rotation[0] = -2 * Math.atan2(q.x,q.w); 276 | state.rotation[1] = -Math.PI/2; 277 | state.rotation[2] = 0; 278 | } 279 | else { 280 | state.rotation[0] = Math.atan2(2*q.y*q.w-2*q.x*q.z , sqx - sqy - sqz + sqw); 281 | state.rotation[1] = Math.asin(2*test/unit); 282 | state.rotation[2] = Math.atan2(2*q.x*q.w-2*q.y*q.z , -sqx + sqy - sqz + sqw); 283 | } 284 | 285 | return state; 286 | }, 287 | }); 288 | 289 | tpf.StereoRenderer.hasWebVR = function() { 290 | return navigator.getVRDevices || navigator.mozGetVRDevices; 291 | } 292 | 293 | 294 | }); 295 | -------------------------------------------------------------------------------- /lib/plugins/twopointfive/system.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'plugins.twopointfive.system' 3 | ) 4 | .requires( 5 | 'impact.system', 6 | 7 | 'plugins.twopointfive.namespace', 8 | 'plugins.twopointfive.renderer.ortho-camera', 9 | 'plugins.twopointfive.renderer.perspective-camera', 10 | 'plugins.twopointfive.renderer.renderer', 11 | 'plugins.twopointfive.renderer.stereo-renderer' 12 | ) 13 | .defines(function(){ "use strict"; 14 | 15 | 16 | ig.System.inject({ 17 | renderer: null, 18 | scene: null, 19 | camera: null, 20 | 21 | isFullscreen: false, 22 | hasMouseLock: false, 23 | 24 | initialWidth: 640, 25 | initialHeight: 480, 26 | fov: 75, 27 | 28 | stereoMode: false, 29 | 30 | init: function( canvasId, fps, width, height, scale ) { 31 | this.initialWidth = width; 32 | this.initialHeight = height; 33 | 34 | this.clock = new ig.Timer(); 35 | this.canvas = ig.$(canvasId); 36 | this.canvas.width = width * ig.ua.pixelRatio; 37 | this.canvas.height = height * ig.ua.pixelRatio; 38 | this.canvas.style.width = width + 'px'; 39 | this.canvas.style.height = height + 'px'; 40 | 41 | this.realWidth = this.width = width; 42 | this.realHeight = this.height = height; 43 | 44 | this.renderer = new tpf.Renderer(canvas); 45 | this.resize( width, height, scale ); 46 | }, 47 | 48 | horizontalFov: function() { 49 | // The renderer may override the system's fov for stereo rendering 50 | if( this.renderer.viewportFov ) { 51 | return this.renderer.viewportFov.toDeg(); 52 | } 53 | 54 | return this.fov * this.camera.aspect; 55 | }, 56 | 57 | resize: function( width, height, scale ) { 58 | var r = ig.System.useRetina ? ig.ua.pixelRatio : 1; 59 | 60 | this.width = width; 61 | this.height = height; 62 | 63 | this.realWidth = this.width = width; 64 | this.realHeight = this.height = height; 65 | this.canvas.width = width * r; 66 | this.canvas.height = height * r; 67 | 68 | this.renderer.setSize( width * r, height * r ); 69 | this.canvas.style.width = width + 'px'; 70 | this.canvas.style.height = height + 'px'; 71 | 72 | this.camera = new tpf.PerspectiveCamera( this.fov, width / height, 1, 10000 ); 73 | }, 74 | 75 | setStereoMode: function( on ) { 76 | if( on && !tpf.StereoRenderer.hasWebVR() ) { 77 | alert('No WebVR Support found :/'); 78 | return; 79 | } 80 | 81 | var fog = this.renderer && this.renderer.fog; 82 | 83 | this.stereoMode = on; 84 | if( on ) { 85 | this.renderer = new tpf.StereoRenderer(canvas); 86 | } 87 | else { 88 | this.renderer = new tpf.Renderer(canvas); 89 | } 90 | 91 | if( fog ) { 92 | this.renderer.setFog( fog.color, fog.near, fog.far ); 93 | } 94 | }, 95 | 96 | setupFullscreenMouselockOnce: function() { 97 | if( this.fullscreenSetupComplete ) { return; } 98 | 99 | 100 | // Fuck yeah, Vendor Prefixes \o/ 101 | 102 | // Request fullscreen 103 | this.canvas.requestFullscreen = 104 | ig.getVendorAttribute( this.canvas, 'requestFullscreen') || 105 | ig.getVendorAttribute( this.canvas, 'requestFullScreen'); // uppercase S (moz) 106 | 107 | var fullscreenCallback = this.fullscreenCallback.bind(this); 108 | document.addEventListener('fullscreenchange', fullscreenCallback, false); 109 | document.addEventListener('mozfullscreenchange', fullscreenCallback, false); 110 | document.addEventListener('webkitfullscreenchange', fullscreenCallback, false); 111 | 112 | // Request pointer lock 113 | ig.normalizeVendorAttribute( this.canvas, 'requestPointerLock' ); 114 | 115 | var mouseLockCallback = this.mouseLockCallback.bind(this); 116 | document.addEventListener('pointerlockchange', mouseLockCallback, false); 117 | document.addEventListener('mozpointerlockchange', mouseLockCallback, false); 118 | document.addEventListener('webkitpointerlockchange', mouseLockCallback, false); 119 | 120 | this.fullscreenSetupComplete = true; 121 | }, 122 | 123 | requestFullscreen: function() { 124 | this.setupFullscreenMouselockOnce(); 125 | this.canvas.requestFullscreen(this.renderer.fullscreenFlags); 126 | }, 127 | 128 | requestMouseLock: function() { 129 | this.setupFullscreenMouselockOnce(); 130 | this.canvas.requestPointerLock(); 131 | }, 132 | 133 | fullscreenCallback: function( ev ) { 134 | if( 135 | document.webkitFullscreenElement === this.canvas || 136 | document.mozFullscreenElement === this.canvas || 137 | document.mozFullScreenElement === this.canvas 138 | ) { 139 | this.isFullscreen = true; 140 | this.resize( screen.width, screen.height, 1 ); 141 | this.canvas.requestPointerLock(); 142 | } 143 | else { 144 | this.isFullscreen = false; 145 | this.resize( this.initialWidth, this.initialHeight, 1 ); 146 | } 147 | return true; 148 | }, 149 | 150 | mouseLockCallback: function( ev ) { 151 | this.hasMouseLock = ( 152 | document.pointerLockElement === this.canvas || 153 | document.mozPointerLockElement === this.canvas || 154 | document.webkitPointerLockElement === this.canvas 155 | ); 156 | }, 157 | 158 | clear: function() {}, 159 | }); 160 | 161 | ig.System.useRetina = true; 162 | 163 | ig.System._hasWebGL = null; 164 | ig.System.hasWebGL = function() { 165 | if( ig.System._hasWebGL === null ) { 166 | var canvas = document.createElement('canvas'); 167 | var gl = null; 168 | 169 | try { gl = canvas.getContext("webgl"); } 170 | catch (x) { gl = null; } 171 | 172 | if (gl === null) { 173 | try { gl = canvas.getContext("experimental-webgl"); } 174 | catch (x) { gl = null; } 175 | } 176 | ig.System._hasWebGL = (gl !== null); 177 | } 178 | return ig.System._hasWebGL; 179 | }; 180 | 181 | }); -------------------------------------------------------------------------------- /lib/plugins/twopointfive/world/culled-sectors.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'plugins.twopointfive.world.culled-sectors' 3 | ) 4 | .requires( 5 | 'plugins.twopointfive.namespace', 6 | 'plugins.twopointfive.renderer.quad' 7 | ) 8 | .defines(function(){ "use strict"; 9 | 10 | 11 | // CulledSectors divides a World into square sectors of 'sectorSize'. 12 | 13 | // The 'fillMap' is used as a guide of walkable space - this is usually the 14 | // floor map. 15 | 16 | // The 'geometryMaps' array in turn contains all maps of which the geometry 17 | // should be combined to one mesh for each sector. For each map and each 18 | // sector, the CulledSectorMap calls the map's 'getQuadsInRect' method and 19 | // puts all returned quads into big array buffers. 20 | 21 | // Once the CulledSectors are created, the visible parts of the map can be 22 | // drawn given an (x,y) position a view angle and fov. 23 | // CulledSectors recursively traverses through all visible dividing portals 24 | // and draws all world meshes and entities in them. 25 | 26 | // Each tpf.Entity comes with a .__sectorX/Y property that denotes the 27 | // current sector this entity lives in. When the entity is moved, it has to 28 | // call the CulledSectors' .moveEntity() function to notify the sector of 29 | // the movement. 30 | 31 | tpf.CulledSectors = ig.Class.extend({ 32 | sectorSize: 4, 33 | tilesize: 8, 34 | 35 | sectors: {}, 36 | numSectors: 0, 37 | 38 | sectorsTraversed: 0, 39 | 40 | init: function( fillMap, geometryMaps, sectorSize ) { 41 | 42 | this.sectorSize = sectorSize || 4; 43 | this.tilesize = fillMap.tilesize; 44 | 45 | this.generateSectors(sectorSize, fillMap, geometryMaps); 46 | }, 47 | 48 | draw: function(cx, cy, angle, fov) { 49 | var visibleSectors = this.collectVisibleSectors(cx, cy, angle, fov); 50 | 51 | this.drawWorld(visibleSectors); 52 | this.drawEntities(visibleSectors); 53 | }, 54 | 55 | drawWorld: function(visibleSectors) { 56 | for( var s in visibleSectors ) { 57 | visibleSectors[s].world.updateAnimations(); 58 | ig.system.renderer.pushMesh(visibleSectors[s].world); 59 | } 60 | }, 61 | 62 | drawEntities: function(visibleSectors) { 63 | var defferedDraw = []; 64 | 65 | for( var s in visibleSectors ) { 66 | var ents = visibleSectors[s].entities; 67 | 68 | for( var e in ents ) { 69 | // Entities with a zIndex are drawn later. This is typically 70 | // used for semi-transparent objects, such as water or effects 71 | if( ents[e].zIndex ) { 72 | defferedDraw.push(ents[e]); 73 | } 74 | else { 75 | ents[e].draw(); 76 | } 77 | } 78 | } 79 | 80 | // Sort all collected entities with zIndices and draw them 81 | defferedDraw.sort(ig.Game.SORT.Z_INDEX); 82 | for( var i = 0; i < defferedDraw.length; i++ ) { 83 | defferedDraw[i].draw(); 84 | } 85 | }, 86 | 87 | collectVisibleSectors: function( cx, cy, angle, fov ) { 88 | this.sectorsTraversed = 0; 89 | var visibleSectors = {}; 90 | 91 | var sx = (cx/(this.sectorSize*this.tilesize))|0, 92 | sy = (cy/(this.sectorSize*this.tilesize))|0; 93 | 94 | if( !this.sectors[sy] || !this.sectors[sy][sx] ) { 95 | // No sector? Shouldn't happen; we bail out. 96 | return visibleSectors; 97 | } 98 | var sector = this.sectors[sy][sx]; 99 | 100 | 101 | // Calculate the view frustum 102 | var fov2 = fov/2; 103 | var viewFrustum = { 104 | cx: cx, cy: cy, // Center position 105 | x1: cx + Math.cos(angle-fov2), y1: cy + Math.sin(angle-fov2), // Left edge 106 | x2: cx + Math.cos(angle+fov2), y2: cy + Math.sin(angle+fov2) // Right edge 107 | }; 108 | 109 | // This is where the magic happens. Gather all sectors that are visible 110 | // with the given frustum into 'visibleSectors', starting in 'sector'. 111 | this.traverseSector( sector, viewFrustum, null, visibleSectors ); 112 | return visibleSectors; 113 | }, 114 | 115 | moveEntity: function( ent ) { 116 | var tt = (this.sectorSize*this.tilesize); 117 | var newsx = ((ent.pos.x + ent.size.x/2) / tt)|0, 118 | newsy = ((ent.pos.y + ent.size.y/2) / tt)|0; 119 | 120 | if( ent.__sectorX === newsx && ent.__sectorY === newsy ) { 121 | // Same sector; nothing to do 122 | return; 123 | } 124 | 125 | if( ent.__sectorX !== null && ent.__sectorY !== null ) { 126 | this.removeEntityFromSector( ent.__sectorX, ent.__sectorY, ent ); 127 | } 128 | this.addEntityToSector( newsx, newsy, ent ); 129 | ent.__sectorX = newsx; 130 | ent.__sectorY = newsy; 131 | }, 132 | 133 | removeEntity: function( ent ) { 134 | if( ent.__sectorX !== null && ent.__sectorY !== null ) { 135 | this.removeEntityFromSector( ent.__sectorX, ent.__sectorY, ent ); 136 | } 137 | ent.__sectorX = null; 138 | ent.__sectorY = null; 139 | }, 140 | 141 | addEntityToSector: function( sx, sy, ent ) { 142 | if( !this.sectors[sy] ) { return; } 143 | 144 | var sector = this.sectors[sy][sx]; 145 | if( !sector ) { return; } 146 | 147 | sector.entities[ent.id] = ent; 148 | }, 149 | 150 | removeEntityFromSector: function( sx, sy, ent ) { 151 | if( !this.sectors[sy] ) { return; } 152 | 153 | var sector = this.sectors[sy][sx]; 154 | if( !sector ) { return; } 155 | delete sector.entities[ent.id]; 156 | }, 157 | 158 | traverseSector: function( sector, frustum, from, visibleSectors ) { 159 | visibleSectors[sector.id] = sector; 160 | this.sectorsTraversed++; 161 | 162 | // Loop through all portals of this sector and check if we can see them 163 | for( var i = 0; i < sector.portals.length; i++ ) { 164 | var portal = sector.portals[i]; 165 | 166 | // Skip if the portal's target is the sector we were coming from. 167 | if( portal.to != from ) { 168 | var fp = this.frustumThroughPortal(portal, frustum); 169 | if( fp ) { 170 | this.traverseSector(portal.to, fp, sector, visibleSectors); 171 | } 172 | } 173 | } 174 | }, 175 | 176 | pointToSideOfRay: function( x, y, rsx, rsy, rex, rey ) { 177 | return (y-rsy)*(rex-rsx) - (x-rsx)*(rey-rsy); 178 | }, 179 | 180 | frustumThroughPortal: function( portal, frustum ) { 181 | 182 | var side = this.pointToSideOfRay; // Shorter local name 183 | 184 | // Are the points of the portal between the frustum edges? 185 | var p1f1 = side(portal.x1, portal.y1, frustum.cx, frustum.cy, frustum.x1, frustum.y1) > 0; 186 | var p1f2 = side(portal.x1, portal.y1, frustum.cx, frustum.cy, frustum.x2, frustum.y2) < 0; 187 | var p2f1 = side(portal.x2, portal.y2, frustum.cx, frustum.cy, frustum.x1, frustum.y1) > 0; 188 | var p2f2 = side(portal.x2, portal.y2, frustum.cx, frustum.cy, frustum.x2, frustum.y2) < 0; 189 | 190 | // The line paralell to the portal, starting at the frustum's center 191 | var ppx1 = frustum.cx, 192 | ppy1 = frustum.cy, 193 | ppx2 = frustum.cx + (portal.x1-portal.x2), 194 | ppy2 = frustum.cy + (portal.y1-portal.y2); 195 | 196 | // Is the portal on the right side of the line paralell to the portal? 197 | var perpp = side(portal.x1, portal.y1, ppx1, ppy1, ppx2, ppy2) > 0; 198 | 199 | // Is the frustum on the right side of the line paralell to the portal? If so, we have 200 | // to invert the perpp result to see if the portal is in front of the frustum. 201 | var perpf = side(frustum.x1, frustum.y1, ppx1, ppy1, ppx2, ppy2) > 0; 202 | var front = perpf ? perpp : !perpp; 203 | 204 | if( 205 | !( 206 | ((p1f1 && p1f2) || (p2f1 && p2f2)) || // At least one point inside the frustum? 207 | (front && (p1f1 || p2f1) && (p1f2 || p2f2)) // Outside the frustum but in front? 208 | ) 209 | ) { 210 | // Can't see portal with the given frustum 211 | return null; 212 | } 213 | 214 | 215 | // Can we use the original frustum edges, or was the frustum narrowed on either side 216 | // by the portal? 217 | 218 | var nfx1, nfy1, nfx2, nfy2; 219 | 220 | // Decide which point to use for the left edge 221 | if( p1f1 && p1f2 ) { 222 | nfx1 = portal.x1; nfy1 = portal.y1; 223 | } 224 | else if( !p1f1 && (p1f2 || front) ) { 225 | nfx1 = frustum.x1; nfy1 = frustum.y1; 226 | } 227 | else { 228 | nfx1 = frustum.x2; nfy1 = frustum.y2; 229 | } 230 | 231 | // Decide which point to use for the right edge 232 | if( p2f1 && p2f2 ) { 233 | nfx2 = portal.x2; nfy2 = portal.y2; 234 | } 235 | else if( !p2f1 && (p2f2 || front) ) { 236 | nfx2 = frustum.x1; nfy2 = frustum.y1; 237 | } 238 | else { 239 | nfx2 = frustum.x2; nfy2 = frustum.y2; 240 | } 241 | 242 | // Build the new, potentially narrowed, frustum. We may have to flip the left and right 243 | // edges, so they are still in clockwise fashion. 244 | var narrowedFrustum = perpp 245 | ? { cx: frustum.cx, cy: frustum.cy, x1: nfx1, y1: nfy1, x2: nfx2, y2: nfy2 } 246 | : { cx: frustum.cx, cy: frustum.cy, x1: nfx2, y1: nfy2, x2: nfx1, y2: nfy1 }; 247 | 248 | return narrowedFrustum; 249 | }, 250 | 251 | 252 | generateSectors: function( sectorSize, fillMap, geometryMaps ) { 253 | // Divide the map into sectors of 'sectorSize' tiles. At each 254 | // sector edge, we insert a portal if there's a floor tile 255 | // left & right of the edge. 256 | 257 | var tilesize = fillMap.tilesize; 258 | 259 | // generate vertical portals each 'sectorSize' 260 | for( var x = sectorSize; x < fillMap.width; x+= sectorSize ) { 261 | var currentLength = 0; 262 | var currentStart = 0; 263 | for( var y = 0; y < fillMap.height; y++ ) { 264 | var left = fillMap.data[y][x-1]; 265 | var right = fillMap.data[y][x]; 266 | if( 267 | (y % sectorSize == 0 || !left || !right || y == fillMap.height-1) && 268 | currentLength 269 | ) { 270 | var sx = (x/sectorSize)|0, 271 | sy = ((y-1)/sectorSize)|0; 272 | 273 | var s1 = this.createSectorIfNeeded( sx-1, sy, geometryMaps ), // left sector 274 | s2 = this.createSectorIfNeeded( sx, sy, geometryMaps ); // right sector 275 | 276 | this.addPortal( 277 | x * tilesize, currentStart * tilesize, // start 278 | x * tilesize, y * tilesize, // end 279 | s1, s2 280 | ); 281 | 282 | currentStart = y; 283 | currentLength = 0; 284 | } 285 | if( left && right ) { 286 | currentLength++; 287 | } 288 | else { 289 | currentStart = y+1; 290 | } 291 | } 292 | } 293 | 294 | // generate horizontal portals each 'sectorSize' 295 | for( var y = sectorSize; y < fillMap.height; y+= sectorSize ) { 296 | var currentLength = 0; 297 | var currentStart = 0; 298 | for( var x = 0; x < fillMap.width; x++ ) { 299 | var top = fillMap.data[y-1][x]; 300 | var bottom = fillMap.data[y][x]; 301 | if( 302 | (x % sectorSize == 0 || !top || !bottom || x == fillMap.width-1) && 303 | currentLength 304 | ) { 305 | var sx = ((x-1)/sectorSize)|0, 306 | sy = (y/sectorSize)|0; 307 | 308 | var s1 = this.createSectorIfNeeded( sx, sy-1, geometryMaps ), // top sector 309 | s2 = this.createSectorIfNeeded( sx, sy, geometryMaps ); // bottom sector 310 | 311 | this.addPortal( 312 | currentStart * tilesize, y * tilesize, // start 313 | x * tilesize, y * tilesize, // end 314 | s1, s2 315 | ); 316 | 317 | currentStart = x; 318 | currentLength = 0; 319 | } 320 | 321 | if( top && bottom ) { 322 | currentLength++; 323 | } 324 | else { 325 | currentStart = x+1; 326 | } 327 | } 328 | } 329 | }, 330 | 331 | createSectorIfNeeded: function( x, y, maps ) { 332 | // Sector already created? 333 | if( !this.sectors[y] ) { 334 | this.sectors[y] = {}; 335 | } 336 | else if( this.sectors[y][x] ) { 337 | return this.sectors[y][x]; 338 | } 339 | 340 | var s = this.createSector(x, y, maps); 341 | this.sectors[y][x] = s; 342 | return s; 343 | }, 344 | 345 | createSector: function( x, y, maps ) { 346 | // Gather geometry from all maps 347 | var geometry = [], 348 | tx = x * this.sectorSize, 349 | ty = y * this.sectorSize, 350 | tw = this.sectorSize, 351 | th = this.sectorSize; 352 | 353 | var tiles = []; 354 | for( var i = 0; i < maps.length; i++ ) { 355 | tiles = tiles.concat( maps[i].getTilesInRect(tx, ty, tw, th) ); 356 | } 357 | var mesh = new tpf.TileMesh(tiles); 358 | 359 | // Return the sector 360 | return { 361 | id: this.numSectors++, 362 | x: x, y: y, 363 | portals: [], 364 | world: mesh, 365 | entities: {} 366 | }; 367 | }, 368 | 369 | addPortal: function( px1, py1, px2, py2, s1, s2 ) { 370 | s1.portals.push({x1: px1, y1: py1, x2: px2, y2: py2, to: s2}); 371 | s2.portals.push({x1: px1, y1: py1, x2: px2, y2: py2, to: s1}); 372 | } 373 | }); 374 | 375 | }); 376 | -------------------------------------------------------------------------------- /lib/plugins/twopointfive/world/light-map.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'plugins.twopointfive.world.light-map' 3 | ) 4 | .requires( 5 | 'impact.image', 6 | 'impact.map', 7 | 8 | 'plugins.twopointfive.namespace' 9 | ) 10 | .defines(function(){ "use strict"; 11 | 12 | 13 | tpf.LightMap = ig.Map.extend({ 14 | white: {r:1, g:1, b:1}, 15 | 16 | init: function( tilesize, data, tileset ) { 17 | this.parent( tilesize, ig.copy(data) ); 18 | 19 | // Grab the colors from the tileset 20 | var tilesetName = tileset instanceof ig.Image ? tileset.path : tileset; 21 | var tiles = new ig.Image( tilesetName ); 22 | 23 | var px = ig.getImagePixels(tiles.data, 0, 0, tiles.width, tiles.height).data; 24 | var colors = []; 25 | 26 | for( var y = 0; y < tiles.height; y+=tilesize ) { 27 | for( var x = 0; x < tiles.width; x+=tilesize ) { 28 | var index = (y * tiles.width + x) * 4; 29 | var color = {r:px[index]/255, g:px[index+1]/255, b:px[index+2]/255}; 30 | colors.push( color ); 31 | } 32 | } 33 | 34 | // Go through the map and replace the tile numbers with the 35 | // actual color values 36 | for( var y = 0; y < this.height; y++ ) { 37 | for( var x = 0; x < this.width; x++ ) { 38 | 39 | // Defaults to white (0xffffff) 40 | var tile = this.data[y][x]; 41 | this.data[y][x] = tile ? colors[tile-1] : this.white; 42 | } 43 | } 44 | }, 45 | 46 | 47 | getLight: function( x, y ) { 48 | if( 49 | (x >= 0 && x < this.width) && 50 | (y >= 0 && y < this.height) 51 | ) { 52 | return this.data[y][x]; 53 | } 54 | else { 55 | return this.white; 56 | } 57 | } 58 | }); 59 | 60 | }); 61 | -------------------------------------------------------------------------------- /lib/plugins/twopointfive/world/map.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'plugins.twopointfive.world.map' 3 | ) 4 | .requires( 5 | 'impact.background-map', 6 | 7 | 'plugins.twopointfive.namespace', 8 | 'plugins.twopointfive.world.tile' 9 | ) 10 | .defines(function(){ "use strict"; 11 | 12 | 13 | tpf.Map = ig.BackgroundMap.extend({ 14 | tileData: {}, 15 | yOffset: 0, 16 | 17 | init: function( tilesize, data, tileset, orientation, anims ) { 18 | this.parent( tilesize, data, tileset ); 19 | 20 | if( tpf.Map.fixTileSeams ) { 21 | this.tiles.expandSeams(tilesize); 22 | } 23 | 24 | this.yOffset = this.tilesize/2 * (orientation == 'floor' ? -1 : 1); 25 | 26 | this.anims = anims || {}; 27 | 28 | // Create tiles 29 | for( var y = 0; y < this.height; y++ ){ 30 | for( var x = 0; x < this.width; x++ ){ 31 | var tile = this.data[y][x]; 32 | if( tile !== 0 ) { 33 | if( !this.tileData[y] ) { 34 | this.tileData[y] = {}; 35 | } 36 | 37 | var anim = this.anims[tile-1] || null; 38 | this.tileData[y][x] = this.createTileAtPosition( tile-1, x, y, anim ); 39 | } 40 | } 41 | } 42 | }, 43 | 44 | draw: function() {}, // Maps are drawn by tpf.CulledSectors 45 | 46 | hasTile: function( x, y ) { 47 | return (x >= 0 && y >= 0 && this.data[y] && this.data[y][x]); 48 | }, 49 | 50 | createTileAtPosition: function( tile, x, y, anim ) { 51 | var t = new tpf.Tile( this.tiles, tile, this.tilesize ); 52 | t.quad.setPosition( 53 | x * this.tilesize + this.tilesize/2, 54 | this.yOffset, 55 | y * this.tilesize + this.tilesize/2 56 | ); 57 | t.quad.setRotation((-90).toRad(), 0, 0); 58 | t.anim = anim; 59 | return t; 60 | }, 61 | 62 | applyLightMap: function( lightMap ) { 63 | for( var y in this.tileData ) { 64 | for( var x in this.tileData[y] ) { 65 | var tile = this.tileData[y][x], 66 | rx = x|0, 67 | ry = y|0; 68 | tile.quad.setColor(lightMap.getLight(rx, ry)); 69 | } 70 | } 71 | }, 72 | 73 | getTilesInRect: function( xs, ys, w, h ) { 74 | var tiles = []; 75 | for( var y = ys; y < ys + h; y++ ) { 76 | if( !this.tileData[y] ) { continue; } 77 | 78 | for( var x = xs; x < xs + w; x++ ) { 79 | if( !this.tileData[y][x] ) { continue; } 80 | tiles.push(this.tileData[y][x]); 81 | } 82 | } 83 | return tiles; 84 | } 85 | }); 86 | 87 | tpf.Map.fixTileSeams = true; 88 | 89 | 90 | }); 91 | -------------------------------------------------------------------------------- /lib/plugins/twopointfive/world/tile.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'plugins.twopointfive.world.tile' 3 | ) 4 | .requires( 5 | 'impact.image', 6 | 7 | 'plugins.twopointfive.namespace', 8 | 'plugins.twopointfive.renderer.quad' 9 | ) 10 | .defines(function(){ "use strict"; 11 | 12 | 13 | // tpf.Tile encapsulates a tpf.Quad and provides a method to set a tile number directly, 14 | // instead of specifying raw UV coords, and a function to draw itself. 15 | 16 | tpf.Tile = ig.Class.extend({ 17 | tile: -1, 18 | scale: 0, 19 | image: null, 20 | quad: null, 21 | 22 | init: function( image, tile, tileWidth, tileHeight, scale ) { 23 | this.scale = scale || 1; 24 | this.image = image; 25 | this.tileWidth = tileWidth; 26 | this.tileHeight = tileHeight || tileWidth; 27 | 28 | this.quad = new tpf.Quad( 29 | this.tileWidth * this.scale, 30 | this.tileHeight * this.scale, 31 | this.image.texture 32 | ); 33 | 34 | this.setTile( tile || 0 ); 35 | }, 36 | 37 | setTile: function( t ) { 38 | if( t == this.tile ) { return; } 39 | this.tile = t; 40 | 41 | var tileSpacing = this.image.seamsExpanded ? 2 : 0, 42 | tx = t % Math.floor(this.image.width / this.tileWidth), 43 | ty = Math.floor(t / Math.floor(this.image.width / this.tileWidth)); 44 | 45 | var px = (tx * this.tileWidth + tx * tileSpacing) / this.image.textureWidth, 46 | py = (ty * this.tileHeight + ty * tileSpacing) / this.image.textureHeight, 47 | wx = this.tileWidth / this.image.textureWidth, 48 | wy = this.tileHeight / this.image.textureHeight; 49 | 50 | this.quad.setUV(px, py + wy, px + wx, py); 51 | }, 52 | 53 | setTileInBuffer: function(buffer, offset, t) { 54 | var tileSpacing = this.image.seamsExpanded ? 2 : 0, 55 | tx = t % Math.floor(this.image.width / this.tileWidth), 56 | ty = Math.floor(t / Math.floor(this.image.width / this.tileWidth)); 57 | 58 | var px = (tx * this.tileWidth + tx * tileSpacing) / this.image.textureWidth, 59 | py = (ty * this.tileHeight + ty * tileSpacing) / this.image.textureHeight, 60 | wx = this.tileWidth / this.image.textureWidth, 61 | wy = this.tileHeight / this.image.textureHeight; 62 | 63 | tpf.Quad.setUVInBuffer(buffer, offset, px, py + wy, px + wx, py); 64 | }, 65 | 66 | draw: function() { 67 | ig.system.renderer.pushQuad(this.quad); 68 | } 69 | }); 70 | 71 | 72 | 73 | // The tpf.TileMesh collects a number of tpf.Tiles into a single large 74 | // buffer, so it can be drawn directly without copying each Quad first. 75 | // This is used for TwoPointFive's world maps. 76 | 77 | tpf.TileMesh = function( tiles ) { 78 | this.animatedTiles = []; 79 | this.length = tiles.length; 80 | 81 | if( !this.length ) { 82 | return; 83 | } 84 | 85 | this.buffer = new Float32Array(tpf.Quad.SIZE * this.length); 86 | this.texture = tiles[0].quad.texture; 87 | 88 | for( var i = 0; i < this.length; i++ ) { 89 | var tile = tiles[i]; 90 | tile.quad.copyToBuffer(this.buffer, i * tpf.Quad.SIZE ); 91 | if( tile.anim ) { 92 | this.animatedTiles.push({tile: tile, offset: i}); 93 | } 94 | } 95 | }; 96 | 97 | tpf.TileMesh.prototype.updateAnimations = function() { 98 | for( var i = 0; i < this.animatedTiles.length; i++ ) { 99 | var at = this.animatedTiles[i]; 100 | at.tile.setTileInBuffer( this.buffer, at.offset, at.tile.anim.tile ); 101 | } 102 | }; 103 | 104 | 105 | 106 | tpf.HudTile = tpf.Tile.extend({ 107 | init: function( image, tile, tileWidth, tileHeight ) { 108 | this.image = image; 109 | this.tileWidth = tileWidth; 110 | this.tileHeight = tileHeight || tileWidth; 111 | 112 | this.quad = new tpf.Quad(this.tileWidth, this.tileHeight, this.image.texture); 113 | this.setTile( tile || 0 ); 114 | }, 115 | 116 | setTile: function( t ) { 117 | if( t == this.tile ) { return; } 118 | this.tile = t; 119 | 120 | var tx = (Math.floor(t * this.tileWidth) % this.image.width) / this.image.width, 121 | ty = (Math.floor(t * this.tileWidth / this.image.width) * this.tileHeight) / this.image.height, 122 | wx = this.tileWidth / this.image.width, 123 | wy = this.tileHeight / this.image.height; 124 | 125 | // Flipped-Y 126 | this.quad.setUV(tx, 1-(ty+wy), tx+wx, 1-ty); 127 | }, 128 | 129 | setPosition: function( x, y ) { 130 | this.quad.setPosition(x + this.tileWidth/2, y + this.tileHeight/2, 0); 131 | }, 132 | 133 | setAlpha: function( a ) { 134 | this.quad.setAlpha(a.limit(0,1)); 135 | } 136 | }); 137 | 138 | 139 | 140 | 141 | }); 142 | -------------------------------------------------------------------------------- /lib/plugins/twopointfive/world/wall-map.js: -------------------------------------------------------------------------------- 1 | ig.module( 2 | 'plugins.twopointfive.world.wall-map' 3 | ) 4 | .requires( 5 | 6 | 'plugins.twopointfive.namespace', 7 | 'plugins.twopointfive.world.map' 8 | ) 9 | .defines(function(){ "use strict"; 10 | 11 | 12 | tpf.WallMap = tpf.Map.extend({ 13 | createTileAtPosition: function( tile, x, y, anim ) { 14 | 15 | // We need 4 tiles, one for each side of the block 16 | var tiles = {}; 17 | for( var name in tpf.WallMap.offsets ) { 18 | var off = tpf.WallMap.offsets[name]; 19 | 20 | var t = new tpf.Tile( this.tiles, tile, this.tilesize ); 21 | 22 | t.quad.setPosition( 23 | (x + off.x/2 + 0.5) * this.tilesize, 24 | 0, 25 | (y + off.y/2 + 0.5) * this.tilesize 26 | ); 27 | t.quad.setRotation(0, off.rot, 0); 28 | t.anim = anim; 29 | 30 | tiles[name] = t; 31 | } 32 | 33 | return tiles; 34 | }, 35 | 36 | applyLightMap: function( lightMap ) { 37 | for( var y in this.tileData ) { 38 | for( var x in this.tileData[y] ) { 39 | 40 | var tiles = this.tileData[y][x], 41 | rx = x|0, 42 | ry = y|0; 43 | 44 | for( var name in tiles ) { 45 | var off = tpf.WallMap.offsets[name]; 46 | tiles[name].quad.setColor( lightMap.getLight(rx+off.x, ry+off.y) ); 47 | } 48 | } 49 | } 50 | }, 51 | 52 | getTilesInRect: function( xs, ys, w, h ) { 53 | var tiles = []; 54 | 55 | for( var y = ys; y < ys + h; y++ ) { 56 | for( var x = xs; x < xs + w; x++ ) { 57 | 58 | // Take care to get the walls _facing_ to this tile, instead of just 59 | // the walls _on_ this tile 60 | for( var name in tpf.WallMap.offsets ) { 61 | var off = tpf.WallMap.offsets[name]; 62 | var tx = x - off.x, 63 | ty = y - off.y; 64 | 65 | if( this.hasTile(tx, ty) && this.tileData[ty][tx][name] ) { 66 | tiles.push(this.tileData[ty][tx][name]); 67 | } 68 | } 69 | 70 | } 71 | } 72 | return tiles; 73 | }, 74 | 75 | 76 | // Typically, all walls that are visible are connected to floor tiles, 77 | // so we can safely remove those that are not. 78 | 79 | eraseDisconnectedWalls: function( floorMap ) { 80 | for( var y in this.tileData ) { 81 | for( var x in this.tileData[y] ) { 82 | 83 | var tiles = this.tileData[y][x], 84 | rx = x|0, 85 | ry = y|0; 86 | 87 | for( var name in tpf.WallMap.offsets ) { 88 | var off = tpf.WallMap.offsets[name]; 89 | if( !floorMap.hasTile(rx + off.x, ry + off.y) ) { 90 | delete tiles[name]; 91 | } 92 | } 93 | 94 | } 95 | } 96 | } 97 | }); 98 | 99 | 100 | tpf.WallMap.offsets = { 101 | top: {x: 0, y:-1, rot: (180).toRad() }, 102 | bottom: {x: 0, y: 1, rot: (0).toRad() }, 103 | right: {x: 1, y: 0, rot: (90).toRad() }, 104 | left: {x:-1, y: 0, rot: (-90).toRad() } 105 | }; 106 | 107 | }); 108 | -------------------------------------------------------------------------------- /media/blob-gib.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/blob-gib.png -------------------------------------------------------------------------------- /media/blob-spawn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/blob-spawn.png -------------------------------------------------------------------------------- /media/blob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/blob.png -------------------------------------------------------------------------------- /media/explosion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/explosion.png -------------------------------------------------------------------------------- /media/fredoka-one.font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/fredoka-one.font.png -------------------------------------------------------------------------------- /media/grenade-launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/grenade-launcher.png -------------------------------------------------------------------------------- /media/grenade-pickup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/grenade-pickup.png -------------------------------------------------------------------------------- /media/grenade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/grenade.png -------------------------------------------------------------------------------- /media/health-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/health-icon.png -------------------------------------------------------------------------------- /media/health.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/health.png -------------------------------------------------------------------------------- /media/hud-blood-low.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/hud-blood-low.png -------------------------------------------------------------------------------- /media/loading-block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/loading-block.png -------------------------------------------------------------------------------- /media/rotate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/rotate.png -------------------------------------------------------------------------------- /media/sounds/blob-gib.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/sounds/blob-gib.m4a -------------------------------------------------------------------------------- /media/sounds/blob-gib.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/sounds/blob-gib.ogg -------------------------------------------------------------------------------- /media/sounds/empty-click.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/sounds/empty-click.m4a -------------------------------------------------------------------------------- /media/sounds/empty-click.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/sounds/empty-click.ogg -------------------------------------------------------------------------------- /media/sounds/explosion.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/sounds/explosion.m4a -------------------------------------------------------------------------------- /media/sounds/explosion.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/sounds/explosion.ogg -------------------------------------------------------------------------------- /media/sounds/grenade-bounce.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/sounds/grenade-bounce.m4a -------------------------------------------------------------------------------- /media/sounds/grenade-bounce.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/sounds/grenade-bounce.ogg -------------------------------------------------------------------------------- /media/sounds/grenade-launcher.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/sounds/grenade-launcher.m4a -------------------------------------------------------------------------------- /media/sounds/grenade-launcher.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/sounds/grenade-launcher.ogg -------------------------------------------------------------------------------- /media/sounds/health-pickup.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/sounds/health-pickup.m4a -------------------------------------------------------------------------------- /media/sounds/health-pickup.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/sounds/health-pickup.ogg -------------------------------------------------------------------------------- /media/sounds/hurt1.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/sounds/hurt1.m4a -------------------------------------------------------------------------------- /media/sounds/hurt1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/sounds/hurt1.ogg -------------------------------------------------------------------------------- /media/sounds/hurt2.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/sounds/hurt2.m4a -------------------------------------------------------------------------------- /media/sounds/hurt2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/sounds/hurt2.ogg -------------------------------------------------------------------------------- /media/sounds/hurt3.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/sounds/hurt3.m4a -------------------------------------------------------------------------------- /media/sounds/hurt3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/sounds/hurt3.ogg -------------------------------------------------------------------------------- /media/tiles/basic-tiles-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/tiles/basic-tiles-64.png -------------------------------------------------------------------------------- /media/tiles/lights-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/tiles/lights-64.png -------------------------------------------------------------------------------- /media/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/TwoPointFive/6b8edc826dd5be3dc61eb7b9a350a256fb532579/media/title.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # TwoPointFive for Impact 2 | 3 | TwoPointFive is a plugin for the [Impact HTML5 Game Engine](http://impactjs.com/) that provides a 3D viewport for the game world. 4 | 5 | 6 | ### Demo 7 | [Super Blob Blaster](http://phoboslab.org/twopointfive/) 8 | 9 | 10 | A demo game that uses this plugin is included in this repository. 11 | 12 | Please note that you need a license for Impact to actually run the demo. The `lib/impact/` and `lib/weltmeister/` directories from Impact need to be copied into the `lib/` directory of this demo. 13 | 14 | 15 | ### Usage 16 | 17 | The demo game and its sources in `lib/game/` should give you a good overview on how to use the plugin. 18 | 19 | The most importantant thing for your entities is to subclass them from `tpf.Entity` rather than from `ig.Entity`. The `tpf.Entity` provides some capabilities to position and draw them in 3D space. Each entity has an additional `.z` property for `.pos` and `.vel` that determines its vertical position and speed in the world. 20 | 21 | The layers in your level need to be named in a certain way for TwoPointFive to recognize them. The tile layers for the graphics need to be named `floor`, `ceiling` and `walls`. An additional `light` layer provides an additional tint for each of the tiles in the level. Note that the tilesize for each of these layers must be the same. Again, have a look a the included `lib/game/levels/base1.js` for an example. 22 | 23 | 24 | TwoPointFive comes with some additions to Impact's Debug Module. To load it, simply require the `plugins.twopointfive.debug` module in your `main.js`. 25 | 26 | 27 | ### A note about Tile Seams 28 | 29 | Whenever drawing parts of an image in WebGL, such is done here when drawing tiles, WebGL may sample pixels from a region of the image that is outside the one you specified. This happens mostly due to rounding errors and will result in ugly seams between tiles. 30 | 31 | TwoPointFive attempts to work around this issue by redrawing your tileset into a slightly larger image and adding a 1 pixel border around each tile. This 1px border is a copy of the neighboring pixels. Whenever WebGL now samples a texture slightly outside the tile boundary, it will sample from this 1px border and thus avoid any seams in your map. 32 | 33 | If you do not want this behaviour, you can disable it by setting `tpf.Map.fixTileSeams = false;` before calling `ig.main()`. 34 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | -webkit-box-sizing: border-box; 3 | -moz-box-sizing: border-box; 4 | box-sizing: border-box; 5 | } 6 | 7 | html { 8 | background-color: #222; 9 | padding: 0; 10 | margin: 0; 11 | } 12 | 13 | body { 14 | color: #eee; 15 | font-family: helvetica, arial, sans-serif; 16 | margin: 0; 17 | font-size: 10pt; 18 | line-height: 1.3; 19 | } 20 | 21 | h1 { 22 | color: #ccc; 23 | font-size: 16px; 24 | font-weight: normal; 25 | text-transform: uppercase; 26 | margin: 10px 0 6px 0; 27 | } 28 | 29 | a { 30 | color: #0170ce; 31 | text-decoration: none; 32 | font-weight: bold; 33 | } 34 | 35 | a:hover { 36 | color: #077fe5; 37 | } 38 | 39 | .panel { 40 | margin-bottom: 32px; 41 | padding: 8px; 42 | background-color: rgba(0,0,0,0.5); 43 | } 44 | 45 | .option { 46 | padding: 4px 0 4px 0; 47 | } 48 | 49 | label { 50 | color: #fff; 51 | font-weight: bold; 52 | cursor: pointer; 53 | } 54 | 55 | #no-webgl-notice { 56 | text-align: center; 57 | font-size: 24px; 58 | padding: 64px 24px; 59 | color: #ffbc6c; 60 | } 61 | 62 | #rotate-to-play { 63 | text-align: center; 64 | margin-bottom: 48px; 65 | } 66 | 67 | 68 | /* 69 | Display or hide certain elements depending on platform (mobile/desktop), 70 | orientation (landscape/portrait) and webgl support. 71 | The body classes .mobile or .desktop and .webgl or .no-webgl are set in 72 | JavaScript in the lib/game/main.js 73 | */ 74 | 75 | @media all and (orientation:portrait) { body.mobile .landscape-only { display: none; } } 76 | @media all and (orientation:landscape) { body.mobile .portrait-only { display: none; } } 77 | 78 | body.mobile .desktop-only { display: none; } 79 | body.desktop .mobile-only { display: none; } 80 | 81 | body.webgl .no-webgl-only { display: none; } 82 | body.no-webgl .webgl-only { display: none; } 83 | 84 | body.desktop { 85 | padding: 32px 0 128px 0; 86 | width: 640px; 87 | margin: 0 auto; 88 | } 89 | 90 | body.mobile { 91 | padding: 0; 92 | width: auto; 93 | font-size: 8px; 94 | } 95 | 96 | body.desktop #canvas { 97 | width: 640px; 98 | height: 480px; 99 | background-color: #000; 100 | } 101 | 102 | body.mobile #canvas { 103 | position: fixed; 104 | width: 100%; 105 | height: 100%; 106 | background-color: #000; 107 | } 108 | 109 | --------------------------------------------------------------------------------