Little Blog Story

Game-Design & Modding le blog de belzaran.

Archive pour la catégorie ‘Unreal Script’

Making-Of : Hunt (2)

Mardi 24 août 2010

J’ai commencé à programmer le mode Hunt pour Unreal Tournament III. Le plus gros problème de départ a été de configurer UT3 pour qu’il compile le code créé et que cela fonctionne in-game (si en soit, l’Unreal Script a peu changé entre UT3 et l’UDK, la façon dont ces deux logiciels le compile est légèrement différentes). J’ai alors rapidement programmé un embryon de gameplay que je suis allé testé. Les mauvaises surprises n’ont pas tardé !

Etat actuel du projet

Actuellement, un joueur démarre avec 200 points de vie et une vitesse ralentie. Lorsqu’il réussit un frag, il passe à 75 points de vie, une vitesse et un saut doublé. Le système de score (plus je reste longtemps en cible, plus je marque des points) n’est pas encore implanté. Actuellement, j’ai le choix de n’avoir aucune cible réelle ou la possibilité d’en avoir plusieurs en même temps. Je n’ai pas encore bloqué le fait qu’il n’y ait qu’une seule cible à la fois.

Le début de partie

Le début de partie est évidemment le problème majeur à régler au départ. Comment choisir la cible ? Mon code actuel dit que si l’on tue la cible, on devient cible. Pas de problème, ça fonctionne. Sauf qu’avec ce code, il faut une cible au départ. Ici, ce n’est pas le cas.

Plusieurs solutions s’offrent à moi. La première serait de choisir un joueur au hasard ou de prendre le premier connecté. Hors de question étant donné que cela donnerait un avantage à quelqu’un sans une bonne raison. La deuxième possibilité est de modifier le principe du « First kill ». En effet, quand la première personne est fraggué sur UT3, un ‘announcement’ signale qu’il a eu lieu et qui l’a fait. Il suffirait alors de mettre une ligne de code afin de faire devenir la cible le joueur qui a réussi le frag.

Je pensais fortement aller dans cette voie quand j’ai songé à un problème que je n’avais pas imaginé : que se passe-t-il si la cible quitte le serveur ? Afin de pallier à ce problème (et à tous les autres), il faut que je programme une fonction qui récupère les données de tous les joueurs afin de voir s’il y a une cible. Cette fonction pourrait ainsi permettre :

  • De signaler aux joueurs s’il n’y a pas de cible (dans ce cas-là, on peut fragguer tout le monde)
  • De pallier au problème du lancement de la partie (sans cible)

Les bots

UT3 étant un jeu déserté actuellement, j’ai peu d’espoir de voir mon mode de jeu joué sur de nombreux serveurs. Il est ainsi essentiel qu’il soit jouable avec des bots. Or, en supprimant la notion qu’équipe (comme signalé dans le précédent article), je perturbe les bots. Ceux-ci continuent à jouer comme en Deathmatch, ignorant les règles nouvelles. En cela, c’est un problème énorme car je n’y connais absolument rien en programmation d’IA. C’est l’occasion de m’y lancer, mais le projet que j’imaginais simple devient d’un coup bien plus ardu. Certes, il me reste la possibilité de publier un mode de jeu jouable uniquement entre humains, mais je perdrais l’occasion de le faire tester au préalable (voir tester tout court).

Etant donné l’absence d’avancées significatives et d’un prototype fonctionnel, je ne publie pas encore le code actuel du projet.

Making-of : Hunt

Mardi 17 août 2010

Introduction

Little God Story est un projet passionnant mais particulièrement long à mettre en place. Ayant été habitué ces dernières années à publier du contenu régulièrement (maps, mutator, game type…), je me sens quelque peu frustré par cet aspect-là du développement. Ayant progressé fortement en programmation suite à mes travaux sur Little God Story, j’ai décidé de programmer un game-type pour Unreal Tournament III  : Hunt. Ce game-type était prévu pour Unreal Safari à l’origine (cf le post-mortem consacré au mod). J’ai décidé de le développer tranquillement et de publier ici les différentes avancées, sous forme d’un making-off. Quand j’aurais terminé (si je termine), je publierai les sources annotées pour aider les personnes souhaitant de mettre à l’UnrealScript.

L’idée ici est d’aller vite. Il n’est pas du tout prévu de développer des maps pour ce game-type ou de reprendre le développement d’Unreal Safari.

Concept du mode

Le mode Hunt est assez simple dans son fonctionnement. Un joueur est la cible. Tant qu’il n’est pas tué, il accumule des points. Quand il est fraggé, le tueur devient la cible et ainsi de suite. Il me semble que ce mode existe déjà dans plusieurs jeux mais je suis bien incapable de dire lesquels. N’hésitez pas à combler cette lacune culturelle dans les commentaires…

Une autre particularité (héritée d’Unreal Safari) est que la cible est très fragile. Elle a peu de points de vie mais court vite et saute très haut, pouvant prendre des raccourcis. Les tueurs sont lents mais très résistants. Si bien que la cible a intérêt à fuir plutôt qu’à chercher le combat.

A ce niveau, la question de l’équilibrage ne se pose pas encore. L’idée est de séparer les différentes façons de faire des points et de rendre leur changement aisé. Ici, il y a trois façons de faire des points :

  • Être la cible
  • Tuer la cible
  • Être la cible et tuer un chasseur

On peut également envisager un malus :

  • Être un chasseur et tuer un chasseur

Conception du code

Ici, je parle de la conception du code avant de plonger les mains dans le bousin. Rien ne dit donc que ça va marcher. A l’époque d’Unreal Safari, j’avais commencé à bosser sur ce mode. En fait, il y a une façon dont je l’ai envisagé qui me paraît mauvaise et compliqué : construire le mode comme un jeu d’équipe. L’équipe rouge chasse, l’équipe bleu est chassé. Si la cible est tué, elle passe dans l’équipe rouge et le tueur passe dans l’équipe bleu. Cela permet également de gérer simplement les friendly fire. Cependant, il faut bien prendre en compte que le mode Hunt n’est pas un feu d’équipe ! Chacun joue pour lui et seul le premier sera vainqueur. C’est une variante du mode Deathmatch. Changer le TDM posait d’autres problèmes tout simples :

  • Le HUD montre un score par équipe
  • La table des scores montre un score par équipe
  • Changer d’équipe nécessite de mourir (imaginez la frustration quand le joueur fraggue et qu’il meurt : il ne sait pas tout de suite s’il a été fraggué ou s’il est devenu la cible).
  • Une cible est définie dès le deuxième joueur connecté aléatoirement (c’est-à-dire qu’un joueur va faire des points sans avoir rien fait en début de partie)
  • Le message “VOUS ÊTES ROUGE” à changer pour “VOUS ÊTES LA CIBLE” (il est toujours plus facile d’ajouter simplement un message plutôt que d’en changer)

J’ai donc décidé de tout articuler sur la fonction ScoreKill (qui gère les frags). Elle se lance quand un joueur est tué et permet d’agir sur le tueur et le tué. Ainsi, je pourrais définir par une variable un changement de cible (certainement un booléen : bTarget). Plutôt que de mettre un système d’équipe complet, j’ajoute une variable à chaque joueur qui définit s’il est la cible ou non. L’idée est de faire :

Si le tué est la cible, alors le tueur devient la cible.

Rien de bien compliqué sur le papier, dans la pratique il y aura deux/trois choses à prendre en compte (notamment le friendly fire). Il faut également permettre aux joueurs de reconnaître la cible rapidement (même si elle saute deux fois plus haut que les autres et court comme Usain Bolt). Pour cela, une lumière dynamique suivra la cible pour qu’elle soit reconnaissable (en cela, utiliser un système d’équipe avait l’avantage de bien séparer visuellement les deux types de joueurs).

Dernière chose importante dans la conception pre-code : quand est-ce que je j’ajoute une cible ? En effet, si deux joueurs se retrouvent seuls sur un serveur, quel intérêt de mettre une cible ? Difficile ici de définir correctement. De toute manière, un joueur qui arrive en milieu de partie est toujours désavantagé pour du deathmatch. L’idée est de faire que la partie démarre sans cible. Dès qu’un joueur tue, il devient la cible.

Autres aspects du mode

Contrairement au game type Safari, ce mode demande beaucoup moins de personnalisation visuelle. Safari prenait en compte des niveaux et des points d’expérience qui avaient un impact important sur le jeu. Ici, quelques messages uniquement à afficher au bon moment :

  • Message : “Vous êtes la cible !” (avec un bruit reconnaissable)
  • Message : “Vous êtes un chasseur !” (lors du respawn)
  • Message : “Vous avez tué un chasseur !” (lors d’un friendly fire)

J’ai envie de faire quelque chose de plus personnalisé et fun avec plutôt des phrases du type : “Accident de chasse, arrêtez de boire !” ou “Vous êtes la cible, faites gaffe à vos fesses”. Le tout avec des sons enregistrés pour l’ambiance. Mais là, on parle déjà de finalisation du mode !

Sinon, le game-type Hunt a un nom pourri bien que représentant bien le principe. J’aimerais en trouver un de plus sympa. Hunting Safari sonne mieux je trouve mais je pense que vous voyez le problème…

C’est tout pour aujourd’hui. N’hésitez pas à donner votre avis sur le mode en lui-même. S’il y a des choses qui vous chiffonnent et qui vous paraissent rédhibitoires.

UnrealScripter #4

Lundi 19 juillet 2010

unrealscripter

VI. HUD

Attaquons-nous aujourd’hui à notre premier HUD. Comme je l’ai dit précédemment, j’ai codé le HUD avant tout pour contrôler que les modifications dans le code fonctionnaient ! Et cela a toujours été le cas dans mes projets. Il est important de savoir faire un HUD (même moche) afin de suivre en direct l’évolution des variables du jeu. Il est également possible, dans la même optique, d’ajouter des messages dans le console, mais ce n’est pas l’objet de ce tutoriel.

Une fois pris en considération qu’il n’y aura ici AUCUN souci esthétique, on va pouvoir se lancer. Avant tout, il existe désormais deux types de HUD dans l’UDK. Depuis la version de mai (attention à bien vérifier la version que vous utilisez) a été intégré Scaleform GFX, une technologie permettant l’affichage d’interfaces 3D interactives. L’avancée en termes graphiques est importante mais l’accessibilité est beaucoup moins évidente. Heureusement, il est toujours possible de créer des HUD à l’ancienne en ajoutant une simple ligne de code dans les defaultsproperties de TT_Game.uc :

bUseClassicHUD = true;

Surtout, n’oubliez pas de changer ça le jour où vous voulez faire une interface 3D !

Pour être honnête, je précise que l’on va ici utiliser une fonction qui ne vient pas de moi, DrawTexting(). Je l’ai adaptée à partir d’un tutoriel ou d’un exemple mais je ne sais plus d’où.

Commençons par créer le fichier, TT_HUD.uc :

TT_HUD extends UTHUD;

Je précise qu’il existe une classe UTTeamHUD qui prend en compte l’équipe dans laquelle on joue pour dessiner le HUD (simplement, ça permet de faire que si on est dans l’équipe bleue, certains textes et images du HUD se colorent en bleu). Je vous laisse donc adapter à ce que vous voulez faire. A ne pas oublier : dire à notre jeu qu’il doit utiliser ce HUD ! On doit doit ajouter la ligne suivante dans les defaultproperties de notre [b]TT_Game.uc[/b].

HUDType=Class’TT_HUD’

Ici, nous allons utiliser (comme toujours pour faire un HUD) des fonctions issues de Canvas.uc. Une nouvelle fois, je vous conseille d’aller jeter un coup d’oeil à cette classe pour voir un peu l’éventail de fonctions qui y existent.

simulated function DrawTexting (UTHUD H, float X, float Y, string Text)

{

if (H == None || H.Canvas == None) return;

// Simule une résolution de 1024*768

X = X * H.ResolutionScaleX;

Y = Y * H.ResolutionScale;

H.Canvas.Font = class’Engine’.static.GetMediumFont();

H.Canvas.SetPos (X, Y);

H.Canvas.DrawColor=WhiteColor;

H.Canvas.DrawText (Text, false);

}

En observant les variables de cette fonction, on a d’abord un HUD (pour savoir pour quel joueur on affichera le texte), des positions X et Y sur l’écran et le texte à écrire. La première condition (H == None || H.Canvas == None) vérifie si le HUD cité correspond à quelque chose et si ce n’est pas le cas, la fonction ne fait rien. Ensuite, la première partie simule un écran d’une taille donnée. En effet, chaque joueur ayant une résolution différente, il faut arriver à coder quelque chose de générique qui fonctionnera sur chaque écran. Globalement, l’idée est d’arriver à une notion de pourcentage plutôt que de nombre de pixels.
Les conditions suivantes sont celles qui vont déterminer la police d’écriture. On utilise ce genre de fonction pour les appeler :

class’Engine’.static.GetMediumFont()

On verra dans un autre tutoriel comment changer les polices d’écriture par défaut.

On utilise ensuite SetPos pour déterminer la position, DrawColor pour la couleur et DrawText pour dessiner le texte. On peut remarquer qu’une fonction, DrawText(), existe donc déjà. Cependant, en créant la notre, on la complexifie beaucoup (il suffit de voir déjà le nombre de variables) et on l’adapter à nos besoins. Il existe également une fonction qui dessine un texte en le centrant. Sans surprise, on trouve DrawTextCentered() dans Canvas.uc.

Pour le moment, on a codé notre base, c’est-à-dire la fonction qui nous permettra d’afficher du texte. Maintenant on va coder la fonction qui permettra d’exécuter cette fonction (un grand classique). Appelons la DrawLevel. Cette fonction a pour but d’afficher le niveau du joueur et son nombre de points d’expérience. Pour cela, on aura shématiquement deux lignes lançant deux fois la fonction DrawText. Reste à récupérer le niveau et points d’XP. Pour cela, on va créer deux nouvelles fonctions…

function int PickLevel(Controller c) //Récupère le Niveau

{

local int WhatLevel;

WhatLevel = TT_PRI(c.PlayerReplicationInfo).Level;

Return WhatLevel;

}

Encore une technique classique. Ici, la fonction PickLevel a pour variable un Controller, car on veut récupérer le niveau d’un joueur précis. On remarque dans la déclaration de la variable le “int”. Cela veut dire que quand je lance la variable, elle va me donner un nombre entier. Par exemple, si je fais PickLevel(Joueur); cela donnera 1, 2, 3 ou 4, etc. On peut évidemment déclarer des functions avec des floats, strings, booléens, etc.

Ici, on a donc déclaré une variable locale, on la définit comme égale au niveau du joueur. Et ensuite on retourne cette variable. Le résultat de la fonction est donc “WhatLevel”. Rien de bien compliqué ici. Je vous laisse faire la même fonction PickXP qui est exactement la même, il suffit de changer “Level” en “XP” (essayez de la refaire sans regarder l’autre, ce sera formateur !).

Maintenant que l’on a toutes nos fonctions, on peut passer à celle qui nous intéresse réellement !

simulated function DrawLevel(UTHUD H)

{

DrawTexting (H,10, 180, ” XP : ” @ PickXP(PlayerOwner));

DrawTexting (H,10, 200, ” Level : ” @ PickLevel(PlayerOwner));

}

A force de faire des fonctions annexes, notre fonction de base se trouve bien simplifiée ! Il faut comprendre que le fait de faire des sous-fonctions permet d’éviter de s’embrouiller dans les variables et surtout de les réutiliser ailleurs. Typiquement, une fonction comme DrawLevel sera certainement utilisable ailleurs (pour afficher des résultats par exemple). On remarque que j’ai décalé en hauteur (3ème variable, Y) les textes pour qu’ils ne soient pas l’un sur l’autre. La variable PlayerOwner est à retenir. C’est un PlayerController est défini dans HUD.uc (et donc aussi à UTHUD.uc qui en dérive) et désigne le joueur qui utilise ce HUD.

Terminé ? Pas du tout ! Nous n’avons pas lancé la fonction DrawLevel ! Pour cela, rien de compliqué, le tout c’est ne pas oublier de le faire !

function DisplayScoring()

{

Super.DisplayScoring();

DrawLevel (self);

}

La fonction DisplayScoring affiche le score (ha bon ?). On y ajoute discrètement notre fonction. Ici aussi, il est intéressant de voir la variable “self” qui dit donc que DrawLevel doit utiliser le HUD dans lequel il est lui-même. Cela peut paraître évident, mais ça ne l’est pas du tout. Par exemple, on pourrait très bien faire un jeu qui afficherait les points de vie de ses coéquipiers !

Voilà, c’est tout pour aujourd’hui. Notre HUD est très simple, voir moche mais permet de jouer en visualisant son niveau et ses points d’XP. Avec nos compétences actuelles, on peut très bien afficher également le nombre de points d’XP restants jusqu’au prochain niveau. N’hésitez surtout pas à utiliser la base du code pour l’adapter à vos besoins.

Dans le prochain tutoriel, on tentera (j’espère) le mode Furie !

UnrealScripter #3

Vendredi 16 juillet 2010

unrealscripter

IV. D’autres variables utilisables

PREREQUIS : il est important d’avoir lu les tutoriels correspondants précédents afin d’avoir la suite du code. Le code des anciens tutoriaux a été corrigé, il comportait quelques petites erreurs.

Certaines variables sont déjà dans le code de l’UDK et il n’est pas nécessaire de les réécrire dans votre propre code. Les plus utiles seront notamment :

GroundSpeed : vitesse de déplacement

JumpZ : hauteur de saut

Health : points de vie

Ces variables sont définies dans le fichier Pawn. Je Si on farfouille un peu dans le fichier Pawn.uc (dont dérive ensuite UTPawn.uc) on peut voir les multiples variables qui existent. Je vous laisse le faire car il y en a énormément. Inutile donc de s’embêter à tout refaire. Pour les définir, on utilise l’arborescence du code :

Controller -> Pawn -> Health

C.Pawn.Health = 100 ;

Pour notre jeu, nous allons faire en sorte que le joueur retrouve ses points de vie quand il gagne un niveau et qu’il gagne en vitesse et en hauteur de saut. Ceci, bien qu’apparemment très simple, va nous perdre de réfléchir un peu à la conséquence de nos actes. En effet, on aurait tendance à écrire simplement une ligne de code donnant 100 points de vie à notre joueur. Faux ! Si jamais le jeu a des power-ups permettant de dépasser cette limite, il faut vérifier avant que le joueur n’a pas déjà plus de 100 points de vie. Sinon, ça reviendrait à lui baisser la santé ! Comme récompense, il y a mieux…

Ici, on va utiliser deux variables OldLevel et NewLevel qui vont nous permettre, simplement, de vérifier si le joueur a changé de niveau. C’est une astuce toute bête, mais très pratique. Il suffit de définir OldLevel avant que l’on lance la fonction proprement dite, puis de définir NewLevel juste après et de les comparer.

Function CheckLevel(Controller C)

{

Local TT_PRI PRI ;

Local int OldLevel;

Local int NewLevel;

PRI = TT_PRI(C.PlayerReplicationInfo) ;

OldLevel = PRI.Level; //définissons le niveau actuel du joueur

if (PRI.XP <= 100)

{

PRI.Level = 0;

}

else if (PRI.XP <= 200)

{

PRI.Level = 1;

C.Pawn.GroundSpeed = 520.0000 ;

C.Pawn.JumpZ = 400 ;

}

else

{

PRI.Level = 2;

C.Pawn.GroundSpeed = 600.0000 ;

C.Pawn.JumpZ = 450 ;

}

NewLevel = PRI.Level;

if (NewLevel != OldLevel)

{

if (C.Health <= 100)

{

C.Health =100;

}

else

{

}

}

else

{

}

}

Ce code est intéressant car il comporte une condition (if, else if, else…) à l’intérieur d’une autre condition. D’où l’importance de hiérarchiser son code (avec des tabulations). Techniquement, cela ne change rien lors de la compilation, mais cela permet de mieux visualiser ce que l’on a fait. Imaginez vous reprendre un code écrit des mois auparavant qui fait 4 pages… Evitons donc de nous poser des problèmes !

V. Messages

Comme mon code ne marchait pas (il se compilait mais in-game, rien ne se passait), j’ai programmé en vitesse l’affichage d’un message afin de voir si les niveaux changeaient. Maintenant que c’est fait, autant l’intégrer dans le tutoriel !

Pour afficher des messages proprement, il nous faut une autre classe. Appelons là TT_MessageLevel.uc . On verra plus tard qu’il est tout à fait possible d’intégrer plusieurs types de messages dans une même classe. Une classe de messages dérivent la plupart du temps de LocalMessage.uc. Une bonne idée est d’ouvrir cette classe pour voir ce qu’elle comporte ! Ici on va utiliser uniquement la fonction GetString(), sans utiliser toutes ses options :

class TT_MessageLevel extends LocalMessage;

static function string GetString(

optional int Switch,

optional bool bPRI1HUD,

optional PlayerReplicationInfo RelatedPRI_1,

optional PlayerReplicationInfo RelatedPRI_2,

optional Object OptionalObject

)

{

return “Level UP”;

}

defaultproperties

{

bIsPartiallyUnique=True

bIsConsoleMessage=False

bBeep=False

Lifetime=5

DrawColor=(G=255,R=50,B=50)

PosY=0.9

FontSize=3

}

Rien de bien compliqué ! Pour information je vous ai laissé quelques propriétés par défaut très pratiques comme Lifetime (en secondes), bBeep (qui permet de faire du bruit lorsque le message s’affiche), PosY (qui ici indique que le message se affiché en bas de l’écran), FontSize (plus le nombre est grand, plus l’écriture est grande) et DrawColor (qui permet de définir la couleur du message). On voit qu’avec quelques propriétés, on a déjà de quoi personnaliser un peu son message ! Bien sûr, je n’ai pas inventé ces propriétés, il suffit d’ouvrir LocalMessage.uc pour les découvrir. Je me répète mais il est essentiel de lire (ou même juste parcourir) les classes dont dérivent les autres pour bien comprendre l’UnrealScript.

Cependant, il paraît évident que l’on préfèrerait un message qui nous précise notre niveau. Pour ça, on va utiliser la variable Switch de la fonction GetString(). Simplement, quand on va envoyer le message, on peut ajouter un nombre comme variable (=Switch).

static function string GetString(

optional int Switch,

optional bool bPRI1HUD,

optional PlayerReplicationInfo RelatedPRI_1,

optional PlayerReplicationInfo RelatedPRI_2,

optional Object OptionalObject

)

{

return “YOU ARE LEVEL ” @Switch;

}

Retenez bien le coup du “@” car c’est très important pour faire des HUD/Messages intéressants et personnalisés.

Reste maintenant à envoyer le message. On retourne donc dans TT_Game.uc. Un message se lance de la façon suivante :

PlayerController(C).ReceiveLocalizedMessage(class’TT_MessageLevel’, UnInteger);

Ici, “UnInteger” sera le niveau du joueur et définira le Switch du message. Avec la fonction complète, ça donne :

Function CheckLevel(Controller C)

{

Local TT_PRI PRI ;

Local int OldLevel;

Local int NewLevel;

PRI = TT_PRI(C.PlayerReplicationInfo) ;

OldLevel = PRI.Level; //définissons le niveau actuel du joueur

if (PRI.XP <= 100)

{

PRI.Level = 0;

}

else if (PRI.XP <= 200)

{

PRI.Level = 1;

C.Pawn.GroundSpeed = 520.0000 ;

C.Pawn.JumpZ = 400 ;

}

else

{

PRI.Level = 2;

C.Pawn.GroundSpeed = 600.0000 ;

C.Pawn.JumpZ = 450 ;

}

NewLevel = PRI.Level;

if (NewLevel != OldLevel)

{

if (C.Health <= 100)

{

C.Health =100;

}

else

{

}

PlayerController(C).ReceiveLocalizedMessage(class’TT_MessageLevel’, NewLevel);

}

else

{

}

}

Quand j’ai commencé à coder, j’avais créé un message pour chaque niveau et j’avais remis plein de conditions pour savoir quel message coder. Ici, le code est plus malléable et permet de fonctionner, quel que soit le nombre de niveaux.

Dans le prochain tutoriel, on verra comment ajouter un HUD très simple (je l’ai créé car le code ne marchait pas et que j’avais besoin de visualiser les XP/Level).

ps : pour les personnes déçues qui espéraient un mode Furie, j’ai eu des problèmes pour le coder. Quand on programme, on a beau être quasiment sûr de savoir faire quelque chose, on ne peut jamais être certain d’aller au bout…

UnrealScripter #2

Samedi 10 juillet 2010

unrealscripter

On continue dans notre apprentissage de l’UnrealScript avec la modification de nos variables préalablement définies.

III. Modifier les variables du PRI

Pour que nos variables aient un intérêt, il faut qu’elles évoluent selon les évènements in-game. Ici, nous allons bien sûr faire en sorte que fragger un joueur/bot nous rapporte des points d’expérience, que les points d’expérience nous fasse changer de niveau et que le mode Furie se déclenche selon les souhaits du développeur.

Pour faire cela, nous allons utiliser la fonction ScoreKill(Controller Killer, Controller Killed). Comme la fonction l’indique, ScoreKill nous permet d’accéder au joueur qui a tué et à celui qui a tué. Si ici nous allons mettre des bonus au tueur, on pourrait également donner des malus au tué. Nous allons ici utiliser un système de points d’expérience très simple, mais facilement personnalisable par la suite (avec des « if » partout !)

Pour simplifier le code (et comme nous n’avons aucun souci d’équilibrage actuellement), nous allons décider que tous les 100 points, notre perso gagne un niveau (sur trois au total). Chaque fois qu’il fraggue quelqu’un, il gagne 10 points. Cela donne :

Function ScoreKill(Controller Killer, Controller Killed)

{

Local TT_PRI KillerPRI;

KillerPRI =  TT_PRI(Killed.PlayerReplicationInfo);

//je précise ici dans la variable KillerPRI que c’est le tueur

//afin d’éviter des confusions si je veux finalement

//utiliser le PRI du tué plus tard

KillerPRI.XP += 10 ; //ajoute 10 points d’XP à ceux déjà existants

CheckLevel(Killer) ;

}

Rien de compliqué ici. Vous remarquez que j’ai ajouté une fonction CheckLevel(Killer) que l’on va créer tout de suite. Elle a simplement pour but de savoir si, maintenant que les points d’expérience ont changé, le joueur a gagné un niveau. On ajoute alors le code suivant (à TT_Game.uc toujours)

Function CheckLevel(Controller C)

{

Local TT_PRI PRI ;

PRI = TT_PRI(C.PlayerReplicationInfo) ;

If (PRI.XP <= 100)

{

PRI.Level = 0;

}

Else if (PRI.XP <= 200)

{

PRI.Level = 1;

}

Else

{

PRI.Level = 2;

}

}

Nous avons donc un joueur qui augmente de points d’expérience et de niveau. Attention, ici, s’il meurt, il perd tout. Dans cette partie, je pense qu’il est nécessaire de bien comprendre ce que ScoreKill() peut apporter. Cela commence par « je tue, j’ai un point » à des systèmes de bonus/malus complexes selon l’arme utilisée, s’il y a headshot, si le joueur tué a un niveau supérieur, etc. Les possibilités sont immenses.

Pour le moment, notre joueur n’a aucun bénéfice à gagner un niveau. Nous allons faire en sorte que ça serve à quelque chose en déclenchant le mode Furie ! Mais pour cela, il faudra attendre le prochain tutoriel.

UnrealScripter #1

Vendredi 9 juillet 2010

unrealscripter

L’UDK est un outil puissant qui permet de faire plus ou moins n’importe quel type de jeu, le problème est de pouvoir le programmer. Cette série de tutoriels a pour but d’aider à la programmation en UnrealScript. Le but ici n’est pas de coder des features incroyables, mais de comprendre comment fonctionne l’UnrealScript.

Introduction

Le PlayerReplicationInfo est quelque chose de relativement peu connu lorsqu’on commence à coder en UnrealScript. Pourtant, beaucoup de codeurs nécessitent d’en comprendre l’usage pour arriver à leurs fins. Surtout que l’usage du PRI est relativement simple.

Le PlayerReplicationInfo est créé pour chaque joueur (en jeu multijoueur ou solo) et contient simplement les informations de ce joueur (points de vie, nom, équipe…). En créant notre propre PRI, on va pouvoir ajouter des caractéristiques à notre joueur.

Ici, le dossier où sont enregistrés mes fichiers est TrashTeam et tous les suffixes sont TT_

Le code ci-dessous est inspiré par mes travaux sur Unreal Safari et Little God Story. Il est donc flexible et adaptable à différents projets.

Prérequis

Savoir compiler un code écrit en Unreal Script et l’utiliser in-game.

I. Créer le fichier PRI

Pour créer un fichier PRI, c’est très simple comme toujours en Unreal Script. Il suffit de dériver du PRI de l’UDK et de l’enregistrer sous le nom correct, ici TT_PRI.uc

class TT_PRI extends UTPlayerReplicationInfo;

Il est ensuite très simple de créer de nouvelles caractéristiques à notre joueur. Ici on va créer plusieurs choses, basé sur un mode RPG classique. On va lui donner des points d’expérience, sous la forme d’un integer et d’un niveau, sous la même forme. On ajoute aussi une feature plus originale (ou pas) : le joueur peut (parfois) passer en mode Furie. On va donc définir une variable booléenne qui dira si le joueur est en mode furie (1) ou pas (0) Même si je ne le fais pas ici, il est tout à fait possible de déclarer des textes (string) également.

Pour cela, il suffit de déclarer les variables, puis de lancer la replication.

class TT_PRI extends UTPlayerReplicationInfo;

var int XP;

var int Level;

var bool bFurie;

//Furie Off = 0 On = 1

replication

{

if ( bNetDirty && (Role == Role_Authority) )

XP, Level,bFurie;

}

Je vous conseille de toujours commenter votre code, notamment pour les valeurs booléennes ou les nombres. Cela vous évitera de vous poser la question à quoi 1 correspond quand vous aurez oublié… Dernière chose, il est d’usage de mettre un petit « b » devant une variable booléenne. Cela permet de les reconnaître facilement.

Evidemment, il est primordial que notre fichier soit reconnu. Direction TT_Game.uc (fichier de base de votre jeu). Il suffit d’ajouter une ligne dans les propriétés par défaut de votre jeu pour que ce soit pris en compte (je vous conseille au passage de regarder dans UTGame tous les fichiers utilisés par UTGame, afin de deviner ceux qu’il faudra modifier).

class TT_Game extends UTGame;

defaultproperties

{

PlayerReplicationInfoClass = class’TrashTeam.TT_PRI’

}

Voilà pour notre fichier PRI, reste à voir désormais comment définir les variables et les appeler dans d’autres classes.

II. Définir les variables du PRI

Afin d’éviter de se retrouver avec des surprises, il est bon de définir les variables au début du jeu. Ici, le joueur démarre avec XP = 0 et un niveau de 0 également. Il n’est pas en mode Furie en revanche (bFurie = false). Attention à bien prendre certaines choses en compte. Il existe de multiples fonctions qui se lancent au démarrage du jeu. Il faut d’abord que le joueur soit réellement « créé » sinon on n’aura rien à définir. Pour cela, rien de mieux que l’évènement PostLogin(PlayerController PC). Etudions d’abord la fonction. Un joueur arrive dans la partie. Il se logue. L’évènement PostLogin est donc activé. Cet évènement dépend donc d’un joueur précis, d’où le PlayerController défini en même temps que l’évènement. Tout ce que l’on fera dans l’évènement PostLogin ne concernera donc que le PlayerController qui vient de se loguer (et heureusement si jamais on veut faire un jeu multijoueur !)

La façon d’appeler un fichier PRI nécessite d’avoir sous la main, défini, un PlayerController ou un Controller (ou un Pawn, mais c’est légèrement différent). Cela tombe bien, ici on en a un sous la main. Pour définir une variable, voilà une commande d’exemple :

TT_PRI(PC.PlayerReplicationInfo).XP = 0 ;

D’où l’évènement :

event PostLogin(PlayerController PC)

{

super.PostLogin(PC);

TT_PRI(PC.PlayerReplicationInfo).XP = 0;

TT_PRI(PC.PlayerReplicationInfo).Level = 0;

TT_PRI(PC.PlayerReplicationInfo).bFurie = false;

}

Fini ? Non ! Si votre perso meurt, il ne va pas se re-loguer. Utilisons alors la fonction  RestartPlayer(Controller C). En gros, c’est la même chose, sauf qu’ici au lieu d’avoir un PlayerController défini, c’est un Controller tout court. Pour nous, ça ne change rien heureusement, attention seulement aux copier-coller (erreur classique…).

Il suffit donc d’ajouter ces lignes de code :

function RestartPlayer(Controller C)

{

super.RestartPlayer(C);

TT_PRI(C.PlayerReplicationInfo).XP = 0;

TT_PRI(C.PlayerReplicationInfo).Level = 0;

TT_PRI(C.PlayerReplicationInfo).bFurie = false;

}

Evidemment, pour le moment, on définit des variables qui ne servent à rien. Mais ça viendra dès la partie III !

Une dernière chose qui permettra à votre code d’être plus propre (et plus lisible). Il est possible de simplifier l’écriture de la définition des variables. Pour cela il suffit de déclarer une variable PRI local, de la définir et on pourra simplifier pas mal le résultat (ceci est utilise dès que vous définissez un paquet de variables dans une même fonction ou un même évènement). Un exemple sur la dernière fonction vue :

function RestartPlayer(Controller C)

{

Local TT_PRI PRI;super.RestartPlayer(C);

PRI = TT_PRI(C.PlayerReplicationInfo);

PRI.XP = 0;

PRI.Level = 0;

PRI.bFurie = false;

}

C’est mieux non ?