Статьи

HTML5 Gaming: анимация спрайтов в Canvas с помощью EaselJS

Если вы хотите писать казуальные игры, используя элемент HTML5 Canvas, вам нужно найти способ обработки ваших спрайтов. Есть несколько библиотек, которые помогут вам писать игры, такие как ImpactJS , CraftyJS и так далее. Со своей стороны, я решил использовать EaselJS, который использовался для написания PiratesLoveDaisies : HTML5 Tower Defense. В этом уроке мы увидим, как использовать существующие элементы спрайта и анимировать их .

Вы знаете, что я делаю это на языке мольеров, а также на французском языке: Jeux HTML5: анимация, дизайн, холст и легкость

Эта статья является первой из серии из 3:

— HTML5 Gaming: анимация спрайтов в Canvas с EaselJS
HTML5 Gaming: создание основных объектов и обработка коллизий с EaselJS
HTML5 Platformer: полный порт игры XNA для <canvas> с EaselJS

Вступление

На официальном сайте EaselJS вы найдете несколько интересных примеров и базовую документацию . Мы будем использовать образец спрайтов в качестве базы. Мы также будем использовать ресурсы, доступные в образце XNA 4.0 Platformer . Для тех из вас, кто читает мой блог, вы, возможно, помните, что я люблю играть с этим образцом. Вот предыдущие прикрепленные статьи:

Начальный комплект платформера Windows Phone 7 для XNA Studio 4.0
Редактор уровня платформера XNA Silverlight 4 для Windows Phone 7

Тем временем образец платформера был обновлен нашей командой XNA и доступен здесь для Xbox 360, ПК и Windows Phone 7: App Hub — платформер . Вы можете скачать его, чтобы поиграть с ним и извлечь спрайты, чтобы использовать их с EaselJS.

В этой статье мы будем использовать эти 2 PNG-файла в качестве источника последовательности спрайтов:

Наш монстр бежит:

который содержит 10 разных спрайтов.

Наш монстр в режиме ожидания:

который содержит 11 различных спрайтов.

Примечание: эти примеры не работают должным образом в Firefox 5.0 из- за ошибки в их реализации canvas . Он был хорошо протестирован в IE9 / IE10 / Chrome 12 / Opera 11 и Firefox Aurora 7.0.  

Урок 1: создание SpriteSheet и BitmapSequence

Мы начнем с перемещения бегущего монстра между началом и концом ширины нашего холста.

Первый шаг — загрузить полную последовательность, содержащуюся в файле PNG, с помощью этого кода:

var imgMonsterARun = new Image();

function init() {
    //find canvas and load images, wait for last image to load
    canvas = document.getElementById("testCanvas");

    imgMonsterARun.onload = handleImageLoad;
    imgMonsterARun.onerror = handleImageError;
    imgMonsterARun.src = "img/MonsterARun.png";
}

Этот код будет вызываться первым для инициализации содержимого нашей игры. После загрузки мы можем начать игру. EaselJS предоставляет объект SpriteSheet для обработки спрайта. Таким образом, используя этот код:

var spriteSheet  = new SpriteSheet(
        imgMonsterARun, //image to use
        64, //width of each sprite
        64, //height of each sprite
        {    
            walk_left: [0, 9]
        });

Мы указываем, что мы хотели бы создать новую последовательность с именем walk_left, которая будет состоять из изображения imgMonsterARun . Это изображение будет разбито на 10 кадров размером 64х64 пикселя. Это основной объект для загрузки нашего спрайта и создания наших последовательностей. При желании может быть создано несколько последовательностей из одного и того же файла PNG, например, в примере спрайтов крыс сайта EaselJS.

После этого нам нужно использовать объект BitmapSequence . Это помогает нам анимировать нашу последовательность и позиционировать наши спрайты на экране. Давайте рассмотрим код инициализации этой BitmapSequence:

// create a BitmapSequence instance to display and play back the sprite sheet:
bmpSeq = new BitmapSequence(spriteSheet);
    
// set the registration point (the point it will be positioned and rotated around)
// to the center of the frame dimensions:
bmpSeq.regX = bmpSeq.spriteSheet.frameWidth/2|0;
bmpSeq.regY = bmpSeq.spriteSheet.frameHeight / 2 | 0;

// start playing the first sequence:
bmpSeq.gotoAndPlay("walk_left");     //animate
    
// set up a shadow. Note that shadows are ridiculously expensive. You could display hundreds
// of animated rats if you disabled the shadow.
bmpSeq.shadow = new Shadow("#454", 0, 5, 4);

bmpSeq.name = "monster1";
bmpSeq.speed = 1;
bmpSeq.direction = 90;
bmpSeq.vX = 3|0.5;
bmpSeq.vY = 0;
bmpSeq.x = 16;
bmpSeq.y = 32;
        
// have each monster start at a specific frame
bmpSeq.currentFrame = 0;
stage.addChild(bmpSeq);

Конструктор BitmapSequence объекта просто нуждается в SpriteSheet элемент в качестве параметра. Затем мы даем имя последовательности, устанавливая некоторые параметры, такие как скорость и начальная позиция нашего первого кадра. Наконец, мы добавляем эту последовательность в список отображения с помощью объекта Stage и его метода addChild () .

Следующий шаг — решить, что бы мы хотели сделать в нашем цикле анимации. Этот цикл анимации вызывается каждые xxx миллисекунды и позволяет вам обновлять положение ваших спрайтов. Для этого EaselJS предоставляет объект Ticker, который обеспечивает централизованную передачу тика или пульса с заданным интервалом. Все, что вам нужно сделать, это подписаться на событие tick и реализовать метод .tick (), который будет вызываться обратно. Этот код, например, регистрирует событие:

Ticker.addListener(window);
// Best Framerate targeted (60 FPS)
Ticker.setInterval(17);

А вот код, который будет вызываться каждые 17 мс (когда это возможно), чтобы обновить положение нашего монстра:

function tick() {
    // Hit testing the screen width, otherwise our sprite would disappear
    if (bmpSeq.x >= screen_width - 16) {
        // We've reached the right side of our screen
        // We need to walk left now to go back to our initial position
        bmpSeq.direction = -90;
    }

    if (bmpSeq.x < 16) {
        // We've reached the left side of our screen
        // We need to walk right now
        bmpSeq.direction = 90;
    }

    // Moving the sprite based on the direction & the speed
    if (bmpSeq.direction == 90) {
        bmpSeq.x += bmpSeq.vX;
        bmpSeq.y += bmpSeq.vY;
    }
    else {
        bmpSeq.x -= bmpSeq.vX;
        bmpSeq.y -= bmpSeq.vY;
    }

    // update the stage:
    stage.update();
}

Вы можете проверить окончательный результат здесь :

Вы также можете просмотреть этот пример здесь: easelJSSpritesTutorial01, если вы хотите просмотреть полный исходный код.

Но ждать! В этой анимации есть 2 проблемы:

1 — шаги анимации странные, так как персонаж, кажется, слишком быстро
проходит по последовательности различных спрайтов 2 — персонаж может нормально ходить только справа налево, в противном случае похоже, что он пытается достичь своего рода лунного танца.Clignement d'œil

Давайте посмотрим, как это исправить во втором уроке.

Урок 2: управление скоростью анимации и переключение спрайтов

Чтобы зафиксировать скорость анимации, я нашел самый простой способ — использовать оператор модуля, чтобы избежать рисования / обновления моей последовательности во время каждого тика. В настоящее время существует открытая проблема по этой теме: https://github.com/gskinner/EaselJS/issues/60 с EaselJS 0.3.2.

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

// {nameOfFlippedSequence:["derivativeSequence", flipHorizontally, flipVertically, optionNameOfNextSequence]}
spriteSheet = SpriteSheetUtils.flip(
spriteSheet,
{
    walk_right: ["walk_left", true, false, null]
});

Мы создаем производную последовательность с именем « walk_right »  на основе последовательности «walk_left» , что мы переворачивать горизонтально. Наконец, вот код, который замедляет скорость анимации и обрабатывает последовательность воспроизведения на основе позиции персонажа:

function tick() {
    // To slow down the animation loop of the sprite, we're not redrawing during each tick
    // With a Modulo 4, we're dividing the speed by 4
    var speedControl = Ticker.getTicks() % 4;

    if (speedControl == 0) {
        // Hit testing the screen width, otherwise our sprite would disappear
        if (bmpSeq.x >= screen_width - 16) {
            // We've reached the right side of our screen
            // We need to walk left now to go back to our initial position
            bmpSeq.direction = -90;
            bmpSeq.gotoAndPlay("walk_left")
        }

        if (bmpSeq.x < 16) {
            // We've reached the left side of our screen
            // We need to walk right now
            bmpSeq.direction = 90;
            bmpSeq.gotoAndPlay("walk_right");
        }

        // Moving the sprite based on the direction & the speed
        if (bmpSeq.direction == 90) {
            bmpSeq.x += bmpSeq.vX;
            bmpSeq.y += bmpSeq.vY;
        }
        else {
            bmpSeq.x -= bmpSeq.vX;
            bmpSeq.y -= bmpSeq.vY;
        }

        // update the stage:
        stage.update();
    }
}

Вы можете проверить окончательный результат здесь :

Вы также можете просмотреть этот пример здесь: easelJSSpritesTutorial02, если вы хотите просмотреть полный исходный код.

Урок 3: загрузка нескольких спрайтов и игра с несколькими анимациями

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

Затем мы будем загружать несколько файлов PNG с веб-сервера. Тогда очень важно дождаться загрузки всех ресурсов, в противном случае вы можете попытаться нарисовать еще не загруженные ресурсы. Вот очень простой способ сделать это:

var numberOfImagesLoaded = 0;

var imgMonsterARun = new Image();
var imgMonsterAIdle = new Image();

function init() {
    //find canvas and load images, wait for last image to load
    canvas = document.getElementById("testCanvas");

    imgMonsterARun.onload = handleImageLoad;
    imgMonsterARun.onerror = handleImageError;
    imgMonsterARun.src = "img/MonsterARun.png";

    imgMonsterAIdle.onload = handleImageLoad;
    imgMonsterAIdle.onerror = handleImageError;
    imgMonsterAIdle.src = "img/MonsterAIdle.png";
}

function handleImageLoad(e) {
    numberOfImagesLoaded++;

    // We're not starting the game until all images are loaded
    // Otherwise, you may start to draw without the resource and raise 
    // this DOM Exception: INVALID_STATE_ERR (11) on the drawImage method
    if (numberOfImagesLoaded == 2) {
        numberOfImagesLoaded = 0;
        startGame();
    }
}

Этот код очень прост. Например, он не обрабатывает ошибки должным образом, пытаясь повторно загрузить изображение в случае первого сбоя. Если вы создаете игру, вам нужно написать собственный менеджер загрузки контента, если используемая вами библиотека JS не реализует его.

Чтобы добавить последовательность простоя и установить параметры положения, нам просто нужно использовать тот же код, который мы видели ранее:

var spriteSheetIdle = new SpriteSheet(
        imgMonsterAIdle, //image to use
        64, //width of each sprite
        64, //height of each sprite
        {    
            idle: [0, 10]
        });

bmpSeqIdle = new BitmapSequence(spriteSheetIdle);

bmpSeqIdle.name = "monsteridle1";
bmpSeqIdle.speed = 1;
bmpSeqIdle.direction = 0;
bmpSeqIdle.x = 16;
bmpSeqIdle.y = 32;

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

if (bmpSeq.x < 16) {
    // We've reached the left side of our screen
    // We need to walk right now
    bmpSeq.direction = 90;
    bmpSeq.gotoAndStop("walk_left");
    stage.removeChild(bmpSeq);
    bmpSeqIdle.gotoAndPlay("idle");
    stage.addChild(bmpSeqIdle);
}

Вы можете проверить окончательный результат здесь :

Вы также можете просмотреть этот образец здесь: easelJSSpritesTutorial03, если вы хотите просмотреть полный исходный код.

Это все, ребята! Но если вы хотите пойти дальше, вы можете прочитать следующую часть здесь: HTML5 Gaming: создание основных объектов и обработка коллизий с EaselJS

Дэвид