Los siguientes sistemas fueron la base en establecer el juego que queríamos para El Misterio de Duvall Drive .
Gestor de estado del juego
El GameStateManager / GameStateClient es probablemente el sistema más complicado en la experiencia, ya que se trata de:
- Comenzando jugadores dentro del lobby, comenzando la cuenta regresiva para teletransportar el grupo a la área de juego principal y teletransportando jugadores a servidores reservados.
- Clon de salas corruptas, sincronización de sincronización de streaming y teletransporte de jugadores a y desde un coordinador de CFrame específico.
- Agarrando y colocando mecánicas de sellos.
- Bloqueo y desbloqueo de puertas.
- Inicializando la teletransportación final para el foyer y jugando la escena de finalización.
Lo implementamos como una máquina de estado simple (función de actualización), y los estados están en DemoConfig (GameStates enum). Algunos estados manejan el teletransportacióndel lobby inicial/reservado, mientras que otros manejan la búsqueda de una misión, el disparo del rompecabezas y la solución de la misión. Tenga en cuenta que aparte de las sellas, intentamos no tener código específico de la misión en el GameStateManager .
GameStates es principalmente del lado del servidor, pero cuando el cliente necesita hacer algo, como mostrar un recuenta regresivo, conocimiento o desactivar la interrupción de la interfaz de usuario de transmisión en vivo, el servidor y el cliente (GameStateClient) comunicarse a través de un evento remoto llamado GameStateEvent . Como con la mayoría de los casos, el cargador de eventos tiene un introducirde evento (Config.GameEvents) como primer parámetro, y datos específicos del evento
Estados del juego de teletransportación
Hay un grupo de 3 estados de juego que ejecutan tres cortinas de carga que ocultan la teleportación a la sala corrupta: Warmup, InFlight y Cooldown. <
Mientras se maneja el streaming, InFlight se ejecuta, manteniendo una pantalla oscura pulsante ligeramente. Cuando ambos Class.Player.RequestStreamAround
Un conjunto similar de Warmup, InFlight y Cooldown cutscenes ocurren cuando teletransportamos al jugador de vuelta al estado normal de la habitación, TeleportWarmupBack, TeleportInFlightBack y TeleportCooldownBack, y
Estados del juego de iluminación y atmósfera
Sabíamos que queríamos que cada estado normal y corrupto de la
Estados de la puerta de bloqueo
Qu
Administrador de eventos
EventManager nos permitió ejecutar "acciones" a lo largo del tiempo usando marcos de clave, como:
- Interpolación de propiedades y atributos de la instancia.
- Ejecutando scripts.
- Reproduciendo sonido, audio.
- Movimiento de cámara al correr.
Idealmente, usaríamos una herramienta con UI basada en el rastro, pero para esta demostración, escribimos las llaves y propiedades con nombres de propiedad manualmente. El sistema de EventManager consiste en varios scripts y un evento / función, incluyendo:
- EventManager - Logica general para crear y detener eventos, así como acciones de lado del servidor.
- EventManagerClient - Acciones del lado del cliente.
- EventManagerModule - Código común para ambas las acciones del lado del servidor y del cliente.
- EventManagerConfig - Archivo pequeño con algunas declaraciones de comando.
- EventManagerDemo - Donde se definen todos los eventos reales para esta demostración en el scriptespecífico del juego.
- EventManagerEvent , EventManagerFunc - Función remota y vinculable para ejecutar y detener eventos desde el cliente o servidor. Así es como otros sistemas pueden configurar, ejecutar y detener eventos.
Cada evento tiene un nombre, una sección con información opcional sobre el tiempo de reutilización, la función de ejecución al iniciar o finalizar, los parámetros de evento y secciones con interpolantes (interpolación de cualquier número de propiedades o atributos a lo largo del tiempo), scripts (ejecutando scripts registrados en keyframes) y sonido, audiode reproducción.
Interpolación
La interpolación permite que las propiedades y atributos de los objetos cambien sin problemas de un valor a otro en lugar de saltar desordenadamente entre marcos de clave. Definimos interpoladores para cambiar una variedad de efectos visuales; por ejemplo, el siguiente código muestra cómo interpolamos la propiedad Class.
interpolants = {objectParam = "TextLabel",property = "TextTransparency",keys = {{value = 1},{time = .5, value = 0},{time = 2.25, value = 0},{time = 3, value = 1}}}
Mientras que podríamos definir qué propiedad o atributo de objeto pertenece a qué objeto en la siguiente muestra de código, queríamos poder reutilizar los mismos eventos en diferentes "grupos de objetos" para permitir que funcione con streaming en el cliente y con objetos creados en el tiempo de ejecución.
object = workspace.SomeFolder.SomeModel
Para lograr esto, permitimos referenciar por nombre de objeto y pasar parámetros nombrados en el evento de iniciar. Para encontrar objetos nombrados, permitimos especificar un "raíz" para el evento, que permite que los objetos sean encontrados por nombre debajo de este raíz cuando el evento comenzó. Por ejemplo, en el siguiente código de ejemplo, el EventManager intenta encontrar un
params = {["RootObject"] = workspace.Content.Interior.Foyer["Ritual-DemoVersion"],},interpolants = {objectName = "Wander",attribute = "TimeScale",keys = {{value = 0.2}}}
Permitimos pasar parámetros en eventos en la sección de parámetros, y los scripts que se ejecutan en el inicio del evento podrían cambiar parámetros existentes o agregar más parámetros en la tabla "param". En el ejemplo siguiente, tenemos isEnabled parámetro
params = {isEnabled = false},interpolants = {{objectName = "FocuserGlow",property = "Enabled",keys = {{valueParam = "isEnabled"}}}
Los parámetros nos permiten referirnos a objetos que ni siquiera existen en el comienzo de la experiencia. Por ejemplo, en el siguiente código de ejemplo, una función que se ejecuta en el momento del evento creará un objeto, y establecerá la entrada BlackScreenObject en los parámetros para apuntar al objeto creado.
{objectParam = "BlackScreenObject",property = "BackgroundTransparency",keys = {{value = 0},{time = 19, value = 0},{value = 1},}}
Ejecutando eventos, instancias de eventos y conexión a triggers
Para ejecutar un evento, usaríamos o un evento remoto de los clientes, o una función del servidor. En el siguiente ejemplo, pasamos algunos parámetros a los RootObject y isEnabled eventos. Internamente, se creó una instancia de la descripción del evento, los parámetros se resolvieron a objetos reales y la función devolvió un ID para el objeto de instancia.
local params = {RootObject = workspace.Content.Interior.Foyer["Ritual-DemoVersion"]["SealDropoff_" .. missionName],isEnabled = enabled}local eventId = eventManagerFunc:Invoke("Run", {eventName = "Ritual_Init_Dropoff", eventParams = params} )
Podríamos dejar de ejecutar un evento al llamar a la función con "Stop":
eventManagerFunc:Invoke("Stop", {eventInstId = cooldownId} )
Interpolantes o otras acciones que son "cosméticas" (no cambia la simulación para todos los jugadores) se pueden ejecutar en los clientes, lo que puede dar lugar a una interpolación más suave. En la descripción del evento, podríamos proporcionar un valor predeterminado para todas las acciones como enServer = true (sin él, el valor predeterminado es el cliente). Cada acción puede sobrescribirla estableciendo su propio valor en Server.
Para conectar fácilmente un evento a un desencadenador, usamos funciones de ayudante ConnectTriggerToEvent o ConnectSpawnedTriggerToEvent , la cual encontrará el disparador por nombre. Para permitir que el mismo disparador se ejecute usando diferentes disparadores, podemos llamar eventManagerFunc con una llave "Config
Parámetros de Evento
Además de los parámetros de evento personalizados que se pasan de los scripts, otros datos que se pueden pasar opcionalmente al crear un evento incluyen el jugador, el llamado de opción (que se llama cuando termina el evento) y los parámetros de llamada. Algunos eventos deben ejecutarse solo para un jugador (los eventos con acciones que se ejecutan en el cliente), mientras que otros deben ejecutarse para todos/todas. Para hacerlo, usamos onlyTriggeredPlayer = true en los
Los eventos pueden tener tiempo de reutilización definido por minCooldownTime y maxCooldownTime . El min y max proporcionan un rango para escalar en función del número de jugadores, pero no lo usamos en esta demostración. Si necesitáramos tiempo de
Llamar a los scripts
Podríamos llamar a Scripts en los marcos de clave específicos en la sección Scripts . Por ejemplo:
scripts = {{startTime = 2, scriptName = "EnablePlayerControls", params = {true}, onServer = false }}
En el ejemplo anterior, el Activar Controles del Jugador Script necesitaría ser registrado con el módulo de gestión de eventos, como se muestra a continuación:
emModule.RegisterFunction("EnablePlayerControls", EnablePlayerControls)
RegisterFunction debe ser llamado en el script del cliente para las funciones llamadas en el cliente, y en el script del servidor para onServer = true . La función en sí misma obtendrá el eventoInstance y los parámetros pasados, pero en este caso, solo se pasa un parámetro con un valor verdadero.
local function EnablePlayerControls(eventInst, params)
Reproducir audio
Tenemos un soporte limitado para jugar non-positional audio en las marcos de clave en la sección Sonidos , por ejemplo:
sounds = {{startTime = 2, name = "VisTech_ethereal_voices-001"},}
Nota que los eventos terminando llamadas de retorno se activan cuando la duración del evento expira, pero las acciones de audio aún pueden estar reproduciéndose después.
Cambios de Vibración de la Cámara
Podríamos definir sacudidas de cámara en la sección sacudidas de cámara , como así:
cameraShakes = {{startTime = 15, shake = "small", sustainDuration = 7, targets = emConfig.ShakeTargets.allPlayers, onServer = true},}
Las "metas" se pueden iniciar solo para el jugador que activó el evento, todosPlayer, o playersInRadius para el jugador que activó el evento. Usamos un script de terceros para las sacudidas de cámara, y las sacudidas se predefinieron: eventManagerDemo.bigShake y eventManagerDemo.smallShake . sustainDuration también se puede pasar.
Lógica de misiones
Hay 7 misiones totales, y solo 6 de ellas usan sellos. La mayoría de las misiones tienen parámetros comunes, pero algunas solo tienen sellos y se teletransportan a habitaciones corruptas. Cada una de las misiones tiene una entrada en el script DemoConfig con un conjunto de parámetros en el mapa Config.Missions :
- MissionRoot : Un directorio de todas las versiones no corruptas de los objetos.
- Puertas : Puertas para bloquear hasta que un jugador recoge una llave.
- SealName / SolvedSealName : Nombre de sellado no corrupto y nombres de sellado corruptos.
- SealPlaceName : Lugares para colocar la cerradura.
- PlacedSealPlaceholderName : Objeto de marcador en el lugar para colocar el sello.
- TeleportPositionsName : Nombre de un directorio con mallas de marcador para definir las posiciones y rotaciones de teletransporte del jugador al moverse a la sala corrupta, y de vuelta a la zona normal. El mismo nombre se usa en ambos casos.
- CorruptRoomName : Nombre de las carpetas de raíz (relativas a ServerStorage) para las habitaciones corruptas. Las habitaciones corruptas se clonan bajo TempStorage.Cloned cuando la misión comienza, y se destruyen cuando la misión termina.
- MissionCompleteButtonName : Un botón de truco en las habitaciones corruptas para terminar la misión inmediatamente. Esto es para propósitos de debugging .
- CheatKey : El mismo truco que un número o CtrlShift[Número] .
Algunas de la lógica de la misión están en los GameStateManager scripts, como los sellos y las puertas proporcionan el flujo de juego principal para la mayoría de las misiones, pero la mayor parte de la lógica específica de la misión está en los MissionsLogic y MissionsLogicClient scripts que definen varios tip
- Usa una llave en una cerradura - La primera misión para abrir una puerta. Este tipo se define por LockName , KeyName .
- Coincidir con elementos - 4 misiones coinciden con los elementos. Este tipo se define por MatchItems .
- Vestir a un maniquí usando ropa doblada en capas - 1 misión en el ático tiene jugadores que recolectan tres artículos. Este tipo se define por DressItemsTagList .
- Haga clic en el elemento para terminar - 1 misión tiene este introducir, que se define por ClickTargetName.
Cada tipo de misión tiene su propio StartMissionFunc y CompleteMissionFunc . La función de inicio generalmente lee parámetros de la Mapa de Match
La lógica de coincidencia de los elementos permite "usar" (haga clic mientras mantiene) los elementos marcados con PuzzlePieceXX etiquetas sobre los elementos con PuzzleSlotXX etiqueta. Hay algunas opciones disponibles como parámetros en MatchItems mapa (si las piezas
Agarrando
Desarrollamos un simple sistema de agarre para sostener un objeto ateniendo el objeto al brazo derecho del personaje. El agarre se implementa en GrabServer2
En cada marco, comprobamos si se está realizando un intento de agarre. Si el jugador está dentro de reachDist , comenzamos a jugar ToolHoldAnim . Cuando un jugador está dentro de maxGrabDistance , el cliente envía una solicitud al servidor para agarrar un modelo ( 1> performGrab1> función).
El código del lado del servidor tiene 2 funciones principales:
- Agarrar - Arrastra la solicitud de un cliente para agarrar un aplicación de modelado.
- Lanzamiento - Gestiona la solicitud para liberar un aplicación de modeladoagarrado.
La información sobre lo que cada jugador sostiene se guarda en el mapa playerInfos . En la función de agarre, comprobamos si este modelo ya fue agarrado por otro jugador. Si es así, se envía un "EquipWorldFail" al cliente y cancela el intento de agarre. Tenga en cuenta que necesitábamos manejar situaciones en las que los jugadores agarran diferentes partes del mismo Model y cancelan el agarre en este
Si se permite agarrar, el script crea dos Attachments, uno a la derecha y otro a la izquierda usando un punto de agarre pasado desde el cliente. L
Para lanzar un aplicación de modeladoagarrado, el script del cliente se conecta a la botón GrabReleaseButton en HUD ScreenGUI. Una función Connected hace un evento al servidor. En el servidor, la liberación elimina el Attachments y 1> Class.Limit|Limitaciones