Здравствуй, Хабр! Совсем недавно разбирая графический движок Pixi.js было обнаружено, что по нему практически нет обучающего материала на русском языке. И хоть все разработчики поголовно должны знать английский, вводный урок на родном языке, вряд ли сильно навредит. Мы будем писать простейшую мини-игру, суть которой уничтожать шарики, до того как они упали на землю. Вот что у нас получится в конечном итоге:
Итак, вкратце о движке. Pixi.js — это 2D фреймворк с поддержкой WebGL. Авторы фреймворка утверждают, что движок можно использовать для создания любой графики на странице, но первое, что приходит на ум видя его возможности — это разработка игр. Главные преимущества движка, это доступный API и конечно же, скорость. Вот для примера, известные в узких кругах зайчики, для оценки производительности:
www.goodboydigital.com/pixijs/bunnymark
Пожалуй, приступим к написанию игры. В первую очередь скачиваем библиотеку.
Pixi можно скачать с официального сайта, вот ссылка. Есть возможность подключить библиотеку с помощью CDN. И так же доступно скачивание с помощью npm. Я буду устанавливать с помощью последнего варианта. Открываем папку с проектом в консоли и пишем:
Ждем когда установятся все пакеты и после этого, как это обычно бывает, создаем файл index.html и main.js подключаем в шапку index.html наш фреймворк. В зависимости от способа установки, будут разные source. Если вы устанавливали с библиотеку с помощью npm путь будет такой:
Сбрасываем стили браузера, я делаю это для собственного удобства (это можно упустить), подключаем main.js в тело страницы и начинаем писать.
В первую очередь нужно создать наш холст, в pixi это делается с помощью одной команда.
Итак, создаем глобальный объект model (в котором будут хранится все генерируемые объекты) и внутри пишем свойство createCanvas, которое будет создавать холст. В нем пишем функцию которая создает сам холст и вторая, очень важная команда (позже я напишу, чем она так важна) выводит это холст в тело страницы. Выглядит это следующим образом (у меня игра будет на всю страницу, поэтому сверху у меня параметры высоты и ширины страницы пользователя):
Если вы все сделали правильно, то при вызове из консоли браузера:
У вас сгенерируется canvas на всю ширину страницы. Сразу создадим объект view, в котором будет функция которая выводит нашу игру на экран:
Следующий объект в модели — это шарик, который будет рандомно генерироваться вверху страницы и падать вниз. Для этого в модели создаем новое свойство (функцию), которая будет генерировать тот самый шарик. И создаем объект controller, в котором будет событие, которое будет происходить при клике на шарик. В нашем случае шарик должен исчезать.
Вот как разросся наш код (в комментариях я указал, что делает каждая строка):
Далее. Теперь добавим гравитацию, чтобы шарики падали и заодно сделаем чтобы эти шарики появлялись каждые пол секунды.
Для этого в начале страницы добавляем переменную gravity (сразу после массива colors), которая отвечает за гравитацию, по умолчанию ставим силу притяжение 4. Так же добавляем переменную figuresAmount с значение 0 (количество фигур при запуске игры) и создаем пустой массив figure, куда мы будем отправлять все ново созданные шарики. В функции drawCircle добавляем два действия (я не буду вставлять весь код, только строку предшествующую новонаписанному, и строку которая была написана ранее и будет после новонаписанного):
— наши добавленные переменные.
И новые действия в функции drawCircle:
Ну и во view.loadGame добавляем отрисовку шарика каждую секунду, сразу после первой отрисовки и запускаем функцию, которая постоянно обновляет холст с внесением изменений. В Pixi за это отвечает такая цепочка:
у нас так:
Теперь шарики начинают падать. Осталось добавить проверку столкновений с землей (нижней границей холста) и вывести на экран строку Game Over.
Для этого нужно в первую очередь для нашего шарика создать два состояния, существует он или уничтожен. Внутри drawCircle добавляем новое свойство circle.live, которое по умолчанию true(когда мы только создали шарик, по другому быть и не может), так же нужно задать ему порядковые номер, чтобы при попадании по шарику именно тот шарик в который мы попали стал false :)
Для этого у нас есть переменная figuresAmount, которая увеличивается при каждом новом шарике. В общем пишем в drawCircle:
Далее создаем функцию в модели которая выводит текст (что да как в комментариях):
В clearFigure указываем, что при каждом попадании по шарику, состояние circle.live меняется на false. Мы делаем это действие по той простой причине, что функция clear() не удаляет объект, а просто стирает его сanvasa (по сути шарик продолжает падать, поэтому надо указать что этот шарик уничтожен и его можно пропустить никак не реагируя).
Пример кода:
И запускаем проверку столкновения и вызов gameOver в случае необходимости при каждом обновлении сцены:
На этом все, надеюсь я ничего не забыл. Если есть какие-то ошибки, то можно свериться с примером на codepen. Пишите в комментариях об ошибках, поправлю.
Надеюсь после прочтения этой статьи изучение Pixi.js станет немного проще. Позже я расскажу, как вывести подсчет очков на экран и сделать анимацию при попадании по шарику.
Всем спасибо!
Итак, вкратце о движке. Pixi.js — это 2D фреймворк с поддержкой WebGL. Авторы фреймворка утверждают, что движок можно использовать для создания любой графики на странице, но первое, что приходит на ум видя его возможности — это разработка игр. Главные преимущества движка, это доступный API и конечно же, скорость. Вот для примера, известные в узких кругах зайчики, для оценки производительности:
www.goodboydigital.com/pixijs/bunnymark
Пожалуй, приступим к написанию игры. В первую очередь скачиваем библиотеку.
Pixi можно скачать с официального сайта, вот ссылка. Есть возможность подключить библиотеку с помощью CDN. И так же доступно скачивание с помощью npm. Я буду устанавливать с помощью последнего варианта. Открываем папку с проектом в консоли и пишем:
npm install pixi.js
Ждем когда установятся все пакеты и после этого, как это обычно бывает, создаем файл index.html и main.js подключаем в шапку index.html наш фреймворк. В зависимости от способа установки, будут разные source. Если вы устанавливали с библиотеку с помощью npm путь будет такой:
node_module/pixi.js/dist/pixi.js
Сбрасываем стили браузера, я делаю это для собственного удобства (это можно упустить), подключаем main.js в тело страницы и начинаем писать.
В первую очередь нужно создать наш холст, в pixi это делается с помощью одной команда.
Итак, создаем глобальный объект model (в котором будут хранится все генерируемые объекты) и внутри пишем свойство createCanvas, которое будет создавать холст. В нем пишем функцию которая создает сам холст и вторая, очень важная команда (позже я напишу, чем она так важна) выводит это холст в тело страницы. Выглядит это следующим образом (у меня игра будет на всю страницу, поэтому сверху у меня параметры высоты и ширины страницы пользователя):
var width = window.innerWidth; //получаем ширину экрана
var height = window.innerHeight; // получаем высоту экрана
var app; //создаем глобальную переменную нашей игры
var model = {
createCanvas: function() {
app = new PIXI.Application(width, height); //создаем холст
document.body.appendChild(app.view); //выводим его в тело страницы
}
}
Если вы все сделали правильно, то при вызове из консоли браузера:
model.createCanvas();
У вас сгенерируется canvas на всю ширину страницы. Сразу создадим объект view, в котором будет функция которая выводит нашу игру на экран:
var view = {
loadGame: function(){
model.createCanvas();
}
}
view.loadGame(); //запускаем игру
Следующий объект в модели — это шарик, который будет рандомно генерироваться вверху страницы и падать вниз. Для этого в модели создаем новое свойство (функцию), которая будет генерировать тот самый шарик. И создаем объект controller, в котором будет событие, которое будет происходить при клике на шарик. В нашем случае шарик должен исчезать.
Вот как разросся наш код (в комментариях я указал, что делает каждая строка):
var width = window.innerWidth; //получаем ширину экрана
var height = window.innerHeight; // получаем высоту экрана
var app; //создаем глобальную переменную нашей игры
var colors = [0xFFFF0B, 0xFF700B, 0x4286f4, 0x4286f4, 0xf441e8, 0x8dff6d, 0x41ccc9, 0xe03375, 0x95e032, 0x77c687, 0x43ba5b, 0x0ea3ba]; //массив цветов, 0x вместо #
var model = {
createCanvas: function() {
app = new PIXI.Application(width, height); //создаем холст
document.body.appendChild(app.view); //выводим его в тело страницы
},
drawCircle: function() {
rand = Math.floor(Math.random() * colors.length); //генерим рандомное число (в промежутке от 0 до количества цветов в массиве цветов)
var radius = 50; //радиус круга
var inAreaX = width - 100; //возможные координаты по оси X, которые может занимать круг, ширина страницы минус его диаметр
var circleY = -50; //круг должен создаваться за пределами холста (чтобы глянуть, отрисовался ли круг, измените отрицательное значение на положительное)
var circleX = Math.floor(Math.random()* inAreaX); //создаем круг в рандомном месте по оси X
var circle = new PIXI.Graphics(); //создаем новый графический элемент
circle.lineStyle(0); //начинаем рисовать
circle.beginFill(colors[rand], 1); //задаем рандомный цвет
circle.drawCircle(circleX, circleY, radius); //рисуем кружок, ведь он наш дружок
circle.endFill(); //закончили отрисовку
circle.interactive = true; //делаем круг интерактивным
circle.buttonMode = true; //меняем курсор при наведении
app.stage.addChild(circle); //выводим круг на холсте
circle.on('pointerdown', controller.clearFigure); //добавляем возможность при клике на фигуру удалить её
}
}
var view = {
loadGame: function() {
model.createCanvas();
model.drawCircle();//отрисовываем кружок, пока один раз
}
}
var controller = {
clearFigure: function(){
this.clear(); //удаляем фигуры по которой кликнули
}
}
view.loadGame();
app.stage.addChild(circle);
— это очень важная команда в Pixi, так как в этом фреймворке, чтобы на холсте что-то появилось, кроме создания объекта, его нужно обязательно вывести. В 70% процентах случаев, если вы все сделали правильно, а объект на холсте не отображается, то скорее всего вы забыли его вывести с помощью этой команды.Далее. Теперь добавим гравитацию, чтобы шарики падали и заодно сделаем чтобы эти шарики появлялись каждые пол секунды.
Для этого в начале страницы добавляем переменную gravity (сразу после массива colors), которая отвечает за гравитацию, по умолчанию ставим силу притяжение 4. Так же добавляем переменную figuresAmount с значение 0 (количество фигур при запуске игры) и создаем пустой массив figure, куда мы будем отправлять все ново созданные шарики. В функции drawCircle добавляем два действия (я не буду вставлять весь код, только строку предшествующую новонаписанному, и строку которая была написана ранее и будет после новонаписанного):
var colors = [0xFFFF0B, 0xFF700B, 0x4286f4, 0x4286f4, 0xf441e8, 0x8dff6d, 0x41ccc9, 0xe03375, 0x95e032, 0x77c687, 0x43ba5b, 0x0ea3ba]; //массив цветов
var gravity = 4;
var figuresAmount = -1; //количество созданных фигур (не ноль, потому как с нуля мы начинаем считать фигуры)
var figure = []; //массив хранящий нашу фигуру
{
— наши добавленные переменные.
И новые действия в функции drawCircle:
circle.buttonMode = true; //меняем курсор при наведении
figuresAmount++; //увеличиваем количество созданных шариков
figure.push(circle); //обратиться на прямую к объекту circle мы не можем, поэтому отправляем его в массив
app.stage.addChild(circle); //выводим круг на холсте
Ну и во view.loadGame добавляем отрисовку шарика каждую секунду, сразу после первой отрисовки и запускаем функцию, которая постоянно обновляет холст с внесением изменений. В Pixi за это отвечает такая цепочка:
app.ticker.add();
у нас так:
model.drawCircle(); //рисуем круг в первый раз
setInterval(model.drawCircle, 500); //рисуем шарик каждые пол секунды
app.ticker.add(function() { //постоянное обновление холста
for (var i = 0; i < figuresAmount; i++) {
figure[i].position.y += gravity; //заставляем гравитацию работать
}
});
} //закрываем функцию loadGame();
Теперь шарики начинают падать. Осталось добавить проверку столкновений с землей (нижней границей холста) и вывести на экран строку Game Over.
Для этого нужно в первую очередь для нашего шарика создать два состояния, существует он или уничтожен. Внутри drawCircle добавляем новое свойство circle.live, которое по умолчанию true(когда мы только создали шарик, по другому быть и не может), так же нужно задать ему порядковые номер, чтобы при попадании по шарику именно тот шарик в который мы попали стал false :)
Для этого у нас есть переменная figuresAmount, которая увеличивается при каждом новом шарике. В общем пишем в drawCircle:
circle.buttonMode = true; //меняем курсор при наведении
circle.live = true; //указываем что наш шарик жив и не пал жертвой выстрела
figuresAmount++;
circle.num = figuresAmount; //даем нашему кругу порядковый номер
figure.push(circle); //обратиться на прямую к объекту circle мы не можем, поэтому отправляем его в массив
Далее создаем функцию в модели которая выводит текст (что да как в комментариях):
gameOver: function() {
var style = new PIXI.TextStyle({ //стили для текста
fill: '0xffffff',
fontSize: 36,
});
var gameOverText = new PIXI.Text('Game Over', style); //собственно выводимый текст
gameOverText.x = width / 2; //центрируем относительно экрана
gameOverText.y = height / 2; //центрируем относительно экрана
gameOverText.pivot.x = 50; //выравниваем по оси х
gameOverText.pivot.y = 50; // выравниваем по оси y
app.stage.addChild(gameOverText); //выводим на холсте
}
В clearFigure указываем, что при каждом попадании по шарику, состояние circle.live меняется на false. Мы делаем это действие по той простой причине, что функция clear() не удаляет объект, а просто стирает его сanvasa (по сути шарик продолжает падать, поэтому надо указать что этот шарик уничтожен и его можно пропустить никак не реагируя).
Пример кода:
clearFigure: function() {
this.clear();
figure[this.num].live = false;
}
И запускаем проверку столкновения и вызов gameOver в случае необходимости при каждом обновлении сцены:
app.ticker.add(function() { //постоянное обновление холста
for (var i = 0; i < figuresAmount; i++) {
figure[i].position.y += gravity; //заставляем гравитацию работать
if (figure[i].position.y > height && figure[i].live == true) {//проверяет столкнулся ли шарик с низом страницы и если он жив, не пропускает его, а отменяет выводит на экран "игра окончена и завершает действие гравитации"
model.gameOver();
return false;
}
}
На этом все, надеюсь я ничего не забыл. Если есть какие-то ошибки, то можно свериться с примером на codepen. Пишите в комментариях об ошибках, поправлю.
Надеюсь после прочтения этой статьи изучение Pixi.js станет немного проще. Позже я расскажу, как вывести подсчет очков на экран и сделать анимацию при попадании по шарику.
Всем спасибо!
Поделиться с друзьями
Комментарии (17)
TheShock
04.06.2017 18:43+2rand = Math.floor(Math.random() * colors.length);
Плохое название переменной. Забыли var. И вообще — лучше бы рандом выделить в отдельную функцию.
function random(max) { return Math.floor(Math.random() * random); } function randomElem (array) { return array[random(array.length)]; } circle.beginFill(randomElem(colors), 1);
У вас объект вроде называется model, а содержит код view, вроде «circle.drawCircle». Вообще в таком коде не стоит стремиться к MVC. Лучше создайте класс, у которого будут методы отрисовки и изменения.\
class GameObject { constructor (x, y) { this.x = x; this.y = y; this.radius = 50; } render () { // draw code } }
На самом деле drawCircle по смыслу больше подходит название createCircle, текущее вводит в заблуждение.
В controller.clearFigure this ссылается на… circle. Не надо так, крайне неочевидно.
При клике по кругу он не удаляется, а только помечается мертвым. И хотя он, вроде, не отрисовывается, но продолжает смещаться каждый кадр. В процессе игры потребление памяти и процессора будет расти линейно.
vasIvas
Я бы не хотел видеть подобные статьи в начале своего обучения, они причинят только вред.
Код говорит о том, что Вы совсем новичок, который забивает гвозди не той стороной молотка.
Поэтому рассудите сами, Вы бы хотели чтобы Вас учили забивать гвозди не той стороной молотка?
И тем более это не статьи уровня хабра.
stranger777
Уровень хабра упал. Тому причина, что он всё больше и больше. Был маленький — знали спецы, уровень был высокий. Приходили люди — меняли содержание хабра. Сейчас его размер таков, что любой новичок (вполне справедливо) считает нужным наследить. Это как температура Вселенной по мере развития — так и тут.
Вот взять мой пост про сборку nginx. Когда я его писал, я хотел, чтобы за счёт нынешних размеров хабра у людей был доступ ко скрипту, который всё сделает быстро. А когда я писал свой первый пост, перевод, вообще «боялся», что не примут. Типа, нафиг оно надо, если действительно все читают в оригинале… Если бы публиковал его при нынешних обстоятельствах, то уже не боялся бы.
Порог вхождения снижен давным давно.
vasIvas
А читателям не нужно чтобы здесь было то, что все за них делает. Им нужно темы для рассуждений и подробные мануалы. Создать такой материал не просто и человек с амбициями не будет выкладывать другой. А для простого материальчика есть медиум. Но для подобного этому, на мой взгляд, места вообще быть не должно.
stranger777
Понимаю ваш идеализм, но сейчас это уже невозможно. Вот представьте, что вы ничего не знаете и не умеете. Только начинаете, так сказать. Гуглите какой-то вопрос на русском — вам выпадает публикация хабра (вместо вопроса на тостере). Гуглите второй, третий, десятый… в конце концов у вас складывается ощущение, что хабр — он и должен быть некоей палочкой выручалочкой, а не стэковерфлоу или что-то такое. И да, тут же вас подмывает наваять что-нибудь самому. И себя научить, и другим чуть помочь. Хабр не стал лучше или хуже. Он стал другим из-за размеров. Опять же аналогия из физики: микромир — одни законы, кванты, неопределённость, а в больших масштабах — гравитация, искривление уже самого пространства.
Вот примерно так оно и получается.
TheShock
Следуя вашей логике, в интернете не может быть хороших материалов.
vasIvas
Вы не так поняли. Я о том, что человек с амбициями не будет спешить помочь миру, он будет ждать, пока не получит достаточных знаний, чтобы реально помочь кому-то. В данном случаи автор явно имеет другие планы. Он уже создал три статьи, каждая из которых является началом отдельной ветки и каждая хуже предыдущей. На мой взгляд, это просто стратегия с любой целью, кроме как помочь людям.
TheShock
Вообще с тем, чтобы автору крайне необходимо повысить качество контента — я согласен. Но, мне кажется, такие вещи необходимо говорить аргументированно, а не просто: «Ваш контент — говно»
vasIvas
Если бы я аргументировал по пунктам — не правильная архитектура, не правильная логика, неправильно-по старинки используется написание кода, то скорее всего, если бы не Вы, то кто-то другой сказал что и этого мало, нужно разжевать, ведь ему так кажется. Но я, как уже сказал ранее, не хожу сюда за этим.
TheShock
Всегда были статьи высокого уровня и низкого.
Ваши статьи что в 2014-м, что в 2016-м приняли одинаково. По две статьи на год. Одна — слегка заминувана, другая — слегка заплюсована. Не понимаю, как вы из этого сделали свои выводы.
stranger777
И всегда будут статьи высокого и низкого уровня. И среднее значение тоже будет. Выводы я свои сделал по тому, что (почти) во всех случаях получал ожидаемое поведение. И по тому, что я видел хабр другим и перед тем, как наконец написать свой «дебют» читал несколько лет анонимно. Именно поэтому, что читал несколько лет молча, хабр относительно предсказуем в моих глазах.
TheShock
У вас интересная смесь эффекта Розенталя и эффекта Даннинга — Крюгера.
В силу своего пессимизма ждали отрицательную реакцию. Вы боялись и никогда не тренировались — статьи получились слабыми. Отрицательная реакция последовала. Если бы не боялись, а писали, тренировались и получали бы фидбек, то статьи получались бы лучше. Из-за пессимизма и страха, что получится плохо — у вас получилось плохо.
Вы просто сидели, ничего не делали, но решили, что у вас много опыта и понимания Хабра. Когда вы выложили статьи и получили на них негативый отклик — посчитали, что вы смогли это предвидеть из-за опытности и хорошего понимания аудитории. Хотя, на самом деле, просто не смогли написать достойные статьи из-за неопытности и плохого понимания аудитории.
stranger777
Радостно говорить с умным человеком. Моим ответом будет статья. Нескоро — я редко пишу. Но будет.
Всего доброго вам! Спасибо. Вы сделали мой вечер. :)
TheShock
Вместо того, чтобы сказать «статья» фигня — аргументировано прошлись бы по недостаткам кода в комментарии и помогли бы этим новичкам. Они бы, прочтя ее, поняли, как это в целом сделать, а потом прочтя ваш комментарий, поняли бы, что именно делать не надо.
А так они остались у разбитого корыта. Ведь так, как в статье — сделать нельзя. И как именно сделать — они не знают.
vasIvas
Признаюсь честно, я сюда прихожу читать «умные слова» и обсуждать «умные темы», а не сюсюськаться.
Вот если бы статья была о архитектуре, проектировании, парадигме, то я бы стал аргументировать, если бы, как в данном случаи, не пришлось бы аргументировать и исправлять всю статью! Да и для такого уровня есть тостер и ему подобные, хотя и там, в большинстве случаев ответят такие-же не компетентные новички, которые хотят показать что они что-то знают. А умных там все меньше и меньше из-за того, что новички им портят удовольствие.
TheShock
Больше похоже, что вы приходите сюда побубнеть.
Тостер? Для статьи?
Вроде вас?
vasIvas
Тостер для вопросов, которые автору не понятны. И да, вроде меня. Меня не тянет писать статьи, только потому, что я не смогу выдать качество, которое считаю приемлемым.