Zum Inhalt

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:

  • getUserTransfs und setUserTransf für die Transformationen einzelner Knoten, etwa Position und Drehung auf dem Gleis. setUserTransf nimmt 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.