HyCodeYourTale

Models

Models

Detailní dokumentace k práci s 3D modely v Hytale.

---

Přehled Architektury

Hytale používá hierarchii tříd pro modely:

ModelAsset (Asset definice v JSON)

Model (Runtime instance s scale, attachments, atd.)

ModelComponent (ECS komponenta pro entity)

---

Model Třída

Z dekompilovaného kódu - hlavní runtime třída pro modely:

public class Model implements NetworkSerializable {
public static final String UNKNOWN_TEXTURE = "textures/Unknown.png";

// Identifikace
private final String modelAssetId;

// Vizuální vlastnosti
private final String model; // Cesta k .blockymodel souboru
private final String texture; // Cesta k textuře
private final String gradientSet; // Gradient set ID
private final String gradientId; // Gradient ID

// Škálování
private final float scale;

// Fyzika a kolize
@Nullable
private final Box boundingBox;
@Nullable
private final Box crouchBoundingBox;
private final PhysicsValues physicsValues;
private final Map detailBoxes;

// Pohled postavy
private final float eyeHeight;
private final float crouchOffset;
private final CameraSettings camera;

// Animace
private final Map animationSetMap;

// Příslušenství
private final ModelAttachment[] attachments;
private final Map randomAttachmentIds;

// Efekty
private final ColorLight light;
private final ModelParticle[] particles;
private final ModelTrail[] trails;

// Fobie (alternativní model pro hráče s fobiemi)
private final Phobia phobia;
private final String phobiaModelAssetId;

// Cache pro síťový packet
private transient SoftReference cachedPacket;
}

Klíčové Vlastnosti

| Vlastnost | Typ | Popis |
|-----------|-----|-------|
| modelAssetId | String | ID assetu v registru |
| model | String | Cesta k .blockymodel souboru |
| texture | String | Cesta k textuře |
| scale | float | Škála modelu |
| boundingBox | Box | Kolizní box |
| eyeHeight | float | Výška očí postavy |
| crouchOffset | float | Offset při dřepu |
| attachments | ModelAttachment[] | Příslušenství modelu |
| animationSetMap | Map | Sety animací |
| particles | ModelParticle[] | Částicové efekty |
| trails | ModelTrail[] | Trail efekty |

---

Vytváření Modelů

Factory Metody

// Unit scale (1:1)
Model model = Model.createUnitScaleModel(modelAsset);

// Unit scale s vlastním bounding boxem
Model model = Model.createUnitScaleModel(modelAsset, customBoundingBox);

// Náhodná škála (z rozsahu definovaného v assetu)
Model model = Model.createRandomScaleModel(modelAsset);

// Konkrétní škála
Model model = Model.createScaledModel(modelAsset, 1.5f);

// Škála s náhodnými attachmenty
Map randomAttachments = modelAsset.generateRandomAttachmentIds();
Model model = Model.createScaledModel(modelAsset, 1.0f, randomAttachments);

// Statický model (bez animací - pro props)
Model model = Model.createStaticScaledModel(modelAsset, 1.0f);

Škálování

Při vytváření modelu s jinou škálou než 1.0 se automaticky škálují:

// Z createScaledModel():
if (scale != 1.0F) {
boundingBox = boundingBox.clone().scale(scale);
eyeHeight *= scale;
crouchOffset *= scale;

if (camera != null) {
camera = camera.clone().scale(scale);
}

if (physicsValues != null) {
physicsValues = new PhysicsValues(physicsValues);
physicsValues.scale(scale);
}

// Škálování particles a trails...
}

---

ModelAsset

Asset definice načtená z JSON:

// Získání assetu z registru
ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset("Player");

// Kontrola existence
if (modelAsset == null) {
modelAsset = ModelAsset.DEBUG; // Fallback na debug model
}

// Generování náhodných attachmentů
Map randomAttachments = modelAsset.generateRandomAttachmentIds();

// Generování náhodné škály
float scale = modelAsset.generateRandomScale();

Asset Vlastnosti

public class ModelAsset {
// Základní
public String getId();
public String getModel();
public String getTexture();

// Fyzika
public Box getBoundingBox();
public float getEyeHeight();
public float getCrouchOffset();
public PhysicsValues getPhysicsValues();

// Vizuální
public String getGradientSet();
public String getGradientId();
public ColorLight getLight();
public CameraSettings getCamera();

// Animace
public Map getAnimationSetMap();

// Efekty
public ModelParticle[] getParticles();
public ModelTrail[] getTrails();

// Attachmenty
public ModelAttachment[] getAttachments(Map randomAttachmentIds);

// Fobie
public Phobia getPhobia();
public String getPhobiaModelAssetId();
}

---

ModelComponent

ECS komponenta pro přiřazení modelu k entitě:

public class ModelComponent implements Component {
private final Model model;
private boolean isNetworkOutdated = true;

// Získání ComponentType
public static ComponentType getComponentType() {
return EntityModule.get().getModelComponentType();
}

public ModelComponent(Model model) {
this.model = model;
}

public Model getModel() {
return this.model;
}

// Pro síťovou synchronizaci
public boolean consumeNetworkOutdated() {
boolean temp = this.isNetworkOutdated;
this.isNetworkOutdated = false;
return temp;
}

@Override
public Component clone() {
return new ModelComponent(this.model);
}
}

Přidání k Entitě

// Vytvoření entity s modelem
Holder holder = EntityStore.REGISTRY.newHolder();

// Přidání pozice
holder.addComponent(
TransformComponent.getComponentType(),
new TransformComponent(position, rotation)
);

// Přidání modelu
ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset("MyModel");
Model model = Model.createUnitScaleModel(modelAsset);
holder.addComponent(
ModelComponent.getComponentType(),
new ModelComponent(model)
);

// Přidání bounding boxu z modelu
if (model.getBoundingBox() != null) {
holder.addComponent(
BoundingBox.getComponentType(),
new BoundingBox(model.getBoundingBox())
);
}

// Spawn do světa
store.addEntity(holder, AddReason.SPAWN);

---

ModelAttachment

Příslušenství modelu (zbraně, oblečení, atd.):

public class ModelAttachment implements NetworkSerializable<...> {
public static final BuilderCodec CODEC;

protected String model; // Cesta k attachment modelu
protected String texture; // Textura
protected String gradientSet; // Gradient set
protected String gradientId; // Gradient ID
protected double weight = 1.0; // Váha pro náhodný výběr

public ModelAttachment(String model, String texture,
String gradientSet, String gradientId,
double weight) {
this.model = model;
this.texture = texture;
this.gradientSet = gradientSet;
this.gradientId = gradientId;
this.weight = weight;
}
}

JSON Definice Attachmentu

{
"Attachments": {
"helmet": [
{
"Model": "models/armor/helmet_iron.blockymodel",
"Texture": "textures/armor/helmet_iron.png",
"Weight": 1.0
},
{
"Model": "models/armor/helmet_gold.blockymodel",
"Texture": "textures/armor/helmet_gold.png",
"Weight": 0.5
}
]
}
}

---

Model.ModelReference

Pro serializaci/deserializaci modelu:

public static class ModelReference {
public static final BuilderCodec CODEC;
public static final ModelReference DEFAULT_PLAYER_MODEL =
new ModelReference("Player", -1.0F, null, false);

private String modelAssetId;
private float scale;
private Map randomAttachmentIds;
private boolean staticModel;

// Konverze zpět na Model
@Nullable
public Model toModel() {
if (this.modelAssetId == null) {
return null;
}

ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset(this.modelAssetId);
if (modelAsset == null) {
modelAsset = ModelAsset.DEBUG;
}

return Model.createScaledModel(modelAsset, this.scale,
this.randomAttachmentIds, null, this.staticModel);
}
}

Použití Reference

// Model na referenci
Model model = ...;
Model.ModelReference reference = model.toReference();

// Reference zpět na model
Model recreatedModel = reference.toModel();

---

PersistentModel Komponenta

Pro perzistenci modelu entity:

// Entity s PersistentModel se automaticky načtou se správným modelem
holder.addComponent(
PersistentModel.getComponentType(),
new PersistentModel(model.toReference())
);

Automatické Systémy

// SetRenderedModel - vytvoří ModelComponent z PersistentModel při loadu
public static class SetRenderedModel extends HolderSystem {
@Override
public void onEntityAdd(Holder holder, AddReason reason, Store store) {
PersistentModel persistentModel = holder.getComponent(persistentModelComponentType);
holder.putComponent(modelComponentType,
new ModelComponent(persistentModel.getModelReference().toModel()));
}
}

// ModelChange - aktualizuje PersistentModel při změně ModelComponent
public static class ModelChange extends RefChangeSystem {
public void onComponentSet(...) {
PersistentModel persistentModel = store.getComponent(ref, persistentModelComponentType);
persistentModel.setModelReference(newComponent.getModel().toReference());
}
}

---

Model Systems

ECS systémy pro práci s modely:

ModelSpawned

Automaticky nastaví BoundingBox při spawnu entity s modelem:

public static class ModelSpawned extends HolderSystem {
@Override
public void onEntityAdd(Holder holder, AddReason reason, Store store) {
Model model = holder.getComponent(modelComponentType).getModel();
Box modelBoundingBox = model.getBoundingBox();

if (modelBoundingBox != null) {
BoundingBox boundingBox = holder.getComponent(boundingBoxComponentType);
if (boundingBox == null) {
boundingBox = new BoundingBox();
holder.addComponent(boundingBoxComponentType, boundingBox);
}

boundingBox.setBoundingBox(modelBoundingBox);
boundingBox.setDetailBoxes(model.getDetailBoxes());
}
}
}

PlayerConnect

Nastaví model hráči při připojení:

public static class PlayerConnect extends HolderSystem {
@Override
public void onEntityAdd(Holder holder, AddReason reason, Store store) {
Player player = holder.getComponent(playerComponentType);
String preset = player.getPlayerConfigData().getPreset();

ModelAsset modelAsset = preset != null
? ModelAsset.getAssetMap().getAsset(preset)
: null;

if (modelAsset != null) {
Model model = Model.createUnitScaleModel(modelAsset);
holder.addComponent(modelComponentType, new ModelComponent(model));
} else {
// Fallback na default Player model
ModelAsset defaultModelAsset = ModelAsset.getAssetMap().getAsset("Player");
if (defaultModelAsset != null) {
Model defaultModel = Model.createUnitScaleModel(defaultModelAsset);
holder.addComponent(modelComponentType, new ModelComponent(defaultModel));
}
}
}
}

UpdateBoundingBox

Aktualizuje bounding box při změně modelu:

public static class UpdateBoundingBox extends RefChangeSystem {
public void onComponentSet(...) {
BoundingBox boundingBox = commandBuffer.getComponent(ref, boundingBoxComponentType);
MovementStatesComponent movementStates = commandBuffer.getComponent(ref, movementStatesComponentType);
updateBoundingBox(newComponent.getModel(), boundingBox, movementStates);
}

protected static void updateBoundingBox(Model model, BoundingBox boundingBox, MovementStates movementStates) {
Box modelBoundingBox = model.getBoundingBox(movementStates);
if (modelBoundingBox == null) {
modelBoundingBox = new Box();
}
boundingBox.setBoundingBox(modelBoundingBox);
}
}

---

Bounding Box a Crouch

Model podporuje různé bounding boxy pro normální stav a dřep:

// Z Model konstruktoru:
this.crouchBoundingBox = boundingBox == null
? null
: new Box(boundingBox.min.clone(),
boundingBox.max.clone().add(0.0, (double)crouchOffset, 0.0));

// Získání správného bounding boxu:
@Nullable
public Box getBoundingBox(@Nullable MovementStates movementStates) {
if (movementStates == null) {
return this.boundingBox;
}
return !movementStates.crouching && !movementStates.forcedCrouching
? this.boundingBox
: this.crouchBoundingBox;
}

---

Animace

Získání Animace

Model model = ...;

// Získání první dostupné animace z preference
String animId = model.getFirstBoundAnimationId("walk", "idle", "default");

// Získání animation set mapy
Map animations = model.getAnimationSetMap();

Animation Sets v JSON

{
"AnimationSets": {
"idle": {
"Animation": "animations/idle.animation",
"Loop": true
},
"walk": {
"Animation": "animations/walk.animation",
"Loop": true,
"BlendTime": 0.2
},
"attack": {
"Animation": "animations/attack.animation",
"Loop": false
}
}
}

---

LoadedAssetsEvent

Reagování na načtení modelů:

@Override
protected void setup() {
getEventRegistry().register(LoadedAssetsEvent.class, ModelAsset.class, event -> {
Map loadedModels = event.getLoadedAssets();

ModelAsset myModel = loadedModels.get("MyCustomModel");
if (myModel != null) {
// Model byl načten nebo změněn - invaliduj cache
this.cachedModel = null;
}
});
}

---

Shrnutí

| Třída | Účel |
|-------|------|
| Model | Runtime instance modelu |
| ModelAsset | Asset definice z JSON |
| ModelComponent | ECS komponenta |
| ModelAttachment | Příslušenství modelu |
| Model.ModelReference | Pro serializaci |
| PersistentModel | Pro perzistenci |

| Factory Metoda | Účel |
|----------------|------|
| createUnitScaleModel() | Model se škálou 1:1 |
| createRandomScaleModel() | Náhodná škála z rozsahu |
| createScaledModel() | Konkrétní škála |
| createStaticScaledModel() | Bez animací (props) |

| Systém | Kdy |
|--------|-----|
| SetRenderedModel | Load entity - vytvoří ModelComponent |
| ModelSpawned | Spawn - nastaví BoundingBox |
| PlayerConnect | Připojení hráče - nastaví model |
| UpdateBoundingBox | Změna modelu - aktualizuje kolize |
| ModelChange | Změna modelu - aktualizuje PersistentModel |

Last updated: 20. ledna 2026