I seguenti sistemi sono stati la base per l'istituzione del gameplay che volevamo per The Mystery of Duvall Drive .
Gestore dello stato del gioco
Il GameStateManager / GameStateClient è probabilmente il sistema più complicato nell'esperienza, poiché si occupa di:
- Iniziare i giocatori all'interno della lobby, iniziare il conto alla rovescia per teletrasportare il gruppo nell'area di gioco principale e teletrasportare i giocatori nei server riservati.
- Clonare le stanze corrompiute, sincronizzarle in tempo reale e teletrasportare i giocatori in e da un coordinato CFrame specifico.
- Afferra e posiziona meccanismi di sigillatura.
- Blocco e sblocco delle porte.
- Inizializzazione del teletrasporto finale e riproduzione della scena di avvio.
L'abbiamo implementato come una semplice macchina di stato (funzione aggiornamento), e gli stati sono in DemoConfig (GameState枚). Alcuni stati gestiscono il Teletrasportoiniziale/riservato, mentre altri gestiscono la ricerca di una missione, l'avvio del puzzle e la risoluzione della missione. Nota che oltre a sigilli, abbiamo cercato di non avere un codice specifico della missione nel GameStateManager GameStates è principalmente lato server, ma quando il client deve fare qualcosa, come mostrare il conto alla rovescia, la conoscenza o disabilitare l'interruzione della UI di streaming, il server e il client (GameStateClient) comunicano tramite un evento remoto chiamato GameStateEvent . Come nella maggior parte dei casi, il parametro di evento ha il inserisci / scrividi evento (Config.GameEvents) come primo parametro e i dati specifici dell'evento dopo.
Giochi di teletrasporto stati
C'è un gruppo di 3 stati di gioco che eseguono tre cutscene uniche che nascondono la teletrasportazione nella stanza corrotta: Warmup, InFlight e Coold
Mentre viene gestito lo streaming, InFlight viene eseguito, mantenendo uno schermo scuro leggermente pulsante. Quando entrambi Class.Player.RequestStream
Si verificano simili set di cutscene di Warmup, InFlight e Cooldown quando teletrasportiamo il giocatore al normale stato della stanza, TeleportWarmupBack , TeleportInFlightBack e TeleportCooldownBack ,
Stati di gioco per illuminazione e atmosfera
Sapevamo che volevamo che lo stato normale e cor
Blocco delle porte Game State
Event Manager
EventManager ci ha permesso di eseguire "azioni" nel tempo utilizzando keyframe, come:
- Interpolazione delle proprietà e degli attributi dell'istanza.
- Eseguire gli script.
- Riproducendo audio/suono.
- Otturazione delle scosse della fotocamera.
Idealmente useremmo uno strumento con interfaccia utente basata sulla traccia, ma per questa dimostrazione abbiamo digitato manualmente le chiavi e i nomi delle proprietà. Il sistema EventManager consiste in più script e un evento / funzione, tra cui:
- EventManager - Logica generale per la creazione e l'interruzione degli eventi, nonché azioni lato server.
- EventManagerClient - Azioni lato client.
- EventManagerModule - Codice comune per entrambe le azioni lato server e lato client.
- EventManagerConfig - Piccolo file con alcune dichiarazioni di comando.
- EventManagerDemo - Dove tutti gli eventi attuali per questa demo sono definiti negli script specifici del gioco.
- EventManagerEvent , EventManagerFunc - Funzione remota e legabile per eseguire e interrompere gli eventi dal client o dal Server. Questo è il modo in cui altri sistemi possono configurare, Eseguiree interrompere gli eventi.
Ogni evento ha un nome, una sezione con informazioni opzionali su cooldown, funzione da eseguire all'avvio o alla Terminare, parametri degli eventi, e sezioni con interpolanti (interpolazione di qualsiasi numero di proprietà o attributi nel tempo), script (esecuzione di script registrati nei keyframe) e l'audio/suonodi riproduzione.
Interpolazione
L'interpolazione consente alle proprietà e agli attributi di oggetti di cambiare senza problemi da un valore all'altro invece di saltare disgiuntamente tra i keyframe. Definimmo interpolanti per modificare una varietà di effetti visivi; per esempio, il seguente codice mostra come abbiam
interpolants = {objectParam = "TextLabel",property = "TextTransparency",keys = {{value = 1},{time = .5, value = 0},{time = 2.25, value = 0},{time = 3, value = 1}}}
Mentre potremmo definire quale proprietà o attributo di oggetto appartiene a cosa come nel seguente esempio di codice, abbiamo voluto essere in grado di riutilizzare gli stessi eventi su diversi "gruppi di oggetti" per consentire che funzioni con lo streaming sul client e con gli oggetti creati in tempo di esecuzione.
object = workspace.SomeFolder.SomeModel
Per raggiungerlo, abbiamo consentito il riferimento per nome oggetto e l'impostazione di parametri in nome all'Iniziare, cominciaredell'evento. Per trovare gli oggetti in nome, abbiamo consentito di specificare un "Radice" per l'evento, che consente agli oggetti di essere trovati per nome sotto questo radice quando l'evento è iniziato. Ad esempio, nel seguente codice snippet
params = {["RootObject"] = workspace.Content.Interior.Foyer["Ritual-DemoVersion"],},interpolants = {objectName = "Wander",attribute = "TimeScale",keys = {{value = 0.2}}}
Abbiamo consentito di passare i parametri negli eventi nella sezione parametri, e gli script in esecuzione all'avvio dell'evento potrebbero modificare i parametri preesistenti o aggiungere più parametri nella tabella "param". Nel seguente esempio, abbiamo is
params = {isEnabled = false},interpolants = {{objectName = "FocuserGlow",property = "Enabled",keys = {{valueParam = "isEnabled"}}}
I parametri ci consentono di fare riferimento a oggetti che non esistono nemmeno all'inizio dell'esperienza. Ad esempio, nel seguente esempio di codice una funzione in esecuzione all'inizio dell'evento creerà un oggetto e imposterà l'elemento BlackScreenObject nella tabella dei parametri per puntare all'oggetto creato.
{objectParam = "BlackScreenObject",property = "BackgroundTransparency",keys = {{value = 0},{time = 19, value = 0},{value = 1},}}
Esegui eventi, istanze di evento e connessione ai trigger
Per eseguire un evento, useremo in ogni caso un evento remoto dai client, o una funzione dal Server. Nel seguente esempio, abbiamo passato alcuni parametri ai RootObject e isEnabled eventi. In interno, è stata creata un'istanza della descrizione dell'evento, i parametri risolti in oggetti reali e la funzione ha restituito un ID per l'istanza dell'evento.
local params = {RootObject = workspace.Content.Interior.Foyer["Ritual-DemoVersion"]["SealDropoff_" .. missionName],isEnabled = enabled}local eventId = eventManagerFunc:Invoke("Run", {eventName = "Ritual_Init_Dropoff", eventParams = params} )
Potremmo interrompere l'esecuzione di un evento chiamando la funzione "Stop":
eventManagerFunc:Invoke("Stop", {eventInstId = cooldownId} )
Interpolanti o altre azioni che sono "cosmetici" (non cambiare la simulazione per tutti i giocatori) possono essere eseguiti sui client, il che potrebbe comportare un'interpolazione (di stringhe)più liscia. Nella Descrizionedell'evento, potremmo fornire un valore predefinito per tutte le azioni come onServer = true (senza di esso, il valore predefinito è il client). Ogni azione può sovrascriverlo impostando il proprio onServer.
Per facilmente connettere un evento a un grilletto, abbiamo utilizzato funzioni ausiliari ConnectTriggerToEvent o ConnectSpawnedTriggerToEvent , quest'ultimo dei quali trova il trigger per nome. Per consentire lo stesso evento di essere attivato utilizzando diversi trigger, possiamo chiamare eventManagerFunc con una chi
Parametri degli eventi
Oltre a parametri evento personalizzati passati dagli script, altre informazioni che possono essere opzionalmente passate quando si crea un evento includono Giocatore, il callback (che può essere chiamato quando l'evento finisce) e i parametri dell' callback. Alcuni eventi dovrebbero essere eseguiti solo per un giocatore (gli eventi con azioni in esecuzione sul client), mentre altri dovrebbero essere eseguiti per tutti/tutte. Per rendere eseguibile solo per un Giocatore, abbiamo us
Gli eventi possono avere cooldown definiti da minCooldownTime e maxCooldownTime . Il min e il max forniscono una gamma per lo scalaggio in base al numero di Giocatore, ma non l'abbiamo utilizzato in questa demo. Se avessimo
Evocare gli script
Potremmo chiamare Scripts in alcuni keyframe specifici nella sezione Script . Ad esempio:
scripts = {{startTime = 2, scriptName = "EnablePlayerControls", params = {true}, onServer = false }}
Nell'esempio precedente, il EnablePlayerControls Class.Script avrebbe dovuto essere registrato con il modulo gestore degli eventi, come segue:
emModule.RegisterFunction("EnablePlayerControls", EnablePlayerControls)
RegisterFunction deve essere chiamato nel script del client per le funzioni chiamate sul client, e nel script del server per onServer = true . La funzione stessa riceverà eventInstance e parametri passati, ma in questo caso, solo un parametro viene passato con un valore vero.
local function EnablePlayerControls(eventInst, params)
Riproduzione Audio
Abbiamo un supporto limitato per giocare non posizionali audio ai keyframe nella sezione Suoni , per esempio:
sounds = {{startTime = 2, name = "VisTech_ethereal_voices-001"},}
Nota che gli eventi di completamento del callback si attivano quando la durata dell'evento scade, ma le azioni audio potrebbero essere ancora in esecuzione dopo.
Oscillazione della fotocamera
Potremmo definire le scosse della fotocamera nella sezione camereShakes , come segue:
cameraShakes = {{startTime = 15, shake = "small", sustainDuration = 7, targets = emConfig.ShakeTargets.allPlayers, onServer = true},}
I "target" possono essere inizializzati solo per il giocatore che ha attivato l'evento, tuttiPlayer, o playersInRadius al Giocatoreche ha attivato l'evento. Abbiamo utilizzato uno script di terze parti per le scosse della telecamera, e le scosse sono predefinite: eventManagerDemo.bigShake e eventManagerDemo.smallShake . sustainDuration potrebbe anche
Logica delle missioni
Ci sono 7 missioni totali, e solo 6 di loro usa i sigilli. La maggior parte delle missioni ha parametri comuni, anche se alcune sono solo per le missioni con i sigilli e il teletrasporto per corrompere le stanze. Ogni missione ha un'entrata nel script DemoConfig con un insieme di parametri nella mappa Config.Missions :
- MissionRoot : Una cartella di tutte le versioni non corrotte degli oggetti.
- Porte : Porte per bloccare fino a quando un giocatore non prende una chiave.
- SealName / SolvedSealName : Non-corrupt seal and corrupted seal names.
- SealPlaceName : Posti per mettere il sigillo.
- PlacedSealPlaceholderName : oggetto Placeholder in luogo per posizionare il sigillo.
- TeleportPositionsName : Nome di un cartella con placeholder meshes per definire le posizioni e le rotazioni del giocatore quando si muove nella stanza corrotta e torna nell'area normale. Lo stesso nome viene utilizzato in entrambi i casi.
- CorruptRoomName : Nomi dei cartelle della root (rispetto a ServerStorage) per le stanze corrette. Le stanze corrette si replicano sotto TempStorage.Cloned quando la missione inizia, e vengono distrutte quando la missione è finita.
- MissionCompleteButtonName : Un pulsante di truffa nelle stanze corrompiute per terminare la missione immediatamente. Questo è per scopi di debugging .
- CheatKey : La stessa truffa di un numero o CtrlShift[Numero] .
Alcune della logica di missione sono negli script GameStateManager , come le sigle e le porte forniscono il flusso di gioco principale per la maggior parte delle missioni, ma la maggior parte della logica specifica delle missioni è in MissionsLogic e MissionsLogicClient script che definiscono diversi tipi di missioni. Il tipo
- Usa una chiave su una serratura - La prima missione per aprire una porta. Questo tipo è definito da LockName , KeyName .
- Corrispondere gli oggetti - 4 missioni corrispondono agli oggetti. Questo tipo è definito da MatchItems .
- Vestire un manichino usando la stoffa a strati - 1 missione nell'attico ha i giocatori a raccogliere tre oggetti. Questo tipo è definito da DressItemsTagList .
- Fai clic sull'elemento per terminare. - 1 missione ha questo inserisci / scrivi, che è definito da ClickTargetName .
Ogni tipo di missione ha il suo proprio StartMissionFunc e CompleteMissionFunc . La funzione di avvio di solito legge i parametri dalla mappa <
La logica di corrispondenza degli oggetti consente di "utilizzare" (cliccare mentre si tiene premuto) gli oggetti contrassegnati con PuzzlePieceXX tag su oggetti con PuzzleSlotXX tag. Ci sono alcune opzioni disponibili come parametri in MatchItems mappa (
Afferra
Abbiamo sviluppato un semplice sistema di raccolta oggetti attaccando l'oggetto al braccio destro del personaggio. La raccolta è implementata in <
Su ogni frame, controlliamo se un tentativo di aggancio è in corso. Se il giocatore è all'interno di reachDist , iniziamo a giocare a ToolHoldAnim . Quando un giocatore è all'interno di maxGrabDistance , il client invia una richiesta al server per ottenere effettivamente un modello ( 1> performGrab1> funzione).
Lo script lato server ha 2 funzioni principali:
- Grab - Gestisce la richiesta di un cliente per prendere un modello.
- Rilascia - Gestisce la richiesta per rilasciare un modello prelevato.
Le informazioni su ciò che ciascun giocatore possiede vengono salvate nella mappa playerInfos . Nella funzione di grab, controlliamo se questo modello è già stato grabby da un altro Giocatore. Se sì - un "EquipWorldFail" viene inviato al client e può annullare l'tentativo/tentativadi grab. Nota che abbiamo dovuto gestire situazioni in cui i giocatori grabber differenti parti del medesimo Model , e
Se viene consentito l'aggancio, lo script crea due Attachments , uno sulla mano destra e uno sulla mano sinistra utilizzando un punto di agg
Per rilasciare un modello preso, lo script del client si connette al pulsante GrabReleaseButton in HUD ScreenGUI. Una funzione Connected fire un evento sul Server. Nello Server, la rilascio elimina il Attachments e 2> Class.Limit|Limitazioni2> ,