Skip to content

加入声音、特效与场景氛围 ​

把玩法判定与音效、特效、光照和后处理表现分离,并管理复用生命周期。

什么时候查这篇 ​

已有正确的玩法判定,希望让命中与结算更容易感知时,使用本专题。按当前需求选择小节即可,不必按专题编号顺序读完。

  • 开始前:能区分服务端判定和本地表现;使用真实音频和编辑器预配置特效。
  • 安装与示例范围:短音效可单独试验;特效须先准备名为 HitEffect 的 EffectUnit。PlayHitFeedback 只定义函数,需由已验证的命中事件调用;光照天空后处理另在实验地图运行。
  • 本次要看到:命中能听见/看见反馈,连续播放不被旧任务关闭;没有表现资源时玩法仍能继续。

如果还没有完成可运行的小游戏,先回到主线:09 课。语法卡住时查Lua 速查,运行结果不符时查按现象排错。

你会学到什么 ​

  • 什么时候用 SoundService,什么时候用 SoundUnit。
  • 如何用 EffectUnit 播放一次性特效,并避免特效残留。
  • 如何把命中反馈拆成“判定”和“表现”。
  • 如何用 LightingService、Sky 和后处理做场景氛围。
  • 如何避免表现层代码污染玩法权威逻辑。

表现层不是玩法判定 ​

音效、特效、相机、光照都属于表现层。它们让玩家“看见”和“听见”结果,但不应该替代服务端玩法判定。

text
server 判定命中 / 得分 / 受伤
→ server 更新权威状态
→ server 或 RemoteEvent 通知需要看到表现的客户端
→ client / server 播放音效、特效、相机震动、UI 反馈

如果所有玩家都必须看到同一个世界特效,可以由 server 创建。如果只有本地玩家需要看到,例如命中准星提示、受击红屏、UI 音效,优先放在 client。

SoundService:短音效和全局音量 ​

SoundService 适合播放短促的 2D/3D 音效,例如点击声、命中声、爆炸声,也适合管理音组音量。

Play2D:不带空间位置的声音 ​

示例类别:独立实验;运行端:server;文件:server/main.lua。

lua
local SoundService = game:GetService("SoundService")
if not SoundService then return end

-- Play2D(soundId, volume, speed, playerOrCampRoleId, campRoleId, duration)
-- false 表示不限定具体玩家或阵营;duration > 0 时便于自动结束。
SoundService:Play2D("official://audio/10772", 80, 1, false, false, 2)

2D 音效适合 UI 点击、倒计时提示、任务完成提示。它不随世界距离衰减。

Play3D:在世界位置播放声音 ​

示例类别:独立实验;运行端:server;文件:server/main.lua。

lua
local SoundService = game:GetService("SoundService")
if not SoundService then return end

local hitPos = Vector3(0, 5, 0)

-- Play3D(soundId, position, duration, volume, speed, player)
-- false 表示不限定具体玩家。
SoundService:Play3D("official://audio/10772", hitPos, 2, 90, 1, false)

3D 音效适合爆炸、机关、环境物体等“声音从某个位置传来”的场景。

音组音量 ​

示例类别:独立实验;运行端:server;文件:server/main.lua。

lua
local SoundService = game:GetService("SoundService")
if not SoundService then return end

SoundService:SetGroupVolume("BGM", 50)
SoundService:SetGroupVolume("SFX", 85)

local sfxVolume = SoundService:GetGroupVolume("SFX")
print("[SE Lua Guide] 当前 SFX 音量:", sfxVolume)

SoundService.GroupVolumeChanged:Connect(function(name, volume)
    print("[SE Lua Guide] 音组音量变化:", name, volume)
end)

GroupVolumeChanged 是本地事件,适合更新设置界面上的音量显示。

SoundUnit:持续播放和可控声音实例 ​

如果声音需要循环、暂停、停止、改变衰减距离,使用 SoundUnit 会比一次性 Play2D/Play3D 更清楚。

示例类别:独立实验;运行端:server;文件:server/main.lua。

lua
local World = game:GetService("World")
if not World then return end

local bgm = World:CreateUnit("SoundUnit", {
    SoundId = "official://audio/10772",
    Looped = true,
    Volume = 45,
})

if not bgm or not bgm:IsA("SoundUnit") then
    print("[SE Lua Guide][WARN] SoundUnit 创建失败。")
    return
end

bgm:Play()

-- 需要切换关卡、暂停游戏或关闭音乐时:
-- bgm:Stop()
-- bgm:Destroy()

常见选择:

需求推荐
UI 点击声SoundService:Play2D
命中点爆炸声SoundService:Play3D
背景音乐SoundUnit
持续环境声SoundUnit
玩家设置音量SoundService:SetGroupVolume

EffectUnit:一次性视觉特效 ​

EffectUnit 用来播放视觉特效。本教程使用编辑器中已配置资源的对象:先把特效命名为 HitEffect,再由 Lua 控制位置、时长和可见性。需要更换资源时回到编辑器配置;不要向 Lua 示例补写没有在本页公开 API 对照中确认的资源字段。

示例类别:独立实验;运行端:server;文件:server/main.lua。

lua
local World = game:GetService("World")
local Task = game:GetService("Task")
if not World or not Task then return end

local effect = World:FindFirstChild("HitEffect", true)
if effect and effect:IsA("EffectUnit") then
    local duration = 1.2
    effect:SetPosition(Vector3(0, 5, 0))
    effect:SetDuration(duration)
    effect:SetVisible(true)

    Task:Delay(duration, function()
        if effect and effect.Parent then
            effect:SetVisible(false)
        end
    end)
end

关键规则:

规则原因
特效资源在编辑器配置资源与对象名一起在编辑器确认,代码只控制已经存在的对象。
SetDuration(duration) 设置时长使用 EffectUnit 已公开的方法,不直接写未公开资源字段。
当前版本没有独立 Play()使用 SetVisible(true/false) 控制可见性。
到时显式隐藏SetDuration 不替代本教程的生命周期清理。
SetColor 传 Color(...)不传十六进制字符串。

特效颜色、缩放和绑定 ​

示例类别:独立实验;运行端:server;文件:server/main.lua。

lua
local World = game:GetService("World")
local Task = game:GetService("Task")
if not World or not Task then return end

local effect = World:FindFirstChild("HitEffect", true)
if effect and effect:IsA("EffectUnit") then
    local duration = 2
    effect:SetPosition(Vector3(0, 4, 0))
    effect:SetDuration(duration)
    effect:SetScale(Vector3(1.5, 1.5, 1.5))
    effect:SetColor(Color(255, 80, 40, 255), 100, 0.6)
    effect:SetRate(1.2)
    effect:SetVisible(true)

    Task:Delay(duration, function()
        if effect and effect.Parent then
            effect:SetVisible(false)
        end
    end)
end

需要让特效跟随某个单位时,使用公开的 SetBindData(otherUnit, socket, offset, bindType)。其中:

  • otherUnit 必须是 SpaceUnit。
  • socket 是具体模型资产提供的挂点名;当前 Meta 没有承诺所有模型都存在名为 Root 的通用挂点。
  • offset 是位置偏移。
  • bindType 使用 Enums.EffectBindType.POS / POS_ROT / POS_SCALE / ALL。

只有从目标模型的公开资源说明或编辑器配置中确认挂点名后,才能填写并实测这次绑定。绑定特效适合角色光环、技能蓄力、拾取物发光;一次性命中特效通常只需要 SetPosition,不应为了示例猜一个挂点名。

命中反馈封装 ​

下面的函数只负责表现,不负责判定。判定命中、加分、扣血仍应在服务端玩法逻辑中完成。

运行端:server 或 client,取决于这段表现要给谁看。

示例类别:解释片段;运行端:server。 这里只定义函数,由调用方传入本小节要求的对象后执行,不是完整入口。

lua
local SoundService = game:GetService("SoundService")
local World = game:GetService("World")
local Task = game:GetService("Task")
if not SoundService or not World or not Task then return end

local hitEffect = World:FindFirstChild("HitEffect", true)
local effectGeneration = 0

local function PlayHitFeedback(position)
    SoundService:Play3D("official://audio/10772", position, 2, 90, 1, false)

    if hitEffect and hitEffect:IsA("EffectUnit") then
        local duration = 1.2
        effectGeneration = effectGeneration + 1
        local generation = effectGeneration

        hitEffect:SetVisible(false)
        hitEffect:SetPosition(position)
        hitEffect:SetDuration(duration)
        hitEffect:SetVisible(true)

        Task:Delay(duration, function()
            -- 连续命中时,旧定时器不能关闭较新的特效播放。
            if generation == effectGeneration and hitEffect and hitEffect.Parent then
                hitEffect:SetVisible(false)
            end
        end)
    end

    print("[SE Lua Guide] 命中表现已播放")
end

如果只有命中者本人需要看到更强反馈,可以由 server 发送 RemoteEvent 给该玩家,在 client 本地播放增强版音效、特效、相机震动或 UI 动画。

LightingService:全局光照 ​

LightingService 控制场景环境光和主光。它的颜色字段使用 Color(RGBA 0~255),不是 Color3。

示例类别:独立实验;运行端:server;文件:server/main.lua。

lua
local LightingService = game:GetService("LightingService")
if not LightingService then return end

LightingService.AmbientColor = Color(40, 50, 80, 255)
LightingService.AmbientIntensity = 0.8

LightingService.Color = Color(180, 210, 255, 255)
LightingService.Intensity = 1.5

-- Orientation 是 Vector3,表示角度制欧拉角,不是 CFrame。
LightingService.Orientation = Vector3(45, -30, 0)
LightingService.ShadowFade = 0.6

常见氛围:

氛围调整方向
白天主光强,环境光偏亮。
夜晚主光弱,环境光偏蓝紫。
危险区域环境光偏红,后处理略提高对比度。
梦幻区域Bloom 强一点,天空模板更鲜艳。

Sky:天空必须挂到 LightingService 下 ​

Sky 是可定制天空单元。新项目优先用 Sky,通过 Enums.SkyTemplate.* 切换模板。它必须挂到 LightingService 下才生效;移除或销毁 Sky 后会恢复默认天空。切换关卡氛围时,要清楚当前是谁创建了天空,避免多个系统互相覆盖。

示例类别:独立实验;运行端:server;文件:server/main.lua。

lua
local LightingService = game:GetService("LightingService")
if not LightingService then return end

local sky = game:CreateUnit("Sky", {
    Parent = LightingService,
    Template = Enums.SkyTemplate.Night,
    Orientation = Vector3(0, 25, 0),
    YOffset = 0,
})

if sky and sky:IsA("Sky") then
    print("[SE Lua Guide] 夜晚天空已设置:", sky.Template)
else
    print("[SE Lua Guide][WARN] Sky 创建失败。")
end

game:CreateUnit 与 World:CreateUnit 的父节点语义不同:前者不会自动设置 Parent,未显式传入时对象保持“游离”(Detached);后者会把新对象放到 World 下。

后处理:挂到 LightingService 下才生效 ​

Bloom、ColorGrading、DepthOfField、AmbientOcclusion 等后处理都继承自 BasePostEffect,核心开关是 Enabled。通常通过 game:CreateUnit 创建,并挂到 LightingService 下。

示例类别:独立实验;运行端:server;文件:server/main.lua。

lua
local LightingService = game:GetService("LightingService")
if not LightingService then return end

local bloom = game:CreateUnit("BloomEffect", {
    Parent = LightingService,
    Enabled = true,
    Intensity = 0.35,
    Threshold = 1.4,
    Size = 0.8,
})

local grading = game:CreateUnit("ColorGradingEffect", {
    Parent = LightingService,
    Enabled = true,
    Saturation = 0.9,
    Contrast = 1.08,
    Temperature = -0.1,
})

if not bloom or not grading then
    print("[SE Lua Guide][WARN] 后处理创建不完整,请检查当前渲染配置。")
end

后处理会影响整体画面,属于高影响表现。建议先做轻量调整,再在目标设备上检查性能和可读性。

常见错误 ​

错误:只设置 Duration,不负责隐藏 ​

本专题复用的是编辑器中预配置的 EffectUnit,不应把它自动销毁。SetDuration 只设置特效时长;教程仍使用 Task:Delay 在到时后显式 SetVisible(false),保证实例回到可复用状态。

错误:连续播放时让旧定时器关闭新特效 ​

同一个预配置特效可能在旧 Delay 到期前再次播放。保存 generation,并只允许最新一轮定时器隐藏实例;否则第二次播放会被第一次的清理提前截断。

错误:把 Color3 传给 LightingService ​

LightingService.AmbientColor 和 LightingService.Color 使用 Color(...),RGBA 范围 0~255。

错误:Sky 或后处理没有挂到 LightingService ​

Sky、BloomEffect、ColorGradingEffect 等需要 Parent = LightingService 才能生效。

错误:表现逻辑决定玩法结果 ​

声音和特效可以由 client 播放,但得分、扣血、奖励发放等结果必须由 server 判定。

练习任务 ​

  1. 写一个 PlayHitFeedback(position),同时播放 3D 音效和一次性特效。
  2. 给特效设置颜色、缩放和播放速率。
  3. 用 LightingService 切换一个夜晚氛围。
  4. 创建一个 Sky 并挂到 LightingService 下。
  5. 思考:哪些表现只给本地玩家看,哪些需要所有玩家看到?

本专题验收标准 ​

  • [ ] 我知道 SoundService 和 SoundUnit 的适用场景。
  • [ ] 我能用 Play2D / Play3D 播放短音效。
  • [ ] 我能定位编辑器中已配置的 EffectUnit,并用公开方法控制时长和可见性。
  • [ ] 我知道 LightingService 使用 Color(...) 和 Vector3(...)。
  • [ ] 我知道 Sky 和后处理要挂到 LightingService 下。
  • [ ] 我能把玩法判定和表现反馈拆开。

本专题产物 ​

  • 一个 PlayHitFeedback(position) 封装,至少包含 3D 音效和一次性特效。
  • 一张表现层截图或运行效果记录,确认特效、音效、光照或天空实际可见/可听。
  • 一份表现归属说明,标出哪些反馈只给本地玩家,哪些需要所有玩家看到。

本专题 API 对照 ​

把结果带回小游戏 ​

先确认本页“本次要看到”的现象,再把选中的功能接到已有模块。保留原有入口和清理逻辑,只迁入需要的部分;不要把多个试验入口拼在一起。

返回主线对应步骤,或去专题导航选择下一项能力。新的代码尚未完成目标地图实测时,记录为待验证,不把编译通过当作行为通过。