FiveM 宠物与伙伴系统:脚本忠诚的伙伴
为您的FiveM服务器添加宠物和伙伴。跟随AI、喂养、商店、自定义模型以及适用于QBCore和ESX设置的顶级宠物脚本。
Agency Scripts
Agency Scripts 创始人兼首席开发者
宠物作为活生生的角色扮演伙伴
宠物为角色扮演服务器带来了独特的情感维度,这是其他系统无法复制的。玩家带着他们的狗在城市中漫步,狗在旁边小跑,创造出丰富每次互动的视觉个性。宠物伙伴将诸如去商店这样的平凡活动转变为沉浸式时刻。当其他玩家注意到你的德国牧羊犬或询问你的猫时,它们成为自然的对话开端。除了社交元素,宠物还可以担任功能性角色,如警戒犬提醒主人入侵者、协助搜查的警犬单位、医院角色扮演中的情感支持动物,或帮助追踪猎物的狩猎伙伴。构建宠物系统需要处理ped的生成与销毁、AI跟随行为、动画整合、饥饿和快乐等需求模拟、跨会话持久化,以及通过自然手势和语音命令让主人控制宠物的交互指令。
可用动物模型和配置
GTA V 包含多个动物行人模型,适合作为伴侣。狗是最通用的,拥有诸如 a_c_shepherd 适用于德国牧羊犬, a_c_husky 适用于哈士奇, a_c_poodle 适用于贵宾犬, a_c_rottweiler 适用于罗威纳犬, a_c_retriever 适用于寻回犬,和 a_c_chop 用于标志性的Chop模型。猫使用 a_c_cat_01. 对于异国选项,有兔子与 a_c_rabbit_01 甚至包括带有 a_c_crow. 每种宠物类型应在共享文件中配置不同的行为参数。狗紧跟并能以中速伴随车辆奔跑。猫更独立,偶尔短距离游荡后返回。鸟类可通过实体附着停留在玩家肩膀上。定义每种宠物的跟随距离、移动速度倍率、闲置动画、交互动画和特殊能力,打造各物种独特个性。
-- shared/config.lua
Config = {}
Config.PetTypes = {
shepherd = {
model = 'a_c_shepherd',
label = 'German Shepherd',
price = 5000,
followDist = 2.5,
runDist = 8.0,
maxSpeed = 7.0,
category = 'dog',
abilities = {'guard', 'fetch', 'search'},
idleAnims = {'sit', 'lie_down', 'sniff'},
},
husky = {
model = 'a_c_husky',
label = 'Husky',
price = 6000,
followDist = 3.0,
runDist = 10.0,
maxSpeed = 8.0,
category = 'dog',
abilities = {'fetch', 'howl'},
idleAnims = {'sit', 'lie_down', 'play'},
},
cat = {
model = 'a_c_cat_01',
label = 'Cat',
price = 2000,
followDist = 4.0,
runDist = 6.0,
maxSpeed = 5.0,
category = 'cat',
abilities = {'purr', 'hunt_rodent'},
idleAnims = {'sit', 'groom', 'sleep'},
},
poodle = {
model = 'a_c_poodle',
label = 'Poodle',
price = 4000,
followDist = 2.0,
runDist = 6.0,
maxSpeed = 5.5,
category = 'dog',
abilities = {'fetch', 'trick'},
idleAnims = {'sit', 'beg', 'spin'},
},
rottweiler = {
model = 'a_c_rottweiler',
label = 'Rottweiler',
price = 7000,
followDist = 2.0,
runDist = 8.0,
maxSpeed = 7.5,
category = 'dog',
abilities = {'guard', 'attack', 'intimidate'},
idleAnims = {'sit', 'lie_down', 'bark'},
},
}
生成与跟随行为
任何宠物系统的核心是跟随AI,使动物自然地跟随主人。当玩家召唤宠物时,使用 CreatePed 带有适当模型。使用 SetEntityAsMissionEntity 并使用关系组配置它以对所有者友好。跟随行为使用客户端循环,不断计算宠物与所有者之间的距离。当距离超过跟随阈值时,使用任务让宠物跑向所有者稍后侧的位置,使用 TaskFollowToOffsetOfEntity此native自动处理路径规划,使宠物绕过障碍物,在人行道上行走并避开交通。当主人停止移动且宠物达到跟随距离时,播放坐下或躺下等闲置动画。如果主人进入车辆并开走,若车辆类型合适则将宠物传送入车,否则在主人下车时消失宠物并重新生成,以防宠物在地图上丢失。
-- client/pet_behavior.lua
local activePet = nil
local petData = nil
function SpawnPet(petType, name)
if activePet then DespawnPet() end
local config = Config.PetTypes[petType]
if not config then return end
local ped = PlayerPedId()
local coords = GetOffsetFromEntityInWorldCoords(ped, 1.5, 1.5, 0.0)
RequestModel(config.model)
while not HasModelLoaded(config.model) do Wait(10) end
activePet = CreatePed(28, config.model,
coords.x, coords.y, coords.z, 0.0, true, false)
SetEntityAsMissionEntity(activePet, true, true)
SetBlockingOfNonTemporaryEvents(activePet, true)
SetPedCanBeTargetted(activePet, false)
SetPedCanRagdoll(activePet, false)
-- Set relationship so pet doesn't attack owner
local playerGroup = GetHashKey('PLAYER')
local petGroup = GetHashKey('PET_COMPANION')
AddRelationshipGroup('PET_COMPANION')
SetPedRelationshipGroupHash(activePet, petGroup)
SetRelationshipBetweenGroups(0, petGroup, playerGroup)
SetRelationshipBetweenGroups(0, playerGroup, petGroup)
petData = {type = petType, name = name, config = config}
-- Start follow loop
CreateThread(PetFollowLoop)
end
function PetFollowLoop()
while activePet and DoesEntityExist(activePet) do
local ped = PlayerPedId()
local petCoords = GetEntityCoords(activePet)
local ownerCoords = GetEntityCoords(ped)
local dist = #(petCoords - ownerCoords)
if dist > 50.0 then
-- Teleport if too far
SetEntityCoords(activePet,
ownerCoords.x, ownerCoords.y, ownerCoords.z)
elseif dist > petData.config.runDist then
-- Run to owner
TaskFollowToOffsetOfEntity(activePet, ped,
0.0, -1.5, 0.0, petData.config.maxSpeed,
-1, 1.0, true)
elseif dist > petData.config.followDist then
-- Walk to owner
TaskFollowToOffsetOfEntity(activePet, ped,
0.0, -1.5, 0.0, 2.0, -1, 1.0, true)
else
-- Idle nearby
if not IsEntityPlayingAnim(activePet, 'creatures@dog@amb@', 'sit_enter', 3) then
if math.random() > 0.95 then
PlayIdleAnimation()
end
end
end
Wait(500)
end
end
宠物需求与亲密系统
需求系统赋予宠物持续责任和深度。追踪饥饿、快乐和健康,数值介于0到100,随真实时间衰减。宠物生成时,饥饿每5分钟减少1点。快乐减少较慢,每10分钟1点,但主人抚摸、玩接物或喂零食时显著增加。健康保持100,除非饥饿低于20,届时开始下降。饥饿降至零,宠物变得无精打采,动作缓慢且表现悲伤动画。健康过低需兽医治疗恢复。玩家通过库存中的食物物品喂养宠物。不同食物恢复不同饥饿值:基础宠物粮恢复30点,高级粮恢复60点,零食恢复10点但快乐提升15点。亲密度追踪主人与宠物的累计关系,通过积极互动增加,忽视则减少。较高亲密度解锁特殊互动并提升宠物对指令的响应。
-- server/pet_needs.lua
local petStates = {}
function InitPetState(ownerId, petId)
petStates[ownerId] = {
petId = petId,
hunger = 100,
happiness = 80,
health = 100,
bond = 0,
lastFed = os.time(),
lastPet = os.time(),
}
end
-- Decay loop runs every 60 seconds
CreateThread(function()
while true do
Wait(60000)
local now = os.time()
for ownerId, state in pairs(petStates) do
-- Hunger decay: 1 point per 5 minutes
local minutesSinceFed = (now - state.lastFed) / 60
state.hunger = math.max(0,
100 - math.floor(minutesSinceFed / 5))
-- Happiness decay
local minutesSincePet = (now - state.lastPet) / 60
state.happiness = math.max(0,
math.min(100, state.happiness -
math.floor(minutesSincePet / 10)))
-- Health decays when starving
if state.hunger < 20 then
state.health = math.max(0,
state.health - 1)
end
-- Sync to client
local player = GetPlayerFromIdentifier(ownerId)
if player then
TriggerClientEvent('pet:syncState', player, state)
end
end
end
end)
RegisterNetEvent('pet:feed', function(foodItem)
local src = source
local id = GetPlayerIdentifier(src, 0)
local state = petStates[id]
if not state then return end
local foodValues = {
petfood = {hunger = 30, happiness = 5},
premium_food = {hunger = 60, happiness = 10},
pet_treat = {hunger = 10, happiness = 15},
}
local food = foodValues[foodItem]
if not food then return end
-- Remove item from inventory
local removed = exports['ox_inventory']:RemoveItem(src, foodItem, 1)
if not removed then return end
state.hunger = math.min(100, state.hunger + food.hunger)
state.happiness = math.min(100, state.happiness + food.happiness)
state.lastFed = os.time()
state.bond = state.bond + 1
TriggerClientEvent('pet:syncState', src, state)
TriggerClientEvent('pet:feedAnimation', src)
end)
交互命令与动画
为玩家提供丰富的命令集以与宠物互动。环形菜单或命令系统应提供坐下、停留、跟随、过来、躺下、握手、取回和说话等选项。每个命令都会触发宠物角色使用 GTA V 内置的生物动画字典对应的动画。坐下命令使用的动画来自 creatures@dog@amb@ 带有sit_enter和sit_loop动画片段。躺下使用类似的环境动画。对于取回,生成一个小球道具,使用抛物线轨迹扔出,指派宠物跑到球落点,用附着物拾起并带回给主人。speak命令触发使用的吠叫音频 PlayPedAmbientSpeechNative. 对于抚摸,玩家角色播放跪下动画的同时,宠物播放快乐摇尾巴动画。这些互动不仅仅是装饰;每次互动都会增加幸福感和亲密度,奖励那些积极与宠物互动而非将其视为被动跟随者的玩家。
持久化与宠物店集成
宠物需要跨服务器会话持久存在,这样玩家在每次重启时不会失去他们的伙伴。将宠物数据存储在数据库中,包括主人标识符、宠物类型、自定义名称、当前需求值、亲密度等级以及任何解锁的技巧或能力。当主人登录时,查询他们的宠物数据,并通过命令提供生成宠物的选项,或者如果他们上次断开连接时宠物处于激活状态,则自动生成。通过位于合理地图位置的游戏内宠物商店整合宠物获取。宠物商店NUI显示可用宠物类型及其模型预览区域、价格和描述。购买后,玩家为新宠物命名,宠物生成在玩家旁边。考虑添加宠物收养中心作为替代,价格更低但宠物类型随机,增加慈善角色扮演角度。对于服务器经济,宠物还可能需要定期开销,如兽医访问以恢复健康、美容以获得外观加成,以及训练课程解锁新指令动画,创造一个与更广泛经济系统整合的真实宠物拥有成本。
-- SQL schema
CREATE TABLE IF NOT EXISTS pets (
id INT AUTO_INCREMENT PRIMARY KEY,
owner_id VARCHAR(64) NOT NULL,
char_slot INT DEFAULT 1,
pet_type VARCHAR(32) NOT NULL,
pet_name VARCHAR(64) NOT NULL,
hunger INT DEFAULT 100,
happiness INT DEFAULT 80,
health INT DEFAULT 100,
bond INT DEFAULT 0,
tricks JSON DEFAULT '[]',
is_active TINYINT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_owner (owner_id, char_slot)
);
-- server/persistence.lua
function SavePet(ownerId, charSlot, state)
MySQL.update([[
UPDATE pets SET
hunger = ?, happiness = ?, health = ?,
bond = ?, is_active = 1, updated_at = NOW()
WHERE owner_id = ? AND char_slot = ?
]], {
state.hunger, state.happiness, state.health,
state.bond, ownerId, charSlot
})
end
function LoadPet(ownerId, charSlot)
return MySQL.single.await([[
SELECT * FROM pets
WHERE owner_id = ? AND char_slot = ?
AND is_active = 1
]], {ownerId, charSlot})
end
警犬整合与特殊角色
适用于执法角色扮演,将宠物系统扩展为完整的K-9单位集成。警犬获得普通宠物无法拥有的特殊能力。搜索命令使K-9嗅探附近车辆或建筑,并通过吠叫和坐在来源旁边来警示可配置的违禁物品。通过在发出搜索命令时检查附近玩家或车辆后备箱的库存中是否有标记物品来实现此功能。追踪命令使K-9跟踪气味轨迹,您可以通过创建逃跑嫌疑人的位置面包屑轨迹并指派警犬跟随来实现。逮捕命令使K-9冲向目标并播放攻击动画,为警员提供非致命的嫌疑人制服选项。将这些能力限制为拥有警察职业和K-9处理员认证的玩家。K-9应穿戴警用背心,使用ped组件变体或道具附件以视觉上区别于民用宠物。此专业化在警察部门层级中创造了一个独特且备受追捧的职位。