我正在尝试创建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>
答案 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将清除内存,以便其他对象可以免费使用它。
这使使用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);
},
}
希望这会有所帮助。 :)