1846c3e421350df747f929a7dc2dbf4aa5530606
[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 camelizeCase (s) {
16 return s.replace(/_([a-z])/g, function (m) {
17 return m[1].toUpperCase();
18 });
19 }
20
21 function wrapEmscriptenType (T) {
22 var P = T.prototype;
23 var keys = Object.keys(P);
24 var members = keys.filter(function (k) { return k.startsWith("get_"); });
25 var accessors = keys.filter(function (k) { return k.startsWith("Get"); });
26 members.forEach(function (k) {
27 var name = camelizeCase(k.slice(4));
28 if (!name || name in P) return;
29 Object.defineProperty(P, name, {
30 get: P[k], set: P['s' + k.slice(1)]
31 });
32
33 });
34 accessors.forEach(function (k) {
35 var name = k.slice(3);
36 name = name[0].toLowerCase() + name.slice(1);
37 if (!name || name in P) return;
38 Object.defineProperty(P, name, {
39 get: P[k], set: P['S' + k.slice(1)]
40 });
41
42 });
43 }
44
45 yf.each(wrapEmscriptenType, [
46 Box2D.b2Vec2, Box2D.b2BodyDef, Box2D.b2Body,
47 Box2D.b2JointDef, Box2D.b2RevoluteJointDef,
48 Box2D.b2Joint, Box2D.b2DistanceJoint, Box2D.b2RevoluteJoint,
49 ]);
50
51 yT.defineProperties(Box2D.b2Vec2.prototype, {
52 0: { alias: 'x' },
53 1: { alias: 'y' },
54 });
55
56
57 var storage;
58
59 var BodyC = yT(yuu.C, {
60 SLOTS: ['transform'],
61
62 constructor: function (body, size) {
63 this.body = body;
64 this._matrix = mat4.create();
65 },
66
67 position: { alias: 'body.position' },
68 angle: { alias: 'body.angle' },
69 linearVelocity: { alias: 'body.linearVelocity' },
70 ApplyForce: { proxy: 'body.ApplyForce' },
71
72 matrix: { get: function () {
73 var mat = this._matrix;
74 mat4.identity(mat);
75 var pos = this.body.position;
76 mat4.translate(mat, mat, [pos.x, pos.y, 0]);
77 mat4.rotateZ(mat, mat, this.body.angle);
78 return mat;
79 } }
80 });
81
82 var PlayerController = yT(yuu.C, {
83 constructor: function (body, left, right, leftJoint, rightJoint) {
84 this.body = body;
85 this.left = left;
86 this.right = right;
87 this.leftJoint = leftJoint;
88 this.rightJoint = rightJoint;
89 this.dleftLeft = this.dleftRight =
90 this.drightLeft = this.drightRight = 0;
91 this.up = 0;
92 this.free = 0;
93 this.commands = {
94 dleftLeft: yuu.propcmd(this, 'dleftLeft'),
95 dleftRight: yuu.propcmd(this, 'dleftRight'),
96 drightLeft: yuu.propcmd(this, 'drightLeft'),
97 drightRight: yuu.propcmd(this, 'drightRight'),
98 up: yuu.propcmd(this, 'up'),
99 free: yuu.propcmd(this, 'free'),
100 };
101 },
102
103 _updatePivots: function () {
104 var PIVOT_SPEED = 2;
105 this.leftJoint.motorSpeed =
106 (this.dleftRight - this.dleftLeft) * PIVOT_SPEED;
107 this.rightJoint.motorSpeed =
108 (this.drightRight - this.drightLeft) * PIVOT_SPEED;
109 },
110
111 _updateTransforms: function () {
112 },
113
114 tick: function () {
115 this._updatePivots();
116 var THRUST = 5;
117 /* var DRAG_FREE = 0.01;
118 var DRAG_OPEN = 0.5;
119 var DRAG_LOCK = 1;
120 var CORRECTION = 1;*/
121
122 var leftAngle = this.leftJoint.GetJointAngle();
123 var rightAngle = this.rightJoint.GetJointAngle();
124 var thrust = +!this.free * +this.up * THRUST;
125 var leftThrust = new b2Vec2(
126 Math.sin(leftAngle) * thrust, Math.cos(leftAngle) * thrust);
127 var rightThrust = new b2Vec2(
128 Math.sin(rightAngle) * thrust, Math.cos(rightAngle) * thrust);
129 this.body.body.ApplyForceToCenter(leftThrust);
130 this.body.body.ApplyForceToCenter(rightThrust);
131
132 var cleft = Math.cos(leftAngle);
133 var cright = Math.cos(rightAngle);
134 var sleft = Math.sin(leftAngle);
135 var sright = Math.sin(rightAngle);
136
137 var ax = thrust * (cleft - cright);
138 var ay = thrust * (sright + sleft);
139
140 /*
141 var v = this.body.linearVelocity;
142 var drag = this.up ? DRAG_OPEN : this.free ? DRAG_FREE : DRAG_LOCK;
143 ax += drag * Math.max(cleft, cright) * v.x * v.x * -Math.sign(v.x);
144 ay += drag * (sleft + sright) * v.y * v.y * -Math.sign(v.y);
145
146 if (!this.up || this.free)
147 ax += CORRECTION * (cleft - cright) * v.y * v.y * Math.sign(v.y);
148 */
149
150 this.body.ApplyForce(new b2Vec2(ax, ay), this.body.position);
151
152 this._updateTransforms();
153 },
154
155 TAPS: ['tick'],
156 });
157
158 function bodyFromAABB (world, position, aabb, density, center) {
159 var dfn = new Box2D.b2BodyDef();
160 var shape = new Box2D.b2PolygonShape();
161 if (center)
162 shape.SetAsBox(aabb.hw, aabb.hh, new b2Vec2(center), 0);
163 else
164 shape.SetAsBox(aabb.hw, aabb.hh);
165 if (density !== undefined)
166 dfn.type = Box2D.DYNAMIC_BODY;
167 dfn.position = new b2Vec2(position[0], position[1]);
168 var body = world.CreateBody(dfn);
169 body.CreateFixture(shape, density || 0.0001);
170 return body;
171 }
172
173 function bodyFromLine (world, p0, p1) {
174 var dfn = new Box2D.b2BodyDef();
175 var shape = new Box2D.b2EdgeShape();
176 shape.Set(new b2Vec2(p0), new b2Vec2(p1));
177 var body = world.CreateBody(dfn);
178 body.CreateFixture(shape, 0);
179 return body;
180 }
181
182 function pinJoint (world, bodyA, bodyB, anchor) {
183 var dfn = new Box2D.b2RevoluteJointDef();
184 dfn.Initialize(bodyA, bodyB, new b2Vec2(anchor));
185 dfn.maxMotorTorque = 100.0;
186 dfn.motorSpeed = 0.1;
187 dfn.enableMotor = true;
188 dfn.enableLimit = true;
189 return Box2D.castObject(world.CreateJoint(dfn), Box2D.b2RevoluteJoint);
190 }
191
192 var GameScene = yT(yuu.Scene, {
193 constructor: function () {
194 yuu.Scene.call(this);
195
196 var zoom = 10;
197 this.layer0.resize(
198 zoom * -1.3333333333/2, zoom * -0.2, zoom * 1.3333333333, zoom * 1);
199
200 var world = new Box2D.b2World(new b2Vec2(0, -5));
201
202 var body, left, right;
203 this.player = new yuu.E(
204 body = new BodyC(bodyFromAABB(
205 world, [0, 5], new yuu.AABB(0.89, 1.0), 1.0)),
206 new yuu.QuadC('@player')
207 .setSize([0.89, 1.0])
208 );
209
210 var leftWing = new yuu.E(
211 left = new BodyC(bodyFromAABB(
212 world, [-0.275, 5.15], new yuu.AABB(0.45, 0.22), 0,
213 [-0.45/2, 0.0])),
214 new yuu.QuadC('@left')
215 .setAnchorAtPosition("right")
216 .setZ(-1)
217 .setSize([0.45, 0.22]));
218 var leftJoint = pinJoint(world, left.body, body.body, [0.1, 5.15]);
219 var rightWing = new yuu.E(right = new BodyC(
220 bodyFromAABB(world, [0.50, 5.15], new yuu.AABB(0.45, 0.22), 0)),
221 new yuu.QuadC('@right')
222 .setZ(-1)
223 .setSize([0.45, 0.22]));
224 var rightJoint = pinJoint(world, right.body, body.body, [0.1, 5.15]);
225 this.player.addChildren(leftWing, rightWing);
226 this.entity0.addChild(this.player);
227 leftJoint.SetLimits(0, Math.PI / 2);
228 rightJoint.SetLimits(-Math.PI / 2, 0);
229
230 var ground = new yuu.E(
231 new BodyC(bodyFromLine(world, [-100, 0], [100, 0])),
232 new yuu.QuadC()
233 .setAnchorAtPosition('top')
234 .setSize([100, 1])
235 .setColor([0, 0.5, 0, 1]));
236 this.entity0.addChild(ground);
237
238 this.player.attach(
239 this.controller = new PlayerController(body, left, right,
240 leftJoint, rightJoint));
241 Object.assign(this.commands, this.controller.commands);
242
243 this.entity0.attach(new yuu.Ticker(function () {
244 world.Step(1/60, 8, 8);
245 return true;
246 }, 1));
247
248 this.ready = yuu.ready([
249 new yuu.Material('@player'),
250 new yuu.Material('@left'),
251 new yuu.Material('@right')]);
252 },
253
254 init: function () {
255 var audio0 = new Audio();
256 audio0.src = audio0.canPlayType('audio/ogg')
257 ? "data/sound/starting-line.ogg"
258 : "data/sound/starting-line.mp3";
259 audio0.autoplay = true;
260 audio0.loop = true;
261 document.body.appendChild(audio0);
262 var source = yuu.audio.createMediaElementSource(audio0);
263 source.connect(yuu.audio.music);
264 },
265
266 KEYBINDS: {
267 space: '+up',
268 up: '+up',
269 q: '+dleftLeft',
270 w: '+dleftRight',
271 o: '+drightLeft',
272 p: '+drightRight',
273 z: '+free',
274 x: '+up',
275 }
276 });
277
278 function start () {
279 yuu.director.start();
280 }
281
282 function load () {
283 storage = ystorage.getStorage();
284 yuu.audio.storage = storage;
285 var game = new GameScene();
286 yuu.director.pushScene(game);
287 return game.ready;
288 }
289
290 window.addEventListener("load", function() {
291 yuu.registerInitHook(load);
292 yuu.init({ backgroundColor: [0, 0, 0, 1], antialias: false })
293 .then(start);
294 });