[DB] Un jeu 2D façon Painter

Questions techniques liées à l’utilisation d'Unity (et n'entrant pas dans le cadre d'une des sections suivantes)
JollyStone
Messages : 33
Inscription : 03 Avr 2019 15:09
Localisation : Liège (B)
Contact :

[DB] Un jeu 2D façon Painter

Message par JollyStone » 15 Juin 2019 12:36

Bonjour,

Je cherche à recréer un petit jeu 2D dans l’idée de « Painter » (si ma mémoire est bonne), un jeu des années ’80 que l’on trouvait sur les bornes arcade dans les bistrots.

En voici le principe, dans les grandes lignes :
Le joueur (représenté par un point dans l’exemple ci-dessous) se déplace en permanence et à vitesse constante le long de la ligne représentant le cadre de la scène. Il peut être contrôlé à l’aide du clavier pour quitter son trajet perpendiculairement à la ligne qu’il suit. Chaque déplacement en-dehors du trajet existant crée une nouvelle ligne. Lorsque la nouvelle ligne rejoint un tracé existant, un nouvelle surface est ajoutée.
Le but étant de remplir au maximum l’espace de la scène pour piéger l’ennemi (ou remplir l’espace de jeu avec le pourcentage de surface le plus élevé).
Cet ennemi se déplace librement dans l’espace vide de la scène, rebondissant contre les tracés qu’il rencontre. S’il touche un tracé en cous de création (donc incomplet et n’ayant pas encore créé une nouvelle surface), le joueur est détruit.

un petit visuel pour mieux comprendre :
Image

Je pense utiliser un TrailRenderer sur le joueur. Mais…
• Comment créer une surface lorsque la ligne en construction rencontre une ligne existante ?
• Comment faire pour que le joueur se déplace uniquement sur le contour de ces surfaces (lignes vertes dans l’exemple) ?
• Comment déterminer qu’une ligne est en construction et que l’ennemi peut la détruire (ligne noire dans l’exemple) ?
• Comment…
• Comment…

Bref, quelles sont les pistes à suivre pour créer cela ?

Bon weekend à vous !

Avatar de l’utilisateur
Max
Messages : 8764
Inscription : 30 Juil 2011 13:57
Contact :

Re: [DB] Un jeu 2D façon Painter

Message par Max » 15 Juin 2019 17:29

Bonjour,

Perso, plutôt que les TrailRenderers, je passerais par les LineRenderers.
Après, concernant les grands principes de réalisation, je ne sais pas.
Ton plan serait considéré comme un système cartésien orthogonal à deux dimensions, avec un ensemble de droites et de points. L'approche mathématique pourrait être une des solutions ?
Image
Pas d'aide par MP, le forum est là pour ça.
En cas de doute sur les bonnes pratiques à adopter sur le forum, consulter la Charte et sa FAQ

JollyStone
Messages : 33
Inscription : 03 Avr 2019 15:09
Localisation : Liège (B)
Contact :

Re: [DB] Un jeu 2D façon Painter

Message par JollyStone » 17 Juin 2019 10:15

Merci Max !

En fait, je pense au TrailRenderer pour afficher le tracé en construction. Et comme tu le suggères, un LineRenderer pour les tracés complétés. Je suppose qu’il sera possible de remplacer le premier par le second une fois que le tracé est complété, donc une fois que le Player a rejoint un tracé existant ou l’un des bords de l’espace de jeu.

Un système cartésien orthogonal… Cela veux dire qu’il va falloir fonctionner avec des coordonnées d’espace 2D dans un tableau ?
Donc, la position du Player est récupérée en permanence et lorsqu’il quitte un tracé existant, sa position devient le premier point du LineRenderer (tracé en construction) et un nouveau point est défini à chaque changement de direction, le point final étant l’intersection entre le joueur et le tracé existant qu’il rencontre.

La question qui me turlupine en ce moment est : comment faire pour que le Player se déplace en suivant les tracés, tant que le joueur n’appuie pas sur une touche pour le faire entamer un nouveau tracé. Comment déterminer cette position ?

Tout ça me semble compliqué et reste assez flou dans mon esprit.

Avatar de l’utilisateur
Max
Messages : 8764
Inscription : 30 Juil 2011 13:57
Contact :

Re: [DB] Un jeu 2D façon Painter

Message par Max » 17 Juin 2019 17:51

JollyStone a écrit :
17 Juin 2019 10:15
En fait, je pense au TrailRenderer pour afficher le tracé en construction. Et comme tu le suggères, un LineRenderer pour les tracés complétés. Je suppose qu’il sera possible de remplacer le premier par le second une fois que le tracé est complété, donc une fois que le Player a rejoint un tracé existant ou l’un des bords de l’espace de jeu.
Oui, en effet techniquement cela ne pose pas de problème de la jouer comme ça.
JollyStone a écrit :
17 Juin 2019 10:15
La question qui me turlupine en ce moment est : comment faire pour que le Player se déplace en suivant les tracés, tant que le joueur n’appuie pas sur une touche pour le faire entamer un nouveau tracé. Comment déterminer cette position ?
La base est que ton Player doit se déplacer sur des droites, dont tu connais les deux points qui les définissent (les deux extrémités dans ce cas). Abordable surtout avec des droites qui sont soit verticales soit horizontales. Idem pour les fonctions permettant de savoir si un point est sur une droite, et quand il y a intersection entre deux droites.
Ça c'est l'approche disons mathématique. Elle a l'avantage d'être indépendant du rendu.

Après tu as l'approche graphique, à l'ancienne je dirais. A coup de SetPixel et GetPixel, sur une surface de couleur neutre. Ton Player se déplace en ligne droite (V ou H) et fait un SetPixel à chaque pas. Très vite tu as visuellement une ligne qui se dessine d'une couleur choisie. En même temps, avant d'attaquer la position suivante, tu testes si le pixel est d'une autre couleur que la couleur neutre. Si c'est le cas, alors c'est qu'il a rencontré une autre ligne (ou une zone fermée). Là tu adapte le comportement du Player (changement de direction, fermer une zone, etc..). Et ainsi de suite.

C'est pas évident en fait. Un jeu qui parait simple de prime abord, mais qui est plus retors qu'il n'y parait en terme de conception ;)
Image
Pas d'aide par MP, le forum est là pour ça.
En cas de doute sur les bonnes pratiques à adopter sur le forum, consulter la Charte et sa FAQ

JollyStone
Messages : 33
Inscription : 03 Avr 2019 15:09
Localisation : Liège (B)
Contact :

Re: [DB] Un jeu 2D façon Painter

Message par JollyStone » 28 Juin 2019 11:34

En effet, plus j'y réfléchi et moins je trouve le chemin à suivre. C'est peut-être trop complexe pour le débutant que je suis.

La première méthode que tu suggères me semble la plus intéressante, sans doute moins gourmande en ressources.

Donc, le processus ressemblerait à ceci :
• Initialiser les quatre angles (coordonnées X et Y) et tracer le LineRenderer pour constituer le cadre au lancement de la partie.

• Placer le Player sur le point supérieur gauche et le mettre en mouvement dans le sens horaire.

• Au premier changement de direction (touche activée par le joueur), capter la position du Player qui sera le point d’origine d’un nouveau tracé. Ensuite, à chaque changement de direction, la position actuelle représente le point suivant, et ainsi de suite jusqu’à la rencontre d’un tracé existant, ce point étant le dernier du nouveau tracé.

Mes réflexions m'amènent à un tas de questions… En voici quelques unes :
• De manière générale, comment construire / organiser la mécanique du jeu (scripts, GameObjects, LineRenderer, …) ?

• Comment fonctionner avec le(s) tableau(x) qui sont sensés se compléter au fur et à mesure que le joueur construits les surfaces ?

• Comment déterminer au fur et à mesure le point du tracé vers lequel le Player doit se déplacer (tant qu’un changement de direction n’est pas effectué par le joueur) ?

• Comment désactiver les points par lesquels le Player ne doit plus passer lorsqu’ils ne sont plus accessibles (ne font plus partie du contour intérieur de l’espace de jeu) ?

• Comment transformer les LineRenderer en surface (mesh ?) lorsque tracé est compléter ?

Je cherche, je teste, je supprime, je recommence…
C'est sûr, j'ai besoin d'un coup de paluche pour trouver comment procéder.

Bon weekend !

EmileF
Messages : 673
Inscription : 18 Mars 2017 19:39

Re: [DB] Un jeu 2D façon Painter

Message par EmileF » 28 Juin 2019 16:03

salut, juste une petite question

La précision de résolution de ton plan, c'est le pixel, ou penses-tu utiliser une grille de 5x5 ou 10x10 pixels par exemple pour que le chemin de ton player soit plus visible?

Si c'est le pixel tu peux utiliser une texture avec get et setPixel, ça me paraît plus confortable à utiliser.
Avec des résolutions plus grande tu pourrais créer une grille de quad, mais ça me parait plus difficile à gérer.

J'ai du mal à voir comment utiliser les LineRenderer pour faire ce que tu veux faire, surtout quand il s'agira de colorer l’intérieur de tes rectangles.
La différence entre l'intelligence et la stupidité est que l'intelligence est limitée.

EmileF
Messages : 673
Inscription : 18 Mars 2017 19:39

Re: [DB] Un jeu 2D façon Painter

Message par EmileF » 28 Juin 2019 22:14

Tiens, ton projet a titiller ma curiosité,

Voilà ce que j'ai pondu,

Dans ta scène tu mets un quad correspondant à la taille de ta grille.
et tu lui mets ce script.

Le player se déplace avec les flèches du clavier.

Je ne connais pas les règles de ton jeu

C'est loin d'être parfait, mais c'est un début

j'ai commenter le script pour essayer d'être plus clair

J'espère que ça pourra t'aider

Code : Tout sélectionner

using System.Collections.Generic;
using UnityEngine;

public class Grille : MonoBehaviour
{
    //Les directions possibles de déplacement du player
    enum Direction
    {
        right,
        down,
        left,
        up
    }

    List<Color> Colors = new List<Color>() {    Color.white,    //le fond de la grille
                                                Color.blue,     //le player
                                                Color.green,    //les bords
                                                Color.black,    //le chemin du player
                                                Color.red,      //les bords dans les zones colorés
                                                Color.yellow }; //les zones colorées

    Vector2Int posStart;//la position de départ
    int y = 255;        //position verticale du player au départ
    int x = 0;          //position horizontale du player au départ
    Texture2D texture;  //la texture à colorer

    int[,] grille;      //la grille qui contiendra les index des couleurs pour la texture

    Direction direction;//la direction à appliquer pour le mouvement du player
    Vector2Int Mini;    //le coin bas gauche du rectangle à colorer
    Vector2Int Maxi;    //le coin haut droit du rectangle à colorer
    List<int> exColor;  //les index des couleurs sous le player
    Vector2Int exPos;   //la dermière position du player
    bool move;          //si true player en mouvement
    bool stop;          //true quand le player est arrêté dans sa course


    void Start()
    {
        grille = new int[256, 256];         //la grille à la taille de la texture
        texture = new Texture2D(256, 256);  //la texture même taille que la grille
        GetComponent<Renderer>().material.mainTexture = texture; //appliquer la texture au matérial

        for (int i = 0; i < 256; i++)       //Tracer les bords
        {
            grille[i, 0] = 2;
            grille[i, 255] = 2;
            grille[0, i] = 2;
            grille[255, i] = 2;
        }
        AfficheTexture();                   //appliquer la modification à la texture
        exColor = new List<int>() { 0, 0, 0, 0, 0 }; //les couleurs de la grille sous le player
        posStart = new Vector2Int(0, 255);  //la position de départ du player
        grille[posStart.x, posStart.y] = 3; //appliquer la couleur du player dans la grille
        Mini = new Vector2Int(255, 255);    //les valeurs du mini au départ
    }

    private void Update()
    {
        ChargeInput();

        if (move)
        {
            MovePlayer();
            if (stop)
            {
                StoppePlayer();
            }
        }
        AfficheTexture();
    }

    //lit les flêches du clavier pour donner la direction au player
    private void ChargeInput()
    {
        if (Input.GetKey(KeyCode.RightArrow))
        {
            direction = Direction.right;
            move = true;
        }
        else if (Input.GetKey(KeyCode.DownArrow))
        {
            direction = Direction.down;
            move = true;
        }
        else if (Input.GetKey(KeyCode.LeftArrow))
        {
            direction = Direction.left;
            move = true;
        }
        else if (Input.GetKey(KeyCode.UpArrow))
        {
            direction = Direction.up;
            move = true;
        }
    }

    //Déplace le player tant que possible
    void MovePlayer()
    {
        EffacePlayer();
        if (direction == Direction.right && x < 255)
        {
            x++;
        }
        if (direction == Direction.down && y > 0)
        {
            y--;
        }
        if (direction == Direction.left && x > 0)
        {
            x--;
        }
        if (direction == Direction.up && y < 255)
        {
            y++;
        }

        stop = false;
        //A ce stade x et y contiennent la nouvelle position du player
        //si le grille est vide() on continue
        //ou si la grille est verte, si c'est le bord, on continue
        //sinon qu'elle que soit la couleur on arrête
        if (grille[x, y] == 0 || (grille[x, y] == 2 && (x == 0 || x == 255 || y == 0 || y == 255)))
        {
            //on met à jour les valeurs mii et maxi du rectangle à colorer
            if (x < Mini.x) Mini.x = x;
            if (y < Mini.y) Mini.y = y;
            if (x > Maxi.x) Maxi.x = x;
            if (y > Maxi.y) Maxi.y = y;

            //si on arrive vers un bord on arrête
            if (direction == Direction.left && x == 0) stop = true;
            if (direction == Direction.right && x == 255) stop = true;
            if (direction == Direction.up && y == 255) stop = true;
            if (direction == Direction.down && y == 0) stop = true;

            //on affiche le player dans sa nouvelle position
            AffichePlayer(x, y);
            //et on colore la case courante en noir
            grille[x, y] = 3;
            exColor[2] = 3;
        }
        else
        {
            stop = true;
        }
    }

    //quand le player doit s'arrêter
    void StoppePlayer()
    {
        //on arrête le mouvement
        move = false;
        stop = false;

        //on parcours le rectangle du déplacement du player
        for (int l = Mini.y; l < Maxi.y + 1; l++)
        {
            for (int c = Mini.x; c < Maxi.x + 1; c++)
            {
                if (grille[c, l] == 0) //si la case est vide, on la peint en jaune
                    grille[c, l] = 5;
                else if (grille[l, c] == 3) // si la case est moire, on la peint en vert
                    grille[c, l] = 2;
                else if (grille[l, c] == 2) // si la case est verte, on la peint en rouge
                    grille[c, l] = 4;
            }
        }

        //on réinitialise la position du player et le rectangle
        posStart = new Vector2Int(x, y);
        Mini = new Vector2Int(255, 255);
        Maxi = new Vector2Int(0, 0);
        AffichePlayer(x, y);

    }
    
    void EffacePlayer()
    {
        //le player occupe 5 cases en croix, on réinitialise les couleurs de la grille
        for (int i = 0; i < 5; i++)
        {
            int xx = exPos.x;
            int yy = exPos.y;
            if (i == 0) yy++;
            if (i == 1) xx--;
            if (i == 3) xx++;
            if (i == 4) yy--;
            if (yy >= 0 && yy <= 255 && xx >= 0 && xx <= 255)
            {
                if (exColor[i] > -1)
                    grille[xx, yy] = exColor[i];
            }
        }
    }

    void AffichePlayer(int x, int y)
    {
        //on colore la crois occupée par le player en bleu, 
        //mais on récupère avent la couleur de la case dans exColor
        //et sa position dans exPos
        exPos = new Vector2Int(x, y);
        for (int i = 0; i < 5; i++)
        {
            int xx = x;
            int yy = y;
            if (i == 0) yy++;
            if (i == 1) xx--;
            if (i == 3) xx++;
            if (i == 4) yy--;
            if (yy >= 0 && yy <= 255 && xx >= 0 && xx <= 255)
            {
                exColor[i] = grille[xx, yy];
                grille[xx, yy] = 1;
            }
        }
    }

    private void AfficheTexture()
    {
        //parcours la grille et applique au pixels de la texture 
        //les couleurs dont l'index est dans la grille
        for (int x = 0; x < 256; x++)
        {
            for (int y = 0; y < 256; y++)
            {
                texture.SetPixel(x, y, Colors[grille[x, y]]);
            }
        }
        texture.Apply();
    }

}

La différence entre l'intelligence et la stupidité est que l'intelligence est limitée.

Avatar de l’utilisateur
Max
Messages : 8764
Inscription : 30 Juil 2011 13:57
Contact :

Re: [DB] Un jeu 2D façon Painter

Message par Max » 29 Juin 2019 10:17

Hello,

Tu t'es éclaté à ce que je vois Emile :mrgreen:
En tous les cas merci pour @JollyStone, cela constitue une vrai base de travail :super:
Image
Pas d'aide par MP, le forum est là pour ça.
En cas de doute sur les bonnes pratiques à adopter sur le forum, consulter la Charte et sa FAQ

EmileF
Messages : 673
Inscription : 18 Mars 2017 19:39

Re: [DB] Un jeu 2D façon Painter

Message par EmileF » 29 Juin 2019 17:32

Houa, :super: , pour moi, cela équivaut à une véritable félicitation de ta part.

merci Max
La différence entre l'intelligence et la stupidité est que l'intelligence est limitée.

Avatar de l’utilisateur
Alesk
Messages : 2303
Inscription : 13 Mars 2012 09:09
Localisation : Bordeaux - France
Contact :

Re: [DB] Un jeu 2D façon Painter

Message par Alesk » 29 Juin 2019 19:18

Yop !

Je me suis déjà penché sur ce type de jeu il y a quelques années.

L'approche mathématique est la plus optimale.

Il faut bosser avec une liste de croisements et une liste de segments (chaque segment étant identifié par ses extrémités, donc deux croisements).
Chaque croisement est une instance d'une classe ayant des références vers les 4 autres croisements adjacents (donc qui sont reliés par des segments du tracé)
Quand le joueur part du milieu d'un segment afin de débuter un nouveau tracé, s'il parvient à terminer ce tracé, un nouveau croisement est créé à la position du point de départ sur le segment initial, et celui-ci est donc coupé en deux segments.

Une fois que le tracé est fermé, il faut le découper mathématiquement la surface qu'il forme en plusieurs rectangles, afin de facilement calculer la surface totale que cela représente.
Il y a une subtilité ici, car à chaque nouveau tracé, on la le choix entre deux surfaces : celle qui contient l'ennemi et celle qui est vide (et qui doit donc être remplie)
Je ne me souviens plus comment j'avais solutionné ça, mais là le plus simple est de diviser les deux zones en rectangles (et identifiant bien quel rectangle appartient à quelle zone) et dès qu'on trouve qu'un rectangle contient la position de l'ennemi, c'est que ce rectangle et tous ceux dans la même liste correspondent à la zone à laisser "libre"

Quand tu as pu déterminer quelle est la zone libre, tu peux en déduire quels sont les segments qui forment sont contour... et donc les marquer comme étant ceux qui définissent le parcours autorisé.

Voilà en gros ce dont je me souviens ;)

Répondre

Revenir vers « Unity le logiciel »