Search code examples
javascripttypescriptjavascript-objectspixi.js

How to extend the PixiJS v7 Container class?


I am trying to port a word game from Pixi.js version 6.5.9 to 7.2.3:

word game screenshot

In the game I use a custom class Tile.js to represent the yellow draggable letter tiles:

'use strict';

function Tile(letter, value) {
    PIXI.Container.call(this); // Uncaught TypeError: Class constructor _Container cannot be invoked without 'new'

    this.col = NaN;
    this.row = NaN;
    this.letter = letter;
    this.value = value;
    
    // is this tile being dragged?
    this.drag = false;

    this.graph = new PIXI.Graphics();
    this.addChild(this.graph);

    this.letterText = new PIXI.Text('', {fontFamily: 'Arial', fontSize: 40, fill: 0x000000});
    this.letterText.anchor.set(0.5);
    this.addChild(this.letterText);

    this.indexText = new PIXI.Text('', {fontFamily: 'Arial', fontSize: 20, fill: 0x000000});
    this.indexText.anchor.set(1);
    this.addChild(this.indexText);

    this.setLetter(letter);
    this.setValue(value);
}

The class extends the PIXI.Container class by using this code:

Tile.prototype = Object.create(PIXI.Container.prototype);
Tile.prototype.constructor = Tile;

And then to implement dragging I have added the methods:

Tile.prototype.setIteractive = function(onDragStart, onDragMove, onDragEnd) {
    this.eventMode = 'static'; // previously: this.interactive = true;
    this.buttonMode = true;

    // it is important to set hitArea, otherwise pointerdown will be called on wrong tiles
    this.hitArea = new PIXI.Rectangle(0, 0, Tile.WIDTH, Tile.HEIGHT);

    this.on('pointerdown', onDragStart)
        .on('pointermove', onDragMove)
        .on('pointerup', onDragEnd);
 };

Tile.prototype.startDragging = function() {
    this.drag = true;
    this.scale.x = 1.4;
    this.scale.y = 1.4;
    this.alpha = 0.9;
};

Tile.prototype.stopDragging = function() {
    this.drag = false;
    this.scale.x = 1;
    this.scale.y = 1;
    this.alpha = 1;
};

After upgrading from Pixi.js version 6.5.9 to 7.2.3 I get the following error in the Edge browser console:

Uncaught TypeError: Class constructor _Container cannot be invoked without 'new'

error message

Has my way of extending the Container class been deprecated?

I have searched at the Github trying to understand, how to extend classes in the PixiJS v7 properly... I have tried to understand how Container extends DisplayObject, but it is very different syntax (maybe because it is typescript?)...

UPDATE:

I have tried the suggestion by @domis86 and have rewritten my custom Tile.js class to have a PIXI.Container member:

function Tile(letter, value) {
    var _col = NaN;
    var _row = NaN;
    var _letter = letter;
    var _value = value;
    
    // is this tile being dragged?
    var _drag = false;
    
    var _cont = new PIXI.Container();
    _cont.eventMode = 'static';
    _cont.buttonMode = true;
    // the following line does not work, is undefined
    _cont.tile = this;
    
    var _graph = new PIXI.Graphics();
    _cont.addChild(_graph);

    var _letterText = new PIXI.Text('', {fontFamily: 'Arial', fontSize: 40, fill: 0x000000});
    _letterText.anchor.set(0.5);
    _cont.addChild(_letterText);

    var _indexText = new PIXI.Text('', {fontFamily: 'Arial', fontSize: 20, fill: 0x000000});
    _indexText.anchor.set(1);
    _cont.addChild(_indexText);

    setLetter(letter);
    setValue(value);
    
    function getContainer() {
        return _cont;
    }

    function setIteractive(onDragStart, onDragMove, onDragEnd) {
        _cont.eventMode = 'static';
        _cont.buttonMode = true;
        setDrawSides(true, true, true, true);

        // it is important to set hitArea, otherwise pointerdown will be called on wrong tiles
        _cont.hitArea = new PIXI.Rectangle(0, 0, WIDTH, HEIGHT);

        _cont.on('pointerdown', onDragStart)
            .on('pointermove', onDragMove)
            .on('pointerup', onDragEnd);
     }

    function startDragging() {
        _drag = true;
        _cont.scale.x = 1.4;
        _cont.scale.y = 1.4;
        _cont.alpha = 0.9;
        redraw();
    }

    function stopDragging() {
        _drag = false;
        _cont.scale.x = 1;
        _cont.scale.y = 1;
        _cont.alpha = 1;
        redraw();
    }

    ...more methods here...

    // return the public methods
    return {
        getContainer: getContainer,
        setIteractive: setIteractive,
        startDragging: startDragging,
        stopDragging: stopDragging,
        ...
        redraw: redraw
    };
}

The problem is, when a Tile instance is being dragged, then the pointerdown and other events are now called on the _cont and not on the Tile instance itself - and then I do not know how to access the additional properties (like _letter, _value, _col, _row) of the letter tile:

function onDragStart(ev) {
    this.data = ev.data;
    var pos = this.data.getLocalPosition(this.parent);
    this.x = pos.x - this.width / 2;
    this.y = pos.y - this.height / 2;
    // put the tile on top
    app.stage.removeChild(this);
    app.stage.addChild(this);
    // the following line gives error because PIXI.Container does not have the method
    this.startDragging();
    // here I only have access to the _cont through "this"
    // but how to access the _letter, _value of the tile?
}

I have tried adding a .tile property to the _cont by:

_cont.tile = this;

but it gives me undefined when I try to access this.tile in onDragStart

UPDATE 2:

The PixiJS Elementals tutorial mentions that there is a way to pass a custom object (which would be the Tile instance in my case) to mouse events:

The basic anatomy of adding an event listener to an imput is: yourObject.on("stringOfWhatYouWantToKnow", functionToBeCalled, contextForTheFunction)

but how to add it to my code, since this seems to be undefined and how to read it in the onStartDrag()etc.?


Solution

  • I prepared jsfiddle :

    https://jsfiddle.net/k1qr5m8g/

    ( is modified example from user bigtimebuddy from question "what's suggested way of implementing dragging in v7?" - https://github.com/pixijs/pixijs/discussions/8813#discussioncomment-4038065 )

    Please run it and observe the browser console. You should see something like this after clicking on rectangle (sprite):

    called Tile.getMyValue() - value: 123
    dumping "this":
    Object { iAmTile: true }
    

    The PixiJS Elementals tutorial mentions that there is a way to pass a custom object (which would be the Tile instance in my case) to mouse events:

    Notice this code fragment:

    // Here we are passing the "that" (aka: original "this" of Tile object instance) as third argument.
    // It will cause "this" inside getMyValue function to point to the Tile instance.
    sprite.on('pointerdown', getMyValue, that);
    

    Btw 1: I would recommend to convert your code to ES6 class - so as is done in https://www.pixijselementals.com/#getting-interactive

    Btw 2: in your code do you create instances of Tile objects using "new" keyword? See at end of jsfiddle:

    var tileInstance = new Tile('A', 123, app);
    tileInstance.initializeInteractiveListeners();