Base item

De WikiPlee.

La classe base_item est la classe des objets de base du moteur, tous les objets du jeu ont en commun ses caractéristiques. Il s'agit grossièrement d'un physical_item auquel est ajouté la notion de visuel. Ainsi, en plus d'une position, une masse, une vitesse et des quelques autres paramètres des objets du moteur physique, il est aussi défini par une position dans la profondeur de l'affichage et possède une méthode d'affichage. Dans la structure des niveaux, il s'agit de l'élément principal des calques.

Sommaire

[modifier] Spécifications

[modifier] Paramètres

base_item (m)
Requis Nom Type Description Défaut
base_item.position.left réel abscisse du côté gauche de l'objet. 0
base_item.position.bottom réel ordonnée du côté bas de l'objet. 0
base_item.position.depth entier position orthogonale à l'écran. 0
base_item.size.height réel hauteur de la boîte englobante. 0
base_item.size.width réel largeur de la boîte englobante. 0
base_item.mass réel masse de l'objet. infini
base_item.density réel densité de l'objet. 1
base_item.elasticity réel élasticité de l'objet. 0
base_item.hardness réel dureté de l'objet. 1
base_item.system_angle réel angle de l'objet (radians). 0
base_item.artificial booléen Indique si l'objet est artificiel, auquel cas il ne sera pas pris en compte dans les collisions. faux
base_item.can_move_items booléen Indique si l'objet peut déplacer un autre objet dans une collision. vrai
base_item.phantom booléen Indique si l'objet ne doit pas être déplacé lors d'une collision. faux
base_item.global booléen Indique si l'appel de la méthode base_item::progress() doit être fait même si l'objet est hors de la zone d'activité. faux
base_item.fixed.x booléen Indique si l'objet ne peut bouger sur l'axe horizontal. faux
base_item.fixed.y booléen Indique si l'objet ne peut bouger sur l'axe vertical. faux

[modifier] L'objet d'un calque

Le plus souvent, un base_item est un objet complexe évoluant dans le monde avec d'autres objets ; mais il peut parfois avoir un rôle plus utilitaire. Ainsi, certains objets n'existent que dans un but décoratif (voir decoration_layer, pattern_layer) ou d'organisation du scénario (level_loader_item). D'autres se limitent à modifier des d'objets (applied_forced_movement), ou à modifier le monde (environment_rectangle_creator, world_parameters). Ce système permet de libérer l'éditeur de niveaux du moteur de jeu, en apportant plus de souplesse au paramétrage de ce dernier. Ainsi, lorsque de nouveaux paramètres sont modifiables dans le jeu, il n'est pas nécessaire de modifier l'éditeur ; il suffit d'ajouter un objet qui passera les paramètres au moteur, apparaissant comme n'importe quel objet du point de vue de l'éditeur. Ces objets se suppriment automatiquement du niveau une fois qu'ils ont transmis les paramètres. De plus, leurs qualités physiques sont le plus souvent ignorées. Ils sont ainsi négligeables dans la charge globale du niveau.

[modifier] Naissance, vie et mort

L'évolution d'un base_item se fait en trois étapes débutant par son ajout dans un calque. Lors dudit ajout, la méthode base_item::build() est appelée pour initialiser l'objet. Lors de cet appel, l'objet a la garantie d'appartenir à un niveau via un calque. Ainsi, il est possible d'appeler la méthode héritée level_object::get_level_globals() pour accéder aux ressources sonores et graphiques.

Une fois qu'un objet appartient à un calque, son évolution se fait de manière itérative par des appels à la méthode base_item::progress(bear::universe::time_type elapsed_time). Durant ces appels, l'objet peut faire ce qu'il veut. Le paramètre elapsed_time indique le temps écoulé du point de vue du jeu, en secondes, depuis le dernier appel. En particulier, ce paramètre peut être utilisé pour mettre à jour les animations de l'objet.

Lorsque l'objet va être supprimé du niveau, la méthode base_item::destroy est appelée. C'est la dernière occasion qu'a l'objet pour accéder aux ressources via level_object::get_level_globals(). Cette suppression se fait automatiquement lors d'un appel à la méthode base_item::kill().

Entre les appels à base_item::progress(…), l'objet peut être affiché à l'écran. Le moteur appelle alors la méthode base_item::get_visual (std::list<scene_visual>& visuals). L'objet ne peut se modifier durant cet appel. Ainsi, il faut qu'il s'y soit préparé lors d'un précédent appel à base_item::progress(…). Le moteur attend de l'objet qu'il ajoute à la liste visuals les visuels le représentant, positionnés par rapport à son coin haut-gauche.

[modifier] Notifications et interactions

Les objets faisant parti d'un monde sont informés de certains événements observés du point de vue de celui-ci.

Lorsque un objet entre en collision avec un autre, la méthode base_item::collision(base_item& that, universe::collision_info& info) est appelée. Le paramètre info donne quelques informations sur la collision, comme la position des objets lors du contact. Cette méthode est appelé deux fois par paire d'objets. Ainsi, si deux objets A et B entrent en collision, le moteur appellera A.collision(B, info_AB) et B.collision(A, info_BA).

De plus, les objets sont prévenus de leur arrivée dans la zone d'activité du monde. Cette zone décrit l'ensemble des objets pour lesquels la méthode progress() est appelée. Elle est en général bien plus petite que le monde, pour des raisons de performances, et centrée sur les joueurs et la caméra. C'est en effet autour de ces objets que le joueur veut voir vivre l'environnement.

Lorsqu'un objet entre dans cette zone, la méthode universe::physical_item::enters_active_region() est appelée. Lorsqu'il quitte la zone, c'est universe::physical_item::leaves_active_region() qui est appelée. Après cet appel, la méthode progress() n'est plus appelée jusqu'au retour de l'objet dans la zone.

Si des objets doivent impérativement évoluer même s'ils ne sont pas dans la zone d'activité, tels que des plates-formes se déplaçant de manière synchrone, il suffit d'appeler la méthode physical_item_state::set_global(bool) avant de l'ajouter au monde.

[modifier] Construction d'un objet

Il existe deux façons de créer un objet : dans le code ou depuis l'éditeur de niveaux. Dans tous les cas, il faut ne pas appeler l'opérateur delete d'un objet qui a été ajouté à un niveau. S'il n'a pas été ajouté, par contre, il faudra libérer la mémoire avec cet opérateur.

[modifier] Instanciation directe dans le code

Un objet créé dans le programme doit être instancié dynamiquement via l'opérateur new. L'initialisation se fait par des accesseurs puis, quand l'objet est totalement initialisé, il faut l'ajouter au niveau en appelant la méthode base_item::new_item(base_item&). C'est lors de cet appel que la méthode build() sera appelée. Il faut donc ne pas l'appeler explicitement. Après cet appel, l'objet évolue dans le calque comme ses pairs.

/* Instanciation de l'objet. */
base_item* item = new item_reference;
 
/* Paramétrage */
item->set_size(24, 216);
item->set_bottom_left( get_bottom_left() );
 
/* Vérification de la validité de l'objet, pour débogage. */
assert( item->is_valid() );
 
/* Ajout dans le monde. Cet appel effectuera l'appel à item->build(). */
new_item( *item );

[modifier] Instanciation depuis l'éditeur de niveaux

La seconde méthode de construction consiste à ajouter l'objet désiré dans le niveau depuis l'éditeur. Dans ce cas, le programmeur n'a rien à faire. C'est le concepteur du niveau qui définit la valeur des paramètres. L'objet sera ajouté au niveau au chargement de celui-ci.

Le paramétrage de l'objet se fait alors par l'appel des méthodes virtuelles d'affectation de champ, nommées base_item::set_type_field(nom, valeur) et base_item::set_type_list_field(nom, valeur), où type peut être égal à :

  • int pour un entier ;
  • u_int pour un entier non négatif ;
  • string pour une chaîne de caractères ;
  • real pour un nombre réel ;
  • item pour un pointeur sur un objet.

Dans le dernier cas, il se peut que l'objet passé en paramètre ne soit pas encore initialisé.

Ces méthodes permettent de donner des valeurs aux champs indiqués dans les fichiers de description d'objet utilisés par l'éditeur de niveaux. La première signature affecte une valeur singleton à un champ, l'autre affecte une liste de valeurs. Elles sont appelées avant la validation par la méthode base_item::is_valid(), qui intervient avant l'appel à base_item::build(). Ainsi, il faut considérer que l'objet n'est pas encore initialisé lors de l'exécution de ces méthodes et, en particulier, qu'il n'est pas encore dans un niveau. Un appel à level_object::get_level_globals() échouera donc.

Pour initialiser un champ d'un objet directement dans le code, on utilisera un modificateur (set()) et surtout pas un appel à une méthode d'affectation de champ. Il y a plusieurs raisons pour cela. Tout d'abord, le contexte est différent. Un modificateur peut être utilisé n'importe quand alors que les méthodes d'affectation de champ sont utilisées avant l'initialisation. De plus, si le nom d'un champ change durant le développement, il sera difficile de trouver le modifications à faire dans le code. Enfin, ces méthodes utilisent des chaînes de caractères pour identifier les champs et parcourent la hiérarchie de la classe par appels récursifs, ce qui est loin d'être aussi rapide qu'un modificateur.

Pour qu'un objet soit accessible depuis l'éditeur de niveaux, il faut d'une part qu'il ait été exporté depuis le code avec la macro BASE_ITEM_EXPORT (voir la section « Instanciation et copie » ci-dessous) et d'autre part qu'un fichier de description d'objet ait été créé pour cet objet, dans un dossier accessible par l'éditeur.

[modifier] Écriture d'un objet

Lors de l'écriture d'une classe héritant de base_item, il faut garder en tête certaines limitations.

[modifier] Imbrication des appels aux classes mères

Les méthodes build(), progress(…) et get_visual(…) devraient toujours débuter par un appel à la fonction équivalente de la classe mère afin que le traitement par défaut soit effectué. De même, la définition de la méthode destroy() doit se terminer par un appel à la méthode de la classe mère et is_valid() doit vérifier que l'objet est valide du point de vue de la classe mère.

Les constructeurs, le destructeur et les méthodes d'affectation de champ doivent ne pas faire appel à level_object::get_level_globals(). Ceci est dû au fait que l'objet n'est, en général, pas encore été ajouté au niveau lors de ces appels. Il ne sera ajouté qu'après avoir été validé par la méthode base_item::is_valid().

Lorsqu'un objet est ajouté à un niveau, sa méthode base_item::pre_cache() est appelée. Cette méthode sert à pré-charger les ressources nécessaires à l'objet et elle devrait débuter par un appel à la méthode équivalente de la classe mère. Pour charger les ressources, il suffit d'utiliser les méthodes de l'objet level_globals du niveau, obtenu par un appel à base_item::get_level_globals().

[modifier] Instanciation et copie

Tous les objets doivent être copiables. Ainsi, il peut être nécessaire de définir un constructeur par copie. L'opérateur d'affectation est, quant à lui, facultatif.

De plus, la définition de la classe de l'objet doit contenir une ligne appelant la macro DECLARE_BASE_ITEM(nom_de_la_classe, espace_de_noms), comme indiqué ci-dessous.

/** \file ma_classe.hpp */
 
#include "engine/export.hpp"
 
namespace bear
{
  class ma_classe:
    public base_item
  {
    DECLARE_BASE_ITEM(ma_classe, bear);
  }; // class ma_class
} // namespace bear

Selon que l'objet soit utilisable depuis l'éditeur de niveau ou qu'il n'ai de sens qu'en interne (comme le dard de la guêpe par exemple), le fichier .cpp doit contenir un appel à l'une des macros BASE_ITEM_EXPORT( ma_classe, espace_de_noms ) ou, respectivement, BASE_ITEM_IMPLEMENT( ma_classe, espace_de_noms ).

/** \file ma_classe.cpp */
 
// si l'objet est utilisable dans l'éditeur de niveau
BASE_ITEM_EXPORT(ma_classe, bear)
// dans les autres cas :
BASE_ITEM_IMPLEMENT(ma_classe, bear)

L'utilisation de ces macros définit automatiquement une méthode nom_de_la_classe* clone() const, redéfinition de la méthode virtuelle base_item* base_item::clone() const et retournant une copie d'un objet. C'est très utile lorsqu'on gère des objets par pointeurs sur base_item mais que l'on a besoin de faire des copies concrètes. Par exemple, l'objet auquel appartient le code suivant crée des copies des objets qui entrent en collision avec lui.

void duplicator::collision( base_item& that, collision_info& info )
{
  /* Créer une copie de l'objet touchant. */
  base_item* c = that.clone();
 
  /* Le centre de la copie est aligné avec le centre du duplicateur. */
  c->set_center_of_mass( get_center_of_mass() );
 
  /* La copie est ajoutée au monde. */
  new_item(*c);
 
  /* On lui applique une force pour l'éloigner. */
  c->add_force( 150000, 150000 );
}

L'instance pointée par c sera du même type réel que that. Ainsi, si that est en réalité une instance de stone, alors la copie sera aussi une instance de stone. De même, si that est une instance de kicker, alors c pointera sur une instance de kicker, etc.