Имеется древняя игрушка LaserAge, которая написана на Flash (на очень древнем Macromedia Flash 4) и работает только под Windows. В детстве она мне очень понравилась, поэтому я решил для души портировать её, чтобы можно было играть с браузера со всех устройств.
Цель игры заключается в том, чтобы уничтожать противников своим
космическим кораблём на различных уровнях и получать бонусы, если
поймать бонус улучшается оружие. При попадании торпеды противника
даунгрейд оружия игрока.
При уничтожении всех противников на уровне происходит переключение
на следующий уровень. Всего 100 уровней.
В терминах игры уровень волна (Wave), а несколько волн объединены в
большой уровень (Level), который представляет из себя просто смену
заднего фона,
т. е. всего 4 больших уровня в каждом из которых 25 волн. В
последней волне большого уровня обычно бывает босс противник с
огромным значением жизни и мощным оружием.
[TOC]
Бизнес логика игры
Игровое пространство
Представляет из себя обычную прямоугольную область, в верхней
части располагаются корабли противника, а снизу игрок.
Область движения игрока ограничена так, что он не может
сталкиваться с кораблями противника, а корабли противника с
игроком.
Оружие
Оружием обладает космический корабль игрока и корабли
противника.
Оружие игрока может быть ручным (стреляет при нажатии мыши) и
дополнительное автоматическое (стреляет периодами).
Оружие стреляет торпедами, алгоритм движения которых очень
примитивный: торпеды противника движутся на игрока (сверху вниз), а
торпеды игрока движутся снизу вверх.
При попадании торпеды противника в игрока вычитается 1 уровень
жизни (апгрейда), при 0 игра завершается поражением.
Оружие космического корабля игрока
- Торпеда стреляет маленькими ракетами
- Одинарная Торпеда 1 уровень апгрейда
- Двойная 2 уровень апгрейда
- Тройная 3 уровень апгрейда
- Автоматические пушки
- Дополнительная автоматическая Торпеда слева корабля 4 уровень апгрейда
- Дополнительная автоматическая Торпеда справа корабля 5 уровень апгрейда
- Зелёная плазма 6 и 7 уровень апгрейда (увеличивается скорострельность)
- Фиолетовая плазма 8 уровень апгрейда (наносит урон всем противникам по траектории полёта)
- Зелёный лазер 9 уровень (наносит урон всем противникам, а также активно одну секунду, тем самым можно задеть соседних противников)
Дополнительное оружие:
- Красная плазма 15-19 уровень (наносит урон всем противникам, а также активно одну секунду, тем самым можно задеть соседних противников)
- Зелёная плазма 20-24 уровень
- Синяя плазма 25-29 уровень апгрейда
- Фиолетовая плазма 30-34 уровень апгрейда
- Фиолетовая плазма 30-34 уровень апгрейда
- Дополнительная автоматическая Торпеда слева стреляет желтой плазмой 35 39 уровень апгрейда
- Дополнительная автоматическая Торпеда справа стреляет желтой плазмой 40+ уровень апгрейда
Таблица с характеристиками оружия игрока
Оружие | Hit Points | Скорость спрайта | Интенсивность | Тип | Дополнительно | Вид |
---|---|---|---|---|---|---|
Торпеда | 1 | 5 | 25 | Торпеда | Одинарная, двойная, тройная | |
Автоматическая Торпеда | 1 | 5 | 50 | Торпеда | Слева и Справа | |
Зелёная плазма | 3 | 7 | 30 | Торпеда | ||
Фиолетовая плазма | 2 | 8 | 30 | Торпеда | Атакует до 3х целей | |
Красная плазма | 2 | 4 | 30 | Торпеда | ||
Синяя плазма | 4 | 4.5 | 30 | Торпеда | ||
Жёлтая плазма | 2 | 3.8 | 40 | Торпеда | Только автоматическая | |
Зелёный Лазер | 4 | - | 15/55 | Лазер | Атакует до 5ти целей одновременно |
Таблица с конфигурацией оружия игрока в зависимости от уровня жизни
Уровень жизни | Конфигурация оружия |
---|---|
1 | Торпеда |
2 | Торпеда + Торпеда |
3 | Торпеда + Торпеда + Торпеда |
4 | Торпеда + Торпеда + Торпеда + Автоматическая торпеда слева |
5 | Торпеда + Торпеда + Торпеда + Автоматическая торпеда слева + справа |
6 | Зелёная плазма + Автоматическая торпеда слева + справа |
7 | Зелёная плазма + Автоматическая торпеда слева + справа |
8 | Фиолетовая плазма + Автоматическая торпеда слева + справа |
9 | Зелёный лазер + Автоматическая торпеда слева + справа |
15 19 | Зелёный лазер + Красная плазма + Автоматическая торпеда слева + справа |
20 24 | Зелёный лазер + Красная плазма + Автоматическая торпеда слева + справа |
25 29 | Зелёный лазер + Синяя плазма + Автоматическая торпеда слева + справа |
30 34 | Зелёный лазер + Фиолетовая плазма + Автоматическая торпеда слева + справа |
35 39 | Зелёный лазер + Фиолетовая плазма + Автоматическая желтая плазма слева + торпеда справа |
40+ | Зелёный лазер + Фиолетовая плазма + Автоматическая желтая плазма слева + желтая плазма справа |
Оружие противников
Таблица с конфигурацией оружия противников
Оружие | Скорость спрайта | Тип |
---|---|---|
Торпеда | 2.5 | Торпеда |
Красная плазма | 3.5 | Торпеда |
Синяя плазма | 4.5 | Торпеда |
Зелёная плазма | 5 | Торпеда |
Синяя Торпеда | 3 | Торпеда |
Жёлтая плазма | 3.2 3.8 | Торпеда |
Белая плазма | 4 6 | Торпеда |
Зелёный Лазер | - | Лазер |
Для того чтобы исключить предсказуемость поведения оружия
игроков, интенсивность имеет псевдослучайный характер.
Интенсивность оружия противников может иметь один и более временных
слотов, в каждом отдельно задаётся минимальное и максимальное время
фреймов и число повторов. Слот может быть паузой или активным
состоянием (стреляет).
Пример конфигурации оружия:
"torpedo": { "sprite": "Bullet1_1.png", //картинка спрайта "isRandomIntensity": false, //нужно ли переключать случайно слоты - true или по порядку - false "intensity": [ //слот 0 { "min": 50, //минимальное число фреймов "max": 200, //максимальное число фреймов "type": "pause" //pause - оружие неактивно, shoot - активное (стреляет) }, //слот 1 { "min": 100, "max": 200, "type": "shoot" }, { "min": 50, "max": 80, "type": "pause" }, { "min": 30, "max": 100, "repeat": 2 } ], "speed": 2.5, //скорость "type": "bullet", //тип оружия "sound": "alienTorpedo"}
Действующие лица
Корабль игрока
Корабль игрока может перемещаться в ограниченной области, чтобы
не пересекаться с кораблями противников.
Управляется движением мыши или стрелочками и . На экране мобильного
телефона тапом и движением по экрану.
Оружие активирует при удержании левой клавиши мыши (тапом и
удержанием по экрану на мобильном телефоне).
Противники
Для разнообразия на каждом уровне может быть различное число противников, каждый из которых обладает собственным набором оружия, имеет своё значение жизни, различную траекторию движения. противники могут быть обычные или боссами.
Корабль противника | Жизнь | Тип | Движения | Оружие | Вид |
---|---|---|---|---|---|
Чужой 1 | 2 | Обычный | Нормальное горизонтальное | Торпеда | |
Чужой 2 | 4 | Обычный | Нормальное все направления | Торпеда | |
Быстрый чужой | 10 | Обычный | Быстрое горизонтальное | Торпеда (Интенсивная) | |
Фрегат чужого | 10 | Обычный | Нормально-быстрое все направления | Красная плазма | |
Броневик чужого | 10 | Обычный | Медленное вниз | Торпеда (Очень интенсивная) | |
Быстрый Фрегат чужого | 30 | Обычный | Медленное вниз (следит за игроком) | Красная плазма (Очень интенсивная) | |
Красный истребитель | 30 | Обычный | Медленное вниз (следит за игроком) | Синяя плазма | |
Зелёный истребитель | 30 | Обычный | Быстро вертикально | Синяя плазма | |
Чужой 1 модификация | 2 | Обычный | Нормальное горизонтальное | Синяя Торпеда | |
Бомбардировщик | 30 | Обычный | Нормальное все направления (следит за игроком) | Зелёная плазма | |
Тяжёлый Чужой | 30 | Обычный | Нормальное все направления | Торпеда | |
Тяжёлый Фрегат Чужого | 35 | Обычный | Нормальное все направления | Синяя Торпеда + Синяя Торпеда | |
Тяжёлый броневик | 35 | Обычный | Нормальное вниз | Жёлтая Плазма + Жёлтая Плазма + Жёлтая Плазма + Жёлтая Плазма | |
Линкор | 100 | Босс | Нормальное все направления | Синяя плазма (очень интенсивная) + Зелёная плазма (очень интенсивная) | |
Крейсер | 250 | Босс | Нормальное все направления | Зелёная плазма (сверх интенсивная) | |
Тяжёлый Крейсер | 500 | Босс | Быстрое все направления | Жёлтая Плазма + Жёлтая Плазма + Синяя Торпеда + Синяя Торпеда + Синяя Торпеда + Синяя Торпеда + Белая плазма + Белая плазма | |
Эпичный Тяжёлый Крейсер | 1000 (восстанавливается) | Босс | Быстрое все направления | Жёлтая Плазма + Жёлтая Плазма + Синяя Торпеда + Синяя Торпеда + Синяя Торпеда + Синяя Торпеда + Белая плазма + Белая плазма+ Зелёная плазма (очень интенсивная) |
JSON-конфигурация противника:
"alien10": { "life": 35, "weapons": [ { "weapon": "blueTorpedo", "position": { "x": -6, "y": 0 } }, { "weapon": "blueTorpedo", "position": { "x": 6, "y": 0 } } ], "sprite": "AlienShip10_1.png", "movement": "horizontalFast", "killPoints": 2100}
JSON-конфигурация движения противника :
"horizontalFast": { "movements": [ { "type": "freeMovement", //freeMovement - обычное, followPlayer - следит за игроком (движется в направление) "speedDelta": { "vx": -6, "vy": 0 }, "intensity": [ //интенсивность движения в виде слотов { "min": 20, "max": 150 }, { "min": 150, "max": 350 } ] } ]}
Бонусы
Специальный вид противника , который не имеет оружия и при уничтожении порождает спрайт с бонусом , который должен поймать корабль игрока. Если игрок поймает бонус, то увеличивается его уровень (жизнь).
Уровни
Каждый уровень содержит множество различного вида противников, которые расположены определённым образом. Также уровень может содержать один и более бонусов.
JSON-конфигурация уровня :
"2": { "level": 1, "enemies": [ // список противников { "id": "alien1", "position": { "x": 200, "y": 35 } }, //... { "id": "alien1", "position": { "x": 525, "y": 40 } } ], "bonuses": [ // список бонусов { "id": "bonus1", "position": { "x": 350, "y": 10 } } ] },
Выбор JavaScript библиотеки для реализации
Я просмотрел множество библиотек графики для JavaScript, но остановился на Hexi JS: https://github.com/kittykatattack/hexi .
Возможности библиотеки:
- Простота
- Рисование примитивов
- Рисование просты интерфейсов (кнопки, события)
- Перемещение, масштабирование, вращение
- Рисование спрайтов
- Анимированные спрайты
- Работа со спрайтами как с объектами
- Загрузка спрайтов в виде большой текстуры-атласа. Можно разместить множество изображений в одном файлы и на выходе получить одну большую текстуру и JSON файл с описанием спрайтов (область, смещение)
- Логика столкновений
- Работа с устройствами ввода (клавиатура), тач-скрин.
Пример текстуры-атласа создаваемого с помощью программы
TexturePacker
Звуковая библиотека: https://github.com/kittykatattack/sound.js
Возможности библиотеки:
- Простота
- Воспроизведение звуков
- Воспроизведение музыки
- Эффекты
Архитектура
Общая диаграмма классов:
Ядро игры
Класс Main
Является точкой входа и контейнером игрового кода.
Поля:
- resources содержит список всех загружаемых ресурсов (текстуры, звук, json)
- sounds словарь звуков: Ключ название, Значение путь
- gameScene объект HexiJS на
- game экземпляр объекта Game
- hexi инстанс HexiJS
- gameStorage сохраняет состояние игры в localStorage
Методы:
- init() инициализирует HexiJS
- load() загружает ресурсы (текстуры, звук, json)
- setup() устанавливает игровую область, события нажатия кнопок, запускает фоновую музыку
- playLoop() точка изменения состояния игры (считает движение, коллизии, снаряды, перерисовывает пространство).
- saveGame() сохраняет игру
- loadGame() загружает игру
Пример списка ресурсов текущей реализации игры:
Main.resources = [ "images/environment1.png", "images/environment2.png", "images/environment3.png", "images/environment4.png", "images/interface.png", "images/life-icon.png", "images/ships-texture.json", "images/bullet-texture.json", "sounds/alien-torpedo-shoot.wav", "sounds/alien-red-plasma-shoot.wav", "sounds/hero-torpedo-shoot.wav", "sounds/explode.wav", "sounds/hero-green-plasma-shoot.wav", "sounds/alien-green-plasma-shoot.wav", "sounds/alien-blue-torpedo-shoot.wav", "sounds/alien-yellow-laser.wav", "sounds/pulse-plasma.wav", "sounds/laser.wav", "sounds/track0.ogg", "sounds/track1.ogg", "sounds/track2.ogg", "sounds/track3.ogg", "sounds/track4.ogg", "data/hero-configuration.json", "data/levels-configuration.json", "data/enemy-configuration.json", "data/ui-configuration.json", ];
Класс Game
Основной класс игры.
Поля:
-
level информация о уровне. Значение:
{ "wave": 1 //номер волны, "type": 1 }
-
score информация об очках. Значение:
{"points": 0 }
- bulletsController Экземпляр класса BulletsController. Управляет поведение торпед и лазеров оружия
- enemyController Экземпляр класса EnemyController. Управляет поведением всех противников на уровне (в т.ч. и бонусами)
- player Экземпляр Player
- hexi экземпляр класса Hexi (ссылка)
- game экземпляр объекта Game
- gameStorage экземпляр объекта GameStorage
Методы:
- clearShips() очистка всех проиивников, бонусов
- setupLevel() настроить уровень (добавить противников, бонусы, расстановка)
- nextLevel() переход на следующий уровень
- previousLevel() переход на предыдущий уровень
- forwardLevel() перепрыгнуть на несколько уровней вперёд (на 5)
- rewindLevel() перепрыгнуть на несколько уровней назад (на 5)
- restoreState(gameState: JSON) восстановить по объекту gameState
- resetGame() сбросить игру (начать сначала)
- update() обновить игровой мир
- enemyDestroyed() обработчик срабатывает при уничтожении всех противников
Класс GameStorage
Сохраняет и загружает состояние игры .
Поля:
- game экземпляр объекта Game
Методы:
- save() сохранить состояние игры
- load() загрузить состояние игры
Класс InputDevice
Работает с событиями устройств ввода: click и touch кнопок, нажатие клавиш клавиатуры.
Поля:
- game экземпляр объекта Game
Методы:
- init() инициализирует все обработчики события и callback'и
- loadTapped() нажата кнопка "Load"
- storeTapped() нажата кнопка "Store"
- resetTapped() нажата кнопка "Reset"
- pauseTapped() нажата кнопка "Pause"
Иерархия классов действующих лиц
Actor
Класс участника.
Поля:
- hexi экземпляр класса Hexi (ссылка)
- game экземпляр объекта Game
- life текущее значение жизни
- initialLife начальное значение жизни
- sprite экземпляр класса Hexi.Sprite
- shipConfiguration конфигурация бонуса
Методы:
- move() переместить действующее лицо
- update() обновить действующее лицо
- setPosition(position: {x, y}) установить по координатам
WeaponedActor
Класс участника (противник или игрок) обладающем оружием.
Поля:
- automatedWeapons массив автоматических оружий
- canShoot мжет ли стрелять
- isWeaponShooting активено ли оружие
Методы:
- startShoot() запустить выстрелы оружием
- stopShoot() остановить выстрелы оружием
- onShootStarted() обработчик события, что запущены выстрелы оружием
- onShootStopped() обработчик события, что остановлены выстрелы оружием
- updateShooting() выполняет алгоритмы выстрелов
Enemy
Класс противника.
Поля:
- type тип противника
- syncWeapons массив конфигураций для синхронного оружия
- movementEngine экземпляр класса MovementEngine
Методы:
- setWeapon() установить оружие используя текущую конфигурацию
- shootWithWeapon() выполняет выстрел противником
- setLifeLine() рисует линию жизни противника
- hit() проверяет столкновение торпед (лазера) игрока с текущим противником
MovementEngine
Класс управляющий движением.
Для придания сложности движения, используется конфигурация со
слотами. В каждом слоте задаётся
вектор направления vx, vy и интенсивность. Имеется возможность
отключения отражения от нижней границы и
режим слежения за игроком (противник всегда движется за
игроком).
Поля:
- movementsConfiguration конфигурация движения
- firstMovementConfiguration первый элемент из списка конфигураций
- movementItensity интенсивность движения
- movementItensityCounter счётчик интенсивности движения
- movementItensitySlot номер слота интенсивности
- isBounceBottom флаг на проверку отражения от нижней границы. Если false, то противник не отражается от нижней границы
Методы:
- setMovement() настраивает движение
- updateMovement() обновляет движение по конфигурации движения
Player
Класс игрока.
Поля:
- weapons массив оружия игрока
- collisionSprite спрайт коллизии (торпеды противника сталкиваются со спрайтом коллизии, а не спрайтом игрока)
- weaponLifeLevels значения уровня жизни для проверки на апгрейд оружия
- invisibilityCounter счётчик невидимости от торпед (нужен для того, чтобы при столкновении с торпедой противника игрок стал временно недосягаем для других торпед)
Методы:
- upgrade() апгрейд игрока (+1 жизнь)
- downgrade() даунгрейд игрока (+1 жизнь)
- shootWithLaser(currentWeapon, weapon) выстрел лазером
- shootWithBullets(currentWeapon, weapon) выстрел торпедой
- setWeapon() установить оружие используя текущую конфигурацию
- setLife(life: number) установить значение жизни (меняет оружие в соответствии со значением жизни)
- hitUpgrade(upgradeItem) проверить столкновение со спрайтом апгрейда
Bonus
Класс Бонуса. При уничтожении порождает спрайт апгрейда.
Поля:
- type тип бонуса
- movementEngine экземпляр класса MovementEngine
- upgradeBonus конфигурация апгрейда
Методы:
- shootWithUpgrade(upgradeBonus: JSON) породить спрайт апгрейда
EnemyController
Управляет состоянием противников, бонусов, апгрейдами.
Поля:
- enemies массив всех противников на уровне
- bonuses массив всех бонусных кораблей на уровне
- player объект игрока
- upgrades массив всех спрайтов апгрейда
Методы:
- isLevelCompleted() проверка на завершённость уровня (уничтожены все противники и бонусы, пойманы апгрейды)
- update() обновляет состояние всех противников
- clear() очистка уровня от проиивников, бонусов
BulletsController
Управляет состоянием торпед (перемещение), лазерами игрока и противников.
Поля:
- playerBullets массив торпед игрока
- enemyBullets массив торпед всех противников
- explosionSplashes массив спрайтов взрыва
- playerLaser состояние спрайта лазера игрока (Если оружие доступно).
Методы:
- update() обновляет состояние всех торпед, лазеров
- clear() очищает уровень от всех торпед, лазеров
- updatePlayerBullets() изменяет состояние всех торпед игрока
- updatePlayerLaser() изменяет состояние лазера игрока
- updateEnemyBullets() изменяет состояние всех торпед противника
- updateExplosions() изменяет состояние всех взрывов
Выводы
Я постарался достаточно подробно рассказать как портировать
существуюшую игру на JavaScript.
Игру можно модифицировавть и снабдить своими текстурами, музыкой,
сделать собственную конфигурацию уровня или вариацию всей игры.
Очень хотелось бы получить от вас комментарии по архитектуре игры,
варианты рефакторинга, по именованию методов и т.д.
Согласен, архитектура не очень идеальная и есть куда стремится!
Спасибо и интересных Вам проектов!
Ссылки
http://laseroid.azurewebsites.net/ сама игра
https://github.com/EntityFX/laseroid исходный код игры