Gestion des text-maps dans un tableau 2D à peindre sur HTML5 Canvas


46

Donc, je fais un jeu de rôle HTML5 juste pour le fun. La carte est une <canvas>(largeur de 512px, hauteur de 352px | 16 tuiles, 11 tuiles de haut en bas). Je veux savoir s’il existe un moyen plus efficace de peindre le <canvas>.

Voici comment je l'ai maintenant.

Comment les tuiles sont chargées et peintes sur la carte

La carte est peinte par des carreaux (32x32) utilisant la Image()pièce. Les fichiers image sont chargés par une simple forboucle et placés dans un tableau appelé tiles[]à être peint en utilisant drawImage().

D'abord, nous chargeons les carreaux ...

entrez la description de l'image ici

et voici comment cela se fait:

// SET UP THE & DRAW THE MAP TILES
tiles = [];
var loadedImagesCount = 0;
for (x = 0; x <= NUM_OF_TILES; x++) {
  var imageObj = new Image(); // new instance for each image
  imageObj.src = "js/tiles/t" + x + ".png";
  imageObj.onload = function () {
    console.log("Added tile ... " + loadedImagesCount);
    loadedImagesCount++;
    if (loadedImagesCount == NUM_OF_TILES) {
      // Onces all tiles are loaded ...
      // We paint the map
      for (y = 0; y <= 15; y++) {
        for (x = 0; x <= 10; x++) {
          theX = x * 32;
          theY = y * 32;
          context.drawImage(tiles[5], theY, theX, 32, 32);
        }
      }
    }
  };
  tiles.push(imageObj);
}

Naturellement, lorsqu'un joueur commence une partie, il charge la carte qu'il a laissée en dernier. Mais pour ici, c'est une carte entièrement en herbe.

Pour le moment, les cartes utilisent des tableaux 2D. Voici un exemple de carte.

[[4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 1, 1, 1, 1], 
[1, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 1, 1, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 1, 1, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 13, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 13, 11, 11, 11, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 1, 1, 1, 1, 1, 1, 1, 13, 13, 13, 13, 13, 1], 
[1, 1, 1, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 1, 1, 1]];

Je reçois différentes cartes en utilisant une ifstructure simple . Une fois que le tableau 2d ci-dessus est return, le nombre correspondant dans chaque tableau sera peint en fonction de Image()stocké à l'intérieur tile[]. Ensuite, drawImage()se produira et peindra selon le xet yet le temps par 32peindre sur la bonne x-ycoordonnée.

Comment se produit le changement de carte multiple

Avec mon jeu, les cartes ont cinq choses à garder une trace de: currentID, leftID, rightID, upIDet bottomID.

  • currentID: l'ID actuel de la carte sur laquelle vous vous trouvez.
  • leftID: Quel ID currentIDà charger lorsque vous quittez à gauche de la carte actuelle.
  • rightID: Quel ID currentIDà charger lorsque vous quittez à droite de la carte actuelle.
  • downID: quel ID currentIDà charger lorsque vous quittez en bas de la carte actuelle.
  • upID: quel identifiant currentIDcharger lorsque vous quittez en haut de la carte actuelle.

Quelque chose à noter: Si l' leftID, rightID, upIDou bottomIDne sont pas spécifiques, ce qui signifie qu'ils sont 0. Cela signifie qu'ils ne peuvent pas quitter ce côté de la carte. C'est simplement un blocus invisible.

Ainsi, une fois qu'une personne quitte un côté de la carte, en fonction de l'endroit où elle est sortie ... par exemple, si elle est sortie en bas, bottomIDle numéro de la mapcharge sera chargé et sera ainsi peint sur la carte.

Voici un fichier .GIF représentatif pour vous aider à mieux visualiser:

entrez la description de l'image ici

Comme vous pouvez le constater, tôt ou tard, avec de nombreuses cartes, je traiterai de nombreux identifiants. Et cela peut éventuellement devenir un peu déroutant et mouvementé.

L’avantage évident est de charger 176 mosaïques à la fois, d’actualiser une petite toile 512x352 et de gérer une carte à la fois. L'inconvénient est que les identifiants MAP, lorsqu'ils traitent de nombreuses cartes, peuvent parfois être confus.

Ma question

  • Est-ce un moyen efficace de stocker des cartes (compte tenu de l'utilisation de mosaïques) ou existe-t-il un meilleur moyen de gérer les cartes?

Je pensais dans les lignes d'une carte géante. La taille de la carte est grande et ne contient qu'un tableau 2D. La fenêtre d'affichage, cependant, est toujours de 512x352 pixels.

Voici un autre .gif que j'ai créé (pour cette question) pour aider à visualiser:

entrez la description de l'image ici

Désolé si vous ne comprenez pas mon anglais. S'il vous plaît demander à tout ce que vous avez du mal à comprendre. Si tout va bien, j'ai été clair. Merci.


16
L'effort mis dans cette question avec les graphiques et tout mérite un upvote. :)
Tapio

Réponses:


5

Edit: Je viens de voir que ma réponse était basée sur votre code mais je n’ai pas répondu à votre question. J'ai gardé l'ancienne réponse au cas où vous pourriez utiliser cette information.

Edit 2: J'ai corrigé 2 problèmes avec le code original: - L'addition +1 dans le calcul de la fin x et y était par erreur entre crochets, mais doit être ajoutée après la division. - J'ai oublié de valider les valeurs x et y.

Vous pouvez simplement avoir en mémoire la carte actuelle et charger les cartes environnantes. Imaginez un monde constitué d'un tableau 2D de niveaux uniques de la taille 5x5. Le joueur commence dans le champ 1. Les limites supérieure et gauche du niveau se trouvant au bout du monde, elles n'ont pas besoin d'être chargées. Donc, dans ce cas, le niveau 1/1 est actif et les niveaux 1/2 et 2/1 sont chargés ... Si le joueur se déplace maintenant vers la droite, tous les niveaux (à l'exception de celui où vous vous déplacez) sont déchargés et les nouveaux environnements sont affichés. chargé. Le niveau moyen 2/1 est maintenant actif, 1/1, 2/2 et 3/1 sont chargés.

J'espère que cela vous donne une idée de la façon dont cela pourrait être fait. Mais cette approche ne fonctionnera pas très bien, car chaque niveau doit être simulé pendant le jeu. Mais si vous pouvez geler les niveaux inutilisés, cela devrait fonctionner correctement.

Ancienne réponse:

Ce que je fais lors du rendu du niveau (également basé sur les tuiles) est de calculer quels éléments du tableau de tuiles se coupent avec la fenêtre d'affichage, puis je ne restitue que ces tuiles. De cette façon, vous pouvez avoir de grandes cartes, mais il vous suffit de restituer la partie à l'écran.

//Mock values
//camera position x
//viewport.x = 233f;
//camera position y
//viewport.y = 100f;
//viewport.w = 640
//viewport.h = 480
//levelWidth = 10
//levelHeight = 15
//tilesize = 32;

//startX defines the starting index for the x axis.
// 7 = 233 / 32
int startX = viewport.x / tilesize;

//startY defines the starting index
// 3 = 100 / 32
int startY = viewport.y / tilesize;

//End index y
// 28 = (233 + 640) / 32 + 1
int endX = ((viewport.x + viewport.w) / tilesize) + 1;

//End index y
// 19 = (100 + 480) / 32 + 1
int endX = ((viewport.x + viewport.w) / tilesize) + 1;

//Validation
if(startX < 0) startX = 0;
if(startY < 0) startY = 0;
//endX is set to 9 here
if(endX >= levelWidth) endX = levelWidth - 1;
//endX is set to 14 here
if(endY >= levelHeight) endY = levelHeight - 1;

for(int y = startY; y < yEnd; y++)
    for(int x = startX; x < xEnd; x++)
          [...]

Note: Le code ci-dessus n’est pas testé mais il devrait donner une idée de ce qu’il faut faire.

En suivant un exemple de base pour une représentation de fenêtre:

public class Viewport{
    public float x;
    public float y;
    public float w;
    public float h;
}

Une représentation javascript ressemblerait à

<script type="text/javascript">
//Declaration
//Constructor
var Viewport = function(xVal, yVal, wVal, hVal){
    this.x = xVal;
    this.y = yVal;
    this.w = wVal;
    this.h = hVal;
}
//Prototypes
Viewport.prototype.x = null;
Viewport.prototype.y = null;
Viewport.prototype.w = null;
Viewport.prototype.h = null;
Viewport.prototype.toString = function(){
    return ["Position: (", this.x, "/" + this.y + "), Size: (", this.w, "/", this.h, ")"].join("");
}

//Usage
var viewport = new Viewport(23, 111, 640, 480);
//Alerts "Position: (23/111), Size: (640/480)
alert(viewport.toString());
</script>

Si vous prenez mon exemple de code, vous devez simplement remplacer les déclarations int et float par var. Assurez-vous également que vous utilisez Math.floor sur ces calculs, qui sont affectés aux valeurs basées sur int, pour obtenir le même comportement.

Une chose que je considérerais (bien que je ne sois pas sûr que cela compte en javascript) est de mettre toutes les tuiles (ou autant que possible) dans une grande texture au lieu d'utiliser plusieurs fichiers uniques.

Voici un lien expliquant comment faire: http://thiscouldbebetter.wordpress.com/2012/02/25/slicing-an-image-into-tiles-in-javascript/


Juste pour clarifier ... viewport.x serait prédiné comme "531" et viewport.y serait "352" et tilesize = "32". Et autres choses de ce genre..?
Test

Si vous voulez dire que la position de la caméra est 531, alors oui. J'ai ajouté quelques commentaires au code ci-dessus.
Tom van green

J'utilise JavaScript ... Je suis Java est presque identique à cela.
Test

Oui, vous devez juste créer une classe de viewport basée sur js (ou vous pouvez simplement créer 4 variables x, y, w et h). Aussi, vous devez vous assurer d’établir (Math.floor) ces calculs, qui sont affectés aux valeurs int. J'ai ajouté un exemple de code, à quoi ressemblerait la classe viewport. Assurez-vous simplement que vous mettez la déclaration avant l'utilisation.
Tom van green

Juste en disant que vous mettez 640, 480.. mais ça peut marcher avec 512, 352non?
Test

3

Le stockage de cartes en tant que tuiles est utile pour vous permettre de réutiliser des ressources et de re-concevoir la carte. En réalité, cela n'apporte pas beaucoup d'avantages au joueur. De plus, vous re-dessinez chaque carreau à chaque fois. Voici ce que je ferais:

Stockez la carte entière dans un grand tableau. Il ne sert à rien d'avoir des cartes et des sous-cartes, ainsi que potentiellement des sous-sous-cartes (si vous entrez dans des bâtiments, par exemple). Vous chargez le tout quand même et cela garde tout simple et agréable.

Rendre la carte en une fois. Créez un canevas hors écran à partir duquel vous rendez la carte, puis utilisez-le dans votre jeu. Cette page vous montre comment faire avec une simple fonction javascript:

var renderToCanvas = function (width, height, renderFunction) {
  var buffer = document.createElement('canvas');
  buffer.width = width;
  buffer.height = height;
  renderFunction(buffer.getContext('2d'));
  return buffer;
};

Cela suppose que votre carte ne va pas beaucoup changer. Si vous souhaitez rendre les sous-cartes sur place (par exemple, à l'intérieur d'un bâtiment), vous devez également effectuer le rendu sur un canevas, puis le dessiner par-dessus. Voici un exemple de la façon dont vous pouvez utiliser ceci pour rendre votre carte:

var map = renderToCanvas( map_width*32, map_height*32, renderMap );

var MapSizeX = 512;
var MapSizeY = 352;

var draw = function(canvas, mapCentreX, mapCentreY) {
  var context = canvas.getContext('2d');

  var minX = playerX - MapSizeX/2;
  var minY = playerY - MapSizeY/2;

  context.drawImage(map,minX,minY,MapSizeX,MapSizeY,0,0,MapSizeX,MapSizeY);
}

Cela va dessiner une petite partie de la carte centrée autour mapCentreX, mapCentreY. Cela vous permettra de faire défiler en douceur toute la carte, ainsi que le mouvement des sous-tuiles. déplacez mosaïque par mosaïque en tant que choix stylistique, vous pouvez simplement incrémenter par tranches de 32).

Vous pouvez toujours diviser votre carte si vous le souhaitez, ou si votre monde est immense et ne tiendrait pas en mémoire sur vos systèmes cibles (n'oubliez pas que même pour 1 octet par pixel, une carte de 512x352 est de 176 Ko), mais par pré Si vous rendez votre carte comme ceci, vous constaterez un gain de performances important dans la plupart des navigateurs.

Cela vous donne la possibilité de réutiliser des mosaïques à travers le monde sans la peine de modifier une seule grande image, mais vous permet également de l'exécuter rapidement et facilement et de ne prendre en compte qu'une seule "carte" (ou autant de "cartes" que vous le souhaitez). .


2

Dans toutes les implémentations que j'ai vues, les cartes de tuiles sont souvent générées sous la forme d'une seule grande image, puis "découpées" en morceaux plus petits.

https://gamedev.stackexchange.com/a/25035/10055

Ces morceaux ont généralement une puissance de 2 et sont donc parfaitement mémorisés.


1

Un moyen facile de garder une trace des ID serait d’utiliser simplement un autre tableau 2d avec eux: en ne conservant que x, y sur ce "méta tableau", vous pourrez facilement rechercher les autres ID.

Cela étant dit, voici le point de vue de Google sur le jeu 2D en mosaïque (en fait, multijoueur HTML5, mais le moteur de tuiles est également couvert) de leur récent I / O 2012: http://www.youtube.com/watch?v=Prkyd5n0P7k It a également le code source disponible.


1

Votre idée de charger d’abord la carte entière dans un tableau est un moyen efficace, mais il sera facile d’obtenir une injection JavaScript, tout le monde pourra la connaître et l’aventure se poursuivra.

Je travaille aussi sur un jeu de RPG basé sur le Web, la façon dont je charge ma carte est via Ajax à partir d'une page PHP, qui charge la carte à partir de la base de données, où il est indiqué le X, le Y, la position Z et la vlaue. de la tuile, dessinez-le, et enfin le joueur se déplace.

Je travaille toujours dessus pour la rendre plus efficace, mais la carte se charge assez vite pour la conserver ainsi.


1
Puis-je voir une démo?
Test

1

Non , un tableau est le moyen le plus efficace de stocker une carte de tuiles .

Quelques structures peuvent nécessiter un peu moins de mémoire, mais uniquement au prix de coûts beaucoup plus élevés pour le chargement et l’accès aux données.


Ce qui mérite cependant d’être pris en compte, c’est quand vous allez charger la carte suivante. Étant donné que les cartes ne sont pas particulièrement grandes, ce qui prendra le plus de temps est d’attendre que le serveur fournisse la carte. Le chargement réel ne prendra que quelques millisecondes. Mais cette attente peut se faire de manière asynchrone, il n’est pas nécessaire de bloquer le déroulement du jeu. La meilleure chose à faire n'est donc pas de charger les données à la demande, mais avant. Le joueur est sur une carte, chargez maintenant toutes les cartes adjacentes. Le joueur se déplace sur la carte suivante, charge à nouveau toutes les cartes adjacentes, etc., etc. Le joueur ne remarquera même pas que votre jeu se charge en arrière-plan.

De cette façon, vous pouvez également créer l'illusion d'un monde sans frontière en dessinant également les cartes adjacentes. Dans ce cas, vous devez charger non seulement les cartes adjacentes, mais également celles adjacentes à celles-ci.


0

Je ne sais pas pourquoi vous pensez: comme vous pouvez le voir, tôt ou tard, avec de nombreuses cartes, je traiterai avec de nombreux identifiants. Et cela peut éventuellement devenir un peu déroutant et mouvementé.

LeftID sera toujours égal à -1 de currentID, rightid sera toujours à +1 de currentID.

UpID sera toujours - (largeur totale de la carte) de l'ID actuel et downID sera toujours + (largeur totale de la carte) de l'ID actuel, à l'exception de zéro, ce qui signifie que vous avez atteint le bord.

Une seule carte peut être divisée en plusieurs cartes et est enregistrée de manière séquentielle avec mapIDXXXX où XXXX est l'identifiant. De cette façon, il n'y a rien à confondre. Je suis allé sur votre site et il semble, je peux me tromper, que le problème vient de votre éditeur de carte et impose une limitation de taille qui empêche votre automatisation de décomposer une grande carte en plusieurs petites lors de la sauvegarde. Cette limitation est probablement restreindre une solution technique.

J'ai écrit un éditeur de carte Javascript qui défile dans toutes les directions, des tuiles 1000 x 1000 (je ne recommanderais pas cette taille) et qui bouge toujours aussi bien qu'une carte 50 x 50 avec 3 couches incluant le défilement parallaxe. Il enregistre et lit au format JSON. Je vous enverrai une copie si vous dites que cela ne vous dérange pas de recevoir un courriel d'environ 2 meg. C'est censé être sur github mais je n'ai pas de dalle (légale) décente, c'est pourquoi je n'ai pas pris la peine de la mettre là encore.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.