1. Pré-requis▲
Cet article explique comment débuter avec kjAPI. Il est du niveau débutant (mes amis professionnels y jetteront peut être aussi un oeil critique :o) ).
Les pré-requis sont les suivants :
- Avoir des connaissances en C++ (les bases devraient suffire)
- Une version de Visual Studio supérieure à 6.0 (le tutoriel utilise la version 7.1,
mais les paramètres sont similaires pour les autres versions)
(note : il est également possible de compiler avec Gcc) - La version telechargeable de la kjAPI (http://dev.kjapi.com/)
2. Mise en place▲
Avant tout il faut installer la kjAPI. Ensuite il nous faut configurer l'environnement de développement :
a. Lancez Visual Studio
b. Ouvrez les options (menu Tools > Options) puis sélectionnez la catégorie
Projects > VC++ Directories. Ajoutez le chemin des fichiers d'en-têtes de
votre installation kjAPI (voir figure)
c. De la même façon ajoutez les chemins vers les répertoires de bibliothèques.
Ces réglages resteront valides pour tous les projets kjAPI. Il n'est donc pas nécessaire de les renouveler à chaque projet.
3. Création du projet▲
Vous allez créer un nouveau projet.
Allez dans File > New > Project puis choisissez Visual C++ projects > Win32 project.
Choisissez un nom de répertoire puis un nom de projet puis cliquez sur OK.
Dans la fenêtre suivante, allez dans "Application settings" puis sélectionnez "Win32 application" et "Empty project".
Appuyez sur Terminer
Il nous faut maintenant configurer les propriétés du projet. Il suffit de changer une seule
option du fait que les bibliothèques kjAPI sont compilées pour le multithread.
Allez dans le menu Project > Properties puis dans la catégorie
C/C++ > Code generation, changez l'option "runtime library".
(Faites la même chose pour la configuration release.)
Le projet est maintenant prêt. Nous pouvons y ajouter le plus intéressant : les fichiers sources.
4. Initialisation▲
kjAPI fournit une classe très utile pour commencer un projet : la classe kjGameFramework.
Cette classe est une classe "abstraite". Il nous faut donc créer une classe personnalisée qui
redéfinira le comportement des quelques fonctionnalités fournies par kjGameFramework.
Nous appellerons cette classe Application (je n'entrerai pas en détail sur les raisons du choix de ce nom :) )
Comme le projet ne contient aucun fichier source. Créez un fichier Application.h et ajoutez-le au projet. Dans ce fichier nous allons définir la classe Application.
#ifndef __APPLICATION_H__
#define __APPLICATION_H__
#include
<kjGameFramework.h>
class
Application : public
kjGameFramework
{
protected
:
virtual
kjRenderer*
OnCreateRenderer(const
kjStr&
RendererName);
public
:
Application();
virtual
bool
Init(const
char
*
lpstrGameName);
virtual
bool
UpdateFrame();
virtual
void
DoFrame();
virtual
void
Release();
}
;
#endif
Comme vous le constatez la définition est plutôt simple. Nous redéfinissons 5 méthodes :
OnCreateRenderer :
Cette méthode permet à l'utilisateur de créer la fenêtre de l'application et de choisir le
moteur de rendu qu'il souhaite. Elle est appelée automatiquement par kjGameFramework::Init().
Init :
Cette méthode permet d'initialiser l'application.
UpdateFrame :
Cette méthode est appelée a chaque "frame" avant la méthode de tracé.
Elle permet de mettre à jour les états de l'application.
DoFrame :
Cette méthode est appelée à chaque frame pour le tracé de l'application
Release :
Cette méthode est appelée en fin d'application pour permettre la libération des ressources
allouées.
Avant d'entrer en détail dans l'implémentation des ces méthodes attardons nous sur le point
d'entrée de l'application.
L'application étant une application Win32 nous devons définir une fonction WinMain qui sera
appelée par Windows lors du démarrage de l'application. C'est dans cette fonction que nous
allons créer l'instance de l'application.
Cette fonction sera implémentée dans le fichier Application.cpp.
Comme ce fichier n'est pas présent dans le projet il faut donc le créer et l'ajouter à ce
dernier.
Une fois fait ajoutez le code suivant :
#include
"Application.h"
#include
<windows.h>
//---------------------------------------------------------------------------------
///
Entry point of program
//---------------------------------------------------------------------------------
int
APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int
nCmdShow)
{
Application Game;
Game.Init("kjAPI - Tutorial #1"
);
Game.Run();
Game.Release();
return
0
;
}
Comme vous le constatez le code parle de lui-même.
Une instance de la classe Application est créée sur la pile (elle sera donc détruite
automatiquement à la sortie de la fonction WinMain).
La méthode Init() est appelée ensuite.
Puis vient l'appel de méthode Run() qui va entrer dans une boucle qui durera tant que
l'utilisateur n'aura pas donné l'ordre de quitter l'application.
En interne cette méthode Run() appelle à tour de rôle la méthode UpdateFrame()
et DoFrame(). Si UpdateFrame() renvoie true alors la boucle est interrompue et
l'exécution redonne la main à la fonction WinMain pour la suite des événements.
Afin de libérer "proprement" les ressources allouées la méthode Release() est appelée.
Puis l'application se termine.
Maintenant que nous avons tout le code de démarrage de l'application il ne nous reste plus qu'à nous concentrer sur l'implémentation de chaque méthode de la classe Application.
Pour rendre le code moins ennuyeux et que l'effort soit récompensé, l'application aura pour but d'afficher un modèle en 3D et que l'utilisateur soit en mesure de tourner autour au moyen du clavier afin de l'observer.
Nous allons donc modifier légèrement la classe Application pour avoir tout ce qu'il nous
faut.
Nous avons besoin d'une caméra de type RTS (qui orbite autour d'un point défini) ainsi que
d'un modèle.
Pour la caméra nous allons utiliser la classe kjRTSCamera qui fournit tous les services
désirés.
Pour le modèle nous avons besoin d'un pointeur sur la classe kjNode (qui permet de gérer des
hiérarchies de modèles).
Voici le nouveau Application.h :
#ifndef __APPLICATION_H__
#define __APPLICATION_H__
#include
<kjGameFramework.h>
#include
<kjRTSCamera.h>
class
kjNode;
class
Application : public
kjGameFramework
{
private
:
kjRTSCamera m_Camera;
kjNode*
m_pNode;
protected
:
virtual
kjRenderer*
OnCreateRenderer(const
kjStr&
RendererName);
public
:
Application();
virtual
bool
Init(const
char
*
lpstrGameName);
virtual
bool
UpdateFrame();
virtual
void
DoFrame();
virtual
void
Release();
}
;
#endif
5. La méthode OnCreateRenderer()▲
La méthode OnCreateRenderer est simple : elle doit créer une instance de moteur de rendu et y
attacher une fenêtre pour que l'affichage soit possible.
Pour cela elle utilise les variables membres de la classe kjGameFramework.
kjRenderer*
Application::
OnCreateRenderer(const
kjStr&
RendererName)
{
kjRenderer *
pRdr =
NULL
;
pRdr =
kjNew(kjDx9Renderer, KJ_MEMPOOL_GRAPHICS);
pRdr->
CreateAndAttachWindow("kjAPI - [DX9 renderer]"
,
m_XRes, m_YRes, m_Depth, m_Stencil);
return
pRdr;
}
Le moteur de rendu est géré par la classe abstraite kjRenderer. Il est possible de créer deux types
de moteur de rendu (OpenGL ou DirectX), la classe abstraite permettant d'avoir une seule et unique
interface d'utilisation quelque soit le choix initial.
La création du moteur de rendu est une instanciation de la classe kjDx9Renderer.
Vous remarquerez que l'opérateur C++ new n'a pas été employé. En effet kjAPI utilise
des macros définitions pour les allocations et désallocations, qui lui permettent de traquer plus facilement
les fuites de mémoire et de gérer la mémoire à sa façon.
Il nous faut ensuite créer une fenêtre et l'associer au moteur de rendu. La création de la
fenêtre est un simple appel a la methode CreateAndAttach de la class kjRenderer.
Les paramètres sont la taille en x et y de la fenêtre, la précision du buffer de profondeur et
l'utilisation ou non du stencil buffer. Il faut également préciser si l'on veut un mode fenêtre
ou plein ecran.
Ces valeurs sont définies par défaut et peuvent être modifiées au bon vouloir de chacun.
6. La méthode Init()▲
Cette méthode est assez importante car elle va nous permettre de créer les ressources nécessaires
à l'application.
Dans notre cas nous avons besoin d'initialiser le clavier (pour les contrôles utilisateurs),
la camèra pour avoir un oeil dans le monde 3D et le modèle lui-même.
bool
Application::
Init(const
char
*
lpstrGameName)
{
if
(kjGameFramework::
Init(lpstrGameName) ==
false
)
{
kjAlert("Unable to init framework"
);
return
false
;
}
// Init input manager
GetkjCore()->
CreateInputManager(false
, GetRenderer->
GetAttachedWindow());
// Init camera
m_Camera.Init(kjVector(0
,1
,0
), 30
, 45
, 2
);
// Load file
m_pNode =
kjNode::
LoadHierarchyFromFile("Datas/skeleton.kjh"
);
if
(m_pNode ==
NULL
)
{
kjAlert("unable to load hierarchy file"
);
return
false
;
}
GetRenderer->
SetClearColor(0.3
f,.4
f,.5
f);
return
true
;
}
Avant toute chose, nous devons appeler la méthode Init() de la classe kjGameFramework qui fait le plus gros du travail d'initialisation de kjAPI. En effet elle initialise les bibliothèques nécessaires (kjCore, kjGraphics) et surtout elle appelle la méthode OnCreateRenderer() qui nous l'avons vu est vitale à l'affichage.
Une fois cet appel effectué avec succès, nous pouvons initialiser le clavier.
Pour cela nous devons initialiser le gestionnaire d'entrées (kjInputManager) qui
initialisera à son tour clavier et souris.
Cette initialisation se fait en une ligne de code via la bibliothèque kjCore. Le 1er
paramètre indique que nous n'avons pas besoin de DirectInput et le deuxième est le
pointeur sur la fenêtre attachée au moteur de rendu.
Nous verrons comment accéder à l'InputManager et au clavier dans la méthode
UpdateFrame().
Vient le tour de la camèra : de la même façon, une seule ligne de code suffit à initilialiser
cet objet.
Le premier paramètre est la position du centre autour duquel la caméra tournera.
Les deux paramètres suivant étant les angles (en degrés) de la caméra autour de ce centre
(en coordonnées sphériques). Et enfin le dernier paramètre est la distance de la caméra par
rapport au centre. (cf figure)
Nous allons créer le modèle à partir d'un fichier kjh (fichier contenant une hiérarchie) (ce fichier a été créé grâce à l'exporteur 3DS Max de la kjAPI). Un fichier kjh a des liens vers d'autres fichiers de ressources (textures et géometrie).
Ces fichiers de données se trouvent en téléchargement ici (fichier zip, 225 Ko).
Pour charger le modèle en mémoire nous utilisons donc la méthode statique
kjNode::LoadHierarchy(). Il suffit de spécifier le fichier à charger et la fonction s'occupe
de tout (création de la géométrie, allocation des ressources graphiques, chargement des
textures ....).
Avec la kjAPI il vous est également possible d'accéder à toutes les fonctions bas-niveau
du moteur de rendu si vous preferez créer votre modèle de façon procédurale.
La méthode de chargement renvoie donc un pointeur sur une instance de classe kjNode ou NULL
si une erreur s'est produite.
Une fois les opérations couronnées de succès, nous pouvons aussi définir la couleur de fond du moteur de rendu grâce a la méthode SetClearColor(). Les paramètres correspondent aux 3 composantes de couleurs (rouge, vert, bleu) dans l'intervalle [0, 1].
L'initialisation de notre application se termine ici et nous pouvons étudier de plus près la méthode UpdateFrame().
7. La méthode UpdateFrame()▲
Comme dit précédemment cette fonction est appelée à chaque frame pour permettre à
l'application de mettre à jour l'état de celle ci.
Dans notre cas l'état est simple : c'est celui de la position de la camera autour du modèle
en fonction des entrées clavier.
Regardons de plus près cette méthode :
bool
Application::
UpdateFrame()
{
// return true means exit of application
if
(kjGameFramework::
UpdateFrame() ==
true
)
return
true
;
// Controls
kjKeyboard*
pKbd =
GetkjCore()->
GetInputManager()->
GetKeyboard();
float
Turn =
GetkjCore()->
GetElapsed() *
180.
f;
if
(pKbd->
IsKeyDown(KJ_KBD_LEFT)) m_Camera.SetYAngle(m_Camera.GetYAngle() -
Turn);
if
(pKbd->
IsKeyDown(KJ_KBD_RIGHT)) m_Camera.SetYAngle(m_Camera.GetYAngle() +
Turn);
if
(pKbd->
IsKeyDown(KJ_KBD_UP)) m_Camera.SetXAngle(m_Camera.GetXAngle() +
Turn *
0.5
f);
if
(pKbd->
IsKeyDown(KJ_KBD_DOWN)) m_Camera.SetXAngle(m_Camera.GetXAngle() -
Turn *
0.5
f);
m_Camera.Update();
// Update geometry (needed for morphed or skinned geometries)
if
(m_pNode)
m_pNode->
Update(GetkjCore()->
GetElapsed());
// return false because we dont need to quit yet
return
false
;
}
Pour commencer, il vous faut appeler la méthode UpdateFrame() de la classe kjGameFramework. Cet appel met à jour principalement la bibliothèque kjCore qui s'occupe de la gestion du temps de l'application et de la mise à jour des divers managers associés (comme l'InputManager par exemple).
Pour accéder au clavier, nous devons utiliser l'InputManager que nous avons créé dans la methode Init(). La méthode GetInputManager() de kjCore nous permet de récupérer un pointeur sur l'instance et d'appeler la méthode GetKeyboard() qui nous renverra l'instance de la classe kjKeyboard dont nous avons besoin.
La classe kjKeyboard possède un état de chaque touche. Une touche peut être appuyée ou relâchée. La classe conserve également un état qui permet de savoir si la touche vient d'être pressée ou vient d'être relâchée (ces états sont valides le temps d'une frame). Dans notre cas nous avons uniquement besoin de savoir si la touche est enfoncée ou non. La méthode IsKeyDown() rempli parfaitement le rôle. Il suffit de lui passer le code de touche à tester. Ici nous testons les touches flèchées (voir le fichier kjKeyboard.h pour la liste complète de tous les codes de touches disponibles).
Pour "animer" la camera en fonction des touches nous calculons d'abord le déplacement éventuel
de la caméra en fonction du temps écoulé depuis la dernière frame. Ainsi la caméra tournera
toujours à la même vitesse quelque soit la vitesse d'affichage.
Le temps écoulé depuis la dernière frame s'obtient grâce à la méthode GetElapsed()
de la bibliothèque kjCore. La valeur retournée est en seconde.
En la multipliant par 180, nous pouvons déterminer de quel angle la caméra doit se pivoter
(180 correspond donc à la vitesse angulaire de la camera soit 180 degrés par seconde).
La série d'instruction If permet de définir le mouvement en fonction du clavier. Ainsi
la flèche de gauche décrémente l'angle Y de la caméra de la valeur calculée précédemment.
Une fois les paramètres de la caméra modifiés, il nous faut les valider à l'aide de la méthode Update() de la classe caméra. Cette fonction recalcule en interne la matrice associée à la caméra que nous utiliserons lors du DoFrame().
De la même façon il nous faut appeler la méthode Update() sur le modèle afin de mettre à jour les éventuels éléments dynamiques.
La méthode renvoie false pour indiquer que nous ne souhaitons pas quitter l'application.
8. La méthode DoFrame()▲
Cette méthode est plutôt simple à mettre en place dans notre cas.
void
Application::
DoFrame()
{
kjGameFramework::
DoFrame();
// Set Matrices
GetRenderer->
SetCameraMatrix(m_Camera.GetViewMatrix());
GetRenderer->
SetWorldMatrix(kjMatrix::
IDENTITY);
// Draw model
if
(m_pNode)
m_pNode->
Draw();
}
La première étape consiste à appeler la méthode DoFrame() de la classe kjGameFramework (une impression de déjà vu ? :) ). Elle initialise la matrice de projection ainsi que le moteur de rendu pour qu'il se tienne prêt à tracer.
Comme nous utilisons notre propre caméra nous devons spécifier au moteur de rendu de
l'utiliser. Cela se fait par l'envoi de la matrice de la caméra au moteur de rendu via la
méthode SetCameraMatrix.
Nous nous assurons également que la matrice des objets (matrice monde) soit bien initialisée
avec la matrice identité (kjMatrix::IDENTITY est une constante définie par la
kjAPI).
Nous pouvons enfin tracer notre modèle grâce à sa méthode Draw(). kjAPI se charge de tous les appels graphiques pour vous.
9. La méthode Release()▲
Une fois l'application terminée, cette méthode est appelée afin que nous puissions faire le ménage.
void
Application::
Release()
{
kjDelete(m_pNode);
kjGameFramework::
Release();
}
Dans notre cas, nous n'avons que le modèle a libérer. La kjAPI utilisant sa propre gestion de
mémoire nous devons utiliser la macro définition kjDelete.
L'appel de la méthode Release() du kjGameFramework se fait (une fois n'est pas
coutumes) après. Car il nous faut libérer nos ressources avant que kjAPI elle-même soit
libérée.
10. Constructeur et dépendances▲
N'oubliez pas le constructeur de la classe Application qui se doit d'initialiser les variables tout simplement :
Application::
Application():m_pNode(NULL
)
{
}
L'utilisation de toutes ces classes n'est pas sans conséquences. Il nous faut donc inclure
les fichiers d'en-têtes nécessaires.
Il faut donc ajouter au début de Application.cpp :
#include
"Application.h"
#include
<windows.h>
#include
<kjNode.h>
#include
<kjInputManager.h>
#include
<kjKeyboard.h>
#include
<kjDx9Renderer.h>
#include
<kjWindowDx8Win32.h>
#pragma comment(lib,
"kjCore.lib"
)
#pragma comment(lib,
"kjGraphics.lib"
)
#pragma comment(lib,
"kjGraphicsDx9.lib"
)
Les commandes du préprocesseur #pragma comment permettent de spécifier les bibliothèques à lier au programme. Le paramétrage de l'environnement de développement permet d'éviter de spécifier leur chemin complet.
11. Conclusion▲
J'espère que ce tutoriel aura permis à certains de découvrir la kjAPI et donné l'envie d'aller
plus loin avec.
Il est possible de se passer de la classe kjGameFramework pour créer une application,
permettant d'avoir plus de contrôle sur les opérations effectuées.
N'hésitez pas à me faire savoir quel type de tutoriel vous aimeriez voir dans ces colonnes.
Cédric
12. Téléchargements▲
Version PDF de ce tutoriel (12 pages, 188 Ko) : télécharger.
Modèle de squelette utilisé par le projet (fichier zip, 225 Ko) : télécharger.