Implementacja zachowania blastera to proces programowania mechaniki wybuchu w grach typu first-person shooter. Chociaż gracze mogą wystrzelić blastera za pomocą jednego kliknięcia lub naciśnięcia przycisku, stworzenie satysfakcjonującego i dokładnego zachowania wybuchu jest ważne, ponieważ zwiększa przyjemność graczy z ogólnej rozgrywki.
Korzystając z przykładowej gry w laserowy tag jako odniesienia, ta sekcja samouczka nauczy cię o skryptach odpowiedzialnych za implementację zachowania blastera dla dwóch różnych typów blasterów, w tym wskazówki dotyczące:
- Wykrywania, kiedy gracze naciskają przycisk wybuchu.
- Sprawdzania, czy gracz może użyć swojego blastera, jeśli niedawno nacisnął przycisk wybuchu.
- Generowania danych wybuchu, które informują serwer, kto zainicjował wybuch, skąd pochodził i jakie było ostateczne miejsce każdego promienia laserowego.
- Powiadamiania serwera o danych wybuchu, aby mógł podjąć odpowiednie działania, jeśli wybuch zderzył się z innym graczem.
- Resetowania blastera między każdym wybuchem, aby dać mu wystarczająco dużo czasu na schłodzenie przed ponownym wystrzałem.
Po ukończeniu tej sekcji dowiesz się o skryptach, które pozwalają blasterowi wykrywać, kiedy jego wybuchy zderzają się z innymi graczami, a następnie odejmować odpowiednią ilość zdrowia w zależności od typu blastera.
Wykrywanie wejścia gracza
Pierwszym krokiem do implementacji zachowania blastera jest nasłuchiwanie, kiedy gracz naciska przycisk wybuchu. Typ wejścia, którego gracze używają do naciśnięcia przycisku wybuchu, zależy od urządzenia, z którego korzystają, aby uzyskać dostęp do gry. Na przykład, przykładowa gra w laserowy tag obsługuje sterowanie myszą i klawiaturą, kontrolery gier oraz sterowanie dotykowe. Możesz zobaczyć każdy z tych typów wejścia w ReplicatedStorage ⟩ UserInputHandler.
Ten skrypt kliencki używa ContextActionService, aby powiązać MouseButton1 i ButtonR2 z akcją wybuchu. Oznacza to, że za każdym razem, gdy gracz naciśnie lewy przycisk myszy lub przycisk R2 kontrolera, wyzwala to wystrzał promienia laserowego z blastera. Zauważ, że HUDGui zawiera przycisk do wybuchu na urządzeniach mobilnych, który jest później połączony w skrypcie.
ContextActionService:BindAction("_", onBlasterActivated, false,
Enum.UserInputType.MouseButton1,
Enum.KeyCode.ButtonR2
)Inna ważna uwaga dotyczy użycia Enum.UserInputState.Begin w definicji onBlasterActivated(). Wiele interakcji z interfejsem użytkownika, takich jak wybór blastera w tym przykładzie, nie występuje, dopóki przycisk myszy nie zostanie zwolniony (Enum.UserInputState.End), co daje użytkownikom ostatnią szansę na uniknięcie interakcji. Jednak mechanika wybuchu nie wydaje się responsywna, chyba że występuje natychmiast po naciśnięciu przycisku.
Aby to zobrazować, możesz zmienić Enum.UserInputState.Begin na Enum.UserInputState.End, a następnie przetestować grę, aby zobaczyć, jak responsywność wybuchu wpływa na rozgrywkę. Na przykład, jeśli gracze mogą przytrzymać przycisk bez wyzwalania wybuchu, jak może to zmienić ich doświadczenie podczas oznaczania innych graczy?
local function onBlasterActivated(_actionName: string,
inputState: Enum.UserInputState, _inputObject: InputObject)
if inputState == Enum.UserInputState.End then -- zaktualizowana linia, pamiętaj, aby zmienić z powrotem
attemptBlastClient()
end
endSprawdzenie, czy gracz może wystrzelić
Po tym, jak UserInputHandler wykryje naciśnięcie przycisku lub stuknięcie w ekran, wywołuje ReplicatedStorage ⟩ Blaster ⟩ attemptBlastClient, aby sprawdzić, czy gracz może wystrzelić, czy nie. Jak w większości sprawdzeń w przykładowej grze w laserowy tag, odbywa się to dwukrotnie: najpierw po stronie klienta, a następnie później po stronie serwera. attemptBlastClient następnie wywołuje ReplicatedStorage ⟩ Blaster ⟩ canLocalPlayerBlast, aby przeprowadzić proste sprawdzenie atrybutu gracza blasterStateClient:
local function canLocalPlayerBlast(): boolean
return localPlayer:GetAttribute(PlayerAttribute.blasterStateClient) == BlasterState.Ready
endJeśli zbadujesz ReplicatedStorage ⟩ Blaster ⟩ BlasterState, zobaczysz, że gra ma trzy stany blastera: Ready, Blasting i Disabled. Aby zobaczyć efekt każdego z tych stanów, możesz przetestować grę, wybrać swojego gracza w usłudze Players, a następnie obserwować atrybut blasterStateClient w oknie Properties. Zauważ, jak wyświetla Disabled, gdy wybierasz swój blaster, Ready przez większość czasu i Blasting przez mniej niż sekundę po naciśnięciu przycisku.
Ta niewielka przerwa uniemożliwia ci wystrzelenie tak szybko, jak możesz kliknąć. Na przykład, jeśli zmienisz funkcję, aby zawsze zwracała true, możesz szybko wystrzelić swój blaster bez żadnego opóźnienia, co jest nierealistyczne w kontekście rozgrywki w laserowy tag.
local function canLocalPlayerBlast(): boolean
return true -- zaktualizowana linia, pamiętaj, aby zmienić z powrotem
endGenerowanie danych wybuchu
Po potwierdzeniu, że blaster gracza jest w stanie Ready, attemptBlastClient wywołuje ReplicatedStorage ⟩ attemptBlastClient ⟩ blastClient. Pierwszym krokiem, który podejmuje blastClient, jest ustawienie atrybutu gracza blasterStateClient na Blasting, co zapobiega wcześniejszemu szybkiemu wystrzałowi.
Następnym krokiem jest wygenerowanie danych wybuchu. Jeśli przejrzysz ReplicatedStorage ⟩ Blaster ⟩ BlastData, zobaczysz, że każdy wybuch składa się z trzech elementów informacji:
- Gracz, który inicjuje wybuch.
- DataType.CFrame, który reprezentuje punkt początkowy wybuchu.
- Tabela RayResult, która zawiera ostateczne miejsce każdego promienia laserowego oraz trafionego gracza, jeśli trafił innego gracza.
Aby wygenerować te dane, blastClient wywołuje ReplicatedStorage ⟩ attemptBlastClient ⟩ blastClient ⟩ generateBlastData, którą możesz przejrzeć poniżej.
local function generateBlastData(): BlastData.Type
local blasterConfig = getBlasterConfig()
local rayDirections = getDirectionsForBlast(
currentCamera.CFrame, blasterConfig)
local rayResults = castLaserRay(
localPlayer, currentCamera.CFrame.Position, rayDirections)
local blastData: BlastData.Type = {
player = localPlayer,
originCFrame = currentCamera.CFrame,
rayResults = rayResults,
}
return blastData
endFunkcja ta zaczyna od użycia getBlasterConfig, aby pobrać typ blastera gracza. Przykład dostarcza dwa typy blasterów: jeden, który produkuje kilka promieni z szerokim, poziomym rozprzestrzenieniem, oraz drugi, który produkuje pojedynczy promień. Możesz znaleźć ich konfiguracje w ReplicatedStorage ⟩ Instances ⟩ LaserBlastersFolder.
Funkcja następnie używa currentCamera.CFrame jako punktu początkowego dla wybuchu, przekazując go do getDirectionsForBlast. W tym momencie kod nie dotyczy już blastera, lecz promienia laserowego, o którym dowiesz się więcej w sekcji wykrywanie trafień samouczka. Na koniec, po utworzeniu tabeli rayResults, generateBlastData ma wszystkie informacje, których potrzebuje, aby zwrócić dane wybuchu do blastClient.
Powiadomienie serwera
Gdy blastClient ma pełne dane dotyczące wybuchu, wyzwala dwa zdarzenia:
local laserBlastedBindableEvent = ReplicatedStorage.Instances.LaserBlastedBindableEvent
local laserBlastedEvent = ReplicatedStorage.Instances.LaserBlastedEvent
laserBlastedBindableEvent:Fire(blastData)
laserBlastedEvent:FireServer(blastData)BindableEvent powiadamia inne skrypty klienckie o wybuchu. Na przykład, ReplicatedStorage ⟩ FirstPersonBlasterVisuals używa tego zdarzenia, aby wiedzieć, kiedy wyświetlić efekty wizualne, takie jak animacja wybuchu i pasek chłodzenia. Podobnie, RemoteEvent powiadamia skrypty serwera o wybuchu, co rozpoczyna przetwarzanie wybuchu w ServerScriptService ⟩ LaserBlastHandler.
local function onLaserBlastedEvent(playerBlasted: Player, blastData: BlastData.Type)
local validatedBlastData = getValidatedBlastData(playerBlasted, blastData)
if not validatedBlastData then
return
end
if not canPlayerBlast(playerBlasted) then
return
end
blastServer(playerBlasted)
processTaggedPlayers(playerBlasted, blastData)
for _, replicateToPlayer in Players:GetPlayers() do
if playerBlasted == replicateToPlayer then
continue
end
replicateBlastEvent:FireClient(replicateToPlayer, playerBlasted, blastData)
end
endAby pomóc zapobiec oszustwom, serwer musi zweryfikować wszystkie dane, które każdy klient wysyła. Te kontrole obejmują:
- Czy BlastData to tabela? Czy zawiera Class.CFrame i inną tabelę o nazwie rayResults?
- Czy gracz ma wyposażony blaster?
- Czy gracz ma postać i lokalizację w świecie?
- Po wysłaniu danych wybuchu, czy gracz przemieścił się na nadmierną odległość od miejsca, w którym wystrzelił promień laserowy?
Ta ostatnia kontrola wymaga oceny, a w zależności od opóźnienia serwera i prędkości ruchu gracza, możesz zdecydować, że różne wartości są nadmierne dla twojej gry. Aby zobrazować, jak dokonać tej oceny, możesz uzyskać poczucie typowej wielkości zmiany pozycji, dodając instrukcję print w getValidatedBlastData i testując grę.
local distanceFromCharacterToOrigin = blastData.originCFrame.Position - rootPartCFrame.Position
print(distanceFromCharacterToOrigin.Magnitude) -- zaktualizowana linia, pamiętaj, aby usunąć
if distanceFromCharacterToOrigin.Magnitude > ToleranceValues.DISTANCE_SANITY_CHECK_TOLERANCE_STUDS then
warn(`Gracz {player.Name} nie przeszedł kontroli poprawności pochodzenia podczas wybuchu`)
return
endGdy się poruszasz i wybuchasz, zwróć uwagę na wynik. Może wyglądać to mniej więcej tak:
1.9019629955291748
3.1549558639526367
2.5742883682250977
4.8044586181640625
2.6434271335601807Jeśli zwiększysz prędkość ruchu graczy w ReplicatedStorage ⟩ PlayerStateHandler ⟩ togglePlayerMovement, a następnie przetestujesz ponownie, prawdopodobnie napotkasz wiele nieudanych kontroli z powodu nadmiernego ruchu między wybuchami.
local ENABLED_WALK_SPEED = 60 -- zaktualizowana linia, pamiętaj, aby zmienić z powrotemSerwer następnie wykonuje następujące czynności:
- Weryfikuje rayResults.
- Sprawdza, czy gracz może wystrzelić.
- Resetuje stan blastera.
- Redukuje zdrowie dla wszystkich oznaczonych graczy.
- Replikuje wybuch do wszystkich innych graczy, aby mogli zobaczyć efekty wizualne z perspektywy trzeciej osoby.
Aby uzyskać więcej informacji na temat tych operacji serwera, zobacz sekcję wykrywanie trafień samouczka.
Resetowanie blastera
W przykładowej grze w laserowy tag blastery używają mechaniki ciepła. Zamiast przeładowywać po ustalonej liczbie wybuchów, potrzebują czasu na "schłodzenie" między każdym wybuchem. Ta sama opóźnienie chłodzenia występuje zarówno po stronie klienta (blastClient), jak i serwera (blastServer), przy czym serwer działa jako źródło prawdy.
local blasterConfig = getBlasterConfig(player)
local secondsBetweenBlasts = blasterConfig:GetAttribute("secondsBetweenBlasts")
task.delay(secondsBetweenBlasts, function()
local currentState = player:GetAttribute(PlayerAttribute.blasterStateServer)
if currentState == BlasterState.Blasting then
player:SetAttribute(PlayerAttribute.blasterStateServer, BlasterState.Ready)
end
end)Atrybut secondsBetweenBlasts jest częścią konfiguracji blastera w ReplicatedStorage ⟩ Instances ⟩ LaserBlastersFolder. Po upływie opóźnienia secondsBetweenBlasts gracz może ponownie wystrzelić, a cały proces się powtarza. Aby pomóc graczowi zrozumieć, kiedy może ponownie wystrzelić, gra zawiera pasek chłodzenia.
W tym momencie gracze mogą się pojawiać i odradzać, celować i wystrzeliwać, ale gra wciąż musi określić wyniki każdego wybuchu. W następnej sekcji samouczka dowiesz się, jak zaprogramować zdolność blastera do wykrywania, kiedy wybuch trafia innego gracza, a następnie zmniejszać odpowiednią ilość zdrowia gracza zgodnie z ustawieniami blastera.