
Qu'est-ce que c'est et pourquoi?
Lors de la conception, un développeur peut rencontrer un problème: les créatures et les objets peuvent avoir des capacités différentes dans différentes combinaisons. Les grenouilles sautent et nagent, les canards nagent et volent, mais pas avec un poids, et les grenouilles peuvent voler avec une branche et des canards. Par conséquent, il est pratique de passer de l'héritage à la composition et d'ajouter dynamiquement des capacités. La nécessité d'animer des grenouilles volantes a entraîné un rejet injustifié des méthodes de compétence et le transfert de leur code aux équipes dans l'une des implémentations. Le voici:
class CastSpellCommand extends Command { constructor (source, target, spell) { this.source = source; this.target = target; this.spell = spell; } execute () { const spellAbility = this.source.getAbility(SpellCastAbility);
Que peut-on faire?
Considérez plusieurs approches de nature différente:
Observateur
class Executor extends Observer {} class Animator extends Observer {}
Une solution classique bien connue des programmeurs. Il vous suffit de le modifier au minimum pour vérifier les valeurs renvoyées par les observateurs:
this.listeners.reduce((result, listener) => result && listener(action), true)
Inconvénient: les observateurs doivent s'abonner aux événements dans le bon ordre.
Si vous effectuez une gestion des erreurs, l'animateur pourra également afficher des animations d'actions ayant échoué. Vous pouvez transmettre la valeur précédente aux observateurs, conceptuellement la solution reste la même. Que les méthodes d'observation ou les fonctions de rappel soient appelées, qu'une boucle régulière soit utilisée à la place de la convolution, les détails ne sont pas si importants.
Laisser tel quel
Et en effet. L'approche actuelle présente à la fois des inconvénients et des avantages:
- Tester la capacité d'exécuter une commande nécessite une commande
- Les arguments dans un ordre changeant, les conditions et les préfixes de méthode sont câblés
- Dépendances de boucle (commande <sort <commande)
- Entités supplémentaires pour chaque action (la méthode est remplacée par la méthode, la classe et son constructeur)
- Connaissances et actions excessives d'une équipe individuelle: de la mécanique du jeu aux erreurs de synchronisation et à la manipulation directe des propriétés des autres
- L'interface est trompeuse (exécute non seulement des appels, mais ajoute également des commandes via addChildren; ce qui, évidemment, fait le contraire)
- Besoin douteux et mise en œuvre d'instructions récursives en soi
- Le cas échéant, la classe de répartiteur n'exécute pas ses fonctions
- [+] Prétendument le seul moyen d'animer dans la pratique, si les animations ont besoin de données complètes (indiquées comme raison principale)
- [+] Probablement d'autres raisons
Certaines des lacunes peuvent être traitées séparément, mais les autres nécessitent des changements plus radicaux.
ad hoc
- Les conditions d'exécution de l'équipe, notamment les mécanismes de jeu, doivent être retirées des équipes et exécutées séparément. Les conditions peuvent changer lors de l'exécution, et la mise en évidence des boutons inactifs en gris se produit en pratique bien avant le début du travail sur l'animation, sans parler de la logique. Pour éviter la copie, il peut être judicieux de stocker les conditions générales dans des prototypes de capacité.
- Les méthodes de retour, en combinaison avec le paragraphe précédent, la nécessité de tels contrôles disparaîtra:
const spellAbility = this.source.getAbility(SpellCastAbility);
Le moteur javascript lui-même affichera la TypeError correcte lorsque la méthode est appelée par erreur. - L'équipe n'a pas non plus besoin de telles connaissances:
healthAbility.health = Math.max( 0, resultHealth );
- Pour résoudre le problème des arguments qui changent de place, ils peuvent être passés par l'objet.
- Bien que le code appelant ne soit pas disponible pour étude, il semble que la plupart des lacunes augmentent en raison de la manière non optimale d'invoquer des actions de jeu. Par exemple, les gestionnaires de boutons accèdent à des entités spécifiques. Par conséquent, les remplacer dans les gestionnaires par des commandes spécifiques semble tout à fait naturel. Si vous avez un répartiteur, il est beaucoup plus facile d'appeler une animation après l'action, vous pouvez lui transférer les mêmes informations, afin qu'il ne manque pas de données.
File d'attente
Pour afficher l'animation de l'action une fois l'action terminée, il suffit de les ajouter à la file d'attente et de les exécuter à peu près comme dans la solution 1.
[ [ walkRequirements, walkAction, walkAnimation ], [ castRequirements, castAction, castAnimation ],
Peu importe les entités dans le tableau: fonctions interdites avec les paramètres nécessaires, instances de classes personnalisées ou objets ordinaires.
La valeur d'une telle solution est la simplicité et la transparence, il est facile de faire une fenêtre coulissante pour visualiser les N dernières commandes.
Convient parfaitement au prototypage et au débogage.
Cours de compréhension
Nous faisons un cours d'animation pour la capacité.
class MovementAbility { walk (...args) {
S'il est impossible d'apporter des modifications à la classe appelante, nous en héritons ou décorons la méthode souhaitée pour qu'elle appelle l'animation. Ou nous transmettons l'animation au lieu de la capacité, ils ont la même interface.
Bien adaptés lorsque vous avez besoin de pratiquement le même ensemble de méthodes, elles peuvent être vérifiées et testées automatiquement.
Combinaisons de méthodes
const AnimatedMovementAbility = combinedClass(MovementAbility, { ['*:before'] (method, ...args) {
Ce serait une opportunité intéressante avec le support de la langue maternelle.
Il est bon de l'utiliser si cette option est plus productive, bien qu'un proxy soit réellement nécessaire.
Procurations
Nous enveloppons les capacités dans les proxys, les méthodes de capture dans les getters.
new Proxy(new MovementAbility, {})
Inconvénient: beaucoup plus lent que les appels réguliers, ce qui n'est pas si important pour l'animation. Sur un serveur qui traite des millions d'objets, le ralentissement serait notable, mais le serveur n'a pas besoin d'animation.
Promesse
Vous pouvez construire des chaînes à partir de Promise, mais il existe une autre option (ES2018):
for await (const action of actionDispatcher.getActions()) {
getActions renvoie un itérateur d'action asynchrone. La méthode suivante de l'itérateur renvoie la promesse différée de l'action suivante. Après avoir traité les événements de l'utilisateur et du serveur, nous appelons resolver (), créons une nouvelle promesse.
Meilleure équipe
Créez des objets comme celui-ci:
{actor, ability, method, options}
Le code revient à vérifier et à appeler la méthode de capacité avec des paramètres. L'option la plus simple et la plus productive.
Remarque