10
\$\begingroup\$

This is a follow up for the 2D Gravity Simulator-like Game question.

Lots of circles colliding and merging into bigger circles

Since then I have developed my javascript skills further, and now I'm using ES6 mainly. Note that even through this code uses the EaselJS library, the question is principally about its design so no EaselJS skill needed.

Also I wanna avoid posting a bigger version of the bible here, so this question is only about the game's Planet class and its properties.

The code is more well divided, but I don't know if this organization is effective, and how can changes in a planet's .physicsBody affect its graphical .skin – colors specially. Any tips and ratings on how I'm using class inheritance are really appreciated.

Also, is there any part that could use a more ES6-ish approach?

Planet class:

class Planet {
    constructor (x, y, radius, color) {
        this.physicsBody = new CircleBody(x, y, radius);
        this.skin = new CircleSkin(x, y, radius, color);
    }
    update (scene) {
        // This part is responsible for changing a planet's radius and animating the transition. I have no idea if this should in fact be made here or elsewhere.
        if (this.physicsBody.radius !== this.skin.radius) {
            this.skin.scaleX = this.skin.scaleY = this.skin.radius / this.physicsBody.radius;
            this.skin.radius = this.physicsBody.radius;
            this.skin.redraw();
            createjs.Tween.get(this.skin, {override: true}).to({scaleX: 1, scaleY: 1}, 1000, createjs.Ease.elasticOut);
        }
        if (!this.physicsBody.destroyed) {
            this.skin.x = this.physicsBody.x;
            this.skin.y = this.physicsBody.y;
        } else scene.removeChild(this);
    }
}

CircleBody class:

class CircleBody extends PhysicsBody {
    constructor (x, y, radius) {
        super(x, y);
        this.radius = radius;
    }
    get area () {
        return this._area;
    }
    set area (newValue) {
        this._area = newValue;
        this._radius = Math.sqrt(newValue / Math.PI / 4);
    }
    get radius () {
        return this._radius;
    }
    set radius (newValue) {
        this._radius = newValue;
        this._area = 4 * Math.PI * newValue * newValue;
    }
    get mass () {
        return this.area * this.density;
    }
}

PhysicsBody class:

class PhysicsBody {
    constructor (x, y) {
        this.x = x;
        this.y = y;

        this.px = x;
        this.py = y;

        this.vx = 0;
        this.vy = 0;

        this.density = 1;

        this.affectedByGravity = true;
        this.static = false;
        this.collision = null;
        this.destroyed = false;
    }
    get dynamic () {
        return !this.static;
    }
    set dynamic (newValue) {
        this.static = !newValue;
    }
    get velocity () {
        return new Vector(this.vx, this.vy);
    }
    clearCollisions () {
        this.collision = null;
    }
}

CircleSkin class:

class CircleSkin extends Skin {
    constructor (x, y, radius, color) {
        super(x, y, color);
        this.radius = radius;
        this.redraw();
    }
    redraw (resolution) {
        var r = this.radius;
        this.graphics.clear();
        this.graphics.f(this.color).dc(0, 0, r);
        //this.cache(-r, -r, r * 2, r * 2, resolution || 1);
    }
}

Skin class:

class Skin extends createjs.Shape {
    constructor (x, y, color) {
        super();
        this.x = x;
        this.y = y;
        this.color = color;
    }
}

And finally, this is the class usage in engine:

for (var i = 0; i < 1024; ++i) {
    var x = Utils.getRandomInt(0, innerWidth),
        y = Utils.getRandomInt(0, innerHeight);
    var myPlanet = new Planet(x, y, 2, Utils.getRandomItem(colors));
    myPlanet.physicsBody.n = i;
    myScene.addChild(myPlanet);
}

Utils and Vector dependencies are out of scope and I think their method names are pretty clear so no declaration needed here.

\$\endgroup\$
  • \$\begingroup\$ Ooh, this actually looks quite fun and beautiful. Are you planning to publish it? \$\endgroup\$ – noɥʇʎԀʎzɐɹƆ Jun 6 '16 at 20:42
  • \$\begingroup\$ @CrazyPython Thanks mate! I don't know if I'm going to publish it since it doesn't look like a game or anything useful in any way, so while I can't find any objective I'll keep it on github only: github.com/tiagomarinho/stardust-2 \$\endgroup\$ – Tiago Marinho Jun 7 '16 at 1:21
  • 1
    \$\begingroup\$ Just wanted to say that your project is pretty cool and the animation is very smooth. :D \$\endgroup\$ – Confettimaker Jun 24 '16 at 20:12
  • \$\begingroup\$ No time for proper answer: why not use vectors internally? \$\endgroup\$ – I'll add comments tomorrow Jul 26 '16 at 16:57
2
\$\begingroup\$

Btw I like the idea of the game/demo!

It would be nice to see a case where a binary star emerges.

Back to code review:

In Class CicleBody

 get mass () {
         return this.area * this.density;
 }

the this.area - did you intentianally wanted to hit the getter of area or perhaps will be better to use the internal this._area?

PhysicsBody class:

As naming goes, the usual convention is to name functions so they reflect what's going on in the code. In that context, I would have that as a function createVelocity() to indicate that in fact you're creating an object.

 get velocity () { // better name: createVelocityVector()
     return new Vector(this.vx, this.vy);
 }

That is important for the performance conscious programmers cause every extra new object or function call might have impact on the performance.

CircleSkin class:

class CircleSkin extends Skin {
    constructor (x, y, radius, color) {
        super(x, y, color);
        this.radius = radius;
        this.redraw(); // <------?!!
    }

It is not usual to have canvas work inside of a object constructor. Normally you'd have the draw logic invoked by the render timer. Normally as the JS games are implemented with two continuous timers:

1) core timer - this timer runs the game logic.

2) render timer - this timer runs every ~300 ms, reads the some shared data structure, updated by the core timer, and renders it to the canvas. This way you guarantee 30 fps and your game feels smooth and responsive.

redraw (resolution) {
        var r = this.radius;
        this.graphics.clear();
        this.graphics.f(this.color).dc(0, 0, r);
        //this.cache(-r, -r, r * 2, r * 2, resolution || 1);
    }
}

From the alone is not clear what are resolution and graphics.

graphics feels like enhanced canvas.context2d, if yes, in my opinion is better to pass it as parameter. This way you can easily reuse the code with different graphic context.

My version of that method would look something along the lines:

render(graphics) {
  var r = this.radius;
  graphics.clear();
  graphics.f(this.color).dc(0, 0, r);
}

You mentioned you are using EasyJS .. I am not familiar with that library but it cannot be too different than the above mentioned structure. Perhaps the library is implementing the timers for you and is providing you with this.variable for a canvas like object.

That's okay, however, I would recommend you to open the hood and read the code of the library - you will need to understand in order to be able to write code with good performance.

I wrote a review for a similar question, have a look : https://codereview.stackexchange.com/a/135326/111698. Hope it helps you.

-- side note --

Ha-Ha on Utils.getRandomInt(0, innerWidth) I think everyone has implemented a version of that in his code. Perhaps, they could add that to the Math lib :)

\$\endgroup\$
  • \$\begingroup\$ I agree with every point made, but can you improve on invoking the draw logic by the render timer? It's not really clear to me what a render timer is since all my logic is running on a single unified fashion, where EaselJS takes care of the graphics. While passing graphics instructions as a parameter might be nice, what would the Planet.update() method implementation look like? Math.randomIntInRange(min, max) would be awesome. \$\endgroup\$ – Tiago Marinho Jul 25 '16 at 3:29
1
\$\begingroup\$

Not sure if this really helps, but instead of using .scaleX and .scaleY, couldn't you just use .scale? I don't think I saw any size differences between .scaleX and .scaleY.

\$\endgroup\$
  • \$\begingroup\$ scaleX and scaleY are properties inherit from EaselJS's DisplayObj class. Sure I could make a setter method for modifying both at the same time, but I think there's still a lot to be improved before diving into such minor modifications. Anyway, thank you, I know it's hard to get into that wall of code and any help is heavily appreciated. ;) \$\endgroup\$ – Tiago Marinho Jun 24 '16 at 0:31
  • \$\begingroup\$ More about EaselJS and DisplayObj here: createjs.com/docs/easeljs/classes/DisplayObject.html \$\endgroup\$ – Tiago Marinho Jun 24 '16 at 0:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.