我创建了一个旋转函数来旋转我绘制的三角形,函数参数是我要旋转形状的度数。旋转功能仍然不会旋转。
我尝试调用该函数以在旋转函数内的不同直线上绘制三角形。
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
//central coordinates, width and height of triangle
let ship_center = {x: 450, y: 300};
let ship_width = 20;
let ship_height = 20;
//coordinates of the points of the vertices for my triangle
let ship_points = [
//top vertex
{x: 450 - ship_width/2, y: ship_center.y + ship_height/2},
//bottom right vertex
{x: 450 + ship_width/2, y: ship_center.y + ship_height/2},
//bottom left vertex
{x: ship_center.x, y: ship_center.y - ship_height/2}
];
function drawRect(x, y, width, height, color){
ctx.rect(x, y, width, height);
ctx.fillStyle = color;
ctx.fill();
}
//bottom left vertices, bottom right verices and top vertices
//as parameters in drawTriangle
function drawTriangle(bottom_left, bottom_right, top, color){
ctx.beginPath();
ctx.moveTo(top.x, top.y);
ctx.lineTo(bottom_left.x, bottom_left.y);
ctx.lineTo(bottom_right.x, bottom_right.y);
ctx.lineTo(top.x, top.y);
ctx.strokeStyle = color;
ctx.stroke();
ctx.closePath();
}
//rotation function
function rotate(angle){
ctx.save();
//draw the triangle
drawTriangle(ship_points[2], ship_points[1], ship_points[0],
"white");
ctx.translate(ship_center.x, ship_center.y);
ctx.rotate(Math.PI/180 * angle);
ctx.restore();
}
function game(){
drawRect(0, 0, 900, 600, "black");
//rotate 10 degrees
rotate(10);
}
let gameLoop = setInterval(game, 10);
<canvas id="canvas">
no support
</canvas>
预期结果:三角形向左旋转10度。实际结果:法线三角形不旋转。
也许比您要的要多,但是您在代码中犯了一些常见错误,这会对最终代码产生负面影响,并从编写游戏中获得一些乐趣
当您具有旋转,移动以及可能的缩放(缩放)形状时,最好以其自身的原点(局部空间)为中心定义其形状(如注释中所指出),以便将其转换为在画布上显示(世界空间),如果您在世界坐标系中创建对象,则不需要将其移至局部空间然后再移回的复杂性。
无需在每次渲染时都创建飞船路径,而是使用Path2D定义形状。这样可以避免通过将计算移至启动位置来创建路径的一些计算开销。
canvas变换的自然方向(正向)沿X轴。当构建在世界空间中移动的物体时,最好使其前端沿同一轴。您使飞船点沿y轴沿负方向向上。
ctx.closePath
认为与...ctx.closePath
类似是一个非常普遍的错误ctx.beginPath
。closePath
与之无关的东西beginPath
更像是lineTo
从最后一个路径点到上一个路径点创建一条额外的线moveTo
该代码将飞船定义为2D路径,其前端指向x轴。
const shipShape = (() => {
const width = 20, height = 20;
const ship = new Path2D()
ship.lineTo(-width / 2, -height / 2);
ship.lineTo(width / 2, 0);
ship.lineTo(-width / 2, height / 2);
ship.closePath();
return ship;
})();
您正在处理过于复杂的代码。随着游戏的发展,这种复杂性将变得越来越难进行更改和管理错误。始终努力使它尽可能简单。
有很多方法可以转换要渲染的对象。最常见的方法是完成操作。但是,此方法需要进行许多GPU状态更改(CPU和GPU之间的数据交换)。状态更改可能非常缓慢(尤其是在低端设备上)
您的代码的下一个片段标记了状态更改
ctx.save();
// the ctx.stroke in the following function is a state change
drawTriangle(ship_points[2], ship_points[1], ship_points[0],"white");
// All 3 of the following calls are state changes.
ctx.translate(ship_center.x, ship_center.y);
ctx.rotate(Math.PI/180 * angle);
ctx.restore(); // Depending on the saved state this can be very time expensive
最坏的状态更改ctx.restore
高度依赖于保存的状态以及保存和还原之间对状态所做的更改。如果需要性能代码,则应避免不惜一切代价使用保存和还原。
下一个示例将以尽可能少的状态更改次数以及使用2D API最快的方式呈现转换后的内容的方式呈现2D形状。但是,它确实保持状态不变,因此您必须在后续渲染中意识到这一点。完全根据需要定义每个状态比使用保存和还原更为有效。
注意我增加了规模,可能需要一些时间。
function strokeShape(shape, pos, rotate = 0, scale = 1, style = ctx.strokeStyle) {
const xAx = Math.cos(rotate) * scale;
const xAy = Math.sin(rotate) * scale;
ctx.setTransform(xAx, xAy, -xAy, xAx, pos.x, pos.y); // set rotate scale and position
// in one state change
ctx.strokeStyle = style;
ctx.stroke(shape);
}
画船然后只需要线
strokeShape(shipShape, {x:450, y:300}, rotate, 1, "white");
放在一起,我们得到以下内容。
requestAnimationFrame
做动画(从不使用setInterval
)Path2D
从一组点创建路径的通用函数注意到关于运动的第二个问题。在回答问题后,我虽然会稍微扩展一下此演示,以提示如何移动飞船以及其他一些与游戏相关的内容。单击以启动推力,向左转右。
var started = false;
canvas.addEventListener("click",() => {
if (!started) {
requestAnimationFrame(updateFrame);
started = true;
}
})
const ctx = canvas.getContext("2d", {aplha:false});// aplha:false to avoid unneeded composition
ctx.font = "16px arial";
ctx.textAlign = "center";
fillBackground();
ctx.fillStyle = "white"
ctx.fillText("Click to Start", ctx.canvas.width / 2, ctx.canvas.height / 2);
document.addEventListener("keydown", keyboardEvent);
document.addEventListener("keyup", keyboardEvent);
const keys = {ArrowUp: false, ArrowLeft: false, ArrowRight: false}
function keyboardEvent(event) {
if(keys[event.code] !== undefined) {
event.preventDefault();
keys[event.code] = event.type === "keydown";
}
}
const width = 20, height = 20;
const TURN_RATE = 0.01; // in radians
const MAX_TURN_RATE = 0.1; // in radians
const REACTOR_WINDUP_RATE = 0.01; // in power units per frame
const REACTOR_MAX_POWER = 0.1; // in pixels per frame (frame = 1/60th sec)
const SPACE_QUANTUM_FLUX = 0.015; // drains ship moment per frame
const DEFLUXING_CONVERTER = 0.8; // How dirty the thruster is
const SHIP_HULL = [-width*(1/3), -height/2, width*(2/3), 0, -width*(1/3), height/2,"close"];
const SHIP_PORT = [width*(1/6), -height/8, width*(1/3), 0, width*(1/6), height/8,"close"];
const thrustParticlePool = [];
const thrustParticle = {
get pos() { return {x:0, y:0} },
get vel() { return {x:0, y:0} },
shape: createPath([-0.5,0,0.5,0]),
style: "#FFF",
rotate: 0,
pool: thrustParticlePool,
update() {
this.pos.x += this.vel.x;
this.pos.y += this.vel.y;
this.vel.x *= 0.996;
this.vel.y *= 0.996;
this.life -= 1;
},
init(x,y,direction, speed) {
const offCenter = Math.random()**2 * (Math.random() < 0.5 ? -1 : 1);
const offCenterA = Math.random()**2 * (Math.random() < 0.5 ? -1 : 1);
speed += speed * offCenterA;
speed **= 2.5;
this.pos.x = x + Math.cos(direction) * width * (2/3) - Math.sin(direction) * height * (1/6) * offCenter;
this.pos.y = y + Math.sin(direction) * width * (2/3) + Math.cos(direction) * height * (1/6) * offCenter;
direction += direction * 0.1 * offCenter;
this.rotate = direction;
this.vel.x = Math.cos(direction) * speed;
this.vel.y = Math.sin(direction) * speed;
this.life = 100;
},
};
const particles = Object.assign([],{
add(type,...args) {
var p;
if(type.pool.length) {
p = type.pool.pop();
} else {
p = Object.assign({}, type);
}
p.init(...args);
this.push(p);
},
updateDraw() {
var i = 0
while(i < this.length) {
const p = this[i];
p.update();
if (p.life <= 0) {
this.splice(i--,1)[0];
if (p.pool) { p.pool.push(p) }
} else {
strokeShape(p.shape, p.pos, p.rotate, 1, p.style);
}
i++;
}
}
});
function createPath(...paths) {
var i, path = new Path2D;
for(const points of paths) {
i = 0;
path.moveTo(points[i++],points[i++])
while (i < points.length -1) { path.lineTo(points[i++],points[i++]) }
points[i] === "close" && path.closePath();
}
return path;
}
const ship = {
shapes: {
normal: createPath(SHIP_HULL, SHIP_PORT),
thrustingA: createPath(SHIP_HULL, SHIP_PORT,
[-width*(1/3), -height/4, -width*(1/3)-height/4,0, -width*(1/3), height/4]
),
thrustingB: createPath(SHIP_HULL, SHIP_PORT,
[-width*(1/3), -height/3.5, -width*(1/3)-height/2.4,0, -width*(1/3), height/3.5]
),
},
shape: null,
rotate: 0, // point left to right along x axis
deltaRotate: 0,
pos: {x : 200, y: 100},
vel: {x : 0, y: 0},
power: 0,
style: "#FFF", // named colours take about 10% longer to set than Hex colours
update() {
if (keys.ArrowUp) {
this.shape = this.shapes.thrustingA === this.shape ? this.shapes.thrustingB : this.shapes.thrustingA;
this.power = this.power < REACTOR_MAX_POWER ? this.power + REACTOR_WINDUP_RATE : REACTOR_MAX_POWER;
if (Math.random() < DEFLUXING_CONVERTER) {
particles.add(
thrustParticle,
this.pos.x, this.pos.y,
this.rotate + Math.PI,
this.power * 8,
);
}
} else {
this.shape = this.shapes.normal;
this.power = 0;
}
var dr = this.deltaRotate;
dr *= 0.95;
dr = keys.ArrowLeft ? dr - TURN_RATE : dr;
dr = keys.ArrowRight ? dr + TURN_RATE : dr;
dr = Math.abs(dr) > MAX_TURN_RATE ? MAX_TURN_RATE * Math.sign(dr) : dr;
this.rotate += (this.deltaRotate = dr);
this.vel.x += Math.cos(this.rotate) * this.power;
this.vel.y += Math.sin(this.rotate) * this.power;
const speed = (this.vel.x * this.vel.x + this.vel.y * this.vel.y)**4;
if (speed > 0.0) {
this.vel.x = this.vel.x * (speed / (speed * (1+SPACE_QUANTUM_FLUX)));
this.vel.y = this.vel.y * (speed / (speed * (1+SPACE_QUANTUM_FLUX)));
}
this.pos.x += this.vel.x;
this.pos.y += this.vel.y;
this.pos.x = (this.pos.x + ctx.canvas.width * 2) % ctx.canvas.width;
this.pos.y = (this.pos.y + ctx.canvas.height * 2) % ctx.canvas.height;
},
draw() {
strokeShape(ship.shape, ship.pos, ship.rotate, 1, ship.style);
}
};
function strokeShape(shape, pos, rotate = 0, scale = 1, style = ctx.strokeStyle) {
const xAx = Math.cos(rotate) * scale; // direction and size of the top of a
const xAy = Math.sin(rotate) * scale; // single pixel
ctx.setTransform(xAx, xAy, -xAy, xAx, pos.x, pos.y); // one state change
ctx.strokeStyle = style;
ctx.stroke(shape);
}
function fillBackground() {
ctx.fillStyle = "#000";
ctx.setTransform(1,0,0,1,0,0); //ensure that the GPU Transform state is correct
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
}
function updateFrame(time) {
fillBackground();
ship.update();
particles.updateDraw();
ship.draw();
requestAnimationFrame(updateFrame);
}
<canvas id="canvas" width="400" height="200"></canvas>
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句