1"use strict";
2
3const whl = require("whale-core");
4const pyxel = require("whale-pyxel");
5const mainLoop = require("whale/std/main_loop.js");
6
7
8function clamp(x, min, max)
9{
10 return Math.min(max, Math.max(min, x));
11}
12
13
14function random(min, max)
15{
16 return min + Math.random() * (max - min);
17}
18
19
20function randomRange(vec)
21{
22 return random(vec.x, vec.y);
23}
24
25
26function midVec(vec)
27{
28 return (vec.x + vec.y) / 2;
29}
30
31
32var game = {
33 init: function()
34 {
35 this.windowSize = whl.Vec2(800, 600);
36 this.window = whl.Window(
37 "WhaleJS", whl.IVec2(100, 100), whl.IVec2(this.windowSize),
38 whl.Window.Options.OPENGL | whl.Window.Options.SHOWN
39 );
40 this.gfx = whl.RenderContext(this.window);
41 this.canvas = whl.Canvas(this.gfx, this.windowSize);
42
43 this.spriteSheet = whl.SpriteSheet(
44 whl.fs.openFile("atlas.json").read(),
45 whl.Texture(whl.loadPNG(whl.fs.openFile("atlas.png").read()))
46 );
47 this.backgroundSprite = this.spriteSheet.sprite("background");
48 this.logoSprite = this.spriteSheet.sprite("logo");
49
50 this.whaleDoc = pyxel.PyxelDoc(whl.fs.openFile("whale.pyxel").read());
51 this.bubbleSprite = this.whaleDoc.animation("bubble").frameAt(0).sprite.clone();
52
53 this.whalePos = whl.Vec2(600, 150);
54 this.whaleScale = 8;
55 this.whaleScaleRange = whl.Vec2(3, 15);
56 this.whaleSpeed = -20;
57 this.bubbleSpeed = -5;
58 this.whaleAnim = "swim2";
59 this.offscreenFactor = 2;
60 this.offscreenDelay = Infinity;
61
62 this.bubbleBurstDelayRange = whl.Vec2(2, 5);
63 this.bubbleBurstCountRange = whl.Vec2(2, 4);
64 this.bubbleDelayRange = whl.Vec2(0.15, 0.5);
65 this.bubbles = [];
66 this.scheduleNextBubbleBurst();
67 this.timeToBubbleBurst = 1; // Force first burst early.
68 },
69
70 draw: function()
71 {
72 // Get total time and time since last frame.
73 var time = whl.ticks() / 1000;
74 if (!this.hasOwnProperty("lastTime"))
75 this.lastTime = time;
76 var dTime = time - this.lastTime;
77 this.lastTime = time;
78
79 // Draw background.
80 this.canvas.draw(this.backgroundSprite);
81
82 // Draw whale (if small).
83 if (this.whaleScale < midVec(this.whaleScaleRange))
84 this.drawWhale(time, dTime);
85
86 // Draw bubbles (if small).
87 var bubbleCutoffY = -this.bubbleSprite.size.y * 0.5;
88 this.bubbles = this.bubbles.filter(
89 function(bubble) { return bubble.pos.y > bubbleCutoffY * bubble.scale; }
90 );
91
92 this.timeToBubbleBurst -= dTime;
93 if (this.timeToBubbleBurst < 0)
94 {
95 this.timeToBubble -= dTime;
96 if (this.bubbleBurstCount > 0 && this.timeToBubble <= 0)
97 {
98 this.bubbles.push({
99 pos: this.whalePos.add(whl.Vec2(this.whaleSpeed < 0 ? -8 : 6, -7).mul(this.whaleScale)),
100 scale: random(this.whaleScale * 0.5, this.whaleScale)
101 });
102 this.timeToBubble = randomRange(this.bubbleDelayRange);
103 --this.bubbleBurstCount;
104 }
105 if (this.bubbleBurstCount <= 0)
106 this.scheduleNextBubbleBurst();
107 }
108
109 this.drawBubbles(time, dTime, true);
110
111 // Draw logo.
112 var logoPos = this.windowSize.sub(this.logoSprite.size).div(2);
113 logoPos.y += 25 * Math.sin(time);
114 this.logoSprite.transform = whl.translate(logoPos);
115 this.canvas.draw(this.logoSprite);
116
117 // Draw whale and bubbles (if large).
118 if (this.whaleScale >= midVec(this.whaleScaleRange))
119 this.drawWhale(time, dTime);
120 this.drawBubbles(time, dTime, false);
121
122 this.window.redraw();
123
124 Duktape.gc();
125 },
126
127 scheduleNextBubbleBurst: function()
128 {
129 this.timeToBubbleBurst = randomRange(this.bubbleBurstDelayRange);
130 this.bubbleBurstCount = randomRange(this.bubbleBurstCountRange);
131 this.timeToBubble = 0;
132 },
133
134 drawWhale: function(time, dTime)
135 {
136 var whale = this.whaleDoc.animation(this.whaleAnim).frameAt(time).sprite.clone();
137 this.offscreenDelay += dTime;
138 if (this.offscreenDelay > 2)
139 this.whalePos.x += this.whaleSpeed * this.whaleScale * dTime;
140 var whaleHalfSize = whale.size.mul(0.5 * this.whaleScale);
141 var preClampX = this.whalePos.x;
142 this.whalePos.x = clamp(this.whalePos.x, -whaleHalfSize.x, this.windowSize.x + whaleHalfSize.x);
143 if (this.whalePos.x != preClampX)
144 {
145 this.whaleScale = randomRange(this.whaleScaleRange);
146 whaleHalfSize = whale.size.mul(0.5 * this.whaleScale);
147 this.whaleSpeed = -this.whaleSpeed;
148 this.whalePos.x = this.whaleSpeed > 0 ? -whaleHalfSize.x : this.windowSize.x + whaleHalfSize.x;
149 this.whalePos.y = random(whaleHalfSize.y, this.windowSize.y - whaleHalfSize.y);
150 this.offscreenDelay = 0;
151 }
152 whale.transform =
153 whl.translate(this.whalePos).mul(
154 whl.scale(whl.Vec2(this.whaleScale * -Math.sign(this.whaleSpeed), this.whaleScale)).mul(
155 whl.translate(whale.size.mul(-0.5))));
156 this.canvas.draw(whale);
157 },
158
159 drawBubbles: function(time, dTime, drawSmall)
160 {
161 for (var idx in this.bubbles)
162 {
163 var bubble = this.bubbles[idx];
164 var midScale = midVec(this.whaleScaleRange);
165 if ((drawSmall && bubble.scale >= midScale) ||
166 (!drawSmall && bubble.scale < midScale))
167 {
168 continue;
169 }
170 bubble.pos.y += this.bubbleSpeed * bubble.scale * dTime;
171 var xOffset = whl.Vec2((2 * bubble.scale) * Math.sin(bubble.pos.y / 25), 0);
172 this.bubbleSprite.transform = whl.translate(bubble.pos.add(xOffset)).mul(
173 whl.scale(whl.Vec2(bubble.scale, bubble.scale)).mul(
174 whl.translate(this.bubbleSprite.size.mul(-0.5))));
175 this.canvas.draw(this.bubbleSprite);
176 }
177 }
178};
179
180
181function loop()
182{
183 mainLoop.run(game);
184}