¿Cómo puedo hacer formas de patrón de viñetas en expansión?

12

Quiero hacer una serie de patrones de viñetas en expansión que formen formas como cuadrados, triángulos, etc. Un ejemplo de lo que busco se puede ver en el siguiente video donde cuando se recogen las estrellas, las balas explotan en forma de estrella en expansión:

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

lepton
fuente
2
Oh, esa es una buena pregunta. No tengo respuestas específicas, pero me imagino que podrías usar un objeto 2D, ya sea un sprite o una forma simple, y generar las balas a lo largo del borde. Por supuesto, el truco sería darles la velocidad adecuada, tanto en su forma externa como si está haciendo un desplazamiento como este, para que avancen con la pantalla. Muy interesado en ver las respuestas aquí.
Jesse Williams
1
Un nombre popular para ese tipo de efecto es "efectos de partículas". ¡Ese término de búsqueda puede ayudarte!
Cort Ammon
1
Gracias, he estado usando efectos de partículas en XNA y libGDX durante bastante tiempo, pero no estaba seguro de cómo manejar este estilo particular de efecto.
lepton
1
Hay otra respuesta a esto que es increíblemente poderosa, pero muy compleja de programar. Y es necesario un teclado real para escribir. Marcar esto para una explicación posterior.
Draco18s ya no confía en SE
Interesante: nunca hubiera usado efectos de partículas para algo como esto. O tal vez es solo una delineación en Unity. Si bien los efectos de partículas pueden tener colisionadores (dañando así un objeto), parece que eso crearía mucho más sobrecarga que simplemente instanciar copias de objetos.
Jesse Williams

Respuestas:

11

La forma más fácil de hacer esto sería diseñar primero la forma y luego calcular el movimiento de las partículas. En esta respuesta, construiré un cuadrado, pero esto se aplica a cualquier forma.

Comience diseñando su forma como posiciones relativas alrededor de algún punto de origen.

cuadrado

Ahora necesita calcular cómo se expandirá la forma. Para hacer esto, simplemente calculamos el vector que apunta de a origina cada pointrestando la originposición de nuestra pointposición y luego normalizando el vector. vector = normalize(point.x - origin.x, point.y - origin.y).

vector

Ahora podemos calcular la posición de los puntos en cualquier momento usando este vector. Calcula la siguiente posición de los puntos haciendo point.position += point.vector * point.velocity. Ejemplo de pseudocódigo utilizando nuestro punto 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)

Hacer esto moverá todos los puntos hacia afuera a 3 unidades cada cuadro.


Notas

  • Puedes leer algunas matemáticas vectoriales simples aquí .
  • La posición puede ser cualquier cosa siempre que todas las posiciones sean relativas a algún punto de origen.
  • La velocidad de todos los puntos debe ser la misma para garantizar un movimiento uniforme, pero tener diferentes velocidades puede brindarle resultados interesantes.
  • Si el movimiento parece apagado, debe verificar el punto de origen. Si no está en el medio exacto de la forma, la forma podría expandirse de una manera extraña.
Charanor
fuente
99
Solo quiero señalar que la velocidad de cada partícula debe ser proporcional a la distancia desde el origen en el primer fotograma (es decir, calcular solo una vez, no por fotograma). Alternativamente, simplemente no podría normalizar el vector de dirección. Si no haces esto, la forma no se escalará linealmente, sino que se moverá hacia un círculo (si todas las velocidades son iguales)
Aaron
@Charanor Muchas gracias por la explicación. De hecho, estudié matemáticas discretas en la universidad, pero fue hace bastante tiempo. Voy a tratar de implementar algo hoy.
lepton
2

Entonces, existe este proyecto llamado BulletML, que es un lenguaje de marcado para crear patrones complejos de partículas / viñetas. Seguramente necesitarás portar el código a tu propio idioma, pero puede hacer algunas cosas realmente sorprendentes.

Por ejemplo, este jefe se realizó en una extensión (muy modificada) de BulletML para Unity3D (el autor de ese patrón subió ese video y Misery es una locura, así como buena 1 ). Es la variación más difícil de ese enemigo y muestra lo que BulletML es capaz de hacer bastante bien (y echa un vistazo a algunos de los otros jefes de Misery, como Wallmaster ).

O puedo mostrar este ejemplo, que es un patrón que escribí mientras trabajaba en una expansión para The Last Federation , usando una revisión anterior del sistema que es menos amigable con los mods y usa solo variables AZ de un solo carácter:

Ejemplo de patrón de bala

Las balas verdes que hacen esos anillos allí se generan a partir de una bala principal que gira a alta velocidad, pero no tienen movimiento. Infligen un daño masivo, manteniendo al jugador a un rango más largo, restringiéndolos a armas de menor daño y permitiendo a los defensores móviles acosar al jugador (el jugador gana si la estructura inmóvil en el medio allí fue destruida).

Aquí hay una parte de la sintaxis XML que crea esas burbujas:

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

Puede ver algunas de las tomas púrpuras de "ondas de gravedad" en la captura de pantalla, que viajan casi instantáneamente desde la fuente (que gira) hasta el borde de la burbuja, con lo que genera la toma verde de "barra curva", que se encuentra allí durante 12 segundos antes desesperando Los tiros azules y amarillos que he omitido, ya que son mucho más complicados.

Misery escribió uno de los otros patrones (un proyectil de artillería ) en la expansión, aunque le hice algunas modificaciones. Inicialmente, es un disparo penetrante de bajo daño que vuela a gran distancia y luego explota en una gran exhibición de fuegos artificiales, infligiendo toneladas de daño. Su alcance máximo fue mucho más alto de lo que el jugador pudo lograr, esencialmente forzando al jugador a atacar a corto alcance, lo que fue ventajoso para los otros tipos de unidades NPC debido al efecto de escopeta (más balas agrupadas en una zona pequeña).

BulletML es fácil de trabajar, en general, y puede hacer cosas increíbles. Las viñetas pueden cambiar de dirección, cambiar la velocidad, generar otros patrones, morir temprano, repetir la recopilación de comandos en un bucle, usar retrasos, cambiar la imagen del sprite de bala, seguir a su padre (o no) ... Y cualquier cosa que no sea compatible con usted podría escribir en él

Definitivamente lo recomendaría si estás haciendo un juego serio de disparos. Aún necesitaría resolver las matemáticas de coordenadas para obtener las formas deseadas, como Charanor habla en su respuesta, pero un motor de bala como BulletML le dará mucha más flexibilidad que pasará más tiempo diseñando nuevos patrones que descubriendo cómo codificarlos.

  1. Para explicar cuán buena es Misery, esos videos están en contra de los jefes de piso con equipo de arranque : sin módulos, sin consumibles y el tirador básico de guisantes. Y xe solo recibe un golpe a pesar de la naturaleza alargada de la pelea. Ok, 9 golpes contra Centrifuge (que no aparece hasta el tercer piso después de que el jugador definitivamente tenga mejoras que resulten en al menos el doble de daño comparativamente).
Draco18s ya no confía en SE
fuente
Gracias, era vagamente consciente de BulletML, ya que ha existido por un tiempo, pero definitivamente es excesivo para mi juego simple, que solo ocasionalmente se mete en el infierno de balas, y no es un tirador de infierno de balas en sí.
lepton
@lepton Totalmente comprensible. Es una decisión que debe tomar, pero la respuesta puede ser la "mejor" para otra persona. Sé que después de trabajar en TLF y comenzar a construir mi propio tirador, quería usarlo solo por lo poderoso y fácil que era trabajar con él. :)
Draco18s ya no confía en SE
1

Como lo señaló Charanor, puede usar una variedad de puntos para definir su forma y luego actualizar su posición con el tiempo. A continuación se muestra un ejemplo práctico de cómo implementar una forma de estrella o una forma personalizada utilizando puntos:

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
fuente
Muchas gracias por el ejemplo, voy a revisarlo esta tarde para ver si puedo ponerlo en funcionamiento.
lepton