Jak mogę tworzyć rozszerzające się kształty pocisków?

12

Chcę stworzyć serię rozszerzających się wzorów pocisków, które tworzą kształty, takie jak kwadraty, trójkąty itp. Przykład tego, czego szukam, można zobaczyć w poniższym filmie, w którym po zebraniu gwiazd pociski eksplodują w kształcie rozszerzająca się gwiazda:

https://youtu.be/7JGcuTWYdvU?t=2m41s

lepton
źródło
2
Och, to dobre pytanie. Nie mam konkretnych odpowiedzi, ale wyobrażam sobie, że możesz użyć obiektu 2D, albo sprite'a, albo prostego kształtu i spawnować pociski wzdłuż krawędzi. Oczywiście sztuczka polegałaby na nadaniu im odpowiedniej prędkości, zarówno w kształcie zewnętrznym, jak i w przypadku takiego przewijania, aby poruszały się do przodu wraz z ekranem. Bardzo chciałbym zobaczyć tutaj odpowiedzi.
Jesse Williams
1
Popularną nazwą tego rodzaju efektu są „efekty cząsteczkowe”. To wyszukiwane hasło może ci pomóc!
Cort Ammon
1
Dzięki, od dłuższego czasu używam efektów cząsteczkowych w XNA i libGDX, ale nie byłem pewien, jak poradzić sobie z tym szczególnym stylem efektu.
lepton
1
Jest jeszcze jedna odpowiedź na to pytanie, która jest niezwykle potężna, ale bardzo złożona w programowaniu. I potrzebuje prawdziwej klawiatury do pisania. Oznacz to jako zakładkę w celu późniejszego wyjaśnienia.
Draco18s nie ufa już
Ciekawe - nigdy nie wybrałbym efektów cząsteczkowych dla czegoś takiego. A może to tylko nakreślenie w Jedności. Podczas gdy efekty cząsteczkowe mogą mieć zderzacze (powodując w ten sposób uszkodzenie obiektu), wydaje się, że spowodowałoby to o wiele więcej narzutów niż zwykłe tworzenie kopii obiektów.
Jesse Williams

Odpowiedzi:

11

Najłatwiejszym sposobem jest zaprojektowanie kształtu, a następnie obliczenie ruchu cząstek. W tej odpowiedzi będę budował kwadrat, ale dotyczy to dowolnego kształtu.

Zacznij od zaprojektowania kształtu jako pozycji względnych wokół jakiegoś punktu początkowego.

kwadrat

Teraz musisz obliczyć, jak kształt się rozszerzy. Aby to zrobić, po prostu obliczamy wektor wskazujący od origindo każdego point, odejmując originpozycję od naszej pointpozycji, a następnie normalizując wektor. vector = normalize(point.x - origin.x, point.y - origin.y).

wektor

Teraz możemy obliczyć położenie punktów w dowolnym momencie za pomocą tego wektora. Kolejną pozycję punktów obliczasz wykonując point.position += point.vector * point.velocity. Przykład pseudokodu z wykorzystaniem naszego poprzedniego punktu:

// When you start your program you set these values.
point.position = (-3, 3); // Start position. Can be anything.
point.vector = normalize(-3, 3); // Normalized vector.
point.velocity = 3; // Can be anything.

// You do this calculation every frame.
point.position += point.vector * point.velocity;
// point.vector * point.velocity = (-3, 3)
// point.position is now (-6, 6) since (-3, 3) + (-3, 3) = (-6, 6)

W ten sposób wszystkie punkty zostaną przesunięte na zewnątrz o 3 jednostki.


Notatki

  • Można przeczytać na niektórych prostych matematyki wektorowej tutaj .
  • Pozycja może być dowolna, o ile wszystkie pozycje odnoszą się do jakiegoś punktu początkowego.
  • Prędkość wszystkich punktów powinna być taka sama, aby zapewnić równomierny ruch, ale posiadanie różnych prędkości może dać interesujące wyniki.
  • Jeśli ruch wydaje się być wyłączony, powinieneś sprawdzić punkt początkowy. Jeśli nie jest dokładnie w środku kształtu, kształt może się rozszerzyć w dziwny sposób.
Charanor
źródło
9
Chcę tylko zaznaczyć, że prędkość każdej cząstki powinna być proporcjonalna do odległości od początku na pierwszej ramce (co oznacza, że ​​obliczaj tylko raz, a nie na ramkę). Alternatywnie, po prostu nie można normalizować wektora kierunku. Jeśli tego nie zrobisz, kształt nie będzie skalowany liniowo, ale raczej zbliży się do koła (jeśli wszystkie prędkości są takie same.)
Aaron
@Charanor Wielkie dzięki za wyjaśnienie. Właściwie studiowałem matematykę dyskretną na uniwersytecie, ale minęło już sporo czasu. Spróbuję dziś coś wdrożyć.
lepton
2

Istnieje więc projekt o nazwie BulletML, który jest językiem znaczników do tworzenia złożonych wzorców cząstek / pocisków. Prawie na pewno będziesz musiał przenieść kod na swój własny język, ale może on zrobić naprawdę niesamowite rzeczy.

Na przykład ten szef został zrobiony w (mocno zmodyfikowanym) rozszerzeniu BulletML dla Unity3D (autor tego wzorca przesłał ten film, a nieszczęście jest szalone, a także dobre 1 ). To najtrudniejsza odmiana tego wroga i pokazuje całkiem dobrze, co potrafi BulletML (i sprawdź innych bossów Misery, takich jak Wallmaster ).

Albo mogę pokazać ten przykład, który jest wzorcem, który napisałem podczas pracy nad rozszerzeniem dla The Last Federation , przy użyciu starszej wersji systemu, który jest mniej przyjazny dla modów i używa tylko pojedynczych zmiennych AZ:

Przykład wzoru pocisku

Zielone pociski, które tworzą te pierścienie, powstają z pocisku macierzystego, który obraca się z dużą prędkością, ale same się nie poruszają. Zadają ogromne obrażenia, utrzymując gracza na większym dystansie, ograniczając go do broni o niższych obrażeniach i pozwalając mobilnym obrońcom na nękanie gracza (gracz wygrał, jeśli zniszczona została nieruchoma struktura na środku).

Oto część składni XML, która tworzy te bąbelki:

<bullet_pattern name="Barrier">
    $WallShotAngle B=.3 A=90
    $WallShotAngle B=.3 A=-90
    $WallShotAngle B=.3 A=0
    $WallShotAngle B=.375 A=180
</bullet_pattern>

<var name="WallShotAngle">
    <bullet angle="[A]" speed="4000" interval_mult=".01" dumbfire="1" shot_type="GravityWavePurple">
        <wait time="[B]" />
        <change angle="0" speed="1000" time=".0001" />
        <spawn>
            <bullet_pattern>
                <bullet angle="[A]" speed="0" shot_type="CurveBarGreen" damage_mult="8">
                <wait time="12" />
                <die />
                </bullet>
            </bullet_pattern>
        </spawn>
        <die />
    </bullet>
</var>

Na obrazku widać niektóre fioletowe ujęcia „fali grawitacyjnej”, które niemal natychmiast przemieszczają się od źródła (które się obraca) do krawędzi bańki, po czym odradza się zielony strzał „zakrzywiony pasek”, który stoi tam przez 12 sekund przed znikanie. Niebieskie i żółte ujęcia, które pominąłem, ponieważ są znacznie bardziej skomplikowane.

Jeden z pozostałych wzorów ( pocisk artyleryjski ) w dodatku został napisany przez Misery'ego, chociaż dokonałem pewnych modyfikacji. Początkowo jest to penetrujący strzał o niskim poziomie obrażeń, który leci na duże odległości, a następnie eksploduje w ogromny pokaz sztucznych ogni, zadając mnóstwo obrażeń. Jego maksymalny zasięg był znacznie wyższy, niż gracz mógł osiągnąć, co zasadniczo zmusiło gracza do walki na krótkim dystansie, co było korzystne dla innych typów jednostek NPC ze względu na efekt strzelby (więcej pocisków skupionych w małej strefie).

BulletML jest ogólnie łatwy w obsłudze i może robić niesamowite rzeczy. Pociski mogą zmieniać kierunek, zmieniać prędkość, odradzać inne wzorce, umrzeć wcześnie, powtarzać kolekcję poleceń w pętli, używać opóźnień, zmieniać obraz duszka pocisku, podążać za ich rodzicem (lub nie) ... I wszystko, czego nie obsługuje napisz do tego.

Zdecydowanie polecam, jeśli grasz w poważną strzelankę. Nadal będziesz musiał opracować matematykę współrzędnych, aby uzyskać pożądane kształty, o czym mówi Charanor w swojej odpowiedzi, ale silnik pocisku, taki jak BulletML, zapewni ci o wiele większą elastyczność, że poświęcisz więcej czasu na projektowanie nowych wzorów, niż wymyślanie jak je kodować.

  1. Aby wyjaśnić, jak dobra jest nędza, te filmy są przeciwko bossom podłogowym z wyposażeniem początkowym : bez modułów, bez materiałów eksploatacyjnych i podstawowej strzelanki do grochu. I xe otrzymuje tylko jedno trafienie, pomimo wydłużonej natury walki. Ok, 9 trafień przeciwko Centrifuge (który nie pojawi się do trzeciego piętra po graczu na pewno będzie miał ulepszenia powodujące co najmniej podwójne obrażenia w porównaniu).
Draco18s nie ufa już SE
źródło
Dzięki, byłem niejasno świadomy BulletML, ponieważ istnieje on już od jakiegoś czasu, ale zdecydowanie jest to przesada w mojej prostej grze, która tylko sporadycznie zagłębia się w kulę piekielną, i sama w sobie nie jest strzelanką do kul.
lepton
@lepton Całkowicie zrozumiałe. To decyzja, którą musisz podjąć, ale odpowiedź może być „najlepsza” dla kogoś innego. Wiem, że po pracy nad TLF i rozpoczęciu budowy własnej strzelanki chciałem jej użyć tylko ze względu na to, jak potężna i łatwa była z nią praca. :)
Draco18s nie ufa już
1

Jak wskazał Charanor, możesz użyć szeregu punktów, aby zdefiniować swój kształt, a następnie zaktualizować ich pozycję w czasie. Poniżej znajduje się przykładowy przykład implementacji kształtu gwiazdy lub kształtu niestandardowego za pomocą punktów:

package com.mygdx.gtest;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;

public class Test extends ApplicationAdapter{

    public SpriteBatch sb;
    private StarShape ss, ssBig;

    @Override
    public void create() {
        sb = new SpriteBatch();
        Pixmap pmap = new Pixmap(2, 2,Format.RGBA8888);
        pmap.setColor(Color.WHITE);
        pmap.fill();
        ss = new StarShape(50,50,new Texture(pmap), 10, true);
        ssBig = new StarShape(250,250,new Texture(pmap), 50, false);
        pmap.dispose();

    }


    @Override
    public void render() {
        super.render();

        Gdx.gl.glClearColor(0, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        ss.update(Gdx.graphics.getDeltaTime());
        ssBig.update(Gdx.graphics.getDeltaTime());

        sb.begin();
            ss.draw(sb);
            ssBig.draw(sb);
        sb.end();

    }


    @Override
    public void dispose() {
        super.dispose();
    }

    private class StarShape{
        public float progress = 1f;
        public Texture bulletTex;
        public Array<Vector2> points = new Array<Vector2>();
        public Vector2 center;

        public StarShape(float x, float y, Texture tex, float initialSize, boolean mathWay){
            center = new Vector2(x,y);
            bulletTex = tex;

            if(mathWay){
                // define star shape with maths
                float alpha = (float)(2 * Math.PI) / 10; 
                float radius = initialSize;

                for(int i = 11; i != 0; i--){
                    float r = radius*(i % 2 + 1)/2;
                    float omega = alpha * i;
                    points.add(
                            new Vector2(
                                    (float)(r * Math.sin(omega)), 
                                    (float)(r * Math.cos(omega)) 
                                )
                            );
                }
            }else{
            // or define star shape manually (better for non geometric shapes etc

                //define circle
                points.add(new Vector2(-3f,0f));
                points.add(new Vector2(-2.8f,1f));
                points.add(new Vector2(-2.2f,2.2f));
                points.add(new Vector2(-1f,2.8f));
                points.add(new Vector2(0f,3f));
                points.add(new Vector2(1f,2.8f));
                points.add(new Vector2(2.2f,2.2f));
                points.add(new Vector2(2.8f,1f));
                points.add(new Vector2(3f,0f));
                points.add(new Vector2(2.8f,-1f));
                points.add(new Vector2(2.2f,-2.2f));
                points.add(new Vector2(1f,-2.8f));
                points.add(new Vector2(0f,-3f));
                points.add(new Vector2(-1f,-2.8f));
                points.add(new Vector2(-2.2f,-2.2f));
                points.add(new Vector2(-2.8f,-1f));

                // mouth
                points.add(new Vector2(-2,-1));
                points.add(new Vector2(-1,-1));
                points.add(new Vector2(0,-1));
                points.add(new Vector2(1,-1));
                points.add(new Vector2(2,-1));
                points.add(new Vector2(-1.5f,-1.1f));
                points.add(new Vector2(-1,-2));
                points.add(new Vector2(0,-2.2f));
                points.add(new Vector2(1,-2));
                points.add(new Vector2(1.5f,-1.1f));

                points.add(new Vector2(-1.5f,1.5f));
                points.add(new Vector2(1.5f,1.5f));

            }

        }

        public void update(float deltaTime){
            this.progress+= deltaTime;
        }

        public void draw(SpriteBatch sb){
            Vector2 temp = new Vector2(0,0);
            for(Vector2 point: points){
                temp.x = (point.x);
                temp.y = (point.y);
                temp.scl(progress);
                sb.draw(bulletTex,temp.x + center.x,temp.y +center.y);
            }
        }
    }
}
dfour
źródło
Wielkie dzięki za przykład, sprawdzę go dziś po południu, aby sprawdzić, czy mogę go uruchomić.
lepton