tableau à  2 dimensions

pierre68314pierre68314 Membre
02:18 modifié dans Vos applications #1
Bonjour,



Je souhaite faire un tableau à  2 dimensions qui stockerait une valeur (int) et non un objet. Est-ce que cela existe en COCOA ?
Sinon est-ce qu'il existe une liste (<=>une dimension) qui ne contient pas d'objet mais des nombres ?

Pour le moment, j'utilise un NSMutableArray qui contient des NSMutableArray qui eux contiennent un objet contenant un NSInteger (cet objet contient également un sprite, sinon j'aurais utilisé un NSNumber).

Ce qui me gêne c'est que c'est lourd à  gérer et certainement pas optimisé.  :'(
Pour récupérer et stocker une valeur, je fais :

<br /><br />- (int) valueWithI:(int) i withJ:(int) j {<br />&nbsp; &nbsp; return&nbsp; [[[row objectAtIndex:i ] objectAtIndex:j] valeurCase];<br />}<br /><br />- (void) setValue:(int)val WithI:(int)i withJ:(int)j {<br />&nbsp; &nbsp; Tile * t = [[row objectAtIndex:i ] objectAtIndex:j];<br />&nbsp; &nbsp; // Tile est une class qui contient un NSInteger<br />&nbsp; &nbsp; t.valeurCase = val;<br />}<br /><br />


Confirmez-vous que je ne dois pas faire un "release" de Tile*t ?


merci d'avance pour vos réponses. :D
Pierre

Réponses

  • DrakenDraken Membre
    mai 2011 modifié #2
    L'Objective-C c'est aussi du .. C ! Pourquoi ne pas utiliser un tableau C classique à  deux dimensions dans ton application ? D'ailleurs c'est plus rapide à  l'exécution qu'un NSMutableArray.



  • pierre68314pierre68314 Membre
    mai 2011 modifié #3
    aaaah  :o

    Je ne savais pas que c'était possible !

    je vais chercher dans ce sens.


    sinon, quelqu'un peut répondre à  ma dernière question ? ai-je une fuite mémoire ?

    Merci beaucoup.



    <br /><br />- (void) setValue:(int)val WithI:(int)i withJ:(int)j {<br />&nbsp; &nbsp; Tile * t = [[row objectAtIndex:i ] objectAtIndex:j];<br />&nbsp; &nbsp; // Tile est une class qui contient un NSInteger<br />&nbsp; &nbsp; t.valeurCase = val;<br />}<br /><br />
    


    Confirmez-vous que je ne dois pas faire un "release" de Tile*t ?

  • 02:18 modifié #4
    Je confirme. Mais j'ajouterai qu'il faudra revoir la gestion mémoire si tu te poses encore ce genre de question
  • AliGatorAliGator Membre, Modérateur
    02:18 modifié #5
    La gestion mémoire est bonne (mais tu ne devrais pas avoir à  te poser la question : pas d'alloc/retain/copy donc pas de raison de release/autorelease, ça devrait être naturel)

    Pour le tableau à  2 dimensions, tu peux le faire en C, ça a l'avantage d'être plus simple à  manipuler et plus rapide à  l'exécution, mais l'inconvénient de ne pas être dynamique : tu ne pourras pas rendimensionner ton tableau pour lui rajouter des "cases" à  la volée, une fois que tu lui as donné une dimension de N*M cases c'est fixe.
  • pierre68314pierre68314 Membre
    02:18 modifié #6
    merci pour ta réponse.

    J'aimerai votre avis sur l'algo que j'utilise. J'ai un algo de recherche de chemin que j'aimerai tester et pour ce faire je crée une grille de points. Certains seront des murs, d'autres de l'herbe permettant le passage.

    J'ai fait un NSMutableArray "ROW" qui contient n NSMutableArray "COL" afin de représenter ma grille.
    Dans COL, je place un objet "Tile" qui lui contient un nombre (<=> valeur de la case) et 2 sprites (un pour le mur, l'autre pour de l'herbe)


    Mais maintenant que je sais que les tableaux façon C pour stocker les valeurs de chaque case sont une possibilité, je me demande si je ne devrais pas plutôt les utiliser et gérer l'affichage de mes sprites directement indépendamment du tableau.
    Je pourrais identifier le sprite associé à  une case en fonction du tag qui correspondrait au n° de la case par ex.




    Quel est d'après vous la meilleure méthode ?



    merci d'avance.
  • BunoBuno Membre
    02:18 modifié #7
    dans 1305117636:

    Dans COL, je place un objet "Tile" qui lui contient un nombre (<=> valeur de la case) et 2 sprites (un pour le mur, l'autre pour de l'herbe)
    merci d'avance.

    Si les sprites sont les même, il ne faut pas les répéter pour chaque case de ton tableau  ;)
  • MalaMala Membre, Modérateur
    02:18 modifié #8
    dans 1305114185:

    Pour le tableau à  2 dimensions, tu peux le faire en C, ça a l'avantage d'être plus simple à  manipuler et plus rapide à  l'exécution, mais l'inconvénient de ne pas être dynamique : tu ne pourras pas rendimensionner ton tableau pour lui rajouter des "cases" à  la volée, une fois que tu lui as donné une dimension de N*M cases c'est fixe.

    realloc() avec un tableau de pointeurs et tu l'as ton tableau 2D dynamique en C. Si c'est plus délicat à  gérer, côté perfs c'est le jour et la nuit.
  • AliGatorAliGator Membre, Modérateur
    02:18 modifié #9
    Il il faut que tu fasses une séparation plus nette entre ton modèle et ta vue, pour respecter le design pattern MVC et avoir une architecture robuste.

    Tu dois donc avoir dans ton modèle une "grille" (tableau 2 dimensions) indiquant, pour chaque case, son type (herbe ou mur). Et indépendamment de ça, tu dois avoir une vue qui dessine le plateau correspondant à  ta "grille", et c'est cette vue qui connait les 2 sprites "herbe" et "mur", qui demande à  ton modèle, pour chaque case, le type de case que c'est, et qui dessine le bon sprite en conséquence.


    En gros, par exemple, dans ton modèle tu peux avoir
    typedef enum {<br />&nbsp; TileTypeWall, // mur<br />&nbsp; TileTypeGrass, // herbe<br />} TileType;<br /><br />TileType grid[kLinesCount][kColumnsCount]; // tableau C 2D de TileTypes, représentant ton plateau côté modèle
    
    Et ton contrôlleur pourra avoir une méthode
    -(TileType)tileTypeForRow:(int)row column:(int)col;
    
    qui saura rendre la TileType pour la colonne et la ligne demandée.
    Si un jour tu changes ton modèle, donc ta façon de stocker les TileType (passage de tableau C à  NSArray ou à  NSArray de NSArray, ou autre représentation), tu n'auras qu'à  adapter le code de [tt]-(TileType)tileTypeForRow:(int)row column:(int)col;[/tt] dans ton contrôleur, mais tu n'auras rien à  changer côté vue. C'est ce qui rend le modèle et la vue indépendants, même en changeant le modèle la vue continue de fonctionner.

    D'un autre côté, tu as ta vue, qui se charge de faire le rendu visuel correspondant à  ton modèle. Pour dessiner, elle va certainement avoir besoin de demander au contrôleur, pour chaque case, quel est le TileType correspndant (case devant contenir de l'herbe ou un mur ?). Donc elle va appeler la méthode [tt]-(TileType)tileTypeForRow:(int)row column:(int)col;[/tt] du contrôleur. Et en fonction de la réponse, dessiner, au bon endroit (calculé en fonction de la ligne et colonne, et éventuellement du scroll de ta vue si tu peux scroller dedans, voire du zoom, etc) le sprite correspondant.
    Si un jour tu changes ta vue, pour la remplacer par une vue 3D, ou 2D isométrique de ton plateau, ou que tu veux pouvoir changer selon les préférences utilisateur les sprites utilisés (dessiner une pierre plutôt qu'un mur en brique, dessiner un chemin de terre plutôt que de l'herbe, changer le look de ton personnage...), ce ne sont que des caractéristiques de la vue et cela n'affectera pas le modèle, la vue restera indépendante du modèle. Tu peux même avoir un seul modèle, et avoir plusieurs vues utilisant le même modèle, pour avoir plusieurs représentations (2D vue de dessus, 3D isométrique, vue à  la première personne pour représenter en 3D iso ce que le personnage verrait si étais à  sa place, ...) du même plateau de jeu.

    En aucun cas il faut mélanger le tout comme tu le fais encore moins à  répéter les sprites dans ton modèle en trainant les deux sprites dans chaque objet qui représente une case. Ton modèle n'a que à  connaitre le TileType (mon énum indiquant case "mur" ou case "libre") de chaque case de ton plateau, pas comment elle sera représentée.
  • DrakenDraken Membre
    mai 2011 modifié #10
    La meilleure méthode c'est de ne pas mélanger les données et la représentation graphique.

    Tu devrais utiliser un tableau ne contenant qu'une information par case : la nature du terrain (herbes ou mur). C'est une information purement descriptive sur la structure de ton terrain.

    Pour ta recherche du chemin, tu devrais passer par une méthode retournant la "passabilité" d'une case à  partir de son contenu. Pour le moment, c'est simple :

    Herbes = traversable
    Mur = non traversable

    Mais que faire s'il y a 5 types différents d'herbes ? Et de la terre battue ou encore de la route ? Et différents types d'obstacles ? Ton algorithme de chemin ne doit pas s'occuper de ces détails, juste de savoir si une case est traversable ou non.

    De plus, la notion de passabilité n'est pas toujours la même, selon le contexte. Imaginons que tu crées un jeu de guerre à  l'époque napoléonienne, avec de l'infanterie, de la cavalerie et des bateaux.

    - L'infanterie peut passer sur l'herbe, dans la forêt et ne peut pas traverser l'eau.
    - Les chevaux se déplacent sur l'herbe, mais pas dans la forêt, ni sur l'eau.
    - Les bateaux ne peuvent se mouvoir que sur l'eau !

    C'est pourtant la même carte pour tous !

    Je ne sais pas quel algo de chemin tu utilises, mais le meilleur est censé être le A* (prononcer "A stars").



  • pierre68314pierre68314 Membre
    02:18 modifié #11
    Merci pour vos réponses.

    donc je résume pour voir si j'ai bien compris.

    - séparer la vue (<=>partie graphique) des données. Comme ça, si je veux modifier qqch au niveau graphique je n'ai pas à  toucher au reste du programme.

    - si je dois utiliser plusieurs fois le sprite "herbe" pour combler ma grille, je dois déclarer le sprite une seule fois et ensuite dans chaque case stocker le pointeur.
    Si j'ai bien compris alors une question me vient : comme position un même sprite à  plusieurs endroits en même temps si ce n'est en dupliquant l'objet?

    - Draken, au début je voulais attribuer à  chaque un objet "Tile" qui est composé d'un integer (stocker la valeur de la case dans la recherche d'algo A*), un sprite herbe visible par défaut et un sprite mur non visible.
    si l'algo de base fonctionne , je pourrai ensuite "complexifier" la classe Tile en y ajoutant d'autres paramètres.
    Après, je peux supprimer les sprites  de la classe Tile et stocker le pointeur à  la place.


    merci pour vos réponses rapides
  • AliGatorAliGator Membre, Modérateur
    02:18 modifié #12
    Ta dernière solution n'est clairement pas la bonne, avoir un sprite herbe visible et un sprite mur non visible partout ça ne sert par à  grand chose (double de RAM utilisé). Mais surtout, c'est à  l'opposé du MVC, et à  proscrire côté architecture logicielle / conception.

    La gestion du dessin et de tes sprites doit donc vraiment se faire côté vue et pas côté modèle, contrairement à  ton code.

    Après pour le reste de la question, ça dépend ce que tu appelles des sprites. C'est un terme assez générique.
    Pour moi un sprite est une image (représentant le contenu visuel d'une case donc). Dans ce cas, il n'y a aucun problème à  dessiner une UIImage plusieurs fois à  l'écran, à  plusieurs positions dans ta vue, un simple drawAtPoint par exemple sur la UIImage permet de la dessiner à  une position, suffit de l'appeler pour toutes les positions où tu veux dessiner l'image.
    Si pour toi un sprite est une vue contenant une image, genre non pas une UIImage mais une UIImageView, alors :
    - ce n'est pas la meilleure façon de faire côté optimisation : tu peux ajouter autant de UIImageViews qu'il y a de cases en subview d'une grande UIView, mais c'est loin d'être le plus optimal si tu commences à  avoir beaucoup de cases et veut optimiser ton dessin
    - et en plus en effet tu devras créer autant d'UIImageViews que de tiles (donc aussi autant d'alloc/init, là  encore pas top côté RAM) et en effet tu ne peux pas ajouter plusieurs fois la même UIImageView à  différents endroits dans la même UIView.

    Donc de toute façon si tu avais cette solution de une UIImageView par tile que tu rajoutes en subviews de ta vue principale, ce serait à  la partie VUE de gérer cela, pas à  la partie MODELE, donc à  la vue de créer et ajouter les UIImageViews pas à  la partie modèle de les garder dans ton tableau. MAIS de toute façon cette solution d'avoir plusieurs UIImageView est une mauvaise idée, et il est préférable de se contenter de deux UIImages (une pour l'herbe une pour le mur) et c'est tout, que tu dessines à  plusieurs endroits de ta vue (pour chaque tile, dessiner une des deux images selon le TileType retourné par ton modèle quand tu lui demandes). Tu gagneras :
    - En architecture (modularité, clareté, structuration)
    - En mémoire (beaucoup moins d'objets créés et manipulés, réduction drastique des alloc/init)
    - En réactivité (méthode de hitTesting plus rapide, détection des taps généralisée)
    - En optimisation de dessin puisque tu n'as que 2 images chargées dans la carte graphique (et même dans le cache de cette dernière certainement) et pas tout plein d'instances de la même image multipliées pour chaque tile, ta carte graphique te remerciera donc par de meilleurs performances sur le rendu

    D'ailleurs à  terme je te conseille de regarder du côté de CGLayer (cf le Programming Guide d'Apple sur le sujet, déjà  cité maintes fois dans ces forums) car pour ce que tu veux faire c'est précisément adapté et optimisé. Mais bon faut déjà  maà®triser le MVC et le dessin de ton tableau côté vue avant d'y passer, chaque chose en son temps.
  • pierre68314pierre68314 Membre
    02:18 modifié #13
    Oups, je n'ai pas été clair dans mon dernier post. Dans la dernière phrase, je disais qu'afin d'améliorer mon programme et de séparer le graphisme du reste du programme, je comptais plutôt stocker les pointeurs vers le sprite dans ma classe Tile.(ce qui est aussi une connerie de ma part)

    je vais plutôt ajouter à  ma classe Tile une variable qui permettra à  la partie du programme qui gère le graphisme d'afficher l'une ou l'autre image.


    Pour ce qui est de la partie graphique, j'utilise Cocos2d qui me permet de charger dans le cache une spritesheet contenant sur laquelle l'herbe et le mur se trouvent. Du coup, mon sprite est un objet CCSprite.


    merci de passer autant de temps à  me répondre  :p
  • pierre68314pierre68314 Membre
    02:18 modifié #14
    Je remercie tout le monde pour vos conseils et pour pour prouver qu'ils servent quand même un peu, je vous laisse un petit screenshot http://p.diether.free.fr/IMG_0171.png

    pour info les points blancs sont les points de départ et d'arrivée, les numéros sur les cases servent à  identifier le chemin. On part du point blanc avec la valeur la plus élevée et on remonte jusqu'à  au point de départ.
  • DrakenDraken Membre
    02:18 modifié #15
    Pas mal. C'est rapide à  l'exécution ?



  • pierre68314pierre68314 Membre
    02:18 modifié #16
    testé sur mon iphone 4, c'est quasi-instantané.

    je vais voir pour faire un petit prog histoire de mesurer le temps que ça met.
    Je vais augmenter le nombre de cases
Connectez-vous ou Inscrivez-vous pour répondre.