Les systèmes suivants étaient la base de l'établissement du gameplay que nous voulions pour The Mystery of Duvall Drive .
Gestionnaire d'état de jeu
Le GameStateManager / GameStateClient est probablement le système le plus complexe de l'expérience, car il gère :
- Démarrer les joueurs dans le lobby, commencer le compte à rebours pour téléporter le groupe dans la zone de jeu principale et téléporter les joueurs sur les serveurs réservés.
- Clonage des chambres corrompues, leur synchronisation en arrière-plan et leur téléportation vers et à partir d'un coordonné spécifique CFrame .
- Attraper et placer les mécaniques de sceaux.
- Verrouillage et ouverture des portes.
- Initialisation de la dernière téléportation vers le foyer et du coup de théâtre final.
Nous l'avons implémenté comme une simple machine d'État (fonction de mise à jour), et les États sont dans DemoConfig (ensemble des états du jeu). Certains États traitent le téléportation vers le serveur initial/réservé, tandis que d'autres traitent la recherche d'une mission, déclenche le puzzle et résoudre la mission. Notez que en plus des sceaux, nous avons essayé de ne pas avoir de code spécifique à la mission dans le GameStateManager .
GameStates est principalement côté serveur, mais lorsque le client doit faire quelque chose, comme afficher le compte à rebours, l'orientation ou désactiver l'arrêt de l'audio, le serveur et le client (GameStateClient) communiquent via un événement distant appelé GameStateEvent. Comme la plupart des cas, le paramètre d'événement a le taperd'événement (Config.GameEvents) comme premier paramètre, et les données spécifiques à l'événement après cela.
États de jeu de téléportation
Il existe un groupe de 3 états de jeu qui exécute trois scènes de cut-scène uniques qui masquent la téléportation vers la salle corrompue : Warmup, In
Alors que le streaming est en cours de gestion, InFlight s'exécute, en gardant un écran noir légèrement pulsant. Lorsque les deux Class.
Une série similaire de scènes de démarrage, d'atterrissage et de recharge se produit lorsque nous téléportons le joueur dans l'état normal de la pièce, TéléportWarmupBack , TéléportInFlightBack et T
États de jeu sur lumière et atmosphère
Nous savions que nous voulions que chaque état normal et corrom
Verrouillage des portes Établir des états
N
Gestionnaire d'événements
EventManager nous a permis de exécuter des « actions » au fil du temps en utilisant des cadres de clé, tels que :
- Interpolation des propriétés et des attributs de l'instance.
- Exécution des scripts.
- Lecture de l'audio.
- Exécution de secousses de caméra.
Nous utiliserions idéalement un outil avec une interface utilisateur basée sur des balises, mais pour cette démonstration, nous avons écrit les clés et les noms des propriétés manuellement. Le EventManager système consiste en plusieurs scripts et une fonction d'événement, y compris :
- EventManager - Logique globale pour la création et l'arrêt d'événements, ainsi que les actions côté serveur.
- EventManagerClient - Actions côté client.
- EventManagerModule - Code commun pour les actions côté serveur et côté client.
- EventManagerConfig - Petit fichier avec quelques déclarations de commande.
- EventManagerDemo - Où tous les événements actuels pour cette démo sont définis dans le script spécifique au jeu.
- EventManagerEvent , EventManagerFunc - Fonctionnalité à distance et liée à l'événement pour exécuter et arrêter les événements du client ou du serveur. Voici comment d'autres systèmes peuvent configurer, lanceret arrêter les événements.
Chaque événement a un nom, une section avec des informations facultatives sur le temps de recharge, la section pour exécuter sur le démarrage ou la terminer, les paramètres d'événement et les sections avec des interpolants (interpolant n'importe quel nombre de propriétés ou d'attributs au fil du temps), les scripts (exécution des scripts enregistrés au démarrage ou à la fin), les secousses de caméra et le joueur audio.
Interprétation
L'interpolation permet aux propriétés et aux attributs d'objets de changer facilement d'un valeur à l'autre au lieu de sauter séparément entre les cadres de clé. Nous avons défini des interpolants pour modifier une variété d'effets visuels ; par exemple, le code suivant montre comment nous avons interpol
interpolants = {objectParam = "TextLabel",property = "TextTransparency",keys = {{value = 1},{time = .5, value = 0},{time = 2.25, value = 0},{time = 3, value = 1}}}
Bien que nous puissions définir quelle propriété ou attribut d'objet appartient à quel type dans l'exemple de code suivant, nous voulions être en mesure de réutiliser les mêmes événements sur différents « groupes d'objets » pour permettre qu'il fonctionne avec le streaming sur le client et avec les objets créés au moment de l'exécution.
object = workspace.SomeFolder.SomeModel
Pour accomplir cela, nous avons autorisé le référencement par nom d'objet et la transmission de paramètres nommés lors de l'événement de commencer. Pour trouver des objets nommés, nous avons autorisé la spécification d'un « racine » pour l'événement, ce qui permet aux objets d'être trouvés par nom sous ce racine lorsque l'événement a commencé. Par exemple, dans le code suivant,
params = {["RootObject"] = workspace.Content.Interior.Foyer["Ritual-DemoVersion"],},interpolants = {objectName = "Wander",attribute = "TimeScale",keys = {{value = 0.2}}}
Nous avons autorisé le paramètre de passer dans des événements dans la section paramètres, et les scripts s'exécutant sur le paramètre de démarrage de l'événement pourraient soit changer les paramètres existants, soit ajouter plus de paramètres dans la table "param". Dans l'exemple suivant, nous avons
params = {isEnabled = false},interpolants = {{objectName = "FocuserGlow",property = "Enabled",keys = {{valueParam = "isEnabled"}}}
Les paramètres nous permettent de nous référer à des objets qui n'existent même pas au début de l'expérience. Par exemple, dans l'exemple de code suivant, une fonction s'exécutant au moment de l'événement créera un objet, et définira l'entrée BlackScreenObject dans les paramètres pour poindre sur l'objet créé.
{objectParam = "BlackScreenObject",property = "BackgroundTransparency",keys = {{value = 0},{time = 19, value = 0},{value = 1},}}
Exécuter des événements, des instances d'événements et se connecter aux déclencheurs
Pour exécuter un événement, nous utiliserions soit un événement distant à partir des clients, soit une fonction du serveur. Dans l'exemple suivant, nous avons passé quelques paramètres aux RootObject et isEnabled événements. Dans l'exemple suivant, une instance de la description de l'événement a été créée, les paramètres résolus en objets réels, et la fonction a renvoyé un id pour l'instant de l'événement.
local params = {RootObject = workspace.Content.Interior.Foyer["Ritual-DemoVersion"]["SealDropoff_" .. missionName],isEnabled = enabled}local eventId = eventManagerFunc:Invoke("Run", {eventName = "Ritual_Init_Dropoff", eventParams = params} )
Nous pourrions arrêter d'exécuter un événement en appelant la fonction avec « Stop » :
eventManagerFunc:Invoke("Stop", {eventInstId = cooldownId} )
Les interpolants ou d'autres actions qui sont « cosmétiques » (ne modifiez pas la simulation pour tous les joueurs) peuvent être exécutés sur les clients, ce qui peut entraîner une interpolation plus douce. Dans la description de l'événement, nous pouvons fournir une valeur par défaut pour toutes les actions comme surServer = true (sans elle, la valeur par défaut est client). Chaque action peut l'écraser en définissant sa propre surServer.
Pour connecter facilement un événement à un déclencheur, nous avons utilisé des fonctions d'aide ConnectTriggerToEvent ou ConnectSpawnedTriggerToEvent, lequel trouve le déclencheur par nom. Pour permettre que le même événement soit déclenché en utilisant différents déclencheurs, nous pouvons appeler event
Paramètres d'événement
En plus des paramètres d'événement personnalisés passés par les scripts, d'autres données qui peuvent être optionnellement passées lors de la création d'un événement incluent le joueur, le rappel (à être appelé lorsque l'événement se termine), et les paramètres de rappel. Certains événements doivent s'exécuter pour un seul joueur (événements avec des actions s'exécutant sur le client), tandis que d'autres doivent s'exécuter pour tout. Pour faire en sorte que cela
Les événements peuvent avoir des temps de recharge définis par minCooldownTime et maxCooldownTime. Les min et max fournissent une gamme pour le redimensionnement en fonction du nombre de joueurs, mais nous ne l'avons pas utilisé dans cette démo. Si nous avions
Appeler des scripts
Nous pourrions appeler Scripts à des keyboards spécifiques dans la section Scripts . Par exemple :
scripts = {{startTime = 2, scriptName = "EnablePlayerControls", params = {true}, onServer = false }}
Dans l'exemple précédent, le EnablePlayerControls Class.Script aurait besoin d'être enregistré avec le module de gestion d'événements, comme suivant :
emModule.RegisterFunction("EnablePlayerControls", EnablePlayerControls)
RegisterFunction doit être appelé dans le script du client pour les fonctions appelées sur le client, et dans le script du serveur pour onServer = true. La fonction elle-même obtiendra un événementInstance et des paramètres passés, mais dans ce cas, seul un paramètre est passé avec une valeur vraie.
local function EnablePlayerControls(eventInst, params)
Jouer de l'audio
Nous avons un support limité pour jouer non positionnel audio dans les clés de la section Sons , par exemple :
sounds = {{startTime = 2, name = "VisTech_ethereal_voices-001"},}
Remarquez que les rappels d'événement se déclenchent lorsque la durée de l'événement expire, mais les actions audio peuvent toujours jouer après.
Mouvements de caméra
Nous pourrions définir les secousses de caméra dans la section caméra secousses , comme suivant :
cameraShakes = {{startTime = 15, shake = "small", sustainDuration = 7, targets = emConfig.ShakeTargets.allPlayers, onServer = true},}
Les « cibles » ne peuvent être initialisées que pour le joueur qui a déclenché l'événement, tous lesJoueurs, ou les joueursInRadius au joueur déclencheur. Nous avons utilisé un script tiers pour les secousses de caméra, et les secousses étaient préfinies : eventManagerDemo.bigShake et eventManagerDemo.smallShake. Nous pouvons également passer.
Missions Logic
Il y a 7 missions totales, et seulement 6 d'entre elles utilisent des selles. La plupart des missions ont des paramètres communs, bien que certaines ne sont que pour les missions avec des selles et se téléportent dans les chambres pour se corrompre. Chaque mission a une entrée dans le script DemoConfig avec un ensemble de paramètres dans la carte Config.Missions :
- MissionRoot : Dossier de toutes les versions non corrompues d'objets.
- Portes : Portes pour verrouiller jusqu'à ce qu'un joueur prenne un sceau.
- SealName / SolvedSealName : noms de scellés non corrompus et scellés corrompus.
- SealPlaceName : Places pour placer le sceau.
- PlacedSealPlaceholderName : Objet de placement à l'endroit où se trouve le sceau.
- Nom de dossier de téléportationPosition de téléportation nommé : Nom d'un dossier avec des mailles de placeholder pour définir les positions et les rotations du joueur lors de la dérive vers la salle corrompue et dans la zone normale. Le même nom est utilisé dans les deux cas.
- CorruptRoomName : Nom des dossiers de racine (relatif à ServerStorage) pour les chambres corrompues. Les chambres corrompues se clonent sous TempStorage.Cloned lorsque la mission commence, et elles sont détruites lorsque la mission est terminée.
- MissionCompleteButtonName : Bouton de triche dans les chambres corrompues pour terminer la mission immédiatement. Ceci est pour les fins de débogage.
- CheatKey : Le même cheat qu'un nombre ou CtrlShift[Numéro] .
Une partie de la logique de mission est dans les GameStateManager scripts, car les phoques et les portes fournissent le flux de jeu principal pour la plupart des missions, mais la plupart de la logique spécifique à la mission est dans MissionsLogic et 0> MissionsLogicClient0> scripts qui descriptionplusieurs types de missions. Le type est défini
- Utiliser une clé sur une verrouillure - La première mission pour ouvrir une porte. Ce type est défini par LockName , KeyName .
- Correspondre aux éléments - 4 missions correspondent aux éléments. Ce type est défini par MatchItems.
- Habiller un mannequin en utilisant des vêtements imprimés en plusieurs couches - 1 mission dans le grenier a des joueurs qui collectent trois articles. Ce type est défini par DressItemsTagList.
- Cliquez sur l'élément pour terminer - 1 mission a ce taper, qui est définie par ClickTargetName.
Chaque type de mission a son propre StartMissionFunc et CompleteMissionFunc . La fonction d'arrêt lance généralement les paramètres à partir de la serveur MatchItem
La logique de correspondance des articles permet de «utiliser» (cliquez pendant que vous maintenez) les éléments marqués avec les PuzzlePieceXX tags sur les éléments avec PuzzleSlotXX tag. Il y a quelques options disponibles en tant que paramètres dans MatchItems map (si les éléments doivent être appl
Prendre
Nous avons développé un simple système de capture pour tenir un objet en attendant qu'il soit attiré par le personnage à droite. La capture est implémentée dans GrabServer2 et GrabClient scripts. Elle commence dans ProcessClick, ce qui lance un rayon à travers le point cliqué/touché
Sur chaque cadre, nous vérifions si une tentative de saisie est en cours. Si le joueur est dans reachDist , nous commençons à jouer ToolHoldAnim . Lorsqu'un joueur est dans maxGrabDistance , le client envoie une demande au serveur pour effectuer un modèle ( 1> performanceGrab1> fonction).
Le script côté serveur a 2 fonctions principales :
- Prenez - Gère la demande d'un client pour prendre un modèlisation.
- Relâcher - Gère la demande de libération d'un modèlisationsaisi.
Les informations sur ce que chaque joueur détient sont conservées dans la carte playerInfos . Dans la fonction d'attrape, nous vérifions si ce modèle est déjà attrapé par un autre joueur. Si oui - un « EquipWorldFail » est envoyé au client et annule l'attrape. Notez que nous avons dû gérer des situations où les joueurs attrapent différentes parties du même Model et annulent l'attrape dans ce cas.
Si la saisie est autorisée, le script crée deux Attachments , un sur la main droite et un sur l'objet en utilisant un point d'attrape pass
Pour la libération d'un modèlisationsaisi, le script client se connecte au bouton GrabReleaseButton dans HUD ScreenGUI. Une fonction Connected lance un événement sur le serveur. Sur le serveur, la libération supprime le Attachments et 2> Class.Limit|Restraints