![]() |
|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Ouvrir sur le forum | Recherche | Messages du jour | Marquer les forums comme lus |
Publicité |
![]() |
|
Outils de la discussion | Modes d'affichage |
![]() |
#1 |
Membre confirmé
Date d'inscription: 15/10/2007
Messages: 69
|
![]() Salut à tous. Vous le savez déjà, je fais des programmes et des jeux sur Nintendo DS. L'idée, c'est que tout ce que j'ai fait devrait permettre à d'autres d'en faire autant. J'ai commencé une repasse sur mon code pour montrer petit à petit comment se servir de mes outils et de mon moteur de jeu.
Pour ça, il vous faudra d'abord le kit de développement non-officiel pour NDS et GBA. Ils ont fait du bon travail et il y a un installeur automatique pour ceux qui travaillent sous Windows. De mon côté, j'ai préparé une structure d'accueil pour les programmes. Règles de génération des ROMs nds à partir des sources et des données du jeu, les petites bibliothèques supplémentaires pour faire de la musique, etc. Et un un premier programme à faire tourner sur DS. Evidemment, on est sur une machine "bare metal", sans système d'exploitation, donc il y a un peu plus de code d'initialisation qu'à l'habitude, mais le fait de créer un objet "Engine" fait déjà une grande part du travail. Code:
void setactive() { FileDataReader fd("efs:/sndtrk.xm"); ntxm9->stop(); u16 err = ntxm9->load(&fd); ge.setWindow(active); if (err!=0) iprintf("ntxm says %x\n",err); } Je propose deux manière d'interagir avec les boutons et l'écran tactile de la console: l'un pour la partie "action" du jeu (pas encore dans le tuto), et l'autre qui me sert pour les éditeurs: ge.setWindow() a défini notre objet window comme étant l'activité actuelle, donc le moteur appellera la fonction ci-dessous chaque fois qu'on pousse sur un des boutons. Code:
bool handle(uint& keys, Event evt) { if (keys & KEY_A) { ntxm9->play(); } if (keys & KEY_Y) { ntxm9->stop(); } return true; } Dernière modification par PypeBros ; 04/04/2018 à 22h58. |
![]() |
![]() |
Publicité |
![]() |
#2 |
Membre confirmé
Date d'inscription: 15/10/2007
Messages: 69
|
![]() Bon, avant de voir comment le moteur 'GEDS' peut charger des images dans la mémoire vidéo de la Nintendo DS, il faut que je vous explique un peu comment son processeur graphique construit les images que l'on voit à l'écran. J'utilise le mode "texte", ici, où les images sont composées d'élément de 8x8 pixels appelés "tiles" (prononcez 'taïlesz') que l'on dispose sur une grille.
![]() Vous avez tous déjà utilisé un éditeur de donjons RPG où l'on travaille carré par carré (je crois qu'on dis volontiers des 'chips' ?) plutôt que de mettre un arbre ou une maison d'un coup. Le mode texte de la DS va un cran plus loin: la mémoire de la machine elle-même est découpée en une région pour retenir des pavés de 8x8 (le 'tileset') et une région pour indiquer quel pavé montrer à quel endroit (la 'map' de l'écran, couvrant généralement 256x256 pixels pour la DS). Parfois, on mettra le même tile à plusieurs endroit de l'écran (en inscrivant juste son numéro dans plusieurs cases de la map), et parfois un tile ne sera pas repris (pour l'instant). C'est une technique a peu près aussi vieille que les machines 8-bit et qui a perduré pendant toute la période 16-bit (mais ça, c'est une autre histoire). Ce qui a changé au fil du temps, c'est le nombre de couleurs autorisées par tile (de 3 pour la NES à 255 pour la NDS) et le nombre de calques que l'on peut superposer pour obtenir l'image souhaitée (jusqu'à 4 sur la NDS). Les fichiers .spr créés par le SpriteEditor pour DS sont principalement une sauvegarde du contenu de la mémoire vidéo tel qu'on voudrait l'avoir pendant le jeu. Du coup, il n'y a vraiment pas grand-chose à faire pour pouvoir les importer dans son programme. Code:
SpriteRam myRam(WIDGETS_CHARSET(512)); SpriteSet mySet(&myRam, BG_PALETTE); mySet.Load("efs:/bg.spr"); Bon, malheureusement, avoir des graphismes en mémoire ne suffit pas pour les avoir à l'écran. La démo n°2 doit aussi configurer la puce graphique pour qu'elle utilise nos données et remplir la grille/map d'un des calques. Code:
REG_DISPCNT|=DISPLAY_BG2_ACTIVE; REG_BG2CNT=BG_MAP_BASE(GEBGROUND)|BG_TILE_BASE(0)|BG_COLOR_256; ![]() La bibliothèque 'libgeds' donne des paires de noms à différentes grilles: un nom pour la configuration de BG_MAP_BASE, et un nom pour le tableau correspondant dans la mémoire vidéo (WIDGETS_BACKGROUND). Du coup avec la configuration qu'on vient de faire, on peut changer le pavé en haut à gauche de l'écran en écrivant dans WIDGETS_BACKGROUND[0]. Dans celui en haut à droite avec WIDGETS_BACKGROUND[31] (parce qu'il y a 32x8 dans 256 ![]() Code:
for (int l=0; l<32; l+=2) { for (int b=0; b<8; b+=2) { WIDGETS_BACKGROUND[b+l*32]=tile++; WIDGETS_BACKGROUND[b+l*32 +1 ]=tile++; WIDGETS_BACKGROUND[b+l*32 +32]=tile++; WIDGETS_BACKGROUND[b+l*32 +33]=tile++; } } Code:
/* 0 1 * 2 3 */ Voilà. Le code est sur github, donc. Avec le fichier .spr (creative common) ![]() N'hésitez pas à me ralentir avec quelques questions si ça va trop vite pour vous. |
![]() |
![]() |
![]() |
#3 |
Membre confirmé
Date d'inscription: 15/10/2007
Messages: 69
|
![]() Un des éléments les plus importants dans un jeu vidéo (2D), ce sont sans doute les sprites. Rien à voir avec une boisson pétillante, il s'agit du terme consacré pour les objets graphiques librement déplaçable à l'écran, y compris le personnage principal, les adversaires, mais aussi certains power-ups ou des effets spéciaux.
Les sprites ont besoin de données graphiques (des valeurs de couleurs) tout comme les décors, et ces données sont de nouveau constituées de "tiles". On va utiliser surtout des blocs de 16x16 pixels ici (et donc constitués de 2x2 tiles) mais la DS peut en réalité utiliser des sprites de 8x8 à 64x64 pisxels (avec quelques restrictions quand-même). Mettre à jour la mémoire vidéo pour que les sprites s'affichent à l'écran (quels graphismes, quels réglages, etc) est délégué à la classe SpritePage dans la libgeds, et la fonction "sync()" de GuiEngine fera le nécessaire pour mettre à jour les coordonnées en respectant les timings de la puce vidéo (et faire en sorte que ça ne clignote pas, par exemple). Les SpritePages ajoute un niveau de correspondance entre les identifiants logiques des graphismes (ceux que le développeur utilise, "c'est sur la page 3, deuxième ligne, première colonne) et les emplacement "physiques" dans la mémoire vidéo (42eme bloc sur 256 en mémoire). Bref, le contenu de la SpriteRam est généralement un joyeux chaos résultant des créations et supressions de blocs alors que chaque SpritePage est organisée exactement comme l'utilisateur le décide. ![]() < le brol de la SpriteRam | la SpritePage, aussi organisé que vous > Code:
class Hero : private UsingSprites { NOCOPY(Hero); const SpritePage *page; unsigned x, y; oamno_t oam; public: Hero(const SpritePage *pg) : page(pg), x(128), y(96), oam((oamno_t) gResources->allocate(RES_OAM)) { page->setOAM((blockno_t)2, sprites); } void move(int dx, int dy) { x += dx; y += dy; iprintf("(%u,%u)", x, y); page->changeOAM(sprites + oam, x, y, (blockno_t) 4); } }; - se souvenir des coordonnées (le bon mot pour parler de la position à l'écran) actuelles; - une SpritePage qui sait comment mettre à jour les entrées de la "MAO" (ou Mémoire des Attributs d'Objets -- Object Attribute Memory -- le terme de Nintendo pour parler des blocs de contrôle des sprites); - avoir réservé une entrée de MAO et en retenir le numéro. En disant que notre classe dérive de "UsingSprites", on obtient l'accès au tableau des MAOs (le tableau 'sprites ![]() Code:
void setactive() { SpriteRam myRam(WIDGETS_CHARSET(512)); SpriteSet mySet(&myRam, BG_PALETTE); mySet.Load("efs:/bg.spr"); /*+*/ sprSet.Load("efs:/hero.spr"); // rest of MetaWindow::setActive() as defined in previous tutorial /*+*/ hero = new Hero(sprSet.getpage(PAGE0)); } Puis il n'y a plus qu'à tester l'état de la croix directionnelle (et les transmettre par des appels à la fonction 'move()') pour pouvoir déplacer le personnage en réaction aux mouvements imprimés par le joueur. Bon, ne vous attendez pas à quelque-chose de fluide ici: j'utilise toujours l'interface utilisateur prévue pour les éditeurs du projet "GEDS" qui ne produit un 'évèment DPAD' que lorsqu'on appuie sur une nouvelle direction. On arrangera ça la semaine prochaine avec les Animators. Code:
if (keys & KEY_UP) { hero->move(0, -4); } if (keys & KEY_DOWN) { hero->move(0, 4); } if (keys & KEY_LEFT) { hero->move(-4, 0); } if (keys & KEY_RIGHT) { hero->move(4, 0); } ![]() Dernière modification par PypeBros ; 25/03/2018 à 22h36. Motif: oups. |
![]() |
![]() |
![]() |
#4 |
Membre confirmé
Date d'inscription: 15/10/2007
Messages: 69
|
![]() Bon, il est temps de vous montrer ce que libgeds peut faire pour ceux qui n'ont pas le C++ dans le sang. Charger des fichiers, définir des animations, positionner les ennemis dans le niveau, tout ça peut être fait à partir de commandes dans des scripts texte lus et transformés en objets C++ par la bibliothèque.
Je vais commencer par un sous-script qui décrit le comportement d'un personnage. On va commencer très simple, avec une seule animation, et toujours le même comportement: ne pas bouger. Voici donc "xdad.cmd" Code:
# this is xdad.cmd behaviour description anim1 0 { spr0 4 delay 3 spr0 5 delay 3 spr0 6 delay 3 spr0 7 delay 3 spr0 8 delay 3 spr0 9 delay 3 spr0 a delay 3 spr0 b delay 3 loop } state0 :anim1 end Le bloc 'anim<numéro-d-animation>' donne utilise une page du SpriteSet et construit une animation en indiquant les images à utiliser (dans cette page) et combien de temps attendre entre chaque deux images. On peut ensuite l'attacher à un état, c'est à dire une unité élémentaire de comportement . Bon, évidemment, pour l'instant, avec un seul état, il ne se passera pas grand-chose. C'est un peu notre 'hello world'. Un fichier comme xdad.cmd, on en créera un par type de personnage (imaginez "goomba.cmd", "koopa.cmd", etc.) Maintenant, il va nous falloir un script qui décrit un niveau. Ce sera demo.cmd, pour nous. Code:
#demo.cmd, resource management print "loading tileset" bg0.load "../bg.spr" print "loading sprites" spr.load "../hero.spr":1 Code:
#demo.cmd, use 'character' description input "xdad.cmd" import state 0 ![]() Pourtant, seuls un ou deux de ces états sont nécessaires pour décrire le niveau (commencer tourné vers la droite ou vers la gauche, par exemple, ou éventuellement par une glissade). Geds propose donc un jeu d'identifiants pour l'ensemble des états d'un personnage (tous les états d'un goomba, par exemple) qui sera utilisé dans xdad.cmd, et l'ensemble des états utilisables pour créer des nouveaux objets (un goomba, mario, un koopa rouge ou un koopa vert), qui sera utilisé dans demo.cmd. Une fois le traitement xdad.cmd terminé, la commande 'import state 0' indique de prendre le premier état de l'ensemble de xdad et d'en faire l'état numéro 0 pour demo.cmd. Code:
# creating gobs gob0 :state0 (128, 100) end Comme on le voit sur github, on laisse tomber la classe Hero: il s'agit maintenant d'un GameObject construit et contrôlé directement par la bibliothèque suite au commandes du script. La classe 'MetaWindow', point de départ de notre démo, a pas mal changé aussi. Code:
#include <GameWindow.hpp> class MetaWindow : public DownWindow { NOCOPY(MetaWindow); Window *active; InputReader* reader; LoadingWindow loadwin; GameWindow gamewin; public: MetaWindow(): active(0), reader(0), loadwin(&reader), gamewin(this, &reader, &loadwin) { ntxm9 = new NTXM9(); // for the sound, remember ? active = &gamewin; } Code:
void setactive() { FileDataReader fd("efs:/sndtrk.xm"); reader = new FileReader("efs:/demo.cmd"); ntxm9->stop(); u16 err = ntxm9->load(&fd); ge.setWindow(active); restore(); // just to have the tileset shown again. if (err!=0) iprintf("ntxm says %x\n",err); } Et c'est tout. A la prochaine fois pour rendre ses déplacement à notre petit père Noël. |
![]() |
![]() |
![]() |
#5 |
Membre confirmé
Date d'inscription: 15/10/2007
Messages: 69
|
![]() ![]() Redonner du mouvement au personnage dans geds, ça correspond à spécifier une série de contrôleurs pour l'état choisi. Chaque contrôleur a une mission bien précise, p.ex. ici Code:
state0 :anim1 { using dpad using momentum(x to 512) using momentum(y to 512) } momentum va utiliser les directions sauvées pour augmenter ou diminuer la vitesse horizontale ou verticale (selon qu'on a utilisé "x" ou "y") On peut aussi passer des paramètres aux contrôleurs: c'est le cas ici du "512" qu'il faut interpréter comme un nombre de 256emes de pixels par frame (1/60eme de seconde) et qui représente la vitesse maximale autorisée sur un axe. Le ScriptParser cherchera les classes C++ correspondantes et fait le nécessaire pour que le code qu'elles contiennent soient appelées à chaque image générée par le jeu. Trois lignes de plus dans le script donc (et quelques modifications dans les Makefiles). C'est tout. |
![]() |
![]() |
![]() |
Liens sociaux |
Publicité |
Utilisateurs regardant la discussion actuelle : 1 (0 membre(s) et 1 invité(s)) | |
Outils de la discussion | |
Modes d'affichage | |
|
|
![]() |
||||
Discussion | Auteur | Forum | Réponses | Dernier message |
![]() |
heaveN. | Articles et Tutos | 218 | 12/04/2011 09h47 |
[Divers] PMP (Portable Game & Media Player) : La nouvelle console cheap! | vgiant | Articles | 46 | 07/01/2010 13h46 |
Concours de quéquettes rhétoriques | dolarcles | Récréation | 20 | 19/12/2009 19h40 |
Problème avec les jeux NTSC sur ma Playstation | Lord Raptor | Discussions Sur Les Autres Consoles | 2 | 26/08/2007 20h11 |
Tuto sur les problèmes Wifi avec les homebrews | olive_69 | [NDS] Divers | 10 | 12/03/2007 23h27 |