Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > разработка игр, графический дизайн и моделирование > Gamedev - cоздание игр: Unity, OpenGL, DirectX
Регистрация

Восстановить пароль
Повторная активизация e-mail

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 30.09.2016, 17:38   #1
8Observer8
Старожил
 
Аватар для 8Observer8
 
Регистрация: 02.01.2011
Сообщений: 3,322
По умолчанию Ваша первая игра на JavaScript и Phaser (перевод)

Ваша первая игра на JavaScript и Phaser (перевод)

Источник

Запустить демку в браузере

Скачать исходники финального проекта

Все любят классические игры. Многие же из вас помнят ретро игру "змейка" на старых Nokia? Мы уверены, что многие. Поэтому мы решили сделать "змейку", используя HTML5. Есть такой великолепный фреймворк с открытым исходным кодом для разработки игры под названием Phaser. Его мы и будем использовать.

Приготовления

Скачайте .zip архив с рисунками и с пустыми файлами кода здесь. Код мы напишем позже. Папки и файлы отражают структуру нашего проекта.

Откройте index.html, напишите название игры в теле тега "title" и подключите скрипты из папки "assets/js/". Позже, чтобы играть вам нужно будет всего лишь открыть index.html в браузере.

index.html
Код:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Snake</title>
    <script src="assets/js/phaser.min.js"></script>
    <script src="assets/js/game.js"></script>
    <script src="assets/js/menu.js"></script>
    <script src="assets/js/game_over.js"></script>
    <script src="assets/js/main.js"></script>
</head>
<body>

</body>
</html>
Сейчас ваша рабочая директория должна выглядеть так:
Изображения
Тип файла: png work_directory.png (8.7 Кб, 71 просмотров)
Вложения
Тип файла: zip FinalGame.zip (175.6 Кб, 30 просмотров)
Тип файла: zip Setup.zip (166.2 Кб, 25 просмотров)
8Observer8 вне форума Ответить с цитированием
Старый 30.09.2016, 17:42   #2
8Observer8
Старожил
 
Аватар для 8Observer8
 
Регистрация: 02.01.2011
Сообщений: 3,322
По умолчанию

Как организована игра

Игры, которые используют Phaser организованы вокруг состояний. Представляйте состояния, как разные части вашей игры.

Это состояния нашей игры:
  • Состояние "Меню". Управляется скриптом menu.js. Это состояние отображает рисунок стартового меню. Когда пользователь кликнет по стартовому рисунку, то состояние "Меню" перейдёт в состояние "Игра"
  • Состояние "Игра". Управляется скриптом game.js. Здесь непосредственно происходит игра. Вы управляете змейкой, едите яблоки и просто получаете радость от процесса. Когда змейка умирает, то состояние "Игра" переходит в состояние "Game_Over"
  • Состояние "Game_Over". Это состояние показывает рисунок "gameover.png" и показывает сколько вы набрали очков. Когда вы кликните, то произойдёт переход в состояние "Игра"

main.js - это наш самый главный скрипт. В этом скрипте мы сейчас создадим объект game и добавим наше первое состояние "Меню"

1. Загрузка изображения

К настоящему моменту наша игра не делает ничего. Давайте добавим состояние "Меню" и сделаем так, чтобы оно отображало изображение с заголовком игры.

В разделе "Установка" мы подключили скрипт phaser.js в нашем файле index.html, поэтому нам доступен глобальный объект Phaser, с помощью которого мы можем получать доступ к функциям и методам библиотеки phaser.js

Сейчас мы, используя глобальный объект Phaser, создадим объект game. Этот объект представляет всю нашу игру. Мы будем добавлять состояния к этому объекту.

main.js

Код:
var game;

// Создание объекта game (600 пикселей в ширину и 450 пикселей в высоту)
game = new Phaser.Game(600, 500, Phaser.AUTO, '');

// Первый параметр - название нашего состояния
// Второй параметр - объект, который будет хранить методы нашего состояния
game.state.add('Menu', Menu);

game.state.start('Menu');
Теперь нам нужно создать и инициализировать объект Menu (который мы задействовали в скрипте выше). Откройте скрипт menu.js и добавьте объект Menu и методы для него (смотрите листинг ниже).

menu.js

Код:
var Menu =
    {
        preload: function ()
        {
            // Загрузка изображения, которое нам
            // будет необходимо позже для создания спрайта
            // Первый аргумент - название, по которому мы будем обращаться к
            // изображению
            // Второй аргумент - путь к файлу изображения
            game.load.image('menu', './assets/images/menu.png');
        },

        create: function ()
        {
            // Создаём и добавляем спрайт в нашу игру. Спрайтом будет
            // игровой лого для меню
            // Аргументы: X, Y, имя изображения (см. выше)
            this.add.sprite(0, 0, 'menu');
        }
    };
Когда состояние Меню запустится (команда game.state.start('Menu')), то первым делом выполнится тело функции preload, которая загрузит все необходимые ассеты (ассеты - это изображения, звуки, музыка и т.д.). Как только функция preload закончит свою работу, то начнёт выполнятся функция create. В теле функции create должен располагаться код, который инициализирует игру (например, расставляет картинки по требуемым местам). В данном случае, create располагает стартовое изображение меню в координате (0, 0)

От переводчика:
Начало координат игры располагается в левом верхнем углу области рисования. Ось X направлена вправо, а ось Y направлена вниз. По умолчанию якорная точка изображения располагается в левом верхнем углу изображения. Эту точку можно менять с помощью метода anchor.setTo. Например, можно установить якорную точку на середину изображения: this.obj.anchor.setTo(0.5, 0.5); Изображения, которые располагаются в игровом мире называют спрайтами.

Из-за ограничения безопасности браузера мы не можем загружать изображения с локального диска. Простыми словами, мы не можем просто кликнуть два раза по index.html и запустить игру. Поэтому запускать игру нужно с локального сервера, например, из среды Visual Studio, которая автоматически запустит локальный сервер IIS.

Если всё было сделано правильно, то стартовое изображение должно появится в вашем браузере: запустить демку
Изображения
Тип файла: png menu.png (7.8 Кб, 74 просмотров)
8Observer8 вне форума Ответить с цитированием
Старый 30.09.2016, 17:44   #3
8Observer8
Старожил
 
Аватар для 8Observer8
 
Регистрация: 02.01.2011
Сообщений: 3,322
По умолчанию

2. Вывод змейки на экран

Как было упомянуто ранее, сам процесс игры происходит в состоянии "Игра". В этом же состоянии мы выводим змейку на экран. Так же как мы поступали с состоянием "Меню" ранее, мы должны зарегистрировать состояние "Игра" в скрипте main.js:

main.js
Код:
var game;

// Создание объекта game (600 пикселей в ширину и 450 пикселей в высоту)
game = new Phaser.Game(600, 450, Phaser.AUTO, '');

// Первый параметр - название нашего состояния
// Второй параметр - объект, который будет хранить методы нашего состояния
game.state.add('Menu', Menu);

// Добавляем состояние "Игра"
game.state.add('Game', Game);

game.state.start('Menu');
Нужно добавить код в menu.js, который бы позволил перейти из состояния "Меню" в состояние "Игра" по клику мыши. Для этого мы заменим спрайт на главном меню кнопкой. Добавление кнопки очень похоже на добавление спрайта. Вам нужно лишь добавить функцию, которая будет вызываться, когда происходит клик мышью.

Это окончательный код скрипта menu.js:

Код:
var Menu =
    {
        preload: function ()
        {
            // Загрузка изображения, которое нам
            // будет необходимо позже для создания спрайта
            // Первый аргумент - название, по которому мы будем обращаться к
            // изображению
            // Второй аргумент - путь к файлу изображения
            game.load.image('menu', './assets/images/menu.png');
        },

        create: function ()
        {
            // Создаём и добавляем кнопку в нашу игру. Спрайтом будет
            // игровой лого для меню
            // Аргументы: X, Y, имя изображения (см. выше), функция обработки клика
            this.add.button(0, 0, 'menu', this.startGame, this);
        },

        startGame: function ()
        {
            // Меняем состояние "Меню" на состояние "Игра"
            this.state.start('Game');
        }
    };
Теперь мы продолжим писать код для состояния "Игра". Структура кода очень похожа на структуру из скрипта menu.js для состояния "Меню".

game.js

Код:
var snake, apple, squareSize, score, speed,
    updateDelay, direction, new_direction,
    addNew, cursors, scoreTextValue, speedTextValue,
    textStyle_Key, textStyle_Value;

var Game = {

    preload: function ()
    {
        // Здесь мы загружаем все необходимые ресурсы для уровня
        // В нашем случае, это просто два квадрата: один для тела змейки и другой для яблока
        game.load.image('snake', './assets/images/snake.png');
        game.load.image('apple', './assets/images/apple.png');
    },

    create: function ()
    {
        // Перед стартом игры, мы здесь инициализируем глобальные переменные, объявленные в начале файла
        // Мы сделали эти переменные глобальными, чтобы они были доступны в функции update
        snake = [];                     // Этот список работает как хранилище для частей нашей змейки
        apple = {};                     // Объект для яблока
        squareSize = 15;                // Длина сторон всех наших квадратов (яблока и части для змейки). Наши изображения имеют размер 15x15 пикселей
        score = 0;                      // Игровые очки
        speed = 0;                      // Скорость игры
        updateDelay = 0;                // Переменная для контроля за частотой обновления
        direction = 'right';            // Направление нашей змейки
        new_direction = null;           // Буфер для хранения нового направления
        addNew = false;                 // Переменная, которая показывает, что еда была съедена

        // Сообщить Phaser'у, что мы хотим использовать для управления клавиатуру (клавиши со стрелками)
        cursors = game.input.keyboard.createCursorKeys();

        game.stage.backgroundColor = '#061f27';

        // Генерируем 10 элементов для нашей змейки
        // Начинаем с позиции X=150 Y=150 и увеличиваем X на каждой итерации
        for (var i = 0; i < 10; i++)
        {
            // Параметры: координата X, координата Y, изображение
            snake[i] = game.add.sprite(150 + i * squareSize, 150, 'snake');
        }

        // Создаём первое яблоко
        this.generateApple();

        // Добавляем текст в верхнюю часть игры
        textStyle_Key = { font: "bold 14px sans-serif", fill: "#46c0f9", align: "center" };
        textStyle_Value = { font: "bold 18px sans-serif", fill: "#fff", align: "center" };
        // Игровые очки
        game.add.text(30, 20, "SCORE", textStyle_Key);
        scoreTextValue = game.add.text(90, 18, score.toString(), textStyle_Value);
        // Скорость игры
        game.add.text(500, 20, "SPEED", textStyle_Key);
        speedTextValue = game.add.text(558, 18, speed.toString(), textStyle_Value);
    },

    update: function ()
    {
        // Функция update вызывается постоянно с высокой частотой (где-то около 60 кадров в секунду)
        // обновляя игровое поле
        // Мы пока оставим тело этой функции пустым
    },

    generateApple: function ()
    {
        // Выбираем случайное место на игровом поле
        // X между 0 и 585 (39*15)
        // Y между 0 и 435 (29*15)
        var randomX = Math.floor(Math.random() * 40) * squareSize,
            randomY = Math.floor(Math.random() * 30) * squareSize;

        // Добавляем новое яблоко
        apple = game.add.sprite(randomX, randomY, 'apple');
    }
};
Так должны сейчас выглядеть змейка, яблоко и вывод текста:
Изображения
Тип файла: png drawing_snake.png (8.4 Кб, 63 просмотров)
8Observer8 вне форума Ответить с цитированием
Старый 30.09.2016, 17:46   #4
8Observer8
Старожил
 
Аватар для 8Observer8
 
Регистрация: 02.01.2011
Сообщений: 3,322
По умолчанию

3. Движение и контроль

Для того, чтобы змейка двигалась нам нужно написать код в функции update скрипта game.js

Мы создадим функции-обработчики нажатий клавиш-стрелок.

Реализация движения немного сложновато для понимания на первый взгляд. Функция update срабатывает с очень высокой частотой (около 60 кадров в секунду (60 fps)) и если мы будем перемещать змейку в каждом вызове функции update, то скорость змейки будет огромная. Для того, чтобы решить эту проблему мы напишем условие, которое будет определять, какой это вызов update по счёту. Например, мы может вызывать код передвижения только каждый 10-й вызов функции update. Для того чтобы узнать, какой это вызов по счёту, мы будем использовать переменную-счётчик updateDelay.

Если это 10-й вызов функции update, то мы удаляем последний квадрат нашей змейки (первый элемент в списке), даём ему новые координаты, согласно текущему направлению и помещаем его перед головой змейки (наверх списка).

Так выглядит код:

Код:
    update: function ()
    {
        // Обработчики нажатия клавиш-стрелок

        if (cursors.right.isDown && direction != 'left')
        {
            new_direction = 'right';
        }
        else if (cursors.left.isDown && direction != 'right')
        {
            new_direction = 'left';
        }
        else if (cursors.up.isDown && direction != 'down')
        {
            new_direction = 'up';
        }
        else if (cursors.down.isDown && direction != 'up')
        {
            new_direction = 'down';
        }

        // Формула, которая вычисляет скорость игры, которая в свою очередь, базируется на очках
        // Наибольшая скорость игры может быть равна максимум 10-и
        speed = Math.min(10, Math.floor(score / 5));
        // Обновляем значение скорости на экране
        speedTextValue.text = '' + speed;

        // Так как функция update имеет частому вызова около 60 раз в секунд, то
        // нам нужно уменьшить частоту вызова кода перемещения змейки

        // Увеличиваем счётчик на единицу каждый вызов функции update
        updateDelay++;

        // Выполняем эту часть кода только если счётчик кратен значению (10 - скорость игры)
        // Do game stuff only if the counter is aliquot to (10 - the game speed).
        // Чем больше значение переменной speed, тем чаще вызывается этот код и тем быстрее движется змейка
        if (updateDelay % (10 - speed) == 0)
        {
            // Передвижение змейки

            var firstCell = snake[snake.length - 1],
                lastCell = snake.shift(),
                oldLastCellx = lastCell.x,
                oldLastCelly = lastCell.y;

            // Если новое направление было выбрано в обработчике нажатия клавиши, то меняем текущее направление змейки
            if (new_direction)
            {
                direction = new_direction;
                new_direction = null;
            }

            // Меняем координаты последней ячейки относительно головы змейки согласно направлению
            // То есть меняем координаты последней ячейки, чтобы эта ячейка оказалась перед головой змейки

            if (direction == 'right')
            {
                lastCell.x = firstCell.x + 15;
                lastCell.y = firstCell.y;
            }
            else if (direction == 'left')
            {
                lastCell.x = firstCell.x - 15;
                lastCell.y = firstCell.y;
            }
            else if (direction == 'up')
            {
                lastCell.x = firstCell.x;
                lastCell.y = firstCell.y - 15;
            }
            else if (direction == 'down')
            {
                lastCell.x = firstCell.x;
                lastCell.y = firstCell.y + 15;
            }

            // Помещаем последнюю ячейку наверх стека
            // Помечаем её, как первую ячейку

            snake.push(lastCell);
            firstCell = lastCell;
        }
Попробуйте управлять змейкой с помощью клавиш-стрелок: запустить демо
Изображения
Тип файла: png movement_and_control.png (3.7 Кб, 69 просмотров)
8Observer8 вне форума Ответить с цитированием
Старый 30.09.2016, 17:47   #5
8Observer8
Старожил
 
Аватар для 8Observer8
 
Регистрация: 02.01.2011
Сообщений: 3,322
По умолчанию

4. Определение столкновений

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

Это обычно уже сделано с помощью физического движка. Phaser поддерживает несколько физических движков. Но они сложные для такой игры, как эта. Вместо этого мы сделаем своё собственное определение столкновений с помощью сравнений координат.

В функции update, после кода, который перемещает змейку, мы вызовем несколько методов. Эти методы будут сравнивать координаты, чтобы определить было столкновение или нет.

Код:
    update: function ()
    {
        // ...

        if (updateDelay % (10 - speed) == 0)
        {
            // ...

            // Увеличиваем размер змейки, если яблоко было съедено
            // Создаём блок позади змейки со старыми координатами последнего блока
            // (последний блок, к настоящему моменту, переместился вместе с остальной частью змейки)
            if (addNew)
            {
                snake.unshift(game.add.sprite(oldLastCellx, oldLastCelly, 'snake'));
                addNew = false;
            }

            // Проверяем столкновение с яблоком
            this.appleCollision();

            // Проверяем столкновение головы змейки со своим телом
            // Параметр - голова змейки
            this.selfCollision(firstCell);

            // Проверяем столкновение головы змейки со стеной
            // Параметр - голова змейки
            this.wallCollision(firstCell);
        }
    },

    generateApple: function ()
    {
        // Выбираем случайное место на игровом поле
        // X между 0 и 585 (39*15)
        // Y между 0 и 435 (29*15)
        var randomX = Math.floor(Math.random() * 40) * squareSize,
            randomY = Math.floor(Math.random() * 30) * squareSize;

        // Добавляем новое яблоко
        apple = game.add.sprite(randomX, randomY, 'apple');
    },

    appleCollision: function ()
    {
        // Проверяем, что любая часть змейки пересекла яблоко
        // Проверка пересечения со всеми частями змейки необходима, чтобы
        // учесть случай, если новое яблоко появится внутри змейки
        for (var i = 0; i < snake.length; i++)
        {
            if (snake[i].x == apple.x && snake[i].y == apple.y)
            {
                // В следущий момент времени, когда змейка переместится, то
                // новый блок будет добавлен к ней
                addNew = true;

                // Удаляем старое яблоко
                apple.destroy();

                // Создаём новое яблоко
                this.generateApple();

                // Увеличиваем число заработанных очков
                score++;

                // Обновняем очки на экране
                scoreTextValue.text = score.toString();
            }
        }

    },

    selfCollision: function (head)
    {
        // Проверяем, что голова змейки не пересекла одну из своих частей
        for (var i = 0; i < snake.length - 1; i++)
        {
            if (head.x == snake[i].x && head.y == snake[i].y)
            {
                // Если пересекла, то показываем экран Game Over
                game.state.start('Game_Over');
            }
        }

    },

    wallCollision: function (head)
    {
        // Проверяем, что голова змейки находится в границах игрового поля
        if (head.x >= 600 || head.x < 0 || head.y >= 450 || head.y < 0)
        {
            // Если это не так, что мы врезались в стену. Значит, показываем экран Game Over
            game.state.start('Game_Over');
        }
    }
Когда змейка сталкивается с яблоком, то мы увеличиваем количество набранных очков и размер змейки. Но когда змейка сталкивается со стеной или со своим телом, мы должны завершить игру. Для того, чтобы сделать это нам нужно создать новое состояние с именем Game_Over. Снова нам нужно зарегистрировать новое состояние в скрипте main.js. Добавим эту строчку кода в самый низ скрипта main.js:

Добавим код самого состояния в скрипт game_over.js:

Код:
var Game_Over = {

    preload: function ()
    {
        // Загружаем рисунок
        game.load.image('gameover', './assets/images/gameover.png');
    },

    create: function ()
    {
        // Создаём кнопку для старта игры, как мы делали в скрипте main.js
        this.add.button(0, 0, 'gameover', this.startGame, this);

        // Выводим на экран сколько мы набрали очков
        game.add.text(235, 350, "LAST SCORE", { font: "bold 16px sans-serif", fill: "#46c0f9", align: "center" });
        game.add.text(350, 348, score.toString(), { font: "bold 20px sans-serif", fill: "#fff", align: "center" });
    },

    startGame: function ()
    {
        // Меняем текущее состояние обратно на состояние "Игра"
        // Change the state back to Game.
        this.state.start('Game');
    }
};
Наша игра готова!

Запустить демку

Для дальнейшего изучения:
8Observer8 вне форума Ответить с цитированием
Старый 01.12.2016, 09:54   #6
8Observer8
Старожил
 
Аватар для 8Observer8
 
Регистрация: 02.01.2011
Сообщений: 3,322
По умолчанию

Ошибка. В конце тутора, после фразы "Добавим эту строчку кода в самый низ скрипта main.js:", нужно добавить:

Код:
game.state.add('Game_Over', Game_Over);
8Observer8 вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц

Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск


Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Моя первая в жизни игра=)) Dmitry96 Gamedev - cоздание игр: Unity, OpenGL, DirectX 17 18.11.2014 16:05
Ваша первая программа MooNDeaR Свободное общение 18 16.08.2011 14:36
Моя первая игра На C++ ThisIzGame Gamedev - cоздание игр: Unity, OpenGL, DirectX 4 31.08.2009 19:40
Моя первая игра на Pascal Gapro Gamedev - cоздание игр: Unity, OpenGL, DirectX 18 20.08.2009 17:58
Моя первая и последняя игра. BangBangFM Gamedev - cоздание игр: Unity, OpenGL, DirectX 13 05.12.2008 22:12