VRath – projektowanie tarczy

I już połowa maja minęła, czas biegnie nieubłaganie, a ja akurat mam teraz milion rzeczy na głowie. Broni jednak nie składam i pokażę dziś jak zbudowałem sobie tarczę i jak rozwiązałem problem punktacji.



Do tej pory moim celem była encja kuli zawieszona w przestrzeni, animująca się z lewej do prawej i na odwrót. Animacje chciałbym zachować, natomiast kule muszę zastąpić jakimś sensownym obiektem, który chociaż trochę będzie przypominał normalną tarczę strzelniczą. Cel będzie grupą encji i będzie się składał z następujących elementów:

  • Belka służąca jako „taśma” po której będzie poruszała się tarcza
  • Belka „zwisająca” z taśmy, służąca do trzymania tarczy
  • Białe płótno w postaci cienkiego prostopadłościanu imitujące ciało
  • Białe płótno w postaci okręgu imitujące głowę
  • Pierścienie z różnych kolorów szarości imitujące wartości na tarczy i umiejscowione na płótnach

I teraz w kolejności powyższe elementy będą w kodzie wyglądały następująco:

1. Taśma:

<Entity primitive="a-box" color="#575757" depth="1" height="0.5" width="25" position="0 4.75 -8">
</Entity>

Belka górna to po prostu prostopadłościan zbudowany z pomocą encji <a-box>. Oprócz swojej wizualnej funkcji, będzie również spełniał rolę kontenera dla pozostałych encji. Pozycjonowanie elementów wewnętrznych odbywać się zatem będzie względem pozycji tej belki, nie względem środka sceny.

2. Encja animacyjna i belka dla tarczy:

<Entity position="10 -1.6 0" animation={{
    property: "position",
    dir: "alternate",
    dur: 4000,
    easing: "easeInOutCubic", 
    loop: true,
    to: "-10 -1.6 0"
}}></Entity>

Powyższa encja nie ma żadnej geometrii poza pozycją, wyróżnia ją jednak komponent animacyjny. To właśnie ta encja przejmie animację po starym prototypie tarczy i również będzie grupą dla wewnętrznych obiektów. Dzięki temu będą się one poruszały wszystkie razem, nie trzeba będzie animować ich osobno (to nawet brzmi absurdalnie). Belka pionowa to z kolei taki prosty obiekt:

<Entity primitive="a-box" color="#333333" depth="0.1" height="2.5" width="0.2"></Entity>
<Entity primitive="a-sphere" radius="0.4" color="#333333" position="0 1.5 0"></Entity>

Oprócz obiektu typu a-box, pojawia się tu też a-sphere, która „symuluje” uchwyt belki pionowej do poziomej. Taki tam smaczek graficzny ha!

3. Płótno główne tarczy:

<Entity primitive="a-box" color="#fafafa" depth="0.02" height="1.66" width="1.38" position="0 -0.9 0.08"></Entity>

I kolejny a-box. Tym razem będzie to właściwy element tarczy i przy okazji grupa dla pierścieni punktacyjnych.

4. Płótno imitujące głowę sylwetki na tarczy:

<Entity primitive="a-circle" radius="0.4" color="#fafafa" position="0 1.15 0">
</Entity>

Tym razem użyłem encji <a-circle>, rysuje ona proste dwuwymiarowe koło na scenie. Encja ta została umieszczona wewnątrz encji sylwetki.

5. Pierścienie punktacyjne:

<Entity events={{
    click: () => {
        console.log("25");
    }
}} primitive="a-ring" color="#666" radius-inner="0.19" radius-outer="0.3" position="0 0 0.04"></Entity>
<Entity events={{
    click: () => {
        console.log("50");
    }
}} primitive="a-circle" radius="0.08" color="#222" position="0 0 0.04"></Entity>

Powyższe dwie encje umieszczone zostały w elemencie głowy. Pierścień zewnętrzny zbudowany z pomocą <a-ring>, który wypisuje na konsoli 25 przy kliknięciu w niego, oraz koło, które po kliknięciu wypisuje 50. Te liczby to oczywiście punktacja. Podobną rzecz wykonuję dla encji sylwetki:

<Entity primitive="a-circle" radius="0.6" color="#ccc" position="0 0 0.04">
    <Entity events={{
        click: () => {
            console.log("5");
        }
    }} primitive="a-ring" color="#666" radius-inner="0.36" radius-outer="0.46" position="0 0 0.04"></Entity>
    <Entity events={{
        click: () => {
            console.log("15");
        }
    }} primitive="a-ring" color="#444" radius-inner="0.19" radius-outer="0.26" position="0 0 0.04"></Entity>
    <Entity events={{
        click: () => {
            console.log("30");
        }
    }} primitive="a-circle" radius="0.1" color="#222" position="0 0 0.04"></Entity>
</Entity>

W powyższym przykładzie znajdują się dwa pierścienie z punktami 5 i 15, oraz środek sylwetki: koło z 30 punktami za strzał. I to by było na tyle jeżeli chodzi o tarczę! No dobrze, może nie do końca, bo przecież nikt nie chce patrzeć w konsole czy trafił. Zarządzaniem punktacją zajmę się w kolejnym wpisie.
Można się zastanawiać jak długo wybierałem wartości dla chociażby pozycji elementów. Otóż z A-Frame jest to bardzo proste, ponieważ dysponuje on wbudowanym A-Frame inspectorem. Za pomocą tego narzędzia można wizualnie przenosić obiekty encji w trzech płaszczyznach, zmieniać właściwości obiektów, a nawet dogrywać komponenty z A-Frame register. O to jak prezentuje się kawałek mojej sceny w A-Frame inspectorze:

Zrzut z A-Frame inspector

A tak prezentuje się cały kod elementu tarczy:

import { Entity } from "aframe-react";
import React from "react";

const TargetComponent = () => (
    <Entity primitive="a-box" color="#575757" depth="1" height="0.5" width="25" position="0 4.75 -8">
        <Entity position="10 -1.6 0" animation={{
            property: "position",
            dir: "alternate",
            dur: 4000,
            easing: "easeInOutCubic", 
            loop: true,
            to: "-10 -1.6 0"
        }}>
            <Entity primitive="a-box" color="#333333" depth="0.1" height="2.5" width="0.2"></Entity>
            <Entity primitive="a-sphere" radius="0.4" color="#333333" position="0 1.5 0"></Entity>
            <Entity primitive="a-box" color="#fafafa" depth="0.02" height="1.66" width="1.38" position="0 -0.9 0.08">
                <Entity primitive="a-circle" radius="0.4" color="#fafafa" position="0 1.15 0">
                    <Entity events={{
                        click: () => {
                            console.log("25");
                        }
                    }} primitive="a-ring" color="#666" radius-inner="0.19" radius-outer="0.3" position="0 0 0.04"></Entity>
                    <Entity events={{
                        click: () => {
                            console.log("50");
                        }
                    }} primitive="a-circle" radius="0.08" color="#222" position="0 0 0.04"></Entity>
                </Entity>
                <Entity primitive="a-circle" radius="0.6" color="#ccc" position="0 0 0.04">
                    <Entity events={{
                        click: () => {
                            console.log("5");
                        }
                    }} primitive="a-ring" color="#666" radius-inner="0.36" radius-outer="0.46" position="0 0 0.04"></Entity>
                    <Entity events={{
                        click: () => {
                            console.log("15");
                        }
                    }} primitive="a-ring" color="#444" radius-inner="0.19" radius-outer="0.26" position="0 0 0.04"></Entity>
                    <Entity events={{
                        click: () => {
                            console.log("30");
                        }
                    }} primitive="a-circle" radius="0.1" color="#222" position="0 0 0.04"></Entity>
                </Entity>
            </Entity>
        </Entity>
    </Entity>
);

export default TargetComponent;

Podsumowanie

Wpis nieco skromniejszy niż zwykle, ale mam już najważniejszy element na scenie. W kolejnym kroku zajmę się stanem gry i uzupełnieniem sceny o dodatkowe elementy.