Я знаком с холстом всего больше месяца. Впервые полностью реализовал игровой процесс, и урожай довольно большой.
Снимаем скриншоты из игры
Сначала перейдите к демо: https://littleyljy.github.io/demo/shootgame/
правила игрыИгроку необходимо управлять самолетом, стрелять пулями и уничтожать движущихся монстров. Если все монстры уничтожены, игра успешна. Если монстры опускаются вниз, игра проваливается.
Игра разделена на несколько сцен:
Чтобы добиться переключения сцен, вам действительно нужно сначала отображать: none для всех сцен, а затем управлять статусом данных через js, чтобы запустить, воспроизвести, не удалось, успешно, полностью успешно и остановить, чтобы реализовать соответствующий дисплей сцены: блок.
HTML и CSS следующие:
<div id=game data-status=start> <div class=game-panel> <section class=game-intro game-ui> <h1 class=section-title>Стрелялка</h1> <p class=game- desc>Это захватывающая игра-стрелялка. Используйте ← и →, чтобы управлять самолетом, используйте пространство для стрельбы и нажмите Enter, чтобы приостановить игру. Давайте уничтожим космических монстров вместе! </p> <p class=game-level>Текущий уровень: 1</p> <button class=js-play button>Начать игру</button> </section> <section class=game-failed game-ui> <h1 class=section-title>Игра окончена</h1> <p class=game-info-text>Окончательный счет: <span class=score></span></p> <button class=js-replay button> Начать снова</button> </section> <section class=game-success game-ui> <h1 class=section-title>Успех игры</h1> <p class=game-next-level game-info-text></p> <button class=js-next button>Продолжить игру</button> </section> <section class=game-all-success game-ui> <h1 class=section-title>Пройдено успешно</h1> <p class=game- следующий уровень game-info-text>Вы успешно защитились от всех атак монстров. </p> <button class=js-replay button>Играть еще раз</button> </section> <section class=game-stop game-ui> <h1 class=section-title>Пауза в игре</h1> < button class=js-stop button>Игра продолжается</button> </section> </div> <div class=game-info game-ui> <span class=title>Оценка:</span> <span class=score > </span> </div> <canvas id=canvas width=700 height=600> <!-- Доска для рисования анимации --> </canvas> </div>
#game{ ширина: 700 пикселей; высота: 600 пикселей; положение: относительное; слева: 50%; поле: 0 0 0 -350 пикселей; фон: линейный градиент (-180 градусов, #040024 0%, #07165C 97% );}.game-ui{ display: none; размер поля: 55 пикселей; border-box; высота: 100%;}[data-status=start] .game-intro { display:block;padding-top: 180px;background: url(./img/bg.png) no-repeat 430px 180px; размер фона: 200 пикселей;}[data-status=playing] .game-info {display: положение блока: абсолютное; left:0; дополнение:20px;}[data-status=failed].game-failed,[data-status=success].game-success,[data-status=all-success].game-all-success,[ data-status=stop] .game-stop{ display: block; padding-top: 180px; URL(./img/bg-end.png) без повтора 380 пикселей 190 пикселей; размер фона: 250 пикселей;}объектно-ориентированный
Вся игра может рассматривать монстров (Enemy), самолеты (Plane) и пули (Bullets) как объекты, а также объекты конфигурации (CONFIG) и игровые объекты (GAME), которые управляют игровой логикой.
Конфигурация, связанная с игрой/** * Конфигурация, связанная с игрой* @type {Object} */var CONFIG = { status: 'start', // Игра запускается по умолчанию со стартовым уровнем: 1, // Уровень игры по умолчанию totalLevel: 6, // Всего 6 Off numPerLine: 7, // Количество монстров в строке по умолчанию в игре CanvasPadding: 30, // Интервал холста по умолчанию BulletSize: 10, // Длина пули по умолчанию BulletSpeed: 10, // Скорость движения пули по умолчаниюenemySpeed: 2, // Расстояние движения врага по умолчаниюenemySize: 50, // Размер врага по умолчаниюenemyGap: 10, // Расстояние между врагами по умолчаниюenemyIcon: './img/enemy.png', // Изображение монстр врагBoomIcon: './img/boom.png', // Изображение смерти монстра врагНаправление: 'right', // Враг по умолчанию перемещается вправо в начале planeSpeed: 5, // Расстояние по умолчанию, на которое самолет перемещается на каждом шаге planeSize: { width: 60, height: 100 }, // Размер самолета по умолчанию, planeIcon: '. /img/plane.png' };Определить родительский класс
Поскольку монстры (Enemy), самолеты (Plane) и пули (Bullet) имеют одинаковые атрибуты x, y, размера, скорости и метод move(), вы можете определить родительский класс Element и реализовать его, унаследовав родительский класс от подкласс.
/*Родительский класс: содержит скорость xy move() draw()*/var Element = function (opts) { this.opts = opts {} //Установим координаты, размер, скорость this.x = opts.x; this.y = opts.y; this.size = opts.size; this.speed = opts.speed;};Element.prototype.move = function (x, y) { var addX = x || 0; addY = y || 0; this.x += addX; this.y += addY;};//Функция, которая наследует функцию прототипа inheritPrototype(subType, superType) { var proto = Object.create(superType.prototype); прото.конструктор = подтип.прототип = прото;}
Метод move(x, y) складывается на основе переданного значения (x, y).
Определение монстраМонстры включают в себя уникальные атрибуты: статус монстра, изображение, BoomCount, который контролирует продолжительность состояния взрыва, а также методы draw(), down(), Direction() и Booming().
/*Enemy*/var Enemy = function (opts) { this.opts = opts || {} //Вызов атрибута родительского класса Element.call(this, opts); //Специальный атрибут status и изображение this.status = 'normal';//нормальный, громкий, ноомированный this.enemyIcon = opts.enemyIcon; this.enemyBoomIcon = opts.enemyBoomIcon; this.boomCount = 0;};//Наследуем метод Element inheritPrototype(Enemy, Element);//Метод: Нарисуйте врага Enemy.prototype.draw = function () { if (this.enemyIcon && this.enemyBoomIcon) { switch (this.status ) { случай «нормальный»: varenemyIcon = new Image(); this.x, this.y, this.size, this.size); разрыв; случай «бум»: var врагBoomIcon = new Image(); врагBoomIcon.src = this.enemyBoomIcon; ctx.drawImage(enemyBoomIcon, this.x, this.y, this.size, this.size); this.size; default:break; } } return this;};//Метод: down Move down Enemy.prototype.down = function () { this.move(0, this.size return this); ;//Метод: перемещение влево или вправо Enemy.prototype.direction = function (direction) { if (direction === 'right') { this.move(this.speed, 0 } else {); this.move(-this.speed, 0); } return this;};//Метод: Враг взрывается Enemy.prototype.booming = function () { this.status = 'boomCount'; this.boomCount += 1; (this.boomCount > 4) { this.status = 'boomed' } верните это;}
У маркеров есть методы fly() и draw().
/*Bullet*/var Bullet = function (opts) { this.opts = opts || Element.call(this, opts);};inheritPrototype(Bullet, Element);//Метод: Пусть пуля полетит .prototype.fly = function () { this.move(0, -this.speed); return this;};//Метод: нарисовать маркер Bullet.prototype.draw = function () { ctx.beginPath(); ctx.strokeStyle = '#fff'; ctx.moveTo(this.x, this.y); ctx.lineTo(this.x, this.y - CONFIG.bulletSize()); ; ctx.stroke(); верните это;};
Объект самолета содержит уникальные атрибуты: статус, ширину и высоту, изображение, максимальное и минимальное значения абсцисс, а также методы hasHit(), draw(), Direction(), Shoot() и drawBullets().
/*Plane*/var Plane = function (opts) { this.opts = opts {}; Element.call(this, opts); //Уникальный статус атрибута и изображение this.status = 'normal'; = opts.width; this.height = opts.height; this.planeIcon = opts.planeIcon; this.minX = opts.minX; this.maxX = opts.maxX; //Пули, связанные с this.bullets = []; this.bulletSpeed = opts.bulletSpeed || (Plane, Element) ;//Метод: Пуля попадает в цель Plane.prototype.hasHit = function (enemy) { var Bullets = this.bullets; for (var i = Bullets.length - 1; i >= 0; i--) { var Bullet = Bullets[i]; var isHitPosX = (enemy.x < Bullet.x) && (bullet.x) < (enemy.x + враг.размер)); var isHitPosY = (enemy.y < Bullet.y) && (bullet.y < (enemy.y + враг.размер)); (isHitPosX && isHitPosY) { this.bullets.splice(i, 1); return true; } } return false;}; //Метод: нарисовать плоскость Plane.prototype.draw = function () { this.drawBullets(); вар planeIcon = новое изображение(); planeIcon.src = this.planeIcon; ctx.drawImage(planeIcon, this.x, this.y, this.width, this.height); return this;};//Метод: направление плоскости Plane.prototype.direction = function (direction) { varspeed = this.speed; var planeSpeed; ) { planeSpeed = this.x < this.minX ? 0 : -скорость } else { planeSpeed = this.x > this.maxX ? console.log('planeSpeed:', planeSpeed); console.log('this.x:', this.x); console.log('this.minX:', this.minX); console.log('this); .maxX:', this.maxX); this.move(planeSpeed, 0); return this;//Удобный цепной вызов};//Метод: запуск пуль Plane.prototype.shoot = function () { var BulletPosX = this.x + this.width / 2; this.bullets.push(new Bullet({ x: BulletPosX, y: this.y, size: this.bulletSize, скорость: this.bulletSpeed})); this; //Метод: нарисовать пули Plane.prototype.drawBullets = function () { var Bullets = this.bullets; var i = Bullets.length; (i--) { var Bullet = Bullets[i]; Bullet.fly(); if (bullet.y <= 0) { Bullet.splice(i, 1 } Bullet.draw() }};
События клавиатуры имеют следующие состояния:
Потому что дрону необходимо продолжать движение, когда нажаты левая кнопка (keyCode=37) и правая кнопка (keyCode=39) (keydown), а клавиша не перемещается при отпускании. При нажатии клавиши пробела (keyCode=32) или клавиши со стрелкой вверх (keyCode=38) (keydown) происходит выстрел, а при отпускании клавиша прекращает стрельбу. Также нажмите клавишу Enter (keyCode=13), чтобы приостановить игру. Поэтому вам необходимо определить объект KeyBoard, чтобы отслеживать, нажимают или отпускают клавишу onkeydown и onkeyup.
Поскольку левая и правая клавиши противоречат друг другу, на всякий случай вам нужно установить для правой клавиши значение false при нажатии левой клавиши. То же самое касается щелчка правой кнопкой мыши.
// Событие клавиатуры var KeyBoard = function () { document.onkeydown = this.keydown.bind(this); document.onkeyup = this.keyup.bind(this);}; // Объект KeyBoard KeyBoard.prototype = {pressedLeft: false, PressRight: ложь, PressUp: ложь, HoldLeft: ложь, HoldRight: ложь, PressSpace: ложь, PressEnter: ложь, keydown: function (e) { var key = e.keyCode; переключатель (клавиша) {case 32://Space - стрелять пулями this.pressedSpace = true; case 37://клавиша со стрелкой влево this.pressedLeft = true; this.heldLeft = true; false; this.heldRight = false; case 38://Клавиша со стрелкой вверх — запуск маркеров this.pressedUp = true; case 39://Кнопка со стрелкой вправо this.pressedLeft = false; = false; this.pressedRight = true; this.heldRight = true; case 13://Введите ключ — приостановите игру this.pressedEnter = true; } }, keyup: function (e) { var key = e .keyCode; переключатель (ключ) { случай 32: this.pressedSpace = false; случай 37: this.heldLeft = false; случай 38: this.pressedUp = false; случай 39: this.heldRight = false; this.pressedRight = false; случай 13: this.pressedEnter = false;Логика игры
Игровой объект (GAME) содержит логику всей игры, включая init (инициализацию), bindEvent (привязку кнопки), setStatus (обновление статуса игры), play (в игре), stop (паузу), end (окончание) и т.д. ., в этом описание не расширяется. Он также включает в себя такие функции, как генерация монстров и отрисовка игровых элементов.
//Весь игровой объект var GAME = { //Ряд логических функций //Функции игрового элемента}1. Инициализация
Функция инициализации в основном определяет начальные координаты самолета, диапазон движения самолета, диапазон движения монстра, инициализирует счет, массив монстров, создает объект KeyBoard и выполняет его только один раз.
/** * Функция инициализации, эта функция выполняется только один раз * @param {object} opts * @return {[type]} [description] */init: function (opts) { //Set opts var opts = Object.assign ( {}, opts, CONFIG);//Объединяем все параметры this.opts = opts; this.status = 'start'; //Рассчитываем начальные координаты объекта самолета this.planePosX = CanvasWidth / 2 - opts.planeSize.width; this.planePosY = CanvasHeight - opts.planeSize.height - opts.canvasPadding; // Координаты предела плоскости this.planeMinX = opts.canvasPadding; this.planeMaxX = CanvasWidth - opts.canvasPadding - opts.planeSize. width; //Рассчитываем зону движения противника this.enemyMinX = opts.canvasPadding this.enemyMaxX =; CanvasWidth - opts.canvasPadding - opts.enemySize; // Оценка установлена на 0 this.score = 0; this.enemies = []; this.keyBoard = new KeyBoard(); this.bindEvent(); },2. Привязать события кнопки
Потому что некоторые игровые сцены включают кнопки для запуска игры (playBtn), перезапуска (replayBtn), следующего уровня игры (nextBtn) и приостановки игры для продолжения (stopBtn). Нам нужно выполнять разные события для разных кнопок.
Причиной определения var self = this в первую очередь является использование this. В функцииbindEvent это указывает на объект GAME, а в playBtn.onclick = function () {} это явно не то, что нам нужно, потому что у playBtn нет события play(), есть только событие play(). Объект GAME имеет его. Следовательно, объект GAME необходимо присвоить переменной self, а затем событие play() можно вызвать в playBtn.onclick = function () {};.
Следует отметить, что кнопка replayBtn появляется как в сценариях сбоя, так и в сценариях очистки, поэтому получается коллекция всех .js-replay. Затем forEach перебирает каждую кнопку replayBtn, сбрасывает уровень и счет и вызывает событие play().
bindEvent: function () { var self = this; var playBtn = document.querySelector('.js-play'); var replayBtn = document.querySelectorAll('.js-replay'); js-next'); var stopBtn = document.querySelector('.js-stop'); Привязка кнопки запуска игры playBtn.onclick = function () { self.play() } // Привязка кнопки перезапуска игры replayBtn.forEach(function (e) { e.onclick = function () { self.opts.level = 1; ; self.play(); self.score = 0; totalScoreText.innerText = self.score; // Привязка кнопки игры следующего уровня nextBtn.onclick = function () { self.opts.level += 1; self.play() } // Приостановка игры и продолжение привязки кнопки stopBtn.onclick = function () { self. ('играет'); self.updateElement() };3. Создать самолет
createPlane: function () { var opts = this.opts; this.plane = new Plane ({ x: this.planePosX, y: this.planePosY, ширина: opts.planeSize.width, высота: opts.planeSize.height, minX : this.planeMinX, скорость: opts.planeSpeed, maxX: this.planeMaxX, planeIcon: opts.planeIcon });}4. Создайте группу монстров.
Поскольку монстры появляются группами, а количество монстров на каждом уровне также разное, функция двух циклов for заключается в создании ряда монстров и увеличении ряда уровней монстров в соответствии с количеством уровней. Или увеличивайте скорость монстра (скорость: скорость + i,), чтобы увеличить сложность каждого уровня и т. д.
//Создаем врагов createEnemy: function (enemyType) { var opts = this.opts; var evil = this.enemies; var numPerLine = opts.numPerLine; var padding = opts.canvasPadding = opts; .enemyGap; размер вар = opts.enemySize; скорость вар = opts.enemySpeed; //Добавляем строку для каждого уровня уровня врага for (var i = 0; i < level; i++) { for (var j = 0; j < numPerLine; j++) { //Параметры комплексных элементов var initOpt = { x : отступ + j * (размер + пробел), y: отступ + i * (размер + пробел), размер: размер, скорость: скорость, статус: врагТип, врагикон: opts.enemyIcon, врагБумИкон: opts.enemyBoomIcon }; враги.push(new Enemy(initOpt));5. Обновление монстров
Получите значение x массива монстров и определите, достигает ли он границы холста. Если оно достигает границы, монстр движется вниз. При этом необходимо также следить за статусом монстров, были ли поражены монстры в нормальном статусе, монстры во взрывном статусе и исчезнувшие монстры должны быть удалены из массива и засчитаны одновременно.
//Обновление статуса врага updateEnemeis: function () { var opts = this.opts; var plane = this.enemies; var i = враги.длина; var isFall = false;//Местоположение врагов var врагиX = getHorizontalBoundary(враги); если (enemiesX.minX < this.enemyMinX || врагиX.maxX >= this.enemyMaxX) { console.log('enemiesX.minX', врагиX.minX); console.log('enemiesX.maxX', врагиX.maxX); opts.enemyDirection = opts.enemyDirection === 'право' ? '; console.log('opts.enemyDirection', opts.enemyDirection); isFall = true; } // Цикл обновления врага, пока (i--) { var враг = враги[i]; if (isFall) { враг.down(); } враг.направление(opts.enemyDirection); переключатель (enemy.status) { регистр «нормальный»: if (plane.hasHit(enemy)) { враг .booming(); } перерыв; случай "бум": враг.booming(); перерыв случай "бум": враги.splice(i, 1); по умолчанию: перерыв } } },
Функция getHorizontalBoundary состоит в том, чтобы пройти значение x каждого элемента массива, отфильтровать большие или меньшие значения и получить максимальное и минимальное значение x массива.
//Получаем функцию горизонтальной границы массива getHorizontalBoundary(array) { var min, max; array.forEach(function (item) { if (!min && !max) { min = item.x; max = item.x; } else { if (item.x < min) { min = item.x; } if (item.x > max) { max = item.x; } }); Макс }}6. Обновить панель клавиатуры.
Нажмите клавишу Enter, чтобы выполнить функцию stop(), нажмите левую кнопку, чтобы переместить самолет влево, нажмите правую кнопку, чтобы переместить самолет вправо, и нажмите кнопку пробела, чтобы запустить самолет, стреляя пулями, чтобы предотвратить попадание пуль. от соединения по прямой, установите здесь keyBoard pressUp и keyBoard.pressedSpace.
updatePanel: функция () {вар plane = this.plane; вар keyBoard = this.keyBoard; если (keyBoard.pressedEnter) {this.stop(); if (keyBoard.pressedLeft || keyBoard.heldLeft) {plane. направление («влево»); } if (keyBoard.pressedRight || keyBoard.heldRight) { plane.direction («вправо» }); (keyBoard.pressedUp || keyBoard.pressedSpace) { keyBoard.pressedUp = false; keyBoard.pressedSpace = false; plane.shoot () };7. Рисуем все элементы
draw: function () { this.renderScore(); this.plane.draw(); this.enemies.forEach(function (enemy) { //console.log('draw:this.enemy',enemy); враг. рисовать(); }); },8. Обновите все элементы
Сначала определите, равна ли длина массива монстров 0. Если она равна 0 и уровень равен totalLevel, это означает, что уровень пройден. В противном случае будет отображен экран подготовки к следующему уровню, если координата y; массива монстров больше, чем координата y самолета плюс высота монстра, это будет означать, что игра провалилась.
Принцип анимации холста заключается в непрерывном рисовании, обновлении и очистке холста.
Принцип игровой паузы заключается в предотвращении выполнения функции requestAnimationFrame(), но не в сбросе элемента. Поэтому, когда статус статуса оценивается как остановленный, функция будет отключена.
//Обновляем статус всех элементов updateElement: function () { var self = this; var opts = this.opts; var враги = this.enemies; if (enemies.length === 0) { if (opts.level = == opts.totalLevel) { this.end('all-success'); else { this.end('success' } return; - 1].y >= this.planePosY - opts.enemySize) { this.end('failed'); //Очищаем холст ctx.clearRect(0, 0, CanvasWidth, CanvasHeight); //Рисуем; Canvas this .draw(); //Обновление статуса элемента this.updatePanel(); this.updateEnemeis(); //Непрерывный цикл updateElement requestAnimationFrame(function () { if(self.status === 'stop'){ return }else { self.updateElement() } };напиши в конце
Посредством вышеуказанных шагов выполняются основные функции игры. Другие элементы управления игровым процессом, включая начало, окончание, подсчет очков и т. д., здесь не описываются.
Что можно оптимизировать: При удержании пробела пули можно стрелять непрерывно. Однако когда я снова нажал клавишу направления, я обнаружил, что больше не могу стрелять. Лучше всего иметь возможность продолжать стрелять, когда вы можете двигаться.
Довольно интересно играть в игры с холстом. Кроме того, эту игру можно расширить и изменить на мобильную версию. Размер холста определяется путем получения ширины и высоты экрана. Часть клавиатуры меняется на сенсорные события (touchstart, touchmove). , touchend). Также можно изменить внешний вид монстров. Измените его на случайное падение с верхней части экрана, и у монстра увеличится здоровье (например, он исчезнет после 4-х выстрелов) и т. д.
Адрес загрузки: https://github.com/littleyljy/shoot.
Выше приведено все содержание этой статьи. Я надеюсь, что она будет полезна для изучения всеми. Я также надеюсь, что все поддержат сеть VeVb Wulin.