Como criar formas de padrão de marcador em expansão?

12

Quero fazer uma série de padrões de marcadores em expansão que formam formas como quadrados, triângulos etc. estrela em expansão:

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

lepton
fonte
2
Oh, essa é uma boa pergunta. Não tenho respostas específicas, mas imagino que você possa usar um objeto 2D, seja um sprite ou uma forma simples, e gerar as balas ao longo da borda. Obviamente, o truque seria dar a eles velocidade adequada, tanto em sua forma externa quanto, se você estiver fazendo um scroller como esse, para fazê-los avançar com a tela. Muito interessado em ver respostas aqui.
Jesse Williams
1
Um nome popular para esse tipo de efeito é "efeitos de partículas". Esse termo de pesquisa pode ajudá-lo!
Cort Ammon
1
Obrigado, estou usando efeitos de partículas no XNA e libGDX há um bom tempo, mas não sabia ao certo como lidar com esse estilo de efeito específico.
Lept
1
Há outra resposta para isso que é incrivelmente poderosa, mas muito complexa de programar. E é preciso um teclado real para digitar. Marcando isso para uma explicação posterior.
Draco18s não confia mais em
Interessante - eu nunca teria usado efeitos de partículas para algo assim. Ou talvez seja apenas um delineamento no Unity. Embora os efeitos de partículas possam ter colisores (danificando um objeto), parece que isso criaria muito mais sobrecarga do que simplesmente instanciar cópias de objetos.
Jesse Williams

Respostas:

11

A maneira mais fácil de fazer isso seria primeiro projetar a forma e depois calcular o movimento das partículas. Nesta resposta, estarei construindo um quadrado, mas isso se aplica a qualquer forma.

Comece projetando sua forma como posições relativas em torno de algum ponto de origem.

quadrado

Agora você precisa calcular como a forma será expandida. Para fazer isso, simplesmente calculamos o vetor apontando de originpara todos pointsubtraindo a originposição da posição de nossa pointe normalizando o vetor. vector = normalize(point.x - origin.x, point.y - origin.y).

vetor

Agora podemos calcular a posição dos pontos a qualquer momento usando esse vetor. Você calcula a próxima posição dos pontos fazendo point.position += point.vector * point.velocity. Exemplo de pseudocódigo usando nosso ponto anterior:

// 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)

Isso moverá todos os pontos para fora em 3 unidades a cada quadro.


Notas

  • Você pode ler algumas matemáticas vetoriais simples aqui .
  • A posição pode ser qualquer coisa, desde que todas as posições sejam relativas a algum ponto de origem.
  • A velocidade de todos os pontos deve ser a mesma para garantir um movimento uniforme, mas ter velocidades diferentes pode fornecer resultados interessantes.
  • Se o movimento parecer errado, verifique o ponto de origem. Se não estiver exatamente no meio da forma, a forma poderá se expandir de uma maneira estranha.
Charanor
fonte
9
Quero apenas salientar que a velocidade de cada partícula deve ser proporcional à distância da origem no primeiro quadro (o que significa calcular apenas uma vez, não por quadro). Como alternativa, você simplesmente não pode normalizar o vetor de direção. Se você não fizer isso, a forma não irá dimensionar linearmente, mas sim avançar para se tornar um círculo (se todas as velocidades são as mesmas.)
Aaron
@ Charanor Muito obrigado pela explicação. Na verdade, eu estudei matemática discreta na universidade, mas já faz um bom tempo. Vou tentar implementar algo hoje.
lepton
2

Portanto, existe um projeto chamado BulletML, que é uma linguagem de marcação para criar padrões complexos de partículas / marcadores. Você quase certamente precisará portar o código para o seu próprio idioma, mas ele pode fazer coisas realmente incríveis.

Por exemplo, esse chefe foi feito em uma extensão (fortemente modificada) do BulletML for Unity3D (o autor desse padrão enviou esse vídeo e Misery é insano, além de bom 1 ). É a variação mais difícil desse inimigo e mostra o que o BulletML é capaz de fazer muito bem (e confira alguns dos outros chefes de Misery também, como o Wallmaster ).

Ou posso mostrar este exemplo, que é um padrão que escrevi enquanto trabalhava em uma expansão para The Last Federation , usando uma revisão mais antiga do sistema que é menos amigável ao mod e usa apenas variáveis ​​AZ de um único caractere:

Exemplo de padrão de marcador

As balas verdes que produzem esses anéis são geradas por uma bala-mãe que gira em alta velocidade, mas elas não têm movimento. Eles causam dano massivo, mantendo o jogador a uma distância maior, restringindo-o a armas com menos danos e permitindo que defensores móveis assediem o jogador (o jogador venceu se a estrutura imóvel no meio fosse destruída).

Aqui está uma parte da sintaxe XML que cria essas bolhas:

<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>

Você pode ver alguns dos tiros roxos de "onda de gravidade" na captura de tela, que viajam quase instantaneamente da fonte (que gira) até a borda da bolha, e então gera o tiro verde de "barra curvada", que fica lá por 12 segundos antes desaparecendo. Os tiros azuis e amarelos que eu omiti, pois são muito mais complicados.

Um dos outros padrões (um projétil de artilharia ) na expansão foi realmente escrito por Misery, embora eu tenha feito algumas modificações. Inicialmente, é um tiro penetrante de baixo dano que voa a longo alcance e depois explode em uma enorme queima de fogos, causando muitos danos. Seu alcance máximo era muito maior do que o jogador poderia alcançar, forçando essencialmente o jogador a se envolver em curto alcance, o que foi vantajoso para os outros tipos de unidades de NPC devido ao efeito de espingarda (mais balas agrupadas em uma pequena zona).

O BulletML é fácil de trabalhar, geralmente, e pode fazer coisas incríveis. Os marcadores podem mudar de direção, mudar de velocidade, gerar outros padrões, morrer cedo, repetir a coleta de comandos em um loop, usar atrasos, alterar a imagem de sprite de bala, seguir o pai (ou não) ... E qualquer coisa que ele não suporte escreva nele.

Definitivamente, eu recomendaria se você estivesse fazendo um jogo de tiro sério. Você ainda precisaria calcular a matemática das coordenadas para obter as formas desejadas, como Charanor fala em sua resposta, mas um mecanismo de bala como o BulletML lhe dará muito mais flexibilidade que você gastará mais tempo projetando novos padrões do que descobrir como codificá-los.

  1. Para explicar o quão boa é Miséria, esses vídeos são contra chefes de chão com equipamentos de partida : sem módulos, sem consumíveis e com o atirador básico de ervilhas. E xe leva apenas um golpe, apesar da natureza prolongada da luta. Ok, 9 acertos contra o Centrifugador (que não aparece até o terceiro andar após o jogador definitivamente ter atualizações, resultando em pelo menos o dobro de dano comparativamente).
Draco18s não confia mais no SE
fonte
Obrigado, eu estava vagamente ciente do BulletML, já existe há algum tempo, mas definitivamente é um exagero para o meu jogo simples, que ocasionalmente se interessa por balas e não é um atirador de balas por si só.
lepton
@lepton Totalmente compreensível. Essa é uma decisão que você deve tomar, mas a resposta pode ser a "melhor" para outra pessoa. Eu sei que depois de trabalhar no TLF e começar a construir meu próprio atirador, eu queria usá-lo apenas por causa de quão poderoso e fácil era trabalhar com ele. :)
Draco18s não confia mais no SE 28/07
1

Conforme indicado por Charanor, você pode usar uma matriz de pontos para definir sua forma e, em seguida, atualizar sua posição ao longo do tempo. Abaixo está um exemplo prático de como implementar uma forma de estrela ou uma forma personalizada usando pontos:

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
fonte
Muito obrigado pelo exemplo, vou ver esta tarde para ver se consigo colocá-lo em funcionamento.
lepton