VRath – podstawowa scena dla strzelnicy

Konkurs DSP 2017 powoli zbliża się ku finałowi, dlatego muszę podkręcić tempo prac. Dzisiaj krótko i zwięźle: o scenie dla mojej strzelnicy i kilku dodatkowych komponentach.

W finalnym wpisie dotyczącym konfiguracji środowiska dodałem do projektu pakiet aframe-react, którego będę używał w kolejnych przykładach.



Śledzenie statystyk wydajnościowych

Framework A-Frame posiada w swoim zestawie wbudowanych komponentów sporo takich, które służą nie tylko odbiorcom ostatecznie skonstruowanej rozgrywki, ale także programistom. Przykładem może być zastosowany domyślnie dla sceny komponent poruszania się pomocą klawiszy WASD. Komponenty zastosowane w tym stylu przyspieszają pracę nad projektem, ponieważ o pewnie rzeczy nie trzeba się już martwić, są załatwiane za programistów.

Statystyki komponentu stats we frameworku A-Frame

Innym rodzajem komponentów użytecznych są te, które wspomagają twórców przy pracy nad kodem i chciałbym tutaj pochylić się nad komponentem Stats. Pozwala on na wyświetlenie w lewym górnym rogu statystyk projektu w postaci zafiksowanego elementu. Najważniejszą informacją jaką ten komponent dostarcza jest ilość klatek na sekundę (ang. FPS), którą pochłania renderowanie sceny. Wraz ze spadkiem tej wartości spada płynność wyświetlania sceny, a tym samym doświadczamy efektu „zacinania się” płynności animacji. Oczywiście jest to zjawisko niepożądane na które ma wpływ wiele rzeczy, chociażby konfiguracja sprzętu użytkownika, czy skomplikowanie sceny.

Jako iż nie mamy wpływu na rozmaite konfiguracje sprzętowe, skupiłbym się raczej na ograniczaniu ilości encji na scenie, oraz nie przesadzanie z dużą ilością takich efektów jak szklane powierzchnie czy cienie. Myślę, że tworząc grę pod VR tym bardziej należy brać pod uwagę ograniczanie efektów, ponieważ mogą one zaszkodzić potencjalnemu graczowi (oczywiście zdrowotnie). Jak dodać wspomniany komponent? Jest tylko jedno miejsce do którego jest on przeznaczony, a jest nim element sceny. Korzystając z aframe-react wygląda to banalnie prosto:

<Scene stats></Scene>

Jedno słowo stats i dynamiczny element ze statystykami dołączony do sceny. Być może skończę ten projekt szybciej niż zakładałem! 😉 No, to jedziemy dalej.

Budujemy podłogę

Postać, która będzie strzelała na mojej strzelnicy musi na czymś stać, a przynajmniej powinniśmy jej dać złudzenie, że jest pod nią solidny grunt. Prowizoryczną „podłogę” można wykonać za pomocą różnych encji i pewnie sporo osób wybierze w tym celu box, czyli pudełko, dając mu odpowiednio dużą szerokość i długość. Niestety box wymaga podania także wysokości, a to jest bardzo często niepotrzebna właściwość, bo przecież stojąca na „ziemi” postać i strzelająca do tarczy niebardzo będzie zainteresowana wysokością podłoża. Wystarczyłaby więc płaska płaszczyzna posiadająca dwie szerokości (uproszczenie myślenia z mojej strony: szerokość po osi X i wysokości po osi Y). Na szczęście taką encje A-Frame posiada i nazywa się ona <a-plane>. Dodam więc przykładową płaszczyznę w kolorze czerwonym oraz szerokości i „wysokości” równi po 25m (tak, A-Frame używa metrów jako jednostek długości) do swojej sceny:

<Entity primitive="a-plane" color="red" width="25" height="25" />

Hmm niestety nic nie widać, pusta przestrzeń! A to dlatego, że domyślnie encja <a-plane> jest ustawiana równolegle do osi Y. Mało tego, bez określenia pozycji znajdzie się ona w tym samym miejscu co nasza kamera. Można to łatwo sprawdzić cofając się na scenie na przykład za pomocą dolnej strzałki na klawiaturze. Aby zmienić to zachowanie należy obrócić o 90 stopni <a-plane> po osi X za pomocą atrybutu rotation:

<Entity primitive="a-plane" color="red" width="25" height="25" rotation="-90 0 0" />

I voila! Krwisto czerwona podłoga jak z planu u Kubricka gotowa! 🙂

Czerwona podłoga

Oczywiście taki kolorek znajdzie swoich fanatyków, ale ja wolałbym coś chłodniejszego, jakiś rodzaj posadzki. Wykorzystam w tym celu teksturę znalezioną na portalu opengameart.org i załaduję ją za pomocą czegoś co nazywa się Asset Management System. Jest to element reprezentowany w postaci encji <a-assets>. Pozwala na wczytywanie zewnętrznych zasobów w jednym miejscu i generalnie ułatwia zarządzanie assetsami (więcej o nim będzie w osobnym wpisie). No to czas pobrudzić sobie rączki:

<Entity primitive="a-assets">
    <img id="floorTexture" src="../images/stone.png" />
</Entity>

Wewnątrz elementu <a-assets> dodałem znany z HTMLa znacznim <img> ze ścieżką do wybranego obrazka. Co ciekawe to nie musiał być wcale obrazek, ponieważ manager assetsów w ten sam sposób jest w stanie poradzić sobie np. z tagami <audio> czy <video>. Aby zastosować wybraną teksturę można wykorzystać właściwość material dla wybranej encji i ustawić atrybut src w taki sam sposób w jaki robi się „kotwice” w HTMLu (na identyfikator zasobu):

<Entity primitive="a-plane" material="src: #floorTexture" width="25" height="25" rotation="-90 0 0" />

Domyśnie tekstura zostanie rozciągnięta na całą płaszczyznę. Można wykorzystać atrybut repeat, który określa ile razy powielić teksturę w osi X oraz ile w osi Y. Skoro ustawiłem 25m szerokości i wysokości płaszczyzny to powielę teksturę 5 razy w obu osiach:

<Entity primitive="a-plane" material="src: #floorTexture" repeat="5 5" width="25" height="25" rotation="-90 0 0" />
Kamienna posadzka w strzelnicy

No i powoli całość zaczyna nawet wyglądać. Czas zająć się elementami kamery.

Blokada poruszania się z klawiatury

Element <a-scene>, lub też gdy korzysta się tak jak w moim przypadku z aframe-react element <Scene>, dostarcza domyślną kamerę dla sceny wraz z możliwością rozglądania się za pomocą myszy czy np. z możliwością poruszania się po scenie za pomocą klawiatury. Za ten drugi odpowiada komponent wasd-controls i już z nazwy można wywnioskować, które klawisze są wykorzystywane do przemieszczania się. Oprócz standardowych klawiszy WASD komponent ten pozwala na poruszanie się także za pomocą klawiszy strzałek, a całe poruszanie odbywa się w czterech kierunkach: do przodu, do tyłu, w lewo i w przód. No dobrze, ale nie chciałbym żeby na mojej strzelnicy ktoś uciekał ze swojego stanowiska i biegał po całej scenie. Rozglądanie się to jedno, a przemieszczanie się to drugie. Na szczęście można to bardzo łatwo wyłączyć:

<Entity primitive="a-camera" wasd-controls="enabled: false"></Entity>

Nadpisuję domyślną kamerę sceny własną i ustawiam dla jej komponentu wasd-controls właściwość enabled: false. Poruszanie się zostało wyłączone, świetnie!

Dodajemy celownik

Skoro jesteśmy już przy kamerze to chciałbym dodać do ekranu celownik, czyli standardową rzecz jaką można spotkać w różnego rodzaju grach FPS. Pamiętając o tym, że cały czas pracujemy w metodyce ECS to nawet taki element jak celownik będzie w tym przypadku encją z określonym komponentem bądź zestawem komponentów. Jeżeli chodzi o sam wygląd to ponownie skorzystam z opengameart.org i wybiorę coś ciekawego.

obrazek celownika

Sam element celownika umieszcza się dość prosto: wystarczy w środku elementu kamery umieścić wybraną encję z komponentem cursor, lub też gotową encję <a-cursor>. Encja kursora wykorzystuje do rysowania element torusa, czli po prostu pierścień w trzech wymiarach posiadający właściwości radiusInner i radiusOuter. Gotowy kod kamery wraz z celownikiem i wybraną przeze mnie teksturą wygląda następująco:

<Entity primitive="a-camera" wasd-controls="enabled: false">
    <Entity primitive="a-cursor" geometry="primitive: ring; radiusInner: 0.00001; radiusOuter: 0.04" material="src: #crosshairTexture" />
</Entity>

Podsumowanie

Miało być krótko i zwięźle, ale jak zwykle chęć dokładnego opisania wykorzystywanych elementów wzięła górę… dlatego fanów TL;DR serdecznie przepraszam i obiecuję, że w przyszłości pojawi się stosowna sekcja na początku każdego wpisu. A wracając do obecnego: podłoga dla strzelnicy zrobiona, celownik przygotowany i blokada ruchu zastosowana. Tak wygląda cały kod sceny z dzisiejszej zabawy:

import { Scene, Entity } from "aframe-react";
import React from "react";
import floorImg from "../../images/stone.png";
import crosshairImg from "../../images/crosshair.png";

const SceneComponent = () => (
    <Scene stats>
        <Entity primitive="a-assets">
            <img id="floorTexture" src={floorImg} />
            <img id="crosshairTexture" src={crosshairImg} />
        </Entity>
        <Entity primitive="a-plane" material="src: #floorTexture" repeat="5 5" width="25" height="25" rotation="-90 0 0" />
        <Entity primitive="a-camera" wasd-controls="enabled: false">
            <Entity primitive="a-cursor" geometry="primitive: ring; radiusInner: 0.00001; radiusOuter: 0.04" material="src: #crosshairTexture" />
        </Entity>
    </Scene>
);

export default SceneComponent;

Kolejny wpis już niebawem!