
Starky的随机化工具 - 库
游戏中实时随机化纹理的库模组!
查看大图Starky's Randomizer (库) — API 指南

Starky's Randomizer 是一个库模组。它本身不执行任何操作:它公开了一个公共的、无状态的工具类 org.starkymods.plugin.api.TextureRandomizer,该类的功能与游戏内的 Live Painter & Editor mod 随机化器完全相同,可以对纹理进行重新着色和图案化。任何模组(或工具)都可以直接调用它。
📊 匿名计数器: 随机化次数会被匿名统计到上方的公共总数中(不收集个人数据)。
📈 指标: 此模组还使用 HStats 报告匿名的模组使用指标。不收集任何个人数据。
注意:两者均可禁用。
它使用纯 Java/AWT 编写——其签名中不包含任何 Hytale 类型。所有输入和输出均为标准的 java.awt.image.BufferedImage。这是有意为之:结果是一个普通的图像,您可以保存、发送或将其输入到任何您喜欢的流程中。
- 组 / 名称: StarkyMods / Starky's Randomizer
- 版本: 最新版本(例如:"1.0.0")
- API 类: org.starkymods.plugin.api.TextureRandomizer
1. 安装依赖
a) 将其声明为必需依赖,在你的模组的 manifest.json 中:
"Dependencies": {
"StarkyMods:Starky's Randomizer": ">=1.0.0"
}
依赖键为 "Group:Name"(英文冒号分隔,Name 部分包含撇号)。值为一个 semver 范围(、 >=1.0.0、>=1.0.0 <2.0.0 等)。服务器保证 Starky's Randomizer 在你的模组之前加载;如果缺少或版本不兼容,你的模组将在启动时拒绝加载(你永远不会遇到运行时 NoClassDefFoundError)。
b) 编译时依赖它,但不要将其打包,在 build.gradle.kts 中:
dependencies {
implementation(files("$hytaleHome/install/$patchline/package/game/latest/Server/HytaleServer.jar"))
compileOnly(files("$hytaleHome/UserData/Mods/Starky's Randomizer-1.0.0.jar"))
}
使用 compileOnly——这些类在运行时由 Randomizer 插件自身的类加载器提供。如果你使用 implementation 将它们打包到你的 jar 中,你将在自己的加载器下创建重复的类,从而破坏共享类的同一性(LinkageError / ClassCastException)。通过声明必需的依赖,你可以直接调用 API——无需反射:
import org.starkymods.plugin.api.TextureRandomizer;
import org.starkymods.plugin.api.TextureRandomizer.PixelChange;
2. 核心模型 —— API 的思维方式
三个核心思想驱动一切:
输入和输出是 BufferedImage。 你提供一个图像供其读取;它永远不会修改你的输入。
它返回一个"变更列表",而不是一个新的图像。 每个重新着色方法都返回一个 List —— 一个记录 PixelChange(int x, int y, int argb) —— 精确描述哪些像素需要重绘以及需要什么 ARGB 颜色。
你来决定如何处理这个列表。这是最重要的设计点(参见第 4 点): 因为变更就是数据,你可以将它们应用于与你分析的图像不同的图像上。
power(0-100)是单一的强度旋钮。 它控制图案概率、颜色分组粒度、中性色阈值以及重新着色像素偏离原始颜色的程度。 0 = 非常细微,50 = 平衡,100 = 完全替换。
两个始终成立的规则:
- 完全透明的像素(alpha 为 0)永远不会被触及。
- 每次调用都是全新的随机(基于时钟随机种子),因此对同一方法的两次调用会得到两种不同的变体。
3. API 参考
randomizeTexture(BufferedImage image, int power, boolean allowPattern) -> List
整个纹理的完整随机调色板变体。相似的源颜色被分组,以便每个区域都能一致地重新着色。 allowPattern = true 允许随机叠加图案(条纹、人字纹、波浪等)也被混合进来——其概率随 power 增加而增加。 永不返回 null(对于 null/空图像返回空列表)。 这相当于"RANDOMIZE / AUTO-PAINT"和"RANDOMIZE COLORS"。
randomizeColorGroup(BufferedImage image, int power, int rank) -> List
仅随机化一个色调组—— rank 0 = 最大的色调组(按像素数量),rank 1 = 第二大组,依此类推。 这相当于"RANDOMIZE PRIMARY / SECONDARY"。 返回的列表仅包含该组的像素的变更——从不包含其他像素。 如果图像没有这样的主导组,则返回 null(例如,在单色调纹理上请求 rank 1)。
一个"色调组"范围很广:每个接近中性的像素(灰色/白色/黑色)是一个组,彩色像素仅通过一个宽的色相族进行分组——亮度和饱和度被忽略。 因此,所有棕色可以是一个组,所有灰色可以是另一个组,无论光照如何。 (可在库内部通过 TONE_HUE_BUCKETS 和 TONE_NEUTRAL_SATURATION 进行调整。)
addPattern(BufferedImage image, int power, int targetRank) -> List
叠加一个随机生成的图案。 targetRank = -1 = 整个纹理; 0 = 仅主色调组; 1 = 仅次色调组。 如果 targetRank ≥ 0 但不存在该组,则返回 null。 这相当于"ADD PATTERN"。
dominantColorGroup(BufferedImage image, int rank) -> OptionalInt
返回第 rank 个最常见色调组的键,如果没有则返回空。 如果你想使用与随机化器相同的分组来驱动你自己的针对特定区域的效果,可以使用它。
toneGroup(int argb) -> int
单个 ARGB 像素的宽色调组键(参见上面的色调组说明)。 两个具有相同键的像素属于同一个色调。 可用于"这个像素是否是我目标组的一部分?"的检查。
apply(BufferedImage image, List changes) -> int
便捷写入器:通过 setRGB 将每个变更写入 image,跳过越界和无操作像素,并返回实际改变的像素数量。 这是唯一会改变图像的方法——会改变你传递给它的图像。
4. ⭐ 获取纹理(输入),并将结果输出到任何地方
重新着色方法会给你一个 List。 完整的循环是:加载一个 BufferedImage -> 获取变更 -> 应用它们 -> 导出 BufferedImage。 因为变更是纯数据,你应用变更的图像不必与分析的图像相同—— 这就是解锁"仅重置一个组"和"逐步构建"的关键。
4.1 获取 BufferedImage(输入)
从文件或任何流:
BufferedImage img = ImageIO.read(new File("path/to/texture.png"));
// 或从类路径/包资源:
BufferedImage img = ImageIO.read(getClass().getResourceAsStream("/Common/.../Texture.png"));
从原始 PNG 字节(例如,Hytale 通用资产的 blob,或从包/zip 读取的字节):
BufferedImage img = ImageIO.read(new ByteArrayInputStream(pngBytes));
从头创建(程序化):
BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
// 以你喜欢的方式填充 img.setRGB(...)
始终使用 TYPE_INT_ARGB 工作,以确保 Alpha 通道(以及 跳过 alpha 为 0 规则)正常工作。如果你读取了其他类型的图像并想确保安全,请先将其复制到 ARGB 图像中。
4.2 应用变更
最简单的方法——原地重新着色同一图像:
BufferedImage img = ImageIO.read(file);
TextureRandomizer.apply(img, TextureRandomizer.randomizeTexture(img, 50, true));
// img 现在包含随机化后的纹理
将变更应用到不同的图像上(强大的模式)。 分析一个图像,将结果绘制到另一个图像上。 这正是"仅重置主色调组,保留我的其他编辑"的工作方式:
BufferedImage base = loadOriginalImage(); // 原始/基础纹理
BufferedImage current = loadMyEditedImage(); // 你进行中的编辑
// 从 BASE 检测并随机化主色调组,
// 然后将这些变更应用到你的 CURRENT 编辑图像上:
List<PixelChange> changes = TextureRandomizer.randomizeColorGroup(base, 50, 0);
if (changes != null) {
TextureRandomizer.apply(current, changes); // 只有主色调组的像素改变;你编辑的其余部分保持不变
}
- 传递基础图像并应用到当前 -> 重置并随机化仅该组。
- 传递当前图像并应用到当前 -> 逐步构建该组。
- 对于整个纹理,使用 randomizeTexture / addPattern 以相同方式操作。
手动应用(如果你想要自己的撤销/历史层而不是使用 apply()):
for (PixelChange c : changes) {
image.setRGB(c.x(), c.y(), c.argb()); // 在这里记录到你自己的撤销系统中
}
(这正是 Live Painter 所做的——它将每个 PixelChange 映射到自己的文档,以使编辑可撤销。)
4.3 获取随机化纹理(输出)—— 在任何地方应用/生成它
一旦你应用了变更,你就持有了一个普通的 BufferedImage。 从这里开始,它可以去到任何你需要的地方:
将其写入 PNG 文件(例如,写入包文件夹、导出目录或生成资产文件夹):
ImageIO.write(img, "png", new File("output/Texture.png"));
将其转换为 PNG 字节(用于通过网络发送、存储或交给资产系统):
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(img, "png", baos);
byte[] pngBytes = baos.toByteArray();
在 Hytale 中实时使用。 图像/字节现在是一般内容,所以你可以将它们插入到你已使用的任何资产路径中,例如:
生成一个全新的纹理资产: 将 PNG 写入到一个被监控的 Common 包文件夹中,使用一个新的名称(例如 Items/YourMod/MyTex_123.png),并将其注册/发布为一个 CommonAsset,然后从你的物品/方块/模型中引用该名称。新的唯一名称始终是安全的。
全局替换现有纹理: 将 PNG 写在一个现有的通用资产名称上并发布它(CommonAssetModule.sendAsset(...))。 连接的客户端会实时更新;在重新启动时,在资产加载后重新发布它以确保生效。 (这在 Live Painter 中相当于"Replace Original..."。)
应用于一个活动的实体模型: 烘焙新的纹理,将实体的 ModelComponent 指向它,然后 putComponent。
最后这些步骤使用 Hytale 自己的资产/通用资产类,这有意地超出了 Randomizer 库的范围(它保持纯净,以便在任何上下文中工作——服务器模组、构建工具或单元测试)。如果你想看到构建在这些 BufferedImage 结果之上的完整资产发布流程,Live Painter & Editor 是参考消费者。
5. 现成配方
import static org.starkymods.plugin.api.TextureRandomizer.*;
// 1) 图案全随机重绘,原地操作
apply(img, randomizeTexture(img, 60, true));
// 2) 仅重新着色,无图案
apply(img, randomizeTexture(img, 40, false));
// 3) 仅随机化主色调(首先要色)—— 保持其他所有不变
List<PixelChange> p = randomizeColorGroup(img, 50, 0);
if (p != null) apply(img, p); else log("no primary tone group");
// 4) 仅随机化次要色调
List<PixelChange> s = randomizeColorGroup(img, 50, 1);
if (s != null) apply(img, s);
// 5) 在整个纹理上应用图案
apply(img, addPattern(img, 70, -1));
// 6) 仅在主色调组上应用图案
List<PixelChange> pat = addPattern(img, 70, 0);
if (pat != null) apply(img, pat);
// 7) 使用库的分组实现你自己的定向效果
OptionalInt primary = dominantColorGroup(img, 0);
if (primary.isPresent()) {
int key = primary.getAsInt();
for (int y = 0; y < img.getHeight(); y++)
for (int x = 0; x < img.getWidth(); x++) {
int argb = img.getRGB(x, y);
if (((argb >>> 24) & 0xff) != 0 && toneGroup(argb) == key) {
// 对主色调的每个像素执行自定义操作
}
}
}
6. 注意事项和陷阱
除 apply() 外,不会进行任何修改。 所有重新着色方法都读取输入并返回数据;只有 apply() 会写入(写入你提供给它的图像)。
null 表示"不存在该组"。 randomizeColorGroup 和针对组的 addPattern 在请求的色调组不存在时返回 null —— 在应用之前始终进行检查。randomizeTexture 从不返回 null。
Alpha 0 被保留 —— 透明像素被跳过,因此轮廓/边缘保持干净。
随机性按调用计算。 没有种子参数;每次调用都是一个全新的变体。 要重现一个外观,请保留生成的图像,而不是调用本身。
线程安全。 该类是无状态的,方法也是纯的(每个方法都创建自己的 Random),因此不同的线程可以自由调用它——只是不要让两个线程同时修改同一个 BufferedImage。
性能。 每次调用的复杂度为 O(宽 × 高)。纹理很小(16² ... 256²),因此几乎是即时的,但如果你批处理许多大图像,请在主服务器线程之外执行。
感谢下载!
正在加载版本记录…



正在加载评论…
评论在新手盒子客户端中发表,这里同步展示。