In questo Tutoriale, imparerai a lanciare un laser dal blaster in Crea strumenti per il giocatore e a determinare se colpisce un Giocatoreo no.
Raycasting per trovare le collisioni
Raycasting crea un raggio invisibile da una posizione di partenza verso una direzione specificata con una lunghezza definita. Se il raggio si scontra con oggetti o terreno sul suo percorso, restituisce informazioni sulla collisione come la posizione e l'oggetto con cui si è scontrato.
Trovare la posizione del mouse
Prima che un laser possa essere sparato, devi prima sapere dove il giocatore punta con il mouse 2D. Questo può essere trovato facendo raycasting dalla posizione del mouse 2D del Giocatoresulla schermata direttamente verso il Mondodel gioco. Il raggio si scontrerà con qualsiasi cosa il giocatore punta con il Topo, or mouse as computer mouse.
Apri lo script ToolController all'interno dello strumento Blaster da Creazione degli strumenti del giocatore. Se non hai ancora completato quel tutorial, puoi scaricare il modello Blaster e inserirlo in StarterPack.
Nella parte superiore dello script, dichiara una costante chiamata MAX_MOUSE_DISTANCE con un valore di 1000 .
Crea una funzione chiamata getWorldMousePosition .
local tool = script.Parentlocal MAX_MOUSE_DISTANCE = 1000local function getWorldMousePosition()endlocal function toolEquipped()tool.Handle.Equip:Play()endlocal function toolActivated()tool.Handle.Activate:Play()end-- Connetti eventi a funzioni appropriatetool.Equipped:Connect(toolEquipped)tool.Activated:Connect(toolActivated)Usa la funzione GetMouseLocation di UserInputService per ottenere la posizione del mouse 2D del Giocatoresullo schermo. Assegna questo a una variabile chiamata mouseLocation .
local UserInputService = game:GetService("UserInputService")local tool = script.Parentlocal MAX_MOUSE_DISTANCE = 1000local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()end
Ora la posizione del mouse 2D è conosciuta, le sue proprietà X e Y possono essere utilizzate come parametri per la funzione Camera:ViewportPointToRay(), che crea un 1> Datatype.Ray1> dalla schermata nel Mondodel gioco 3D.
Usa le proprietà X e Y di mouseLocation come argomenti per la funzione 1> Class.Camera:ViewportPointToRay()|ViewportPointToRay() . Assegna questo a una variabile chiamata4> screenToWorldRay4> .
local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()-- Crea un raggio dalla posizione del mouse 2Dlocal screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)end
È ora di utilizzare la funzione Raycast per controllare se il raggio colpisce un oggetto. Ciò richiede una posizione di partenza e un vettorialedi direzione: in questo esempio, utilizzerai le proprietà di partenza e di direzione di screenToWorldRay .
La lunghezza del vettore di direzione determina la distanza percorsa dal raggio. Il raggio deve essere lungo come il MAX_MOUSE_DISTANCE , quindi dovrai moltiplicare il vettore di direzione per MAX_MOUSE_DISTANCE .
Dichiarare una variabile chiamata direzioneVector e assegnarle il valore di screenToWorldRay.Direction moltiplicato da MAX_MOUSE_DISTANCE .
local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()-- Crea un raggio dalla posizione del mouse 2Dlocal screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)-- La direzione di unità del ray multiplicata da una distanza massimalocal directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCEChiama la funzione Raycast dello workspace, passando la proprietà Origin di screenToWorldRay come primo argomento e 1> durationVector1> come secondo. Assign this to a variable named 4> raycastResult4> .
local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()-- Crea un raggio dalla posizione del mouse 2Dlocal screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)-- La direzione di unità del ray multiplicata da una distanza massimalocal directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE-- Raycast dall'origine del raggio verso la sua direzionelocal raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
Informazioni sulla collisione
Se l'operazione raycast trova un oggetto colpito dal ray, restituirà un RaycastResult , che contiene informazioni sulla collisione tra il ray e l'oggetto.
Proprietà RaycastResult | Descrizione |
---|---|
istanza | La BasePart o Terrain cellula che il raggio ha interseccato. |
Posizione | Dove si è verificata l'intersezione; di solito un punto direttamente sulla superficie di una parte o terreno. |
Materiale | Il materiale al punto di collisione. |
Normale | Il normale vector della faccia interrotta. Questo può essere utilizzato per determinare in quale direzione la faccia punta. |
La proprietà Posizione sarà la posizione dell'oggetto che il mouse si trova sopra. Se il mouse non si trova su nessun oggetto all'interno di una distanza di MAX_MOUSE_DISTANCE , raycastResult sarà nullo.
Crea un if statement per controllare se raycastResult esiste.
Se raycastResult ha un valore, restituisci la sua Proprietà Posizione .
Se raycastResult è nullo, allora trova la fine del raycast. Calcola la posizione 3D della mouse aggiungendo screenToWorldRay.Origin e directionVector insieme.
local function getWorldMousePosition()
local mouseLocation = UserInputService:GetMouseLocation()
-- Crea un raggio dalla posizione del mouse 2D
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- La direzione di unità del ray multiplicata da una distanza massima
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
-- Raycast dall'origine del raggio verso la sua direzione
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
-- Restituisci il punto 3D di intersezione
return raycastResult.Position
else
-- Nessun oggetto è stato colpito, quindi calcola la posizione alla fine del raggio
return screenToWorldRay.Origin + directionVector
end
end
Sparando verso il bersaglio
Ora che la posizione del mouse 3D è nota, può essere utilizzata come posizione di destinazione per sparare un laser verso. Un secondo raggio può essere cast tra l'arma del Giocatoree la posizione di destinazione usando la funzione Raggiocast .
Dichiarare una costante chiamata MAX_LASER_DISTANCE nella parte superiore dello script e assegnarla a 500 , o la tua gamma scelta per il laser blaster.
local UserInputService = game:GetService("UserInputService")local tool = script.Parentlocal MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 500Crea una funzione chiamata fireWeapon sotto la funzione getWorldMousePosition .
Chiama getWorldMousePosition e assegna il risultato a una variabile chiamata mousePosition . Questa sarà la posizione di destinazione per il raycast.
-- Nessun oggetto è stato colpito, quindi calcola la posizione alla fine del raggioreturn screenToWorldRay.Origin + directionVectorendendlocal function fireWeapon()local mouseLocation = getWorldMousePosition()endlocal function toolEquipped()tool.Handle.Equip:Play()end
Questa volta, il vettore di direzione per la funzione raycast rappresenterà la direzione dalla posizione dell'utensile del Giocatorealla posizione di destinazione.
Dichiarare una variabile chiamata targetDirection e calcolare il veicolo di direzione sottraendo la posizione dell'strumento da mouseLocation .
Normalizza il veicolo utilizzando la sua Proprietà Unit . Ciò lo rende facile da moltiplicare in seguito con una lunghezza.
local function fireWeapon()local mouseLocation = getWorldMousePosition()-- Calcola un vettore di direzione normalizzato e moltiplica per la distanza laserlocal targetDirection = (mouseLocation - tool.Handle.Position).UnitendDichiarare una variabile chiamata 向zione Vector e assegnare a essa il targetDirection moltiplicato per il MAX_LASER_DISTANCE .
local targetDirection = (mouseLocation - tool.Handle.Position).Unit-- La direzione a cui sparare l'arma, moltiplicata da una distanza massimalocal directionVector = targetDirection * MAX_LASER_DISTANCEend
Un oggetto RaycastParams può essere utilizzato per memorizzare parametri aggiuntivi per la funzione raycast. Sarà utilizzato nel tuo laser blaster per assicurarsi che il raycast non si sconvolga accidentalmente con il giocatore che spara l'arma. Tutte le parti incluse nella proprietà Datatype.RaycastParams.FilterDescendantsInstances|Filter
Continua la funzione fireWeapon e dichiara una variabile chiamata weaponRaycastParams . Assegna un nuovo oggetto RaycastParams a esso.
Crea una tabella che contiene il personaggio locale del Giocatoree assegnalo alla proprietà weaponRaycastParams.FilterDescendantsInstances .
Raycast dalla posizione della maniglia dell'strumento del Giocatore, in una direzione verso il directionVector . Ricorda di aggiungere weaponRaycastParams come argument this time. Assign this to a variable named weaponRaycastResult .
local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")
local tool = script.Parent
local MAX_MOUSE_DISTANCE = 1000
local MAX_LASER_DISTANCE = 500
local function getWorldMousePosition()
local function fireWeapon()
local mouseLocation = getWorldMousePosition()
-- Calcola un vettore di direzione normalizzato e moltiplica per la distanza laser
local targetDirection = (mouseLocation - tool.Handle.Position).Unit
-- La direzione per cui sparare l'arma si moltiplica per una distanza massima
local directionVector = targetDirection * MAX_LASER_DISTANCE
-- Ignora il personaggio del Giocatoreper impedirgli di danneggiarsi
local weaponRaycastParams = RaycastParams.new()
weaponRaycastParams.FilterDescendantsInstances = {Players.LocalPlayer.Character}
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
end
Infine, dovrai controllare che l'operazione raycast abbia restituito un valore. Se viene restituito un valore, un oggetto viene colpito dal raggio e viene creato un laser tra l'arma e la posizione di colpo. Se non è stato restituito nulla, la posizione finale deve essere calcolata per creare il laser.
Dichiarare una variabile vuota chiamata hitPosition .
Usa una dichiarazione se per controllare se weaponRaycastResult ha un valore. Se un oggetto è stato colpito, assegnare weaponRaycastResult.Position a 1> hitPosition1> .
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)-- Controlla se sono stati colpiti oggetti tra la posizione di partenza e la posizione di finelocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.PositionendSe weaponRaycastResult non ha valore, calcola la posizione della fine del raycast aggiungendo insieme la posizione della maniglia del strumento con il directionVector. Assegna questo a 1> hitPosition1> .
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)-- Controlla se sono stati colpiti oggetti tra la posizione di partenza e la posizione di finelocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.Positionelse-- Calcola la posizione di fine raggio in base alla distanza laser massimahitPosition = tool.Handle.Position + directionVectorendendNavigate to the toolActivated function and call the fireWeapon function so that the laser fires each time the tool is activated.
local function toolActivated()tool.Handle.Activate:Play()fireWeapon()end
Controllo dell'oggetto colpito
Per trovare se l'oggetto colpito dal laser fa parte del personaggio di un Giocatoreo solo di un pezzo di sceneria, dovrai cercare un Humanoid, poiché ogni personaggio ne ha uno.
Per prima cosa, dovrai trovare il modello di personaggio . Se una parte del personaggio è stata colpita, non puoi assumere che il padre dell'oggetto colpito fosse il personaggio. Il laser potrebbe aver colpito una parte del corpo, un Accessorioo uno strumento, tutti i quali sono situati in diverse parti della gerarchia del personaggio.
Puoi usare FindFirstAncestorOfClass to find a character model ancestor of the object hit by the laser, if one exists. If you find a model and it contains a humanoid, in most cases you can assume it's a character.
Aggiungi il codice evidenziato di seguito alla weaponRaycastResult if dichiarazione per controllare se un personaggio è stato colpito.
-- Controlla se sono stati colpiti oggetti tra la posizione di partenza e la posizione di finelocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.Position-- L'istanza colpita sarà un figlio di un modello di personaggio-- Se viene trovato un humanoid nel modello, è probabile che sia il personaggio di un Giocatorelocal characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid thenprint("Player hit")endendelse-- Calcola la posizione di fine raggio in base alla distanza laser massimahitPosition = tool.Handle.Position + directionVectorend
Ora il laser blaster dovrebbe stampare Player hit alla finestra di uscita ogni volta che l'operazione raycast colpisce un altro Giocatore.
Testare con più giocatori
Sono necessari due giocatori per testare se il raggio dell'arma sta trovando altri giocatori, quindi devi iniziare un Serverlocale.
Seleziona la scheda Test in Studio.
Assicurati che il menu a discesa dei giocatori sia impostato su "2 giocatori" e fai clic sul pulsante Start per avviare un server locale con 2 client. Tre finestre appariranno. La prima finestra sarà il Serverlocale, le altre finire saranno i client per Player1 e Player2.
Su un client, testare di sparare l'altro giocatore con l'arma facendo clic su di loro. "Player hit" dovrebbe essere visualizzato nell'output ogni volta che un giocatore viene colpito.
Puoi saperne di più sulla scheda Test qui .
Trovare la posizione del laser
Il blaster dovrebbe sparare un raggio di luce rosso al suo target. La funzione per questo sarà all'interno di un ModuleScript in modo che possa essere riutilizzato in altri script in seguito. Prima, lo script dovrà trovare la posizione in cui il raggio laser dovrebbe essere generato.
Crea un ModuleScript chiamato LaserRender , che è figlio di StarterPlayerScripts sotto StarterPlayer.
Apri lo script e rinomina la tabella del modulo in nome dello script LaserRender .
Dichiarare una variabile chiamata SHOT_DURATION con un valore di 0.15 . Questa sarà la quantità di tempo (in secondi) per cui il laser è visibile.
Crea una funzione di LaserRender chiamata createLaser con due parametri chiamati toolHandle e endPosition .
local LaserRenderer = {}local SHOT_DURATION = 0.15 -- Tempo durante cui il laser è visibile-- Crea un raggio laser dalla posizione di partenza verso una posizione di destinazionefunction LaserRenderer.createLaser(toolHandle, endPosition)endreturn LaserRendererDichiarare una variabile chiamata startPosition e impostare la proprietà Position di toolHandle come suo valore. Questa sarà la posizione del laser del Giocatore.
Dichiarare una variabile chiamata laserDistance e sottrarre endPosition da startPosition per trovare la differenza tra i due veettori. Usa la proprietà 1> Magnitude1> di questo per ottenere la lunghezza del raggio laser.
function LaserRenderer.createLaser(toolHandle, endPosition)local startPosition = toolHandle.Positionlocal laserDistance = (startPosition - endPosition).MagnitudeendDichiarare una variabile laserCFrame per memorizzare la posizione e l'orientamento del raggio laser. La posizione deve essere il punto medio della partenza e la fine del raggio. Usa CFrame.lookAt per creare una nuova Datatype.CFrame</
function LaserRenderer.createLaser(toolHandle, endPosition)local startPosition = toolHandle.Positionlocal laserDistance = (startPosition - endPosition).Magnitudelocal laserCFrame = CFrame.lookAt(startPosition, endPosition) * CFrame.new(0, 0, -laserDistance / 2)end
Creazione della parte laser
Ora che sai dove creare un raggio laser, devi aggiungere il raggio stesso. Questo può essere fatto facilmente con una parte Neon.
Dichiarare una variabile laserPart e assegnare a essa una nuova esempioPart .
Imposta le seguenti proprietà di laserPart :
- Dimensione : Vector3.new(0.2, 0.2, laserDistance)
- CFrame : laserCFrame
- Anchored : vero
- CanCollide : false
- Colore : Color3.fromRGB(225, 0, 0) (un forte colore rosso)
- Materiale : Enum.Material.Neon
Parent laserPart a Workspace .
Aggiungi la parte al servizio Debris in modo che venga rimossa dopo il numero di secondi nella variabile SHOT_DURATION .
function LaserRenderer.createLaser(toolHandle, endPosition)local startPosition = toolHandle.Positionlocal laserDistance = (startPosition - endPosition).Magnitudelocal laserCFrame = CFrame.lookAt(startPosition, endPosition) * CFrame.new(0, 0, -laserDistance / 2)local laserPart = Instance.new("Part")laserPart.Size = Vector3.new(0.2, 0.2, laserDistance)laserPart.CFrame = laserCFramelaserPart.Anchored = truelaserPart.CanCollide = falselaserPart.Color = Color3.fromRGB(225, 0, 0)laserPart.Material = Enum.Material.NeonlaserPart.Parent = workspace-- Aggiungi raggio laser al servizio Debris per essere rimosso e pulitoDebris:AddItem(laserPart, SHOT_DURATION)end
Ora la funzione per rendere il raggio laser è completa, può essere chiamata dal ToolController .
Nella parte superiore dello script ToolController , dichiara una variabile chiamata LaserRenderer e richiedi il modulo di script LaserRendererModuleScript situato in PlayerScripts.
local UserInputService = game:GetService("UserInputService")local Players = game:GetService("Players")local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer)local tool = script.ParentNella parte inferiore della funzione fireWeapon, chiama la funzione LaserRender createLaser utilizzando il trattino degli strumenti e hitPosition come argomenti.
-- Calcola la posizione di fine raggio in base alla distanza laser massimahitPosition = tool.Handle.Position + directionVectorendLaserRenderer.createLaser(tool.Handle, hitPosition)endProva l'arma facendo clic sul pulsante Play. Un raggio laser dovrebbe essere visibile tra l'arma e il mouse quando lo strumento è attivato.
Controllo della velocità di fuoco dell'arma
Le armi hanno un ritardo tra ogni colpo per impedire ai giocatori di infliggere troppo danno in un breve periodo di tempo. Questo può essere controllato controllando se abbia passato abbastanza tempo dal giocatore l'ultima volta che ha sparato.
Dichiarare una variabile nella parte superiore del ToolController chiamato FIRE_RATE . Questo sarà il tempo minimo tra ogni colpo. Dai un valore a scelta tua; questo esempio usa 0.3 secondi.
Dichiarare un'altra variabile sotto chiamata timeOfPreviousShot con un valore di 0 . Questo memorizza l'ultimo tempo che il giocatore ha sparato e verrà aggiornato con ogni sparo.
local MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 300local FIRE_RATE = 0.3local timeOfPreviousShot = 0Crea una funzione chiamata canShootWeapon senza parametri. Questa funzione guarderà a quanto tempo è passato dal precedente colpo e restituirà vero o falso.
local FIRE_RATE = 0.3local timeOfPreviousShot = 0-- Controlla se sia passato abbastanza tempo dal momento in cui è stato sparato l'ultimo colpolocal function canShootWeapon()endlocal function getWorldMousePosition()All'interno della funzione dichiara una variabile chiamata currentTime ; assegna al risultato dell'invocazione della funzione tick() . Questo restituisce il tempo trascorso, in secondi, dal primo gennaio 1970 (una data arbitraria ampiamente utilizzata per calcolare il tempo).
Sottrai il timeOfPreviousShot da currentTime e restituisci false se il risultato è inferiore a 1> FIRE_RATE1>; altrimenti, restituisci 4> true 4> .
-- Controlla se sia passato abbastanza tempo dal momento in cui è stato sparato l'ultimo colpolocal function canShootWeapon()local currentTime = tick()if currentTime - timeOfPreviousShot < FIRE_RATE thenreturn falseendreturn trueendAlla fine della funzione fireWeapon, aggiorna timeOfPreviousShot ogni volta che l'arma viene sparata usando tick .
hitPosition = tool.Handle.Position + directionVectorendtimeOfPreviousShot = tick()LaserRenderer.createLaser(tool.Handle, hitPosition)endAll'interno della funzione toolActivated, crea un'istruzione se e chiama canShootWeapon per controllare se l'arma può essere sparata.
local function toolActivated()if canShootWeapon() thentool.Handle.Activate:Play()fireWeapon()endend
Quando testi il blaster dovresti trovare che, indipendentemente dalla velocità con cui fai clic, ci sarà sempre un breve ritardo di 0,3 secondi tra ogni colpo.
Danneggiare il giocatore
I client non possono danneggiare direttamente altri client; il server deve essere responsabile per l'emissione del danno quando un giocatore viene colpito.
I client possono utilizzare un RemoteEvent per informare il server che un personaggio è stato colpito. Questi devono essere memorizzati in ReplicatedStorage , dove sono visibili sia per il client che per il Server.
Crea una cartella in ReplicatedStorage chiamata Eventi .
Inserisci un RemoteEvent nella cartella Eventi e chiamalo DamageCharacter .
In ToolController , crea variabili all'inizio dello script per ReplicatedStorage e la cartella Eventi.
local UserInputService = game:GetService("UserInputService")local Players = game:GetService("Players")local ReplicatedStorage = game:GetService("ReplicatedStorage")local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer)local tool = script.Parentlocal eventsFolder = ReplicatedStorage.Eventslocal MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 500Sostituisci la print statement in fireWeapon con una linea di Lua per il fuoco del DamageCharacter evento remoto con la variabile 2> characterModel2> come argomento.
local characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid theneventsFolder.DamageCharacter:FireServer(characterModel)endendelse-- Calcola la posizione di fine raggio in base alla distanza laser massimahitPosition = tool.Handle.Position + directionVectorend
Il server deve infliggere danni al giocatore che è stato colpito quando l'evento è stato attivato.
Inserisci uno Script in ServerScriptService e chiamalo ServerLaserManager.
Dichiarare una variabile chiamata LASER_DAMAGE e impostarla su 10 , o un valore a tua scelta.
Crea una funzione chiamata damageCharacter con due parametri chiamati playerFired e characterToDamage .
All'interno della funzione, trova il carattere del suo Umanoide e sottrai LASER_DAMAGE dalla sua salute.
Connetti la funzione damageCharacter alla remota evento DamageCharacter nella cartella Eventi.
local ReplicatedStorage = game:GetService("ReplicatedStorage")local eventsFolder = ReplicatedStorage.Eventslocal LASER_DAMAGE = 10function damageCharacter(playerFired, characterToDamage)local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")if humanoid then-- Rimuovi la salute dal personaggiohumanoid.Health -= LASER_DAMAGEendend-- Connetti eventi a funzioni appropriateeventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)Prova il blaster con 2 giocatori iniziando un Serverlocale. Quando spari l'altro Giocatore, la loro salute diminuirà del numero assegnato a LASER_DAMAGE .
Rendere i laser del giocatore
Attualmente, il raggio laser viene creato dal client che spara l'arma, quindi solo loro potranno vedere il raggio laser.
Se il laser beam è stato creato sul server, allora tutti sarebbero in grado di vederlo. Tuttavia, ci sarebbe un piccolo ritardo tra il client che spara l'arma e il server che riceve le informazioni sul colpo. Ciò significerebbe che il client che spara l'arma vedrebbe un ritardo tra quando attiva l'arma e quando vede il laser beam; l'arma si sentirebbe laggy come Risultato.
Per risolvere questo problema, ogni client creerà i suoi laser. Ciò significa che il cliente che spara l'arma vedrà il laser istantaneamente. Altri cliente vedranno un breve ritardo tra quando un altro giocatore spara e un raggio appare. Questo è lo scenario di caso migliore: non ci sono modo di comunicare un cliente del laser a altri clienti più velocemente.
Client del fotografo
Innanzitutto, il client deve informare il server di aver sparato un laser e fornire la posizione di fine.
Inserisci un RemoteEvent nel cartello degli eventi in ReplicatedStorage e chiamalo LaserFired .
Individua la funzione fireWeapon nella script ToolController . Alla fine della funzione, attiva l'evento remoto LaserFired utilizzando 1> hitPosition1> come argomento.
hitPosition = tool.Handle.Position + directionVectorendtimeOfPreviousShot = tick()eventsFolder.LaserFired:FireServer(hitPosition)LaserRenderer.createLaser(tool.Handle, hitPosition)end
Il server
Il server ora deve ricevere l'evento che il client ha avviato e comunicare a tutti i client la posizione di avvio e fine del laser in modo che possano anche renderlo.
Nell'script del ServerLaserManager, crea una funzione chiamata playerFiredLaser sopra damageCharacter con due parametri chiamati 2> playerFired2> e 5> endPosition5> .
Connetti la funzione all'evento remoto LaserFired .
-- Avvisa tutti i client che un laser è stato sparato in modo che possano visualizzare il laserlocal function playerFiredLaser(playerFired, endPosition)end-- Connetti eventi a funzioni appropriateeventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)
Il server ha bisogno della posizione di avvio del laser. Questo potrebbe essere inviato dal client, ma è meglio evitare di fidarsi del client dove possibile. La posizione della maniglia dell'arma del personaggio sarà la posizione di avvio, in modo che il server possa trovarla da lì.
Crea una funzione getPlayerToolHandle sopra la funzione playerFiredLaser con un parametro chiamato player .
Usa il seguente codice per cercare il personaggio del Giocatoreper l'arma e restituire l'oggetto maniglia.
local LASER_DAMAGE = 10-- Trova la maniglia dello strumento che il giocatore sta tenendolocal function getPlayerToolHandle(player)local weapon = player.Character:FindFirstChildOfClass("Tool")if weapon thenreturn weapon:FindFirstChild("Handle")endend-- Avvisa tutti i client che un laser è stato sparato in modo che possano visualizzare il laserlocal function playerFiredLaser(playerFired, endPosition)
Il server ora può chiamare FireAllClients sull'evento remoto LaserFired per inviare le informazioni richieste per rendere il laser ai client. Ciò include il giocatore che ha sparato il laser (in modo che il client per quel giocatore non renda il laser due volte) e la posizione di fine del laser.
Nella funzione playerFiredLaser, chiama la funzione getPlayerToolHandle con playerFired come argomento e assegna il valore a una variabile chiamata 1> toolHandle1> .
Se toolHandle esiste, fire the LaserFired event for all client using playerFired , toolHandle and 1> endPosition1> as arguments.
-- Avvisa tutti i client che un laser è stato sparato in modo che possano visualizzare il laserlocal function playerFiredLaser(playerFired, endPosition)local toolHandle = getPlayerToolHandle(playerFired)if toolHandle theneventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)endend
Renderizzazione sui client
Ora FireAllClients è stato chiamato, ogni cliente riceverà un evento dal server per rendere un raggio laser. Ogni cliente può riutilizzare il modulo LaserReceiver da precedente per rendere il raggio laser utilizzando la posizione e la posizione di destinazione inviata dal Server. Il giocatore che ha sparato il raggio laser in prima posizione dovrebbe ignorare questo evento altrimenti vedrà 2 laser.
Crea un Script locale in StarterPlayerScripts chiamato ClientLaserManager .
Dentro lo script, richiedi il modulo LaserRender .
Crea una funzione chiamata createPlayerLaser con i parametri playerWhoShot , toolHandle e 1> endPosition1> .
Connetti la funzione all'evento remoto LaserFired nella cartella Eventi.
Nella funzione, usa un se statement to check if playerWhoShot does not equal the LocalPlayer.
All'interno della dichiarazione if, chiama la funzione createLaser dal modulo LaserRender utilizzando toolHandle e endPosition come argomenti.
local Players = game:GetService("Players")local ReplicatedStorage = game:GetService("ReplicatedStorage")local LaserRenderer = require(script.Parent:WaitForChild("LaserRenderer"))local eventsFolder = ReplicatedStorage.Events-- Mostra il laser di un altro Giocatorelocal function createPlayerLaser(playerWhoShot, toolHandle, endPosition)if playerWhoShot ~= Players.LocalPlayer thenLaserRenderer.createLaser(toolHandle, endPosition)endendeventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)Prova il blaster con 2 giocatori iniziando un Serverlocale. Posiziona ciascun client su lati diversi del tuo monitor in modo da poter vedere entrambe le finestre contemporaneamente. Quando spari su un client, dovresti vedere il laser sull'altro client.
Effetti sonori
L'effetto sonoro di sparo viene attualmente riprodotto solo sul client che spara il proiettile. Avrai bisogno di spostare il codice per riprodurlo in modo che altri giocatori possano ascoltarlo anche.
Nell'script ToolController, naviga alla funzione toolActivated e rimuovi la linea che riproduce il suono di Attivazione.
local function toolActivated()if canShootWeapon() thenfireWeapon()endendNella parte inferiore della funzione createLaser in LaserRender, dichiara una variabile chiamata shootingSound e usa il metodo 2>Class.Instance:FindFirstChild()|FindFirstChild() di 5> toolHandle5> per controllare il suono 8> Attiva8>.
Usa una if dichiarazione per controllare se shootingSound esiste; se lo fa, chiama la sua funzione Play .
laserPart.Parent = workspace-- Aggiungi raggio laser al servizio Debris per essere rimosso e pulitoDebris:AddItem(laserPart, SHOT_DURATION)-- Riproduci il suono di sparo dell'armalocal shootingSound = toolHandle:FindFirstChild("Activate")if shootingSound thenshootingSound:Play()endend
Protezione dei telecomandi usando la convalida
Se il server non controlla i dati provenienti dalle richieste in arrivo, un hacker può abusare di funzioni e eventi remoti e usarli per inviare valori falsi al Server. È importante utilizzare la convalida lato server per prevenire questo.
Nella sua attuale forma, l'evento remoto DamageCharacter è molto vulnerabile agli attacchi. Gli hacker potrebbero usare questo evento per danneggiare qualsiasi giocatore che vogliono nel gioco senza sparargli.
La validazione è il processo di controllo che il server invia al server sono realistici. In questo caso, il server dovrà:
- Controlla se la distanza tra il giocatore e la posizione colpita dal laser è all'interno di un certo limite.
- Raycast tra l'arma che ha sparato il laser e la posizione di colpo per assicurarsi che il colpo fosse possibile e non passasse attraverso nessun muro.
Client
Il client deve inviare al server la posizione colpita dal raycast in modo che possa controllare la distanza che è realistica.
In ToolController , fai clic sulla linea in cui viene eseguito l'evento remoto del personaggio danneggiante nella funzione fireWeapon.
Aggiungi hitPosition come argomento.
if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid theneventsFolder.DamageCharacter:FireServer(characterModel, hitPosition)endend
Server
Il client ora sta inviando un parametro extra attraverso l'evento remoto DamageCharacter, quindi il ServerLaserManager deve essere regolato per accettarlo.
Nell'script ServerLaserManager, aggiungi un hitPosition parametro alla funzione damageCharacter.
function damageCharacter(playerFired, characterToDamage, hitPosition)local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")if humanoid then-- Rimuovi la salute dal personaggiohumanoid.Health -= LASER_DAMAGEendendSotto la funzione getPlayerToolHandle, crea una funzione chiamata isHitValid con tre parametri: playerFired , 1> characterToDamage1> e 4> hitPosition4> .
endlocal function isHitValid(playerFired, characterToDamage, hitPosition)end
Il primo controllo sarà la distanza tra la posizione di colpo e il personaggio colpito.
Dichiarare una variabile chiamata MAX_HIT_PROXIMITY nella parte superiore dello script e assegnarle un valore di 10 . Questa sarà la distanza massima consentita tra il colpo e il personaggio. È necessario un tolleranza poiché il personaggio potrebbe essere spostato leggermente dal momento che il client ha avviato l'evento.
local ReplicatedStorage = game:GetService("ReplicatedStorage")local eventsFolder = ReplicatedStorage.Eventslocal LASER_DAMAGE = 10local MAX_HIT_PROXIMITY = 10Nella funzione isHitValid, calcola la distanza tra il personaggio e la posizione di colpo. Se la distanza è maggiore di MAX_HIT_PROXIMITY allora restituisci false .
local function isHitValid(playerFired, characterToDamage, hitPosition)-- Validare la distanza tra il carattere colpito e la posizione di colpolocal characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitudeif characterHitProximity > MAX_HIT_PROXIMITY thenreturn falseendend
Il secondo check coinvolgerà un raycast tra l'arma sparata e la posizione di colpo. Se il raycast restituisce un oggetto che non è il personaggio, puoi assumere che il colpo non sia valido poiché qualcosa stava bloccando il colpo.
Copia il codice sottostante per eseguire questo Controllare /Verificare. Ritorna true alla fine della funzione: se raggiunge la Terminare, tutti i controlli sono passati.
local function isHitValid(playerFired, characterToDamage, hitPosition)-- Validare la distanza tra il carattere colpito e la posizione di colpolocal characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitudeif characterHitProximity > 10 thenreturn falseend-- Controlla se sparare attraverso i murilocal toolHandle = getPlayerToolHandle(playerFired)if toolHandle thenlocal rayLength = (hitPosition - toolHandle.Position).Magnitudelocal rayDirection = (hitPosition - toolHandle.Position).Unitlocal raycastParams = RaycastParams.new()raycastParams.FilterDescendantsInstances = {playerFired.Character}local rayResult = workspace:Raycast(toolHandle.Position, rayDirection * rayLength, raycastParams)-- Se un'istanza è stata colpita che non era il personaggio, ignorare il colpoif rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) thenreturn falseendendreturn trueendDichiarare una variabile nella funzione damageCharacter chiamata validoSparo . Assegnare al risultato di una chiamata alla funzione isHitValid con tre argomenti: 2> playerFired2> , 5> characterToDamage5> e 8> hitPosition8> .
Nella dichiarazione seguente, aggiungi un operatore e per controllare se validShot è vero .
function damageCharacter(playerFired, characterToDamage, hitPosition)local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")local validShot = isHitValid(playerFired, characterToDamage, hitPosition)if humanoid and validShot then-- Rimuovi la salute dal personaggiohumanoid.Health -= LASER_DAMAGEendend
Ora l'evento remoto del danno è più sicuro e impedirà la maggior parte dei giocatori di abusarci. Nota che alcuni giocatori malevoli troveranno spesso modi intorno alla validazione; mantenere gli eventi remoti sicuri è un sforzo continuo.
Il tuo laser blaster è ora completato, con un sistema di rilevamento dei colpi di base che utilizza il raycasting. Prova il tutorial Rilevamento dell'input dell'utente per scoprire come puoi aggiungere un'azione di ricarica al tuo laser blaster, o creare una mappa di gioco divertente e provare il tuo laser blaster con altri giocatori!
Codice finale
ToolController
local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer)
local tool = script.Parent
local eventsFolder = ReplicatedStorage.Events
local MAX_MOUSE_DISTANCE = 1000
local MAX_LASER_DISTANCE = 500
local FIRE_RATE = 0.3
local timeOfPreviousShot = 0
-- Controlla se sia passato abbastanza tempo dal momento in cui è stato sparato l'ultimo colpo
local function canShootWeapon()
local currentTime = tick()
if currentTime - timeOfPreviousShot < FIRE_RATE then
return false
end
return true
end
local function getWorldMousePosition()
local mouseLocation = UserInputService:GetMouseLocation()
-- Crea un raggio dalla posizione del mouse 2D
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- La direzione di unità del ray multiplicata da una distanza massima
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
-- Raycast dall'origine del re verso la sua direzione
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
-- Restituisci il punto 3D di intersezione
return raycastResult.Position
else
-- Nessun oggetto è stato colpito, quindi calcola la posizione alla fine del raggio
return screenToWorldRay.Origin + directionVector
end
end
local function fireWeapon()
local mouseLocation = getWorldMousePosition()
-- Calcola un vettore di direzione normalizzato e moltiplica per la distanza laser
local targetDirection = (mouseLocation - tool.Handle.Position).Unit
-- La direzione a cui sparare l'arma, moltiplicata da una distanza massima
local directionVector = targetDirection * MAX_LASER_DISTANCE
-- Ignora il personaggio del Giocatoreper impedirgli di danneggiarsi
local weaponRaycastParams = RaycastParams.new()
weaponRaycastParams.FilterDescendantsInstances = {Players.LocalPlayer.Character}
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
-- Controlla se sono stati colpiti oggetti tra la posizione di partenza e la posizione di fine
local hitPosition
if weaponRaycastResult then
hitPosition = weaponRaycastResult.Position
-- L'istanza colpita sarà un figlio di un modello di personaggio
-- Se viene trovato un humanoid nel modello, è probabile che sia il personaggio di un Giocatore
local characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")
if characterModel then
local humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")
if humanoid then
eventsFolder.DamageCharacter:FireServer(characterModel, hitPosition)
end
end
else
-- Calcola la posizione di fine raggio in base alla distanza laser massima
hitPosition = tool.Handle.Position + directionVector
end
timeOfPreviousShot = tick()
eventsFolder.LaserFired:FireServer(hitPosition)
LaserRenderer.createLaser(tool.Handle, hitPosition)
end
local function toolEquipped()
tool.Handle.Equip:Play()
end
local function toolActivated()
if canShootWeapon() then
fireWeapon()
end
end
tool.Equipped:Connect(toolEquipped)
tool.Activated:Connect(toolActivated)
Renderer laser
local LaserRenderer = {}
local Debris = game:GetService("Debris")
local SHOT_DURATION = 0.15 -- Tempo durante cui il laser è visibile
-- Crea un raggio laser dalla posizione di partenza verso una posizione di destinazione
function LaserRenderer.createLaser(toolHandle, endPosition)
local startPosition = toolHandle.Position
local laserDistance = (startPosition - endPosition).Magnitude
local laserCFrame = CFrame.lookAt(startPosition, endPosition) * CFrame.new(0, 0, -laserDistance / 2)
local laserPart = Instance.new("Part")
laserPart.Size = Vector3.new(0.2, 0.2, laserDistance)
laserPart.CFrame = laserCFrame
laserPart.Anchored = true
laserPart.CanCollide = false
laserPart.Color = Color3.fromRGB(255, 0, 0)
laserPart.Material = Enum.Material.Neon
laserPart.Parent = workspace
-- Aggiungi raggio laser al servizio Debris per essere rimosso e pulito
Debris:AddItem(laserPart, SHOT_DURATION)
-- Riproduci il suono di sparo dell'arma
local shootingSound = toolHandle:FindFirstChild("Activate")
if shootingSound then
shootingSound:Play()
end
end
return LaserRenderer
Gestore Laser del Server
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local eventsFolder = ReplicatedStorage.Events
local LASER_DAMAGE = 10
local MAX_HIT_PROXIMITY = 10
-- Trova la maniglia dello strumento che il giocatore sta tenendo
local function getPlayerToolHandle(player)
local weapon = player.Character:FindFirstChildOfClass("Tool")
if weapon then
return weapon:FindFirstChild("Handle")
end
end
local function isHitValid(playerFired, characterToDamage, hitPosition)
-- Validare la distanza tra il carattere colpito e la posizione di colpo
local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
if characterHitProximity > MAX_HIT_PROXIMITY then
return false
end
-- Controlla se sparare attraverso i muri
local toolHandle = getPlayerToolHandle(playerFired)
if toolHandle then
local rayLength = (hitPosition - toolHandle.Position).Magnitude
local rayDirection = (hitPosition - toolHandle.Position).Unit
local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {playerFired.Character}
local rayResult = workspace:Raycast(toolHandle.Position, rayDirection * rayLength, raycastParams)
-- Se un'istanza è stata colpita che non era il personaggio, ignorare il colpo
if rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) then
return false
end
end
return true
end
-- Avvisa tutti i client che un laser è stato sparato in modo che possano visualizzare il laser
local function playerFiredLaser(playerFired, endPosition)
local toolHandle = getPlayerToolHandle(playerFired)
if toolHandle then
eventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)
end
end
function damageCharacter(playerFired, characterToDamage, hitPosition)
local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")
local validShot = isHitValid(playerFired, characterToDamage, hitPosition)
if humanoid and validShot then
-- Rimuovi la salute dal personaggio
humanoid.Health -= LASER_DAMAGE
end
end
-- Connetti eventi a funzioni appropriate
eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)
ClientLaserManager
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local LaserRenderer = require(Players.LocalPlayer.PlayerScripts:WaitForChild("LaserRenderer"))
local eventsFolder = ReplicatedStorage.Events
-- Mostra il laser di un altro Giocatore
local function createPlayerLaser(playerWhoShot, toolHandle, endPosition)
if playerWhoShot ~= Players.LocalPlayer then
LaserRenderer.createLaser(toolHandle, endPosition)
end
end
eventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)