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\$
4
  • \$\begingroup\$ Ooh, this actually looks quite fun and beautiful. Are you planning to publish it? \$\endgroup\$ Jun 6, 2016 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\$ Jun 7, 2016 at 1:21
  • 1
    \$\begingroup\$ Just wanted to say that your project is pretty cool and the animation is very smooth. :D \$\endgroup\$ Jun 24, 2016 at 20:12
  • \$\begingroup\$ No time for proper answer: why not use vectors internally? \$\endgroup\$ Jul 26, 2016 at 16:57

2 Answers 2

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\$
1
  • \$\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\$ Jul 25, 2016 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\$
2
  • \$\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\$ Jun 24, 2016 at 0:31
  • \$\begingroup\$ More about EaselJS and DisplayObj here: createjs.com/docs/easeljs/classes/DisplayObject.html \$\endgroup\$ Jun 24, 2016 at 0:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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