[英]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.
实际结果:没有任何旋转的正常三角形。
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可能超出了您的要求,但您在代码中犯了一些常见错误,这些错误将对最终代码产生负面影响,并从编写游戏中获得一些乐趣
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,则不需要将其移动到本地空间并返回的复杂性。
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.
这避免了通过将计算移动到启动来创建路径的一些计算开销。
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
closePath
与beginPath
无关,它更像是lineTo
并创建从最后一个路径点到上一个moveTo
的附加线
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;
})();
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.
始终努力使其尽可能简单。
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.如果您需要性能代码,则应不惜一切代价避免使用保存和恢复。
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");
Putting it all together we get the following.把它们放在一起,我们得到以下结果。
requestAnimationFrame
to do the animation (Never use setInterval
)requestAnimationFrame
执行 animation (切勿使用setInterval
)Path2D
from a set of pointsPath2D
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.