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