Façons de gérer l'évolution des données du concepteur en même temps que les données des joueurs

14

J'ai un jeu en ligne où les joueurs peuvent façonner le monde d'une manière ou d'une autre - par exemple. Le logement d'Ultima Online, où vous pouvez construire vos maisons directement sur certaines parties de la carte du monde. Ce sont des changements qui devraient persister au fil du temps dans le cadre d'un monde persistant.

Dans le même temps, l'équipe de conception ajoute du nouveau contenu et modifie l'ancien contenu pour améliorer et étendre le jeu aux nouveaux joueurs. Ils le feront d'abord sur un serveur de développement pendant les tests et devront ensuite fusionner leur travail avec le "travail" des joueurs sur le serveur en direct.

En supposant que nous résolvions les problèmes de conception de jeu - par exemple. les joueurs ne peuvent construire que dans des zones désignées, de sorte qu'ils ne se heurtent jamais géographiquement aux modifications du concepteur - quelles sont les bonnes façons de gérer les données ou d'organiser les structures de données afin d'éviter les conflits lorsque de nouvelles données de concepteur sont fusionnées avec de nouvelles données de joueur?

Exemple 1: un joueur fabrique un nouveau type d'objet et le jeu lui attribue l'ID 123456. Les instances de cet élément se réfèrent toutes à 123456. Imaginons maintenant que les concepteurs de jeux aient un système similaire et qu'un concepteur crée un nouvel élément également numéroté 123456. Comment éviter cela?

Exemple 2: quelqu'un crée un mod populaire qui donne à tous vos dragons un accent français. Il comprend un script avec un nouvel objet appelé assignFrenchAccentqu'ils utilisent pour attribuer les nouveaux actifs vocaux à chaque objet dragon. Mais vous êtes sur le point de déployer votre DLC "Napoleon vs Smaug" qui a un objet du même nom - comment pouvez-vous le faire sans beaucoup de problèmes de service client?

J'ai pensé aux stratégies suivantes:

  • Vous pouvez utiliser 2 fichiers / répertoires / bases de données distincts, mais vos opérations de lecture sont alors considérablement compliquées. "Afficher tous les éléments" doit effectuer une lecture sur la base de données du concepteur et une lecture sur la base de données du lecteur (et doit toujours faire la distinction entre les 2, d'une manière ou d'une autre.)
  • Vous pouvez utiliser 2 espaces de noms différents dans un même magasin, par exemple. utiliser des chaînes comme clé primaire et les préfixer avec "DESIGN:" ou "PLAYER:", mais la création de ces espaces de noms peut être non triviale et les dépendances ne sont pas claires. (par exemple. Dans un SGBDR, il se peut que vous ne puissiez pas utiliser efficacement des chaînes comme clés primaires. Vous pouvez utiliser des entiers et allouer toutes les clés primaires en dessous d'un certain nombre, par exemple 1 million, pour être des données de concepteur, et tout ce qui est au-dessus de ce point doit être Mais ces informations sont invisibles pour le SGBDR et les liens de clé étrangère traverseront le «fossé», ce qui signifie que tous les outils et scripts doivent explicitement contourner ce problème.)
  • Vous pouvez toujours travailler sur la même base de données partagée en temps réel, mais les performances peuvent être médiocres et le risque de dommages aux données du joueur peut être amélioré. Il ne s'étend pas non plus aux jeux qui s'exécutent sur plus d'un serveur avec des données mondiales différentes.
  • ... d'autres idées?

Il me semble que même si c'est principalement un problème pour les jeux en ligne, les concepts peuvent également s'appliquer au modding, où la communauté crée des mods en même temps que les développeurs corrigent leur jeu. Des stratégies sont-elles utilisées ici pour réduire les risques de rupture de mod lorsque de nouveaux correctifs sortent?

J'ai également étiqueté cela comme "contrôle de version" car à un niveau c'est ce que c'est - 2 branches de développement de données qui doivent être fusionnées. Peut-être que certaines idées pourraient venir de cette direction.

EDIT - quelques exemples ajoutés ci-dessus pour aider à clarifier le problème. Je commence à penser que le problème est vraiment celui de l'espace de noms, qui pourrait être implémenté dans un magasin via des clés composites. Cela simplifie au moins la stratégie de fusion. Mais il y a peut-être des alternatives que je ne vois pas.

Kylotan
la source
1
J'imagine que la réponse à cela pourrait dépendre au moins en partie du type de données que les concepteurs ajoutent et de ce que les joueurs ajoutent.
lathomas64
C'est possible, mais je suis plus intéressé par des solutions génériques pour 2 parties contribuant toutes deux à un seul magasin de données.
Kylotan
1
Beaucoup de gens manquent le point de cette question - ce ne sont pas les changements des joueurs qui sont en conflit: plutôt les mises à jour de contenu, etc. brisant les dispositions existantes. @Kylotan ai-je raison?
Jonathan Dickinson
C'est là que les mises à jour du contenu du développeur peuvent potentiellement entrer en conflit avec les mises à jour du contenu du lecteur. Je ne suis pas vraiment intéressé par les solutions de contournement de la conception de jeux (par exemple, ne laissez les joueurs construire qu'à certains endroits) mais par les solutions de contournement de la structure de données (par exemple, ne laissez les joueurs créer des choses avec des ID supérieurs à 1 million).
Kylotan
2
Vous n'avez pas précisé, vous attendez-vous à faire des mises à jour en temps réel d'un monde en direct? Remarque: 2 parties contribuant à un magasin de données unique EST une base de données, c'est ce que font les bases de données, vous ne pouvez pas contourner ce fait et ce serait une folie d'ignorer des décennies de connaissances sur la façon d'éviter les problèmes avec les données partagées.
Patrick Hughes

Réponses:

2

Je pense que les réponses proposant des solutions DB sautent sur une implémentation spécifique sans comprendre le problème. Les bases de données ne facilitent pas les fusions, elles vous fournissent simplement un cadre dans lequel stocker vos données. Un conflit est toujours un conflit même s'il se trouve dans une base de données. Et la vérification est la solution d'un pauvre homme au problème - cela fonctionnera, mais à un coût paralysant pour votre facilité d'utilisation.

Ce dont vous parlez ici tombe dans le modèle de développement distribué du problème. La première étape, je crois, n'est pas de considérer les joueurs et les concepteurs comme des types distincts de créateurs de contenu. Cela supprime une dimension artificielle à votre problème qui n'affecte pas la solution.

En fait, vous avez votre ligne principale - la version canonique approuvée par les développeurs. Vous pouvez (probablement) également avoir d'autres branches - des serveurs en direct où les gens développent et partagent activement des mods. Le contenu peut être ajouté sur n'importe quelle branche. Surtout, vos concepteurs n'ont rien de spécial ici - ce ne sont que des créateurs de contenu qui vivent en interne (et vous pouvez aller les trouver et les frapper quand ils bousillent).

Accepter le contenu généré par l'utilisateur est alors un problème de fusion standard. Vous devez soit ramener leurs modifications sur la ligne principale, fusionner, puis repousser, ou tirer les modifications de la ligne principale sur leur branche et fusionner (en laissant la ligne principale «propre» des éléments générés par l'utilisateur). Comme d'habitude, tirer vers votre branche et y remédier est plus convivial que de demander à d'autres personnes de retirer vos modifications, puis d'essayer de le réparer à distance de leur côté.

Une fois que vous travaillez avec ce type de modèle, tous les processus normaux pour éviter les conflits de fusion s'appliquent. Certains des plus évidents:

  • Encouragez l'utilisation libérale des espaces de noms pour limiter le contenu d'un auteur / mod / équipe particulier.
  • Lorsque le contenu doit interagir, établissez des conventions d'appel / d'utilisation claires, des conventions de dénomination et d'autres «règles» lâches qui guident le développement afin de faciliter la fusion. Fournissez des outils qui permettent aux créateurs de contenu de savoir s'ils respectent ces règles, idéalement intégrés dans la création de contenu elle-même.
  • Fournissez des outils de rapport / d'analyse pour repérer les échecs de fusion probables avant qu'ils ne se produisent. Le réparer après la fusion est probablement très douloureux. Assurez-vous qu'un contenu particulier puisse être vérifié et que tout soit clair comme prêt pour la fusion, afin que la fusion soit indolore
  • Rendez votre fusion / intégration robuste. Permettre des retours en arrière faciles. Faites des tests rigoureux du contenu fusionné: s'il échoue, ne le fusionnez pas! Répétez leur contenu ou le vôtre jusqu'à ce que la fusion se déroule proprement.
  • Évitez d'utiliser des ID entiers incrémentiels pour quoi que ce soit (vous n'avez pas de moyen fiable de les distribuer aux créateurs). Cela ne fonctionne que dans une base de données, car la base de données elle-même est un fournisseur canonique d'ID, donc vous n'obtenez jamais de doublons; cependant, il introduit également un point de défaillance / charge unique dans votre système.
  • Utilisez plutôt des GUID - ils coûtent plus cher à stocker, mais sont spécifiques à la machine et ne provoqueront donc pas de collisions. Vous pouvez également utiliser des identificateurs de chaîne, c'est beaucoup plus facile à déboguer / résoudre, mais plus cher pour le stockage et la comparaison.
MrCranky
la source
Malheureusement, certains de ces éléments ne sont pas utiles à mon problème (par exemple, faire en sorte que les joueurs suivent certaines règles, car tout cela doit être fait automatiquement côté serveur) et je ne pense pas qu'il sera pratique de prendre en charge le degré de gestion des fusions et des transactions. la sémantique que vous mentionnez, mais l'approche générale de l'allocation d'ID uniques garantis, peut-être des GUID, est probablement la plus proche de ce que j'irai avec.
Kylotan
Ah, je vois. Eh bien, puisque vous contrôlez leurs outils de construction, appliquer au moins ces approches conviviales (espaces de noms, etc.) est quelque chose que vous pouvez faire sans que les joueurs ne soient d'accord.
MrCranky
Que faites-vous si deux joueurs créent un contenu en double? Ou les instances distinctes de votre monde de jeu sont-elles traitées comme uniques? Dans ce cas, c'est peut-être une approche utile pour vérifier automatiquement chaque instance unique que vous connaissez par rapport à votre branche principale / principale pour les conflits qui se produiront lorsque vous déplacerez ces modifications dans les instances. Si vous ne pouvez pas contrôler les joueurs, vous pouvez au moins avertir votre équipe interne que le travail qu'ils font est en conflit avec l'instance X du monde, dès le début du développement.
MrCranky
Le concept d'espaces de noms n'est pas tellement le problème - choisir des espaces de noms adéquats dans l'espace de noms de tous les espaces de noms possibles l'est! :) Et le contenu en double pour moi n'est pas un problème - c'est juste 2 instances de quelque chose qui sont équivalentes. L'important est qu'aucune fusion ou écrasement dommageable ne se produise. Quant aux vérifications de collision automatiques, cela arrête les dommages causés en écriture, mais ne résout pas le problème de nommage d'origine. (Renommer des choses pour éviter une collision peut ne pas être trivial, en raison de références croisées.)
Kylotan
Ah oui, je vois maintenant, ce ne sont pas tant les espaces de noms eux-mêmes que le choix du nom. Dans ce cas, les GUID sont probablement à nouveau la réponse - le contenu est effectivement conservé dans sa propre petite zone. Un nom décoratif peut être donné, mais le jeu utiliserait le GUID.
MrCranky
1

Stockez tout en tant qu'attribut (ou décorateur) - avec des points de montage. Prenons l'exemple d'une maison conçue par le joueur:

o House: { Type = 105 } // Simple square cottage.
 o Mount point: South Wall:
  o Doodad: Chair { Displacement = 10cm }
   o Mount point: Seat:
    o Doodad: Pot Plant { Displacement = 0cm, Flower = Posies } // Work with me here :)
 o Mount point: North Wall:
  o Doodad: Table { Displacement = 1m }
    o Mount point: Left Edge:
     o Doodad: Food Bowl { Displacement = 20cm, Food = Meatballs}

Ainsi, chaque entité peut avoir un ou plusieurs points de montage - chaque point de montage peut accepter zéro ou plusieurs autres composants. Ces données seraient stockées avec la version dans laquelle elles ont été enregistrées, ainsi que toutes les propriétés pertinentes (telles que le déplacement, etc. dans mon exemple) - NoSQL ferait probablement un très bon ajustement ici (clé = ID d'entité, valeur = binaire sérialisé Les données).

Chaque composant devrait alors pouvoir «mettre à niveau» les anciennes données d'une version précédente (ne jamais supprimer les champs des données sérialisées - juste les «annuler») - cette mise à niveau se produit la minute où elle est chargée (elle serait ensuite immédiatement stockée dans la dernière version disponible). Disons que notre maison a vu ses dimensions changées. Le code de mise à niveau déterminerait relativement la distance entre les murs nord et sud et modifierait proportionnellement les déplacements de toutes les entités contenues. Comme autre exemple, notre bol de viande pourrait avoir le champ «Aliments» supprimé, et obtenir à la place une «Variété» (Viande) et une «Recette» (Boulettes). Le script de mise à niveau transformerait «Boulettes de viande» en «Viande», «Boules». Chaque composant doit également savoir comment gérer les modifications apportées aux points de montage - par exemple

Tout cela laisse exactement un problème ouvert: que se passe-t-il si deux objets se heurtent (pas leur conteneur - les points de montage vous protègent de cela)? Après une mise à niveau, vous devez vérifier les collisions et tenter de les résoudre (en séparant les choses, un peu comme SAT). Si vous ne savez pas comment résoudre la collision, retirez l'un des objets et placez-le dans une cachette - où ils peuvent acheter ces articles supprimés (gratuitement) ou les vendre (au prix fort); et informez évidemment le joueur que la mise à niveau a cassé une partie de sa mise en page - éventuellement avec une fonction de «zoom avant» afin qu'il puisse voir le problème.

En fin de compte, vous devriez laisser des changements complexes dans les mains des joueurs (échouer rapidement) car aucun algorithme ne peut rendre compte de l'esthétique - vous devriez simplement être en mesure de donner au joueur un contexte sur l'emplacement de l'élément (afin qu'ils puissent se souvenir, pas seulement atterrir avec tous ces articles dans leur cachette et ne pas savoir où ils étaient).

Jonathan Dickinson
la source
Cela se concentre un peu trop sur le positionnement des objets, ce qui n'est pas vraiment le problème clé que j'essaie de résoudre. Il s'agit davantage d'avoir des identifiants uniques dans des ensembles de données simultanés et d'avoir besoin de pouvoir les fusionner sans aucun risque possible de conflit. J'ai ajouté 2 exemples à mon message l'autre jour pour essayer d'expliquer un peu plus.
Kylotan
1

J'essaie d'associer cela à quelque chose que je comprends, donc je pense en termes de Minecraft en ce moment. J'imagine un serveur en direct avec des joueurs apportant des modifications en temps réel pendant que les développeurs s'exécutent sur un serveur de test fixant / créant du nouveau contenu.

Votre question ressemble presque à 2 questions uniques:

  1. Comment s'assurer que les ID d'objet sont uniques
  2. Comment s'assurer que les espaces de noms de script n'entrent pas en collision

J'essaierais de résoudre # 1 grâce à un système de référencement temporaire. Par exemple, lorsqu'un nouvel objet est créé par quelqu'un, il peut être marqué comme volatile ou temporaire. J'imagine que tout nouveau contenu créé sur le serveur de test serait marqué volatile (bien qu'il puisse également faire référence à du contenu non volatile).

Lorsque vous êtes prêt à apporter du nouveau contenu au serveur en direct, votre processus d'importation trouve les objets volatils et leur attribue des ID d'objet de serveur en direct qui sont définis dans la pierre. C'est différent d'une importation / fusion directe car vous devez pouvoir référencer des objets non volatils existants si vous avez besoin de les corriger ou de les mettre à jour.

Pour # 2, il semble que vous ayez vraiment besoin d'avoir un certain niveau de transmutation de script intermédiaire qui peut hacher le nom de la fonction dans un espace de noms unique. c'est à dire

assignFrenchAccent

Devient

_Z11assignFrenchAccent
Erreur 454
la source
0

Si les fichiers des données sont du texte par opposition au binaire et que les concepteurs et les joueurs modifient différentes zones, vous pouvez essayer une fusion SVN.

lathomas64
la source
0

Je pense qu'une base de données / système de fichiers répliqué à travers des environnements avec une procédure de «check-out» serait le meilleur.

Ainsi, chaque fois qu'un concepteur souhaite apporter des modifications au monde, il extrait / verrouille tous les actifs qu'il souhaite créer / modifier sur toutes les copies de la base de données (développement et production), donc aucun autre joueur ou concepteur ne peut le modifier . Il travaillerait ensuite sur la base de données de développement jusqu'à ce que la nouvelle conception soit terminée, et à ce moment-là, les modifications seraient fusionnées avec la base de données de production et ces actifs seraient archivés / déverrouillés dans tous les environnements.

Les éditions des joueurs fonctionneraient de la même manière, sauf que les rôles de base de données / système de fichiers seraient inversés - ils fonctionnent sur la base de données de production, et toutes les mises à jour sont téléchargées vers dev une fois terminées.

Le verrouillage des actifs peut être limité aux propriétés pour lesquelles vous ne souhaitez garantir aucun conflit: dans l'exemple 1, vous verrouillez ID 123456dès que le joueur commence à le créer, de sorte que les développeurs ne se verront pas attribuer cet ID. Dans l'exemple 2, vos développeurs auraient verrouillé le nom du script assignFrenchAccentpendant le développement, donc le joueur devrait choisir un nom différent lors du développement de sa modification (cette petite nuisance peut être réduite par l'espace de noms, mais cela en soi n'évitera pas les conflits à moins que vous ne donniez chacun utilisateur / développeur un espace de noms spécifique, puis vous aurez le même problème avec la gestion des espaces de noms). Cela signifie que tout développement devrait lire à partir d'une seule base de données en ligne, mais tout ce dont vous avez besoin dans cette base de données dans ces exemples sont les noms des objets, donc les performances ne devraient pas être un problème.

En termes d'implémentation, disposer d'une table unique avec toutes les clés et l'état des actifs (disponible, verrouillé depuis le développeur, verrouillé depuis la prod) synchronisé / accessible en temps réel dans tous les environnements devrait suffire. Une solution plus complexe implémenterait un système de contrôle de version complet - vous pouvez utiliser un système existant comme CVS ou SVN si vos actifs sont tous dans un système de fichiers.

SkimFlux
la source
Habituellement, il n'est pas pratique de verrouiller des données à l'échelle mondiale - les joueurs peuvent éditer le monde juste par le biais d'un jeu normal, et vous ne voudriez pas que ce jeu empêche les concepteurs de travailler. Si vous autorisez les verrous globaux, une opération de fusion est essentiellement une opération de remplacement, ce qui est facile - mais si vous n'avez pas de verrous globaux, que faire alors?
Kylotan
Comme l'a mentionné lathomas64, la réponse dépendra du type de données dont vous parlez. Sans verrous globaux, je pense que vous auriez besoin d'un système de version et d'un ensemble de règles pour résoudre tout conflit - ces règles dépendraient des données et des exigences de gameplay. Une fois que vous les avez, je suppose que chaque fusion se réduit à une simple opération de remplacement.
SkimFlux
0

Je pense que le point ici est d'accepter proprement votre responsabilité. 1) Le serveur indique ce qui est actuellement acceptable et l'API avec laquelle accéder. La base de données est en cours de modification, selon certaines règles. 2) Les créateurs sont autorisés à créer du contenu, mais il doit être jouable après les mises à jour. Ceci est de votre seule responsabilité: toute mise à jour doit pouvoir analyser les anciennes structures de données de préférence aussi propres et simples que possible.

L'idée de point de montage a des avantages si vous êtes intéressé à garder la trace d'objets et de positions uniques à l'intérieur d'une structure malléable, surtout si nous acceptons que toute la structure `` maison '' d'un joueur va subir un changement spectaculaire, et que vous souhaitez garder cela petits trucs déco dans leurs casiers respectifs.

C'est un problème extrêmement compliqué, bonne chance! Il n'existe probablement pas de réponse unique.

karmington
la source
0

Je ne pense pas que cela pose un gros problème pendant que vous le faites.

Je voudrais simplement écraser les mods créés par l'utilisateur, avec un avertissement notant que "Mod X peut ne pas fonctionner correctement avec cette version", laisser aux créateurs de mods le soin de changer leur travail. Je ne pense pas que ce soit une attente irréaliste que les mises à jour puissent désactiver certains mods.

Idem pour le contenu créé par l'utilisateur, effectuez simplement une sauvegarde et écrasez.

Je n'ai aucune expérience dans ce domaine, je fais juste des suggestions.

Woody Zantzinger
la source
Je pense que si c'était uniquement pour les mods fournis par l'utilisateur, alors vous auriez raison. Mais certains jeux concernent explicitement le contenu créé par l'utilisateur, et vous ne pouvez donc pas simplement le détruire.
Kylotan
Ensuite, laissez simplement de la place dans le système pour le contenu que vous pourriez ajouter plus tard. Si vous utilisez des numéros d'identification, réservez 1-1000. Ou si les utilisateurs peuvent nommer leurs actifs, ne laissez pas les utilisateurs commencer le nom par "FINAL-" ou quelque chose (réservez-le pour vos propres actifs). EDIT: ou mieux encore, faites-le à l'envers, en forçant le contenu utilisateur dans une plage ou un préfixe
Woody Zantzinger