Os seguintes sistemas foram a base na criação do gameplay que queríamos para The Mystery of Duvall Drive .
Gerenciador de estado de jogo
O Gerenciador de Estado de Jogo / Gerenciador de Estado de Cliente é provavelmente o sistema mais complicado na experiência, pois trata de:
- Começando jogadores dentro do lobby, começando a contagem regressiva para teletransportar o grupo para a área de jogabilidade principal e teletransportar os jogadores para servidores reservados.
- Clonar salas corrompidas, sincronizá-las e teletransportar jogadores para e a partir de um específico atribuído CFrame coordenado.
- Pegando e colocando mecânicas de selos.
- Bloqueando e destrancando portas.
- Inicializando o teleporte final para o lobby e jogando a cena final.
Nós implementamos como uma simples máquina de estado (função de atualização), e os estados estão em DemoConfig (GameStates enum). Alguns estados lidam com o teleportarinicial/reservado do servidor, enquanto outros lidam com encontrar uma missão, iniciar o quebra-cabeça e resolver a missão. Observe que, além de selos, nós tentamos não ter código específico de missão no GameStateManager .
O GameStates é principalmente do lado do servidor, mas quando o cliente precisa fazer algo, como mostrar contagem regressiva, lore ou desativar streaming pause UI, o servidor e o cliente (GameStateClient) comunicam via um evento remoto chamado GameStateEvent. Como na maioria dos casos, o tipo de evento tem o digitarde evento (Config.GameEvents) como primeiro parâmetro e os dados de evento específicos depois disso.
Estados de Jogo de Teletransporte
Existem um grupo de 3 estados de jogo que executam três cenas exclusivas que escondem a teleportação para a sala corrupta: Warmup, InFlight e Cooldown
Enquanto o streaming está sendo tratado, InFlight é executado, mantendo uma tela escura ligeiramente pulsante. Quando ambos Class.Player.Request
Um conjunto semelhante de cenas de aquecimento, início de voo e cooldown ocorre quando teletransportamos o jogador de volta para o estado normal da sala, TeleportWarmupBack, TeleportInFlightBack e TeleportCo
Estados de Jogo de Iluminação e Ambiente
Sabíamos que queríamos que o estado normal e corrupto de
Estados de Portas de Bloqueio
Gerenciador de eventos
EventManager nos permitiu executar "ações" ao longo do tempo usando keyframes, como:
- Interpolando propriedades e atributos da instância.
- Executando scripts.
- Tocando áudio.
- Executando vibrações de câmera.
Idealmente, usaríamos uma ferramenta com UI baseada em faixa, mas para esta demonstração, nós digitamos as chaves e nomes de propriedade manualmente. O sistema EventManager consiste em vários scripts e uma função de evento/fungção, incluindo:
- EventManager - Logic geral para a criação e o stop de eventos, bem como ações do lado do servidor.
- EventManagerClient - Ações do lado do cliente.
- EventManagerModule - Código comum para ambas as ações do lado do servidor e do cliente.
- EventManagerConfig - Pequeno arquivo com algumas declarações de comando.
- EventManagerDemo - Onde todos os eventos reais para esta demonstração são definidos no script específico do jogo.
- EventManagerEvent , EventManagerFunc - Função remota e vinculável para executar e parar eventos do cliente ou servidor. É assim que outros sistemas podem configurar, executar e parar eventos.
Cada evento tem um nome, uma seção com informações opcionais sobre o tempo de execução, a função de executar em um ou em terminar/parar/sair, parâmetros de evento, e seções com interpolantes (interpolando qualquer número de propriedades ou atributos ao longo do tempo), scripts ( executando scripts registrados em keyames ou câmeras, e toques de áudio.
Interpretação
A interpolação permite que propriedades e atributos de objetos sejam alterados sem problemas entre um valor e outro em vez de pular desordenadamente entre keyFRAMEs. Definimos interpolantes para alterar uma variedade de efeitos visuais; por exemplo, o seguinte código de exemplo mostra como alteramos a propriedade
interpolants = {objectParam = "TextLabel",property = "TextTransparency",keys = {{value = 1},{time = .5, value = 0},{time = 2.25, value = 0},{time = 3, value = 1}}}
Embora possamos definir qual propriedade ou atributo de objeto pertence a que tipo na seguinte amostra de código, queríamos ser capazes de reutilizar os mesmos eventos em diferentes "grupos de objetos" para permitir que funcione com streaming no cliente e com objetos criados em tempo de execução.
object = workspace.SomeFolder.SomeModel
Para alcançar isso, permitimos referência por nome de objeto e a passagem de parâmetros nomeados no iniciardo evento. Para encontrar objetos nomeados, permitimos especificar um "raíz" para o evento, que permite que os objetos sejam encontrados por nome sob essa raiz quando o evento começou. Por exemplo, no seguinte código de exemplo, o EventManager tenta encontrar um
params = {["RootObject"] = workspace.Content.Interior.Foyer["Ritual-DemoVersion"],},interpolants = {objectName = "Wander",attribute = "TimeScale",keys = {{value = 0.2}}}
Permitimos que você passe parâmetros em eventos na seção de parâmetros, e os scripts executando no início do evento podem alterar parâmetros existentes ou adicionar mais parâmetros na tabela "param". No exemplo a seguir, temos isEnabled parâmetro com
params = {isEnabled = false},interpolants = {{objectName = "FocuserGlow",property = "Enabled",keys = {{valueParam = "isEnabled"}}}
Os parâmetros nos permitem referir-nos a objetos que nem mesmo existem no começo da experiência. Por exemplo, no seguinte código de exemplo, uma função executando no início do evento criará um Objetoe definirá a entrada BlackScreenObject nos parâmetros para apontar para o Objetocriado.
{objectParam = "BlackScreenObject",property = "BackgroundTransparency",keys = {{value = 0},{time = 19, value = 0},{value = 1},}}
Executando Eventos, Instâncias de Evento e Conectando aos Gatilhos
Para executar um evento, usaríamos um evento remoto dos clientes ou uma função do servidor. No seguinte exemplo, passamos alguns parâmetros para o RootObject e isEnabled eventos. Internamente, uma instância da descrição do evento foi criada, os parâmetros foram resolvidos para objetos reais e a função retornou um ID para o instância do 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} )
Podemos parar de executar um evento chamando a função com "Parar":
eventManagerFunc:Invoke("Stop", {eventInstId = cooldownId} )
Interpolantes ou outras ações que são "cosméticos" (não alterar a simulação para todos os jogadores) podem ser executados em clientes, o que pode resultar em interpolação mais suave. Na descrição do evento, podemos fornecer um valor padrão para todas as ações como onServer = true (sem ele, o padrão é o cliente). Cada ação pode sobrescrevê-lo definindo seu próprio onServer.
Para conectar facilmente um evento a um gatilho, usamos funções de ajuda ConnectTriggerToEvent ou ConnectSpawnedTriggerToEvent, a última das quais encontram o gatilho por nome. Para permitir que o mesmo gatilho seja acionado usando diferentes gatilhos, podemos chamar eventManagerFunc
Parâmetros de Evento
Além de parâmetros de evento personalizados passados por scripts, outros dados que podem ser opcionalmente passados ao criar um evento incluem o jogador, o retorno de chamada (para ser chamado quando o evento terminar) e os parâmetros de retorno. Alguns eventos devem ser executados apenas para um jogador (eventos com ações em andamento no cliente), enquanto outros devem ser executados para todas / todos. Para fazer isso, usamos onlyTriggeredPlayer = true </
Os eventos podem ter cooldowns definidos por minCooldownTime e maxCooldownTime. O min e max fornecem uma faixa para escalonar com base no número de jogadores, mas não usamos isso neste exemplo. Se tivéssemos necessidades de coold
Chamando Scripts
Podemos chamar Scripts em keyboards específicos na seção Scripts . Por exemplo:
scripts = {{startTime = 2, scriptName = "EnablePlayerControls", params = {true}, onServer = false }}
No exemplo anterior, o habilitar controles do jogador Script precisaria ser registrado com o módulo de gerenciador de eventos, como:
emModule.RegisterFunction("EnablePlayerControls", EnablePlayerControls)
O script do cliente deve ser chamado para funções no cliente e no script do servidor para funções chamadas no cliente, e no script do servidor para onServer = true. O próprio script da função receberá o eventoInstance e os parâmetros passados, mas neste caso, apenas um parâmetro é passado com um valor verdadeiro.
local function EnablePlayerControls(eventInst, params)
Reproduzindo Áudio
Temos suporte limitado para jogar áudio não posicional em keyframes na seção Sons , por exemplo:
sounds = {{startTime = 2, name = "VisTech_ethereal_voices-001"},}
Nota que os retornos de chamada de evento são acionados quando a duração do evento expira, mas as ações de áudio ainda podem estar tocando depois.
Movimentos de Câmera
Podemos definir vibrações de câmera na seção câmera Shakes , como:
cameraShakes = {{startTime = 15, shake = "small", sustainDuration = 7, targets = emConfig.ShakeTargets.allPlayers, onServer = true},}
Os "alvos" só podem ser iniciados para o jogador que desencadeou o evento, todosJogador, ou playersInRadius para o jogador que desencadeou. Nós usamos um script de terceiros para caminhadas de câmera, e as caminhadas foram pré-definidas: eventManagerDemo.bigShake e eventManagerDemo.smallShake. sustainDuration também pode ser
Lógica de Missões
Existem 7 missões totais, e apenas 6 delas usam selos. A maioria das missões tem parâmetros comuns, embora alguns sejam apenas para missões com selos e teletransporte para corromper salas. Cada missão tem uma entrada no script DemoConfig com um conjunto de parâmetros no mapa Config.Missions :
- MissionRoot : Um pasta de todas as versões não corrompidas de objetos.
- Portas : Portas para trancar até que um jogador pegue um selo.
- SealName / SolvedSealName : Nome de selo não corrompido e nomes de selo corrompidos.
- SealPlaceName : Locais para colocar o selo.
- PlacedSealPlaceholderName : Objeto de espaço reservado no local para colocar o selo.
- TeleportPositionsName : Nome de um pasta com malhas de espaço reservadas para definir as posições e rotações do jogador ao se mover para a sala corrompida e de volta para a área normal. O mesmo nome é usado em ambos os casos.
- CorruptRoomName : Nomes dos pastas de raiz (relativos ao Serviço de Armazenamento) para as salas corrompidas. Salas corrompidas clonadas sob o TempStorage.Cloned quando a missão começa, e elas são destruídas quando a missão terminar.
- MissionCompleteButtonName : Um botão de cheat nas salas corrompidas para terminar a missão imediatamente. Isso é para fins de diagnóstico.
- CheatKey : O mesmo cheat que um número ou CtrlShift[Número].
Alguns da lógica da missão estão nos GameStateManager scripts, como selos e portas fornecem o fluxo de jogo principal para a maioria das missões, mas a maioria da lógica específica da missão está em MissionsLogic e MissionsLogicClient scripts que definem vários tipos de missões. O tipo é definido ap
- Use uma chave em uma fechadura - A primeira missão para abrir uma porta. Este tipo é definido por LockName , KeyName .
- Corresponder a itens - 4 missões correm com itens. Este tipo é definido por MatchItems .
- Vestindo um manequim usando roupa de camadas - 1 missão no sótão tem os jogadores coletar três itens. Este tipo é definido por DressItemsTagList.
- Clique no item para terminar. - 1 missão tem este digitar, que é definido por ClickTargetName.
Cada tipo de missão tem sua própria StartMissionFunc e CompleteMissionFunc. A função de início geralmente lê parâmetros do mapa
A lógica de correspondência de itens permite que você “use” (clique enquanto segura) itens marcados com PuzzlePieceXX tags sobre itens com PuzzleSlotXX tags. Existem algumas opções disponíveis como parâmetros em MatchItems mapa (se as peças prec
Pegando
Desenvolvemos um simples sistema de agarramento para segurar um objeto ao anexar o objeto ao braço direito do personagem. O agarramento é implementado em
Em cada quadro, verificamos se uma tentativa de agarre está em andamento. Se o jogador estiver dentro de reachDist , começamos a jogar ToolHoldAnim . Quando um jogador estiver dentro de maxGrabDistance , o cliente executa uma solicitação ao servidor para agarrar um modelo (1> playerGrab1> função).
O script do lado do servidor tem 2 funções principais:
- Pega - Aceita o pedido de um cliente para pegar um modelo.
- Liberar modelo
A informação sobre o que cada jogador possui é mantida no mapa playerInfos . Na função de agarre, verificamos se este modelo já foi agarrado por outro jogador. Se sim - um "EquipWorldFail" é enviado ao cliente e cancela a tentarde agarre. Nota que precisávamos lidar com situações onde os jogadores agarram diferentes partes do mesmo Model e cancelam a agarre neste caso.
Se a agilidade for permitida, o script cria dois Attachments, um à mão direita e outro à mão esquerda, usando um ponto de agarre
Para liberar um modelo prendado, o script do cliente se conecta ao botão GrabReleaseButton no HUD ScreenGUI. Uma função Connected faz um evento para o servidor. No servidor, liberar exclui o Attachments e 2> Class.Limit|Restrições