Starsector Nihilism Guidance
首页
GitHub 项目
首页
GitHub 项目
  • 首页
  • 基础及舰船相关
    • MOD 骨架和入口文件
    • 制作一艘基本舰船
    • 制作一艘航母与战机
    • 制作一艘相位舰
    • 制作火炮、光束与导弹
    • 为舰船加入内置武器、船插与战机
    • 补充描述文本
  • 无需入门的代码修改
    • 以船插为例初步熟悉代码
    • 制作完整船插
    • 制作最小战术系统
    • 为武器加入命中与光束特效
  • 势力与生涯星系
    • 注册一个势力
    • 配置势力舰队
    • 制作 MOD 入口类的最基础适配
    • 制作一个生涯星系
    • 补完描述文本
  • 战役与无代码对象
    • 制作测试战役装配
    • 为模拟战与标题画面加入装配
    • 为舰船加入外观风格
    • 制作一艘模块船
    • 制作一种星体类型
    • 制作一个自定义实体
  • 音频资源与播放
    • 准备 OGG 音频资源
    • 注册音频资源
    • 为游戏对象加入音频
  • Java 工程化
    • 确认 Java 与编译依赖
    • 迁移代码文件到 src
    • 配置 Gradle 工程
    • 构建 JAR 并声明产物
  • 全局服务
    • 制作 MOD 入口类
    • 使用 Global 访问游戏服务
    • 读取设置
    • 调用声音播放器
    • 记录和定位 MOD 日志
  • 战斗运行时
    • 读取战斗引擎状态
    • 读取战斗对象
    • 制作每帧战斗插件
    • 修改舰船数值
    • 配置武器脚本
    • 配置战斗监听器
    • 制作分层渲染插件
  • 战斗内容扩展
    • 详解舰船插件
    • 详解战术系统
    • 详解武器动画
  • 生涯世界与基础服务
    • 读取生涯世界状态
    • 构建生涯地点
    • 配置生涯势力
    • 放置生涯实体
  • 生涯舰队与舰队成员
    • 生成生涯舰队
    • 管理舰队中的成员
    • 舰队状态的重算过程
    • 维护舰队成员状态
    • 读取并修改舰船装配
  • 市场与商品
    • 创建生涯市场
    • 读写货舱内容
    • 注册市场商品
    • 实现专用子市场
    • 制作专用产业
    • 注册特殊物品
    • 实现市场条件
  • 人物与技能
    • 配置人物基础
    • 让人物承担角色
    • 制作角色技能
  • 生涯脚本与情报事件
    • 编写生涯逐帧脚本
    • 监听生涯事件
    • 选择生涯插件
    • 管理舰队活动情报
  • 酒吧事件与任务
    • 组织生涯交互对话
    • 实现对话插件
    • 接入酒吧事件
    • 构建阶段任务
    • 接入酒吧任务
  • Rules 系统、命令与内存
    • 组织 Rules 规则表
    • 编排 Rules 对话
    • 接入 Rules 生涯场景
    • 编写 Rules 命令
    • 使用 MemoryAPI 保存状态
  • 文件读取与写入
    • 读取 MOD 内文本
    • 读取 MOD 内 JSON 文件
    • 读取 MOD 内 CSV
    • 读写 common 数据
    • 实现本地化机制
  • 常用依赖库
    • 接入常用依赖库
    • 使用 LazyLib 几何工具
    • 使用 LazyLib 查询战场
    • 使用 LazyLib 保存公共 JSON
    • 注册 GraphicsLib 光照数据
    • 创建 GraphicsLib 动态光源
    • 制作 GraphicsLib 纹理映射
    • 配置 MagicLib 自动尾迹
    • 创建 MagicLib 运行时尾迹
    • 配置 MagicLib 数据赏金
    • 使用 MagicLib 辅助生涯开发
    • 使用 MagicLib 战斗小工具
  • 可选依赖与 Nexerelin
    • 接入可选依赖
    • 登记 Nexerelin 势力与外交配置
    • 配置 Nexerelin 的两种星区开局
    • 配置 Nexerelin 战略 AI 与扩张规模
    • 登记 Nexerelin 地面战单位与产业
    • 为 Nexerelin 特遣队添加命名与事件响应
    • 制作即时生效的 LunaLib 设置面板
  • 着色器与渲染
    • 使用 Java OpenGL
    • 使用 GLSL 进阶
    • 使用 VBO 和 VAO 保存顶点
    • 使用 SSBO 传递参数
    • 使用实例化进行绘制
    • 选择 BoxUtil 渲染方案

为游戏对象加入音频

声音已经登记,本节将它们用于都林自动炮、突击填弹器、GEAR_TECH 舰船风格、进入生涯的界面反馈与齿轮工业市场。各入口使用同一组已登记 ID,分别观察触发、持续与停止时的结果。

本节预期阅读时间:~15 分钟。

本节预期操作时间:~20 分钟。

学习目标

完成后,您将能够:

  • 在武器和系统定义中引用声音;
  • 在舰船风格中选择分档声音集;
  • 在回调中播放 UI 提示并保持循环;
  • 通过音乐插件选择市场曲目;
  • 按资源、登记与调用位置定位播放问题。

前置知识

  • [C5-S1] 的四个 OGG 文件;
  • [C5-S2] 的音效、分档集与音乐登记;
  • [C1-S5] 的都林武器定义;
  • [C2-S3] 的突击填弹器效果;
  • [C3-S3] 的 GearInModPlugin 入口与回调;
  • [C3-S4] 的 Prime 市场;
  • [C4-S3] 的 GEAR_TECH 风格。

为都林设置开火声音

打开 data/weapons/GearIn_durin.wpn,把两种安装形式的开火字段设置为本节音效,局部片段如下:

"fireSoundOne": "GearIn_weapon_fire",
"fireSoundTwo": "GearIn_weapon_fire"

武器开火时按这些 ID 取得声音,实际资源由上一节的注册条目提供。文件仍使用第一章的武器 ID 与弹体,当前只更换播放资源。

命中音另由武器与冲击设置决定。需要关闭默认冲击音时配置 noImpactSounds;自定义位置播放还可以用 Global.getSoundPlayer().playSound(...),完整方法与位置参数由 [C7-S4] 讲解。

为突击填弹器设置三段声音

在 GearIn_assaultfeed.system 中添加或替换:

"useSound": "GearIn_weapon_fire",
"loopSound": "GearIn_system_loop",
"deactivateSound": "GearIn_weapon_fire"

useSound 在激活时播放,deactivateSound 在停用时播放,loopSound 用于持续阶段。三个字段对应系统的三个播放时机,当前先使用已登记资源,最终设计可以换成自己的系统素材。

循环播放需要持续续期。 定义字段由系统控制播放;由效果脚本控制时,持续调用放在每帧 apply 中,通过 playLoop 传入稳定的舰船对象和当前坐标、速度。

下面是效果类 apply 末尾的局部片段,添加在原来的属性修改之后:

ShipAPI ship = (ShipAPI) stats.getEntity();
if (ship != null && effectLevel > 0f) {
    Global.getSoundPlayer().playLoop("GearIn_system_loop", ship, 1f,
            effectLevel, ship.getLocation(), ship.getVelocity());
}

创建前的数值对象可能没有战斗舰船,实际播放使用可用的舰船实体,因此这段代码在取得舰船后才调用播放器。effectLevel 同时作为音量,让声音随系统进入和退出变化。同一个声音 ID 与同一实体标记对应持续声源,后续调用延续这个声源,停止调用后自然淡出。

给舰船风格选择声音集

在 hull_styles.json 的 GEAR_TECH 中修改两个字段:

{
    "GEAR_TECH": {
        "engineLoopSet": "GearIn_engine_loop",
        "engineAccelerateSet": "GearIn_engine_accelerate"
    }
}

这是局部合并示意,当前风格的颜色与其他声音字段保留。两个字段分别引用上一节的引擎循环与加速声音集,再按船体等级选择资源和音量。其他护盾、排散等声音仍使用当前已有的原版 ID。

一个风格引用一组明确的声音集,比逐艘船重复填写素材更容易维护。新增船体继续引用 GEAR_TECH 时,也会沿用它的声音配置,替换该声音集中的资源便可统一调整。

在进入生涯时播放 UI 反馈

在既有 data/scripts/gearin/GearInModPlugin.java 的方法区添加下面的局部回调:

@Override
public void onGameLoad(boolean newGame) {
    Global.getSoundPlayer().playUISound("GearIn_ui_feedback", 1f, 1f);
}

Global 已由前文入口类导入。onGameLoad 在进入生涯时调用,这条播放语句随回调执行一次;以后给按钮或选项配声音,同样放到对应动作回调中。

playUISound 接收 ID、音高、音量,使用上一节独立准备的双声道资源。界面提示按调用触发,因此这里提供播放参数即可,不需要实体位置。

为齿轮工业市场选择音乐

创建 data/scripts/gearin/campaign/GearInMusicPlayerPlugin.java,使用后面的完整类。它继承原版音乐选择逻辑,只为所属势力为 gear_industry 的市场返回 GearIn_market_music,其他市场交由基类继续选择。

在 data/config/settings.json 的 plugins 对象中登记:

{
    "plugins": {
        "musicPlugin": "data.scripts.gearin.campaign.GearInMusicPlayerPlugin"
    }
}

musicPlugin 是原版音乐插件入口键,游戏通过它选择当前配置的音乐插件。这个入口服务整个音乐选择流程,本例因此保留基类的其他行为,只调整目标市场。项目中多个功能需要修改音乐选择时,在同一个插件中组织对应条件。

原版市场选择方法也读取势力 music 映射和市场状态,本节通过类返回已登记的音乐 ID。返回值确定曲目,sounds.json 的 music 分类提供资源,source 与 file 再组合为实际文件路径。

完整最小例

系统类替换现有 data/scripts/gearin/shipsystems/GearInAssaultFeedSystem.java,音乐类保存到上面的 campaign 目录。入口 UI 回调、武器和风格的局部修改按前文接入。

完整成品:GearInAssaultFeedSystem
package data.scripts.gearin.shipsystems;

import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.MutableShipStatsAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.impl.combat.BaseShipSystemScript;

public class GearInAssaultFeedSystem extends BaseShipSystemScript {

    public static final float SPEED_BONUS = 50f;
    public static final float MANEUVER_BONUS = 200f;
    public static final float ROF_BONUS = 1f;
    public static final float FLUX_REDUCTION = 50f;

    @Override
    public void apply(MutableShipStatsAPI stats, String id, State state, float effectLevel) {
        stats.getBallisticRoFMult().modifyMult(id, 1f + ROF_BONUS * effectLevel);
        stats.getBallisticWeaponFluxCostMod().modifyMult(id,
                1f - FLUX_REDUCTION * 0.01f * effectLevel);
        stats.getMaxSpeed().modifyFlat(id, SPEED_BONUS * effectLevel);
        stats.getAcceleration().modifyPercent(id, MANEUVER_BONUS * effectLevel);
        stats.getDeceleration().modifyPercent(id, MANEUVER_BONUS * effectLevel);
        stats.getTurnAcceleration().modifyFlat(id, 30f * effectLevel);
        stats.getTurnAcceleration().modifyPercent(id, MANEUVER_BONUS * effectLevel);
        stats.getMaxTurnRate().modifyFlat(id, 15f * effectLevel);
        stats.getMaxTurnRate().modifyPercent(id, 100f * effectLevel);

        ShipAPI ship = (ShipAPI) stats.getEntity();
        if (ship != null && effectLevel > 0f) {
            Global.getSoundPlayer().playLoop("GearIn_system_loop", ship, 1f,
                    effectLevel, ship.getLocation(), ship.getVelocity());
        }
    }

    @Override
    public void unapply(MutableShipStatsAPI stats, String id) {
        stats.getBallisticRoFMult().unmodify(id);
        stats.getBallisticWeaponFluxCostMod().unmodify(id);
        stats.getMaxSpeed().unmodify(id);
        stats.getAcceleration().unmodify(id);
        stats.getDeceleration().unmodify(id);
        stats.getTurnAcceleration().unmodify(id);
        stats.getMaxTurnRate().unmodify(id);
    }

    @Override
    public StatusData getStatusData(int index, State state, float effectLevel) {
        if (index == 0) {
            return new StatusData("+" + (int) (SPEED_BONUS * effectLevel) + " top speed", false);
        }
        if (index == 1) {
            return new StatusData("improved maneuverability", false);
        }
        if (index == 2) {
            return new StatusData("ballistic flux use -" + (int) (FLUX_REDUCTION * effectLevel) + "%", false);
        }
        if (index == 3) {
            return new StatusData("ballistic rate of fire +" + (int) (ROF_BONUS * effectLevel * 100f) + "%", false);
        }
        return null;
    }
}
完整成品:GearInMusicPlayerPlugin
package data.scripts.gearin.campaign;

import com.fs.starfarer.api.campaign.econ.MarketAPI;
import com.fs.starfarer.api.impl.MusicPlayerPluginImpl;

public class GearInMusicPlayerPlugin extends MusicPlayerPluginImpl {

    @Override
    protected String getMarketMusicSetId(Object param) {
        MarketAPI market = (MarketAPI) param;
        if ("gear_industry".equals(market.getFactionId())) {
            return "GearIn_market_music";
        }
        return super.getMarketMusicSetId(param);
    }
}

验证方案

  1. 保存声音资源、登记与对象引用,重启游戏,检查音频加载。
  2. 进入生涯,观察 UI 提示,再给工匠安装都林并进入模拟战。
  3. 开火、移动与进入零幅能加速,分别比较武器、引擎循环和加速声音。
  4. 激活突击填弹器,观察激活、持续和停用声音,结束后确认循环淡出。
  5. 进入 Prime 市场,听取市场曲目;再进入其他势力市场,核对原版选择继续生效。
  6. 调整一个条目的 volume,重启后以同一场景比较,并核对相关日志。

预期结果:

  • 进入生涯时播放 UI 反馈,武器开火采用 GearIn_weapon_fire。
  • GEAR_TECH 移动与加速采用两个自有声音集,分档与当前船体对应。
  • 系统持续阶段保持循环,结束后停止续期。
  • 齿轮工业市场播放 GearIn_market_music,其他市场沿用原版选择。
  • 声音参数修改后实际听感随新值变化。
  • 日志可以定位无效 ID、分类与资源路径错误。

故障分析

  • 开火没有声音:武器字段 ID 未登记或不同。

    • 解决方案:核对 GearIn_durin.wpn 字段与 sounds.json 自有键。
  • 循环只响一下:脚本未持续调用或声源对象每次不同。

    • 解决方案:在效果回调中用同一舰船对象持续调用 playLoop。
  • 引擎声音不符:GEAR_TECH 引用了不同或未登记的声音集。

    • 解决方案:核对 engineLoopSet、engineAccelerateSet 及分档结构。
  • UI 声音空间表现不符:播放入口或资源声道不同。

    • 解决方案:使用 GearIn_ui_feedback 双声道与 playUISound。
  • 市场音乐未选择:musicPlugin 未登记、类加载错误或市场所属势力不同。

    • 解决方案:核对插件完整类名与 gear_industry 归属,再进入市场。
  • 音乐资源未播放:声音 ID 未处于 music 分类,或 source/file 组合错误。

    • 解决方案:核对注册结构和当前资源路径,重启后比较。

总结

本节将已登记声音用于实际对象和回调。武器和系统读取定义字段,舰船风格选择分档集,UI 反馈由动作回调播放,市场音乐由选择插件返回曲目 ID。

资源声道、注册分类与调用时机共同决定播放结果。测试时分别触发每个入口,记录声音何时开始、何时保持、何时结束,再调整素材和参数。

建议参考项

  • data/config/sounds.json:普通音效、分档集与 music 分类。
  • data/config/settings.json:plugins.musicPlugin 入口。
  • data/config/hull_styles.json:风格声音集字段。
  • data/weapons/:武器开火与冲击声音字段。
  • data/shipsystems/:系统激活、持续和停用声音字段。
  • com.fs.starfarer.api.SoundPlayerAPI:UI、位置和循环播放。
  • com.fs.starfarer.api.BaseModPlugin:onGameLoad 回调。
  • com.fs.starfarer.api.impl.MusicPlayerPluginImpl:市场音乐选择与原版行为。
在 GitHub 上编辑此页
上次更新: 2026/10/14 12:25
贡献者: 1847905557
上一页
注册音频资源