Скидки до 28% + 2-ая профессия бесплатно и подарки на 50 000₽

Главная | Все статьи | Дневник студента

Как устроены активные умения в игре на JS

Время чтения статьи ~5 минут
Статья написана студентом Хекслета. Мнение автора может не совпадать с позицией редакции
Как устроены активные умения в игре на JS главное изображение

1 часть

2 часть

3 часть

В прошлый раз я описал как устроены классы персонажей, в этот раз расскажу об активных умениях.

class SkillHero {
  constructor(name, type, damage, description, iconPath, animationAttackNumber) {
    this.name = name;
    this.type = type;
    this.damage = Math.round(damage);
    this.description = description;
    this.iconPath = iconPath;
    this.animationAttackNumber = animationAttackNumber;
  }
}

В этот раз конструктор получился небольшим и относительно понятным.

name - название умения (только на английском. Об этом дальше).

type - тип урона (пока реализовано два: physical и piercing).

damage - указываем количество урона, которое будет нанесено врагу.

description - описание умения, которое будет появляться при наведении.

iconPath - путь до иконки умения.

animationAttackNumber - т.к. у каждого персонажа не меньше двух разных анимаций атак (их названия Attack1.png, Attack2.png...), то здесь указывается номер нужного изображения.

Вот как выглядит обычная атака у героя:

const attackSkill = new SkillHero('Sword Attack', 'physical', hero.attack, `Простая атака мечом. Наносит ${damageInHTML(hero.attack, 'physical')} физического урона`, 'images/icons/hero-skill-icons/icon-attack.png', 1);

Тут нужно пояснить момент с damageInHTML. Эту функцию я прописывал отдельно, и нужно она для того чтобы в описании цифры урона подсвечивались определённым цветом, в зависимости от типа урона.

const damageInHTML = (number, typeOfDamage) => `<span class="attack-skills__damage attack-skills__damage-${typeOfDamage}">${number}</span>`;

В свою очередь, в css есть соответствующие классы.

.attack-skills__damage-physical {
  color: rgb(255, 0, 0);
}

.attack-skills__damage-piercing {
  color: rgb(51, 255, 0);
}

.attack-skills__damage-magic {
  color: rgb(0, 0, 255);

Вот теперь можно переходить к методам.

dealDamage() {
    let damage;

    switch (this.type) {
      case 'physical':
        damage = this.damage - enemy.armor;
        break;
      case 'piercing':
        damage = this.damage;
        break;
    }

    enemy.health = enemy.health - damage;
    enemyHpHTML.innerHTML = enemy.health;
    enemyHitBarHTML.innerHTML = `-${damage}`;
  }

dealDamage - Тут всё просто. В зависимости от типа мы высчитываем урон. Потом этот урон вычитаем у жизней противника, и получившееся число записываем в хитбар оппонента и всплывающую табличку.

addSkillToInteface() {
    const idIcon = `${this.name.split(' ').join('')}-icon`;
    const idDescription = `${this.name.split(' ').join('')}-description`;

    attackSkillsIconsHTML.insertAdjacentHTML('beforeend', `<img class="attack-skills__icon" id="${idIcon}" src="${this.iconPath}">`);

    attackSkillsDescriptionsHTML.insertAdjacentHTML('beforeend', `<p class="attack-skills__description" id="${idDescription}"><span class="attack-skills__name">${this.name}.</span> ${this.description}</p>`);

    const icon = document.getElementById(idIcon);
    const description = document.getElementById(idDescription);

    icon.onmouseover = () => {
      description.style.display = 'block';
    };

    icon.onmouseout = () => {
      description.style.display = 'none';
    };

    icon.onclick = () => {
      hero.endTurn();
      hero.stopAnimationIdle();
      hero.animateRun();
      switch (this.animationAttackNumber) {
        case 1:
          setTimeout(hero.animateAttack1.bind(hero), hero.animationTime);
          break;
        case 2:
          setTimeout(hero.animateAttack2.bind(hero), hero.animationTime);
          break;
        case 3:
          setTimeout(hero.animateAttack3.bind(hero), hero.animationTime);
          break;
      }
      setTimeout(hero.animateRunBack.bind(hero), (hero.animationTime * 2));

      this.dealDamage();

      if (enemy.health <= 0) {
        setTimeout(enemy.animateDeath.bind(enemy), (hero.animationTime * 1.5));
        setTimeout(enemy.animateHitBar.bind(enemy), (hero.animationTime * 1.5));
        setTimeout(() => alert(`Поздравляем! Могущественный ${enemy.name} повержен! Обновите страницу, чтобы совершить ещё один подвиг!`), (enemy.animationTime * 3.5));
      } else {
        setTimeout(enemy.animateHit.bind(enemy), (hero.animationTime * 1.5));
        setTimeout(enemy.startTurn.bind(enemy), (hero.animationTime * 3));
      }
    };
  }

addSkillToInteface это самое страшное, что вы сегодня увидите. Этот метод делает сразу две вещи - добавляет иконку умения в HTML код, и вешает на неё onclick.

idIcon и idDescription создают id для элементов из имени умения, и именно по этой причине название умения должно быть на английском. Далее эти элементы добавляются в DOM. Включаем элемент description при наведении мыши, а на onmouseout отключаем.

В icon.onclick мы прописываем логику при нажатии мыши. Тут я использовал кучу setTimeout, чтобы анимации, такие как бег, атака и бег обратно, шли друг за другом. При вызове некоторых методов терялся контекст, так что через bind я привязал контекст где только можно было, просто на всякий случай. Логику с таймаутами я писал до того как прошёл курс по асинхронному программированию, но т.к. это была самая тяжёлая для моего понимания тема, я не уверен что с помощью async/await можно будет решить эту проблему. В ближайшей время собираюсь попробовать переписать эту логику.

Что касается класса SkillEnemy, то как и в случае с классами персонажей, он не сильно отличается от SkillHero. Главное отличие заключается в том, что в методе addSkillToInteface, мы не прописываем логику при нажатии кнопки, т.к. враг автоматически совершает атаку сразу после нашей. Вместо этого мы создаём метод useSkill, в котором и описывается логика.

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

Код проекта на GitHub.

А здесь можно поиграть в текущую версию.

Аватар пользователя Георгий Баратели
Георгий Баратели 03 февраля 2021
3
Похожие статьи
Рекомендуемые программы
профессия
Верстка на HTML5 и CSS3, Программирование на JavaScript в браузере, разработка клиентских приложений используя React
10 месяцев
с нуля
Старт 26 декабря
профессия
Программирование на Python, Разработка веб-приложений и сервисов используя Django, проектирование и реализация REST API
10 месяцев
с нуля
Старт 26 декабря
профессия
Тестирование веб-приложений, чек-листы и тест-кейсы, этапы тестирования, DevTools, Postman, SQL, Git, HTTP/HTTPS, API
4 месяца
с нуля
Старт 26 декабря
профессия
Программирование на Java, Разработка веб-приложений и микросервисов используя Spring Boot, проектирование REST API
10 месяцев
с нуля
Старт 26 декабря
профессия
новый
Google таблицы, SQL, Python, Superset, Tableau, Pandas, визуализация данных, Anaconda, Jupyter Notebook, A/B-тесты, ROI
9 месяцев
с нуля
Старт 26 декабря
профессия
Программирование на PHP, Разработка веб-приложений и сервисов используя Laravel, проектирование и реализация REST API
10 месяцев
с нуля
Старт 26 декабря
профессия
Программирование на Ruby, Разработка веб-приложений и сервисов используя Rails, проектирование и реализация REST API
5 месяцев
c опытом
Старт 26 декабря
профессия
Программирование на JavaScript в браузере и на сервере (Node.js), разработка бекендов на Fastify и фронтенда на React
16 месяцев
с нуля
Старт 26 декабря
профессия
Программирование на JavaScript, разработка веб-приложений, bff и сервисов используя Fastify, проектирование REST API
10 месяцев
с нуля
Старт 26 декабря
профессия
новый
Git, JavaScript, Playwright, бэкенд-тесты, юнит-тесты, API-тесты, UI-тесты, Github Actions, HTTP/HTTPS, API, Docker, SQL
8 месяцев
c опытом
Старт 26 декабря