Demo

Source code

  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}