Пошаговая боевая система на примере JS

от автора

Возможно, вы, как программист, когда-то интересовались пошаговыми стратегиями. В этой статье я решил рассказать о собственном взгляде на эту тему, используя JavaScript
Кто заинтересован — добро пожаловать под кат


Всем привет, это stalker320, я отсутствовал какое-то время и только вернулся из спячки.

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

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

  • Класс, ответственный за создание игрока и моба, класс Entity;

  • Класс инвентаря;

  • Класс предмета;

  • И напоследок класс действующего эффекта.

Класс Effect

Начинать нужно с того элемента, который задействует наименьшее количество упоминаний других элементов. В нашем случае это класс Effect и у него будет два свойства name и steps_left.

class Effect {   constructor(name, steps_left) {     this.name = name;     this.steps_left = steps_left;   }   get_name() {     return this.name;   }   get_steps_left() {     return this.steps_left;   }   count() {     // отсчитывает 1 ход.     this.steps_left -= 1;   } }

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

// Кровотечение class BleedingEffect extends Effect {   constructor(name, steps_left, damage) {     super(name, steps_left);     this.damage = damage;   }   get_damage() {     return this.damage;   } }  // Регенерация class RegeneartionEffect extends Effect {   constructor(name, steps_left, heal) {     super(name, steps_left);     this.heal = heal;   }   get_heal_count() {     return this.heal;   } }  // Пассивная броня каждый ход class PassiveArmorEffect extends Effect {   constructor(name, steps_left, armor) {     super(name, steps_left);     this.armor = armor;   }   get_armor() {     return this.armor;   } }

А ещё более интересным методом создания эффектов является создание на месте.

let berserk = new class BerserkEffect extends Effect {   constructor(name, steps_left) {     super(name, steps_left);   }   get_dmg_multiplier() {return 1.5;}   get_resistance() {return -0.5;} // добавляет отрицательное сопротивление урону, что увеличивает получаемый урон на 50% }();

Класс Item

Предмет представляет собой шаблон для многого — оружие, инструменты, действия.

class Item {   constructor(name) {     this.name = name;   }   get_name() {     return this.name;   } }

Сам класс Item нам ничего не даёт, но является отправной точкой для других классов. В качестве примера я приведу ещё несколько классов, наследующихся от Item.

class Weapon extends Item {   constructor(name, damage, effect) {     super(name);     this.damage = damage;     this.effect = effect;   }   get_damage() {     return this.damage;   }   get_effect() {     return this.effect;   } }

Класс Weapon имеет два свойства в дополнение к свойствам Item. Это свойства damage и effect.

class Shield extends Item {   constructor(name, armor) {     super(name);     this.armor = armor;   }   get_shield() {     return this.armor;   } }

Щит, позволяет пропустить некоторое количество урона мимо полосы здоровья

class Potion extends Item {   constructor(name, effect) {     super(name);     this.effect = effect;   }   get_effect() {     return this.effect;   } } // Зелье лечения за один ход, как пример class HealPotion extends Potion {   constructor(name, heal_count) {     super(name, new RegenerationEffect(name, 1, heal_count));   } } class RegeneratonPotion extends Potion {   constructor(name, steps, heal_count) {     super(name, new RegenerationEffect(name, steps, heal_count));   } }

А также тонны различных видов зелий, накладывающих соответствующие эффекты на персонажа при применении.

Класс Inventory

Простой контейнер для массива, ограничивающий входящий тип данных и размер.

class Inventory {   // Инвентарь представляет собой место, где хранятся все предметы   container;   container_size = 16;      constructor(container_size = 16) {     this.container_size = container_size;     this.container = Array();   }   set_item(idx, item) {     /**      * idx - число      * item - объект от класса Item     */     if ( !(item instanceof Item)) {       throw new Error("item isn't instance of Item");     }     if (idx < 0 || idx > this.container_size) {       throw new Error("idx out of bounds");     }     this.container[idx] = item;   }   get_item(idx) {     /**       * idx - число     */     if (idx < 0 || idx > this.container_size) {       throw new Error("idx out of bounds");     }     return container[idx];   } }

Класс Entity

Этот класс наверное второй по важности класс. Он отвечает за всех мобов и игрока, созданных в дальнейшем.
У него будет не особо много свойств, но они будут уместными

— максимальное здоровье max_health
— текущее здоровье health
— Щит shield
— инвентарь от Inventory inventory
— эффекты effects
— очки действий steps
— максимум очков действий max_steps

class Entity {   max_health;   health;   shield = 0;   inventory = new Inventory(16);   effects = Array();   steps;   max_steps;   weapon_idx = -1;   constructor(max_health = 100, max_steps = 3) {     this.max_health = max_health;     this.health = max_health;     this.steps = max_steps;     this.max_steps = max_steps;   }      gain_damage(damage, effect) {     if (effect !== null) {       this.effects.push(effect);     }     let dmg = damage;     // ОБРАБОТКА СОПРОТИВЛЕНИЙ     for (const effect in this.effects) {         if (effect.get_resistance !== null) {           dmg *= (1 - effect.get_resistance());         }       }     let dmg_left = dmg - this.shield;     if (dmg_left >= 0) {       this.health = health - dmg_left;       this.reset_shield();     }     else {       this.shield -= damage;     }      if (this.health < 0) this.health = 0;   }   setup_shield(shield_count, effect) {     if (effect !== null) {       this.effects.push(effect);     }     let def = shield_count;      // ОБРАБОТКА ИНКРЕМЕНТОВ в первую очередь     this.effects.forEach((effect) => {       if (effect.get_def_incrementation !== null) {         def += effect.get_def_incrementation();       }     });     // ОБРАБОТКА МНОЖИТЕЛЕЙ далее     this.effects.forEach((effect) => {       if (effect.get_def_multiplier !== null) {         def *= effect.get_def_multiplier();       }     })          this.shield += def;   }   reset_shield() {     this.shield = 0;   }   heal(heal_count, effect) {     if (effect !== null) {       this.effects.push(effect);     }     if (this.health < this.max_health) {       this.health += heal_count;     }     if (this.health > this.max_health) this.health = this.max_health;        }      deal_damage(target, weapon) {     if (weapon instanceof Weapon) {       let dmg = weapon.get_damage();       // ОБРАБОТКА ИНКРЕМЕНТОВ в первую очередь       this.effects.forEach((effect) => {         if (effect.get_dmg_incrementation !== null) {           dmg += effect.get_dmg_incrementation();         }       });       // ОБРАБОТКА МНОЖИТЕЛЕЙ далее       this.effects.forEach((effect) => {         if (effect.get_dmg_multiplier !== null) {           dmg *= effect.get_dmg_multiplier();         }       })       target.gain_damage(dmg, weapon.get_effect());     }   }   is_alive() {     return this.health > 0;   }   step() {     /** считать после действий атаки/защиты/лечения.       *     */     for (let i = 0; i < this.effects.length; i++) {       const effect = this.effects[i];       if (effect.get_damage !== null) {         this.gain_damage(effect.get_damage(), null);       }       if (effect.get_heal_count !== null) {         this.heal(effect.get_heal_count(), null);       }       if (effect.get_armor !== null) {         this.heal(effect.get_armor(), null);       }       effect.count();       if (effect.get_steps_left() <= 0) {         this.effects[i] = null;       }     }     this.effects = this.effects.filter((elem) => {return elem != null;});     // Здесь использована стрелочная функция для фильтрации по элементам без null   }   get_weapon() {     if (this.weapon_idx >= 0) return this.inventory.get_item(this.weapon_idx);     else return null;   } }

Надеюсь, что комментарии излишни, но если какие-то момент не понятны, не стесняйтесь, дорогие читатели, уточнять непонятные или неверные элементы в комментариях.

Класс Game

Самый главный элемент в проекте. Он отвечает за игровой цикл, который управляет последовательностью событий. Вот что он должен содержать:

— Номер Entity, сейчас ходящего, step_idx
— массив из Entity, entities
— Номер игрока player_idx
— Список союзников ally_idxes
— Список противников enemy_idxes
— Количество Entity, entities_count

class Game {   entities = Array();   step_idx = -1;   player_idx = -1;   entities_count = 0;   ally_idxes = Array();   enemy_idxes = Array();      constructor(allies_count, enemies_count) {     this.entities_count = allies_count + enemies_count;     this.step_idx = 0;     this.player_idx = 0;          for (let i = 0; i < this.entities_count; i++) {       if (i < allies_count) {         // Составляем списки союзников         ally_idxes[ally_idxes.length] = i;       }       else if (i > allies_count && i < allies_count + enemies_count) {         // ... и противников         enemy_idxes[enemy_idxes.length] = i;       }     }   }   step(action) {     const entity = this.entities[step_idx];      if (action == 0) {       entity.dealDamage(target, entity.get_weapon());     }     else if (action == 1) {       entity.     }          entity.step();          this.step_count(entity);   }   step_count(entity) {     entity.steps -= 1;     if (entity.steps <= 0) {       entity.steps = entity.max_steps;       this.step_idx += 1;       if (this.step_idx >= this.entities_count) this.step_idx = 0;     }   } }

Заключение

Сегодня в этой статье я написал, так называемый фреймворк, если не игровой движок на JavaScript.

P. S.

Мне слишком сильно хотелось высказаться, поэтому я решил написать эту статью.


ссылка на оригинал статьи https://habr.com/ru/post/694390/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *