<Первая часть — Java Script. Космическая стрелялка. Туториал. Часть 1. ©HD4E>
4.Вражеские Корабли.
Время добавить несколько врагов. Я снова взял картинку с сайта ….. .org и сохранил в нашей папке images, дав ей название enemy.png. Затем я загрузил их в функции load:
wade.loadImage('images/enemy.png');
(< Можете скопировать моё изображение, либо создать своё
©HD4E>)
Теперь создадим новую функцию внутри нашей функции App, назвав её spawnEnemy. В этой функции мы создадим вражеский корабль где-то за пределом экрана(мы вычислим позицию, которая будет находится прямо за верхним краем экрана). Затем мы переместим корабль вниз, вычисляя позицию, которая находится прямо под нижним краем экрана. Для стартовой и конечной позиции, мы берем случайную координату X:
this.spawnEnemy = function()
{
// создаем спрайт
var sprite = new Sprite('images/enemy.png');
// вычисляем стартовую и конечную координату
var startX = (Math.random() - 0.5) * wade.getScreenWidth();
var endX = (Math.random() - 0.5) * wade.getScreenWidth();
var startY = -wade.getScreenHeight() / 2 - sprite.getSize().y / 2;
var endY = -startY;
// добавляем обьект на сцену и приводим его в движение
var enemy = new SceneObject(sprite, 0, startX, startY);
wade.addSceneObject(enemy);
enemy.moveTo(endX, endY, 200);
// когда враг закончит движение, удаляем его
enemy.onMoveComplete = function()
{
wade.removeSceneObject(this);
};
};
И теперь внизу функции init, мы можем вызвать функцию, которую мы создали. Вместо того, чтобы заспавнить вражеский корабль напрямую, давайте дадим игроку пару секунд чтобы подготовиться. Чтобы сделать это, мы создадим две других переменных в верхней части нашей App функции:
var nextEnemy;
var enemyDelay;
И внизу функции init добавим код:
enemyDelay = 2000;
nextEnemy = setTimeout(wade.app.spawnEnemy, enemyDelay);
Как вы можете видеть в выше приведенном коде, я ввожу задержку в 2000мс до того как заспавниться первый враг. Теперь вы можете попробовать запустить игру, и хотя это довольно круто, мы хотим больше чем одного врага.Фактически, внизу нашей функции spawnEnemy, мы вызовем ту же самую функцию spawnEnemy снова, чтобы заспавнить другого врага через некоторое время:
nextEnemy = setTimeout(wade.app.spawnEnemy, enemyDelay);
И чтобы сделать всё это более интересным, я собираюсь снижать задержку enemyDelay каждый раз когда мы спавним нового врага — но сделаем так чтобы минимальная задержка была 200мс ( мы не хотим спавнить более чем 5 врагов в секунду, или игра будет просто слишком сложной). Добавим этот код в конце функции spawnEnemy.
enemyDelay = Math.max(enemyDelay - 30, 200);
5.Уничтожение вражеских кораблей.
Игра начинает выглядеть многообещающей, но теперь мы хотим чтобы наши пули действительно уничтожали врагов. Чтобы сделать это, мы должны создать массив, чтобы сохранить все снаряды, которые активны в данный момент. Мы сделаем объявление массива, как обычно вначале функции App:
var activeBullets = [];
Каждый раз когда мы создаём снаряд, мы добавляем его в наш массив:
activeBullets.push(bullet);
(< Добавьте этот код в функцию init после создания объекта пули
var bullet = new SceneObject(sprite, 0, shipPosition.x, shipPosition.y - shipSize.y / 2);
// вставляем код сюда
©HD4E>)
Каждый раз когда мы удаляем снаряд, мы убираем его из массива activeBullets:
wade.removeObjectFromArray(this, activeBullets);
(< Добавьте этот код в функцию init после удаления снаряда
bullet.onMoveComplete = function()
{
wade.removeSceneObject(this);
// вставляем код сюда
};
©HD4E>)
Теперь в нашем основном цикле(main loop - fire), для каждого снаряда, который активен в данный момент, мы бы хотели видеть сталкивается ли он с врагом или нет:
for (var i=0; i < activeBullets.length; i++)
{
//объекты с которыми мы сталкиваемся
var colliders = activeBullets[i].getOverlappingObjects();
// перебираем эти объекты
for (var j=0; j < colliders.length; j++)
{
// если этот обьект враг, то ...
if (colliders[j].isEnemy)
{
// удаляем со сцены этот объект
wade.removeSceneObject(colliders[j]);
break;
}
}
}
(< Добавьте код выше в функцию setMainLoop после закрытия скобок
if (wade.isMouseDown() && time >= nextFireTime){
// ...
}
//вставляем код сюда
©HD4E>)
Конечно, для этой работы, нам нужно установить флаг isEnemy для каждого врага, когда мы создадим его. Итак в spawnEnemy пропишем код:
enemy.isEnemy = true;
(< Добавьте этот код в функцию spawnEnemy после создания врага:
enemy.moveTo(endX, endY, 200);
// вставляем код сюда
©HD4E>)
Но мы также хотим удалять все снаряды которые ударились во врагов. Это нормально, но мы также должны обновить массив activeBullets, и поскольку мы выполняем перебор по массиву, это было бы плохой идеей … Если только мы не выполняем перебор по массиву в обратном направление, в этом случае удаление элементов, в то время когда мы перебираем массив будет прекрасным. Таким образом мы можем переписать наш цикл вот так:
for (var i=activeBullets.length-1; i>=0; i--)
{
// получаем объекты с которыми мы сталкиваемся
var colliders = activeBullets[i].getOverlappingObjects();
// перебираем эти объекты
for (var j=0; j < colliders.length; j++)
{
//если этот объект враг, то ...
if (colliders[j].isEnemy)
{
// удаляем со сцены этот объект
wade.removeSceneObject(colliders[j]);
// удаляем со сцены снаряд
wade.removeSceneObject(activeBullets[i]);
// удаляем снаряд из нашего массива
wade.removeObjectFromArrayByIndex(i, activeBullets);
break;
}
}
}
(< Замените старый код
for (var i=0; i < activeBullets.length; i++){
//... много кода ...
}
на вышеприведенный. Либо вставьте этот код, если вы еще не напечатали предыдущий :) ©HD4E>)
Взрыв это круто, и это замечательное место чтобы добавить взрыв, итак до удаления нашего объекта давайте добавим взрыв:
// находим позицию объекта с которым столкнулись
var position = colliders[j].getPosition();
// запускаем функцию создания взрыва
wade.app.explosion(position);
(< Добавьте вышеприведенный код до удаления объекта с которым мы сталкиваемся
if (colliders[j].isEnemy)
{
// вставляем код сюда
wade.removeSceneObject(colliders[j]);
//... остальной код
©HD4E>)
Теперь создадим функцию взрыва с названием explosion
this.explosion = function(position)
{
// создаем новую анимацию
var animation = new Animation('images/boom.png', 6, 4, 30);
// создаем новый спрайт
var explosionSprite = new Sprite();
// устанавливаем размер спрайта
explosionSprite.setSize(100, 100);
//добавляем к спрайту анимацию
explosionSprite.addAnimation('boom', animation);
// создаём новый объект взрыва
var explosion = new SceneObject(explosionSprite, 0, position.x, position.y);
// добавляем обьект на сцену
wade.addSceneObject(explosion);
// запускаем анимацию взрыва
explosion.playAnimation('boom');
//событие которое срабатывает когда кончится анимация
explosion.onAnimationEnd = function()
{
// удаляем обьект
wade.removeSceneObject(this);
};
};
(<Добавтье эту функцию explosion в самом конце общей функции App, переде её закрытием ©HD4E>)
Я просто добавил обьект анимации, который мы сразу же удалим, с помощью функции-события onAnimationEnd, как только кончится анимация. Конечно же мы должны загрузить изображение, которое мы здесь используем( это 6x4 таблица спрайтов, которую я назвал boom.png и разместил в папке images).
Итак вставим этот код в нашу функцию load:
wade.loadImage('images/boom.png');
И теперь у нас есть враги, которые взрываются.
6.Вражеский огонь.
Но было бы хорошо если бы эти враги разварачивались к нам лицом, и стреляли снарядами в нас самих. Давайте сначала разберемся с ориентацией. Мы будем обновлять ориентацию каждого врага на каждом шаге симуляции, итак добавим некоторый код в конце нашей функции main loop(основоного цикла). Или даже лучше, мы бы могли создать новую функцию main loop, назва её 'rotate enemy'(вращение врагов), для примера, и запустим её вместе с нашим основным циклом main loop 'fire'. Но мы уже сделали это, и я хочу показать вам другой способ как сделать это.
На каждом шаге симуляции, WADE вызывает функцию step для каждого обьекта на сцене
(<
SceneObject.step () - выполняет шаг симуляции для объекта
(c)HD4E>)
Таким образом мы можем сделать это после создания каждого врага:
// В объекте enemy создаем переменную originalStep и присваиваем ей оригинальную функцию step
enemy.originalStep = enemy.step;
// переписываем оригинальную функцию step
enemy.step = function()
{
// сначала запускаем оригинальную функция step, а потом ...
this.originalStep();
// создадим вращение здесь
};
Таким образом мы перепишем функцию, которая вызывается при каждом шаге симуляции для данного обьекта(т.е. врага) и WADE будет вызывать нашу step функцию. Эта функция, в свою очередь, вызывает оригинальную step функцию и затем продолжает вращать обьект. Этот подход оказывается проще, поскольку(в отличие от того, что мы делали для нашего основного цикла 'fire') здесь нам не нужно поддерживать список активных вражеских кораблей и беспокоится об обновление вещей, когда вражеский корабль умирает.
Теперь давайте сделаем немного вращения. Нам нужно вычислить угол между вражеским кораблем и кораблем игрока, затем мы повернем вражеский корабль на этот угол:
// получаем позицию врага
var enemyPosition = this.getPosition();
// получаем позицию игрока
var playerPosition = ship.getPosition();
// делаем вычисление
var angle = Math.atan2(playerPosition.y - enemyPosition.y, playerPosition.x - enemyPosition.x) - 3.141 / 2;
// устанавливаем угол который мы вычислили
this.setRotation(angle);
Теперь давайте заставим вражеский корабль стрелять. Я добавил новое изображение enemyBullet.png, в нашу папку images, и загрузил его как обычно.
wade.loadImage('images/enemyBullet.png');
Теперь в функции spawnEnemy мы можем создать новую функцию fire для врагов, где мы вычислим направление нашего снаряда(вектор от врага к игроку, который будет нормализован), затем мы создадим снаряд и скажем ему двигаться. Хотя мы не хотим вызывать эту функцию сразу же. Мы хотим запланировать её выполнение(например, через 1 секунду после того, как появиться вражеский корабль) и затем снова задать выполнение этой функции, каждые 0.5 сек для вычисления нового угла. WADE сделает это очень легко, обеспечивая нас очень удобной функцией планирования
(<
SceneObject.schedule (time, functionName, data) - планирует выполнение и обработку функции для этого объекта.
(c)HD4E>)
enemy.fire = function()
{
var enemySize = this.getSprite().getSize();
var enemyPosition = this.getPosition();
var playerPosition = ship.getPosition();
// вычисляем направление
var dx = playerPosition.x - enemyPosition.x;
var dy = playerPosition.y - enemyPosition.y;
var length = Math.sqrt(dx * dx + dy * dy);
dx /= length;
dy /= length;
// вычисляем начальную и конечную позицию для снаряда
var startX = enemyPosition.x + dx * enemySize.x / 2;
var startY = enemyPosition.y + dy * enemySize.y / 2;
var endX = startX + dx * 3000;
var endY = startY + dy * 3000;
// создаем снаряд
var sprite = new Sprite('images/enemyBullet.png');
var bullet = new SceneObject(sprite, 0, startX, startY);
wade.addSceneObject(bullet);
bullet.moveTo(endX, endY, 200);
// удаляем снаряд когда он закончил движение
bullet.onMoveComplete = function()
{
wade.removeSceneObject(this);
};
// планируем следующий снаряд, через 1 сек = 1000 мс запуститься функция "fire"
this.schedule(1000, 'fire');
};
// через 500 мс, запуститься функция fire
enemy.schedule(500, 'fire');
Обратите внимание, что когда враг умирает(т.е. когда он удаляется со сцены) любые запланированные функции для него, будут отменены.
Вот весь код на данном этапе:
App = function()
{
var ship;
var lastFireTime = 0;
var fireRate = 5;
var nextEnemy;
var enemyDelay;
var activeBullets = [];
this.load = function()
{
wade.loadImage('images/ship.png');
wade.loadImage('images/bullet.png');
wade.loadImage('images/enemy.png');
wade.loadImage('images/boom.png');
wade.loadImage('images/enemyBullet.png');
};
this.init = function()
{
wade.setMinScreenSize(708, 398);
wade.setMaxScreenSize(708, 398);
var sprite = new Sprite('images/ship.png');
var mousePosition = wade.getMousePosition();
ship = new SceneObject(sprite, 0, mousePosition.x, mousePosition.y);
wade.addSceneObject(ship);
wade.setMainLoop(function()
{
var nextFireTime = lastFireTime + 1 / fireRate;
var time = wade.getAppTime();
if (wade.isMouseDown() && time >= nextFireTime)
{
lastFireTime = time;
var shipPosition = ship.getPosition();
var shipSize = ship.getSprite().getSize();
var sprite = new Sprite('images/bullet.png');
var bullet = new SceneObject(sprite, 0, shipPosition.x, shipPosition.y - shipSize.y / 2);
wade.addSceneObject(bullet);
activeBullets.push(bullet);
bullet.moveTo(shipPosition.x, -500, 600);
bullet.onMoveComplete = function()
{
wade.removeSceneObject(this);
};
}
for (var i=activeBullets.length-1; i>=0; i--)
{
var colliders = activeBullets[i].getOverlappingObjects();
for (var j=0; j < colliders.length; j++)
{
if (colliders[j].isEnemy)
{
var position = colliders[j].getPosition();
wade.app.explosion(position);
wade.removeSceneObject(colliders[j]);
wade.removeSceneObject(activeBullets[i]);
wade.removeObjectFromArrayByIndex(i, activeBullets);
break;
}
}
}
}, 'fire');
enemyDelay = 2000;
nextEnemy = setTimeout(wade.app.spawnEnemy, enemyDelay);
};
this.explosion = function(position)
{
var animation = new Animation('images/boom.png', 6, 4, 30);
var explosionSprite = new Sprite();
explosionSprite.setSize(100, 100);
explosionSprite.addAnimation('boom', animation);
var explosion = new SceneObject(explosionSprite, 0, position.x, position.y);
wade.addSceneObject(explosion);
explosion.playAnimation('boom');
explosion.onAnimationEnd = function()
{
wade.removeSceneObject(this);
};
};
this.onMouseMove = function(eventData)
{
ship.setPosition(eventData.screenPosition.x, eventData.screenPosition.y);
};
this.spawnEnemy = function()
{
var sprite = new Sprite('images/enemy.png');
var startX = (Math.random() - 0.5) * wade.getScreenWidth();
var endX = (Math.random() - 0.5) * wade.getScreenWidth();
var startY = -wade.getScreenHeight() / 2 - sprite.getSize().y / 2;
var endY = -startY;
var enemy = new SceneObject(sprite, 0, startX, startY);
wade.addSceneObject(enemy);
enemy.moveTo(endX, endY, 200);
enemy.isEnemy = true;
enemy.originalStep = enemy.step;
enemy.step = function()
{
this.originalStep();
var enemyPosition = this.getPosition();
var playerPosition = ship.getPosition();
var angle = Math.atan2(playerPosition.y - enemyPosition.y, playerPosition.x - enemyPosition.x) - 3.141 / 2;
this.setRotation(angle);
};
enemy.onMoveComplete = function()
{
wade.removeSceneObject(this);
};
enemy.fire = function()
{
var enemySize = this.getSprite().getSize();
var enemyPosition = this.getPosition();
var playerPosition = ship.getPosition();
var dx = playerPosition.x - enemyPosition.x;
var dy = playerPosition.y - enemyPosition.y;
var length = Math.sqrt(dx * dx + dy * dy);
dx /= length;
dy /= length;
var startX = enemyPosition.x + dx * enemySize.x / 2;
var startY = enemyPosition.y + dy * enemySize.y / 2;
var endX = startX + dx * 3000;
var endY = startY + dy * 3000;
var sprite = new Sprite('images/enemyBullet.png');
var bullet = new SceneObject(sprite, 0, startX, startY);
wade.addSceneObject(bullet);
bullet.moveTo(endX, endY, 200);
bullet.onMoveComplete = function()
{
wade.removeSceneObject(this);
};
this.schedule(1000, 'fire');
};
enemy.schedule(500, 'fire');
nextEnemy = setTimeout(wade.app.spawnEnemy, enemyDelay);
enemyDelay = Math.max(enemyDelay - 30, 100);
};
};
Группа в вк:
https://vk.com/hd4e_games
Продолжение следует ...