
Big Tuto SFML 2 : Rabidja v. 3.0
Annexe 3 : Gestion des tiles lumières !
Tutoriel présenté par : Skrool
Relecture et corrections : Jérémie F. Bellanger (Jay81)
Date d'écriture : 5 avril 2015
Date de révision : 20 mars 2016
Introduction
Maintenant que nous avons créé nos lumières, il va être temps de raccorder notre système avec nos tiles, comme on a fait avec les tiles d'obscurité ! 
On est reparti ! 
Ajoutons nos tiles lumières
Tout d'abord, mettons à jour nos constantes dans map.h :
Fichier : map.h, rajouter :
|
//Tiles pour les lumières
const int TILE_LUMIERE_DEBUT = 141;
const int TILE_LUMIERE_FIN = 148;
const int TILE_LUMIERE_OMNIDIRECTIONNELLE = 141;
const int TILE_LUMIERE_PLONGEANTE_GAUCHE = 142;
const int TILE_LUMIERE_PLONGEANTE_BAS = 143;
const int TILE_LUMIERE_PLONGEANTE_DROITE = 144;
const int TILE_LUMIERE_PLONGEANTE_MOUVANTE = 145;
const int TILE_LUMIERE_DOUCHE_BAS = 146;
const int TILE_LUMIERE_DOUCHE_HAUT = 147;
const int TILE_LUMIERE_TOURNANTE = 148;
|
Fichier : map.h, rajouter la fonction surchargée setTile() :
|
//Mutateurs
void setLevel(int valeur);
void setStartX(int valeur);
void setStartY(int valeur);
void setTile(int y, int x, int valeur);
//Rajouter la ligne ci-dessous :
void setTile(int y, int x, int valeur, int couche);
void setNombreMonstres(int valeur);
void setNombrePlateformes(int valeur);
|
Voilà, et donc forcément, il va nous falloir rajouter le nouveau mutateur dans map.cpp
:
Fichier : map.cpp : Rajouter :
|
void Map::setTile(int y, int x, int valeur, int couche)
{
if (couche == 1)
tile[y][x] = valeur;
else if (couche == 2)
tile2[y][x] = valeur;
else if (couche == 3)
tile3[y][x] = valeur;
}
|
Fichier : map.cpp : Modifier la fonction draw() :
|
// Couche 1
/* On teste si c'est une lumière*/
else if (tile[mapY][mapX] > TILE_LUMIERE_DEBUT - 1 && tile[mapY][mapX] < TILE_LUMIERE_FIN + 1)
{
if (tile[mapY][mapX] == TILE_LUMIERE_OMNIDIRECTIONNELLE)
manager.AddLight(15, sf::Color::White, 200, 250,
sf::Vector2f(mapX*TILE_SIZE + TILE_SIZE / 2, mapY*TILE_SIZE + TILE_SIZE / 2),
1, *this, mapX, mapY, 1);
else if (tile[mapY][mapX] == TILE_LUMIERE_PLONGEANTE_GAUCHE)
manager.AddDirectionalLight(sf::Color::White, 200, 200,
sf::Vector2f(mapX*TILE_SIZE + TILE_SIZE / 2, mapY*TILE_SIZE + TILE_SIZE / 2),
1, 120, 50, *this, mapX, mapY, 1);
else if (tile[mapY][mapX] == TILE_LUMIERE_PLONGEANTE_BAS)
manager.AddDirectionalLight(sf::Color::White, 200, 200,
sf::Vector2f(mapX*TILE_SIZE + TILE_SIZE / 2, mapY*TILE_SIZE + TILE_SIZE / 2),
1, 90, 50, *this, mapX, mapY, 1);
else if (tile[mapY][mapX] == TILE_LUMIERE_PLONGEANTE_DROITE)
manager.AddDirectionalLight(sf::Color::White, 200, 200,
sf::Vector2f(mapX*TILE_SIZE + TILE_SIZE / 2, mapY*TILE_SIZE + TILE_SIZE / 2),
1, 50, 50, *this, mapX, mapY, 1);
else if (tile[mapY][mapX] == TILE_LUMIERE_PLONGEANTE_MOUVANTE)
manager.AddDirectionalLight(sf::Color::White, 200, 200,
sf::Vector2f(mapX*TILE_SIZE + TILE_SIZE / 2, mapY*TILE_SIZE + TILE_SIZE / 2),
2, 90, 50, *this, mapX, mapY, 1);
else if (tile[mapY][mapX] == TILE_LUMIERE_DOUCHE_BAS)
manager.AddDirectionalLight(sf::Color::White, 200, 200,
sf::Vector2f(mapX*TILE_SIZE + TILE_SIZE / 2, mapY*TILE_SIZE + TILE_SIZE / 2),
1, 90, 80, *this, mapX, mapY, 1);
else if (tile[mapY][mapX] == TILE_LUMIERE_DOUCHE_HAUT)
manager.AddDirectionalLight(sf::Color::White, 200, 200,
sf::Vector2f(mapX*TILE_SIZE + TILE_SIZE / 2, mapY*TILE_SIZE + TILE_SIZE / 2),
1, 270, 80, *this, mapX, mapY, 1);
else if (tile[mapY][mapX] == TILE_LUMIERE_DOUCHE_BAS)
manager.AddDirectionalLight(sf::Color::White, 200, 200,
sf::Vector2f(mapX*TILE_SIZE + TILE_SIZE / 2, mapY*TILE_SIZE + TILE_SIZE / 2),
3, 90, 70, *this, mapX, mapY, 1);
//On remplace la tile par du vide
tile[mapY][mapX] = 0;
}
// Couche 2
/* On teste si c'est une lumière*/
else if (tile2[mapY][mapX] > TILE_LUMIERE_DEBUT - 1 && tile2[mapY][mapX] < TILE_LUMIERE_FIN + 1)
{
if (tile2[mapY][mapX] == TILE_LUMIERE_OMNIDIRECTIONNELLE)
manager.AddLight(15, sf::Color::White, 200, 250,
sf::Vector2f(mapX*TILE_SIZE + TILE_SIZE / 2, mapY*TILE_SIZE + TILE_SIZE / 2),
1, *this, mapX, mapY, 2);
else if (tile2[mapY][mapX] == TILE_LUMIERE_PLONGEANTE_GAUCHE)
manager.AddDirectionalLight(sf::Color::White, 200, 200,
sf::Vector2f(mapX*TILE_SIZE + TILE_SIZE / 2, mapY*TILE_SIZE + TILE_SIZE / 2),
1, 120, 50, *this, mapX, mapY, 2);
else if (tile2[mapY][mapX] == TILE_LUMIERE_PLONGEANTE_BAS)
manager.AddDirectionalLight(sf::Color::White, 200, 200,
sf::Vector2f(mapX*TILE_SIZE + TILE_SIZE / 2, mapY*TILE_SIZE + TILE_SIZE / 2),
1, 90, 50, *this, mapX, mapY, 2);
else if (tile2[mapY][mapX] == TILE_LUMIERE_PLONGEANTE_DROITE)
manager.AddDirectionalLight(sf::Color::White, 200, 200,
sf::Vector2f(mapX*TILE_SIZE + TILE_SIZE / 2, mapY*TILE_SIZE + TILE_SIZE / 2),
1, 50, 50, *this, mapX, mapY, 2);
else if (tile2[mapY][mapX] == TILE_LUMIERE_PLONGEANTE_MOUVANTE)
manager.AddDirectionalLight(sf::Color::White, 200, 200,
sf::Vector2f(mapX*TILE_SIZE + TILE_SIZE / 2, mapY*TILE_SIZE + TILE_SIZE / 2),
2, 90, 50, *this, mapX, mapY, 2);
else if (tile2[mapY][mapX] == TILE_LUMIERE_DOUCHE_BAS)
manager.AddDirectionalLight(sf::Color::White, 200, 200,
sf::Vector2f(mapX*TILE_SIZE + TILE_SIZE / 2, mapY*TILE_SIZE + TILE_SIZE / 2),
1, 90, 80, *this, mapX, mapY, 2);
else if (tile2[mapY][mapX] == TILE_LUMIERE_DOUCHE_HAUT)
manager.AddDirectionalLight(sf::Color::White, 200, 200,
sf::Vector2f(mapX*TILE_SIZE + TILE_SIZE / 2, mapY*TILE_SIZE + TILE_SIZE / 2),
1, 270, 80, *this, mapX, mapY, 2);
else if (tile2[mapY][mapX] == TILE_LUMIERE_DOUCHE_BAS)
manager.AddDirectionalLight(sf::Color::White, 200, 200,
sf::Vector2f(mapX*TILE_SIZE + TILE_SIZE / 2, mapY*TILE_SIZE + TILE_SIZE / 2),
3, 90, 70, *this, mapX, mapY, 2);
//On remplace la tile par du vide
tile2[mapY][mapX] = 0;
}
// Couche 3
/* On teste si c'est une lumière*/
else if (tile3[mapY][mapX] > TILE_LUMIERE_DEBUT - 1 && tile[mapY][mapX] < TILE_LUMIERE_FIN + 1)
{
if (tile3[mapY][mapX] == TILE_LUMIERE_OMNIDIRECTIONNELLE)
manager.AddLight(15, sf::Color::White, 200, 250,
sf::Vector2f(mapX*TILE_SIZE + TILE_SIZE / 2, mapY*TILE_SIZE + TILE_SIZE / 2),
1, *this, mapX, mapY, 3);
else if (tile3[mapY][mapX] == TILE_LUMIERE_PLONGEANTE_GAUCHE)
manager.AddDirectionalLight(sf::Color::White, 200, 200,
sf::Vector2f(mapX*TILE_SIZE + TILE_SIZE / 2, mapY*TILE_SIZE + TILE_SIZE / 2),
1, 120, 50, *this, mapX, mapY, 3);
else if (tile3[mapY][mapX] == TILE_LUMIERE_PLONGEANTE_BAS)
manager.AddDirectionalLight(sf::Color::White, 200, 200,
sf::Vector2f(mapX*TILE_SIZE + TILE_SIZE / 2, mapY*TILE_SIZE + TILE_SIZE / 2),
1, 90, 50, *this, mapX, mapY, 3);
else if (tile3[mapY][mapX] == TILE_LUMIERE_PLONGEANTE_DROITE)
manager.AddDirectionalLight(sf::Color::White, 200, 200,
sf::Vector2f(mapX*TILE_SIZE + TILE_SIZE / 2, mapY*TILE_SIZE + TILE_SIZE / 2),
1, 50, 50, *this, mapX, mapY, 3);
else if (tile3[mapY][mapX] == TILE_LUMIERE_PLONGEANTE_MOUVANTE)
manager.AddDirectionalLight(sf::Color::White, 200, 200,
sf::Vector2f(mapX*TILE_SIZE + TILE_SIZE / 2, mapY*TILE_SIZE + TILE_SIZE / 2),
2, 90, 50, *this, mapX, mapY, 3);
else if (tile3[mapY][mapX] == TILE_LUMIERE_DOUCHE_BAS)
manager.AddDirectionalLight(sf::Color::White, 200, 200,
sf::Vector2f(mapX*TILE_SIZE + TILE_SIZE / 2, mapY*TILE_SIZE + TILE_SIZE / 2),
1, 90, 80, *this, mapX, mapY, 3);
else if (tile3[mapY][mapX] == TILE_LUMIERE_DOUCHE_HAUT)
manager.AddDirectionalLight(sf::Color::White, 200, 200,
sf::Vector2f(mapX*TILE_SIZE + TILE_SIZE / 2, mapY*TILE_SIZE + TILE_SIZE / 2),
1, 270, 80, *this, mapX, mapY, 3);
else if (tile3[mapY][mapX] == TILE_LUMIERE_DOUCHE_BAS)
manager.AddDirectionalLight(sf::Color::White, 200, 200,
sf::Vector2f(mapX*TILE_SIZE + TILE_SIZE / 2, mapY*TILE_SIZE + TILE_SIZE / 2),
3, 90, 70, *this, mapX, mapY, 3);
//On remplace la tile par du vide
tile3[mapY][mapX] = 0;
}
|
Fichier : light.cpp : Remplacer le code précédent par :
|
void Light::update(Map &map)
{
// On rajoute une partie de la différence entre les valeurs r, g et b des
//deux variables pour avoir un effet de transition en douceur
if (m_shadowColor.r < m_targetShadowColor.r)
m_shadowColor.r++;
else if (m_shadowColor.r > m_targetShadowColor.r)
m_shadowColor.r--;
if (m_shadowColor.g < m_targetShadowColor.g)
m_shadowColor.g++;
else if (m_shadowColor.g > m_targetShadowColor.g)
m_shadowColor.g--;
if (m_shadowColor.b < m_targetShadowColor.b)
m_shadowColor.b++;
else if (m_shadowColor.b > m_targetShadowColor.b)
m_shadowColor.b--;
// Si le timer est arrivé à 0, on met la variable timerOk à true.
bool timerOk = decrementeTimer();
//On boucle sur toutes les lumières
for (int i = 0; i<m_light.size(); i++)
{
//On met à jour les coordonnées m_position de la lumière par rapport à
//sa position sur la carte, et à map.startX et startY
m_light[i].move(sf::Vector2f((m_light[i].getAbsolutePosition().x
- m_light[i].getPosition().x) - map.getStartX(),
(m_light[i].getAbsolutePosition().y - m_light[i].getPosition().y)
- map.getStartY()));
// Si la lumière est hors de l'écran, on détruit et on replace la tile
//source par la tile correspondante
if (m_light[i].getPosition().x + m_light[i].GetRadius() < 0 ||
m_light[i].getPosition().x - m_light[i].GetRadius() > 800 ||
m_light[i].getPosition().y + m_light[i].GetRadius() < 0 ||
m_light[i].getPosition().y - m_light[i].GetRadius() > 600)
{
//Si les coordonnées de la tile sont pas 0,0 ( = ne provient pas d'une tile)
//on remplace la tile par la tile de lumière correspondant
if (m_light[i].getSourceX() != 0 || m_light[i].getSourceY() != 0)
{
map.setTile(m_light[i].getSourceY(), m_light[i].getSourceX(),
m_light[i].getSourceID(), m_light[i].getCouche());
}
m_light[i].clear();
m_light.erase(m_light.begin() + i);
//et on baisse i de 1, sinon gare aux erreurs d'allocation
if (i >0) i--;
else break;
}
if (timerOk) //Si le timer s'est écoulé on met à jour la lumière
m_light[i].update(map);
}
}
|
|
//Tile source, pour replacer la tile de lumière à sa place quand
//elle sortira de l'écran pour économiser du CPU
int m_sourceX, m_sourceY, m_sourceID, m_couche;
|
Fichier : light.h : Faire les modifications nécessaires :
|
//Ajout de lumière
void AddLight(int quality, sf::Color color, float radius, float intensity,
sf::Vector2f position, int type, Map &map, int sourceX, int sourceY, int couche);
void AddDirectionalLight(sf::Color color, float radius, float intensity, sf::Vector2f position,
int type, float angle, float o_angle, Map &map, int sourceX, int sourceY, int couche);
|
Fichier : light.cpp : Faire les modifications nécessaires :
|
void Light::AddLight(int quality, sf::Color color, float radius, float intensity,
sf::Vector2f position, int type, Map &map, int sourceX, int sourceY, int couche)
{
if (m_light.size() < MAX_LIGHT)
{
m_light.push_back(lightEntity());
m_light.back().create(quality, color, radius, intensity, position, type,
map, sourceX, sourceY, couche);
}
}
void Light::AddDirectionalLight(sf::Color color, float radius, float intensity,
sf::Vector2f position, int type, float angle, float o_angle, Map &map, int sourceX,
int sourceY, int couche)
{
if (m_light.size() < MAX_LIGHT)
{
m_light.push_back(lightEntity());
m_light.back().createDirectionalLight(color, radius, intensity, position,
type, angle, o_angle, map, sourceX, sourceY, couche);
}
}
|
Fichier : lightEntity.h : Faire les modifications nécessaires :
|
//Création d'une lumière omnidirectionnelle
void create(int quality, sf::Color color, float radius, float intensity, sf::Vector2f position,
int type, Map &map, int sourceX, int sourceY, int couche);
//Création d'une lumière directionnelle
void createDirectionalLight(sf::Color color, float radius, float intensity, sf::Vector2f position,
int type, float angle, float o_angle, Map &map, int sourceX, int sourceY, int couche);
|
Fichier : lightEntity.cpp : Faire les modifications nécessaires :
|
void lightEntity::create(int quality, sf::Color color, float radius, float intensity,
sf::Vector2f position, int type, Map &map, int sourceX, int sourceY, int couche)
{
//Initialisation de variables
m_quality = quality;
m_color = color;
m_radius = radius;
m_intensity = intensity;
m_absoluteX = (int)position.x;
m_absoluteY = (int)position.y;
m_position = position; // Par défaut. Sera modifié au prochain update
m_type = type;
//Récupération des données de tiles
m_sourceX = sourceX;
m_sourceY = sourceY;
m_couche = couche;
m_sourceID = map.getTile(sourceY, sourceX);
m_directionnal = false;
Generate(map);
}
void lightEntity::createDirectionalLight(sf::Color color, float radius, float intensity,
sf::Vector2f position, int type, float angle, float o_angle, Map &map, int sourceX,
int sourceY, int couche)
{
//Initialisation de variables
m_color = color;
m_radius = radius;
m_intensity = intensity;
m_type = type;
m_angle = angle;
m_opening_angle = o_angle;
m_absoluteX = (int)position.x;
m_absoluteY = (int)position.y;
m_position = position; // Par défaut. Sera modifié au prochain update
//Récupération des données de tiles
m_sourceX = sourceX;
m_sourceY = sourceY;
m_couche = couche;
m_sourceID = map.getTile(sourceY, sourceX);
m_directionnal = true;
Generate(map);
}
|
Et voilà pour les couches ! ![]()
Par contre, comme la map se recharge à chaque mort, il faut aussi nettoyer les lumières, donc dans la fonction initialize() de player.cpp, on rajoute tout à la fin :
Fichier : player.cpp, rajouter :
|
//On réinitialise les lumières
manager.clear();
manager.setTargetShadowColor(sf::Color::White);
|
On compile, et ... MAGNIFIQUE !!
Cela marche ! ![]()
Ou pas... ![]()
Note de Jay : Pour ma part, j'ai eu ici un problème avec Visual Studio 2013, alors que Skrool n'avait aucun souci avec Xcode sur Mac !
Bon, je laisse la guerre des Trolls Windows vs Mac pour d'autres sites, plus spécialisés là-dedans.
Et je vais simplement essayer de vous expliquer comment j'ai résolu le problème.
Si, maintenant, tout roule pour vous, vous pouvez passer ce qui suit ! ![]()
Voilà, donc comme je le disais, je compilais vaillamment sur Visual Studio, quand mon compilo m'a craché à la figure l'erreur suivante :
error C2582: 'operator =' fonction non disponible dans 'lightEntity'
D'abord dubitatif, je me suis dit : "Allons, bon, on recompile tout à zéro, et ça devrait lui passer !"
Mais, non ! ![]()
Alors, j'ai cherché 5 minutes sur internet, et j'ai vu que le compilo n'était pas content car il ne trouvait pas de constructeur de copie par opération d'affectation (=) valide.
OK, c'est vrai que j'en ai pas écrit, mais normalement, il devrait pouvoir en faire un bateau, tout seul comme un grand, non ?! ![]()
Eh ben, non (sans que je sache pourquoi Xcode y arrive, lui !).
De plus, pourquoi a-t-il besoin d'un tel constructeur, qui pour ceux qui n'auraient pas suivi, sert simplement à copier un objet dans un autre, en faisant Objet1 = Objet2; ?
Et bien, en cherchant un peu dans la lib standard, il semblerait que cela vienne de nos tableaux dynamiques. En effet, quand on supprime un objet du tableau, les autres sont déplacés à l'aide d'une fonction move(), qui fait ni plus ni moins que la même chose que moi dans mes tableaux statiques. Elle copie donc des objets du tableau à la place des éléments supprimés.
Or, c'est ici que ça coince, puisque VS ne sait pas comment faire lightEntity1 = lightEntity2;, par exemple.
Soit, s'il n'y a que ça ! Créons un constructeur par opérateur d'affectation, dans le fichier lightEntity.cpp :
Fichier : lightEntity.cpp, rajouter :
|
// Constructeur par opérateur d'affectation =
lightEntity& lightEntity::operator=(const lightEntity& source)
{
// On copie les variables d'un objet à l'autre
m_quality = source.GetQuality();
m_color = source.GetColor();
m_radius = source.GetRadius();
m_intensity = source.GetIntensity();
m_absoluteX = source.getAbsolutePosition().x;
m_absoluteY = source.getAbsolutePosition().y;
m_position = source.getPosition();
m_type = source.getType();
m_sourceX = source.getSourceX();
m_sourceY = source.getSourceY();
m_couche = source.getCouche();
m_sourceID = source.getSourceID();
m_directionnal = source.getDirectionnal();
m_angle = source.getAngle();
m_opening_angle = source.getO_Angle();
m_timer = source.getTimer();
m_targetIntensity = source.getTargetIntensity();
m_triangle = source.getTriangle();
return *this;
}
|
Cela ressemble beaucoup à nos fonctions copy() précédentes.
Fichier : lightEntity.cpp, rajouter :
|
int lightEntity::getType(void) const { return m_type; }
int lightEntity::getDirectionnal(void) const { return m_directionnal; }
float lightEntity::getAngle(void) const { return m_angle; }
float lightEntity::getO_Angle(void) const { return m_opening_angle; }
int lightEntity::getTimer(void) const { return m_timer; }
int lightEntity::getCouche(void) const { return m_couche; }
float lightEntity::getTargetIntensity(void) const { return m_targetIntensity; }
std::vector <sf::VertexArray> lightEntity::getTriangle(void) const { return m_triangle; }
|
Voilà, et n'oublions pas le header, à présent ! ![]()
Fichier : lightEntity.h, rajouter :
|
// Opérateur d'affectation
lightEntity& operator=(lightEntity const&);
// Et plus loin, nos nouveaux prototypes d'accesseurs :
int getType(void) const;
int getDirectionnal(void) const;
float getAngle(void) const;
float getO_Angle(void) const;
int getTimer(void) const;
int getCouche(void) const;
float getTargetIntensity(void) const;
std::vector <sf::VertexArray> getTriangle(void) const;
|
Et voilà pour ce passage ! ![]()
Normalement, Skrool avait prévu de ne faire qu'un chapitre des annexes 2, 3 et 4, mais je vais encore reporter la suite au chapitre suivant, car cela me semble déjà bien suffisant pour un chapitre. ![]()
Au chapitre suivant, Skrool vous montrera comment améliorer ce système en rendant l'affichage des lumières plus doux, et en ajoutant un shader de blur (= flou en anglais). ![]()

@ bientôt pour l'annexe 4 ! ![]()
Skrool

English
Français 