В прошлый раз я описал как устроены классы персонажей, в этот раз расскажу об активных умениях.
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.
А здесь можно поиграть в текущую версию.