javascript

Введение в Pixi.js или пишем мини-игру за 20 минут

  • понедельник, 5 июня 2017 г. в 03:13:14
https://habrahabr.ru/post/330174/
  • Разработка игр
  • JavaScript
  • Canvas


Здравствуй, Хабр!

Совсем недавно разбирая графический движок 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 станет немного проще.
Позже я расскажу, как вывести подсчет очков на экран и сделать анимацию при попадании по шарику.

Всем спасибо!