FiveM 实体管理:处理行人、车辆、物体
掌握 FiveM 实体管理。生成、所有权、剔除和清理行人、车辆及物体,保持服务器稳定快速。
Agency Scripts
Agency Scripts 创始人兼首席开发者
理解FiveM中的实体
在FiveM中,实体是游戏世界中存在的任何对象:车辆、行人、道具、拾取物,甚至玩家角色。每个实体都有一个句柄(整数ID),用于通过本地函数与其交互。实体管理是FiveM开发中最基本的技能之一,因为几乎所有脚本都会以某种方式与实体交互。实体管理不当会导致内存泄漏、资源重启后残留的幽灵对象、玩家间不同步,最终导致服务器不稳定。本指南涵盖创建、跟踪和清理实体的正确模式。
生成物体和道具
生成道具的主要native是 CreateObject (或 CreateObjectNoOffset)。在生成任何对象之前,您必须请求模型并等待加载。未先请求模型将导致实体不可见或缺失。创建对象后务必释放模型以释放内存。
-- client/entity_spawner.lua
local function SpawnProp(modelName, coords, rotation, isNetwork)
local model = joaat(modelName)
-- Request the model and wait for it to load
RequestModel(model)
local timeout = 0
while not HasModelLoaded(model) do
Wait(10)
timeout = timeout + 10
if timeout > 5000 then
print(('[ERROR] Model %s failed to load after 5s'):format(modelName))
return nil
end
end
local obj = CreateObject(model, coords.x, coords.y, coords.z, isNetwork, true, false)
if rotation then
SetEntityRotation(obj, rotation.x, rotation.y, rotation.z, 2, true)
end
-- Freeze the object so it does not fall through the ground
FreezeEntityPosition(obj, true)
-- Release the model from memory
SetModelAsNoLongerNeeded(model)
return obj
end
-- Usage
local chair = SpawnProp('prop_chair_01a', vector3(200.0, -800.0, 31.0), nil, false)
生成车辆
车辆生成遵循类似模式,但使用 CreateVehicle 并且对网络所有权、燃油水平和车牌文字有额外考虑。尽可能在服务器端生成车辆,或使用服务器端回调验证客户端的车辆创建请求。
-- client/vehicle_spawner.lua
local function SpawnVehicle(modelName, coords, heading)
local model = joaat(modelName)
RequestModel(model)
while not HasModelLoaded(model) do
Wait(10)
end
local vehicle = CreateVehicle(model, coords.x, coords.y, coords.z, heading, true, false)
-- Basic vehicle setup
SetVehicleOnGroundProperly(vehicle)
SetEntityAsMissionEntity(vehicle, true, true)
SetVehicleHasBeenOwnedByPlayer(vehicle, true)
SetVehicleNeedsToBeHotwired(vehicle, false)
SetVehRadioStation(vehicle, 'OFF')
-- Set fuel if using a fuel system
Entity(vehicle).state:set('fuel', 100.0, true)
SetModelAsNoLongerNeeded(model)
return vehicle
end
实体跟踪与清理
实体管理最关键的是跟踪脚本创建的每个实体,并在资源停止时清理它们。若无适当清理,实体将作为孤儿持续存在游戏世界,浪费内存和渲染资源。使用跟踪表存储所有实体句柄,并注册一个 onResourceStop handler 用于移除它们。
-- client/entity_manager.lua
local ManagedEntities = {}
function RegisterEntity(entity, category)
if not DoesEntityExist(entity) then return end
ManagedEntities[entity] = {
category = category or 'default',
created = GetGameTimer(),
model = GetEntityModel(entity),
}
end
function UnregisterEntity(entity)
if ManagedEntities[entity] then
if DoesEntityExist(entity) then
SetEntityAsMissionEntity(entity, false, true)
DeleteEntity(entity)
end
ManagedEntities[entity] = nil
end
end
function CleanupCategory(category)
for entity, data in pairs(ManagedEntities) do
if data.category == category then
UnregisterEntity(entity)
end
end
end
function CleanupAllEntities()
for entity, _ in pairs(ManagedEntities) do
UnregisterEntity(entity)
end
ManagedEntities = {}
end
-- Critical: Clean up when resource stops
AddEventHandler('onResourceStop', function(resourceName)
if GetCurrentResourceName() ~= resourceName then return end
CleanupAllEntities()
end)
网络实体所有权
在FiveM的网络环境中,每个实体都有一个所有者,即负责模拟其物理和位置的客户端。网络所有权决定哪个玩家客户端控制实体的移动和碰撞。理解和管理网络所有权对于同步实体行为至关重要,尤其是需要远程控制NPC或车辆的脚本。
-- server/ownership.lua
-- Request control of a networked entity
local function RequestEntityOwnership(src, netId)
local entity = NetworkGetEntityFromNetworkId(netId)
if not DoesEntityExist(entity) then return false end
-- Set the requesting player as the owner
SetEntityRoutingBucket(entity, GetPlayerRoutingBucket(src))
return true
end
-- client/ownership.lua
-- Request network control of an entity
local function TakeEntityControl(entity, timeout)
timeout = timeout or 2000
local start = GetGameTimer()
NetworkRequestControlOfEntity(entity)
while not NetworkHasControlOfEntity(entity) do
Wait(100)
NetworkRequestControlOfEntity(entity)
if GetGameTimer() - start > timeout then
return false
end
end
return true
end
-- Usage: Move an entity you need control of
local function MoveEntity(entity, targetCoords)
if TakeEntityControl(entity) then
SetEntityCoords(entity, targetCoords.x, targetCoords.y, targetCoords.z, false, false, false, false)
return true
end
print('[WARN] Could not get control of entity')
return false
end
生成和管理行人
Ped(行人)实体用于 NPC、商店供应商、任务发布者和环境角色。它们需要特殊处理,因为它们具有 AI,可能会漫游、对环境做出反应,或在配置不当时攻击玩家。静态 NPC 应始终冻结并禁用 AI。
-- client/ped_spawner.lua
local function SpawnStaticPed(modelName, coords, heading, scenario)
local model = joaat(modelName)
RequestModel(model)
while not HasModelLoaded(model) do
Wait(10)
end
local ped = CreatePed(0, model, coords.x, coords.y, coords.z - 1.0, heading, false, true)
-- Make the ped static and non-interactive with ambient AI
SetEntityAsMissionEntity(ped, true, true)
SetBlockingOfNonTemporaryEvents(ped, true)
SetPedFleeAttributes(ped, 0, false)
SetPedCombatAttributes(ped, 17, true)
SetPedDiesWhenInjured(ped, false)
SetEntityInvincible(ped, true)
FreezeEntityPosition(ped, true)
-- Play a scenario animation if specified
if scenario then
TaskStartScenarioInPlace(ped, scenario, 0, true)
end
SetModelAsNoLongerNeeded(model)
RegisterEntity(ped, 'npc')
return ped
end
-- Spawn a shop vendor
local vendor = SpawnStaticPed(
's_m_y_ammucity_01',
vector3(22.0, -1105.0, 29.8),
160.0,
'WORLD_HUMAN_STAND_IMPATIENT'
)
实体管理性能提示
- 限制活动实体数。 每个实体都会消耗内存和渲染周期。使用流式系统在玩家接近时加载实体,离开区域时卸载它们。
- 使用对象池。 对于频繁生成和销毁的实体,如弹壳或粒子效果,重用实体句柄,避免不断创建和删除新实体。
- 优先使用客户端实体。 如果实体只需对单个玩家可见(如UI道具或预览对象),请创建为非网络实体以减少服务器负载。
- 始终调用 SetModelAsNoLongerNeeded。 加载的模型会一直保留在内存中,直到显式释放。忘记调用此操作是FiveM脚本中内存泄漏最常见的原因之一。
- 在操作前检查 DoesEntityExist。 实体可能被游戏引擎、其他脚本或不同步删除。修改前务必确认实体仍存在。
- 使用实体状态包存储元数据。 无需维护单独的实体数据查找表,使用
Entity(entity).state直接在实体上存储自定义属性。