Introduction
Je projette d'écrire une "modification" ou script pour le jeu 3D Unreal Tournament dont le but seras de garder une ambiance bonne enfant sur les serveurs en l'absence d'un administrateur. Le langage qu'utilise ce jeu est le UnrealScript qui est comme un mélange de de C++ et de Java.
La plupart des "modifications" dont j'ai pu voir le code mélangent l'orienté objet et le procédural, semble t-il pour des raisons de performances (je n'en suis pas tout à fait convaincu).
Par exemple, il existe une classe Mutator (dont je vais me servir de base) qui modifie certains aspects d'une partie en cours. Les mutators proposés par des auteurs tiers étendent le plus souvent cette classe avec parfois un océan d'instructions ajoutées aux méthodes et le code est souvent difficile à comprendre. Pour les curieux, le code source de Mutator se trouve en bas de ce post.
Je n'en suis pas certain à 100% mais il semble qu'il soit possible de travailler en objet car j'ai repéré la keyword New qui permettrais une instantiation. Comme j'ai vu du Java, j'aimerais appliquer quelques principes que j'ai appris au mod que je compte créer.
Notez qu'il y a une contrainte. Il est déconseillé de modifier les classes standards pour conserver une certaine compatibilité avec les scripts existants.
Maintenant, venons en aux questions pratiques.
Mon objectif est d'écrire le script de sorte qu'il demande le moins de maintenance possible tout en offrant un maximum de possibilités. A ce titre, ce serais intéressant d'utiliser des design patterns.
Classe intermédiaire
La première question que je me pose c'est de savoir si c'est intéressant de créer une classe intermédiaire avec laquelle ma version de Mutator pourras communiquer.
Par exemple, pour détecter l'arrivée d'un nouveau joueur dans le serveurs, il n'existe pas de méthode appropriée. Je dois donc utiliser une combinaison des méthodes ModifyLogin() et ModifyPlayer() pour détecter sa présence. Le ModifyLogin() permet d'intercepter l'arrivée d'un joueur et le ModifyPlayer() permet de confirmer que la connection au server s'est établi sans accroche.
Il serais donc peut être plus intéressant de créer un objet intermédiare avec une méthode, disons nouveauJoueur(). Ou alors, je pourrais ajouter cette méthode à ma version de Mutator. Je ne sais pas si ce genre de chose est une pratique courante.
Classes pour faciliter la gestion
Ensuite, pour faciliter la gestion, je voudrais créer des classes utilitaires par rapport au joueur, la partie en cours et le serveur. Par exemple, je pourrais avoir une classe Joueur avec la méthode getIP() qui me renverrais un string contenant l'adresse IP d'un joueur.
A ce propos, je me demande si je devrais créer l'équivalent d'une classe statique avec des méthodes qui pourraient être utilisées sur n'importe quel joueur ou une classe qui comporterais une référence vers le joueur (aggrégation ?).
A ce titre, chaque joueur est representé par la classe PlayerPawn (pion du joueur). Comme le joueur peut disparaître à tout moment suite à une déconnection, je dois vérifier si Pawn pointe bien sur quelque chose avant de faire un appel pour chercher l'adresse IP. L'instruction utilisée est PlayerPawn( Player ).GetPlayerNetworkAddress() qui renvois l'adresse IP suivi de deux point et le numéro de port distant utilisé.
Pour ce qui est de la partie, il n'y en a qu'une en cours à un moment donné. Quant au serveur, il n'est représenté par aucune classe mais il est possible d'obtenir quelques informations à son propos.
Classes pour représenter des evenements
Lorsque le script auras pris forme, je souhaiterais créer des classes pour représenter divers évènements comme le début d'une partie ou le départ d'un joueur. Ainsi, un autre programmeur auras la possibilité d'étendre ces classes et d'y intégrer son propre code.
Pour arriver à cela, je pense créer un classe pour représenter une interface commune qui pourras être étendue (UnrealScript ne supporte ni les interfaces, ni l'héritage multiple).
Ainsi, je pourrais instancier ces classes selon le cours d'une partie. Bien entendu, un seul évènement ne pourras avoir lieu à un moment donné. Je n'ai pas encore réflêchit sur la manière de signaler la fin d'un évènement mais il existe une méthode Destroy() commune à tous les objets.
"Plugins"
Pour augmenter les possibilités du script, j'aimerais ajouter la possibilité d'ajouter des plugins qui viendrons ajouter des fonctionalités au script de base. Pour cela, je compte également utiliser une classe qui va servir d'interface.
Le souci à ce niveau c'est que les informations qui vont déclencher ces plugins proviendrons de Mutator. Donc, il faudrais que je prévoie ces informations et que j'intégre un maximum de méthodes dans l'interface pour relayer ces informations. Du coup, cela impose une certaine rigidité mais un autre programmeur pourras étendre Mutator et ajouter ses propres méthodes à l'interface.
Communication inter classes
Pour la dernière étape du projet, je dois réflêchir à la manière dont les classes instanciées doivent communiquer entre elles. Est-ce la classe Mutator devras superviser les méssages ou est-ce que ces classes peuvent directement s'adresser des méssages entre elles ?
Pour donner un exemple concret, on pourrais avoir une classe évènement qui signale un abus commis par un joueur. Ensuite il faudras envoyer un méssage pour savoir quelle action dois être prise. Cette action peut être le simple ajout d'une entrée au fichier log du serveur ou, alors, le système peut mettre en garde ou expulser le joueur.
A ce niveau, je ne suis pas encore décidé sur la manière dont je vais concevoir le processus. En plus des évenements, j'entrevois d'autres classes possibles tel des genres de listeners et des classes qui vont déclencher une série d'actions.
Conclusion
Merci d'avoir lu ou parcouru ce post. Tout cela à peut être l'air fouilli à première vue mais j'ai souhaité expliquer le projet de long en large ainsi que mes réflexions.
N'hésitez pas à me poser des questions s'il y a quelque chose que vous ne comprenez pas. J'attends vos retours avec une certaine impatience et je serais ravi de discuter des problèmes pratiques que ce projet soulève.
Le contenu de Mutator.uc :
P.S: Si vous souhaitez en découvrir plus sur l'UnrealScript, n'hésitez pas à visiter ce wiki : http://wiki.beyondunreal.com/
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228 //============================================================================= // Mutator. // called by the IsRelevant() function of DeathMatchPlus // by adding new mutators, you can change actors in the level without requiring // a new game class. Multiple mutators can be linked together. //============================================================================= class Mutator expands Info native; var Mutator NextMutator; var Mutator NextDamageMutator; var Mutator NextMessageMutator; var Mutator NextHUDMutator; var bool bHUDMutator; var class<Weapon> DefaultWeapon; event PreBeginPlay() { //Don't call Actor PreBeginPlay() } simulated event PostRender( canvas Canvas ); function ModifyPlayer(Pawn Other) { // called by GameInfo.RestartPlayer() if ( NextMutator != None ) NextMutator.ModifyPlayer(Other); } function bool HandleRestartGame() { if ( NextMutator != None ) return NextMutator.HandleRestartGame(); return false; } function bool HandleEndGame() { // called by GameInfo.RestartPlayer() if ( NextMutator != None ) return NextMutator.HandleEndGame(); return false; } function bool HandlePickupQuery(Pawn Other, Inventory item, out byte bAllowPickup) { if ( NextMutator != None ) return NextMutator.HandlePickupQuery(Other, item, bAllowPickup); return false; } function bool PreventDeath(Pawn Killed, Pawn Killer, name damageType, vector HitLocation) { if ( NextMutator != None ) return NextMutator.PreventDeath(Killed,Killer, damageType,HitLocation); return false; } function ModifyLogin(out class<playerpawn> SpawnClass, out string Portal, out string Options) { if ( NextMutator != None ) NextMutator.ModifyLogin(SpawnClass, Portal, Options); } function ScoreKill(Pawn Killer, Pawn Other) { // called by GameInfo.ScoreKill() if ( NextMutator != None ) NextMutator.ScoreKill(Killer, Other); } // return what should replace the default weapon // mutators further down the list override earlier mutators function Class<Weapon> MutatedDefaultWeapon() { local Class<Weapon> W; if ( NextMutator != None ) { W = NextMutator.MutatedDefaultWeapon(); if ( W == Level.Game.DefaultWeapon ) W = MyDefaultWeapon(); } else W = MyDefaultWeapon(); return W; } function Class<Weapon> MyDefaultWeapon() { if ( DefaultWeapon != None ) return DefaultWeapon; else return Level.Game.DefaultWeapon; } function AddMutator(Mutator M) { if ( NextMutator == None ) NextMutator = M; else NextMutator.AddMutator(M); } /* ReplaceWith() Call this function to replace an actor Other with an actor of aClass. */ function bool ReplaceWith(actor Other, string aClassName) { local Actor A; local class<Actor> aClass; if ( Other.IsA('Inventory') && (Other.Location == vect(0,0,0)) ) return false; aClass = class<Actor>(DynamicLoadObject(aClassName, class'Class')); if ( aClass != None ) A = Spawn(aClass,Other.Owner,Other.tag,Other.Location, Other.Rotation); if ( Other.IsA('Inventory') ) { if ( Inventory(Other).MyMarker != None ) { Inventory(Other).MyMarker.markedItem = Inventory(A); if ( Inventory(A) != None ) { Inventory(A).MyMarker = Inventory(Other).MyMarker; A.SetLocation(A.Location + (A.CollisionHeight - Other.CollisionHeight) * vect(0,0,1)); } Inventory(Other).MyMarker = None; } else if ( A.IsA('Inventory') ) { Inventory(A).bHeldItem = true; Inventory(A).Respawntime = 0.0; } } if ( A != None ) { A.event = Other.event; A.tag = Other.tag; return true; } return false; } /* Force game to always keep this actor, even if other mutators want to get rid of it */ function bool AlwaysKeep(Actor Other) { if ( NextMutator != None ) return ( NextMutator.AlwaysKeep(Other) ); return false; } function bool IsRelevant(Actor Other, out byte bSuperRelevant) { local bool bResult; // allow mutators to remove actors bResult = CheckReplacement(Other, bSuperRelevant); if ( bResult && (NextMutator != None) ) bResult = NextMutator.IsRelevant(Other, bSuperRelevant); return bResult; } function bool CheckReplacement(Actor Other, out byte bSuperRelevant) { return true; } function Mutate(string MutateString, PlayerPawn Sender) { if ( NextMutator != None ) NextMutator.Mutate(MutateString, Sender); } function MutatorTakeDamage( out int ActualDamage, Pawn Victim, Pawn InstigatedBy, out Vector HitLocation, out Vector Momentum, name DamageType) { if ( NextDamageMutator != None ) NextDamageMutator.MutatorTakeDamage( ActualDamage, Victim, InstigatedBy, HitLocation, Momentum, DamageType ); } function bool MutatorTeamMessage( Actor Sender, Pawn Receiver, PlayerReplicationInfo PRI, coerce string S, name Type, optional bool bBeep ) { if ( NextMessageMutator != None ) return NextMessageMutator.MutatorTeamMessage( Sender, Receiver, PRI, S, Type, bBeep ); else return true; } function bool MutatorBroadcastMessage( Actor Sender, Pawn Receiver, out coerce string Msg, optional bool bBeep, out optional name Type ) { if ( NextMessageMutator != None ) return NextMessageMutator.MutatorBroadcastMessage( Sender, Receiver, Msg, bBeep, Type ); else return true; } function bool MutatorBroadcastLocalizedMessage( Actor Sender, Pawn Receiver, out class<LocalMessage> Message, out optional int Switch, out optional PlayerReplicationInfo RelatedPRI_1, out optional PlayerReplicationInfo RelatedPRI_2, out optional Object OptionalObject ) { if ( NextMessageMutator != None ) return NextMessageMutator.MutatorBroadcastLocalizedMessage( Sender, Receiver, Message, Switch, RelatedPRI_1, RelatedPRI_2, OptionalObject ); else return true; } // Registers the current mutator on the client to receive PostRender calls. simulated function RegisterHUDMutator() { local Pawn P; ForEach AllActors(class'Pawn', P) if ( P.IsA('PlayerPawn') && (PlayerPawn(P).myHUD != None) ) { NextHUDMutator = PlayerPawn(P).myHud.HUDMutator; PlayerPawn(P).myHUD.HUDMutator = Self; bHUDMutator = True; } } defaultproperties { }
Partager