Пошаговая боевая система на примере 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/


Интересные статьи

Интересные статьи

Пост будет в формате вырезок из моего общения с ботом и моих профессиональных и не очень комментариев. Каждые несколько лет я по приколу развлекался попытками обучить прогрессивного чатбота чему-то о ...
Недавно мы опубликовали перевод статьи о конвейерах в Unix, у автора которой есть ещё немало подобных материалов. В той публикации мы устроили опрос о целесообразности перевода других ста...
В ноябре 2019 года люди услышали о первых случаях неизвестной смертельно опасной болезни в Китае. Теперь все знают о том, что эта болезнь называется COVID-19. Видимо, эпидемия навсегд...
В конце мая компания Google представила новую фичу в Google Tag Manager (GTM): Custom Templates или пользовательские шаблоны. Давайте разберемся, зачем она нужна, как ей пользоваться, в чем отлич...
В этот раз выбрана игра «Змейка». Создана библиотека для нейросети на языке Go. Найден принцип обучения, зависимый от «глубины» памяти. Написан сервер для игры между разработчиками. ...