Partie 23: Objets - Partie 3: Poser et ramasser des objets

Téléchargements

Code source
Exécutable de l'éditeur de niveaux - exactement le même que la partie 21 (Windows 32bits)
Exécutable du jeu (Windows 32bits)

Avant d'essayer de compiler le code, allez dans l'onglet "Projets" dans le menu de gauche, séléctionnez l'onglet "Run" pour votre kit,
et mettez dans "Working directory" le chemin de "editor\data" pour l'éditeur ou "game\data" pour le jeu.

Graphismes des objets

Les images des objets dans votre main ou dans une feuille de personnage sont dans des planches de sprtes que vous
trouverez dans "data/gfx/interface".


On a déjà utilisé une de ces planches de sprites auparavant. pour accéder à une de ces images, on a besoin du nom
de fichier de la planche et du numéro du sprite dans cette planche.
Alors j'ai ajouté ces données à chaque objet dans "items.xml":

		<?xml version="1.0" encoding="utf-8"?>
		<items>
			<!-- ======================== Armes ======================== -->
			<!-- OEIL DU TEMPS -->
			<item name="ITEM001">
				<category>Weapon</category>
				<floor_image>00W_Ring.png</floor_image>
				<image_file>Items0.png</image_file>
				<image_num>17</image_num>
			</item>

			<!-- ROND D'ECLAIRS -->
			<item name="ITEM002">
				<category>Weapon</category>
				<floor_image>00W_Ring.png</floor_image>
				<image_file>Items0.png</image_file>
				<image_num>19</image_num>
			</item>
			
			[...]
		</items>
				
On verra plus tard que certains objets ont plus d'une image, mais pour l'instant on n'en affichera qu'une.

Zones souris

On va ajouter 2 nouveaux types de zones: eMouseAreaG_PickObject pour ramasser un objet par terre, et
eMouseAreaG_DropObject pour poser l'objet dans votre main au sol.
L'objet qu'on tient en main sera stocké dans la variable mObjectInHand de la classe CMouse.
Quand la main sera vide, le type de cet objet sera 0.

		enum EMouseAreaTypes
		{
			eMouseArea_None = 0,

			// zones de l'interface
			[...]

			// zones du jeu
			eMouseAreaG_Champion,
			eMouseAreaG_DoorButton,
			eMouseAreaG_PickObject,
			eMouseAreaG_DropObject
		};

		class CMouse
		{
		public:
			[...]
			CObject mObjectInHand;
			[...]
		};
				
On peut seulement ramasser des objets qui sont à nos pieds (c'est-à-dire la rangée "arrière" de la case où on est) et
ceux qui sont juste devant nous (la rangée "avant" de la case devant nous).
Et bien sur, vous ne pouvez poser un objet que sur ces mêmes emplacements aussi.
Pour clarifier ça, voici une image des zones que l'on va utiliser pour poser les objets:


Mais il y a autre chose à prendre en compte: quand il y a un mur en face de nous, on n'a pas besoin d'ajouter les
zones de la case devant nous.


Alors j'ai d'abord écrit une petite fonction pour tester si un mur est en face du joueur

		bool CObjects::isWallInFront()
		{
			CTile*  tile = map.getTile(player.pos);
			EWallSide   side = player.getWallSide(eWallSideUp);
			CWall*  wall = &tile->mWalls[side];

			return (wall->getType() != eWallNothing);
		}
				
Pour les zones de "ramassage", on a seulement besoin d'ajouter une zone pendant qu'on dessine les objets dans CObjects::drawObjectsStack:

		// dessine l'objet à l'écran
		pos -= CVec2(scaledObject.size().width() / 2, scaledObject.size().height() - 1);
		graph2D.drawImage(image, pos, scaledObject, 0, false, QRect(0, 33, MAINVIEW_WIDTH, MAINVIEW_HEIGHT));

		// ajoute la zone souris
		if (mouse.mObjectInHand.getType() == 0)
		{
			if (tablePos.x >= WALL_TABLE_WIDTH - 1 && tablePos.x <= WALL_TABLE_WIDTH)
			{
				if (tablePos.y == (WALL_TABLE_HEIGHT - 1) * 2 ||
				    (tablePos.y == (WALL_TABLE_HEIGHT - 2) * 2 + 1 && isWallInFront() == false))
				{
					CRect   mouseRect(pos, CVec2(pos.x + scaledObject.width() - 1, pos.y + scaledObject.height() - 1));
					mouse.addArea(eMouseAreaG_PickObject, mouseRect, eCursor_Hand, (void*)stack, (void*)i);
				}
			}
		}
				
Ici, on teste tablePos.x pour voir si l'objet est en face de nous (pas sur la gauche ni sur la droite).
On teste tablePos.y pour savoir si on est sur la case du jouer ou sur la case juste devant.
Et on utilise isWallInFront() pour savoir s'il y a un mur dans cette dernière case.
Vous pouvez remarquer que dans les paramètres on met le pointeur sur le tas et l'index de l'objet dans ce tas.

Ensuite, pour les zones de "dépose", comme on a besoin d'avoir les coordonnées dans la map, c'est plus facile de le
faire séparément dans drawBackRow() et drawFrontRow().
Regardons ça dans drawBackRow():

		void CObjects::drawBackRow(QImage* image, CVec2 mapPos, CVec2 tablePos)
		{
			[...]
			
			CVec2   mapLeft = mapPos * 2 + pos1;
			CVec2   mapRight = mapPos * 2 + pos2;
			drawObjectsStack(image, mapLeft, tablePos * 2 + CVec2(0, 0));
			drawObjectsStack(image, mapRight, tablePos * 2 + CVec2(1, 0));

			// zones souris pour poser un objet
			if (mouse.mObjectInHand.getType() != 0)
			{
				if (tablePos.x == WALL_TABLE_WIDTH / 2 &&
				    tablePos.y == WALL_TABLE_HEIGHT - 1)
				{
					CRect   rectLeft(CVec2(31, 153), CVec2(111, 168));
					mouse.addArea(eMouseAreaG_DropObject, rectLeft, eCursor_Hand, (void*)mapLeft.x, (void*)mapLeft.y);
					CRect   rectRight(CVec2(112, 153), CVec2(192, 168));
					mouse.addArea(eMouseAreaG_DropObject, rectRight, eCursor_Hand, (void*)mapRight.x, (void*)mapRight.y);
				}
			}
		}
				
Les coordonnées des zones sont les mêmes que celles qu'on vues dans l'image au-dessus.
Ici on n'a pas besoin de tester un mur parce que comme c'est dans la rangée "arrière", on est dans la case où le
joueur se trouve.

Mais on teste les murs dans le cas de la rangée "avant":

		void CObjects::drawFrontRow(QImage* image, CVec2 mapPos, CVec2 tablePos)
		{
			[...]

			CVec2   mapLeft = mapPos * 2 + pos1;
			CVec2   mapRight = mapPos * 2 + pos2;
			drawObjectsStack(image, mapLeft, tablePos * 2 + CVec2(0, 1));
			drawObjectsStack(image, mapRight, tablePos * 2 + CVec2(1, 1));

			// zones souris pour poser un objet
			if (mouse.mObjectInHand.getType() != 0)
			{
				if (tablePos.x == WALL_TABLE_WIDTH / 2 &&
				    tablePos.y == WALL_TABLE_HEIGHT - 2 &&
				    isWallInFront() == false)
				{
					CRect   rectLeft(CVec2(46, 138), CVec2(111, 152));
					mouse.addArea(eMouseAreaG_DropObject, rectLeft, eCursor_Hand, (void*)mapLeft.x, (void*)mapLeft.y);
					CRect   rectRight(CVec2(112, 138), CVec2(177, 152));
					mouse.addArea(eMouseAreaG_DropObject, rectRight, eCursor_Hand, (void*)mapRight.x, (void*)mapRight.y);
				}
			}
		}
				
Dans les paramètres des zones de "dépose" on passe les coordonnées du tas où on va mettre l'objet.

Tester les zones

Dans CGame::update() on teste si on a cliqué dans ces zones:

		void CGame::update(SMouseArea* clickedArea)
		{
			[...]

			if (clickedArea != NULL)
			{
				if (mouse.mButtonPressing == true)
				{
					if (clickedArea->type == eMouseAreaG_Champion)
					{
						[...]
					}
					else if (clickedArea->type == eMouseAreaG_DoorButton)
					{
						[...]
					}
					else if (clickedArea->type == eMouseAreaG_PickObject)
					{
						CObjectStack*   stack = (CObjectStack*)clickedArea->param1;
						int index = (int)clickedArea->param2;
						mouse.mObjectInHand = stack->getObject(index);
						stack->removeObject(index);

						if (stack->getSize() == 0)
							map.removeObjectsStack(stack->mPos);
					}
					else if (clickedArea->type == eMouseAreaG_DropObject)
					{
						CVec2   pos = CVec2((int)clickedArea->param1, (int)clickedArea->param2);
						CObjectStack*   stack = map.addObjectsStack(pos);
						stack->addObject(mouse.mObjectInHand);
						mouse.mObjectInHand.setType(0);
					}
				}
			}
		}
				
Dans le cas du ramassage d'un objet, on le copie dans mObjectInHand, puis on le retire du tas, et enfin on teste si
le tas est vide pour l'effacer complètement.

Dans le cas de la dépose d'un objet, on l'ajoute au tas ou on en crée un nouveau (map.addObjectsStack() gère les
deux cas) puis on met le type de mObjectInHand à 0 pour "vider" la main.

Les objets comme curseurs


Pour dessiner l'objet à la place du curseur quand on en tient un, c'est assez simple.

		SMouseArea* CMouse::clickedArea(QImage* image)
		{
			[...]

			if (area == NULL || area->cursor != eCursor_None)
			{
				if (area == NULL)
				{
					[...]
				}
				else if (area->cursor == eCursor_Arrow)
				{
					[...]
				}
				else
				{
					if (mObjectInHand.getType() == 0)
					{
						QImage  cursor = fileCache.getImage("gfx/interface/CursorHand.png");
						graph2D.drawImage(image, mPos, cursor);
					}
					else
					{
						CObjects::CObjectInfo  object = objects.mObjectInfos[mObjectInHand.getType() - 1];
						QImage  cursor = fileCache.getImage(object.imageFile.toLocal8Bit().constData());
						CRect   rect = interface.getItemRect(object.imageNum);
						graph2D.drawImageAtlas(image, mPos - CVec2(ITEM_WIDTH / 2, ITEM_HEIGHT / 2), cursor, rect);
					}
				}

			}

			[...]
		}
				
On récupère le type de l'objet dans mObjectInHand et si il n'est pas à 0, on récupère la planche de sprite
correspondante et le numéro du sprite à partir de mObjectInfos.
Ensuite, on dessine le sprite avec la fonction "atlas" comme on l'a déjà fait auparavant.
Vous pouvez voir que je ne fais tout ça que dans le cas où l'on dessinait le curseur "main" auparavant.
Parce que j'ai remarqué que le curseur "flèche" avait la priorité dans le jeu d'origine.

Ecrire le nom de l'objet


Afficher le nom de l'objet au-dessus de la zone des sorts est assez facile aussi.
Comme avant, on a juste besoin de récupérer le nom de l'objet à partir de mObjectInfos en fonction de son type.

		void CInterface::drawObjectName(QImage* image)
		{
			int type = mouse.mObjectInHand.getType();

			if (type != 0)
			{
				std::string& name = objects.mObjectInfos[type - 1].name;
				drawText(image, CVec2(233, 33), eFontStandard, name.c_str(), MAIN_INTERFACE_COLOR);
			}
		}