# Wrapping preload(), setup() and draw() in an ES6 class

**URL:** <https://discourse.processing.org/t/wrapping-preload-setup-and-draw-in-an-es6-class/13071>\
**Category:** Libraries\
**Created:** [July 30, 2019, 8:41pm UTC](https://discourse.processing.org/t/wrapping-preload-setup-and-draw-in-an-es6-class/13071 "2019-07-30T20:41:30Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![kenny83](https://avatars.discourse-cdn.com/v4/letter/k/c67d28/32.png) [@kenny83](https://discourse.processing.org/u/kenny83)\
**Post date:** [July 30, 2019, 8:41pm UTC](https://discourse.processing.org/t/wrapping-preload-setup-and-draw-in-an-es6-class/13071/1 "2019-07-30T20:41:30Z")

</div>

Hi all,

Apologies if this has been discussed before; I did a quick search across Google and this forum but didn’t find much. Please point me in the right direction if this **is** a duplicate question 🙂

So what I’m trying to do is create a clone of the classic Super Mario Bros. game on the Nintendo Entertainment System, using [p5.play.js](http://molleindustria.github.io/p5.play).

In the interest of adhering to proper OO design principles, I’d like to have the following ES6 class as the main “controller” of my game:

```
class SuperMarioJS {
    constructor() {
        this.currentLevel = new Level('world 1.1');
        this.mario = new Mario(); // 'Mario' class extends p5.play.Sprite

        // other initialisation code...
    }

    static get GRAVITY() { return 1.0; }

    // other global constants...

    setup() {
        createCanvas(this.currentLevel.width, windowHeight);
        // other setup code...
    }

    draw() {
        this.mario.move();
        drawSprites();
    }
}

```

And then the only global code attached to the `window` object would be:

```
var gameController = new SuperMarioJS();
function setup() { gameController.setup(); }
function draw() { gameController.draw(); }

```

But this doesn’t work. I don’t get any errors or anything logged to the browser console, but I simply end up with a black screen and nothing else!

So what would be the best way to approach this? MTIA! 😃

---

<div class="post-metadata">

**Author:** ![kenny83](https://avatars.discourse-cdn.com/v4/letter/k/c67d28/32.png) [@kenny83](https://discourse.processing.org/u/kenny83)\
**Post date:** [July 30, 2019, 10:27pm UTC](https://discourse.processing.org/t/wrapping-preload-setup-and-draw-in-an-es6-class/13071/2 "2019-07-30T22:27:19Z")

</div>

UPDATE: I just discovered [this wiki post about p5 global and instance modes](https://github.com/processing/p5.js/wiki/Global-and-instance-mode) and have updated my code thusly:

```
class SuperMarioJS {
    static get GRAVITY() { return 1.0; }
    static get SPACEBAR() { return 32; }

    constructor() {
        this.level = new p5(l => {
            l.preload = () => {
                l.bgImage = l.loadImage('assets/maps/1-1.png');
                this.scale = l.windowHeight / l.bgImage.height;

                this.mario = new Mario(l, 48, l.bgImage.height - 40, this.scale);
                this.mario.loadAnimations('mario');

                // blockAnims.push({ 'name': 'item-idle', 'elem': loadAnimation(
                // loadSpriteSheet('assets/sprites/items/items.png', itemFrames))});
            };

            l.setup = () => {
                l.createCanvas(this.scale * l.bgImage.width, l.windowHeight);

                // ground = new Group();
                // ground.add(new ES6Sprite(width / 2, bgRatio *
                // (bgImage.height - 31), 1, width, 1));
                // ground.add(new Block(264, 152, bgRatio, ITEM_BLOCK, null));
            };

            l.draw = () => {
                l.image(l.bgImage, 0, 0, l.width, l.height);
                //mario.checkCollisions(ground);
                l.drawSprites();
            };

            l.keyPressed = () => {
                this.mario.onKeyPressed();
            }

            l.keyReleased = () => {
                this.mario.onKeyReleased();
            }
        });
    }
}

let gameController = new SuperMarioJS();

```

But now I get `Error: "createVector" is not a p5 method` in the browser console, and the same black screen as before. The problem probably lies in the way I’m extending `p5.play.Sprite`:

```
class Mario extends p5.prototype.Sprite {
    constructor(l, x, y, s, w = 16 * s, h = 16 * s) {
        x *= s;
        y *= s;

        super(l, x, y, w, h);
        // other class code...
    }
}

```

Which was working in global mode, although the `l` constructor parameter was unnecessary then. In fact, it took me ages to realise that even though `p5.play` relies on a `pInst` variable that references the p5 instance, it actually **expects** that variable to be set to the window object, which it does itself. And because of that, you actually **need** to omit the `pInst` parameter from the `Sprite` constructor!

Now that I understand the difference between global and instance mode, this all finally makes sense…except that I want to run in instance mode, and it still seems to be expecting global mode!

After searching for a solution to this perplexing problem on Google, I came across [this GitHub comment](https://github.com/molleindustria/p5.play/issues/12#issuecomment-480091063) posted by @GoToLoop. I tried his/her example code and it works fine, so what the heck is wrong with mine?!

Thanks again for any help that you guys may be able to provide 😃

---

<div class="post-metadata">

**Author:** ![GoToLoop](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/gotoloop/32/86_2.png) [@GoToLoop](https://discourse.processing.org/u/GoToLoop)\
**Post date:** [July 31, 2019, 2:52am UTC](https://discourse.processing.org/t/wrapping-preload-setup-and-draw-in-an-es6-class/13071/3 "2019-07-31T02:52:10Z")

</div>

> [@kenny83](#):
>
> The problem probably lies in the way I’m extending p5.play’s Sprite:

In my original sketch “p5.Play Instance Mode Example”, I rely on p5::**createSprite()** in order to instantiate class Sprite:

`sprite = p.createSprite(p.width>>1, p.height>>1, 0o100, 0o100);`

Obviously, it returns an already instantiated Sprite object:  
[https://github.com/molleindustria/p5.play/blob/master/lib/p5.play.js#L179-L184](https://github.com/molleindustria/p5.play/blob/master/lib/p5.play.js#L179-L184)

But you wanna create a subclass which `extends` it instead:

> [@kenny83](#):
>
> `class Mario extends p5.prototype.Sprite {`

However, p5.prototype.Sprite is an extremely hacked version of the original constructor Sprite:  
[https://github.com/molleindustria/p5.play/blob/master/lib/p5.play.js#L2439-L2440](https://github.com/molleindustria/p5.play/blob/master/lib/p5.play.js#L2439-L2440)  
[https://github.com/molleindustria/p5.play/blob/master/lib/p5.play.js#L86-L105](https://github.com/molleindustria/p5.play/blob/master/lib/p5.play.js#L86-L105)  
[https://github.com/molleindustria/p5.play/blob/master/lib/p5.play.js#L68-L84](https://github.com/molleindustria/p5.play/blob/master/lib/p5.play.js#L68-L84)

And we can’t directly get the original Sprite constructor’s reference b/c it’s inside a factory function:  
[https://github.com/molleindustria/p5.play/blob/master/lib/p5.play.js#L7-L14](https://github.com/molleindustria/p5.play/blob/master/lib/p5.play.js#L7-L14)  
[https://github.com/molleindustria/p5.play/blob/master/lib/p5.play.js#L744-L2437](https://github.com/molleindustria/p5.play/blob/master/lib/p5.play.js#L744-L2437)

But after analyzing the internal function **boundConstructorFactory()**, I’ve spotted a glimmer of hope in this statement:  
[https://github.com/molleindustria/p5.play/blob/master/lib/p5.play.js#L101-L103](https://github.com/molleindustria/p5.play/blob/master/lib/p5.play.js#L101-L103)

The p5.prototype.Sprite that you attempt to inherit from is actually constructor F from **boundConstructorFactory()**.

And F’s _prototype{}_ holds the original _constructor.prototype{}_'s reference.

And in this case, _constructor_ refers to Sprite.

B/c that _constructor.prototype{}_ object was originally created by the constructor Sprite, its property **constructor** references back to Sprite:

> **[Object.prototype.constructor - JavaScript | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor)**
>
> The constructor data property of an Object instance returns a reference to the constructor function that created the instance object. Note that the value of this property is a reference to the function itself, not a string containing the function's...

So, rather than just `class Mario extends p5.prototype.Sprite {`, which points to a screwed up Sprite constructor, we go w/ `class Mario extends p5.prototype.Sprite.prototype.constructor {`, which points to the original Sprite:

```javascript
// class Mario extends p5.prototype.Sprite {
class Mario extends p5.prototype.Sprite.prototype.constructor {
  static get SC() {
    return 16;
  }

  constructor(p, x, y, s = 1, w = Mario.SC * s, h = Mario.SC * s) {
    super(p, x, y, w, h);
    this.depth = p.allSprites.maxDepth() + 1;
    p.allSprites.add(this);
  }
}

```

Notice that I’ve also added code from method p5::**createSprite()** inside your fixed subclass Mario’s **constructor()** after invoking **super()**.

---

<div class="post-metadata">

**Author:** ![kenny83](https://avatars.discourse-cdn.com/v4/letter/k/c67d28/32.png) [@kenny83](https://discourse.processing.org/u/kenny83)\
**Post date:** [July 31, 2019, 3:48am UTC](https://discourse.processing.org/t/wrapping-preload-setup-and-draw-in-an-es6-class/13071/4 "2019-07-31T03:48:03Z")

</div>

> [@GoToLoop](#):
>
> Notice that I’ve also added code from method p5:: **createSprite()** inside your fixed subclass Mario’s **constructor()** after invoking **super()**.

Yeah I’ve been doing that too, just didn’t show that part of the code. But thank you so much for such a lengthy and detailed explanation of why `boundConstructorFactory()` and the hacked prototype constructor were causing me grief!

Your version now works perfectly 😃 The only thing left to do now is extend the p5 instance into a `Level` class! 😛 Any idea how I could do that, using ES6 class syntax? I’ve tried the following:

```
class Level extends p5 {
    constructor(levelNum) {
        super(() => {
            this.preload = loadLevel;
        });
    }

    loadLevel() {
        // preload() code here...
    }
}

```

But it doesn’t work ☹ I also tried extending from `p5.prototype.constructor` instead of just `p5`, since that was the solution to my first problem, but in both cases I get `ReferenceError: Level is not defined`.

Anyway thanks again for the super detailed reply to my initial question 😃

---

<div class="post-metadata">

**Author:** ![GoToLoop](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/gotoloop/32/86_2.png) [@GoToLoop](https://discourse.processing.org/u/GoToLoop)\
**Post date:** [July 31, 2019, 4:29pm UTC](https://discourse.processing.org/t/wrapping-preload-setup-and-draw-in-an-es6-class/13071/5 "2019-07-31T16:29:29Z")

</div>

> [@kenny83](#):
>
> I also tried extending from `p5.prototype.constructor` instead of just `p5` , since that was the solution to my first problem,

Class p5 is the original _constructor_ already. 😛  
It’s not been hacked like the reference stored in p5.prototype.Sprite. 👾  
Here’s a very simple “index.html” code proving so: 🤓

* * *

## index.html:

```auto
<script src=https://cdn.JsDelivr.net/npm/p5></script>
<script src=https://MolleIndustria.GitHub.io/p5.play/lib/p5.play.js></script>

<script>
  console.log('p5 constructor', p5 === p5.prototype.constructor);
  const Sprite = p5.prototype.Sprite;
  console.log('Sprite constructor', Sprite === Sprite.prototype.constructor);
</script>

```

* * *

> [@kenny83](#):
>
> The only thing left to do now is extend the p5 instance into a Level `class`! 😛  
> Any idea how I could do that using ES6 `class` syntax?

> [@How i extends class in p5.js?](https://discourse.processing.org/t/how-i-extends-class-in-p5-js/894/5):
>
> Yes, it is possible. But in order to use an extended p5 subclass, you’re gonna need to write your code using the dreadful “instance mode” approach: roll_eyes[https://GitHub.com/processing/p5.js/wiki/Global-and-instance-mode](https://GitHub.com/processing/p5.js/wiki/Global-and-instance-mode) “index.html”: \<script defer src=https://CDN.JsDelivr.net/npm/p5\>\</script\> \<script defer src=sketch.js\>\</script\> “sketch.js”: /\*\* \* Class p5 Extended (v1.0.1) \* GoToLoop (2018-Jun-13) \* https://Discourse.Processing.org/t/how-i-extends-class-in-p5-js/894/5 \*/ "use st…

> [@kenny83](#):
>
> But it doesn’t work! ☹

Here’s my attempt on subclassing p5, based on my previous “Class p5 Extended” sketch: 🕶

* * *

## index.html:

```auto
<script defer src=https://cdn.JsDelivr.net/npm/p5></script>
<script defer src=sketch.js></script>

```

* * *

## sketch.js:

```auto
/**
 * Class p5 Extended II (v1.1.1)
 * GoToLoop (2019-Jul-31)
 *
 * https://Discourse.Processing.org/t/
 * wrapping-preload-setup-and-draw-in-an-es6-class/13071/5
 *
 * https://Discourse.Processing.org/t/how-i-extends-class-in-p5-js/894/5
 *
 * https://CodePen.io/GoSubRoutine/pen/voJozR/left?editors=0011
 */

'use strict';

class Level extends p5 {
  static get FILENAME() {
    return 'https://upload.Wikimedia.org/wikipedia/commons/thumb/2/2e/' +
           'Processing_3_logo.png/480px-Processing_3_logo.png';
  }

  static redirectP5Callbacks(p) {
    p.preload = p.loadLevel.bind(p); // preload() callback has to be bind()
    p.setup = p.initLevel; // but callback setup() doesn't need bind()
    p.draw = p.runLevel.bind(p); // draw() has to be bind() like preload()
    p.mousePressed = p.mouseDragged = p.restartLevel; // optional for mouse
    p.touchStarted = p.touchMoved = p.restartLevel; // optional for touch
    p.keyPressed = p.keyReleased = p.restartLevel.bind(p); // required for key
  }

  constructor(levelNum) {
    super(Level.redirectP5Callbacks);
    this.levelNum = levelNum;
  }

  loadLevel() {
    this.img = this.loadImage(Level.FILENAME);
  }

  initLevel() {
    this.createCanvas(this.img.width, this.img.height);
    this.noLoop();

    this.rectMode(this.CORNER).ellipseMode(this.CENTER);
    this.colorMode(this.RGB).blendMode(this.BLEND);
    this.fill('yellow').stroke(0).strokeWeight(1.5).strokeCap(this.ROUND);
  }

  runLevel() {
    this.background('#' + this.hex(~~this.random(0x1000), 3));
    this.set(0, 0, this.img);
  }

  restartLevel() {
    this.redraw();
    return false;
  }

  // When bind() isn't needed, we can directly override the callback method:
  windowResized() {
    this.print('LevelNum: ' + this.levelNum);
    return false;
  }
}

// const levels = [new Level(0), new Level(1)];
const levels = Array.from({ length: 2 }, (v, i) => new Level(i));

```

* * *

BtW, I’ve hacked the callbacks w/ Function::**bind()**: 🤖

> **[Function.prototype.bind() - JavaScript | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind)**
>
> The bind() method of Function instances creates a new function that, when called, calls this function with its this keyword set to the provided value, and a given sequence of arguments preceding any provided when the new function is called.

---

<div class="post-metadata">

**Author:** ![kenny83](https://avatars.discourse-cdn.com/v4/letter/k/c67d28/32.png) [@kenny83](https://discourse.processing.org/u/kenny83)\
**Post date:** [August 1, 2019, 6:53am UTC](https://discourse.processing.org/t/wrapping-preload-setup-and-draw-in-an-es6-class/13071/6 "2019-08-01T06:53:51Z")

</div>

Wow I can’t believe that I was sooooo close to figuring it out myself! Thank you so much @GoToLoop!!

---

<div class="post-metadata">

**Author:** ![GoToLoop](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/gotoloop/32/86_2.png) [@GoToLoop](https://discourse.processing.org/u/GoToLoop)\
**Post date:** [August 2, 2019, 5:25pm UTC](https://discourse.processing.org/t/wrapping-preload-setup-and-draw-in-an-es6-class/13071/7 "2019-08-02T17:25:34Z")

</div>

For completeness’ sake, just letting you know that not all p5 callbacks are required to use **bind()** in order to have the correct `this` inside their `function` body.

For example, neither **setup()** nor mouse and touch related callbacks, even **windowResized()**, need **bind()** at all.

However, **preload()**, **draw()** and key related callbacks all have a wrong `this` and require **bind()**.

For those callbacks w/ an already correct `this`, they can be directly overridden inside the subclass.

For example, we could delete `p.setup = p.initLevel;` and have method **initLevel()** renamed as **setup()** if we wanted to.

Here’s an example I have which goes w/ the p5js instance mode, but uses `this` instead of parameter _p_:

> <https://github.com/GoSubRoutine/Ball-in-the-Chamber/blob/v3.0.0/p5js-instance/sketch/sketch.js#L40-L55>

Notice above I had to invoke `}.bind(p);` right after the block `function () {` in order to assign it to `p.draw` so the keyword `this` would correctly refer to the sketch’s p5 current instance; but no **bind()** was needed for `p.setup`.

BtW, besides `p.draw = p.runLevel.bind(p);` syntax, we coulda used `p.draw = Level.prototype.runLevel.bind(p);` and even `p.draw = p. __proto__.runLevel.bind(p);` instead.

Also, I’ve posted the new “Class p5 Extended II” version on [CodePen.io](http://CodePen.io), so we can run it online too:

[https://CodePen.io/GoSubRoutine/pen/voJozR/left?editors=0011](https://CodePen.io/GoSubRoutine/pen/voJozR/left?editors=0011)

---

<div class="post-metadata">

**Author:** ![kenny83](https://avatars.discourse-cdn.com/v4/letter/k/c67d28/32.png) [@kenny83](https://discourse.processing.org/u/kenny83)\
**Post date:** [August 3, 2019, 12:42am UTC](https://discourse.processing.org/t/wrapping-preload-setup-and-draw-in-an-es6-class/13071/8 "2019-08-03T00:42:18Z")

</div>

Awesome good to know! Thanks again 😃
