├── 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 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | Rotate to Landscape to play!
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
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 |
--------------------------------------------------------------------------------