You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Web-Dev-For-Beginners/translations/uk/6-space-game/3-moving-elements-around/README.md

34 KiB

Створення космічної гри, частина 3: Додавання руху

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

Коли інженери NASA програмували комп'ютер керування для місій Apollo, вони стикалися з подібним викликом: як зробити так, щоб космічний корабель реагував на команди пілота, одночасно автоматично коригуючи курс? Принципи, які ми сьогодні вивчимо, відображають ті самі концепції управління рухом, контрольованим гравцем, поряд із автоматичними системними поведінками.

У цьому уроці ви навчитеся змушувати космічні кораблі ковзати по екрану, реагувати на команди гравця та створювати плавні рухи. Ми розділимо все на зрозумілі концепції, які природно доповнюють одна одну.

До кінця уроку гравці зможуть керувати своїм героїчним кораблем на екрані, а ворожі судна патрулюватимуть зверху. Що важливіше, ви зрозумієте основні принципи, які забезпечують рух у системах ігор.

Тест перед лекцією

Тест перед лекцією

Розуміння руху в іграх

Ігри оживають, коли об'єкти починають рухатися, і є два основні способи, як це відбувається:

  • Рух, контрольований гравцем: Коли ви натискаєте клавішу або клацаєте мишкою, щось рухається. Це прямий зв'язок між вами та світом гри.
  • Автоматичний рух: Коли сама гра вирішує рухати об'єкти наприклад, ті ворожі кораблі, які повинні патрулювати екран незалежно від ваших дій.

Змусити об'єкти рухатися на екрані комп'ютера простіше, ніж ви думаєте. Пам'ятаєте ті координати x і y з уроків математики? Саме з ними ми тут працюємо. Коли Галілей відстежував супутники Юпітера у 1610 році, він фактично робив те саме визначав позиції з часом, щоб зрозуміти моделі руху.

Рух об'єктів на екрані схожий на створення анімації у блокноті потрібно виконати три прості кроки:

  1. Оновити позицію Змінити місце розташування об'єкта (наприклад, перемістити його на 5 пікселів вправо)
  2. Стерти старий кадр Очистити екран, щоб не залишалися сліди
  3. Намалювати новий кадр Розмістити об'єкт у новій точці

Робіть це достатньо швидко, і вуаля! Ви отримуєте плавний рух, який здається природним для гравців.

Ось як це може виглядати в коді:

// Set the hero's location
hero.x += 5;
// Clear the rectangle that hosts the hero
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Redraw the game background and hero
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.drawImage(heroImg, hero.x, hero.y);

Що робить цей код:

  • Оновлює x-координату героя на 5 пікселів для горизонтального руху
  • Очищає всю область полотна, щоб видалити попередній кадр
  • Заповнює полотно чорним фоном
  • Перемальовує зображення героя на новій позиції

Чи можете ви придумати причину, чому багаторазове перемальовування героя за секунду може спричинити витрати продуктивності? Читайте про альтернативи цьому шаблону.

Обробка подій клавіатури

Тут ми з'єднуємо введення гравця з діями гри. Коли хтось натискає пробіл, щоб випустити лазер, або натискає стрілку, щоб ухилитися від астероїда, ваша гра повинна виявити та відповісти на цей ввід.

Події клавіатури відбуваються на рівні вікна, тобто весь ваш браузер слухає ці натискання клавіш. Клацання миші, з іншого боку, можуть бути прив'язані до конкретних елементів (наприклад, клацання кнопки). Для нашої космічної гри ми зосередимося на управлінні клавіатурою, оскільки це дає гравцям класичне аркадне відчуття.

Це нагадує мені, як оператори телеграфу в 1800-х роках повинні були переводити введення азбуки Морзе в осмислені повідомлення ми робимо щось схоже, переводячи натискання клавіш у команди гри.

Щоб обробити подію, потрібно використовувати метод addEventListener() вікна та надати йому два параметри. Перший параметр це назва події, наприклад keyup. Другий параметр це функція, яка має бути викликана в результаті події.

Ось приклад:

window.addEventListener('keyup', (evt) => {
  // evt.key = string representation of the key
  if (evt.key === 'ArrowUp') {
    // do something
  }
});

Розбір того, що тут відбувається:

  • Слухає події клавіатури у всьому вікні
  • Захоплює об'єкт події, який містить інформацію про те, яка клавіша була натиснута
  • Перевіряє, чи натиснута клавіша відповідає певній клавіші (у цьому випадку стрілці вгору)
  • Виконує код, коли умова виконана

Для подій клавіш є дві властивості в об'єкті події, які можна використовувати, щоб дізнатися, яка клавіша була натиснута:

  • key - це текстове представлення натиснутої клавіші, наприклад 'ArrowUp'
  • keyCode - це числове представлення, наприклад 37, відповідає ArrowLeft

Маніпуляція подіями клавіш корисна не лише для розробки ігор. Які ще застосування ви можете придумати для цієї техніки?

Спеціальні клавіші: важливе зауваження!

Деякі клавіші мають вбудовану поведінку браузера, яка може заважати вашій грі. Стрілки прокручують сторінку, а пробіл переміщує вниз поведінка, яка вам не потрібна, коли хтось намагається керувати своїм космічним кораблем.

Ми можемо запобігти цим стандартним діям і дозволити нашій грі обробляти введення. Це схоже на те, як ранні програмісти комп'ютерів повинні були перевизначати системні переривання, щоб створювати власну поведінку ми просто робимо це на рівні браузера. Ось як:

const onKeyDown = function (e) {
  console.log(e.keyCode);
  switch (e.keyCode) {
    case 37:
    case 39:
    case 38:
    case 40: // Arrow keys
    case 32:
      e.preventDefault();
      break; // Space
    default:
      break; // do not block other keys
  }
};

window.addEventListener('keydown', onKeyDown);

Розуміння цього коду запобігання:

  • Перевіряє певні коди клавіш, які можуть спричинити небажану поведінку браузера
  • Запобігає стандартній дії браузера для стрілок і пробілу
  • Дозволяє іншим клавішам працювати нормально
  • Використовує e.preventDefault(), щоб зупинити вбудовану поведінку браузера

Рух, викликаний грою

Тепер поговоримо про об'єкти, які рухаються без введення гравця. Подумайте про ворожі кораблі, що рухаються по екрану, кулі, що летять прямими лініями, або хмари, що дрейфують на задньому плані. Цей автономний рух робить світ вашої гри живим, навіть коли ніхто не торкається управління.

Ми використовуємо вбудовані таймери JavaScript, щоб оновлювати позиції через регулярні інтервали. Ця концепція схожа на те, як працюють маятникові годинники регулярний механізм, який запускає послідовні, синхронізовані дії. Ось як це просто:

const id = setInterval(() => {
  // Move the enemy on the y axis
  enemy.y += 10;
}, 100);

Що робить цей код руху:

  • Створює таймер, який запускається кожні 100 мілісекунд
  • Оновлює y-координату ворога на 10 пікселів кожного разу
  • Зберігає ID інтервалу, щоб ми могли зупинити його пізніше, якщо потрібно
  • Рухає ворога вниз по екрану автоматично

Цикл гри

Ось концепція, яка об'єднує все цикл гри. Якби ваша гра була фільмом, цикл гри був би кінопроектором, який показує кадр за кадром так швидко, що все здається плавно рухомим.

Кожна гра має один із таких циклів, що працює за лаштунками. Це функція, яка оновлює всі об'єкти гри, перемальовує екран і повторює цей процес безперервно. Це дозволяє відстежувати вашого героя, усіх ворогів, будь-які лазери, що летять весь стан гри.

Ця концепція нагадує мені, як ранні аніматори, такі як Волт Дісней, повинні були перемальовувати персонажів кадр за кадром, щоб створити ілюзію руху. Ми робимо те саме, тільки за допомогою коду, а не олівців.

Ось як зазвичай виглядає цикл гри, виражений у коді:

const gameLoopId = setInterval(() => {
  function gameLoop() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = "black";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    drawHero();
    drawEnemies();
    drawStaticObjects();
  }
  gameLoop();
}, 200);

Розуміння структури циклу гри:

  • Очищає все полотно, щоб видалити попередній кадр
  • Заповнює фон суцільним кольором
  • Малює всі об'єкти гри на їхніх поточних позиціях
  • Повторює цей процес кожні 200 мілісекунд, щоб створити плавну анімацію
  • Керує частотою кадрів, контролюючи час інтервалу

Продовження космічної гри

Тепер ми додамо рух до статичної сцени, яку ви створили раніше. Ми перетворимо її зі скріншота на інтерактивний досвід. Ми будемо працювати над цим крок за кроком, щоб кожна частина доповнювала попередню.

Візьміть код, з якого ми зупинилися в попередньому уроці (або почніть із коду в папці Part II- starter, якщо вам потрібен новий старт).

Ось що ми створюємо сьогодні:

  • Управління героєм: Стрілки будуть керувати вашим космічним кораблем на екрані
  • Рух ворогів: Ті інопланетні кораблі почнуть наступ

Давайте почнемо реалізовувати ці функції.

Рекомендовані кроки

Знайдіть файли, які були створені для вас у підпапці your-work. Вона повинна містити наступне:

-| assets
  -| enemyShip.png
  -| player.png
-| index.html
-| app.js
-| package.json

Ви починаєте свій проект у папці your-work, ввівши:

cd your-work
npm start

Що робить ця команда:

  • Переходить до вашої директорії проекту
  • Запускає HTTP-сервер за адресою http://localhost:5000
  • Служить вашими файлами гри, щоб ви могли протестувати їх у браузері

Вищезазначене запустить HTTP-сервер за адресою http://localhost:5000. Відкрийте браузер і введіть цю адресу, зараз він повинен відображати героя та всіх ворогів; нічого не рухається - поки що!

Додайте код

  1. Додайте спеціальні об'єкти для hero, enemy та game object, вони повинні мати властивості x та y. (Пам'ятайте розділ про Спадкування або композицію).

    ПІДКАЗКА game object має бути тим, що має x та y і здатність малювати себе на полотні.

    Порада: Почніть із додавання нового класу GameObject із його конструктором, як показано нижче, а потім намалюйте його на полотні:

    class GameObject {
      constructor(x, y) {
        this.x = x;
        this.y = y;
        this.dead = false;
        this.type = "";
        this.width = 0;
        this.height = 0;
        this.img = undefined;
      }
    
      draw(ctx) {
        ctx.drawImage(this.img, this.x, this.y, this.width, this.height);
      }
    }
    

    Розуміння цього базового класу:

    • Визначає загальні властивості, які мають усі об'єкти гри (позиція, розмір, зображення)
    • Включає прапорець dead, щоб відстежувати, чи об'єкт має бути видалений
    • Надає метод draw(), який відображає об'єкт на полотні
    • Встановлює значення за замовчуванням для всіх властивостей, які дочірні класи можуть перевизначити

    Тепер розширте цей GameObject, щоб створити Hero та Enemy:

    class Hero extends GameObject {
      constructor(x, y) {
        super(x, y);
        this.width = 98;
        this.height = 75;
        this.type = "Hero";
        this.speed = 5;
      }
    }
    
    class Enemy extends GameObject {
      constructor(x, y) {
        super(x, y);
        this.width = 98;
        this.height = 50;
        this.type = "Enemy";
        const id = setInterval(() => {
          if (this.y < canvas.height - this.height) {
            this.y += 5;
          } else {
            console.log('Stopped at', this.y);
            clearInterval(id);
          }
        }, 300);
      }
    }
    

    Основні концепції в цих класах:

    • Наслідує від GameObject за допомогою ключового слова extends
    • Викликає конструктор батьківського класу за допомогою super(x, y)
    • Встановлює конкретні розміри та властивості для кожного типу об'єкта
    • Реалізує автоматичний рух для ворогів за допомогою setInterval()
  2. Додайте обробники подій клавіш, щоб керувати навігацією (рух героя вгору/вниз, вліво/вправо)

    ПАМ'ЯТАЙТЕ, це декартова система, верхній лівий кут 0,0. Також пам'ятайте додати код для зупинки стандартної поведінки

    Порада: Створіть функцію onKeyDown і прикріпіть її до вікна:

    const onKeyDown = function (e) {
      console.log(e.keyCode);
      // Add the code from the lesson above to stop default behavior
      switch (e.keyCode) {
        case 37:
        case 39:
        case 38:
        case 40: // Arrow keys
        case 32:
          e.preventDefault();
          break; // Space
        default:
          break; // do not block other keys
      }
    };
    
    window.addEventListener("keydown", onKeyDown);
    

    Що робить цей обробник подій:

    • Слухає події натискання клавіш у всьому вікні
    • Логує код клавіші, щоб допомогти вам налагодити, які клавіші натискаються
    • Запобігає стандартній поведінці браузера для стрілок і пробілу
    • Дозволяє іншим клавішам працювати нормально

    Перевірте консоль вашого браузера на цьому етапі та спостерігайте за натисканнями клавіш, які логуються.

  3. Реалізуйте Шаблон Pub-Sub, це допоможе зберегти ваш код чистим, коли ви будете слідувати наступним частинам.

    Шаблон Publish-Subscribe допомагає організувати ваш код, розділяючи виявлення подій від їх обробки. Це робить ваш код більш модульним і легшим для підтримки.

    Для виконання цього останнього пункту ви можете:

    1. Додати слухач подій у вікно:

      window.addEventListener("keyup", (evt) => {
        if (evt.key === "ArrowUp") {
          eventEmitter.emit(Messages.KEY_EVENT_UP);
        } else if (evt.key === "ArrowDown") {
          eventEmitter.emit(Messages.KEY_EVENT_DOWN);
        } else if (evt.key === "ArrowLeft") {
          eventEmitter.emit(Messages.KEY_EVENT_LEFT);
        } else if (evt.key === "ArrowRight") {
          eventEmitter.emit(Messages.KEY_EVENT_RIGHT);
        }
      });
      

    Що робить ця система подій:

    • Виявляє введення клавіатури та перетворює його на власні події гри
    • Розділяє виявлення введення від логіки гри
    • Полегшує зміну управління пізніше без впливу на код гри
    • Дозволяє кільком системам реагувати на одне введення
    1. Створіть клас EventEmitter, щоб публікувати та підписуватися на повідомлення:

      class EventEmitter {
        constructor() {
          this.listeners = {};
        }
      
        on(message, listener) {
          if (!this.listeners[message]) {
            this.listeners[message] = [];
          }
          this.listeners[message].push(listener);
        }
      
      
    2. Додайте константи та налаштуйте EventEmitter:

      const Messages = {
        KEY_EVENT_UP: "KEY_EVENT_UP",
        KEY_EVENT_DOWN: "KEY_EVENT_DOWN",
        KEY_EVENT_LEFT: "KEY_EVENT_LEFT",
        KEY_EVENT_RIGHT: "KEY_EVENT_RIGHT",
      };
      
      let heroImg, 
          enemyImg, 
          laserImg,
          canvas, ctx, 
          gameObjects = [], 
          hero, 
          eventEmitter = new EventEmitter();
      

    Розуміння налаштування:

    • Визначає константи повідомлень, щоб уникнути помилок і полегшити рефакторинг
    • Оголошує змінні для зображень, контексту полотна та стану гри
    • Створює глобальний EventEmitter для системи pub-sub
    • Ініціалізує масив для зберігання всіх об'єктів гри
    1. Ініціалізуйте гру

      function initGame() {
        gameObjects = [];
        createEnemies();
        createHero();
      
        eventEmitter.on(Messages.KEY_EVENT_UP, () => {
          hero.y -= 5;
        });
      
        eventEmitter.on(Messages.KEY_EVENT_DOWN, () => {
          hero.y += 5;
        });
      
        eventEmitter.on(Messages.KEY_EVENT_LEFT, () => {
          hero.x -= 5;
        });
      
      
  4. Налаштуйте цикл гри

    Переробіть функцію window.onload, щоб

  • Створює сітку ворогів за допомогою вкладених циклів
  • Призначає зображення ворога кожному об'єкту ворога
  • Додає кожного ворога до глобального масиву об'єктів гри

і додайте функцію createHero(), щоб виконати схожий процес для героя.

```javascript
function createHero() {
  hero = new Hero(
    canvas.width / 2 - 45,
    canvas.height - canvas.height / 4
  );
  hero.img = heroImg;
  gameObjects.push(hero);
}
```

Що робить створення героя:

  • Розміщує героя внизу по центру екрану
  • Призначає зображення героя об'єкту героя
  • Додає героя до масиву об'єктів гри для відображення

і нарешті, додайте функцію drawGameObjects(), щоб почати малювання:

```javascript
function drawGameObjects(ctx) {
  gameObjects.forEach(go => go.draw(ctx));
}
```

Розуміння функції малювання:

  • Ітерує через всі об'єкти гри в масиві
  • Викликає метод draw() для кожного об'єкта
  • Передає контекст canvas, щоб об'єкти могли самостійно відображатися

Ваші вороги повинні почати наступ на ваш космічний корабель героя!
}
}
```

and add a `createHero()` function to do a similar process for the hero.

```javascript
function createHero() {
  hero = new Hero(
    canvas.width / 2 - 45,
    canvas.height - canvas.height / 4
  );
  hero.img = heroImg;
  gameObjects.push(hero);
}
```

і нарешті, додайте функцію drawGameObjects(), щоб почати малювання:

```javascript
function drawGameObjects(ctx) {
  gameObjects.forEach(go => go.draw(ctx));
}
```

Ваші вороги повинні почати наступ на ваш космічний корабель героя!


Виклик GitHub Copilot Agent 🚀

Ось виклик, який покращить ваш ігровий процес: додавання меж екрану та плавного управління. Зараз ваш герой може вилетіти за межі екрану, а рух може здаватися ривковим.

Ваше завдання: Зробіть рух вашого космічного корабля більш реалістичним, реалізувавши межі екрану та плавне управління. Це схоже на те, як системи управління польотом NASA запобігають перевищенню безпечних параметрів роботи космічного корабля.

Що потрібно зробити: Створіть систему, яка утримує ваш космічний корабель героя на екрані, і зробіть управління плавним. Коли гравці утримують клавішу зі стрілкою, корабель повинен ковзати безперервно, а не рухатися дискретними кроками. Розгляньте можливість додавання візуального зворотного зв'язку, коли корабель досягає меж екрану можливо, легкий ефект, щоб вказати на край ігрової зони.

Дізнайтеся більше про режим агента тут.

🚀 Виклик

Організація коду стає все важливішою, коли проєкти зростають. Ви могли помітити, що ваш файл переповнений функціями, змінними та класами, змішаними разом. Це нагадує, як інженери, які організовували код місії Apollo, повинні були створювати чіткі, підтримувані системи, над якими могли працювати кілька команд одночасно.

Ваше завдання:
Думайте як архітектор програмного забезпечення. Як би ви організували свій код, щоб через шість місяців ви (або ваш колега) могли зрозуміти, що відбувається? Навіть якщо все залишається в одному файлі зараз, ви можете створити кращу організацію:

  • Групування пов'язаних функцій разом із чіткими заголовками коментарів
  • Розділення завдань відокремте логіку гри від відображення
  • Використання послідовних імен для змінних і функцій
  • Створення модулів або просторів імен для організації різних аспектів вашої гри
  • Додавання документації, яка пояснює призначення кожного основного розділу

Питання для роздумів:

  • Які частини вашого коду найважче зрозуміти, коли ви повертаєтеся до них?
  • Як ви могли б організувати свій код, щоб іншим було легше внести свій вклад?
  • Що станеться, якщо ви захочете додати нові функції, такі як бонуси або різні типи ворогів?

Післялекційний тест

Післялекційний тест

Огляд і самостійне навчання

Ми створювали все з нуля, що чудово для навчання, але ось маленький секрет існують дивовижні фреймворки JavaScript, які можуть виконати багато важкої роботи за вас. Коли ви відчуєте себе комфортно з основами, які ми розглянули, варто дослідити, що доступно.

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

Речі, які варто дослідити:

  • Як ігрові рушії організовують код ви будете здивовані їхніми розумними підходами
  • Трюки для підвищення продуктивності, щоб ігри на canvas працювали плавно
  • Сучасні функції JavaScript, які можуть зробити ваш код чистішим і більш підтримуваним
  • Різні підходи до управління об'єктами гри та їх взаємозв'язками

Завдання

Коментуйте ваш код


Відмова від відповідальності:
Цей документ був перекладений за допомогою сервісу автоматичного перекладу Co-op Translator. Хоча ми прагнемо до точності, будь ласка, майте на увазі, що автоматичні переклади можуть містити помилки або неточності. Оригінальний документ на його рідній мові слід вважати авторитетним джерелом. Для критичної інформації рекомендується професійний людський переклад. Ми не несемо відповідальності за будь-які непорозуміння або неправильні тлумачення, що виникають внаслідок використання цього перекладу.