debe1b5b6343b888f8ebabf4594b946f55241a31
[featherfall2.git] / src / main.js
1 "use strict";
2
3 var b2Vec2 = yf.argcd(
4 function (a) {
5 return b2Vec2.call(this, a[0] || a.x || 0.0, a[1] || a.y || 0.0);
6 },
7 Box2D.b2Vec2
8 );
9
10
11 yf.ipairs(function (k, v) { Box2D[k] = Box2D[v]; }, {
12 DYNAMIC_BODY: 'b2_dynamicBody'
13 });
14
15 function accessor (T, p) {
16 if (p[0].toLowerCase() === p[0])
17 return { get: T.prototype['get_' + p],
18 set: T.prototype['set_' + p] };
19 else
20 return { get: T.prototype['Get' + p],
21 set: T.prototype['Set' + p] };
22 }
23
24 yT.defineProperties(Box2D.b2Vec2.prototype, {
25 x: accessor(Box2D.b2Vec2, 'x'),
26 y: accessor(Box2D.b2Vec2, 'y'),
27 0: { alias: 'x' },
28 1: { alias: 'y' },
29 });
30
31 yT.defineProperties(Box2D.b2BodyDef.prototype, {
32 type: accessor(Box2D.b2BodyDef, 'type'),
33 position: accessor(Box2D.b2BodyDef, 'position'),
34 });
35
36 yT.defineProperties(Box2D.b2Body.prototype, {
37 position: accessor(Box2D.b2Body, 'Position'),
38 linearVelocity: accessor(Box2D.b2Body, 'LinearVelocity'),
39 angle: accessor(Box2D.b2Body, 'Angle'),
40 });
41
42
43 var storage;
44
45 var BodyC = yT(yuu.C, {
46 SLOTS: ['transform'],
47
48 constructor: function (body, size) {
49 this._body = body;
50 this._matrix = mat4.create();
51 },
52
53 position: { alias: '_body.position' },
54 linearVelocity: { alias: '_body.linearVelocity' },
55 ApplyForce: { proxy: '_body.ApplyForce' },
56
57 matrix: { get: function () {
58 var mat = this._matrix;
59 mat4.identity(mat);
60 mat4.rotateZ(mat, mat, this._body.angle);
61 var pos = this._body.position;
62 mat4.translate(mat, mat, [pos.x, pos.y, 0]);
63 return mat;
64 } }
65 });
66
67 var PlayerController = yT(yuu.C, {
68 constructor: function (body, left, right) {
69 this.body = body;
70 this.left = left;
71 this.right = right;
72 this.dleftLeft = this.dleftRight =
73 this.drightLeft = this.drightRight = 0;
74 this.up = 0;
75 this.free = 0;
76 this.leftPivot = 0;
77 this.rightPivot = 0;
78 this.commands = {
79 dleftLeft: yuu.propcmd(this, 'dleftLeft'),
80 dleftRight: yuu.propcmd(this, 'dleftRight'),
81 drightLeft: yuu.propcmd(this, 'drightLeft'),
82 drightRight: yuu.propcmd(this, 'drightRight'),
83 up: yuu.propcmd(this, 'up'),
84 free: yuu.propcmd(this, 'free'),
85 };
86 },
87
88 _updatePivots: function () {
89 var PIVOT_SPEED = 0.05;
90 var leftSpeed = (this.dleftRight - this.dleftLeft) * PIVOT_SPEED;
91 var rightSpeed = (this.drightLeft - this.drightRight) * PIVOT_SPEED;
92 this.leftPivot = yf.clamp(this.leftPivot + leftSpeed, 0, 1);
93 this.rightPivot = yf.clamp(this.rightPivot + rightSpeed, 0, 1);
94 },
95
96 _updateTransforms: function () {
97 this.left.yaw = -this.leftPivot * Math.PI / 2;
98 this.right.yaw = this.rightPivot * Math.PI / 2;
99 },
100
101 tick: function () {
102 this._updatePivots();
103 var THRUST = 3.5;
104 var DRAG_FREE = 0.01;
105 var DRAG_OPEN = 0.5;
106 var DRAG_LOCK = 1;
107 var CORRECTION = 1;
108
109 var leftAngle = (1 - this.leftPivot) * Math.PI / 2;
110 var rightAngle = (1 - this.rightPivot) * Math.PI / 2;
111
112 var cleft = Math.cos(leftAngle);
113 var cright = Math.cos(rightAngle);
114 var sleft = Math.sin(leftAngle);
115 var sright = Math.sin(rightAngle);
116
117 var thrust = +!this.free * +this.up * THRUST;
118 var ax = thrust * (cleft - cright);
119 var ay = thrust * (sright + sleft);
120
121 var v = this.body.linearVelocity;
122 var drag = this.up ? DRAG_OPEN : this.free ? DRAG_FREE : DRAG_LOCK;
123 ax += drag * Math.max(cleft, cright) * v.x * v.x * -Math.sign(v.x);
124 ay += drag * (sleft + sright) * v.y * v.y * -Math.sign(v.y);
125
126 if (!this.up || this.free)
127 ax += CORRECTION * (cleft - cright) * v.y * v.y * Math.sign(v.y);
128
129 this.body.ApplyForce(new b2Vec2(ax, ay), this.body.position);
130
131 this._updateTransforms();
132 },
133
134 TAPS: ['tick'],
135 });
136
137 function bodyFromAABB (world, position, aabb, density) {
138 var bd = new Box2D.b2BodyDef();
139 var shape = new Box2D.b2PolygonShape();
140 shape.SetAsBox(aabb.w / 2, aabb.h / 2);
141 if (density)
142 bd.type = Box2D.DYNAMIC_BODY;
143 bd.position = new b2Vec2(position[0], position[1]);
144 var body = world.CreateBody(bd);
145 body.CreateFixture(shape, density || 0);
146 return body;
147 }
148
149 function bodyFromLine (world, p0, p1) {
150 var bd = new Box2D.b2BodyDef();
151 var shape = new Box2D.b2EdgeShape();
152 shape.Set(new b2Vec2(p0), new b2Vec2(p1));
153 var body = world.CreateBody(bd);
154 body.CreateFixture(shape, 0);
155 return body;
156 }
157
158 var GameScene = yT(yuu.Scene, {
159 constructor: function () {
160 yuu.Scene.call(this);
161
162 var zoom = 10;
163 this.layer0.resize(
164 zoom * -1.3333333333/2, zoom * -0.2, zoom * 1.3333333333, zoom * 1);
165
166 var world = new Box2D.b2World(new b2Vec2(0, -5));
167
168 var body, left, right;
169 this.player = new yuu.E(
170 body = new BodyC(bodyFromAABB(
171 world, [0, 5], new yuu.AABB(0.89, 1.0), 1.0)),
172 new yuu.QuadC('@player')
173 .setSize([0.89, 1.0])
174 );
175
176 var leftWing = new yuu.E(left = new yuu.Transform()
177 .setPosition([-0.25, 0.15, 0]),
178 new yuu.QuadC('@left')
179 .setZ(-1)
180 .setAnchor("right")
181 .setSize([0.45, 0.22])
182 .setPosition([0, 0]));
183 var rightWing = new yuu.E(right = new yuu.Transform()
184 .setPosition([0.25, 0.15, 0]),
185 new yuu.QuadC('@right')
186 .setZ(-1)
187 .setAnchor('left')
188 .setSize([0.45, 0.22])
189 .setPosition([0, 0]));
190 this.player.addChildren(leftWing, rightWing);
191 this.entity0.addChild(this.player);
192
193 var ground = new yuu.E(
194 new BodyC(bodyFromLine(world, [-100, 0], [100, 0])),
195 new yuu.QuadC()
196 .setAnchorAtPosition('top')
197 .setSize([100, 1])
198 .setColor([0, 0.5, 0, 1]));
199 this.entity0.addChild(ground);
200
201 this.player.attach(
202 this.controller = new PlayerController(body, left, right));
203 Object.assign(this.commands, this.controller.commands);
204
205 this.entity0.attach(new yuu.Ticker(function () {
206 world.Step(1/60, 4, 4);
207 return true;
208 }, 1));
209
210 this.ready = yuu.ready([
211 new yuu.Material('@player'),
212 new yuu.Material('@left'),
213 new yuu.Material('@right')]);
214 },
215
216 init: function () {
217 var audio0 = new Audio();
218 audio0.src = audio0.canPlayType('audio/ogg')
219 ? "data/sound/starting-line.ogg"
220 : "data/sound/starting-line.mp3";
221 audio0.autoplay = true;
222 audio0.loop = true;
223 document.body.appendChild(audio0);
224 var source = yuu.audio.createMediaElementSource(audio0);
225 source.connect(yuu.audio.music);
226 },
227
228 KEYBINDS: {
229 space: '+up',
230 up: '+up',
231 q: '+dleftLeft',
232 w: '+dleftRight',
233 o: '+drightLeft',
234 p: '+drightRight',
235 z: '+free',
236 x: '+up',
237 }
238 });
239
240 function start () {
241 yuu.director.start();
242 }
243
244 function load () {
245 storage = ystorage.getStorage();
246 yuu.audio.storage = storage;
247 var game = new GameScene();
248 yuu.director.pushScene(game);
249 return game.ready;
250 }
251
252 window.addEventListener("load", function() {
253 yuu.registerInitHook(load);
254 yuu.init({ backgroundColor: [0, 0, 0, 1], antialias: false })
255 .then(start);
256 });