hytale-custom-assets
npx skills add https://github.com/mnkyarts/hytale-skills --skill hytale-custom-assets
Agent 安装分布
Skill 文档
Creating Custom Hytale Assets
Complete guide for creating and managing custom assets including models, textures, sounds, and particles.
When to use this skill
Use this skill when:
- Creating asset packs for plugins
- Adding custom textures and models
- Configuring sounds and music
- Creating particle effects
- Setting up animations
- Managing asset inheritance and tags
Asset System Architecture
Hytale uses a hierarchical asset system:
AssetPack
âââ Server/ # Server-side assets
â âââ Content/ # Game content definitions
â âââ BlockTypes/
â âââ Items/
â âââ Entities/
â âââ ...
âââ Client/ # Client-side assets
âââ Models/
âââ Textures/
âââ Sounds/
âââ Particles/
âââ ...
Asset Pack Structure
my-plugin/
âââ assets/
âââ pack.json # Pack metadata
âââ Server/
â âââ Content/
â âââ BlockTypes/
â â âââ custom_block.blocktype
â âââ Items/
â â âââ custom_item.item
â âââ Entities/
â â âââ custom_entity.entity
â âââ Recipes/
â â âââ custom_recipe.recipe
â âââ Sounds/
â âââ custom_soundset.soundset
âââ Client/
âââ Models/
â âââ custom_model.model
âââ Textures/
â âââ blocks/
â â âââ custom_block.png
â âââ items/
â â âââ custom_item.png
â âââ entities/
â âââ custom_entity.png
âââ Sounds/
â âââ custom/
â âââ sound.ogg
âââ Particles/
â âââ custom_particle.particle
âââ Animations/
âââ custom_animation.animation
Pack Metadata
File: pack.json
{
"Name": "MyPlugin Assets",
"Id": "MyPlugin",
"Version": "1.0.0",
"Description": "Custom assets for MyPlugin",
"Author": "Your Name",
"Priority": 100,
"Dependencies": ["Hytale:Core"]
}
Pack Properties
| Property | Type | Description |
|---|---|---|
Name |
String | Display name |
Id |
String | Unique identifier |
Version |
String | Semantic version |
Description |
String | Pack description |
Author |
String | Creator name |
Priority |
Integer | Load order (higher = later) |
Dependencies |
Array | Required packs |
Textures
Texture Formats
| Format | Use Case |
|---|---|
| PNG | Standard textures (recommended) |
| TGA | Textures with alpha |
| DDS | Compressed textures |
Block Textures
Location: Client/Textures/blocks/
custom_block.png # All faces
custom_block_top.png # Top face only
custom_block_side.png # Side faces
custom_block_bottom.png # Bottom face
Texture Size: 16×16, 32×32, 64×64 (power of 2)
Item Textures
Location: Client/Textures/items/
custom_item.png # Inventory icon (32x32)
custom_item_held.png # Held model texture
Entity Textures
Location: Client/Textures/entities/
custom_entity.png # Main texture atlas
custom_entity_glow.png # Emissive layer
custom_entity_normal.png # Normal map
Models
Model Format
Hytale uses a custom JSON-based model format.
File: custom_model.model
{
"Parent": "Hytale/Models/base_entity",
"Textures": {
"main": "MyPlugin/Textures/entities/custom_entity"
},
"Bones": [
{
"Name": "root",
"Pivot": [0, 0, 0],
"Children": [
{
"Name": "body",
"Pivot": [0, 12, 0],
"Cubes": [
{
"Origin": [-4, 0, -2],
"Size": [8, 12, 4],
"UV": [0, 0]
}
],
"Children": [
{
"Name": "head",
"Pivot": [0, 24, 0],
"Cubes": [
{
"Origin": [-4, 0, -4],
"Size": [8, 8, 8],
"UV": [0, 16]
}
]
}
]
}
]
}
],
"Animations": {
"idle": "MyPlugin/Animations/custom_idle",
"walk": "MyPlugin/Animations/custom_walk"
}
}
Model Properties
| Property | Type | Description |
|---|---|---|
Parent |
String | Parent model to inherit |
Textures |
Object | Texture bindings |
Bones |
Array | Bone hierarchy |
Animations |
Object | Animation bindings |
Scale |
Float | Overall scale |
TextureSize |
Array | Texture dimensions [W, H] |
Bone Properties
| Property | Type | Description |
|---|---|---|
Name |
String | Bone identifier |
Pivot |
Array | Rotation pivot point |
Rotation |
Array | Default rotation [X, Y, Z] |
Cubes |
Array | Geometry cubes |
Children |
Array | Child bones |
Mirror |
Boolean | Mirror UV horizontally |
Cube Properties
| Property | Type | Description |
|---|---|---|
Origin |
Array | Position offset |
Size |
Array | Dimensions [W, H, D] |
UV |
Array | Texture UV offset [U, V] |
Inflate |
Float | Expand cube size |
Mirror |
Boolean | Mirror this cube |
Animations
File: custom_animation.animation
{
"Length": 1.0,
"Loop": true,
"Bones": {
"body": {
"Rotation": {
"0.0": [0, 0, 0],
"0.5": [5, 0, 0],
"1.0": [0, 0, 0]
}
},
"leftArm": {
"Rotation": {
"0.0": [0, 0, 0],
"0.25": [-30, 0, 0],
"0.5": [0, 0, 0],
"0.75": [30, 0, 0],
"1.0": [0, 0, 0]
}
},
"rightArm": {
"Rotation": {
"0.0": [0, 0, 0],
"0.25": [30, 0, 0],
"0.5": [0, 0, 0],
"0.75": [-30, 0, 0],
"1.0": [0, 0, 0]
}
}
}
}
Animation Properties
| Property | Type | Description |
|---|---|---|
Length |
Float | Duration in seconds |
Loop |
Boolean | Loop animation |
Bones |
Object | Per-bone keyframes |
Keyframe Channels
| Channel | Type | Description |
|---|---|---|
Rotation |
Array | Rotation [X, Y, Z] degrees |
Position |
Array | Position offset [X, Y, Z] |
Scale |
Array | Scale [X, Y, Z] |
Sounds
Sound Formats
| Format | Use Case |
|---|---|
| OGG | Music, ambient (recommended) |
| WAV | Short sound effects |
Sound Event
File: custom_sound.soundevent
{
"Sounds": [
{
"Path": "MyPlugin/Sounds/custom/sound1",
"Weight": 1.0
},
{
"Path": "MyPlugin/Sounds/custom/sound2",
"Weight": 0.5
}
],
"Volume": {
"Min": 0.8,
"Max": 1.0
},
"Pitch": {
"Min": 0.9,
"Max": 1.1
},
"Category": "Effects",
"Subtitle": {
"en-US": "Custom sound plays"
}
}
Sound Set
File: custom_soundset.soundset
{
"Sounds": {
"break": "MyPlugin/SoundEvents/custom_break",
"place": "MyPlugin/SoundEvents/custom_place",
"step": "MyPlugin/SoundEvents/custom_step",
"hit": "MyPlugin/SoundEvents/custom_hit"
}
}
Block Sound Set
File: custom_blocksound.blocksound
{
"Break": {
"Sound": "MyPlugin/SoundEvents/metal_break",
"Volume": 1.0,
"Pitch": 1.0
},
"Place": {
"Sound": "MyPlugin/SoundEvents/metal_place",
"Volume": 1.0
},
"Step": {
"Sound": "MyPlugin/SoundEvents/metal_step",
"Volume": 0.5
},
"Fall": {
"Sound": "MyPlugin/SoundEvents/metal_fall"
}
}
Sound Categories
| Category | Description |
|---|---|
Master |
All sounds |
Music |
Background music |
Effects |
Sound effects |
Ambient |
Environmental sounds |
Voice |
Voice/dialogue |
UI |
Interface sounds |
Weather |
Weather sounds |
Particles
File: custom_particle.particle
{
"Texture": "MyPlugin/Textures/particles/sparkle",
"MaxParticles": 100,
"EmissionRate": 10,
"Lifetime": {
"Min": 0.5,
"Max": 1.5
},
"Size": {
"Start": 0.2,
"End": 0.0
},
"Color": {
"Start": { "R": 1.0, "G": 0.8, "B": 0.2, "A": 1.0 },
"End": { "R": 1.0, "G": 0.2, "B": 0.0, "A": 0.0 }
},
"Velocity": {
"Min": [-0.5, 1.0, -0.5],
"Max": [0.5, 2.0, 0.5]
},
"Gravity": -0.5,
"Collision": false,
"BlendMode": "Additive"
}
Particle Properties
| Property | Type | Description |
|---|---|---|
Texture |
String | Particle texture |
MaxParticles |
Integer | Maximum active particles |
EmissionRate |
Float | Particles per second |
Lifetime |
Range | Particle lifespan |
Size |
Range/Gradient | Size over lifetime |
Color |
Color/Gradient | Color over lifetime |
Velocity |
Range | Initial velocity |
Gravity |
Float | Gravity effect |
Collision |
Boolean | Collide with blocks |
BlendMode |
Enum | Alpha, Additive, Multiply |
Block Particle
File: custom_blockparticle.blockparticle
{
"Break": {
"Particle": "MyPlugin/Particles/shatter",
"Count": 30
},
"Step": {
"Particle": "MyPlugin/Particles/dust",
"Count": 3
},
"Ambient": {
"Particle": "MyPlugin/Particles/glow",
"Rate": 1,
"Radius": 0.5
}
}
Trails
File: custom_trail.trail
{
"Texture": "MyPlugin/Textures/trails/slash",
"Width": 0.5,
"Length": 10,
"Lifetime": 0.3,
"Color": {
"Start": { "R": 1.0, "G": 1.0, "B": 1.0, "A": 1.0 },
"End": { "R": 1.0, "G": 1.0, "B": 1.0, "A": 0.0 }
},
"BlendMode": "Additive",
"AttachPoint": "weapon_tip"
}
Asset Inheritance
Assets can inherit from parents:
{
"Parent": "Hytale:Stone",
"DisplayName": { "en-US": "Enchanted Stone" },
"LightLevel": 5
}
Inheritance Rules
- Child inherits all parent properties
- Child can override any property
- Nested objects merge (not replace)
- Arrays replace (not merge)
Asset Tags
Categorize assets with tags:
{
"Tags": {
"Category": ["Building", "Decorative"],
"Material": ["Stone"],
"Origin": ["Natural", "Cave"]
}
}
Tag Queries
Used in spawning, loot tables, etc.:
{
"Blocks": {
"MatchAll": ["Material:Stone"],
"MatchAny": ["Category:Natural", "Origin:Cave"],
"Exclude": ["Category:Artificial"]
}
}
Localization
File: lang/en-US.json
{
"item.myPlugin.customItem.name": "Custom Item",
"item.myPlugin.customItem.description": "A special custom item",
"block.myPlugin.customBlock.name": "Custom Block",
"entity.myPlugin.customEntity.name": "Custom Creature"
}
Localization Keys
| Pattern | Example |
|---|---|
item.{plugin}.{id}.name |
item.myPlugin.sword.name |
block.{plugin}.{id}.name |
block.myPlugin.ore.name |
entity.{plugin}.{id}.name |
entity.myPlugin.mob.name |
ui.{plugin}.{key} |
ui.myPlugin.menuTitle |
Registering Assets in Plugin
@Override
protected void setup() {
// Asset pack is auto-registered if manifest.json has:
// "IncludesAssetPack": true
// Listen for asset loading
getEventRegistry().register(
LoadedAssetsEvent.class,
BlockType.class,
this::onBlockTypesLoaded
);
getEventRegistry().register(
LoadedAssetsEvent.class,
Item.class,
this::onItemsLoaded
);
}
private void onBlockTypesLoaded(LoadedAssetsEvent<BlockType> event) {
// Access loaded block types
for (BlockType block : event.getLoadedAssets()) {
getLogger().atInfo().log("Loaded block: %s", block.getId());
}
}
Hot Reloading
Assets support hot-reload during development:
/reload assets
Listen for reload events:
getEventRegistry().register(
AssetStoreMonitorEvent.class,
this::onAssetReload
);
private void onAssetReload(AssetStoreMonitorEvent event) {
if (event.getAssetStore().getAssetClass() == BlockType.class) {
getLogger().atInfo().log("Block types reloaded");
// Re-initialize dependent systems
}
}
Custom Asset Store
Register entirely new asset types:
public class MyCustomAsset implements JsonAssetWithMap<String, DefaultAssetMap<String, MyCustomAsset>> {
public static final AssetBuilderCodec<String, MyCustomAsset> CODEC =
AssetBuilderCodec.builder(MyCustomAsset.class, "MyCustomAsset")
.appendRequired(Codec.STRING.fieldOf("Name"), MyCustomAsset::getName, (a, v) -> a.name = v)
.appendOptional(Codec.INT.fieldOf("Value"), MyCustomAsset::getValue, (a, v) -> a.value = v, 0)
.build();
private String id;
private String name;
private int value;
@Override
public String getId() { return id; }
public String getName() { return name; }
public int getValue() { return value; }
}
// Register in plugin
@Override
protected void setup() {
HytaleAssetStore<String, MyCustomAsset, DefaultAssetMap<String, MyCustomAsset>> store =
new HytaleAssetStore<>(
MyCustomAsset.class,
"MyCustomAssets", // Directory name
".mycustomasset", // File extension
MyCustomAsset.CODEC,
DefaultAssetMap::new
);
getAssetRegistry().register(store);
}
Troubleshooting
Textures Not Loading
- Check file path matches reference
- Verify PNG format and dimensions
- Ensure pack.json is valid
- Check console for loading errors
Models Not Displaying
- Verify model JSON syntax
- Check texture references exist
- Ensure bone hierarchy is valid
- Test with simpler model first
Sounds Not Playing
- Check OGG/WAV format compatibility
- Verify sound event references
- Check volume and category settings
- Ensure sound files exist at path
Asset Inheritance Not Working
- Verify Parent path is correct
- Check parent asset loads first
- Ensure pack dependencies are set
See references/asset-formats.md for detailed format specs.
See references/texture-specs.md for texture requirements.