返回博客
Tutorial3 分钟阅读

FiveM 角色创建界面:界面与脚本指南

设计一个惊艳的 FiveM 角色创建界面。面部控制、服装、背景场景及最佳免费和付费角色创建器。

Agency Scripts

Agency Scripts 创始人兼首席开发者

为什么角色创建在角色扮演中很重要

角色创建是玩家在您的FiveM角色扮演服务器上体验的第一个互动时刻。它为后续一切奠定基调。一个精致、沉浸式的角色创建器告诉新玩家您的服务器专业且值得投入时间。一个简陋或有缺陷的创建器会让他们直接去服务器浏览器寻找替代品。除了第一印象,角色创建定义了玩家整个角色扮演体验中携带的视觉身份。它决定血统混合、面部特征、发型、服装以及姓名和出生日期等传记细节。做好这一步意味着构建一个与GTA V原生外观系统通信的系统,通过直观的NUI界面展示选项,将选择持久化到数据库,并在玩家每次生成时可靠加载。

理解GTA V的行人外观系统

GTA V使用基于遗传的外观系统,角色面部由两个父母模型混合而成。有46种可能的母亲和父亲模型,每个都有独特的面部结构。 SetPedHeadBlendData native 控制混合,接受 0.0 到 1.0 之间的形状和肤色混合值,决定角色与各父母的相似度。除了血统,游戏还通过 SetPedHeadOverlay 涵盖瑕疵、面部毛发、眉毛、老化、化妆和皮肤损伤等功能。每个覆盖层都有自己的不透明度值,并可使用独立颜色 SetPedHeadOverlayColor. 头发通过单独的 SetPedComponentVariation 用于模型和 SetPedHairColor 用于颜色和高亮。眼睛颜色使用 SetPedEyeColor 带有0到31范围的值。理解这些原生函数至关重要,因为你的NUI界面需要将用户友好的滑块和选择器映射到这些特定函数调用。

-- client/appearance.lua
function ApplyAppearance(ped, data)
    -- Heritage blend (mother, father, shape mix, skin mix)
    SetPedHeadBlendData(ped,
        data.mother, data.father, 0,
        data.mother, data.father, 0,
        data.shapeMix, data.skinMix, 0.0,
        false
    )

    -- Face features (-1.0 to 1.0 for each of 20 features)
    for i = 0, 19 do
        SetPedFaceFeature(ped, i, data.features[i] or 0.0)
    end

    -- Overlays (blemishes, beard, eyebrows, aging, makeup, etc.)
    for i = 0, 12 do
        local overlay = data.overlays[i] or {style = 0, opacity = 0.0}
        SetPedHeadOverlay(ped, i, overlay.style, overlay.opacity)
        if overlay.color then
            SetPedHeadOverlayColor(ped, i, overlay.colorType or 1,
                overlay.color, overlay.secondColor or overlay.color)
        end
    end

    -- Hair
    SetPedComponentVariation(ped, 2, data.hair or 0, 0, 2)
    SetPedHairColor(ped, data.hairColor or 0, data.hairHighlight or 0)

    -- Eye color
    SetPedEyeColor(ped, data.eyeColor or 0)
end

构建 NUI 界面

NUI(自然用户界面)是玩家创建角色时交互的HTML、CSS和JavaScript覆盖层。设计良好的角色创建器将流程划分为逻辑步骤:先填写传记信息,再选择血统、面部特征、覆盖层如胡须和化妆、发型,最后是服装。每步应在角色模型上实时预览更新,让玩家准确看到选择效果。使用标签页或向导式布局,避免玩家一次面对数十个选项。连续值如形状混合和面部特征适合用范围滑块,离散选项如发型和服装适合用带缩略图预览的网格选择器。通过 fetch 或NUI消息系统,使ped即时更新。

<!-- nui/index.html (simplified structure) -->
<div id="creator" class="creator-wrapper">
  <div class="steps-nav">
    <button data-step="bio" class="active">Identity</button>
    <button data-step="heritage">Heritage</button>
    <button data-step="features">Features</button>
    <button data-step="overlays">Appearance</button>
    <button data-step="hair">Hair</button>
    <button data-step="clothing">Clothing</button>
  </div>

  <div class="step-content" id="step-heritage">
    <label>Mother</label>
    <input type="range" id="mother" min="0" max="45" value="0">
    <label>Father</label>
    <input type="range" id="father" min="0" max="45" value="0">
    <label>Resemblance (Shape Mix)</label>
    <input type="range" id="shapeMix" min="0" max="100" value="50">
    <label>Skin Tone (Skin Mix)</label>
    <input type="range" id="skinMix" min="0" max="100" value="50">
  </div>
</div>

<script>
document.querySelectorAll('input[type="range"]').forEach(slider => {
  slider.addEventListener('input', () => {
    fetch('https://myresource/updateAppearance', {
      method: 'POST',
      body: JSON.stringify({
        type: slider.id,
        value: parseFloat(slider.value)
      })
    });
  });
});
</script>

角色预览摄像头系统

专用摄像系统对精致的角色创建体验至关重要。玩家需要在调整遗传混合和覆盖层时查看面部特写,查看发型和上身服装时的中景,以及查看裤子和鞋子时的全身视角。实现摄像机控制器,基于当前创建步骤平滑插值预设位置。使用 CreateCam 带有 DEFAULT_SCRIPTED_CAMERA type 和 RenderScriptCams 接管游戏摄像机控制。旋转时,允许玩家拖动鼠标绕角色旋转,通过调整摄像机朝向同时保持对角色的注视。角色上有细微的空闲动画,比如 WORLD_HUMAN_STAND_IMPATIENT 场景,在创建过程中保持角色自然状态,而非僵硬的T字姿势。

-- client/camera.lua
local creatorCam = nil
local camAngle = 0.0

local CamPositions = {
    bio      = {offset = vector3(0.0, 0.9, 0.65), fov = 35.0},
    heritage = {offset = vector3(0.0, 0.7, 0.65), fov = 30.0},
    features = {offset = vector3(0.0, 0.5, 0.68), fov = 22.0},
    overlays = {offset = vector3(0.0, 0.5, 0.68), fov = 22.0},
    hair     = {offset = vector3(0.0, 0.6, 0.72), fov = 25.0},
    clothing = {offset = vector3(0.0, 2.2, 0.30), fov = 45.0},
}

function SetupCreatorCam(ped, step)
    local pos = CamPositions[step] or CamPositions.bio
    local pedCoords = GetEntityCoords(ped)
    local target = pedCoords + vector3(0.0, 0.0, pos.offset.z)

    if not creatorCam then
        creatorCam = CreateCam('DEFAULT_SCRIPTED_CAMERA', true)
        RenderScriptCams(true, true, 500, true, false)
    end

    local rad = math.rad(camAngle)
    local camPos = pedCoords + vector3(
        math.sin(rad) * pos.offset.y,
        math.cos(rad) * pos.offset.y,
        pos.offset.z
    )

    SetCamCoord(creatorCam, camPos.x, camPos.y, camPos.z)
    PointCamAtCoord(creatorCam, target.x, target.y, target.z)
    SetCamFov(creatorCam, pos.fov)
    SetCamActive(creatorCam, true)
end

保存和加载角色数据

所有角色外观数据需要保存在数据库中,以确保在服务器重启后依然存在,并且每次玩家生成时都能加载。将数据库表结构设计为存储完整外观的 JSON 数据块,同时包含名字、姓氏、出生日期、性别和国籍等传记字段。当玩家完成创建并确认角色时,客户端将完整数据对象发送到服务器,服务器验证输入范围后插入数据库。后续登录时,服务器在生成流程中获取角色数据并发送回客户端,客户端调用相同的处理。 ApplyAppearance function 用于创建时。对于多角色服务器,存储角色槽标识符,使每个玩家可以拥有多个外观独立的角色。

-- server/database.lua
-- Using oxmysql for database operations
function SaveCharacter(playerId, charData)
    local identifier = GetPlayerIdentifier(playerId, 0)
    local appearance = json.encode(charData.appearance)

    MySQL.insert([[
        INSERT INTO characters
            (identifier, slot, firstname, lastname,
             dateofbirth, gender, nationality, appearance)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?)
    ]], {
        identifier,
        charData.slot or 1,
        charData.firstname,
        charData.lastname,
        charData.dob,
        charData.gender,
        charData.nationality,
        appearance
    })
end

function LoadCharacter(playerId, slot)
    local identifier = GetPlayerIdentifier(playerId, 0)
    local result = MySQL.single.await([[
        SELECT * FROM characters
        WHERE identifier = ? AND slot = ?
    ]], {identifier, slot})

    if result then
        result.appearance = json.decode(result.appearance)
    end
    return result
end

-- Spawn handler
RegisterNetEvent('character:loaded', function(slot)
    local src = source
    local char = LoadCharacter(src, slot)
    if char then
        TriggerClientEvent('character:applyAppearance', src, char.appearance)
    end
end)

服装集成和组件变体

GTA V中的服装通过组件变体和道具变体管理。组件涵盖躯干、腿部、脚部、配饰和内衣等身体部位,道具则处理可附加物品如帽子、眼镜、耳环和手表。 SetPedComponentVariation native 接受组件 ID、drawable ID、纹理 ID 和调色板 ID。挑战在于男性和女性行人模型的可用 drawable 不同,且某些 drawable 和纹理组合无效。你的 NUI 需要使用 GetNumberOfPedDrawableVariations 以及每个drawable的最大纹理,使用 GetNumberOfPedTextureVariations. 构建你的服装选择器,使其根据所选性别动态填充选项,这样玩家只会看到有效的组合。将服装数据与外观数据一起存储,并在生成流程中使用相同的组件变体本地函数应用。

验证和防滥用措施

切勿信任未经验证的客户端数据。修改过客户端的玩家可能发送超出正常范围的外观数值,可能导致其他客户端崩溃或利用视觉漏洞。保存到数据库前,务必在服务器端验证每个字段。遗传指数必须在 0 到 45 之间,混合值在 0.0 到 1.0 之间,面部特征值在 -1.0 到 1.0 之间,覆盖层索引必须在各覆盖类型的有效范围内。拒绝或限制任何超出范围的值。此外,在创建期间对外观更新事件实施速率限制,防止恶意客户端通过快速连续更改外观来刷屏。更新间隔 100 毫秒的冷却时间对合法玩家无感,但能阻止自动刷屏。最后,将角色创建流程锁定在适当的会话管理下,防止玩家在非预期流程中触发创建事件。

准备好开始了吗?

在我们的商店获取脚本,或加入 Discord 获取支持、更新以及新功能预告。