Grundlegende Spielsysteme

*Dieser Inhalt wurde mit KI (Beta) übersetzt und kann Fehler enthalten. Um diese Seite auf Englisch zu sehen, klicke hier.

Die folgenden Systeme waren die Grundlage für die Einrichtung des Gameplays, das wir für Das Geheimnis von Duvall Drive wollten.

Spielstatusmanager

Der GameStateManager / GameStateClient ist wahrscheinlich das komplizierteste System im Erlebnis, da es sich mit:

  • Spieler in der Lobby starten, Countdown zum Teleportieren der Gruppe in den Hauptspielbereich starten und Spieler auf reservierte Server teleportieren.
  • Klone beschädigte Räume, asynpstreamen sie und teleportiere Spieler zu und von einer bestimmten zugewiesenen CFrame Koordinate.
  • Schnappen und Platzieren von Seal-Mechanismen.
  • Verriegelung und Entriegelung von Türen.
  • Initialisierung des letzten Teleports zum Foyer und Wiedergabe der Abschluss-Cutscene.

Wir haben es als einfache Zustandsmaschine implementiert (Update-Funktion) und die Zustände sind in DemoConfig (GameStates-Enum).Einige Staaten beschäftigen sich mit der ersten Lobby/reservierten teleportieren, während andere sich mit der Suche nach einer Mission, dem Auslösen des Rätsels und der Lösung der Mission beschäftigen.Beachten Sie, dass wir neben Seelen versuchten, keinen missionsspezifischen Code im GameStateManager zu haben.

GameStates ist größtenteils serverseitig, aber wenn der Client etwas tun muss, wie Countdown anzeigen, Lore anzeigen oder die Streaming-Pause-Benutzeroberfläche deaktivieren, kommunizieren Server und Client (GameStatesClient) über ein Remote-Ereignis namens GameStateEvent.Wie bei den meisten Fällen hat der Event-Payload das Ereignis "eingeben" (Config.GameEvents) als ersten Parameter und danach Ereignis spezifische Daten.

Teleportationsspielzustände

Es gibt eine Gruppe von 3 Spielzuständen, die drei einzigartige Cutscenes ausführen, die die Teleportation in den korrupten Raum verbergen: Aufwärmen, ImFlight und Abklingzeit. Aufwärmen läuft für die gesamte Dauer und endet mit einem fast schwarzen Bildschirm, auf dem die 3D-Welt nicht mehr sichtbar ist.Während dieser Zeit klonen wir den Raum, holen die gewünschten Spielerpositionen im beschädigten Raum für jeden Spieler:in, rufen Player.RequestStreamAroundAsync an und transportieren Spieler in eine bestimmte zugewiesene CFrame Koordinate innerhalb des beschädigten Raums.Diese Art der Teleportation kann eine Streaming-Pause auslösen.Wenn eine Streaming-Pause auftritt, zeigt der Client eine Nachrichtan.Wir haben diese Standard-Benutzeroberfläche deaktiviert, um das Erlebnis immersiv zu halten.

Während des Streamings wird InFlight ausgeführt, wobei ein leicht pulsierender dunkler Bildschirm gehalten wird.Wenn beide Player.RequestStreamAroundAsync zurückgebenund Clients den Server informieren, dass die Streaming-Pause ausgeschaltet ist und jeder Spieler nahe genug an den gewünschten Ort ist, stornieren wir die InFlight-Cutscene und starten die Abklingzeit-Cutscene.Das Ziel von Abklingzeit ist es, die 3D-Welt wieder sichtbar zu machen, indem der dunkle Bildschirm fließend entfernt wird.Wenn Player.RequestStreamAroundAsync zu lange zum Rückruf dauert oder der Client nicht berichtet, dass die Streaming-Pause ausgeschaltet ist, gehen wir immer noch zur Abklingzeit-Szene nach einem Timeout von mehreren Sekunden vor.

Eine ähnliche Reihe von Aufwärm-, InFlight- und Abklingzeit-Szenen tritt auf, wenn wir den Spieler zurück in den normalen Zustand des Raums teleportieren, TeleportWarmupBack , TeleportInFlightBack und TeleportCooldownBack , und am Ende der Erlebnisführen wir auch TeleportWarmupFinal , TeleportInFlightFinal und TeleportCooldownFinal aus, um Spieler in den Foyer für die abschließende Szenen zu teleportieren.

Beleuchtungs- und Atmosphäre-Spielzustände

Wir wussten, dass wir wollten, dass der normale und korrupte Zustand eines Raums ein anderes visuelles Aussehen hat, damit Spieler ein klares visuelles Feedback erhalten können, dass sie an einem völlig anderen Ort sind.Spielzustände ermöglichten es uns, die Beleuchtungs- und Atmosphäreneigenschaften für normale und korrupte Räume zu ändern, in denen der GameStateManager ausgewählt hat, welche Instanzen verwendet werden sollen, basierend darauf, ob Spieler vom normalen in den korrupten Zustand des Raums teleportieren ( TeleportWarmup ), oder umgekehrt ( TeleportWarmupBack ).Veranstaltungen, die während des Teleports abgespielt werden, machen den gesamten Bildschirm entweder dunkel oder weiß, also beschlossen wir, die Lighting und Atmosphere Instanzen in jenen Momenten zu ändern, um den Prozess vor den Spielern zu verbergen.Um es einfach zu ändern, enthält DemoConfig Karten, die definieren, welche Instanzen unter diesen Diensten geändert werden müssen.

Türsperr-Spielzustände sperren

Wir wollten in der Lage sein, Spieler in bestimmten Räumen zu halten, während sie Missionen abschließen, also haben wir Spielzustände erstellt, um Türen zu verschließen: InMission und CanGetSeal.InMission sperrt Spieler in ihren aktiven Missionsraum und CanGetSeal hält die Tür des Missionsraums geschlossen, bis sie das "wiederhergestellte" Siegel abholen.Wir verwendeten das hauptsächlich, damit die Türen sich verschließen, wenn Spieler von einer Mission zurückkehren, damit sie einen Anreiz haben, das Siegel aufzuheben.Nachdem sie das Siegel abgeholt haben, öffnen sich die Türen, damit sie es innerhalb des Standorts des Siegels im Foyer platzieren können.Die letzte Mission ist einzigartig für diesen typischen Prozess, da die Tür zum Raum mit seinem Siegel verschlossen ist, bis die Spieler jedes andere Rätsel lösen ( EnableRegularMissionDoors , EnableOneMissionDoors Funktionen).

Veranstaltungsmanager

EventManager ermöglichte es uns, "Aktionen" im Laufe der Zeit durch die Verwendung von Schlüsselframes auszuführen, wie:

  • Interpolieren von Instanz-Eigenschaften und Attributen.
  • Ausführen von Skripten.
  • Spielt Audiodateienab.
  • Laufende Kamerabewegungen.

Wir würden idealerweise ein Werkzeug mit einer trackbasierten Benutzeroberfläche verwenden, aber für diese Demo haben wir die Tasten und Eigennamen manuell eingegeben.Das EventManager -System besteht aus mehreren Skripten und einem Ereignis/Funktion, einschließlich:

  • EventManager - Gesamtlógik für die Erstellung und Unterbrechung von Ereignissen sowie Server-seitige Aktionen.
  • EventManagerClient - Client-seitige Aktionen.
  • EventManagerModule - Gemeinsamer Code für Aktionen auf Server- und Clientseite.
  • EventManagerConfig - Kleine Datei mit einigen Befehlserklärungen.
  • EventManagerDemo - Wo alle tatsächlichen Ereignisse für diese Demo im Spielspezifischen Skript, das. PL: die Skriptsdefiniert sind.
  • EventManagerEvent , EventManagerFunc - Remote-Ereignis und bindbare Funktion, um Ereignisse vom Client oder Server auszuführen und zu stoppen.So können andere Systeme Ereignisse einrichten, ausführen und stoppen.

Jedes Ereignis hat einen Namen, einen Abschnitt mit optionalen Informationen über die Abklingzeit, eine Funktion, die zum Start oder zum beendenausgeführt wird, Ereignisparameter und Abschnitte mit Interpolatoren (Interpolieren einer beliebigen Anzahl von Eigenschaften oder Attributen im Laufe der Zeit), Skripten (Ausführung von registrierten Skripten bei Schlüsselframes), Kamerabewegungen und Wiedergabe von Audiodateien.

Interpolierung

Interpolation ermöglicht es, Objekt-Eigenschaften und Attribute nahtlos von einem Wert auf einen anderen wechseln, anstatt sich unabhängig voneinander zwischen Schlüsselframes zu bewegen.Wir haben Interpolierer definiert, um eine Vielzahl von visuellen Effekten zu ändern; zum Beispiel zeigt das folgende Code-Snippet, wie wir die TextLabel.TextTransparency Eigenschaft auf einem Objekt, das durch den TextLabel Parameter definiert wurde, von einem Wert von 1 am Anfang auf 0 interpoliert haben, dann später wieder auf 1 zurück:


interpolants = {
objectParam = "TextLabel",
property = "TextTransparency",
keys = {
{value = 1},
{time = .5, value = 0},
{time = 2.25, value = 0},
{time = 3, value = 1}
}
}

Während wir definieren konnten, welches Objekt-Eigenschaft oder Attribut zu welchem wie im folgenden Codebeispiel gehört, wollten wir in der Lage sein, die gleichen Ereignisse auf verschiedenen "Gruppen von Objekten" erneut zu verwenden, um es mit Streaming auf dem Client und mit Objekten, die zur Laufzeit erstellt wurden, zu ermöglichen.


object = workspace.SomeFolder.SomeModel

Um dies zu erreichen, erlaubten wir die Referenzierung durch Objektname und das Übergeben benannter Parameter am starten.Um benannte Objekte zu finden, erlaubten wir die Angabe einer "Stamm" für das Ereignis, die es Objekten ermöglicht, unter dieser Wurzel nach Namen gefunden zu werden, wenn das Ereignis begann.Zum Beispiel versucht der folgende Code-Snippet, EventManager irgendwo unter Workspace.Content.Interior.Foyer["Ritual-DemoVersion"] ein Objekt mit dem Namen "Wander" zu finden.


params = {
["RootObject"] = workspace.Content.Interior.Foyer["Ritual-DemoVersion"],
},
interpolants = {
objectName = "Wander",
attribute = "TimeScale",
keys = {
{value = 0.2}
}
}

Wir haben die Übermittlung von Parametern in Ereignisse im Abschnitt Params erlaubt, und die Skripte, die beim Ereignisstart ausgeführt wurden, konnten entweder vorhandene Parameter ändern oder mehr Parameter in die "Param"-Tabelle hinzufügen.Im folgenden Beispiel haben wir den isEnabled Parameter mit einem Standardwert von falsch und die Eigenschaft "Aktiviert" auf einem Objekt mit dem Namen FocuserGlow wird auf den Wert von isEnabled gesetzt.Ein Skript, das beim Start eines Ereignisses ausgeführt wird oder ein Skript, das das Ereignis aufruft, kann isEnabled festlegen, sodass wir die gleiche Ereignisbeschreibung sowohl zum Aktivieren als auch zum Deaktivieren von FocuserGlow verwenden könnten.


params = {
isEnabled = false
},
interpolants = {
{
objectName = "FocuserGlow",
property = "Enabled",
keys = {
{valueParam = "isEnabled"}
}
}

Parameter ermöglichten es uns, auf Objekte zu verweisen, die am Beginn der Erlebnisnicht einmal existieren.Zum Beispiel erstellt in dem folgenden Code-Beispiel eine Funktion, die beim Eventstart ausgeführt wird, ein Objekt und legt den BlackScreenObject-Eintrag in den Parametern auf das erstellte Objekt fest.


{objectParam = "BlackScreenObject",
property = "BackgroundTransparency",
keys = {
{value = 0},
{time = 19, value = 0},
{value = 1},
}}

Führe Ereignisse, Ereignisinstanzen aus und verbinde dich mit Auslösern

Um ein Ereignis auszuführen, würden wir entweder ein Remote-Ereignis von Clients verwenden oder eine Funktion vom Server.Im folgenden Beispiel haben wir ein paar Parameter an die RootObject- und isEnabled-Ereignisse übergeben.Interne wurde eine Instanz der Eventbeschreibung erstellt, die Parameter auf reale Objekte umgelöst wurden, und die Funktion eine ID für die Eventinstanz zurückgab.


local params = {
RootObject = workspace.Content.Interior.Foyer["Ritual-DemoVersion"]["SealDropoff_" .. missionName],
isEnabled = enabled
}
local eventId = eventManagerFunc:Invoke("Run", {eventName = "Ritual_Init_Dropoff", eventParams = params} )

Wir könnten aufhören, ein Ereignis auszuführen, indem wir die Funktion mit "Stop" aufrufen:


eventManagerFunc:Invoke("Stop", {eventInstId = cooldownId} )

Interpolierer oder andere Aktionen, die "kosmetisch" sind (ändere die Simulation nicht für alle Spieler), könnten auf Clients ausgeführt werden, was zu einer glatteren Interpolation führen kann.In der Beschreibungkönnten wir einen Standardwert für alle Aktionen bereitstellen, wie aufServer = true (ohne ihn ist der Standard der Client).Jede Aktion kann es überschreiben, indem sie ihr eigenes onServer festlegt.

Um ein laufendes Ereignis leicht mit einem Trigger zu verbinden, verwendeten wir Hilfsfunktionen ConnectTriggerToEvent oder ConnectSpawnedTriggerToEvent, von denen die letztere den Trigger nach Namen findet.Um das gleiche Ereignis mit verschiedenen Auslösern auslösen zu können, könnten wir eventManagerFunc mit einem "Einstellung"-Schlüssel und einer Reihe von Auslöser-Volumen aufrufen.Für ein Beispiel eines Trigger-Volumens in Actionsiehe Erweiterte Pantry erweitern.

Ereignisparameter

Zusätzlich zu benutzerdefinierten Ereignisparametern, die von Skripten übergeben werden, enthält ein anderes Daten, die optional beim Erstellen eines Ereignisses übermittelt werden können, Spieler:in, Rückruf (zum Anrufen, wenn das Ereignis endet) und Rückrufsparameter.Einige Ereignisse sollten nur für einen Spieler laufen (Ereignisse mit Aktionen, die auf dem Client ausgeführt werden), während andere für Allelaufen sollten.Um es nur für einen Spieler:inlaufen zu lassen, verwendeten wir onlyTriggeredPlayer = true in den Parametern.

Ereignisse können Abklingzeiten haben, die von minCooldownTime und maxCooldownTime definiert wurden.Die Minimal- und Maximalwerte bieten einen Bereich für die Skalierung basierend auf der Anzahl der Spieler, aber wir haben ihn in dieser Demo nicht verwendet.Wenn wir Abklingzeiten pro Spieler:inbenötigen würden, hätten wir die Möglichkeit, perPlayerCooldown = true zu verwenden.Jedes Ereignis hat eine Dauer in Sekunden, und Abklingzeiten und Rückrufe basieren darauf.Um über das Beenden eines Events zu informieren, könnte das Auslösen von Code einen Rückruf und die darin übermittelten Parameter übermitteln.

Ausführungsskripte aufrufen

Wir könnten Scripts in bestimmten Schlüsselframes im Skript-Abschnitt aufrufen. Zum Beispiel:


scripts = {
{startTime = 2, scriptName = "EnablePlayerControls", params = {true}, onServer = false }
}

Im vorherigen Beispiel müssten die EnablePlayerControls Script mit dem Event-Manager-Modul registriert werden, wie folgt:


emModule.RegisterFunction("EnablePlayerControls", EnablePlayerControls)

RegisterFunction muss im Client-Skript für Funktionen aufgerufen werden, die auf dem Client aufgerufen werden, und im Server-Skript für onServer = true .Die Funktion selbst wird EventInstance und Parameter übergeben, aber in diesem Fall wird nur ein Parameter mit einem echten Wert übergeben.


local function EnablePlayerControls(eventInst, params)

Spiele Audiodateienab

Wir haben eine begrenzte Unterstützung für das Spielen von nicht positionalem Audio an Schlüsselframes im Sounds Abschnitt, zum Beispiel:


sounds = {
{startTime = 2, name = "VisTech_ethereal_voices-001"},
}

Beachten Sie, dass die Event-Beendungs-Callbacks feuern, wenn die Eventdauer abläuft, aber Audio-Aktionen möglicherweise noch danach abgespielt werden.

Kamerabewegungen ausführen

Wir könnten Kamerabewegungen in der Kamerabewegungen Sektion definieren, wie folgt:


cameraShakes = {
{startTime = 15, shake = "small", sustainDuration = 7, targets = emConfig.ShakeTargets.allPlayers, onServer = true},
}

Ziele können nur für den Spieler initiiert werden, der das Ereignis ausgelöst hat, allPlayer oder SpielerInRadius zum auslösenden Spieler:in.Wir verwendeten ein Drittskript für Kamerabewegungen und die Bewegungen wurden vordefiniert: eventManagerDemo.bigShake und eventManagerDemo.smallShake.sustainDuration könnte auch übergeben werden.

Missionenlogik

Es gibt insgesamt 7 Missionen, und nur 6 von ihnen verwenden Seelen.Die meisten Missionen haben gemeinsame Parameter, obwohl einige nur für Missionen mit Seelen und Teleportation in korrupte Räume sind.Jede Mission hat einen Eintrag im DemoConfig -Skript mit einer Reihe von Parametern in der Config.Missions -Karte:

  • MissionRoot : Ein Ordner aller nicht korrupten Versionen von Objekten.
  • Türen : Türen, die verschlossen bleiben, bis ein Spieler eine Dichtung aufhebt.
  • SealName / Lösbare SealName : Nicht korrupte Seal und korrupte Seelnamen.
  • SealPlaceName : Orte, an denen das Siegel platziert werden kann.
  • PlatziertSealPlaceholderName : Platzhalter-Objekt an der Stelle, an der das Siegel platziert werden soll.
  • TeleportPositionsName : Name eines Ordners mit Platzhalter-Meshs, um Spielerteleportpositionen und - rotationen zu definieren, wenn du in den beschädigten Raum bewegst und zurück in den normalen Bereich.Der gleiche Name wird in beiden Fällen verwendet.
  • CorruptRoomName : Namen der Wurfänder (im Vergleich zu ServerStorage) für die beschädigten Räume.Verdorbenen Räume klonen unter TempStorage.Cloned, wenn die Mission beginnt, und sie werden zerstört, wenn die Mission abgeschlossen ist.
  • MissionCompleteButtonName : Ein Schummeln-Button in den korrupten Räumen, um die Mission sofort abzuschließen. Dies ist für Debugging-Zwecke .
  • CheatKey : Der gleiche Cheat wie eine Zahl oder CtrlShift[Number] .

Ein Teil der Missionslogik befindet sich in den GameStateManager -Skripten, da Seelen und Türen den Hauptfluss des Spiels für die meisten Missionen bereitstellen, aber der größte Teil der missionsspezifischen Logik sich in den MissionsLogic - und MissionsLogicClient -Skripten befindet, die mehrere "Typen" von Missionen definieren.Der Typ wird nur durch die Anwesenheit von speziell benannten Mitgliedern in der Beschreibungdefiniert.Es gibt einige Arten von Missionen:

  • Verwende einen Schlüssel auf einem Schloss - Die erste Mission, eine Tür zu öffnen. Dieser Typ wird durch LockName , KeyName definiert.
  • Passende Gegenstände - 4 Missions-Match-Gegenstände. Dieser Typ wird durch MatchItems.
  • Verkleiden eines Mannequins mit mehreren Schichtenstoff - 1 Mission im Dachgeschoss hat Spieler, die drei Artikel sammeln. Dieser Typ wird durch DressItemsTagList definiert.
  • Klicken Sie auf Element, um zu beenden - 1 Mission hat diese eingeben, die durch ClickTargetName.

Jeder Missionstyp hat seine eigene StartMissionFunc und CompleteMissionFunc.Die Startfunktion liest normalerweise die Parameter von der MatchItem Karte, löst Namen in Objekte um und konfiguriert alle Klickdetektoren oder UI-Elemente.Fast die gesamte Logik befindet sich auf einem Server, aber MissionsLogicClient bietet eine Benutzeroberfläche zum Anzeigen der Artikelanzahl, die in vielen Missionen verwendet wird. MissionLogicEvent Das Remote-Ereignis wird für Server-Client-Kommunikation verwendet, mit kleinen MissionEvents, die Arten von Befehlen definieren, die übermittelt werden.Das MiscGameLogic Skript bindet einige Trigger an Ereignisse und entfernt Fehlerobjekte in der Veröffentlichungsversion.

Die Logik der Übereinstimmung von Elementen erlaubt es, Elemente mit PuzzlePieceXX -Tags über Elemente mit PuzzleSlotYY -Tag "zu verwenden".Es gibt einige Optionen, die als Parameter in der MatchItems -Karte verfügbar sind (wenn Stücke in der Reihenfolge angewendet werden müssen, wenn nur eines von jedem erforderlich ist).Wir könnten Namen für einfache Audio- und VideofX spezifizieren.Wenn Stücke an bestimmten Orten platziert werden müssen, bietet eine zusätzliche "Platzierung"-Karte die Kartierung von Stück-Tags zu Platzhalterteilen, die Transformationen definieren.

Greifen

Wir haben ein einfaches Greifsystem entwickelt, um ein Objekt zu halten, indem wir das Objekt dem rechten Arm des Charakters anfügen.Grabbing wird in GrabServer2 und GrabClient Skripts implementiert.Es beginnt in ProcessClick, das einen Strahl durch den angeklickten/berührten Punkt feuert.Es prüft dann, ob wir ein Mesh treffen, das greifbar ist, und der Treffer sich innerhalb der maxMovingDist befindet, wo wir mit der Interaktion beginnen können.Wenn das Modell angeklickt hat Attachments aufgerufen wurde GrabHint , wählen wir das nächste zum angeklickten Ort.Wir erinnern uns an das geschnappte Teil, das zugehörige Modell und entweder den nächsten GrabHint oder die angeklickte Position in der grabAttempt Struktur.Wenn die Entfernung mehr als maxGrabDist beträgt, muss der Spieler zuerst so nah wie möglich am versuchten Greifpunkt laufen, also rufen wir Humanoid.MoveTo an.

Auf jedem Frame prüfen wir, ob ein Greifversuch im Gange ist.Wenn der Spieler innerhalb von reachDist ist, starten wir mit dem Spielen von ToolHoldAnim .Wenn ein Spieler innerhalb von maxGrabDist ist, sendet der Client eine Anfrage an den Server, um tatsächlich ein Modell zu greifen (performGrab Funktion).

Die Serverseiten-Skript hat 2 Hauptfunktionen:

  • Greifen - Bearbeitet eine Clientanforderung, um ein Modell zu greifen.
  • Freigabe - Bearbeitet die Anfrage, ein geschnapptes Modell freizugeben.

Informationen darüber, was jeder Spieler besitzt, werden in der Spielerinfos -Karte gespeichert.In der Funktion "Schnappen" prüfen wir, ob dieses Modell bereits von einem anderen Spieler:ingeschlagen wurde.Wenn ja - wird ein "EquipWorldFail" an den Client gesendet, und er kann den Grab-Versuch abbrechen.Beachten Sie, dass wir Situationen handhaben mussten, in denen Spieler verschiedene Teile desselben Model greifen und in diesem Fall den Griff abbrechen mussten.

Wenn das Greifen erlaubt ist, erstellt das Skript zwei Attachments, eine auf der rechten Seite und eine auf dem Objekt, indem es einen übergebenen Greifpunkt vom Client verwendet.Dann erstellt es eine zwischen den beiden >.Constraints und Attachments werden unter dem Ordner CurrentGrips des Spielers unter dem Charakter des Spieler:ingespeichert.Grabbing spielt auch einen Ton ab, deaktiviert Kollisionen am angegriffenen Objekt und kümmert sich um Wiederherstellbare, wenn nötig.

Für die Freigabe eines ergriffenen Modells verbindet sich das Client-Skript mit der GrabReleaseButton Schaltfläche in HUD ScreenGui.Eine Connected Funktion feuert ein Ereignis an den Server ab.Auf dem Server werden Löschungen von Attachments und Constraints freigegeben, die Kollision wiederherstellen, mit allen anwendbaren Restaurierbaren umgehen und Daten für diesen Client in playerInfos löschen.