功能图混合属性

时间:2019-01-01 13:33:45

标签: javascript html css html5 html5-canvas

我正在尝试创建2d平台游戏,而代码是它的基础。

由于某些未知的原因,我的最终函数绘图混合了其他函数的属性(尤其是颜色和线宽等)。

如果有类型原因(“ this。”在功能上不适当,等等)

我想了解更多项目。

任何好的回答都将不胜感激!。

/* 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>

2 个答案:

答案 0 :(得分:1)

问题在于您首先绘制形状,然后设置填充和笔触。这样,您可以为下一个形状设置填充和描边。

在我的代码中,我使用的是ctx.translate(0,-400),因为否则画布会太大。设置画布大小时,请删除此行。

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>

答案 1 :(得分:1)

一些加分。

现有的answer将解决您的问题,但是我花了一些时间,并注意到了一些有关您的代码可以改进的地方。

性能为王

在编写游戏(或为此制作动画内容)时,您将到达动画的复杂程度(动画和绘制项目的数量)达到设备无法再以全帧速率执行的阶段。当您尝试覆盖更大范围的设备时,这将成为更多问题。

要使Java代码中的每行代码获得最快的速度,您需要了解一些有关对象以及它们如何创建和销毁的简单规则(其他对象的可用内存)。

JavaScript受管理

这意味着作为程序员,您无需担心内存。您创建一个对象,并为其找到内存。当您不再需要对象时,javascript将清除内存,以便其他对象可以免费使用它。

这使使用Java编程更加容易。但是,在动画中,这可能会成为问题,因为管理内存分配和清理(又称为删除分配的内存或GC垃圾回收)的代码需要时间。如果您的动画要花费很长时间来计算和渲染每一帧,则必须强制GC阻止脚本和清理。

这种内存管理是Java动画(游戏)中Jank的最大来源。

它还引入了创建对象的额外处理,因为在创建新对象时必须找到并分配可用内存。在具有少量RAM的低端设备上情况更糟。创建新对象通常会迫使GC为新对象释放内存(窃取宝贵的CPU周期)。这使得低端设备的性能不是线性下降,而是对数下降。

对象类型。

对象(玩家,拾取器,子弹,FX)可以根据它们的生存时间以及一次可以存在的数量进行分类。对象的生存期意味着您可以利用JS如何管理内存来优化对象和内存使用。


单个实例对象。

这些是在动画中仅存在一次的对象。从开始到结束都有生命周期(在游戏中可能是从关卡开始到关卡结束)。

例如玩家,得分显示。

示例

创建这些对象的最佳方法是作为单例或对象工厂。下面的项目符号示例使用单例

EG对象工厂创建播放器

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;
}

您可以像创建其他任何对象一样使用它

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

// to draw

player.draw();

许多实例对象。

这些物体寿命很短,可能只有几帧。它们也可以成百上千种存在(例如爆炸时产生的火花FX或快速发射的子弹)

在您的代码中,您只有一个项目符号,但我可以想象,您可能有很多项目,而不是项目符号,这可能是乱斗或火花效果。

实例

创建对象需要CPU周期。现代JS有许多优化,因此创建对象的方式并没有太大差异。但是仍然存在差异,使用最佳方法会有所收获,特别是如果您每秒进行1000次。 (我写的最后一个JS游戏每秒处理多达80,000个FX对象,大多数实时对象不超过3-6帧)

对于许多短暂的对象,请定义原型或使用类语法(这将减少约50%的创建时间)。不用于停止GC命中并减少实例化开销时,将项目存储在池中。变得聪明,不要不必要地浪费时间等待GPU进行无意义的状态更改。

内存

由于内存管理开销的创建和删除它们的创建,这些短暂的对象是减慢速度和JANK的最大来源。

要消除内存管理的开销,您可以自己进行管理。最好的方法是复杂的(使用预先分配的(在级别开始时)气泡数组,并在级别中为每个对象设置最大数量)。

对象池

使用对象池是最简单的解决方案,它将为您带来90%的最佳解决方案,并且可以无限使用(取决于总RAM)。

池是未使用对象的数组,通常可以让GC删除它们。活动对象存储在数组中。他们做自己的事情,完成后就从对象数组移到池中。

当您需要一个新对象而不是用new Bullet()创建它时,请首先检查该池是否有任何对象。如果是这样,则将池中的旧对象重置为其属性,然后将其放在活动阵列上。如果池为空,则创建一个新对象。

这意味着您永远不会删除对象(在关卡/动画的整个生命周期内)。因为每次创建最大内存时都要检查池,所以实际上项目符号将使用的次数少于每次创建新对象的次数(GC不会立即删除)

渲染

2D上下文是用于渲染的出色API,但使用不当,可以在不更改渲染外观的情况下杀死帧速率。

如果您有许多使用相同样式的对象。不要将它们渲染为单独的路径。定义一条路径,添加对象,然后填充并描边。

示例

快速布斗池示例。所有子弹具有相同的样式。该界面将主代码隐藏在项目符号中。您只能访问项目符号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;
})();

要使用

...在开火功能中

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

...在主渲染循环中

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

...如果重新启动级别

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

...如果在级别结束时释放内存

bullets.clear();    

对象之间的某个地方。

这些对象位于上述两种类型之间

例如大功率装备,背景物品,敌方AI特工。

如果没有大量创建对象,并且寿命可能超过一秒甚至少于一个完整的水平,则需要确保可以使用最佳方法实例化它们。 (我个人将池(气泡数组)用于在关卡生命周期内存在的所有对象,但可能会导致大量代码)

定义原型

有两种方法可以有效地创建这些对象。使用类语法(个人讨厌对JS的这种添加)或在实例化函数之外定义原型。

示例

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);
    },
}    

希望这会有所帮助。 :)