Здравствуй, Хабр! Совсем недавно разбирая графический движок 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)


  1. vasIvas
    04.06.2017 13:53
    +1

    Я бы не хотел видеть подобные статьи в начале своего обучения, они причинят только вред.
    Код говорит о том, что Вы совсем новичок, который забивает гвозди не той стороной молотка.
    Поэтому рассудите сами, Вы бы хотели чтобы Вас учили забивать гвозди не той стороной молотка?

    И тем более это не статьи уровня хабра.


    1. stranger777
      04.06.2017 14:48
      +5

      уровня хабра

      Уровень хабра упал. Тому причина, что он всё больше и больше. Был маленький — знали спецы, уровень был высокий. Приходили люди — меняли содержание хабра. Сейчас его размер таков, что любой новичок (вполне справедливо) считает нужным наследить. Это как температура Вселенной по мере развития — так и тут.
      Вот взять мой пост про сборку nginx. Когда я его писал, я хотел, чтобы за счёт нынешних размеров хабра у людей был доступ ко скрипту, который всё сделает быстро. А когда я писал свой первый пост, перевод, вообще «боялся», что не примут. Типа, нафиг оно надо, если действительно все читают в оригинале… Если бы публиковал его при нынешних обстоятельствах, то уже не боялся бы.
      Порог вхождения снижен давным давно.


      1. vasIvas
        04.06.2017 15:52
        -1

        А читателям не нужно чтобы здесь было то, что все за них делает. Им нужно темы для рассуждений и подробные мануалы. Создать такой материал не просто и человек с амбициями не будет выкладывать другой. А для простого материальчика есть медиум. Но для подобного этому, на мой взгляд, места вообще быть не должно.


        1. stranger777
          04.06.2017 18:41
          -3

          Понимаю ваш идеализм, но сейчас это уже невозможно. Вот представьте, что вы ничего не знаете и не умеете. Только начинаете, так сказать. Гуглите какой-то вопрос на русском — вам выпадает публикация хабра (вместо вопроса на тостере). Гуглите второй, третий, десятый… в конце концов у вас складывается ощущение, что хабр — он и должен быть некоей палочкой выручалочкой, а не стэковерфлоу или что-то такое. И да, тут же вас подмывает наваять что-нибудь самому. И себя научить, и другим чуть помочь. Хабр не стал лучше или хуже. Он стал другим из-за размеров. Опять же аналогия из физики: микромир — одни законы, кванты, неопределённость, а в больших масштабах — гравитация, искривление уже самого пространства.
          Вот примерно так оно и получается.


        1. TheShock
          04.06.2017 18:47

          Создать такой материал не просто и человек с амбициями не будет выкладывать другой

          Следуя вашей логике, в интернете не может быть хороших материалов.


          1. vasIvas
            04.06.2017 18:52

            Вы не так поняли. Я о том, что человек с амбициями не будет спешить помочь миру, он будет ждать, пока не получит достаточных знаний, чтобы реально помочь кому-то. В данном случаи автор явно имеет другие планы. Он уже создал три статьи, каждая из которых является началом отдельной ветки и каждая хуже предыдущей. На мой взгляд, это просто стратегия с любой целью, кроме как помочь людям.


            1. TheShock
              04.06.2017 19:15

              Вообще с тем, чтобы автору крайне необходимо повысить качество контента — я согласен. Но, мне кажется, такие вещи необходимо говорить аргументированно, а не просто: «Ваш контент — говно»


              1. vasIvas
                04.06.2017 19:22

                Если бы я аргументировал по пунктам — не правильная архитектура, не правильная логика, неправильно-по старинки используется написание кода, то скорее всего, если бы не Вы, то кто-то другой сказал что и этого мало, нужно разжевать, ведь ему так кажется. Но я, как уже сказал ранее, не хожу сюда за этим.


      1. TheShock
        04.06.2017 18:26

        Уровень хабра упал

        Всегда были статьи высокого уровня и низкого.

        Когда я его писал

        Ваши статьи что в 2014-м, что в 2016-м приняли одинаково. По две статьи на год. Одна — слегка заминувана, другая — слегка заплюсована. Не понимаю, как вы из этого сделали свои выводы.


        1. stranger777
          04.06.2017 18:50

          И всегда будут статьи высокого и низкого уровня. И среднее значение тоже будет. Выводы я свои сделал по тому, что (почти) во всех случаях получал ожидаемое поведение. И по тому, что я видел хабр другим и перед тем, как наконец написать свой «дебют» читал несколько лет анонимно. Именно поэтому, что читал несколько лет молча, хабр относительно предсказуем в моих глазах.


          1. TheShock
            04.06.2017 19:05
            +2

            У вас интересная смесь эффекта Розенталя и эффекта Даннинга — Крюгера.

            В силу своего пессимизма ждали отрицательную реакцию. Вы боялись и никогда не тренировались — статьи получились слабыми. Отрицательная реакция последовала. Если бы не боялись, а писали, тренировались и получали бы фидбек, то статьи получались бы лучше. Из-за пессимизма и страха, что получится плохо — у вас получилось плохо.

            Вы просто сидели, ничего не делали, но решили, что у вас много опыта и понимания Хабра. Когда вы выложили статьи и получили на них негативый отклик — посчитали, что вы смогли это предвидеть из-за опытности и хорошего понимания аудитории. Хотя, на самом деле, просто не смогли написать достойные статьи из-за неопытности и плохого понимания аудитории.


            1. stranger777
              04.06.2017 20:03

              Радостно говорить с умным человеком. Моим ответом будет статья. Нескоро — я редко пишу. Но будет.
              Всего доброго вам! Спасибо. Вы сделали мой вечер. :)


    1. TheShock
      04.06.2017 18:28
      +1

      Поэтому рассудите сами, Вы бы хотели чтобы Вас учили забивать гвозди не той стороной молотка?

      Вместо того, чтобы сказать «статья» фигня — аргументировано прошлись бы по недостаткам кода в комментарии и помогли бы этим новичкам. Они бы, прочтя ее, поняли, как это в целом сделать, а потом прочтя ваш комментарий, поняли бы, что именно делать не надо.

      А так они остались у разбитого корыта. Ведь так, как в статье — сделать нельзя. И как именно сделать — они не знают.


      1. vasIvas
        04.06.2017 18:37

        Признаюсь честно, я сюда прихожу читать «умные слова» и обсуждать «умные темы», а не сюсюськаться.
        Вот если бы статья была о архитектуре, проектировании, парадигме, то я бы стал аргументировать, если бы, как в данном случаи, не пришлось бы аргументировать и исправлять всю статью! Да и для такого уровня есть тостер и ему подобные, хотя и там, в большинстве случаев ответят такие-же не компетентные новички, которые хотят показать что они что-то знают. А умных там все меньше и меньше из-за того, что новички им портят удовольствие.


        1. TheShock
          04.06.2017 18:45
          +1

          Больше похоже, что вы приходите сюда побубнеть.

          Да и для такого уровня есть тостер

          Тостер? Для статьи?

          не компетентные новички, которые хотят показать что они что-то знают

          Вроде вас?


          1. vasIvas
            04.06.2017 18:57
            -1

            Тостер для вопросов, которые автору не понятны. И да, вроде меня. Меня не тянет писать статьи, только потому, что я не смогу выдать качество, которое считаю приемлемым.


  1. TheShock
    04.06.2017 18:43
    +2

    rand = 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. Не надо так, крайне неочевидно.

    При клике по кругу он не удаляется, а только помечается мертвым. И хотя он, вроде, не отрисовывается, но продолжает смещаться каждый кадр. В процессе игры потребление памяти и процессора будет расти линейно.