Scena w moim projekcie wymaga nieco liftingu, w końcu składa się obecnie z samej podłogi. Czas więc pobawić się w wirtualnego pana budowlańca i uatrakcyjnić nieco moją strzelnicę.
A-Frame ma to do siebie, że wystarczy odrobina kodu aby pokazać użytkownikowi coś ciekawego. Patrząc jednak przez pryzmat ilości kodu na mój projekt muszę stwierdzić, że wygląda to dość biednie, no ale przecież nie od razu Kraków zbudowano (tak to sobie tłumaczę). Mam wypasioną podłogę imitującą jakiś rodzaj kamienia to teraz czas na ściany, bo strzelnica musi być bezpieczna dla osób na zewnątrz!
Kolejne fundamenty – ściany
Podłogę mam już gotową, ale chciałbym poprawić jakość tekstury. Do tej pory powielałem ją w osi X i Y równo po 5 razy. Przypominam, że cały plan jest ustawiony na długość i szerokość 25m. Powiększę więc szczegółowość powtarzając 12.5 razy po obu osiach ustawiając właściwość repeat="12.5 12.5"
. Aby utworzyć ściany do sceny również posłużę się elementem <a-plane>
. Co do tekstury: potrafię godzinami szukać inspiracji czy też odpowiedniego designu, ale teraz nie mam już na to czasu. Ponownie skorzystam ze strony opengameart.org i poszukam tekstury muru. Potrzebuję czterech otaczających ścian, zaczynam od tylnej z punktu widzenia użytkownika:
<Entity primitive="a-plane" material="src: #wallTexture" repeat="5 1" width="25" height="5" rotation="180 0 0" position="0 2.5 12.5" />
Tekstura została zapisana managerze zasobów pod identyfikatorem #wallTexture
. Powtarzam ją w nieco innych proporcjach dopasowując te wartości „na oko”, ponieważ szerokość ściany zgodnie z planem ma 25m ale już taka wysokość byłaby przesadą – 5m wystarczy w zupełności. Ściana jest przesunięta za pomocą atrybutu position
za użytkownika (licząc od środka sceny), natomiast jest tutaj coś na co warto zwrócić uwagę. Otóż plan jest widoczny tylko z jednej strony. Jeżeli wejdziemy kamerą pod podłoge i spojrzymy w górę to nie zobaczymy nic, podłoga będzie przezroczysta. Taka natura tego elementu i można się z takim efektem spotkać w grach gdy podczas rozgrywki trafimy na błąd i wylecimy poza tekstury. Obracam więc moją ścianę o 180 stopni po osi X aby była widoczna dla użytkownika. Czas na ścianę z przodu, przed którą będą cele:
<Entity primitive="a-plane" material="src: #wallTexture" repeat="5 1" width="25" height="5" rotation="0 0 0" position="0 2.5 -12.5" />
W tym przypadku ścianę wystarczyło jedynie podnieść do góry i przesunąć. No to jeszcze prawa ściana:
<Entity primitive="a-plane" material="src: #wallTexture" repeat="5 1" width="25" height="5" rotation="0 -90 0" position="12.5 2.5 0" />
Oraz lewa:
<Entity primitive="a-plane" material="src: #wallTexture" repeat="5 1" width="25" height="5" rotation="0 90 0" position="-12.5 2.5 0" />
A o to jak prezentuje się całość:
Całkiem sympatycznie nieprawdaż? 🙂
… i przy okazji sufit
Strzelnica pod gołym niebem to też jakieś rozwiązanie, ale mimo wszystko obmyśliłem sobie surowy klimat wojskowego bunkra… no przynajmniej coś w tym stylu. Muszę więc stworzyć w pewnym sensie pudełko w którym jest gracz i brakuje mi już tylko „wieczka”. Ponownie stosuję element <a-plane>:
<Entity primitive="a-plane" material="src: #ceilTexture" repeat="5 5" width="25" height="25" rotation="90 0 0" position="0 5 0" />
Ponownie używam rotacji aby odbić element odwrotnie do podłogi i na wysokości 5 metrów. Tak prezentuje się całe „pudełko”:
Światełko w tunelu
Scena wygląda całkiem ciekawie ale brakuje jej jeszcze pewnego dynamizmu/klimatu. Można go uzyskać wprowadzając różne kombinacje świateł na scenie, co zamierzam za chwilę uczynić. Można się zdziwić, że pomimo braku deklaracji elementu światła, pudełko w środku jest oświetlone. Otóż jeżeli nie określi się żadnego źródła światła, A-Frame robi to za użytkownika. A świateł mamy kilka rodzajów m.in. punktowe (ang. point), kierunkowe (ang. directional) czy miejscowe (ang. spot). Szczególnie to ostatnie jest ciekawe, ponieważ tworzy efekt reflektora światła (kto czytał komiksy z batmanem ten od razu załapie o jaki efekt chodzi). Światło można użyć za pomocą komponentu light
, lub też elementu <a-light>
. Dodam więc światło miejscowe, które rozświetli ścianę przed graczem:
<Entity primitive="a-light" type="spot" intensity="1" penumbra="0.3" color="#fff" position="0 1 5" rotation="-15 0 0"></Entity>
Ciekawym i wartym wspomnienia atrybutem, który wykorzystałem jest penumbra
za pomocą której uzyskuje się rozmycie światła (granica z otoczeniem jest rozmyta). Chciałbym jeszcze dodać zielone światło punktowe, które będzie tworzyło „wojskowy” klimacik, oraz jedno punktowe białe, aby tył sceny nie był zbyt ciemny:
<Entity primitive="a-light" type="point" intensity="0.5" color="green" position="0 3 5"></Entity> <Entity primitive="a-light" type="point" intensity="0.2" color="#fff" position="0 3 12"></Entity>
Całość prezentuje się lepiej niż myślałem:
Podsumowanie
Zadziwiające, że ponownie ilość kodu, która przybyła jest niewielka, ale efekty już na tym etapie mogą się podobać. To na prawdę wielka siła A-Frame’a i mam nadzieję, że nie zniknie on w przyszłości jedynie jako ciekawostka. Tymczasem mnie pozostało jeszcze kilka rzeczy, w tym najważniejsza czyli strzelanie. Dlatego też w następnym wpisie poruszę kwestie zdarzeń. Poniżej kod całej sceny:
import { Scene, Entity } from "aframe-react"; import React from "react"; import floorImg from "../../images/stone.png"; import crosshairImg from "../../images/crosshair.png"; import ceilImg from "../../images/wall2.jpg"; import wall2Img from "../../images/wall2.png"; const SceneComponent = () => ( <Scene stats> <Entity primitive="a-assets"> <img id="floorTexture" src={floorImg} /> <img id="crosshairTexture" src={crosshairImg} /> <img id="ceilTexture" src={ceilImg} /> <img id="wallTexture" src={wall2Img} /> </Entity> <Entity primitive="a-plane" material="src: #floorTexture" repeat="12.5 12.5" width="25" height="25" rotation="-90 0 0" /> <Entity primitive="a-plane" material="src: #wallTexture" repeat="5 1" width="25" height="5" rotation="180 0 0" position="0 2.5 12.5" /> <Entity primitive="a-plane" material="src: #wallTexture" repeat="5 1" width="25" height="5" rotation="0 0 0" position="0 2.5 -12.5" /> <Entity primitive="a-plane" material="src: #wallTexture" repeat="5 1" width="25" height="5" rotation="0 -90 0" position="12.5 2.5 0" /> <Entity primitive="a-plane" material="src: #wallTexture" repeat="5 1" width="25" height="5" rotation="0 90 0" position="-12.5 2.5 0" /> <Entity primitive="a-plane" material="src: #ceilTexture" repeat="5 5" width="25" height="25" rotation="90 0 0" position="0 5 0" /> <Entity primitive="a-camera" wasd-controls="enabled: false" position="0 0 10"> <Entity primitive="a-cursor" geometry="primitive: ring; radiusInner: 0.00001; radiusOuter: 0.04" material="src: #crosshairTexture" /> </Entity> <Entity primitive="a-light" type="spot" intensity="1" penumbra="0.3" color="#fff" position="0 1 5" rotation="-15 0 0"></Entity> <Entity primitive="a-light" type="point" intensity="0.5" color="green" position="0 3 5"></Entity> <Entity primitive="a-light" type="point" intensity="0.2" color="#fff" position="0 3 12"></Entity> </Scene> ); export default SceneComponent;