简体   繁体   English

在普通 javascript 中旋转三角形

[英]Rotate triangle in plain javascript

I created a rotation function to rotate a triangle I drew, the function parameter is the amount of degrees I want to rotate the shape by.我创建了一个旋转 function 来旋转我绘制的三角形,function 参数是我想要旋转形状的度数。 The rotation function still wont rotate.旋转 function 仍然不会旋转。

I tried calling the function to draw the triangle in different lines within the rotation 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>

Expected result: Triangle rotated by 10 degrees to the left.预期结果:向左旋转 10 度的三角形。 Actual result: Normal triangle without any rotation.实际结果:没有任何旋转的正常三角形。

Rotating a shape.旋转一个形状。

maybe more than you asked for but you are making some common mistakes in your code that will negatively effect the final code and take some of the fun out of writing a game可能超出了您的要求,但您在代码中犯了一些常见错误,这些错误将对最终代码产生负面影响,并从编写游戏中获得一些乐趣

Defining the shape.定义形状。

Local Space局部空间

When you have a rotating, moving, and maybe scaling (zoom) shape it is best to define its shape centered on its own origin (local space) (as pointed out in the comments) so that transforming it to appear on the canvas (world space) does not require a the complexity of moving it to local space and back if you create the object in world coordinates.当您有一个旋转、移动和可能缩放(缩放)形状时,最好定义以它自己的原点(局部空间)为中心的形状(如评论中指出的那样),以便将其转换为出现在 canvas(世界如果您在世界坐标中创建 object,则不需要将其移动到本地空间并返回的复杂性。

Pre-define the path预定义路径

Rather than create the ships path each time you render it use a Path2D to define the shape.而不是每次渲染时都创建船只路径,而是使用Path2D来定义形状。 This avoid some of the computational overhead of creating the path by moving the computations to startup.这避免了通过将计算移动到启动来创建路径的一些计算开销。

Orientation方向

The natural orientation (forward) of the canvas transform is along the X axis. canvas 变换的自然方向(向前)沿 X 轴。 When building object that move in world space its best to have the front point along the same axis.在构建在世界空间中移动的 object 时,最好让前点沿同一轴。 You have the ship point up along the y axis in the negative direction.您将船沿 y 轴指向负方向。

ctx.closePath

It is a very common mistake to think that ctx.closePath is akin to ctx.beginPath .认为ctx.closePath类似于ctx.beginPath是一个非常常见的错误。 closePath has nothing to do with beginPath it is rather more like lineTo and creates an additional line from the last path point to the previous moveTo closePathbeginPath无关,它更像是lineTo并创建从最后一个路径点到上一个moveTo的附加线

Example例子

The code defines the ship as a 2D path with the front pointing along the x axis.代码将船定义为 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;
})();

Code complexity代码复杂度

You are working your way to overly complicated code.您正在努力编写过于复杂的代码。 As your game grows this complexity will start to make harder and hard to make changes and manage bugs.随着游戏的发展,这种复杂性将开始变得越来越难进行更改和管理错误。 Always strive to keep it as simple as possible.始终努力使其尽可能简单。

Transforming an object转换 object

There are many ways to transform an object to be rendered.有很多方法可以转换要渲染的 object。 The most common way is as you have done it.最常见的方法是你已经完成了。 However this method requires many GPU state changes (the exchange of data between the CPU and GPU).然而这种方法需要很多 GPU state 变化(CPU和GPU之间的数据交换)。 State change can be very slow (especially on low end devices) State 变化可能非常缓慢(尤其是在低端设备上)

The next snippet of your code marks the state changes您的代码的下一个片段标记了 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

The worst state change is ctx.restore which is highly dependent on the saved state and the changes made to the state between save and restore.最糟糕的 state 更改是ctx.restore ,它高度依赖于保存的 state 以及在保存和恢复之间对 state 所做的更改。 You should avoid using save and restore at all costs if you need performance code.如果您需要性能代码,则应不惜一切代价避免使用保存和恢复。

Two state render两个 state 渲染

The next example will render a 2D shape in the minimum number of state changes possible and the fastest way to render transformed content using the 2D API.下一个示例将以可能的最小 state 更改次数和使用 2D API 渲染转换内容的最快方式来渲染 2D 形状。 It does however leave the state as is so you must be aware of this in subsequent renders.然而,它确实使 state 保持原样,因此您必须在后续渲染中注意这一点。 It is more efficient to fully define each state as needed rather than use save and restore.根据需要完全定义每个 state 比使用保存和恢复更有效。

Note I added scale as you may need is some time.注意我添加了规模,因为您可能需要一些时间。

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

To draw the ship then just needs the line画船然后只需要这条线

strokeShape(shipShape, {x:450, y:300}, rotate, 1, "white");

Demo演示

Putting it all together we get the following.把它们放在一起,我们得到以下结果。

  • Using requestAnimationFrame to do the animation (Never use setInterval )使用requestAnimationFrame执行 animation (切勿使用setInterval
  • A generic function to create a path Path2D from a set of points通用 function 从一组点创建路径Path2D
  • Defining the ship as an object to keep the data organised将船定义为 object 以保持数据井井有条

UPDATE更新

Noticed your second question regarding movement.注意到你关于运动的第二个问题。 As the question was answered I though I would just extend this demo a little to give some hints to how to move the ship and some other game related stuff.当问题得到回答时,我虽然只是稍微扩展了这个演示,以提供一些关于如何移动船和其他一些与游戏相关的东西的提示。 Click to start Up to thrust, Left right turns.点击开始向上推,左转右转。

 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