繁体   English   中英

在普通 javascript 中旋转三角形

[英]Rotate triangle in plain javascript

我创建了一个旋转 function 来旋转我绘制的三角形,function 参数是我想要旋转形状的度数。 旋转 function 仍然不会旋转。

我尝试调用 function 在旋转 function 内以不同的线绘制三角形。

 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 度的三角形。 实际结果:没有任何旋转的正常三角形。

旋转一个形状。

可能超出了您的要求,但您在代码中犯了一些常见错误,这些错误将对最终代码产生负面影响,并从编写游戏中获得一些乐趣

定义形状。

局部空间

当您有一个旋转、移动和可能缩放(缩放)形状时,最好定义以它自己的原点(局部空间)为中心的形状(如评论中指出的那样),以便将其转换为出现在 canvas(世界如果您在世界坐标中创建 object,则不需要将其移动到本地空间并返回的复杂性。

预定义路径

而不是每次渲染时都创建船只路径,而是使用Path2D来定义形状。 这避免了通过将计算移动到启动来创建路径的一些计算开销。

方向

canvas 变换的自然方向(向前)沿 X 轴。 在构建在世界空间中移动的 object 时,最好让前点沿同一轴。 您将船沿 y 轴指向负方向。

ctx.closePath

认为ctx.closePath类似于ctx.beginPath是一个非常常见的错误。 closePathbeginPath无关,它更像是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;
})();

代码复杂度

您正在努力编写过于复杂的代码。 随着游戏的发展,这种复杂性将开始变得越来越难进行更改和管理错误。 始终努力使其尽可能简单。

转换 object

有很多方法可以转换要渲染的 object。 最常见的方法是你已经完成了。 然而这种方法需要很多 GPU state 变化(CPU和GPU之间的数据交换)。 State 变化可能非常缓慢(尤其是在低端设备上)

您的代码的下一个片段标记了 state 更改

 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

最糟糕的 state 更改是ctx.restore ,它高度依赖于保存的 state 以及在保存和恢复之间对 state 所做的更改。 如果您需要性能代码,则应不惜一切代价避免使用保存和恢复。

两个 state 渲染

下一个示例将以可能的最小 state 更改次数和使用 2D API 渲染转换内容的最快方式来渲染 2D 形状。 然而,它确实使 state 保持原样,因此您必须在后续渲染中注意这一点。 根据需要完全定义每个 state 比使用保存和恢复更有效。

注意我添加了规模,因为您可能需要一些时间。

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执行 animation (切勿使用setInterval
  • 通用 function 从一组点创建路径Path2D
  • 将船定义为 object 以保持数据井井有条

更新

注意到你关于运动的第二个问题。 当问题得到回答时,我虽然只是稍微扩展了这个演示,以提供一些关于如何移动船和其他一些与游戏相关的东西的提示。 点击开始向上推,左转右转。

 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>

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM