Thực hiện hành vi bắn súng

*Nội dung này được dịch bằng AI (Beta) và có thể có lỗi. Để xem trang này bằng tiếng Anh, hãy nhấp vào đây.

Thực hiện hành vi bắn súng là quá trình lập trình một cơ chế bắn trong các trải nghiệm bắn súng góc nhìn thứ nhất. Trong khi người chơi có thể bắn chỉ với một cú nhấp chuột hoặc nhấn nút, việc tạo ra một hành vi bắn chính xác và thỏa mãn là rất quan trọng, vì nó nâng cao niềm vui của người chơi trong toàn bộ trải nghiệm gameplay.

Sử dụng trải nghiệm bắn súng laser mẫu làm tài liệu tham khảo, phần này của hướng dẫn dạy bạn về các đoạn mã phía sau việc thực hiện hành vi bắn súng cho hai loại súng khác nhau, bao gồm hướng dẫn về:

  • Phát hiện khi người chơi nhấn nút bắn.
  • Kiểm tra xem người chơi có thể sử dụng súng bắn của họ không nếu họ vừa mới nhấn nút bắn.
  • Tạo dữ liệu bắn cho biết ai đã khởi động vụ bắn, nó xuất phát từ đâu, và mỗi tia laser có đích đến cuối cùng là gì.
  • Thông báo cho máy chủ về dữ liệu bắn để nó có thể thực hiện các hành động phù hợp nếu vụ bắn va chạm với một người chơi khác.
  • Đặt lại súng bắn giữa mỗi lần bắn để cho súng đủ thời gian làm mát trước khi có thể bắn tiếp.

Sau khi bạn hoàn thành phần này, bạn sẽ tìm hiểu về các đoạn mã cho phép súng bắn phát hiện khi tia bắn va chạm với những người chơi khác, từ đó trừ đi lượng sức khỏe tương ứng theo từng loại súng.

Phát hiện đầu vào của người chơi

Bước đầu tiên để thực hiện hành vi bắn súng là lắng nghe khi một người chơi nhấn nút bắn. Loại đầu vào mà người chơi sử dụng để nhấn nút bắn phụ thuộc vào thiết bị mà họ đang sử dụng để truy cập trải nghiệm. Ví dụ, trải nghiệm bắn súng laser mẫu hỗ trợ điều khiển bằng chuột và bàn phím, gamepad, và điều khiển cảm ứng. Bạn có thể thấy mỗi loại đầu vào này trong ReplicatedStorageUserInputHandler.

Đoạn mã client này sử dụng ContextActionService để gán MouseButton1ButtonR2 cho hành động bắn. Điều này có nghĩa là mỗi khi một người chơi nhấn nút chuột trái hoặc nút R2 của gamepad, nó sẽ kích hoạt một tia laser phóng ra từ súng bắn. Lưu ý rằng HUDGui có một nút để bắn trên các thiết bị di động, cái mà nó kết nối ở phần sau trong đoạn mã.

UserInputHandler

ContextActionService:BindAction("_", onBlasterActivated, false,
Enum.UserInputType.MouseButton1,
Enum.KeyCode.ButtonR2
)

Một lưu ý quan trọng khác là việc sử dụng Enum.UserInputState.Begin trong định nghĩa onBlasterActivated(). Nhiều tương tác với giao diện người dùng, chẳng hạn như chọn một khẩu súng trong ví dụ này, không xảy ra cho đến khi nút chuột nhả lên (Enum.UserInputState.End), điều này cho người dùng một cơ hội phút cuối để tránh tương tác. Tuy nhiên, một cơ chế bắn không cảm thấy phản hồi trừ khi nó xảy ra ngay lập tức khi nút được nhấn xuống.

Để minh họa, bạn có thể thay đổi Enum.UserInputState.Begin thành Enum.UserInputState.End, sau đó chơi thử để xem độ phản hồi của vụ bắn ảnh hưởng đến gameplay của trải nghiệm như thế nào. Ví dụ, nếu người chơi có thể giữ nút mà không kích hoạt vụ bắn, điều đó có thể thay đổi trải nghiệm của họ khi đánh dấu những người chơi khác như thế nào?

UserInputHandler

local function onBlasterActivated(_actionName: string,
inputState: Enum.UserInputState, _inputObject: InputObject)
if inputState == Enum.UserInputState.End then -- dòng đã cập nhật, chắc chắn rằng bạn thay đổi lại
attemptBlastClient()
end
end

Kiểm tra xem người chơi có thể bắn không

Sau khi UserInputHandler phát hiện một lần nhấn nút hoặc chạm màn hình, nó gọi ReplicatedStorageBlasterattemptBlastClient để kiểm tra xem người chơi có thể bắn hay không. Giống như hầu hết các kiểm tra trong trải nghiệm bắn súng laser mẫu, nó xảy ra hai lần: đầu tiên trên client, sau đó là trên server. attemptBlastClient sau đó gọi ReplicatedStorageBlastercanLocalPlayerBlast để thực hiện một kiểm tra đơn giản về thuộc tính người chơi blasterStateClient:

canLocalPlayerBlast

local function canLocalPlayerBlast(): boolean
return localPlayer:GetAttribute(PlayerAttribute.blasterStateClient) == BlasterState.Ready
end

Nếu bạn kiểm tra ReplicatedStorageBlasterBlasterState, bạn có thể thấy rằng trải nghiệm có ba trạng thái súng: Ready, Blasting, và Disabled. Để thấy tác động của từng trạng thái này, bạn có thể chơi thử trải nghiệm, chọn nhân vật của bạn dưới dịch vụ Players, sau đó quan sát thuộc tính blasterStateClient trong cửa sổ Properties. Lưu ý cách nó hiển thị Disabled trong khi bạn chọn súng của mình, Ready hầu hết thời gian, và Blasting trong chưa đầy một giây sau khi bạn nhấn nút.

Khoảng dừng nhỏ này ngăn bạn không thể bắn nhanh như bạn có thể nhấp. Ví dụ, nếu bạn thay đổi hàm để luôn trả về true, bạn có thể bắn nhanh chóng súng của mình mà không có bất kỳ độ trễ nào, điều này không thực tế trong gameplay bắn súng laser.

canLocalPlayerBlast

local function canLocalPlayerBlast(): boolean
return true -- dòng đã cập nhật, chắc chắn rằng bạn thay đổi lại
end

Tạo dữ liệu bắn

Sau khi xác minh rằng súng bắn của người chơi ở trạng thái Ready, attemptBlastClient gọi ReplicatedStorageattemptBlastClientblastClient. Bước đầu tiên mà blastClient thực hiện là thiết lập thuộc tính người chơi blasterStateClient thành Blasting, điều này tránh trường hợp bắn liên tục giống như trước đó.

Bước tiếp theo là tạo dữ liệu bắn. Nếu bạn xem lại ReplicatedStorageBlasterBlastData, bạn có thể thấy rằng mỗi vụ bắn bao gồm ba phần thông tin:

  • Người chơi đã khởi động vụ bắn.
  • Một DataType.CFrame đại diện cho điểm xuất phát của vụ bắn.
  • Một bảng RayResult chứa đích đến cuối cùng của từng tia laser và người chơi bị trúng, nếu có.

Để tạo dữ liệu này, blastClient gọi ReplicatedStorageattemptBlastClientblastClientgenerateBlastData, mà bạn có thể xem dưới đây.

generateBlastData

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
end

Hàm này bắt đầu bằng cách sử dụng getBlasterConfig để lấy kiểu súng bắn của người chơi. Mẫu cung cấp hai loại súng: một loại tạo ra nhiều tia với sự phân tán ngang rộng, và một loại sản xuất một tia duy nhất. Bạn có thể tìm thấy cấu hình của chúng trong ReplicatedStorageInstancesLaserBlastersFolder.

Hàm sau đó sử dụng currentCamera.CFrame làm điểm xuất phát cho vụ bắn, truyền nó cho getDirectionsForBlast. Tại thời điểm này, mã không còn về súng bắn, mà về tia laser, cái mà bạn sẽ tìm hiểu thêm trong phần phát hiện va chạm của hướng dẫn. Cuối cùng, sau khi tạo bảng rayResults, generateBlastData có tất cả thông tin cần thiết để trả về dữ liệu bắn cho blastClient.

Thông báo máy chủ

Một khi blastClient đã có dữ liệu hoàn chỉnh cho vụ bắn, nó sẽ kích hoạt hai sự kiện:

blastClient

local laserBlastedBindableEvent = ReplicatedStorage.Instances.LaserBlastedBindableEvent
local laserBlastedEvent = ReplicatedStorage.Instances.LaserBlastedEvent
laserBlastedBindableEvent:Fire(blastData)
laserBlastedEvent:FireServer(blastData)

BindableEvent thông báo cho các đoạn mã client khác về vụ bắn. Ví dụ, ReplicatedStorageFirstPersonBlasterVisuals sử dụng sự kiện này để biết khi nào hiển thị hiệu ứng hình ảnh, như hoạt ảnh bắn và thanh thời gian làm mát. Tương tự, RemoteEvent thông báo cho các đoạn mã server về vụ bắn, điều này bắt đầu quá trình xử lý vụ bắn trong ServerScriptServiceLaserBlastHandler.

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
end

Để giúp ngăn chặn gian lận, máy chủ phải xác minh tất cả dữ liệu mà từng client gửi. Các kiểm tra này bao gồm:

  1. Dữ liệu BlastData có phải là bảng không? Nó có chứa một Class.CFrame và một bảng khác tên là rayResults không?
  2. Người chơi có đang trang bị một khẩu súng không?
  3. Người chơi có nhân vật và vị trí trong thế giới không?
  4. Sau khi gửi dữ liệu bắn, người chơi có di chuyển quá xa so với nơi mà họ đã bắn tia laser không?

Kiểm tra cuối cùng này liên quan đến một quyết định phán đoán, và theo độ trễ của máy chủ và tốc độ di chuyển của người chơi, bạn có thể quyết định rằng các giá trị khác nhau là không hợp lý cho trải nghiệm của riêng bạn. Để minh họa cách đưa ra quyết định này, bạn có thể nắm bắt được độ lớn điển hình của sự thay đổi vị trí bằng cách thêm một câu lệnh in trong getValidatedBlastData và chơi thử trải nghiệm.

getValidatedBlastData

local distanceFromCharacterToOrigin = blastData.originCFrame.Position - rootPartCFrame.Position
print(distanceFromCharacterToOrigin.Magnitude) -- dòng đã cập nhật, chắc chắn rằng bạn xóa
if distanceFromCharacterToOrigin.Magnitude > ToleranceValues.DISTANCE_SANITY_CHECK_TOLERANCE_STUDS then
warn(`Player {player.Name} failed an origin sanity check while blasting`)
return
end

Khi bạn di chuyển và bắn, hãy lưu ý đầu ra. Nó có thể trông giống như sau:


1.9019629955291748
3.1549558639526367
2.5742883682250977
4.8044586181640625
2.6434271335601807

Nếu bạn tăng tốc độ di chuyển cho người chơi trong ReplicatedStoragePlayerStateHandlertogglePlayerMovement, thì hãy chơi thử lại, bạn sẽ gặp nhiều lần kiểm tra không thành công do di chuyển quá mức giữa các lần bắn.

togglePlayerMovement

local ENABLED_WALK_SPEED = 60 -- dòng đã cập nhật, chắc chắn rằng bạn thay đổi lại

Máy chủ sau đó thực hiện các bước sau:

  • Xác nhận rayResults.
  • Kiểm tra xem người chơi có thể bắn hay không.
  • Đặt lại trạng thái khẩu súng.
  • Giảm sức khỏe cho bất kỳ người chơi nào bị đánh dấu.
  • Nhân bản vụ bắn cho tất cả các người chơi khác để họ có thể thấy hiệu ứng hình ảnh từ góc nhìn thứ ba.

Để biết thêm thông tin về các hoạt động máy chủ này, hãy xem phần phát hiện va chạm trong hướng dẫn.

Đặt lại súng bắn

Trong trải nghiệm bắn súng laser mẫu, súng bắn sử dụng cơ chế làm mát. Thay vì nạp lại sau một số lần bắn nhất định, chúng cần thời gian để "làm mát" giữa mỗi lần bắn. Độ trễ làm mát này xảy ra cả trên client (blastClient) và máy chủ (blastServer), với máy chủ đóng vai trò là nguồn thông tin chính xác.

blastServer

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)

Thuộc tính secondsBetweenBlasts là một phần của cấu hình súng bắn trong ReplicatedStorageInstancesLaserBlastersFolder. Sau khi thời gian secondsBetweenBlasts trôi qua, người chơi có thể bắn lại, và toàn bộ quy trình lặp lại. Để giúp người chơi hiểu khi nào họ có thể bắn lại, trải nghiệm bao gồm một thanh làm mát.

Tại thời điểm này, người chơi có thể xuất hiện và tái xuất hiện, ngắm bắn và bắn, nhưng trải nghiệm vẫn phải xác định kết quả của mỗi lần bắn. Trong phần tiếp theo của hướng dẫn, bạn sẽ học cách lập trình khả năng cho súng bắn phát hiện khi vụ bắn trúng một người chơi khác, rồi giảm đi lượng sức khỏe tương ứng theo cài đặt của súng bắn.

©2026 Roblox Corporation. Roblox, logo Roblox và Powering Imagination là các nhãn hiệu đã đăng ký và chưa đăng ký của chúng tôi tại Hoa Kỳ và các quốc gia khác.