簡體   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