Поверните треугольник в простом JavaScript - PullRequest
1 голос
/ 25 октября 2019

Я создал функцию вращения, чтобы повернуть нарисованный мной треугольник, параметр функции - это количество градусов, на которое я хочу повернуть фигуру. Функция вращения по-прежнему не будет вращаться.

Я попытался вызвать функцию, чтобы нарисовать треугольник в разных линиях в функции вращения.

 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 градусов влево. Фактический результат: нормальный треугольник без вращения.

1 Ответ

0 голосов
/ 25 октября 2019

Вращение фигуры.

Возможно, больше, чем вы просили, но вы допускаете некоторые распространенные ошибки в вашем коде, которые негативно влияют на окончательный код и отнимают часть удовольствия от написания игры

Определение формы.

Локальное пространство

Если у вас есть вращающаяся, движущаяся и, возможно, масштабирующая (масштабирующая) форма, лучше всего определять ее форму с центром в ее собственном начале (локальном). пространство) (как указано в комментариях), так что преобразование его для отображения на холсте (мировое пространство) не требует сложности перемещения его в локальное пространство и обратно, если вы создаете объект в мировых координатах.

Предварительно определить путь

Вместо того, чтобы создавать путь кораблей каждый раз, когда вы его визуализируете, используйте Path2D для определения формы. Это позволяет избежать некоторых накладных расходов на создание пути путем перемещения вычислений в автозагрузку.

Ориентация

Естественная ориентация (вперед) преобразования холста происходит вдоль оси X. При построении объекта, который движется в мировом пространстве, лучше всего иметь переднюю точку вдоль одной оси. У вас есть корабль, направленный вверх по оси y в отрицательном направлении.

ctx.closePath

Очень распространенная ошибка - думать, что ctx.closePath сроднидо ctx.beginPath. closePath не имеет ничего общего с beginPath, оно больше похоже на lineTo и создает дополнительную строку от последней точки пути к предыдущему moveTo

Пример

Кодопределяет корабль как двухмерный путь, фронт которого направлен вдоль оси 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;
})();

Сложность кода

Вы работаете над слишком сложным кодом. По мере роста вашей игры эта сложность будет усложнять внесение изменений и исправление ошибок. Всегда старайтесь, чтобы это было как можно проще.

Преобразование объекта

Существует множество способов преобразования объекта для визуализации. Наиболее распространенный способ, как вы это сделали. Однако этот метод требует много изменений состояния GPU (обмен данными между CPU и GPU). Изменение состояния может быть очень медленным (особенно на младших устройствах)

Следующий фрагмент вашего кода отмечает изменения состояния

 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

Худшее изменение состояния - ctx.restore, который сильно зависит от сохраненного состояния и изменений, внесенных в состояние между сохранением и восстановлением. Вы должны избегать использования сохранения и восстановления любой ценой, если вам нужен код производительности.

Визуализация с двумя состояниями

В следующем примере будет отображена двухмерная фигура с минимальным числом возможных изменений состояния и самым быстрымспособ визуализации преобразованного контента с использованием 2D API. Тем не менее, он оставляет состояние как есть, поэтому вы должны знать об этом при последующих визуализациях. Более эффективно полностью определять каждое состояние по мере необходимости, а не использовать сохранение и восстановление.

Примечание Я добавил масштаб, поскольку вам может понадобиться некоторое время.

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 для анимации (Никогда не используйте 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>
...