Lors du chargement d'énormes cartes supplémentaires, la méthode de chargement libère une exception de mémoire lorsqu'une nouvelle instance de tuile de carte est créée. J'aimerais que toute la carte soit traitée au moins sur l'application serveur (et sur le client si possible). Comment dois-je résoudre ce problème?
UPD: la question ici est de savoir comment arrêter le plantage du jeu lorsqu'il y a encore de la mémoire disponible pour l'utiliser. En ce qui concerne la division de la carte en morceaux, c'est une bonne approche, mais pas ce que je veux dans mon cas.
UPD2: Au début, je suis allé attribuer une texture à chaque nouvelle instance de classe de tuiles et c'est ce qui a pris tellement de mémoire (également le temps de chargement). Maintenant, cela prend environ quatre fois moins d'espace. Merci à tout le monde, maintenant je peux exécuter d'énormes cartes sans penser à les décomposer en morceaux pour l'instant.
UPD3: Après avoir réécrit le code pour voir si les tableaux de propriétés de tuiles fonctionnent plus rapidement ou consomment moins de mémoire (que lesdites propriétés dans leurs tuiles respectives en tant que propriétés d'objet), j'ai découvert que non seulement il m'a fallu beaucoup de temps pour essayer cela, mais n'a apporté aucune amélioration des performances et a rendu le jeu terriblement difficile à déboguer.
la source
Réponses:
Cela suggère fortement que vous ne représentez pas correctement vos tuiles, car cela signifierait que chaque tuile a une taille d'environ 80 octets.
Ce que vous devez comprendre, c'est qu'il doit y avoir une séparation entre le concept de gameplay d'une tuile et la tuile visuelle que l'utilisateur voit. Ces deux concepts ne sont pas la même chose .
Prenez Terraria par exemple. Le plus petit monde Terraria occupe 4200x1200 tuiles, soit 5 millions de tuiles. Maintenant, combien de mémoire faut-il pour représenter ce monde?
Eh bien, chaque carreau a une couche de premier plan, une couche d'arrière-plan (les murs d'arrière-plan), une "couche de fils" où vont les fils et une "couche de meubles" où vont les meubles. Combien de mémoire prend chaque tuile? Encore une fois, nous parlons simplement conceptuellement, pas visuellement.
Une tuile de premier plan pourrait facilement être stockée dans un court non signé. Il n'y a pas plus de 65536 types de tuiles de premier plan, il est donc inutile d'utiliser plus de mémoire que cela. Les tuiles d'arrière-plan peuvent facilement être dans un octet non signé, car il existe moins de 256 types différents de tuiles d'arrière-plan. La couche de fil est purement binaire: soit une tuile contient un fil, soit elle n'en a pas. C'est donc un bit par tuile. Et la couche de meubles pourrait à nouveau être un octet non signé, selon le nombre possible de meubles différents.
Taille totale de la mémoire par tuile: 2 octets + 1 octet + 1 bit + 1 octet: 4 octets + 1 bit. Ainsi, la taille totale d'une petite carte Terraria est de 20790000 octets, soit ~ 20 Mo. (Remarque: ces calculs sont basés sur Terraria 1.1. Le jeu s'est beaucoup développé depuis lors, mais même Terraria moderne pourrait tenir dans les 8 octets par emplacement de tuile, soit ~ 40 Mo. Toujours tout à fait tolérable).
Vous ne devriez jamais avoir cette représentation stockée sous forme de tableaux de classes C #. Ils doivent être des tableaux d'entiers ou quelque chose de similaire. La structure AC # fonctionnerait également.
Maintenant, quand vient le temps de dessiner une partie d'une carte (notez l'accent), Terraria doit convertir ces tuiles conceptuelles en tuiles réelles . Chaque tuile doit réellement choisir une image de premier plan, une image d'arrière-plan, une image de meuble en option et avoir une image filaire. C'est là que XNA entre en scène avec ses diverses feuilles de sprites et autres.
Ce que vous devez faire est de convertir la partie visible de votre carte conceptuelle en tuiles de feuille de sprite XNA réelles. Vous ne devriez pas essayer de convertir le tout en une seule fois . Chaque tuile que vous stockez doit simplement être un index indiquant que «Je suis de type tuile X», où X est un entier. Vous utilisez cet index entier pour récupérer le sprite que vous utilisez pour l'afficher. Et vous utilisez les feuilles de sprite de XNA pour rendre cela plus rapide que de simplement dessiner des quads individuels.
Maintenant, la zone visible des tuiles doit être divisée en différents morceaux, de sorte que vous ne construisez pas constamment de feuilles de sprites chaque fois que la caméra se déplace. Donc, vous pourriez avoir des morceaux 64x64 du monde sous forme de feuilles de sprite. Quels que soient les morceaux 64x64 du monde visibles depuis la position actuelle de la caméra du joueur, ce sont les morceaux que vous dessinez. Tous les autres morceaux n'ont même pas de feuilles de sprite; si un morceau tombe de l'écran, vous jetez cette feuille (remarque: vous ne le supprimez pas vraiment; vous le conservez et le spécifiez pour un nouveau morceau qui peut devenir visible plus tard).
Votre serveur n'a pas besoin de connaître ou de se soucier de la représentation visuelle des tuiles. Il lui suffit de se soucier de la représentation conceptuelle. L'utilisateur ajoute une tuile ici, donc il change cet index de tuile.
la source
Divisez le terrain en régions ou en morceaux. Ensuite, ne chargez que les morceaux visibles par les joueurs et déchargez ceux qui ne le sont pas. Vous pouvez y penser comme un tapis roulant, où vous chargez des morceaux à une extrémité et les déchargez à l'autre pendant que le joueur avance. Toujours en avance sur le joueur.
Vous pouvez également utiliser des astuces comme l'instanciation. Où si toutes les tuiles d'un même type n'ont qu'une seule instance en mémoire et sont dessinées plusieurs fois dans tous les emplacements où cela est nécessaire.
Vous pouvez trouver plus d'idées comme celle-ci ici:
Comment l'ont-ils fait: des millions de carreaux à Terraria
«Zonage» des zones sur une grande carte de tuiles, ainsi que des donjons
la source
La réponse de Byte56 est bonne, et j'ai commencé à écrire ceci en tant que commentaire, mais elle est devenue trop longue et contient quelques conseils qui pourraient être plus utiles dans une réponse.
L'approche est tout aussi bonne pour le serveur que pour le client. En fait, c'est probablement plus approprié, étant donné que le serveur sera appelé à se préoccuper de bien plus de domaines qu'un client. Le serveur a une idée différente de ce qui doit être chargé qu'un client (il se soucie de toutes les régions qui intéressent tous les clients connectés), mais il est également capable de gérer un ensemble de régions de travail.
Même si vous pouviez mettre en mémoire une carte aussi gigantesque (et vous ne pouvez probablement pas) en mémoire, vous ne le souhaitez pas . Il est absolument inutile de charger des données que vous n'avez pas à utiliser immédiatement, c'est inefficace et lent. Même si vous pouviez le mettre en mémoire, vous ne seriez probablement pas en mesure de tout traiter dans un laps de temps raisonnable.
Je soupçonne votre objection au déchargement de certaines régions parce que votre mise à jour mondiale est en train d'itérer sur toutes les tuiles et de les traiter? C'est certainement valable, mais cela n'empêche pas d'avoir seulement certaines pièces chargées. Au lieu de cela, lorsqu'une région est chargée, appliquez les mises à jour manquées pour la mettre à jour. C'est également beaucoup plus efficace en termes de traitement (en concentrant une grande quantité d'efforts sur une petite zone de mémoire). S'il y a des effets de traitement qui pourraient traverser les limites des régions (par exemple, une coulée de lave ou une coulée d'eau qui se poursuivra dans une autre région), alors liez ces deux régions ensemble de sorte que lorsqu'une est chargée, l'autre l'est aussi. Idéalement, ces situations devraient être minimisées, sinon vous vous retrouverez rapidement dans le cas «toutes les régions doivent être chargées à tout moment».
la source
Un moyen efficace que j'ai utilisé dans un jeu XNA était:
Vous pouvez également utiliser une seule grande carte de cette façon si elle n'est pas si grande et utiliser la logique présentée.
Bien sûr, la taille de vos textures doit être équilibrée et la résolution paie un grand prix. Essayez de travailler à une résolution inférieure et, si vous en avez besoin, créez un pack haute résolution à charger si un paramètre de configuration est défini sur.
Vous devrez boucler à chaque fois uniquement les parties pertinentes de cette carte et afficher uniquement ce qui est nécessaire. De cette façon, vous améliorerez considérablement les performances.
Un serveur peut traiter une énorme carte plus rapidement car il n'a pas à rendre (l'une des opérations les plus lentes) le jeu, donc la logique peut être l'utilisation de blocs plus gros ou même de la carte entière, mais ne traite que la logique autour des joueurs, comme dans une fenêtre d'affichage de 2 fois la zone autour du joueur.
Un conseil ici est que je ne suis en aucun cas un expert en développement de jeu et que mon jeu a été fait pour le projet final de mon diplôme (que j'avais le meilleur score), donc je ne suis peut-être pas si juste à chaque point, mais j'ai fait des recherches sur le Web et des sites comme http://www.gamasutra.com et le site des créateurs XNA creators.xna.com (à ce moment http://create.msdn.com ) pour rassembler des connaissances et des compétences et cela a bien fonctionné pour moi .
la source
Soit
la source
texture2d
? Chaque tuile en a-t-elle une unique ou partage-t-elle des textures? Aussi, où avez-vous dit cela? Vous ne l'avez certainement pas mis dans votre question, qui est où l'information devrait être. Expliquez mieux votre situation particulière, s'il vous plaît.En réponse à votre mise à jour, vous ne pouvez pas allouer
Int32.MaxValue
de taille supérieure aux tableaux . Vous devez le diviser en morceaux. Vous pouvez même l'encapsuler dans une classe wrapper qui expose une façade de type tableau:la source