Tutorials

Создание простой игры - Часть 4

Вы можете найти полный проект здесь. Если вы еще не видели Часть 1, Часть 2 и Часть 3, прочитайте их сначала.

Футбольный мяч

Футбольный мяч является центром внимания в нашей игре Keepy Up. Он реагирует на ввод пользователя, он реагирует на окружающую среду (ну, гравитацию), он издает звуки. Вероятно, это самая сложная часть игры. К счастью, мы объясним все части вам как можно проще.

ball.js

var Ball = pc.createScript('ball');

Ball.attributes.add('gravity', {
    type: 'number',
    default: -9.8,
    description: 'Значение гравитации для использования'
});

Ball.attributes.add('defaultTap', {
    type: 'number',
    default: 5,
    description: 'Скорость, которую нужно установить для мяча при нажатии'
});

Ball.attributes.add('impactEffect', {
    type: 'entity',
    description: 'Эффект частиц, который срабатывает при нажатии на мяч'
});

Ball.attributes.add('ballMinimum', {
    type: 'number',
    default: -6,
    description: 'Когда мяч опускается ниже минимального значения y, срабатывает конец игры'
});

Ball.attributes.add('speedMult', {
    type: 'number',
    default: 4,
    description: 'Множитель для применения к скорости X при нажатии вне центра'
});

Ball.attributes.add('angMult', {
    type: 'number',
    default: -6,
    description: 'Множитель для применения к угловой скорости при нажатии вне центра'
});

Ball.tmp = new pc.Vec3();

// инициализация кода, вызываемого один раз для каждой сущности
Ball.prototype.initialize = function() {
    this.paused = true;

    // Получить "Game" Entity и начать слушать события
    this.game = this.app.root.findByName("Game");

    this.app.on("game:start", this.unpause, this);
    this.app.on("game:gameover", this.pause, this);
    this.app.on("game:reset", this.reset, this);

    // Инициализация свойств
    this._vel = new pc.Vec3(0, 0, 0);
    this._acc = new pc.Vec3(0, this.gravity, 0);
    this._angSpeed = 0;

    // Сохранить начальную позицию и вращение для сброса
    this._origin = this.entity.getLocalPosition().clone();
    this._rotation = this.entity.getLocalRotation().clone();
};

// код обновления, вызываемый каждый кадр
Ball.prototype.update = function(dt) {
    // Не обновлять при паузе
    if (this.paused) {
        this.entity.rotate(0, 30*dt, 0);
        return;
    }

    var p = this.entity.getLocalPosition();
    var tmp = Ball.tmp;

    // интегрировать скорость во временной переменной
    tmp.copy(this._acc).scale(dt);
    this._vel.add(tmp);

    // интегрировать позицию во временной переменной
    tmp.copy(this._vel).scale(dt);
    p.add(tmp);

    // обновить позицию
    this.entity.setLocalPosition(p);

    // вращать с угловой скоростью
    this.entity.rotate(0, 0, this._angSpeed);

    // проверить условие окончания игры
    if (p.y < this.ballMinimum) {
        this.game.script.game.gameOver();
    }
};

/*
 * Вызывается обработчиком ввода для тапа мяча в воздухе
 * dx - расстояние нажатия от центра мяча по оси x
 * dy - расстояние нажатия от центра мяча по оси y
 */
Ball.prototype.tap = function (dx, dy) {
    // Обновить скорость и вращение на основе позиции нажатия
    this._vel.set(this.speedMult * dx, this.defaultTap, 0);
    this._angSpeed += this.angMult * dx;

    // рассчитать позицию нажатия в мировом пространстве
    var tmp = Ball.tmp;
    tmp.copy(this.entity.getLocalPosition());
    tmp.x -= dx;
    tmp.y -= dy;

    // запустить эффект частиц на позицию нажатия, смотрящий в противоположную сторону от центра мяча
    this.impactEffect.setLocalPosition(tmp);
    this.impactEffect.particlesystem.reset();
    this.impactEffect.particlesystem.play();
    this.impactEffect.lookAt(this.entity.getPosition());

    // воспроизвести аудио
    this.entity.sound.play("bounce");

    // увеличить счет на 1
    this.game.script.game.addScore(1);
};

// Приостановить обновление мяча, когда игра не идет
Ball.prototype.unpause = function () {
    this.paused = false;

    // начать игру с нажатием
    this.tap(0, 0);
};

// Возобновить обновление мяча
Ball.prototype.pause = function () {
    this.paused = true;
};

// Сбросить мяч до начальных значений
Ball.prototype.reset = function () {
    this.entity.setLocalPosition(this._origin);
    this.entity.setLocalRotation(this._rotation);
    this._vel.set(0,0,0);
    this._acc.set(0, this.gravity, 0);
    this._angSpeed = 0;
};

Атрибуты скрипта

Первое, что вы заметите в верхней части скрипта, - это набор атрибутов скрипта, которые мы определили. Определение атрибутов скрипта позволяет вам открывать значения из вашего скрипта в редакторе. Есть три очень хорошие причины для этого.

Атрибуты скрипта

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

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

Это называется "скорость итерации". Чем быстрее вы можете изменять и тестировать вашу игру, тем быстрее вы сможете ее разработать!

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

В-третьих, атрибут скрипта - отличный способ связать скрипт с Entity или Asset в вашей сцене. Например, скрипт мяча должен запускать эффект частиц при нажатии на него. Эффект частиц находится на другом Entity в нашей сцене. Мы определяем атрибут скрипта с именем impactEffect типа entity, и в редакторе мы связываем его с Entity, на котором находится наш эффект частиц. Теперь наш скрипт имеет ссылку на Entity, и мы можем свободно изменять этот Entity или переключаться на другой Entity без нарушения нашего кода.

Физическая симуляция

Для тех из вас, кто знаком с основами векторной математики, этот update() цикл мяча должен быть простым, но для всех остальных мы объясним немного о симуляции мяча в видеоигре.

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

Вы можете влиять на положение вашего объекта одним из трех способов.

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

Симуляция

Цикл обновления делает это:

(Изменение скорости) = (Ускорение) * (Время с последнего кадра)

(Новая скорость) = (Старая скорость) + (Изменение скорости)

(Изменение положения) = (Новая скорость) * (Время с последнего кадра)

(Новое положение) = (Старое положение) + (Изменение положения)

В коде это выглядит так:

var p = this.entity.getLocalPosition();

// интегрируем скорость во временную переменную
tmp.copy(this._acc).scale(dt);
this._vel.add(tmp);

// интегрируем позицию во временную переменную
tmp.copy(this._vel).scale(dt);
p.add(tmp);

// обновляем позицию
this.entity.setLocalPosition(p);

Вы заметите, что мы используем временный вектор tmp для хранения промежуточных значений. Важно не создавать новый вектор каждый кадр для этого. Также обратите внимание, что нам нужно вызвать setLocalPosition для применения обновленной позиции.

Наконец, для приятного эффекта, мы добавляем вращение мяча на значение угловой скорости с помощью entity.rotate(). Это не очень физически точно, но выглядит хорошо.

Реагирование на ввод

Вы можете вспомнить из Части 2, что скрипт input.js проверял, попал ли ввод в мяч, и если да, то вызывает метод tap(). Определенный выше метод tap() применяет прямое изменение скорости и угловой скорости мяча. Мы используем пару наших атрибутов скрипта this.speedMult и this.angMult для умножения новой скорости и угловой скорости, чтобы соответствовать нашим ожиданиям от игрового процесса.

Мы также используем метод tap для запуска облака частиц пыли в точке удара и воспроизведения звукового эффекта. Мы поговорим о частицах и звуках в Части 5.

Резюме

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

This site is translated by the community. If you want to get involved visit this page