Создайте солнечную систему с помощью Canvas API, используя Javascript

Вот что мы будем создавать в этом уроке:

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

Фон

Я изучаю Canvas API всего около недели и смотрел несколько видеороликов на YouTube, созданных Крисом и на его веб-сайте https://chriscourses.com/.

Давайте начнем

Для начала загрузите этот шаблон холста, сделанный Крисом. Это всего лишь настройка проекта для получения необходимых файлов HTML, CSS и Javascript.



Запустите npm install внутри каталога, в котором находится этот шаблон. Это установит необходимые зависимости для запуска проекта.

Затем в том же терминале запустите npm start. Откройте браузер и перейдите по адресу localhost: 3000. Вы увидите следующее:

Пока текст просто следует за положением мыши. Мы удалим некоторые вещи и начнем с создания Солнца.

Откройте js / canvas.js и измените файл, чтобы он выглядел следующим образом:

Несколько замечаний.

  • Я удалил прослушиватели событий мыши и объект отслеживания мыши.
  • Я изменил имя class на Planet и обновил конструктор, чтобы он принял несколько дополнительных параметров. (те, которые нам понадобятся при построении наших объектов Planet.

Солнце

Для начала создадим наш объект Sun.

Вернемся к canvas.js и добавим в функцию init следующее.

planets.push(new Planet(canvas.width / 2, canvas.height / 2, 35, 'yellow', 0, 0));

Здесь мы добавляем планету (на самом деле Солнце - звезда, но ради этого мы пренебрежем этим фактом).

Мы определяем координаты x и y как центр нашего холста, радиус 35 и желтый цвет. Поскольку наше Солнце неподвижно, у него не будет ни радиуса орбиты, ни скорости.

Затем в функции animate добавьте следующий код:

// Animation Loop
function animate() {
    requestAnimationFrame(animate);
    c.clearRect(0, 0, canvas.width, canvas.height);
    c.fillStyle = 'rgb(0, 0, 0)';
    c.fillRect(0, 0, canvas.width, canvas.height);
    planets.forEach(planet => {
       planet.update();
    });
}

Мы добавляем черный стиль заливки и заполняем холст новым добавленным стилем заливки.

Затем мы вызываем функцию обновления для каждой планеты, которую добавляем в наш массив planet.

Теперь вы увидите, как солнце ярко светит в центре нашего холста.

Теперь давайте перейдем к нашей первой планете, Меркурию, и поймем, как мы можем сделать ее вращающейся по орбите.

Что нам нужно сделать, так это поддерживать круговую орбиту на определенном радиусе от центра нашего холста. Оттуда нам нужно определить, насколько быстро наша планета будет двигаться вокруг своего центра. Для ртути мы хотим обновить ее скорость в цикле анимации на 0,005. Итак, учитывая начальные координаты x и y планеты, мы можем добавить скорость к обоим из них, чтобы перемещать ее во время каждого кадра анимации.

(Поворот сюжета, одно это на самом деле не даст нам нужного кругового рисунка, но давайте пока разберемся с ним)

Добавьте следующий код обратно в canvas.js

в функции init() добавим еще одну планету в наш массив planets

// Mercury
planets.push(new Planet(canvas.width / 2, canvas.height / 2, 5, 'gray', 0.005, 65));

Мы снова добавляем планету, начиная с центра нашего холста, и определяем радиус равным 5, скорость равной 0,005 и радиус орбиты равным 65.

Теперь добавьте следующий код в функцию Planet update(),

update() {
    this.draw();
    // Update the x and y by the velocity
    this.x += this.velocity;
    this.y += this.velocity;
}

Здесь, в каждом кадре анимации, мы добавляем скорость к нашим x и y. Со временем планета переместится на эту величину. Очень медленно.

Давайте теперь узнаем, как заставить планету двигаться по кругу по радиусу орбиты, который мы определили.

Используя радианы (как угол), синус и косинус, мы можем заставить планету двигаться только по кругу.

Мы можем увеличить наш «угол» (опять же, в радианах) с помощью нашей скорости. Со временем это будет постепенно увеличивать угол. Для каждого нового «радиана» мы можем получить координату x и y, используя функцииcos и sin соответственно. Соответствующие x и y теперь будут ограничены кругом, так как это даст нам точное место на краю круга.

Результат этих sin and cos чисел теперь находится между -1 и 1. Это считается нашим единичным кругом, поэтому, чтобы сделать его большим кругом, нам просто нужно умножить его на наш orbit radius. Одна крутая вещь

Попробуйте сами: https://www.mathsisfun.com/geometry/unit-circle.html

Следует отметить одну небольшую вещь. Нам нужно все время использовать нашу начальную позицию x и начальную позицию y вместо this.x += Math.cos(rad). Это даст непредвиденные результаты, и мы хотим только добавить результирующие x, y к нашей начальной позиции.

Используя эти концепции, давайте теперь вернемся и отредактируем наш update() метод.
Но сначала мы хотим отметить наши начальные x и начальные y pos. Вернувшись в constructor() нашего Planet, добавьте эти две переменные:

constructor(x, y, radius, color, velocity, orbitRadius) {
    this.x = x;
    this.y = y;
    this.startingPos = {
       x,
       y
    }
    this.radian = 0;
    ... // Everthing else
}

Здесь мы отмечаем начальную позицию таким образом, когда мы постоянно обновляем значения x и y, мы не теряем начальную позицию. И начинаем с угла 0 радиан.

Теперь в функции обновления внесите следующие изменения:

this.radian += this.velocity; // increase our angle every animation frame

// Get the new x based on our new angle and radius
this.x = this.startingPos.x + Math.cos(this.radian) * this.orbitRadius;
// Get the new y based on our new angle and radius
this.y = this.startingPos.y + Math.sin(this.radian) * this.orbitRadius;

Представьте, что начальная позиция - это верхний круг. Мы будем увеличивать угол со временем (добавляя скорость к радианам) и получим соответствующее новое положение (нижний кружок). Поскольку я упоминал, что наши значения от -1 до 1, мы можем умножить его на радиус нашей орбиты, чтобы получить новые x и y.

Как видите, наша планета ведет себя как планета.

Угол постоянно обновляется, и наш радиус 65 обновляет его, чтобы он находился за пределами нашего Солнца.

Давайте поработаем над рисованием круга орбиты, по которому будет следовать наша планета. Это можно сделать в функции рисования следующим образом:

Мы создаем новую «дугу», которую мы будем обводить с шириной линии 2. Координаты x и y будут нашими начальными точками для пути, а радиус орбиты - это радиус, который мы будем использовать для дуги.

Теперь, когда у нас это есть, давайте добавим другие планеты!

Вернувшись в init(), добавьте следующую вспомогательную функцию и код:

Я создал вспомогательную функцию для построения или Planet для нас и добавил оставшиеся планеты. Теперь вы должны увидеть что-то вроде этого:

Используя эти концепции, давайте добавим луну на каждую планету!

К счастью, каждая планета может просто знать о своем спутнике, и нам не нужно использовать какие-либо дополнительные конструкции на наших планетах. Все данные о Луне будут храниться в самом классе планет.

Итак, в constructor() для Planet добавьте следующий код:

constructor(x, y, radius, color, velocity, orbitRadius) {
    ... // Exisiting properties
     
    this.moon = {
        x: this.x + this.orbitRadius + this.radius,
        y,
        radian: 0,
        velocity: (Math.random() + 0.1) / 30
    };
}

Мы определяем положение нашей луны как текущее положение нашей планеты по оси x (поскольку она начинается в центре холста) + радиус орбиты + радиус планеты. Это поместит нашу луну прямо на край планеты (на 2d).

Теперь нарисуем нашу луну в функции draw() в самом низу:

// Moon (not sun)
if (this.velocity > 0) {
    c.beginPath();
    c.arc(this.moon.x, this.moon.y, 2, 0, Math.PI * 2, false);
    c.fillStyle = 'gray';
    c.fill();
}

Мы рисуем нашу луну, только если это не солнце. Мы знаем, что это не Солнце, по скорости, так как наше Солнце находится в состоянии покоя (я знаю, что это уловка, но я оставил его для примера)

Мы рисуем нашу луну с радиусом 2, это текущие координаты x и y.

Теперь давайте оживим нашу луну в функции обновления! Вернувшись к функции update() в самом низу, добавьте:

this.moon.radian += this.moon.velocity;
this.moon.x = this.x + Math.cos(this.moon.radian) * (this.radius + 5);
this.moon.y = this.y + Math.sin(this.moon.radian) * (this.radius + 5);

Это использует x и y текущей планеты и анимирует луну вокруг нее на расстоянии 5 пикселей от края планеты.

Теперь наши планеты и луна оживают вокруг нашего солнца. Ах, как красиво.

А теперь давайте добавим someStars!

Давайте создадим новую Class звезду и нарисуем ее.

Положение нашей звезды будет случайными координатами x и y в пределах размера нашего холста. Размер звезды (радиус) будет варьироваться от 0 до 2.

Теперь, вернувшись к функции init(), добавьте следующее:

Здесь мы добавили цикл for, чтобы добавить 400 звезд.

Далее нам просто нужно нарисовать каждую звезду. В функции анимации давайте переберем все звезды и нарисуем их.

Не забудьте добавить это над planets.forEach()

stars.forEach(star => {
    star.draw();
});

И это все!

Вот ссылка на полный исходный код.