[英]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
是一個非常常見的錯誤。 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;
})();
您正在努力編寫過於復雜的代碼。 隨着游戲的發展,這種復雜性將開始變得越來越難進行更改和管理錯誤。 始終努力使其盡可能簡單。
有很多方法可以轉換要渲染的 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 更改次數和使用 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
)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>
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.