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