Оновіть сторінку для нового положення випадкових квадратів!

Перехід до об’єктно-орієнтованого зіткнення

Вітаю! Ви пройшли через значну кількість коду з виявлення колізій. Але ці приклади призначені лише для простої демонстрації того, як працюють алгоритми. Об’єднання їх у більші проекти, ймовірно, означає переведення вашого коду на об’єктно-орієнтований підхід. (Чудовий вступ до об’єктно-орієнтованого програмування можна подивитись у книзі Деніела Шиффмана Природа коду).

Навіщо? Скажімо, у нас є круг та купа прямокутників, як у інтерактивному прикладі вище. Ми могли б зберігати окремі позиції, розміри та стан зіткнення для кожного з них, але це швидко призведе до безладдя. Натомість класи Circle і Rectangle нададуть нашому коду набагато більше зручності та гнучкості.

Почнімо зі створення класу Circle:

class Circle {
  constructor(x, y, r) {
    this.x = x; // x-координата
    this.y = y; // y-координата
    this.r = r; // радіус
  }

  update(x, y) {
    this.x = x;
    this.y = y;
  }

  // малювання круга
  display() {
    fill(0, 150);
    noStroke();
    ellipse(this.x, this.y, this.r * 2, this.r * 2);
  }
}

Це було досить просто. Ми також можемо створити базовий клас Rectangle:

class Rectangle {
  constructor(x, y, w, h) {
    // xy-координати лівого кута
    this.x = x;
    this.y = y;
    this.w = w; // ширина,
    this.h = h; // висота,
    this.isHit = false; // чи є зіткнення
  }

  // перевірка на зіткнення з кругом
  // використовуючи функцію isCircleWithRectCollides, яку ми зробили на початку
  checkCollisionWithCircle(c) {
    this.isHit = isCircleWithRectCollides(c.x, c.y, c.r, this.x, this.y, this.w, this.h);
  }

  // малювання прямокутника
  display() {
    // при зіткненні змінюємо колір
    if (this.isHit) {
      fill(255, 150, 0);
    } else {
      fill(0, 150, 255);
    }
    noStroke();
    rect(this.x, this.y, this.w, this.h);
  }
}

Зауважте, що у класі Rectangle у нас є змінна під назвою isHit. У ній ми будемо зберігати поточний стан обʼєкта стосовно того, чи він має колізію з кругом, і відповідно змінювати колір його заливки. За замовчуванням значення встановлено на false.

У нас буде лише один обʼєкт типу Circle, але ми створимо масив об’єктів Rectangle. Ось так виглядатиме функція draw(), яка малюватиме наші фігури і оновлюватиме положення круга:

function draw() {
  background(255);

  // перебір усіх прямокутників
  for (const rectangle of rects) {
    rectangle.checkCollisionWithCircle(circle);  // перевірка на колізію
    rectangle.display();                         // малювання прямокутника
  }

  // оновлення положення круга положенням курсора і його зображення
  circle.update(mouseX, mouseY);
  circle.display();
}

Отже, як ми перевіримо, чи коло прямокутник зіткнувя з кругом? Давайте створимо у класі Rectangle метод (внутрішню функцію) під назвою checkCollisionWithCircle(). Ми передамо у цей метод об’єкт Circle як аргумент, а потім виконаємо базовий тест на перевірку зіткнення круга з прямокутником.

function checkCollision(c) {
    this.isHit = isCircleWithRectCollides(c.x,c.y,c.r, x,y,w,h);
}

Результат функції isCircleWithRectCollides() встановлює значення змінної isHit на true або false, що, у свою чергу, змінює колір заливки. Тепер ми просто додаємо виклик цьої перевірки у функцію draw() loop:

// перебір усіх прямокутників
for (const rectangle of rects) {
    rectangle.checkCollisionWithCircle(circle);  // перевірка на колізію
    rectangle.display();                         // малювання прямокутника
}

Дуже добре! Ось повний код:

// змінна для єдиного обʼєкту круга, що контролюватиметься курсором
let circle;
// масив прямокутників
let rects = new Array(8);

function setup() {
  createCanvas(window.innerWidth, window.innerHeight);

  // створення круга з радіусом у 30 пікселів
  circle = new Circle(0, 0, 30);

  // генерація прямокутників у випадкових місцях, але з привʼязкою до сітки у 50 пікселів
  for (let i = 0; i < rects.length; i++) {
    const x = round(random(50, width - 50) / 50) * 50;
    const y = round(random(50, height - 50) / 50) * 50;
    rects[i] = new Rectangle(x, y, 50, 50);
  }
}

function draw() {
  background(255);

  // перебір усіх прямокутників
  for (const rectangle of rects) {
    rectangle.checkCollisionWithCircle(circle);  // перевірка на колізію
    rectangle.display();                         // малювання прямокутника
  }

  // оновлення положення круга положенням курсора і його зображення
  circle.update(mouseX, mouseY);
  circle.display();
}


class Circle {
  constructor(x, y, r) {
    this.x = x; // x-координата
    this.y = y; // y-координата
    this.r = r; // радіус
  }

  update(x, y) {
    this.x = x;
    this.y = y;
  }

  // малювання круга
  display() {
    fill(0, 150);
    noStroke();
    ellipse(this.x, this.y, this.r * 2, this.r * 2);
  }
}


class Rectangle {
  constructor(x, y, w, h) {
    this.x = x; // x-координата лівого кута
    this.y = y; // y-координата лівого кута
    this.w = w; // ширина
    this.h = h; // висота
    this.isHit = false; // чи є зіткнення
  }

  // перевірка на зіткнення з кругом
  // використовуючи функцію isCircleWithRectCollides, яку ми зробили на початку
  checkCollisionWithCircle(circleObj) {
    this.isHit = isCircleWithRectCollides(circleObj.x, circleObj.y, circleObj.r, this.x, this.y, this.w, this.h);
  }

  // малювання прямокутника
  display() {
    // при зіткненні змінюємо колір
    if (this.isHit) {
      fill(255, 150, 0);
    } else {
      fill(0, 150, 255);
    }
    noStroke();
    rect(this.x, this.y, this.w, this.h);
  }
}


// перевірка на перетин між кругом і прямокутником
function isCircleWithRectCollides(cx, cy, radius, rx, ry, rw, rh) {
  // тестові змінні точки, з якою буде відбуватися перевірка на перетин
  let testX = cx;
  let testY = cy;

  // які координати квадрата знаходяться найближче до круга?
  if (cx < rx)         testX = rx;        // compare to left edge
  else if (cx > rx+rw) testX = rx+rw;     // right edge
  if (cy < ry)         testY = ry;        // top edge
  else if (cy > ry+rh) testY = ry+rh;     // bottom edge

  // визначення відстані до найближчої точки ребра прямокутника, якщо круг за межами прямокутника
  const distX = cx-testX;
  const distY = cy-testY;
  const distance = sqrt((distX*distX) + (distY*distY));

  // якщо відстань менша за радіус круга це колізія!
  return distance <= radius;
}

Зверніть увагу, що наш код трохи задовгий, бо включає усі потрібні класи, тому фактичний проєкт прикладу у репозиорії розбитий на окремі файли. За потреби ви можете обʼєднати усі допоміжні функції по виявленню колізій, які необхідні для вашого проєкту, в один файл під назвою collision-functions.

Ви можете побачити інший, більш складний приклад об’єктно-орієнтованої колізії у вступі. Він використовує класи для кругів, прямокутників і ліній.

Далі: Робота з перетвореннями матриці