工具枪
如何为沙盒游戏模式创建新的工具枪

这种工具枪方案是沙盒游戏模式的一种约定,并非全局游戏概念。
沙盒游戏模式定义了一个 ToolGun 类(在 GitHub 上查看:服务器与客户端),有助于基于基础工具枪创建新工具。
创建自定义工具枪
要基于沙盒的 ToolGun 创建你自己的工具,你需要创建一个继承自它的新类,并向该类传递一些自定义参数。沙盒在每一端分别定义了 ToolGun,因此你必须在 Client/ 和 Server/ 上都继承它(以下自定义值仅在客户端需要):
MyToolGun = ToolGun.Inherit("MyToolGun", {
-- 生成菜单
name = "My Tool Gun",
image = "package://my-package/Client/MyToolGun.webp",
category = "tool-guns",
-- 工具枪描述,将显示在左上方
description = "施展魔法!",
-- 工具枪教程,将显示在左上方
tutorials = {
{ key = "LeftClick", text = "生成我的实体" },
{ key = "Undo", text = "撤销生成" },
{ key = "ContextMenu", text = "生成设置" },
},
-- 工具枪提示,将每 1 分钟作为通知显示一次
tips = {
"你可以使用这款工具枪来做些了不起的事情!",
"注意不要玩得太嗨了!"
},
-- 工具枪追踪调试设置,使用此工具枪瞄准时将显示在世界中
debug_trace = {
-- 定义使用此工具枪开火时要追踪的碰撞通道
collision_channel = CollisionChannel.WorldStatic | CollisionChannel.WorldDynamic | CollisionChannel.PhysicsBody | CollisionChannel.Vehicle,
-- 瞄准时工具枪是否应在追踪结果上显示十字准星
show_crosshair = false,
-- 瞄准时工具枪是否应在追踪结果上显示预览网格体
show_preview_mesh = true,
-- 预览网格体配置
preview_mesh = "nanos-world::SM_Toaster",
preview_mesh_scale = Vector(1, 1, 1),
preview_mesh_offset = Vector(0, 0, 0),
preview_mesh_rotation = Rotator(0, 0, 0),
preview_mesh_rotation_fixed = false
}
})
name、image 和 category 的值供生成菜单使用,以便在菜单中正确显示。 category 必须是 tools 标签页下的分类之一:tool-guns、spawners 或 constrainers。
其他值 description、tutorials、tips 和 debug_trace 由 ToolGun 使用,以便在使用它时添加自定义教程或行为。这些参数是可选的。
这些值仅在客户端使用,因此在服务器你只需调用 MyToolGun = ToolGun.Inherit("MyToolGun")。
你还可以通过在工具枪类上定义 picked_context_menu_items 表,为你的工具枪在上下文菜单中添加自定义条目。当你捡起工具枪并打开上下文菜单时,这些配置就会出现:

-- 为你的自定义工具枪值定义初始值
MyToolGun.my_custom_value = 123
-- 我的工具枪上下文菜单回调
function MyToolGun.SetMyCustomValue(value)
MyToolGun.my_custom_value = value
-- 你可以在这里对该值做些处理,
-- 例如调用远程事件以通知服务端
end
-- 工具枪上下文菜单条目,将显示在上下文菜单中
MyToolGun.picked_context_menu_items = {
{
id = "my_tool_gun_custom_value",
type = "range",
label = "custom value",
min = 0,
max = 1000,
callback = MyToolGun.SetMyCustomValue,
-- 使用函数,使上下文菜单始终读取当前值
value = function()
return MyToolGun.my_custom_value
end
},
}
你可以参阅上下文菜单文档页面了解如何配置上下文菜单项。
重写工具枪客户端方法
ToolGun 在客户端有一些方法,你可以重写这些方法,以便在本地玩家与其交互时为工具实现自定义行为。
你只需在自定义工具枪类中重写它们即可实现:
-- 当你使用它开火时调用
function MyToolGun:OnLocalPlayerFire(character)
-- 执行某些操作
-- 这里很有用的做法是检测玩家射击的位置,
-- 并将该坐标发送到服务端,以便在该位置生成物体
end
-- 当你捡起它时调用
function MyToolGun:OnLocalPlayerPickUp(character)
-- 执行某些操作
-- 比如启动某种效果或计算
end
-- 当你丢弃它时调用
function MyToolGun:OnLocalPlayerDrop(character)
-- 执行某些操作
-- 丢弃工具时,你很可能需要禁用任何效果或类似的东西
end
工具枪单目标与双目标
我们实现了 ToolGun 的两个子类,分别称为 ToolGunSingleTarget 和 ToolGunDoubleTarget,它们提供了一些额外功能,使创建在开火时需要一个或两个目标的工具枪变得更加简单。
ToolGunSingleTarget
当你想要创建一个在开火时只需要一个目标的工具枪时(例如在射击时于检测位置生成实体的工具枪),此类非常有用。
它使实现变得非常容易,因为你只需重写 OnLocalPlayerTarget 方法,在使用工具枪开火时该方法将携带检测结果被调用,因此你只需将该坐标发送到服务器以在该位置生成物体即可。
MyToolGun = ToolGunSingleTarget.Inherit("MyToolGun")
-- 重写 ToolGunSingleTarget 方法
function MyToolGun:OnLocalPlayerTarget(location, relative_location, relative_rotation, normal, entity)
-- 调用远程事件以在该位置生成物品
self:CallRemoteEvent("SpawnSomething", Reliability.Reliable, location, relative_location, relative_rotation, normal, entity)
end
在服务器端接收并执行操作(始终对客户端发送的内容进行校验):
MyToolGun = ToolGunSingleTarget.Inherit("MyToolGun")
-- 服务端远程事件接收 Player 作为 self 后的第一个参数
function MyToolGun:OnSpawnSomething(player, location, relative_location, relative_rotation, normal, entity)
-- 不要信任客户端:检查玩家是否手持此工具并且靠近该位置
local character = player:GetControlledCharacter()
if (not character or character:GetPicked() ~= self) then return end
if (character:GetLocation():Distance(location) > 5000) then return end
local prop = Prop(location, Rotator(), "nanos-world::SM_Toaster")
-- 允许玩家撤销它
Sandbox.SpawnHistory.AddItemToHistory(player, prop)
end
MyToolGun.SubscribeRemote("SpawnSomething", MyToolGun.OnSpawnSomething)
ToolGunDoubleTarget
当你想要创建一个开火时需要两个目标的工具枪时(例如将两个实体焊接在一起的工具枪),此类非常有用。
你可以重写 OnLocalPlayerTarget 方法,该方法仅在两个目标都已设置时才会被调用,因此你只需将这两个坐标发送到服务端对其执行相应操作即可。
MyToolGun = ToolGunDoubleTarget.Inherit("MyToolGun")
-- 重写 ToolGunDoubleTarget 方法
function MyToolGun:OnLocalPlayerTarget(targeting_first_to, targeting_first_relative_location, targeting_first_relative_rotation, targeting_second_to, targeting_second_relative_location, targeting_second_relative_rotation)
-- 调用远程事件将两个实体焊接在一起
self:CallRemoteEvent("Weld", Reliability.Reliable, targeting_first_to, targeting_second_to, targeting_second_relative_location)
end
服务器端的工作方式与上面的 ToolGunSingleTarget 示例相同:在服务端继承 ToolGunDoubleTarget,并使用 MyToolGun.SubscribeRemote 订阅 "Weld" 远程事件。
示例
你可以在沙盒的 Tools/ 文件夹中找到更多工具示例,包含客户端和服务器端。
现在轮到你来创建带有你专属工具的包了!