Comment puis-je créer des formes de motifs de puces en expansion?

12

Je veux créer une série de motifs de balles en expansion qui forment des formes telles que des carrés, des triangles, etc. Un exemple de ce que je recherche peut être vu dans la vidéo suivante où lorsque les étoiles sont collectées, les balles explosent sous la forme d'un étoile en expansion:

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

lepton
la source
2
Oh, c'est une bonne question. Je n'ai pas de réponses spécifiques, mais j'imagine que vous pourriez utiliser un objet 2D, soit une image-objet ou une forme simple, et faire apparaître les balles le long du bord. Bien sûr, l'astuce serait de leur donner une vitesse appropriée, à la fois vers l'extérieur dans leur forme et si vous faites un scroller comme celui-ci, pour les faire avancer avec l'écran. Très intéressé de voir toutes les réponses ici.
Jesse Williams
1
Un nom populaire pour ce type d'effet est «effets de particules». Ce terme de recherche peut vous aider!
Cort Ammon
1
Merci, j'utilise les effets de particules dans XNA et libGDX depuis un certain temps maintenant, mais je ne savais pas comment gérer ce style d'effet particulier.
lepton
1
Il y a une autre réponse à cela qui est incroyablement puissante, mais très complexe à programmer. Et il faut un vrai clavier pour taper. Marquer ceci pour une explication ultérieure.
Draco18s ne fait plus confiance au SE
Intéressant - je n'aurais jamais opté pour des effets de particules pour quelque chose comme ça. Ou peut-être que c'est juste une délimitation dans Unity. Bien que les effets de particules puissent avoir des collisionneurs (endommageant ainsi un objet), il semble que cela créerait beaucoup plus de frais généraux que la simple instanciation de copies d'objets.
Jesse Williams

Réponses:

11

La façon la plus simple de le faire serait de concevoir d'abord la forme, puis de calculer le mouvement des particules. Dans cette réponse, je construirai un carré, mais cela s'applique à n'importe quelle forme.

Commencez par concevoir votre forme comme des positions relatives autour d'un point d'origine.

carré

Vous devez maintenant calculer la façon dont la forme va se développer. Pour ce faire, nous calculons simplement le vecteur pointant de originà tous pointen soustrayant la originposition de la nôtre pointpuis en normalisant le vecteur. vector = normalize(point.x - origin.x, point.y - origin.y).

vecteur

Nous pouvons maintenant calculer la position des points à tout moment dans le temps en utilisant ce vecteur. Vous calculez la position suivante des points en faisant point.position += point.vector * point.velocity. Exemple de pseudocode utilisant notre point précédent:

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

Faire cela déplacera tous les points vers l'extérieur à 3 unités chaque image.


Remarques

  • Vous pouvez lire ici quelques mathématiques vectorielles simples .
  • La position peut être n'importe quoi tant que toutes les positions sont relatives à un point d'origine.
  • La vitesse de tous les points doit être la même pour assurer un mouvement uniforme, mais avoir des vitesses différentes peut vous donner des résultats intéressants.
  • Si le mouvement semble éteint, vous devez vérifier le point d'origine. Si ce n'est pas exactement au milieu de la forme, la forme peut s'étendre de manière étrange.
Charanor
la source
9
Je veux juste souligner que la vitesse de chaque particule doit être proportionnelle à la distance depuis l'origine sur la première image (c'est-à-dire ne calculer qu'une fois, pas par image). Alternativement, vous ne pouvez tout simplement pas normaliser le vecteur de direction. Si vous ne le faites pas, la forme ne sera pas mise à l'échelle linéairement, mais évoluera plutôt pour devenir un cercle (si toutes les vitesses sont les mêmes.)
Aaron
@Charanor Merci beaucoup pour l'explication. J'ai étudié des mathématiques discrètes à l'université mais c'était il y a un bon moment maintenant. Je vais essayer d'implémenter quelque chose aujourd'hui.
lepton
2

Donc, il y a ce projet appelé BulletML qui est un langage de balisage pour créer des motifs de particules / puces complexes. Vous aurez presque certainement besoin de porter le code dans votre propre langue, mais cela peut faire des choses vraiment incroyables.

Par exemple, ce boss a été fait dans une extension (fortement modifiée) de BulletML pour Unity3D (l'auteur de ce modèle a téléchargé cette vidéo et Misery est fou, ainsi que la bonne 1 ). C'est la variation la plus difficile de cet ennemi et cela montre ce que BulletML est capable de faire assez bien (et consultez également certains des autres patrons de Misery, comme Wallmaster ).

Ou je peux montrer cet exemple, qui est un modèle que j'ai écrit en travaillant sur une expansion pour The Last Federation , en utilisant une ancienne version du système qui est moins adaptée aux mods et utilise uniquement des variables AZ à un seul caractère:

Exemple de modèle de puce

Les balles vertes qui font ces anneaux sont générées à partir d'une balle parent qui tourne à grande vitesse, mais elles-mêmes n'ont aucun mouvement. Ils infligent des dégâts massifs, gardant le joueur à une plus longue portée, les limitant à des armes à dégâts plus faibles et permettant aux défenseurs mobiles de harceler le joueur (le joueur a gagné si la structure immobile au milieu était détruite).

Voici une partie de la syntaxe XML qui crée ces bulles:

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

Vous pouvez voir quelques-uns des plans violets de "l'onde de gravité" dans la capture d'écran, qui se déplacent presque instantanément de la source (qui tourne) jusqu'au bord de la bulle, après quoi il déclenche le plan vert "barre incurvée", qui reste là pendant 12 secondes avant disparaître. Les plans bleus et jaunes que j'ai omis, car ils sont beaucoup plus compliqués.

L'un des autres motifs (un obus d'artillerie ) de l'expansion a été écrit par Misery, bien que j'y ai apporté quelques modifications. Au départ, c'est un tir pénétrant à faible dégâts qui vole à longue portée et explose ensuite en un énorme feu d'artifice, infligeant des tonnes de dégâts. Sa portée maximale était beaucoup plus élevée que ce que le joueur pouvait atteindre, forçant essentiellement le joueur à s'engager à courte portée, ce qui était avantageux pour les autres types d'unités PNJ en raison de l'effet de fusil de chasse (plus de balles regroupées dans une petite zone).

BulletML est facile à utiliser en général et peut faire des choses incroyables. Les balles peuvent changer de direction, changer de vitesse, engendrer d'autres modèles, mourir tôt, répéter la collecte de commandes dans une boucle, utiliser des retards, changer l'image de sprite de balle, suivre leur parent (ou non) ... Et tout ce que cela ne prend pas en charge, vous pourriez écrire dedans.

Je le recommanderais certainement si vous faites un jeu de tir sérieux. Vous auriez encore besoin de calculer les coordonnées pour obtenir les formes souhaitées, comme le dit Charanor dans sa réponse, mais un moteur de puces comme BulletML vous donnera tellement plus de flexibilité que vous passerez plus de temps à concevoir de nouveaux modèles qu'à trouver comment les coder.

  1. Pour expliquer à quel point Misery est bon, ces vidéos sont contre les boss du sol avec l' équipement de départ : pas de modules, pas de consommables et le jeu de tir de pois de base. Et xe ne prend qu'un coup malgré la nature allongée du combat. Ok, 9 coups contre Centrifuge (qui n'apparaîtra qu'au troisième étage après que le joueur aura certainement des améliorations entraînant au moins deux fois plus de dégâts comparativement).
Draco18s ne fait plus confiance à SE
la source
Merci, j'étais vaguement au courant de BulletML, car il existe depuis un certain temps, mais c'est certainement exagéré pour mon jeu simple, qui ne se mêle qu'occasionnellement au bullet-hell, et n'est pas un tireur de bullet-hell en soi.
lepton
@lepton Totalement compréhensible. C'est une décision que vous devez prendre, mais la réponse peut être la "meilleure" pour quelqu'un d'autre. Je sais qu'après avoir travaillé sur TLF et commencé à construire mon propre jeu de tir, je voulais l'utiliser juste à cause de sa puissance et de sa facilité de travail. :)
Draco18s ne fait plus confiance au SE
1

Comme l'a souligné Charanor, vous pouvez utiliser un tableau de points pour définir votre forme, puis mettre à jour leur position au fil du temps. Vous trouverez ci-dessous un exemple pratique de mise en œuvre d'une forme d'étoile ou d'une forme personnalisée à l'aide de points:

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
la source
Un grand merci pour l'exemple, je vais le vérifier cet après-midi pour voir si je peux le mettre en service.
lepton