Function Draw Mixing Properties

I am trying to create a 2d platformer, and the code is the base of it.

For some unknown reason, my final function draw mixes the other functions' properties (especially colour, and line width etc.).

If there is a type reason ( "this." is functioning inappropriate etc.)

I want to know for further projects.

Any good answer will be fully appreciated!.

/* main.js */

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d")

function Shooter() {
    this.x = 100;
    this.y = 500;
    this.size = 50;
    this.color = "blue";
    this.borderColor = "black";
    this.borderWidth = 5;
    this.draw = function() {
        ctx.fillRect(this.x, this.y, this.size, this.size);
        ctx.strokeRect(this.x, this.y, this.size, this.size);
        ctx.fillStyle = this.color;
        ctx.strokeStyle = this.borderColor;
        ctx.lineWidth = this.borderWidth;
    }
}

function Gun() {
    this.x = sh.x + sh.size / 2 + 10;
    this.y = sh.y + sh.size / 2;
    this.color = "grey";
    this.borderColor = "brown";
    this.borderWidth = 1;
    this.width = 20;
    this.height = 10;
    this.draw = function() {
        ctx.fillRect(this.x,this.y,this.width,this.height);
        ctx.strokeRect(this.x,this.y,this.width,this.height);
        ctx.fillStyle = this.color;
        ctx.strokeStyle = this.borderColor;
        ctx.lineWidth = this.borderWidth;
    }
}

function Bullet() {
    this.x = sh.x + sh.size * 2;
    this.y = sh.y + sh.size / 2;
    this.color = "orange";
    this.radius = 5;
    this.vx = 20;
    this.borderColor = "green";
    this.borderWidth = 2;
    this.draw = function() {
        ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
        ctx.fillStyle = this.color;
        ctx.strokeStyle = this.borderColor;
        ctx.lineWidth = this.borderWidth;
        ctx.stroke();
    }
}
var sh = new Shooter();
var g = new Gun();
var b = new Bullet();

function draw() {
  
    sh.draw();

    g.draw();
    
    b.draw();
    
requestAnimationFrame(draw);
}

draw();
/* main.css */

html, body {
    overflow: hidden;
}
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Page Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="main.css" />
   
</head>
<body>
    <canvas id="canvas" width="1536px" height="754px"></canvas>
    <!-- device innerWidth and innerHeight -->
<!-- make fullScreen to see the issue -->       
    
    <script src="main.js"></script>
</body>
</html>

Answers:

Answer

The problem is that you first draw the shape and after that you set the fill and the stroke. Doing so you set the fill and stroke for the next shape.

In my code I'm using ctx.translate(0,-400) because otherwise the canvas would have been too large. Remove this line when setting your canvas size.

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
//setting the canvas size
canvas.width = 400;
canvas.height = 200;

ctx.translate(0,-400);

function Shooter() {
    this.x = 100;
    this.y = 500;
    this.size = 50;
    this.color = "blue";
    this.borderColor = "black";
    this.borderWidth = 5;
    this.draw = function() {
    // first set the colors for this shape
    ctx.fillStyle = this.color;
    ctx.strokeStyle = this.borderColor;
    ctx.lineWidth = this.borderWidth;
    // then fill and stroke the shape  
    ctx.fillRect(this.x, this.y, this.size, this.size);
    ctx.strokeRect(this.x, this.y, this.size, this.size);
    }
}

function Gun() {
    this.x = sh.x + sh.size / 2 + 10;
    this.y = sh.y + sh.size / 2;
    this.color = "grey";
    this.borderColor = "brown";
    this.borderWidth = 1;
    this.width = 20;
    this.height = 10;
    this.draw = function() {
    ctx.fillStyle = this.color;
    ctx.strokeStyle = this.borderColor;
    ctx.lineWidth = this.borderWidth;
    ctx.fillRect(this.x,this.y,this.width,this.height);     ctx.strokeRect(this.x,this.y,this.width,this.height);

    }
}

function Bullet() {
    this.x = sh.x + sh.size * 2;
    this.y = sh.y + sh.size / 2;
    this.color = "orange";
    this.radius = 5;
    this.vx = 20;
    this.borderColor = "green";
    this.borderWidth = 2;
    this.draw = function() {
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
        ctx.fillStyle = this.color;
        ctx.strokeStyle = this.borderColor;
        ctx.lineWidth = this.borderWidth;
        ctx.fill();
        ctx.stroke();
      
    }
}
var sh = new Shooter();
var g = new Gun();
var b = new Bullet();

function draw() {
  
    sh.draw();

    g.draw();
    
    b.draw();
    
requestAnimationFrame(draw);
}

draw();
canvas{border:1px solid}
<canvas id="canvas"></canvas>

Answer

Some extra points.

The existing answer will solve your problem, however I have some time and noticed some points about your code that can be improved.

Performance is king

When writing games (Or for that matter animating content) you will get to a point where the complexity of the animation (number of animated and drawn items) gets to a stage where the device can no longer do it at full frame rate. This become more of a problems as you try to cover a larger range of devices.

To get the most speed per line of code in Javascript you need to be aware of some simple rules in regard to objects and how they are created and destroyed (free memory for other objects).

JavaScript is managed

This means that as a programmer you don't need to worry about memory. You create an object and the memory for it is found for you. When you no longer need the object javascript will cleanup the memory so it is free for other objects.

This make programing in Javascript easier. However in animations this can become a problem as the code that manages the memory allocation and clean up (AKA delete allocated memory or GC Garbage Collection) takes time. If your animation is taking a long time to compute and render each frame then GC is forced to block your script and cleanup.

This management of memory is a the biggest source of Jank in Javascript animations (games).

It also introduces extra processing to create objects, as free memory has to be located and allocated when you create a new object. It gets worse on low end devices with low amounts of RAM. Creating new objects will more often force GC to free up memory for the new object (stealing precious CPU cycles). This make performance on low end devices not a linear degradation but rather a logarithmic degradation.

Types of Objects.

Objects (player, pickups, bullets, FX) can be classified by how long they live and how many can exist at one time. The lifetime of the object means you can take advantages of how JS manages memory to optimise objects and memory use.


Single instance object.

These are objects that exist only once in the animation. That is have a lifetime from start to end (in a game that may be from level start to level end).

Eg the player, the score display.

Example

The best ways to create these objects is as a singleton or object factory. The bullets example below this uses a singleton

EG Object factory to create player

function Shooter() {
    // Use closure to define the properties of the object
    var x = 100;
    var y = 500;
    const size = 50;
    const style = {
        fillStyle : "blue",
        strokeStyle : "black",
        lineWidth : 5,
    }
    // the interface object defines functions and properties that
    // need to be accessed from outside this function
    const API = {
        draw() {
            ctx.fillStyle = style.fillStyle;
            ctx.strokeStyle = style.strokeStyle;
            ctx.lineWidth = style.lineWidth;

            ctx.fillRect(x, y, size, size);
            ctx.strokeRect(x, y, size, size);

            // it is quicker to do the above two lines as
            /*
            ctx.beginPath(); // this function is done automatically 
                             // for fillRect and strokeRect. It                                 
            ctx.rect(x, y, size, size);
            ctx.fill();
            ctx.stroke();
            */
        }
    }
    return API;
}

You create an use it just like any other object

const player = new Shooter();
// or
const player = Shooter();  / You dont need the new for this type of object

// to draw

player.draw();

Many instance object.

These are objects that have very short lives, maybe a few frames. They can also exist in the hundreds (think of sparks FX in an explosion, or rapid fire bullets)

In your code you have only one bullet, but I can imagine that you could have many, or rather than a bullets, it could be gribble or spark FXs.

Instantiation

Creating objects requires CPU cycles. Modern JS has many optimisations and thus there is not much difference in how you create objects. But there is still a difference and using the best method pays off, especially if you do it 1000's of times a second. (the last JS game I wrote handles upto 80,000 FX objects a second, most live no more than 3-6 frames)

For many short lived objects define a prototype or use the class syntax (this reduces creating time by around 50%). Store items in a pool when not used to stop GC hits and reduce instantiation overheads. Be render smart, and don't needlessly waste time waiting for GPU to do pointless state changes.

Memory

These short lived objects are the biggest source of slowdown and JANK due to memory management overhead creating and deleting them creates.

To combat the memory management overhead you can do that management yourself. The best way is complicated (use a pre allocated (at level start) bubble array and sets a max number for each object during the level).

Object Pool

The simplest solutions that will get you 90% of the best and allows for unlimited (depends on total RAM) is to use object pools.

A pool is an array of unused objects, that you would normally have let GC delete. Active objects are stored in an array. They do their thing and when done they are moves from the object array into the pool.

When you need a new object rather than create it with new Bullet() you first check if the pool has any. If it does you take the old object from the pool reset its properties and put it on the active array. If the pool is empty you create a new object.

This means that you never delete an object (over the lifetime of the level / animation). Because you check the pool each time you create the max memory the bullets will use will actually be less than creating new objects each time (GC does not delete immediately)

Rendering

The 2D context is a great API for rendering, but use it badly and you can kill the frame rate without changing the rendered appearance.

If you have many objects that use the same style. Don't render them as separate path. Defined one path, add the objects then fill and stroke.

Example

Example of a rapid fire buller pool. All bullets have the same style. This interface hides the bullets from the main code. You can only access the bullets API

const bullets = (() => { // a singleton
    function Bullet() { }
    const radius = 5;
    const startLife = 100;
    this.radius = 5;
    const style = {
        fillStyle : "orange",
        strokeStyle : "green",
        lineWidth : 2,
    }    
    // to hold the interface 
    Bullets.prototype = {
        init(x,y,dx,dy) { // dx,dy are delta
            this.life = startLife;
            this.x = x;
            this.y = y;
            this.dx = dx;
            this.dy = dy;
        },
        draw() {
            ctx.arc(this.x, this.y, radius, 0 , Math.PI * 2);
        },
        move() {
            this.x += this.dx;
            this.y += this.dy;
            this.life --;
        }
    };
    const pool = []; // holds unused bullets
    const bullets = []; // holds active bullets
    // The API that manages the bullets
    const API = {
        fire(x,y,dx,dy) {
            var b;
            if(pool.length) {
                b = bullets.pop();
            } else {
                b = new Bullet();
            }               
            b.init(x,y,dx,dy);
            bullets.push(bullets); // put on active array
        },
        update() {
            var i;
            for(i = 0; i < bullets.length; i ++) {
                const b = bullets[i];
                b.move();
                if(b.life <= 0) { // is the bullet is no longer needed move to the pool
                    pool.push(bullets.splice(i--, 1)[0]);
                }
            }            
        },
        draw() {
            ctx.lineWidth = style.lineWidth;
            ctx.fillStyle = style.fillStyle;
            ctx.strokeStyle = style.strokeStyle;
            ctx.beginPath();
            for(const b of bullets) { b.draw() }
            ctx.fill();
            ctx.stroke();            
        },
        get count() { return bullets.length }, // get the number of active
        clear() { // remove all 
            pool.push(...bullets); // move all active to the pool;
            bullets.length = 0; // empty the array;
        },
        reset() { // cleans up all memory
            pool.length = 0;
            bullets.length = 0;
        }
    };
    return API;
})();

To use

...in the fire function

// simple example
bullets.fire(gun.x, gun.y, gun.dirX, gun.dirY);

...in the main render loop

bullets.update(); // update all bullets
if(bullets.count) { // if there are bullets to draw
    bullets.draw();
}        

...if restarting level

bullets.clear(); // remove bullets from previouse play

...if at end of level free the memory

bullets.clear();    

The somewhere in between object.

These are objects that lay somewhere in between the above two types,

Eg a power ups, background items, enemy AI agents.

If object does not get created in high numbers, and have lives that may last more than a second to less than a complete level you need to ensure that they can be instantiated using the optimal method. (Personally I use pools (bubble array) for all but objects that live for the life of the level, but that can result in a lot of code)

Define the prototype

There are two way to effectively create these objects. Using the class syntax (personally hate this addition to JS) or define the prototype outside the instantiating function.

Example

function Gun(player, bullets) {
    this.owner = player;
    this.bullets = bullets; // the bullet pool to use.
    this.x = player.x + player.size / 2 + 10;
    this.y = player.y + player.size / 2;

    this.width = 20;
    this.height = 10;
    const style = {
        fillStyle : "grey",
        strokeStyle : "brown",
        lineWidth : 1,
    };   
}

// Moving the API to the prototype improves memory use and makes creation a little quicker
Gun.prototype = {
    update() {
        this.x = this.owner.x + this.owner.size / 2 + 10;
        this.y = this.owner.y + this.owner.size / 2;
    },
    draw() {
        ctx.lineWidth = this.style.lineWidth;
        ctx.fillStyle = this.style.fillStyle;
        ctx.strokeStyle = this.style.strokeStyle;
        ctx.beginPath();
        ctx.rect(this.x,this.y,this.width,this.height);
        ctx.fill();
        ctx.stroke();
    },
    shoot() {
        this.bullets.fire(this.x, this.y, 10, 0);
    },
}    

Hope this helps. :)

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.