Modelle und Assets¶
Alles, was du im Spiel siehst, ist ein Modell (.mdl). Es besteht aus Meshes (.msh plus .msh.blob) mit Materialien (.mtl), und die Materialien verweisen auf Texturen (.dds, .tga, .hdr). Das Basisspiel bringt 4.473 Modelle und 6.806 Materialien mit. Modelle, Mesh-Indexdateien, Materialien und entpackte Animationen (.ani) sind Lua-Textdateien mit einer Funktion data(). Binär sind nur die Mesh-Daten in .msh.blob und die Texturen (Resource types). Unter den entpackten Spieldateien, aus denen diese Seiten zitieren, gibt es keine .msh-, .msh.blob- oder .ani-Dateien. Ihr Format stammt deshalb allein aus dem Wiki.
Modelle importierst und bearbeitest du mit dem Model Editor, der zum Spiel gehört. Wie man mit ihm arbeitet (Einrichtung, Namensregeln beim FBX-Import, Metadaten- und Material-Editor, Prüfung, Icons), steht auf einer eigenen Seite: Model Editor.
Dateinamen und Pfade¶
Datei- und Ordnernamen schreibst du klein und ohne Leerzeichen, Textdateien speicherst du als UTF-8 ohne Byte Order Mark (siehe Dateiverweise im Inhalt). Ein Modell verweist meist relativ zu sich selbst auf seine Meshes und Materialien (msh/... und mat/... in Ordnern neben der .mdl), ein Material relativ zu sich auf seine Texturen (tex/...). Die absoluten Formen (/, ::/ für Dateien des Basisspiels, <modId>::/) erklärt dieselbe Seite. Das Wartehäuschen der Bushaltestelle unten nutzt mat/... für die eigenen Teile und /assets/stations/street/... für Teile, die es mit anderen Haltestellen teilt. Ein Mod, der diese Basisteile wiederverwendet, schreibt ::/assets/stations/street/....
Eine Modelldatei¶
Die Funktion data() einer .mdl-Datei gibt eine Tabelle mit den Schlüsseln boundingInfo, collider, lods, metadata und version zurück. version = 2 kennzeichnet das TF3-Format und unterscheidet es vom alten Modellformat aus Transport Fever 2 (Model definition). Hier das Wartehäuschen base/content/stations/street/street/small_stops/small_new.mdl (gekürzt):
function data()
return {
boundingInfo = {
bbMax = { 3.67032, 1.30928, 2.8806, },
bbMin = { -3.60838, -1.08571, -0.181345, },
},
collider = {
params = {
halfExtents = { 3.63935, 1.1975, 1.53097, },
},
transf = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, },
type = "BOX",
},
lods = {
{
node = {
children = {
{
materials = { "mat/bus_era_c_glass.mtl", },
mesh = "msh/shelter_era_c_left_1_post_glass_lod0.msh",
name = "shelter_era_c_left_1_post_glass",
transf = { 1, 0, 0, 0, 0, -1.62921e-07, -1, 0, 0, 1, -1.62921e-07, 0, 2.09323, 0.397267, 1.36608, 1, },
},
{
materials = { "/assets/stations/street/mat/streetstation_1_transparent.mtl", "/assets/stations/street/mat/streetstation_1.mtl", },
mesh = "/assets/stations/street/msh/bus_stop_sign_era_c_1_lod0.msh",
name = "bus_stop_sign_era_c_1",
transf = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -3.34, 0.9, 0, 1, },
},
-- ...
},
name = "RootNode",
transf = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, },
},
visibleFrom = 0,
visibleTo = 50,
},
-- weitere LODs mit visibleFrom/visibleTo 50-250 und 250-850
},
metadata = {
lightSourceList = { ... },
},
version = 2,
}
end
Bounding Box und Collider¶
boundingInfo ist der Quader um das ganze Modell, angegeben als Abstände vom Modellursprung (bbMin, bbMax). Der Renderer entscheidet damit, ob das Modell im Bild ist. Laut Wiki soll der Quader deshalb auch den Bereich der Lichter des Modells einschließen, sonst tauchen sie am Bildrand plötzlich auf. Der Model Editor berechnet ihn per Knopfdruck neu.
collider regelt Kollisionen zwischen Modellen. Das Wiki nennt vier Typen:
type |
Form |
|---|---|
"MESH" |
die Geometrie des Mesh selbst, params = { } |
"BOX" |
ein Quader der doppelten Größe von params.halfExtents, verschoben um transf |
"CYLINDER" |
ein Zylinder der doppelten Größe von params.halfExtents, verschoben um transf |
"POINT_CLOUD" |
die Punkte in params.points (relativ zum Ursprung); transf wird ignoriert |
Die Dateien des Basisspiels verwenden BOX (4.464 Collider), CYLINDER (96) und NONE (41). Auch das Collider-Format für Konstruktionen, ColliderUtilSerialized.Collider, akzeptiert NONE. Kein Basismodell nutzt MESH oder POINT_CLOUD, und das Wiki erwähnt NONE nicht.
Detailstufen¶
lods ist eine Liste von Detailstufen (LODs). Jeder Eintrag hat einen Baum aus Knoten (node) und einen Entfernungsbereich von visibleFrom bis visibleTo in Metern. Mindestens ein LOD ist Pflicht. Das Wartehäuschen hat drei, mit getrennten Meshes _lod0, _lod1 und _lod2. Pro LOD können zwei weitere Schlüssel vorkommen:
textureLodBase = truemarkiert den ersten LOD, ab dem texturierte Materialien an die Stelle der übrigen treten, zum Beispiel bei Gebäuden, die ab LOD 1 auf gebackene Texturen umschalten. 616 Basismodelle setzen ihn.staticsteht im Beispiel des Wikis (static = false) und in 82 Basismodellen. Das Wiki erklärt ihn nicht.
Die Leistungstipps des Wikis für LODs: Teile die Zahl der Dreiecke pro LOD etwa durch vier, ziele im letzten LOD auf ein paar hundert Dreiecke und halte die Zahl der Meshes, Materialien und sortierten (transparenten) Materialien pro LOD niedrig. Typische Fahrzeuge des Basisspiels haben 20.000 bis 50.000 Dreiecke, die größten bis zu 100.000 (guidelines). Weitergehende Regeln zu Draw Calls stehen unter Best practice.
Knoten¶
Ein Knoten hat einen name, eine 4×4-Matrix transf aus 16 Zahlen (relativ zum Elternknoten) und optional mesh, materials, animations und children. Die Regeln aus dem Wiki:
materialsnennt pro Submesh des Mesh ein.mtl. Das Schild oben braucht zwei. Die Anzahl muss passen. Der Model Editor meldet eine Abweichung und repariert die Gruppe.- Über
namesprechen die Metadaten den Knoten an (childId,group,meshId,loadIndicator). Der Name muss innerhalb eines LOD eindeutig sein, und der entsprechende Knoten in jedem anderen LOD sollte gleich heißen. - Meshes mit Skinning (Figuren, Tiere, Faltenbälge, manche Brücken- und Tunnelteile) verwenden
skinundskinMaterialsstattmeshundmaterials. Ihrechildrensind Knochen mitnameundtransf. 303 Basismodelle sind so aufgebaut.
Ein Fahrzeugmodell verschachtelt Knoten für bewegliche Teile. Der Dampflastwagen in base/content/vehicle/truck/steam_lorry_univ/steam_lorry_univ/steam_lorry_univ.mdl hat einen Knoten body neben einem Gruppenknoten, dessen Kind g1 wiederum w1 enthält.
Animationen¶
animations an einem Knoten ordnet Ereignisnamen Animationen zu. Transformator-Skripte (siehe transformatorConfig weiter unten) lösen die Ereignisse über ihren Namen aus. Es gibt drei Formen:
animations = {
attack = { params = { id = "ani/attack/root.ani", }, type = "FILE_REF", },
open = {
forward = true, -- false spielt die Keyframes rückwärts ab
params = {
keyframes = {
{ rot = { 0, 0, 0, }, time = 0, transl = { 0, 0, 0, }, },
{ rot = { 0, 0, 0, }, time = 1200, transl = { 0.75, 0.055, 0, }, },
},
origin = { 0, 0, 0, },
},
type = "KEYFRAME",
},
-- "KEYFRAME_MATRIX": Keyframes mit time und einem transf aus 16 Zahlen
},
Zeiten zählen in Millisekunden ab dem Start. Das Wiki empfiehlt FILE_REF, damit Modelldateien klein bleiben, und das Basisspiel hält sich daran: 539 Basismodelle nutzen FILE_REF, 6 nutzen KEYFRAME, keines KEYFRAME_MATRIX. Eine .ani-Datei gibt times und eine Liste transfs mit einer Matrix pro Zeitpunkt zurück. Beide Listen müssen gleich lang sein, und die erste Zeit muss 0 sein.
Im Beispiel des Wikis steht als Kommentar zu id „relative to res/models/animation/“. Das ist die Ordnerstruktur von Transport Fever 2. Die Dateien des Basisspiels verweisen relativ zum Modell auf Animationen, etwa id = "ani/die/cougar_root.ani" in base/content/animal/animal/cougar/cougar.mdl.
Meshes¶
Ein Mesh besteht aus zwei Dateien mit gleichem Namen: cube_lod_0.msh ist ein Textindex, cube_lod_0.msh.blob enthält die binären Vertex- und Indexdaten (Mesh definition). Die Indexdatei gibt zwei Listen zurück:
function data()
return {
subMeshes = { ... }, -- ein Eintrag pro Submesh, jeder bekommt in der .mdl ein Material
vertexAttr = { ... }, -- wo jedes Vertex-Attribut im Blob liegt
}
end
Der Blob hat zwei Abschnitte. Im ersten liegen die Vertex-Attribute als 4-Byte-Floats. Für jedes Attribut nennt die Indexdatei den offset, die Zahl der Floats pro Element (numComp) und die Zahl der Elemente (count). Der zweite Abschnitt enthält pro Submesh eine Liste von Indizes auf diese Elemente, ebenfalls über Offset und Anzahl angegeben. Die Attribute heißen position, normal, tangent, uv0, uv1, jointWeights, offset, positionAmbient und attrOffsetLOD. Ein Mesh hat nur die, die es braucht. Manche Materialtypen setzen bestimmte Attribute voraus: _UV1_-Materialien brauchen uv1, Skinning-Materialien brauchen Joint Weights. Fehlt ein Attribut, meldet der Model Editor „requires missing mesh attribute“.
Meshes schreibt niemand von Hand. Der Model Editor erzeugt sie beim Import einer .fbx-Datei.
Modell-Metadaten¶
metadata legt fest, was ein Modell im Spiel ist: ein Fahrzeug, ein Baum, eine Person, ein Teil eines Bahnhofs. Jeder Block ist optional. Über alle .mdl-Dateien des Basisspiels gezählt, kommen diese Blöcke am häufigsten vor:
| Block | Dateien | Block | Dateien |
|---|---|---|---|
lightSourceList |
703 | transportVehicle |
311 |
transformatorConfig |
540 | particleSystem |
291 |
soundConfig |
514 | landVehicle |
288 |
availability |
471 | cargoModel |
242 |
description |
469 | versioning |
201 |
emissions |
449 | railVehicle |
168 |
transportNetworkProvider |
402 | loadIndicator |
142 |
extent |
376 | roadVehicle |
120 |
categoryList |
357 | tree |
90 |
cost |
351 | colorConfig |
85 |
seatProvider |
343 | airVehicle, waterVehicle |
42, 28 |
maintenance |
322 | person, rock, animal |
38, 23, 15 |
Die Teal-Definition ModelMetadata in api/tealdef/api/type.d.tl („Contains all metadata of a model. A specific metadata might be nil.“) typisiert nur einen Teil davon: Animal, Description, Category, CategoryList, MenuCategory, Availability, SoundConfig, Maintenance, Emission/Emissions, VehicleEngine, TransportVehicle, Cost, LandVehicle, WaterVehicle, AirVehicle, Person und Tree. Blöcke wie lightSourceList, particleSystem oder loadIndicator stehen in den Dateien, aber nicht in diesem Record. Die eigene Liste der Metadatenfelder des Model Editors mit Anzeigenamen und Werttypen liegt in base/content/model_editor/model_editor/meta_metadata.lua.
Das Wiki beschreibt die Blöcke, die die meisten Modelltypen gemeinsam haben, auf der Modellseite und die fahrzeugspezifischen unter Vehicles. Die Metadaten eines Fahrzeugs am Beispiel des Dampflastwagens (gekürzt):
metadata = {
availability = { yearFrom = 1905, yearTo = 1945, },
cost = { price = -1, },
description = {
description = _("VEHICLE_TRUCK_STEAM_LORRY_UNIV_DESCRIPTION"),
name = _("VEHICLE_TRUCK_STEAM_LORRY_UNIV_NAME"),
},
emissions = { noise = { score = 15, }, pollution = { score = 18, }, },
landVehicle = {
engines = { { power = 14, tractiveEffort = 5, type = "STEAM", }, },
topSpeed = 6.944,
weightEmpty = 1000,
weightMaxPayload = 12000,
},
maintenance = { lifespan = 14610, runningCosts = -1, },
-- in einem Mod: "::/vehicle/truck/shared/sound/truck_old.snd"
soundConfig = { soundSet = { name = "/vehicle/truck/shared/sound/truck_old.snd", }, },
transformatorConfig = {
skipFromLod = 2,
-- in einem Mod: "::/vehicle/shared/default_road.trf"
transformator = { name = "/vehicle/shared/default_road.trf", },
},
transportVehicle = {
carrier = "ROAD",
compartments = {
{
loadConfigs = {
{
cargoEntry = {
capacity = 40,
cargoTypeSet = { cargoClassesIncluded = { "UNIVERSAL", }, },
loadIndicator = "bay_0",
seats = { },
},
toHide = { },
},
},
},
},
engineTransportModes = { "TRUCK", },
filterTags = { "default", },
transportModes = { "TRUCK", },
},
},
Die Verschachtelung compartments → loadConfigs → cargoEntry entspricht ModelMetadata.TransportVehicle. cargoEntry.loadIndicator = "bay_0" verweist auf den Eintrag metadata.loadIndicator.configs.bay_0.
Häufige Blöcke¶
| Block | Was er festlegt |
|---|---|
description |
name und description (beide in _() für die Übersetzung) für den Fahrzeugladen und die Infofenster. In Konstruktionen werden sie nicht verwendet. |
availability |
yearFrom, yearTo; fehlt ein Wert oder ist er 0, gibt es kein Anfangs- oder Endjahr |
cost |
price; -1 schaltet die automatische Preisberechnung ein, ein fehlender price gilt als 0. priceScale multipliziert den berechneten Preis. |
maintenance |
runningCosts (-1 für automatisch) mit runningCostScale; lifespan in Vierteltagen bei normaler Geschwindigkeit (365 × 4 pro Jahr, die 14610 des Lastwagens sind also zehn Jahre); maintenanceScale für den Verschleiß (bei 1,0 verliert das Fahrzeug seine ganze Wartung in einem Zehntel seiner Lebensdauer) |
extent |
bbMin/bbMax wie bei der Bounding Box, von der Simulation genutzt, etwa für die Fahrzeuglänge beim Kuppeln |
cameraConfig |
positions, jeweils mit group (Knotenname), transf und fov, für die Cockpitkamera, die sonst am ersten Platz der Besatzung sitzt |
loadIndicator |
wie Gütermodelle angezeigt werden, siehe unten |
labelList |
dynamische Textbeschriftungen, siehe unten |
lightSourceList |
Lichtquellen, siehe unten |
particleSystem |
Emitter für Rauch, Dampf, Staub und Flammen, siehe unten |
transformatorConfig |
das Transformator-Skript, das Animationen und Partikel steuert, siehe unten |
Die automatischen Kosten kommen aus den Basisskripten. ModelMetadataUtil in base/content/base/base/model_metadata_util.lua ersetzt einen negativen price durch round(maintenanceCost * 6 * (priceScale or 1)) und negative runningCosts durch round(maintenanceCost * (runningCostScale or 1)). In einem Satz des Wikis heißt das Feld runningCost. Die Dateien und das Codebeispiel des Wikis selbst schreiben runningCosts. maintenanceScale kommt in keinem Basismodell vor.
Ladeanzeigen¶
loadIndicator.configs ist eine Tabelle benannter Anzeigen (bay_0, slots_0, ...), auf die ein cargoEntry über den Namen verweist. Jede gehört zu einer dieser drei Arten:
cargoBay: ein Quader (bbMin,bbMax, relativ zum KnotenchildId), der sich mit Gütermodellen füllt.type = "DISCRETE"legtcargoFormatswie"SMALL"oder"BIG"in einem RastergridSizeaus (x, y und optional Schichten in z) und skaliert sie nachsizePolicy("STRETCH","STRETCH_HEIGHT_SCALEY","BEST_FIT"oder ohne Skalierung).type = "LEVEL"zeigt eine Schüttgutoberfläche, deren Höhe von der Ladung abhängt. Dazu gehörtsizePolicy = "STRETCH".cargoSlots: einecapacityund eine Listeconfigurations. Eine Konfiguration ist eine Liste von Füllstufen von leer bis fast voll, und jede Stufe nennt Slot-Indizes ausloadIndicator.slots. Bei mehreren Konfigurationen wird eine zufällig gewählt. Jeder Slot hat einegroup(Knoten), eine Listemodels, aus der zufällig gewählt wird, einetransfund einerandomId. Slots mit gleicherrandomIdwählen denselben Modellindex. Slot-Modelle sind echte.mdl-Dateien oder ein Verweis auf ein Güterformat wie"#RECT_11x2_5".cargoLanes: für statische Modelle.nodesaus Position, Tangente und Breite bilden Bahnen mit Gütern inSMALL/BIG, dazu optionalrenderDistance.
Beschriftungen¶
labelList.labels setzt dynamischen Text auf ein Modell. Eine Beschriftung hat einen Knoten childId, eine transf an der unteren linken Ecke und eine size (Breite, Höhe; sie bestimmt auch die Schriftgröße). type wählt den Text: "LINE_NAME", "NEXT_STOP", "NAME", "COMPANY_NAME", "STATION_NAME", "CUSTOM" (aus einem labelText in der Konstruktion) oder "NONE". Weitere Schlüssel:
| Schlüssel | Werte |
|---|---|
color, alpha, alphaMode |
RGB 0 bis 1; 0 bis 1; "CUTOUT", "BLEND", "NONE" |
renderMode |
"EMISSIVE" für LCD-artige Anzeigen, sonst "STD" |
alignment, verticalAlignment |
"LEFT"/"CENTER"/"RIGHT"; "BOTTOM"/"CENTER"/"TOP" |
fitting |
"NONE" (läuft über), "CUT", "SCALE" |
nLines |
Zahl der Zeilen |
filter, params |
"NONE", "NUMBER" oder "CUSTOM" mit params.expr (regulärer Ausdruck), params.replace (\\0, \\1, ...) und für NEXT_STOP offset und relative |
font |
nur die eingebauten Schriften Lato und Noto; andere bringen das Spiel zum Absturz |
Lichtquellen¶
lightSourceList.lights enthält Lichter, die am Knoten aus meshId hängen und über transf platziert werden. Wird der Elternknoten skaliert, skaliert das Licht mit. Eine Animation, die ein Mesh ausblendet, schaltet also auch sein Licht aus.
| Schlüssel | Bedeutung |
|---|---|
type |
"SPOT" (radius, angleMin, angleMax), "POINT" (radius), "CYLINDRICAL" und "CHEAP_CYLINDRICAL" (length, radius), "PARALLEL" (length, width, height) |
mode |
"LIGHT", "ABSORBER" (dunkelt ab), "DISCARD" (schneidet Meshes weg, an Tunnel- und Treppeneingängen) |
color, strength |
RGB 0 bis 1, Intensität |
mask, invertMask |
Bitmaske gegen light_receiver.lightMask des Materials (1 Gelände, 2 außen, 4 Wasser, 8 Partikel, 32 innen). Außenlichter nutzen mask = 0 mit invertMask = true, Innenlichter von Fahrzeugen 32 mit invertMask = false. |
backFaceLit |
beleuchtet auch Flächen, die vom Licht abgewandt sind |
fromTime, toTime |
Stunden 0 bis 24; beide auf -1 schaltet das Licht bei Dunkelheit und starkem Regen ein |
visibleFrom, visibleTo, fadeOutStart |
Sichtweite in Metern ab der Kamera, mit Ausblenden von fadeOutStart bis visibleTo |
Das Wartehäuschen setzt visibleFrom = -1 und visibleTo = -1. Diesen Wert erklärt das Wiki nicht. Es rät zu wenigen, gut platzierten Lichtern und schlägt für Gebäudefenster Lichtmaterialien vor (map_id, map_lgt).
Partikel-Emitter¶
particleSystem.emitters ist eine Liste von Emittern. Jeder braucht child (den Knoten), frequency (Partikel pro Sekunde) und lifeTime. Optional sind unter anderem particleId (für den Transformator), position, velocity, gravity, albedoTexture, normalMapTexture, Einstellungen für Sprite Sheets (numFrames, frame, frameSpeed), animateInvisible und maxDistance (Standard 100). Zahlenwerte sind fest ({ value = 30 }) oder zufällig ({ randomMinMax = { min = 10, max = 30 } }). Schlüssel mit der Endung OverLifeTime (Alpha, Farbe, Geschwindigkeit, Größe) sind Kurven aus Punkten { time, value, easing } mit time von 0 bis 1. easing nimmt die üblichen Easing-Namen von Linear bis EaseInOutBounce. Die vollständige Schlüsseltabelle mit Standardwerten steht auf der Modellseite im Wiki. Halte frequency, lifeTime und maxDistance niedrig, und setze animateInvisible nur bei großen, langlebigen Partikeln wie dem Abdampf einer Dampflok.
Transformator-Konfiguration¶
In TF3 sind Animations- und Partikelereignisse nicht fest eingebaut, ein Transformator-Skript löst sie aus. transformatorConfig.transformator.name verweist auf eine .trf-Datei (die Vorgaben des Basisspiels sind /vehicle/shared/default_road.trf und /vehicle/train/shared/default_train.trf), und params reicht Werte des Modells an sie weiter, etwa pantographMin/pantographMax. Die Modelle des Basisspiels schreiben diese Pfade mit /. In einem Mod schreibst du ::/vehicle/shared/default_road.trf, denn ein Pfad mit / am Anfang zeigt in deinen eigenen Mod. So macht es carnival_waggon_1.mdl aus der Kampagne (mods/release/urbangames_campaign_mission_01/content/vehicle/vehicle/bus/carnival_waggon_1/). skipFromLod ist der erste LOD, der den Transformator nicht mehr ausführt. Fehlt der Wert, gilt laut Wiki 1. 438 Basismodelle nutzen skipFromLod = 2. Siehe Transformator-Typen und die vehicle advanced topics im Wiki.
Metadaten werden beim Laden nachbearbeitet¶
Das Basisspiel schreibt die Modell-Metadaten beim Laden um. base/content/base/base/base_mod.lua registriert eine Kette von loadModel-Modifiern, darunter:
addModifier("loadModel", model_metadata_util.addTransformatorConfig)
addModifier("loadModel", metadataanimationutil.createAnimationEventsRoadVehicle)
-- ...
addModifier("loadModel", model_metadata_util.toCompartmentList)
addModifier("loadModel", model_metadata_util.turnRoadAndRailToLandVehicle)
addModifier("loadModel", model_metadata_util.addVehicleExtent)
Alle Umwandler stehen in ModelMetadataUtil (addEmissionMetadata, addCostMetadata, convertCargoTypes, makeLoadIndicators, sortLods, addTextureLods, ...). Namen wie turnRoadAndRailToLandVehicle oder convertCargoTypeSet lassen vermuten, dass ältere Metadatenformate ins aktuelle umgewandelt werden. Einige davon (addTransformatorConfig, sortLods und die Funktionen createAnimationEvents*) wandeln nur Modelle vor version = 2 um. Ein neues Modell muss diese Blöcke also in ihrer endgültigen Form enthalten. Andere Werte, etwa Standard-Icons und Filter-Tags, werden auch bei Modellen mit version = 2 ergänzt (siehe Fahrzeuge). Dieselbe Datei leitet außerdem seatProvider aus transportVehicle.seats ab und füllt comfortFactor/priceFactor je Verkehrsträger, wenn sie -1 sind. Der Model Editor führt aus base/content/model_editor/model_editor/editor_base_mod.lua eine eigene Kopie des größten Teils dieser Kette aus. Ein Modell sieht im Editor deshalb so aus wie im Spiel.
Ein Mod kann sich an derselben Stelle einklinken. mods/release/urbangames_vehicles_no_end_year/content/mod.script.lua setzt in einem loadModel-Modifier für jedes Fahrzeug data.metadata.availability.yearTo = 0 (gezeigt unter Ressourcen-Modifier). Nach dem Laden liegen die Modelle in api.res.modelRep. Kampagnenmission 01 lässt das Pferdefuhrwerk in ihrer postRunFn schneller beladen, indem sie model.metadata["transportVehicle"].loadSpeed ändert.
Materialien¶
Eine .mtl-Datei wählt einen Materialtyp (type) und füllt seine Parameter. order sortiert Materialien desselben Typs fürs Rendern. Der Standard ist 0, und ein Innenraummaterial mit order = -1 wird vor den Fenstern desselben Typs gerendert (Material definition). Aus base/content/stations/street/street/small_stops/mat/bus_era_c.mtl (gekürzt):
function data()
return {
__version = "",
order = 0,
params = {
map_albedo = {
fragmentSamplers = {
albedoTex = {
fileName = "tex/bus_stop_era_c_alb.dds",
type = "TWOD",
wrapS = "REPEAT",
wrapT = "REPEAT",
},
},
},
map_metal_gloss_ao = {
fragmentSamplers = {
metalGlossAoTex = { fileName = "tex/bus_stop_era_c_mga.dds", type = "TWOD", wrapS = "REPEAT", wrapT = "REPEAT", },
},
},
map_normal = {
fragmentSamplers = {
normalTex = { fileName = "tex/bus_stop_era_c_nrm.dds", redGreen = true, type = "TWOD", wrapS = "REPEAT", wrapT = "REPEAT", },
},
},
window_light_color = {
fragmentProperties = {
{ color1 = { 0.92, 0.81, 0.72, }, color2 = { 0.9, 0.8, 0.7, }, },
},
},
-- map_id, map_lgt, map_op_1, map_op_2, operation_1, operation_2, texcoord_scale, light_receiver ...
},
type = "PHYSICAL_NRML_MAP_OP_LIGHT",
}
end
Jeder Parameter packt seine Werte in fragmentSamplers (Texturen), fragmentProperties (die meisten Einstellungen) oder vertexProperties (einige wenige wie fade_out_range). Das Wiki empfiehlt, Materialien im Model Editor zu bearbeiten, weil er diese Verschachtelung übernimmt.
Wie Materialtypen definiert sind¶
type nennt einen Materialtyp, der in base/content/rendering/ definiert ist (die Liste files in base/_content.json führt sie als lose Dateien rendering/*.mat.lua). rendering/physical_nrml_map_op_light.mat.lua deklariert legacyName = "PHYSICAL_NRML_MAP_OP_LIGHT", das kurze prefix, die Liste der Eigenschaften, die der Typ annimmt, seine Render-Passes und seine Technique:
function data()
return {
hasSmoothLod = false,
legacyName = "PHYSICAL_NRML_MAP_OP_LIGHT",
needsTangentVertexAttrib = true,
prefix = "PNOL",
properties = {
{ name = "map_metal_gloss_ao", id = "properties/map_metal_gloss_ao.prop", },
{ name = "map_albedo", id = "properties/map_albedo.prop", },
{ name = "map_normal", id = "properties/map_normal.prop", },
-- ...
},
renderPasses = {
COLOR = { needsAlphaBlending = true, needsBlend4 = false, programName = "programs/color_nrml_map.prog" },
DEPTH = { needsAlphaBlending = false, needsBlend4 = false, programName = "programs/depth.prog" },
NORMAL = { needsAlphaBlending = false, needsBlend4 = false, programName = "programs/phys_nrml_map_op_light.prog" },
},
technique = "techniques/standard_nrml_map_op.tec",
transparent = false,
order = 10,
}
end
Jeder Schlüssel unter params in einer .mtl entspricht hier einem name in den Eigenschaften, und eine Eigenschaftsdatei wie rendering/properties/map_albedo.prop.lua deklariert die Sampler, die sie bindet (albedoTex). Shader-Programme sind .prog.lua-Dateien in rendering/programs. Das prefix ist der Kurzcode, den der Model Editor anzeigt. Mit ihm wählt ein FBX-Materialname seinen Typ (mymaterial|PNOL, siehe Model Editor).
Unter einem Materialtyp liegen weitere Rendering-Dateien, alle in base/content/rendering/. technique nennt eine .tec.lua-Datei (32 im Basisspiel). Sie deklariert für jeden Render-Pass die Vertex-, Instanz- und Storage-Eingaben und die Topologie. Jeder programName nennt eine .prog.lua-Datei (223, davon 8 Konsolenvarianten unter programs/xbox/ und programs/ps5/). Ein Programm listet seine defines, seine Descriptor Sets (.desc.lua in programs/descriptors/, die Bindungen von Samplern und Uniform Buffern) und seine shaderStages, deren sourceFiles Shader-Quellen wie shaders/mat/fs/color/std.fs sind. Eine Eigenschaftsdatei (.prop.lua, 47 Stück) deklariert jeden Wert eines Parameters mit Typ und Standardwert. Aus rendering/programs/color_nrml_map.prog.lua (gekürzt):
descriptorSets = { "descriptors/phys_world.desc", "descriptors/phys_material_type.desc", "descriptors/set_36.desc", },
shaderStages = {
{
sourceFiles = { "shaders/mat/fs/color/std.fs", "shaders/util/lighting.fs", --[[ ... ]] },
stage = "FRAGMENT",
},
{
sourceFiles = { "shaders/mat/vs/std/color.vs", "shaders/util/fade_out.glsl", "shaders/util/inst_attr.vs", "shaders/util/fog.glsl", },
stage = "VERTEX",
},
},
Die Shader-Quellen (.fs, .vs, .glsl und andere) stehen in base/_content.json, gehören aber nicht zu den entpackten Dateien, aus denen diese Seiten zitieren. Für jeden dieser Dateitypen gibt es einen Ressourcen-Modifier (loadMaterialType, loadTechnique, loadProgram, loadDescriptorSet, loadProperty). Ein Materialtyp, der anders aussehen soll als die vorhandenen, braucht eigene Shader. Die upload guidelines verbieten eigene Shader-Dateien nur auf Konsolen und stellen für den PC keine solche Regel auf. Wie man einen Shader für das Spiel schreibt, erklären aber weder sie noch eine andere Wiki-Seite.
Materialtypen¶
Das Basisspiel definiert 51 Materialtypen. Die Tabellen im Wiki führen 43 auf und sprechen von „over 40“. Sie lassen sich in diese Gruppen einteilen (Präfix in Klammern):
| Gruppe | Typen | Verwendung |
|---|---|---|
| Physikalisch, deckend | PHYSICAL (P), PHYSICAL_OP (PO), PHYSICAL_NRML_MAP (PN), PHYSICAL_NRML_MAP_CBLEND (PNC), PHYSICAL_NRML_MAP_CBLEND_DIRT (PNCD), PHYSICAL_NRML_MAP_OP (PNO), PHYSICAL_NRML_MAP_CBLEND_OP (PNCO), PHYSICAL_NRML_MAP_UV1_AO (PNUA), PHYSICAL_NRML_MAP_OP_UV1_AO (PNOUA), ..._OP_UV1_AO_MASK (PNOUAM), ..._OP_DIRT_UV1_AO_MASK_DIRT_MASK (PNODUAM) |
die meisten Gebäude, Assets und Fahrzeugkarosserien |
| Physikalisch, transparent | PHYS_TRANSPARENT (T), _OP (TO), _UV1_OP (TUO), _NRML_MAP (TN), _NRML_MAP_OP (TNO), _NRML_MAP_SMOOTH_LOD (TNS), _NRML_MAP_UV1_OP (TNUO), _NRML_MAP_UV1_OP_SMOOTH_LOD (TNUOS), _NRML_MAP_CBLEND (TNC), _NRML_MAP_CBLEND_DIRT (TNCD) |
Glas, Fahrzeugfenster, verzierte Zäune |
| Physikalisch mit Licht | PHYSICAL_NRML_MAP_LIGHT (PNL), PHYSICAL_NRML_MAP_OP_LIGHT (PNOL), PHYSICAL_NRML_MAP_OP_UV1_AO_LIGHT (PNOUAL), PHYS_TRANSPARENT_NRML_MAP_LIGHT (TNL), PHYS_TRANSPARENT_NRML_MAP_OP_UV1_AO_MASK_LIGHT (TNOUAML) |
beleuchtete Fenster von Stadtgebäuden, Bahnhöfen und Industrien |
| Skinning | SKINNING_TEXTURED (S), SKINNING_PHYS_CBLEND4 (SC), SKINNING_PHYS_NRML_MAP (SN), ..._NRML_MAP_OP (SNO), ..._NRML_MAP_CBLEND4 (SNC), ..._NRML_MAP_CBLEND_DIRT (SNCD), SKINNING_PHYS_TRANSPARENT_NRML_MAP (STN) mit _OP (STNO), _CBLEND4 (STNC), _CBLEND_DIRT (STNCD) |
Meshes mit Knochen: Menschen, Tiere |
| Sonstige | BILLBOARD (B), BILLBOARD_MULTI (BM), EMISSIVE (E), emissiv mit Skinning (SE), LEAF_CARD (L), PHYSICAL_GLOSS_ONLY (PG), PHYSICAL_OP_GLOSS_ONLY (POG) |
ferne Baum-LODs, leuchtende Flächen, Baumlaub |
Welche Maps ein Typ annimmt, steht in den Tabellen des Wikis. Verbindlich für einen Typ ist die Liste properties in seiner rendering/*.mat.lua. Meshes mit Skinning können bis zu 40 Knochen haben, und jeder Vertex folgt höchstens vier davon, mit Gewichten aus der Modellierungssoftware.
In diesen Punkten weichen die Spieldateien von den Tabellen im Wiki ab:
- Das Wiki nennt den emissiven Skinning-Typ
SKINNED_EMISSIVE. Inrendering/skinning_emissive.mat.luastehtlegacyName = "SKINNING_EMISSIVE", und so schreiben ihn auch die.mtl-Dateien. - Acht Typen gibt es in
rendering/, aber nicht im Wiki:BILLBOARD2(B2, 49 Basismaterialien),PHYSICAL_NRML_MAP_CBLEND4(PNC4),PHYSICAL_NRML_MAP_CBLEND_DIRT_LOGO(ohne Präfix),PHYSICAL_NRML_MAP_OP_UV1_AO_MASK_LIGHT(PNOUAML),PHYSICAL_NRML_MAP_OP_UV1_LIGHT(PNOUL),PHYS_TRANSPARENT_DIFFUSE(TD, das Platzhaltermaterial des Importers),PHYS_TRANSPARENT_NRML_MAP_AO_SMOOTH_LODundPHYS_TRANSPARENT_NRML_MAP_CBLEND_DIRT_UV1_OP(TNCDUO). PHYS_TRANSPARENT_NRML_MAP_AO_SMOOTH_LODnutzen 288 Basismaterialien. Der Typ teilt sich das Präfix TNS mitPHYS_TRANSPARENT_NRML_MAP_SMOOTH_LOD, das Präfix allein wählt also keinen der beiden eindeutig aus.
Textur-Sampler¶
Ein Map-Parameter enthält genau einen Sampler. Alle Sampler-Schlüssel:
map_albedo_opacity = {
fragmentSamplers = {
albedoOpacityTex = {
fileName = "tex/bus_d40_albo.dds",
type = "TWOD", -- TWOD, CUBE_MAP, TWOD_ARRAY
magFilter = "LINEAR", -- NEAREST, LINEAR
minFilter = "LINEAR_MIPMAP_LINEAR", -- NEAREST, LINEAR, LINEAR_MIPMAP_LINEAR, LINEAR_MIPMAP_NEAREST, NEAREST_MIPMAP_NEAREST
wrapS = "CLAMP_TO_EDGE", -- REPEAT, CLAMP_TO_EDGE
wrapT = "CLAMP_TO_EDGE",
maxDegreeOfAnisotropy = -1,
mipmapAlphaScale = 0,
mipmapBaseLevel = 0,
redGreen = false,
scaleDownAllowed = true,
textureLodOffset = 0,
},
},
},
wrapS/wrapT wählen zwischen Kacheln und Abschneiden am Rand. maxDegreeOfAnisotropy = -1 ist der empfohlene Standard. mipmapAlphaScale skaliert das Alpha in Mipmaps, die aus .tga-Dateien erzeugt werden, und macht Cutout-Kanten weicher. mipmapBaseLevel erzwingt eine kleinere Mipmap einer .dds. redGreen kennzeichnet eine Normal Map mit zwei Kanälen. scaleDownAllowed erlaubt Texture Streaming, und textureLodOffset verschiebt die gestreamte Mipmap. Die Liste der Materialfelder im Model Editor (base/content/model_editor/model_editor/meta_material.lua) kennt zusätzlich ein Flag compressionAllowed, das das Wiki nicht erwähnt.
Maps¶
| Map | Inhalt |
|---|---|
map_albedo, map_color |
Farbe, kein Alpha |
map_color_alpha |
Farbe mit Transparenz an/aus |
map_albedo_opacity |
Farbe mit 0 bis 100 % Transparenz |
map_albedo_gloss |
Farbe mit Glanz im Alpha (alter Train-Fever-Stil) |
map_emissive |
Lichtfarbe, nur für EMISSIVE |
map_metal_gloss_ao |
R Metall, G Glanz, B Ambient Occlusion (0 = volle Verschattung) |
map_ao |
eigene Ambient Occlusion auf dem zweiten UV-Satz |
map_normal |
Normal Map im Tangentenraum |
map_cblend |
vier Masken für die Farbmischung in RGBA, mit colorConfig bei Figuren |
map_cblend_dirt_rust |
R Farbmischung, G Schmutz, B Roststärke |
map_dirt, map_rust, map_dirt_normal, map_rust_normal |
eigene Schmutz- und Rosttexturen; ohne sie gelten die gemeinsamen aus vehicle/shared/mat/tex/ |
map_op_1, map_op_2 |
Overlay-Texturen für die beiden Operationen |
map_id |
8-Bit-Graustufen mit IDs für Fensterlicht: Flächen mit gleichem Wert schalten gemeinsam ein. Erlaubt sind 0, 255 oder 4 + 8 × n. Nutze NEAREST-Filter, CLAMP_TO_EDGE und scaleDownAllowed = false. |
map_lgt |
der Rotkanal maskiert die Stärke des Fensterlichts (Vorhänge, eine dunklere Oberkante) |
Die Kanäle der Farbmischung wirken umgekehrt: Je dunkler der Kanal, desto stärker die Umfärbung. Setze color_blend.albedoScales auf die Helligkeit der ursprünglichen Texturfarbe in diesem Bereich, damit umgefärbte Teile zu anderen Modellen passen.
Operationen¶
operation_1 und operation_2 mischen map_op_1/map_op_2 in die Albedo, etwa um sich wiederholende Wandtexturen aufzulockern:
operation_1 = {
fragmentProperties = {
{
op = "OVERLAY", -- NO_OP, MULTIPLICATION, OVERLAY, LINEAR_BURN, ALPHA_BLEND
mode = "NORMAL", -- TEXCOORD, WORLD_XY, NORMAL, OFFSET_TEXCOORD
scale = { 0.05, 0.5 },
opacity = 0.5,
},
},
},
MULTIPLICATION multipliziert die Kanäle (dunkler), OVERLAY multipliziert unter 0,5 und hellt darüber auf (Screen), LINEAR_BURN addiert und zieht 1 ab, und ALPHA_BLEND legt das Overlay dort über die Albedo, wo es deckend ist (Moos am Fuß eines Baumstamms). mode wählt die Koordinaten des Overlays: modell-lokal (NORMAL), Welt (WORLD_XY), UV (TEXCOORD) oder UV mit zufälligem Versatz (OFFSET_TEXCOORD).
Eigenschaften¶
| Parameter | Schlüssel | Hinweise |
|---|---|---|
light_receiver |
lightMask, isLegacyMaterial, disableShadowMapWrite, lightMaxDistanceScale |
Bits von lightMask wie bei den Lichtern: 2 für die meisten Modelle, 32 für Fahrzeuginnenräume, 34 für Figuren. isLegacyMaterial ist nur für umgewandelte TF2-Texturen gedacht. |
albedo_scale |
albedoScale (RGB) |
färbt eine Textur in mehrere Farbvarianten ein |
alpha_scale |
alphaScale |
skaliert die Transparenz |
emissive_scale |
emissiveScale (RGB), fromTime, toTime |
für EMISSIVE; Zeiten auf -1 folgen Tageslicht und Wetter |
normal_scale |
normalScale |
Skalieren des Knotens ändert auch die Stärke der Normalen |
alpha_test |
alphaThreshold, a2CThreshold, cutout, sorted, disableDepthAndNormalWrite |
Schwelle für Schatten, Schwelle für Sichtbarkeit, Transparenzmodus und Sortierung |
color_blend |
albedoScales, color oder colors |
ein Wert für die rote Maske, vier für SKINNING_PHYS_NRML_MAP_CBLEND4 |
dirt_rust |
age, dirtColor, dirtOpacity, dirtScale, rustColor, rustOpacity, rustScale |
age 0 bis 1 für Modelle, die vom Spiel kein Alter bekommen |
fade_out_range |
fadeInStartDist, fadeInEndDist, fadeOutStartDist, fadeOutEndDist |
in vertexProperties; blendet aus, statt schlagartig zu verschwinden |
polygon_offset |
factor, units, forceColorWrite, forceDepthWrite |
verschiebt die Tiefe; setze factor und units auf denselben kleinen negativen Wert und forceDepthWrite beim Material mit dem niedrigsten Offset |
two_sided |
twoSided, flipNormal |
rendert die Flächen doppelt; setze flipNormal bei Normal Maps |
tex_animation |
uvSpeed, frameSize, frameSpeed |
laufende UVs oder ein Sprite-Raster |
window_light_color |
color1, color2, fromTime, toTime |
Farben beleuchteter Fenster |
Im Basisspiel setzen 1.162 .mtl-Dateien isLegacyMaterial = true und 5.585 setzen es auf false, obwohl das Wiki das Flag für alte Texturen aus Transport Fever 2 vorsieht. Die Standardvorlage des Editors für Schmutz setzt außerdem dirtFactor und rustFactor, die das Wiki nicht aufführt.
Transparenz¶
Deckende Typen werden vor transparenten gerendert, und alles hinter einer transparenten Fläche muss vorher gerendert werden, sonst verschwindet es. Das Wiki nennt vier Mittel in dieser Reihenfolge. Erstens order innerhalb eines Typs. Zweitens alpha_test.cutout = true: Es macht aus der Transparenz ein Dithermuster (sauber bei 0, 25, 50, 75 und 100 %, dazwischen körnig) und passt zu Geländern und Laub. Drittens alpha_test.sorted = true: Es sortiert Meshes nach dem Abstand ihres Ursprungs zur Kamera und kostet zusätzliche Draw Calls, gehört also nicht in niedrige LODs. Viertens polygon_offset, das die Fläche im Tiefenpuffer nach hinten schiebt, etwa bei Scheinwerfern hinter Glas.
Texturen¶
Das bevorzugte Texturformat ist .dds mit Kompression und Mipmaps (Resource types). Die unterstützten Kompressionen:
| Format | Verwendung |
|---|---|
| BC1/DXT1 | kein Alpha oder Alpha an/aus; kleinste Dateien |
| BC2/DXT3 | 4-Bit-Alpha |
| BC3/DXT5, BC7 | weiches Alpha |
BC5/3Dc (ATI2A2XY) |
Normal Maps; laut Wiki für sie Pflicht |
| BC4/R8/L8 | 8-Bit-Graustufenmasken wie map_id |
Für Grafikkarten von ATI und Intel müssen die Größen Zweierpotenzen von 16 bis 4096 sein. .tga ist für Grafiken der Oberfläche gedacht. Icons liegen für 4K-Bildschirme in doppelter Größe vor, mit der Endung @2x.tga. Für Modelltexturen nutzt du .tga nur während der Arbeit: Der Model Editor wandelt .tga-Texturen beim Speichern eines Modells in .dds um. .hdr steht ohne weitere Angaben in der Liste der Texturformate. Werkzeuge, die DDS exportieren, findest du unter External tools.
Materialien verweisen relativ zur Materialdatei (tex/...) oder absolut (/placeholders/mat/tex/default_map_id.dds) auf Texturen. Das Basisspiel benennt sie mit einer Endung nach der Map: _alb, _albo, _nrm, _mga, _cdr, _cblend, _id, _e (Lichtmaske) und so weiter. Der Model Editor verlässt sich beim Import einer .fbx auf diese Endungen. Die vollständige Tabelle steht auf der Seite Model Editor.
texture_metadata_cache.lua im Hauptordner jedes Mods und von base/ listet jede Textur mit channels, extent, numMipmaps und numStrippedMipmaps:
["animal/bird_crane/mat/tex/bird_crane_albedo_opacity.dds"] = {
channels = 4,
extent = { 512, 512, },
numMipmaps = 10,
numStrippedMipmaps = 0,
},
Ob ein Mod diesen Cache mitliefern muss, ist offen. Mehr dazu unter Vom Spiel erzeugte Dateien.
Weitere Asset-Dateien¶
| Endung | Beispiel | Inhalt |
|---|---|---|
.gtex.lua |
base/content/stations/air/air/helipad/helipad.gtex.lua |
Bodentextur: eine Textur mit Materialindizes plus materialIndexMap vom Farbindex auf .tmat-Geländematerialien |
.tmat.lua |
::/terrain/materials/asphalt_02/asphalt_02.tmat |
Geländematerial, siehe Geländematerialien |
.snd.lua |
::/vehicle/truck/shared/sound/truck_old.snd |
Soundset, auf das soundConfig.soundSet.name verweist, siehe Soundsets |
.trf.lua |
::/vehicle/shared/default_road.trf |
Transformator, auf den transformatorConfig verweist (Transformator-Typen) |
.ani |
ani/die/cougar_root.ani (neben ::/animal/cougar/cougar.mdl) |
Animation: times in Millisekunden und eine Matrix transfs pro Zeitpunkt |
.wav, .ogg |
Soundeffekte, Musik |
Offizielles Wiki: Resource types, Model definition (.mdl), Mesh definition (.msh), Material definition (.mtl), Model Editor, Best practice.