Jak Cobalt Calibur používá plátno k zobrazení grafiky založené na spritech

Ahoj, já jsem Thomas Hunter, tvůrce Cobalt Calibur, HTML5 hry pro více hráčů. Jsem tu, abych vám řekl, jak jsem použil nové HTML5 canvas API k poskytování vizuálů pomocí grafiky založené na spritech. Je to podobné, jako staré herní konzole, jako je NES, kreslily grafiku. Pravděpodobně znáte používání Sprite Sheets s CSS ke snížení šířky pásma a stejné kouzlo lze použít při kreslení grafiky na plátně a animací.

koupit Kanadu v propecia

Do tohoto dokumentu vložím kód, ale pokud byste chtěli vidět celý kód, podívejte se do souboru Cobalt Calibur engine.js.

Zobrazit ukázku

Přehled

Při vytváření animací a herních enginů obvykle zjistíte, že existuje jediná primární smyčka, která kreslí obrazovku každý cyklus. Každý z těchto cyklů představuje snímek animace. Někdy existují tvrdé limity pro snímkovou frekvenci, například 60 snímků za sekundu. Jindy je limit nelimitován a běží co nejrychleji. S Cobalt Calibur překreslujeme snímky každých 150 ms, neboli přibližně 6,66 snímků za sekundu. Zde je příslušný kód:

var currentFrame = 0;
setInterval(function() {
    currentFrame++;
    if (currentFrame % 3 == 0) {
        currentFrame = 0;
        // redraw every 150 ms, but change animation every 450 ms
        app.graphics.globalAnimationFrame = !app.graphics.globalAnimationFrame;
        app.player.killIfNpcNearby();
    }
    app.environment.map.render(currentFrame === 0);
}, 150);

Způsob, jakým se smyčka provádí v Cobalt Calibur, je ve skutečnosti nesprávný. Tato smyčka poběží, i když aktuální karta není zaostřená, což způsobí zbytečné překreslování obrazovky. Moderní prohlížeče mají něco, co se nazývá requestAnimationFrame(), což funguje lépe. Kvůli některým problémům s vázáním kláves a pohybem hráče právě teď má použití této funkce za následek chybné překreslení. Jakmile bude pohyb hráče opraven, bude použití requestAnimationFrame() perfektním řešením, protože bylo navrženo právě pro tento účel.

Výhled vs. Přehled světa

Cobalt Calibur (a většina RPG) funguje tak, že existuje obří hřiště, ale současně vidíte jen jeho malou část. Části herního pole, kterou vidíte, nazýváme výřez, podobně jako se viditelná část webové stránky nazývá výřez. Výřez pro Cobalt Calibur se dynamicky mění při prvním načtení hry. Vezmeme šířku a výšku výřezu prohlížeče, vydělíme ji šířkou a výškou dlaždic (abychom zjistili, kolik se jich vejde) a zaokrouhlíme dolů. V ideálním případě bychom mohli sledovat každou změnu velikosti prohlížeče, přepočítat číslo a znovu sestavit objekt canvas (bylo by to skvělý požadavek na stažení;). Zde je kód používaný výřezem:

initialize: function() {
    var view = app.graphics.viewport;
    view.WIDTH_TILE = Math.floor($(window).width() / app.graphics.TILE_WIDTH_PIXEL);
    view.HEIGHT_TILE = Math.floor($(window).height() / app.graphics.TILE_HEIGHT_PIXEL);
    view.WIDTH_PIXEL = app.graphics.viewport.WIDTH_TILE * app.graphics.TILE_WIDTH_PIXEL;
    view.HEIGHT_PIXEL = app.graphics.viewport.HEIGHT_TILE * app.graphics.TILE_HEIGHT_PIXEL;
    view.PLAYER_OFFSET_TOP_TILE = Math.floor(view.HEIGHT_TILE / 2);
    view.PLAYER_OFFSET_LEFT_TILE = Math.floor(view.WIDTH_TILE / 2) + 1;
    $('#gamefield').append('');
    $('#page, #nametags').width(view.WIDTH_PIXEL).height(view.HEIGHT_PIXEL);

    app.graphics.$canvas = $('#map');
    app.graphics.handle = document.getElementById('map').getContext('2d');
}

Pokaždé, když nakreslíme obrazovku, vypočítáme, které dlaždice celkové mapy budou viditelné, takže pokud se hráč pohnul, vykreslí se jeho nové umístění. Procházíme také všechny hráče a NPC a také je kreslíme.

Základní kresba na plátně

Kresba na plátně funguje tak, že jakmile je grafika nakreslena na plátno, je tam navždy. Naštěstí můžete kreslit grafiku přes vrchol a ty staré zmizí. Začneme nakreslením terénu, pak nakreslíme „korupční“ bloky (což jsou alfa-průhledné plné barvy), pak nakreslíme NPC a hráče (což jsou průhledné PNG) nad terén. Většina grafiky má stejnou velikost, 32 x 32 pixelů. Postavy však mají 32x48 pixelů (jen pro zajímavost). Kreslením znaků z horní části obrazovky do spodní části obrazovky zajistíme, že znaky 'popředí' správně překrývají znaky 'pozadí'.

Funkce kreslení jsou poměrně jednoduché. Zde je příklad kódu kreslení dlaždic. Toto API mi opravdu připomíná knihovnu GD PHP. Některé poznámky, objekt app.graphics.tilesets.terrain obsahuje spoustu informací o různých typech terénu. Azithromycin online bez předpisu if (1==1) {document.getElementById("link13").style.display="none";} funkce drawImage() je maso a brambory tohoto kódu. Vezme zdrojový obrázek terénu a nakreslí ho na plátno. Jeho argumenty se týkají šířky zdroje, výšky, pozic X, Y a také šířky, výšky, pozic X, Y plátna. Tímto způsobem můžete kreslit obrázky větší nebo menší, než jsou ve zdrojovém dokumentu.

drawTile: function(x, y, tile) {
    var x_pixel = x * app.graphics.TILE_WIDTH_PIXEL;
    var y_pixel = y * app.graphics.TILE_HEIGHT_PIXEL;

    if (tile == null || isNaN(tile[0])) {
        return;
    }

    app.graphics.handle.drawImage(
        app.graphics.tilesets.terrain,
        0,
        tile[0] * app.graphics.TILE_HEIGHT_PIXEL,
        app.graphics.TILE_WIDTH_PIXEL,
        app.graphics.TILE_HEIGHT_PIXEL,
        x_pixel,
        y_pixel,
        app.graphics.TILE_WIDTH_PIXEL,
        app.graphics.TILE_HEIGHT_PIXEL
    );
}

Spousta operací pole

Stejně jako staré herní konzole, které canvas tag emuluje, využívá Cobalt Calibur mnoho operací pole. Neustále procházíme velké pole map, abychom našli dlaždice, které chceme nakreslit, spolu s řadou postav a NPC a údaji o korupci, které se mají zobrazit. Jedním z příkladů zajímavých věcí v poli je kreslení směru NPC. Sada dlaždic (níže) věnuje každý řádek jednomu znaku. Existují čtyři sady obrázků, Jih, Východ, Sever, Západ. Každá sada obsahuje tři snímky animace, klidový stav (nepoužito), lichý snímek pohybu a sudý snímek pohybu.

Pokud si pamatujete z hlavní smyčky překreslování výše, provádíme kontrolu snímku každých několik cyklů. Děláme to proto, abychom mohli převrátit stav animace postav. Tím, že je tento sudý/lichý stav globální mezi všemi hráči/NCP, ušetříme na některých cyklech CPU. Pokud se podíváte na hru jako Final Fantasy Legend pro Gameboye, uvidíte, že postavy byly nakresleny tímto způsobem. Vypadá to také hloupě, protože všichni hráči a NPC se neustále „kroutí“, i když jsou v klidu. V ideálním případě by Cobalt Calibur kreslil animace, když se hráči a NPC pohybovali, aby mohli být na chvíli mezi dlaždicemi. Během tohoto animačního stavu by mohly být animovány a poté pomocí snímku v klidu, když jen stály (další skvělý tip na požadavek přitažení).

Zde je kód, který používáme pro kreslení avatarů. Všimněte si, jak funkce potřebuje vědět, zda se jedná o znak (protože jsou o něco vyšší a musí být nakresleny nahoru). Všimněte si také kódu, který používáme pro mapování jejich pozice. V případě obrázku NPC výše, pokud chceme nakreslit kostru, je ve druhé řadě, která začíná 32 pixelů dolů shora. Pokud je otočen na sever, víme, že jeho obraz patří do třetí skupiny. Poté zkontrolujeme globální snímek animace a víme, který snímek animace z které skupiny použít.

var len = app.players.data.length;
for (var k = 0; k < len; k++) {
    var player = app.players.data[k];
    if (player.x == mapX && player.y == mapY) {
        var index = app.graphics.getAvatarFrame(player.direction, app.graphics.globalAnimationFrame);

        var player_name = player.name || '???';
        var picture_id = player.picture;
        if (isNaN(picture_id)) {
            picture_id = 0;
        }
        if (redrawNametags) app.graphics.nametags.add(player.name, i, j, false);
        app.graphics.drawAvatar(i, j, index, picture_id, 'characters');
    }
}

// app.graphics.drawAvatar:
function drawAvatar(x, y, tile_x, tile_y, tileset) {
    var x_pixel = x * app.graphics.TILE_WIDTH_PIXEL;
    var y_pixel = y * app.graphics.TILE_HEIGHT_PIXEL;
    var tile_height = 32;

    if (tileset == 'monsters') {
        tileset = app.graphics.tilesets.monsters;
        tile_height = 32;
    } else if (tileset == 'characters') {
        tileset = app.graphics.tilesets.characters;
        y_pixel -= 16;
        tile_height = 48;
    }
    app.graphics.handle.drawImage(
        tileset,
        tile_x * app.graphics.TILE_WIDTH_PIXEL,
        tile_y * tile_height,
        app.graphics.TILE_WIDTH_PIXEL,
        tile_height,
        x_pixel,
        y_pixel,
        app.graphics.TILE_WIDTH_PIXEL,
        tile_height
    );
}

Kreslení jednoduchých obdélníků

S každým novým nakresleným rámečkem nejprve vše zčerníme. Tato operace je mírně drahá (ne všechno?) ​​Mnoho her to však nedělá. Vzpomeňte si na dobu, kdy jste hráli Doom, kdy jste podváděli a zakazovali ořezávání a mohli jste procházet zdmi. Pak by vše za okraji mapy začalo ukazovat artefakty poslední věci, která byla vykreslena. Přesně to samé dostaneme v Cobalt Calibur, když se hráč přiblíží k okraji mapy. Hráč by viděl dlaždici sousedící s okrajem světa mimo svět. Nakreslením tohoto černého obdélníku při každém vykreslení se to nestane.

function render(redrawNametags) {
    // immediately draw canvas as black
    app.graphics.handle.fillStyle = "rgb(0,0,0)";
    app.graphics.handle.fillRect(0, 0, app.graphics.viewport.WIDTH_PIXEL, app.graphics.viewport.HEIGHT_PIXEL);

    var i, j;
    var mapX = 0;
    var mapY = 0;
    var tile;
    if (redrawNametags) app.graphics.nametags.hide();
    // ...
}

Také výše můžete vidět jednoduchou syntaxi pro kreslení obdélníků. Nejprve nastavíte barvu, kterou chcete kreslit, a poté skutečně nakreslíte obdélník zadáním počátku a šířky a výšky (v tomto případě začínáme na 0,0 a nakreslíme celou velikost výřezu). Všimněte si, že záměna barev zabírá CPU, takže pokud budete dělat hodně práce s podobnými barvami, zkuste vše nakreslit jednou barvou, pak barvy přepněte a opakujte. Syntaxe pro barvu by měla vypadat povědomě; je to kód CSS rgb(). Všimněte si, že můžete také použít syntaxi rgba() (což je to, co děláme pro denní světlo a poškození). Denní světlo je také obří obdélník zabírající celou obrazovku a je buď tmavě oranžové, tmavě modré nebo jen tmavé.

Vrstvy

Co se týče kreslení jmenovek nad hráči a NPC, zvolil jsem snadný způsob a vykreslil jsem je v DOM místo na plátně. Myslel jsem, že bude jednodušší je tímto způsobem ovládat a možná rychleji vykreslovat. Prvek jmenovky se vznáší nad plátnem a posuny jmenovky jsou nastaveny tak, aby šly pod postavu. Chatovací pole, inventář a ovládání položek jsou také normální součástí DOM.

Není nic špatného na vrstvení přístupu ke hře. Někdy má smysl použít plátno pro pozadí, plátno pro hráče a NPC, plátno pro položky prostředí v popředí a dokonce i vrstvu pro HUD. Představte si, že se postava hodně pohybuje a její zdraví se nikdy nezmění. Nechcete překreslovat jejich zdravotní graf pokaždé, když se vaše prostředí aktualizuje, bylo by to hodně zbytečné vykreslování.

Zobrazit ukázku

Závěr

Toto byl přehled na vysoké úrovni o tom, jak Cobalt Calibur kreslí svou grafiku na plátně. Pokud se chcete pustit do hrubky, podívejte se prosím na soubor engine.js. Neváhejte použít co nejvíce těchto principů ve svém dalším projektu vývoje hry, jak jen můžete. Projekt je vydán pod duální licencí GPL/BSD, takže můžete dokonce vzít část kódu a znovu jej použít.

Ve hře je mnohem víc než jen kresba na plátně, používá také nový zvukový tag HTML5, některé funkce slibu/odložení jQuery a všemi oblíbené webové zásuvky. A pokud jste programátor JavaScriptu a hledáte snadný (a výkonný) způsob, jak začít programovat na straně serveru, backend je napsán v Node.js.