Fahrzeuge: fortgeschrittene Themen¶
Diese Seite setzt Fahrzeuge fort. Sie behandelt, was die meisten Fahrzeug-Mods nur gelegentlich brauchen: eigene Transformator-Skripte, Zweirichtungszüge, gruppierte Einträge im Fahrzeugladen und Fake-Drehgestelle für Gelenkfahrzeuge. Der Teil über Transformatoren gilt für jedes animierte Modell, nicht nur für Fahrzeuge.
Transformator-Skripte¶
Ein Transformator berechnet in jedem Frame die Animationszustände und Partikeleinstellungen eines Modells. Das Modell nennt ihn in transformatorConfig (siehe Transformator-Konfiguration). Der Transformator selbst ist eine .trf.lua-Datei mit bis zu vier Skriptverweisen. Die Concorde aus dem DLC hat einen eigenen, dlcs/urbangames_deluxe_upgrade_pack/content/vehicle/vehicle/plane/concorde/concorde.trf.lua:
function data()
return {
updateScript = {
fileName = "transformator_concorde.script@concorde.updateFn",
params = {}
},
updateParticleSystemScript = {
fileName = "transformator_concorde.script@concorde.updateParticleSystemFn",
params = {}
},
}
end
Ihr Modell nennt ihn mit dem Mod-Präfix:
transformatorConfig = {
skipFromLod = 2,
transformator = {
name = "urbangames_deluxe_upgrade_pack::/vehicle/plane/concorde/concorde.trf",
},
},
fileName hat die Form <Skriptdatei ohne .tl>@<Tabelle>.<Funktion> und ist relativ zur .trf.lua. Eine Schlüssel-Wert-Tabelle in params kommt als erstes Argument in der Funktion an (captureParams). Die vier Skripte:
| Schlüssel | Aufgerufen | Drittes Argument |
|---|---|---|
updateScript |
um das Modell zu bewegen und zu animieren | TransfOutput |
updateParticleSystemScript |
einmal pro particleSystem |
ParticleSystem |
getEmittableModelsScript |
gibt die Modelle zurück, die computeEmittedModelsScript erzeugen darf |
keins |
computeEmittedModelsScript |
erzeugt zusätzliche Modelle relativ zu diesem | ModelEmitter |
Das zweite Argument ist immer TransformatorParams. In diesen Funktionen steht nur api.type zur Verfügung, keine anderen api-Funktionen. Als Grund nennt das Wiki die Performance.
Die mitgelieferten Transformatoren:
| Datei | Verwendet von |
|---|---|
vehicle/shared/default_road.trf |
Busse, Lkw, Autos (125 Modelle) |
vehicle/train/shared/default_train.trf |
Züge und Waggons (110) |
vehicle/train/shared/tilting_train.trf |
Neigezüge (7) |
vehicle/tram/shared/default_tram.trf |
Trams (56) |
vehicle/ship/shared/default_ship.trf, hovercraft_ship.trf |
Schiffe (26), Luftkissenboote (2) |
vehicle/shared/default_air.trf |
Flugzeuge und Helikopter (42) |
vehicle/zeppelin/shared/zeppelin.trf |
die drei Zeppeline |
concorde.trf, hot_air_balloon.trf |
Concorde und Heißluftballon aus dem DLC |
Update-Funktion¶
Die Standard-Transformatoren setzen sich aus Hilfsfunktionen in base/content/scripts/scripts/transformator_util.tl zusammen (das Wiki schreibt transformatorutil.tl), dokumentiert als TransformatorUtil. Das Skript der Concorde, transformator_concorde.script.tl neben der .trf.lua, ruft die üblichen Flugzeug-Hilfsfunktionen auf und ergänzt dann die absenkbare Nase (gekürzt):
local transformator_util = ug_require "::/scripts/transformator_util.tl" as TransformatorUtil
local updateFnConcorde = function(
__captureParams : NativeLuaTable,
params : TransformatorParams,
transfsOutput : Transformator.TransfOutput
)
if params.currentInfo.aircraft ~= nil then
transformator_util.addFlapsAnimation(
params.currentInfo.aircraft.flaps,
params.aircraftStaticInfo.flapsDuration,
transfsOutput
)
transformator_util.addDoorAnimationState(params.currentInfo.vehicle.doorAnimationInfo, transfsOutput)
-- addGearAnimation, addAircraftControlSurfacesAnimation, addAircraftLightsAnimation, ...
-- state2fraction ordnet jedem FlightState eine Nasenstellung von 0 bis 1 zu
local timeNose = math.ceil(state2fraction[params.currentInfo.aircraft.flightState] * 11000)
transfsOutput:addAnimationState("nose_up", -1, timeNose, false, false)
end
end
return {
concorde = {
updateFn = updateFnConcorde,
updateParticleSystemFn = transformator_util.updateParticleSystemFn
},
}
TransfOutput hat diese Funktionen:
getUserTransfsundsetUserTransffür die Transformationen einzelner Knoten, etwa Position und Drehung auf dem Gleis.setUserTransfnimmt den Knotenindex, eine Matrix und die Angabe, ob sie absolut ist (Standard: relativ).getAnimationStates.addAnimationState(Ereignisname, Startzeit, Parameter, Schleife, rückwärts) springt an eine Stelle einer Animation.triggerAnimation(Ereignisname, optional Start- und Endzeit) spielt eine Animation ab.
Das Wiki schreibt die erste Funktion als getUserTransf, in der Definition heißt sie getUserTransfs. Laut Wiki laufen einfachere Transformatoren schneller, und manche Hilfsfunktionen greifen auf Werte zurück, die die Simulation schon berechnet hat (zum Beispiel das Timing der Türen).
Partikelfunktion¶
updateParticleSystemFn bekommt das Partikelsystem des Modells. Das Wiki empfiehlt diese Reihenfolge: Prüfe, ob die benötigten Zustandsdaten in TransformatorParams vorhanden sind, berechne die Werte einmal und gehe dann die Emitter durch. getSize liefert die Zahl der Emitter und getParticleId die particleId eines Emitters. So kann ein Skript Emitter vom Typ "exhaust" anders behandeln als "brake". Die Emitter-Indizes beginnen bei 0:
for i = 0, particleSystem:getSize() - 1 do
if particleSystem:getParticleId(i) == "exhaust" then
particleSystem:setLifeTimeScale(i, lifeTimeScale)
end
end
Die Setter (setLifeTimeScale, setColorOffset, setFrequencyScale, setVelocity usw.) stehen unter Transformator.ParticleSystem.
Erzeugte Modelle¶
computeEmittedModelsFn kann Modelle relativ zum Fahrzeug platzieren, etwa eigene Passagiere oder Nummernschilder. getModelId macht aus einem Modellpfad eine ID (ein Pfad ohne Mod-Präfix wird aus dem Basisspiel genommen). emitModel und emitModels platzieren ein oder mehrere Modelle mit Matrix, Animationszuständen, Elternknoten und einem Flag für die Ausrichtung am Gelände. Laut Wiki lassen sich nur Modelle erzeugen, die getEmittableModelsFn zurückgibt. Die Modellliste übergibst du am besten über transformatorConfig.params, so empfiehlt es das Wiki.
Die Zeppeline machen das für ihre Besatzung. base/content/vehicle/zeppelin/zeppelin_nt/zeppelin_nt/zeppelin_nt.mdl übergibt die Pfade:
transformatorConfig = {
params = {
crewPath = "characters/era_c_driver_air/era_c_driver_air.mdl",
passengerPathsA = { },
passengerPathsB = { },
passengerPathsC = { },
},
skipFromLod = -1,
transformator = { name = "/vehicle/zeppelin/shared/zeppelin.trf", },
},
transformator_zeppelin.script.tl setzt dann auf jeden Besatzungssitz ein Besatzungsmodell:
local crewModelId = modelEmitter:getModelId(transformatorConfigParamsZeppelin.crewPath)
-- für jeden Sitz aus params.modelMetadataInfo.seats dieser Instanz:
modelEmitter:emitModel(crewModelId, seat.transf, {a}, seat.group, false)
Sein getEmittableModelsZeppelinFn gibt eine leere Liste zurück. Dieses mitgelieferte Skript hält sich also nicht an die Regel aus dem Wiki. Ob die Liste nötig oder nur empfohlen ist, geht aus den Dateien nicht hervor. Das herunterladbare Beispiel im Wiki (eigene Nummernschilder an einer EMD F-Unit) sammelt in getEmittableModelsFn alle Modelle seines Parameters randomGroups und wählt pro Gruppe eines mit math.randomseed(transformatorParams.entityId + index). So behält jedes Fahrzeug seine Nummer.
Transformator-Parameter¶
TransformatorParams hat immer entityId. Alles andere hängt vom Modell ab und kann fehlen. Prüfe es also, bevor du es verwendest.
Statische Daten: modelMetadataInfo (Sitze), vehicleStaticInfo (Achsen, Kaufzeitpunkt), aircraftStaticInfo (nur Flugzeuge) und transformatorConfigParams (die params aus der transformatorConfig des Modells).
Dynamische Daten stehen für den aktuellen Frame in currentInfo und für den vorigen in previousInfo (sofern das Modell da schon existierte und sichtbar war). Dazu gehören world (Bewölkung, Spielzeit, Datum, Tageszeit, Echtzeit), vehicle (Geschwindigkeit, Leistung, Beschleunigung, Wartung, Zugverband), die trägerspezifischen Tabellen roadVehicle, railVehicle, tram, landVehicle, aircraft, ship und airWaterVehicle und für statische Modelle industry, station und townBuilding. Zur Spielzeit (world.gameTime) schreibt das Wiki „ticks since map start“. Die Definitionen nennen keine Einheit, und die Missionsaufgabe wait des Basisspiels (base/content/mission/mission/tasks/wait/wait.tl) behandelt GameTime.gameTime der Engine als Millisekunden. Die Concorde vergleicht previousInfo.aircraft.flightState mit dem aktuellen Wert und startet die Nasenanimation, wenn sich der Flugzustand ändert.
Zweirichtungszüge¶
Zweirichtungszüge und -trams wechseln im Bahnhof die Fahrtrichtung, ohne umzusetzen. Dafür brauchen das erste und das letzte Fahrzeug des Zugverbands und jedes angetriebene Fahrzeug darin reversible = true in transportVehicle. 75 Modelle des Basisspiels haben das gesetzt, darunter die Traxx-Lok base/content/vehicle/train/br_185_traxx/br_185_traxx/br_185_traxx.mdl und die Teile des ICE 1.
Meshes nach Position ausblenden¶
Die Standard-Transformatoren für Züge und Trams blenden Knoten je nach Position im Zugverband und Fahrtrichtung ein und aus. Jeder Knoten braucht dafür die Animation _on und die Animation _off. Die Scheinwerfer der Traxx:
{
animations = {
front_forward_parts_off = {
params = { id = "/vehicle/shared/ani/front_forward_parts_off.ani", },
type = "FILE_REF",
},
front_forward_parts_on = {
params = { id = "/vehicle/shared/ani/front_forward_parts_on.ani", },
type = "FILE_REF",
},
},
materials = { "/vehicle/train/emissive/train_all_lights.mtl", },
mesh = "msh/headlights_fwd_lod0.msh",
name = "headlights_fwd",
transf = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, },
},
| Ereignispaar | Sichtbar, wenn das Fahrzeug |
|---|---|
front_forward_parts_* |
vorne im Zugverband ist und vorwärts fährt |
inner_forward_parts_* |
weder erstes noch letztes ist und vorwärts fährt |
back_forward_parts_* |
hinten ist und vorwärts fährt |
front_backward_parts_* |
vorne ist und rückwärts fährt (nur Zweirichtungszüge) |
inner_backward_parts_* |
weder erstes noch letztes ist und rückwärts fährt (nur Zweirichtungszüge) |
back_backward_parts_* |
hinten ist und rückwärts fährt (nur Zweirichtungszüge) |
forward_parts_*, backward_parts_* |
vorwärts oder rückwärts fährt, egal an welcher Position |
has_previous_*, has_no_previous_* |
ein Fahrzeug vor sich hat oder nicht |
has_next_*, has_no_next_* |
ein Fahrzeug hinter sich hat oder nicht |
Eine Mesh-Instanz kann nur einen dieser Fälle abdecken. Für einen weiteren Fall legst du eine zweite Instanz des Meshes an. Die Positionsereignisse sind für Zugschlusssignale, Stromabnehmer, die von der Position im Zug abhängen, und Übergänge zwischen Wagen gedacht. Die gemeinsamen Animationen liegen in vehicle/shared/ani/. Der Traxx-Auszug ist Code aus dem Basisspiel. In einem Mod schreibst du ::/vehicle/shared/ani/front_forward_parts_on.ani und ::/vehicle/train/emissive/train_all_lights.mtl. Brems- und Blinklichter von Straßenfahrzeugen und Trams haben eigene Ereignisse, siehe Lichter.
Führerstandsitze¶
forward an einem Besatzungssitz beschränkt ihn auf eine Fahrtrichtung. Ein Sitz, dessen group ein Knoten ist, der nur vorne sichtbar ist, erscheint nur im führenden Fahrzeug. Die Traxx nutzt beides:
seatProvider = {
crewModels = { },
drivingLicense = "RAIL",
seats = {
{ animation = "driving_upright", crew = true, group = "headlights_fwd",
transf = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 7.336, -0.673, 1.761, 1, }, },
{ animation = "driving_upright", crew = true, forward = false, group = "headlights_bwd",
transf = { -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, -7.342, 0.705, 1.761, 1, }, },
},
},
Gruppen im Fahrzeugladen¶
Der Fahrzeugladen (Vehicle Store) kann mehrere Fahrzeuge als einen Eintrag mit „n Varianten“ und einer Liste der Varianten zeigen. Die technischen Daten gehören dann zur gewählten Variante. Das passt für Formvarianten, Repaints und Triebzüge unterschiedlicher Länge, bei jedem Verkehrsträger. Ist in einem Jahr nur eine Variante verfügbar, erscheint sie als normaler Eintrag. Die Gruppe erscheint erst, wenn zwei oder mehr Varianten verfügbar sind. Haben die Varianten einer Schienengruppe unterschiedliche Antriebsarten, zeigt der Antriebsfilter des Ladens („Dampf“, „Diesel“, „Elektrisch“) nur die passenden.
Eine Variante verweist mit groupFileName in transportVehicle auf das Elternmodell der Gruppe, relativ zu ihrer eigenen .mdl (das Wiki nennt den Laden „buy menu“):
transportVehicle = {
carrier = "RAIL",
-- ...
multipleUnitOnly = false,
groupFileName = "menu_1020.mdl",
},
Ein Triebzug setzt groupFileName in seiner .mu.lua, relativ zu dieser Datei. base/content/gui/gui/line_vehicle_mgmt/vehicle_store_util.tl liest den Wert als variantGroup des Fahrzeugs. Kein Fahrzeug aus Basisspiel oder DLC verwendet groupFileName, die Beispiele hier stammen aus dem Wiki. Ein relativer Pfad erreicht nur Modelle im selben Mod. Für eine Variante eines Fahrzeugs aus dem Basisspiel oder einem anderen Mod siehe Repaints: Modell.
Für eine Gruppe mit eigenem Namen und Icon legst du ein Menümodell an, das nie verkauft wird. Gib ihm Namen und Icon der Gruppe, setze multipleUnitOnly = true und setze kein groupFileName. Solange kein Triebzug es verwendet, erscheint es nur als Kopf seiner Gruppe. Das Wiki gibt für das Icon eines solchen Menümodells diese Richtwerte: bis zu vier Varianten nebeneinander, 30 px versetzt, die rechte vorne, dazwischen etwa 2 px Abstand. Bei Triebzügen zeigst du zusätzlich den zweiten Wagen halb oder ganz. Bei normaler Menügröße ist das Icon höchstens 327 px breit (breitere Icons dehnen das Menü, ab 700 px wird der rechte Rand abgeschnitten). Üblich ist auch eine diagonale Anordnung.
Fake-Drehgestelle¶
Das Spiel richtet Fahrzeugteile anhand ihrer Achsen am Gleis oder an der Fahrspur aus:
- Der Ursprung einer Achse liegt mittig über der Spur.
- Ein Knoten mit Achs-Meshes als Kindern ist ein Drehgestell. Sein Drehpunkt ist der Ursprung seines Meshes, der mittig zwischen seinen Achsen liegen sollte.
- Ein Knoten mit zwei Drehgestellen dreht sich um die Mitte zwischen ihnen.
- Ein Knoten mit mehr als zwei Achsen oder Drehgestellen richtet sich an den äußersten aus.
Eine Gelenktram, deren Endteile auf je einem Drehgestell stehen, würde in Kurven schlecht einlenken. Ein Fake-Drehgestell ist ein per Skript gesetzter Drehpunkt, der anstelle eines fehlenden echten Drehgestells an einem Knoten hängt. fakeBogies enthält eine Liste pro LOD, jeder Eintrag mit diesen Feldern:
| Feld | Bedeutung |
|---|---|
group |
Knoten, zu dem das Fake-Drehgestell gehört |
position |
Abstand entlang der Spur vom Wurzelknoten des Modells, positiv nach vorne. Das Spiel nimmt an diesem Punkt die Tangente an die Spur. |
offset |
Abstand entlang dieser Tangente von position bis zum eigentlichen Drehpunkt |
upright |
optional; true hält den Knoten aufrecht stehend oder hängend |
Ein Knoten kann bis zu zwei Fake-Drehgestelle haben. Sind echte und Fake-Drehgestelle zusammen mehr als zwei, haben die Fake-Drehgestelle Vorrang, und der Knoten richtet sich an der Linie zwischen seinen beiden Drehpunkten aus. Die Tram Be 4/6 (base/content/vehicle/tram/be4_6mirage/be4_6mirage/be4_6mirage.mdl, gezeigt auf der Seite Fahrzeuge) gibt ihren Endteilen front_grp und back_grp je ein Fake-Drehgestell und ihrem Mittelteil middle_grp zwei. Straßenfahrzeuge setzen ihres meist zwischen die beiden Achsen. Gelenkbusse setzen eines auf die Hinterachse des vorderen Teils, zum Gelenk hin versetzt.
In den Dateien des Basisspiels steht fakeBogies direkt in config, als Liste von LOD-Listen (config = { axles = {...}, fakeBogies = { { ... }, { ... } } }), so wie auf der Wiki-Seite zu den Fahrzeugtypen. Das Beispiel auf der Wiki-Seite zu den weiterführenden Themen packt axles und fakeBogies in eine zusätzliche Tabelle innerhalb von config und listet die Drehgestelle ohne die LOD-Ebene. Kein Modell des Basisspiels setzt upright.
Offizielles Wiki: Vehicle advanced topics, Vehicle types, Vehicle basics, Scripting API.