Types de scripts JS
Le ctx, le wrapper et les types de données propres à chacun des quatre types de scripts. Pour l’enregistrement et le cycle de vie, voir Premiers pas en JavaScript ; pour les classes communes, voir API communes JS.
Scripts de véhicule
Dans MTR 4, un « véhicule » est une entrée de modèle de voiture ; le joueur assemble librement ces entrées en rames dans l’interface de la voie de garage. Le script n’est lié ni à une voiture isolée ni à la rame entière, mais à une entrée de script : l’entrée de véhicule y fait référence par scriptId, et plusieurs véhicules peuvent réutiliser le même script — composition mixte, performance et compatibilité NTE réunies.
Trois callbacks (ctx est un VehicleScriptContext, vehicle un VehicleWrapper) :
function create(ctx, state, vehicle) { /* avant le premier rendu : créer les textures, enregistrer les callbacks */ }
function render(ctx, state, vehicle) { /* à chaque frame : lire l'état, soumettre dessins et sons */ }
function dispose(ctx, state, vehicle) { /* au retrait : libérer les textures */ }
VehicleScriptContext (ctx)
| Membre | Description |
|---|---|
getCarRenderManager(carIndex): RenderManager? | retourne le gestionnaire de rendu de la voiture carIndex. Référentiel = centre de la voiture, 1 m au-dessus du plancher. Retourne null pour une voiture non rattachée à cette entrée |
getCarBogieRenderManager(carIndex, bogieIndex): RenderManager? | rendu des bogies : référentiel relatif au bogie bogieIndex (0 avant, 1 arrière ; 0 pour une voiture à bogie unique) |
getCarSoundManager(carIndex): SoundManager? | la source sonore de la voiture (référentiel = centre de la voiture) |
playAnnSound(id, volume, pitch) | diffuse l’annonce de station — audible uniquement des joueurs à bord de cette rame, sans spatialisation |
getScriptEntryId(): String | l’id de l’entrée de script |
getMyCars(): int[] | tableau des indices des voitures rattachées à cette entrée |
setDebugInfo(key, value) / setDebugInfo(value) | affichage dans la surcouche de débogage (présent sur le ctx de tous les types ; une GraphicsTexture s’y affiche directement comme image) |
Obsolète drawCarModel(...) / playCarSound(...) / setDataFetchMode(...) restent utilisables, mais le nouveau code doit employer les RenderManager / SoundManager / champs de déclaration ci-dessus. Les deux surcharges de drawConnModel (attelages entre voitures) lèvent NotImplementedException ; drawConnStretchTexture n’est pas exposé dans ce contexte. Aucune de ces deux API n’est utilisable.
VehicleWrapper (vehicle)
| Membre | Description |
|---|---|
getStops() / getThisRouteStops() / getNextRouteStops() | listes de Stop : toutes / ligne courante / ligne suivante |
getNextStopIndex(stopList, overrunTolerance=0.5): int | indice du prochain arrêt dans la liste ; tolérance de dépassement en mètres ; une fois tous passés, retourne stopList.size() |
isStopsDataFetched(): boolean | les données d’arrêts sont-elles parvenues du serveur (toujours false en mode SKIP) |
isRendered() / isCarRendered(cars...) | la rame entière / les voitures indiquées sont-elles rendues (non éliminées par le culling) |
isClientPlayerRiding(): boolean | le joueur local est-il à bord de cette rame |
getId(): long · getSiding(): Siding? · getTransportMode(): TransportMode | id unique / voie de garage de rattachement / mode de transport |
getVehicleId(carIndex): String? · getLength(carIndex) · getWidth(carIndex) · getCarCount() | id de modèle du pack de ressources / longueur et largeur des voitures / nombre de voitures |
getServiceAcceleration() · getServiceDeceleration() · getMaxManualSpeed() · isManualAllowed() | accélération / décélération configurées à la voie de garage et plafond de conduite manuelle |
getSpeedMs() / getSpeedKmh() · getNotchLevel() / getNotchPosition() | vitesse ; cran (-5..5 en régime normal, ±7 = 140 %, -8 = freinage d’urgence) et position normalisée |
getDoorValue(): double · isDoorOpenable(car, pos[, pos2]) · isDoorOpen(car, pos[, pos2]) | ouverture des portes 0..1 ; test, par coordonnées relatives dans la voiture, de l’ouverture possible / effective des portes |
getPathData() · getRailProgress(carIndex=0) · getRailIndex(progress, roundDown) · getRailSpeed(pathIndex) | chemin et progression (MTR 4 ne synchronise qu’une partie du chemin) |
isCurrentlyManual() · isReversed() · isOnRoute() · getManualToAutomaticTime() | conduite manuelle / marche arrière / sur ligne / délai de retour à l’ATO sans conducteur (millisecondes) |
getDepartureIndex(): long · getTotalDwellTime() / getElapsedDwellTime() | indice de départ (remis à zéro à minuit UTC, -1 = pas parti / conduite manuelle) ; durée totale d’arrêt et temps déjà passé à quai (millisecondes, -1 hors quai ; le temps déjà passé est restitué tel que synchronisé par le serveur) |
getMtrVehicle(): VehicleExtension | l’objet véhicule MTR de bas niveau — sous la limite de l’API interne, n’y descendez qu’en connaissance de cause |
Yanyang getDirectionOfDoorOpening(): String | mémoire du sens d’ouverture des portes : "not" / "left" / "right" / "all", voir Extensions Yanyang |
Yanyang getTransportType(): String | id de type de transport canonique (subdivisions MAGIC comprises), voir Extensions Yanyang |
Obsolète : les anciennes interfaces homonymes de NTE (getAllPlatforms / trainCars / speed / doorValue / mtrTrain, etc.) restent toutes disponibles et équivalent, une à une, aux nouvelles interfaces du tableau ci-dessus.
Stop (arrêt)
| Champ / méthode | Description |
|---|---|
route: SimplifiedRoute? · station: Station? · platform: Platform? | ligne / station / quai de rattachement ; null si les données ne sont pas parvenues (pensez à tester la vacuité) |
destinationName: String · customDestination: String? | nom affiché du terminus et texte brut du terminus personnalisé |
routeInterchanges: List<RouteInterchange> | lignes de correspondance de cette station (hors ligne courante et hors lignes masquées) |
connectingInterchanges: Map<String, List<RouteInterchange>> | table des correspondances groupée par « nom de station connectée » (les stations homonymes sont fusionnées) |
dwellTimeMillis: long · distance: double | durée d’arrêt (-1 si inconnue) ; progression sur rail au point d’arrêt (-1 en mode SKIP) |
isRouteSwitchoverStop: boolean · roundUpRoute: Stop | station de changement de ligne ou non ; le cas échéant, roundUpRoute donne la copie de cette même station « vue depuis la ligne suivante » |
turnbackPlatform: boolean | cette station est-elle un point de retournement |
RouteInterchange (ligne de correspondance)
color: int (RGB) · name: String ; champs d’extension Yanyang getTransportMode(): TransportMode?, getTransportType(): String (subdivisions MAGIC comprises), getRouteId(): long (id de la ligne d’origine, 0 si la donnée manque) — voir Extensions Yanyang.
Pour les véhicules bâtis sur les modèles vanilla, hideDisplayParts (MTR 4) / hide_display_cube (MTR 3) masque les blocs d’affichage de destination intégrés, à remplacer par votre propre dessin de script.
Modes de récupération des données du véhicule
MTR 4 ne synchronise que les données proches, alors que les scripts de véhicule veulent souvent les noms de stations de toute la desserte. D’où les trois niveaux de JCM (champ dataFetchMode) :
| Mode | Comportement |
|---|---|
SKIP | Défaut de MTR 4. Ne demande rien au serveur et reconstitue localement, au mieux, une table des arrêts — sans garantie d’exhaustivité. Suffit pour les scripts qui ne regardent que la vitesse, du genre « sonner dès que le train bouge » |
ALL | Récupère d’abord toute la table des arrêts, puis, id par id, les données de stations / lignes. Avant l’arrivée des données, se comporte comme SKIP ; même la table en main, Stop.station / Stop.route peuvent encore valoir null — testez la vacuité |
MANDATORY | Comme ALL, mais le script ne démarre pas du tout tant que toutes les données ne sont pas parvenues. Valeur par défaut du format MTR 3 (les anciens scripts supposent des données immédiatement exploitables et ne tolèrent pas null) |
Scripts d’eyecandy
Rattaché à une entrée de modèle de décoration MTR (Decoration Object) : le script entre en action dès qu’un bloc de décoration arborant ce modèle est rendu. Enregistrement via objects + objectScripts (l’ancien format MTR 3 se déclare dans eyecandy.json) ; isScriptControlled: true sur l’entrée de contenu masque le modèle d’origine et confie tout l’affichage au script.
Trois callbacks : ctx est un EyeCandyScriptContext, eyecandy un EyecandyWrapper.
EyeCandyScriptContext (ctx)
| Membre | Description |
|---|---|
getRenderManager(): RenderManager | référentiel = position du bloc + décalage GUI, rotation GUI comprise |
getSoundManager(): SoundManager | référentiel = position du bloc ; le commode ctx.playSound(id, volume, pitch) joue directement au niveau du bloc |
drawModel(model, matrices?) | dessine le modèle (accepte aussi un DynamicModelHolder) — équivaut à getRenderManager().drawModel(...) |
events(): EyecandyEvents | accès aux événements : clic droit events().onBlockUse.occurred() → traitement → events().handled() pour confirmer ; le détail de l’événement .detail().player() fournit le PlayerEntity acteur (un clic droit pinceau en main ne compte pas) |
setOutlineShape(shape) / setCollisionShape(shape) | contour / boîte de collision personnalisés (VoxelShape) ; sans effet tant que le pinceau est en main |
setDebugInfo(...) | comme pour les autres types |
EyecandyWrapper (eyecandy)
getModelId(): String (id de l’entrée de modèle courante) · getTranslateX/Y/Z(): float (décalage GUI, en mètres) · getRotateX/Y/Z(): float (rotation GUI, en radians) · pos(): Vector3f (bloc + décalage) · blockPos(): Vector3f · facing(): Direction · facingAngle(): double (angle d’orientation) · isCrosshairTarget(): boolean (visé par le réticule — pratique pour afficher une indication) · getFullBrightness(): boolean · redstoneLevel(): int (0–15).
Obsolète : la lecture directe des champs (eyecandy.translateX, eyecandy.prefabId, etc.) reste utilisable.
Accrochez une musique en boucle avec TickableSoundInstance.create(...) + setRepeatable(true) + play() ; dans les 5 secondes qui suivent un clic droit, setSoundVolume(0.25), puis retour à 1.0 au terme des 5 secondes ; stop dans dispose — code complet dans la documentation amont sounds.
Scripts d’ascenseur
Rattaché à une entrée d’habillage de cabine d’ascenseur : le script prend effet lorsque le joueur sélectionne cet habillage avec le rafraîchisseur d’ascenseur. Enregistrement via lifts + liftScripts ; isScriptControlled: true sur l’entrée d’habillage masque le modèle d’origine.
Trois callbacks : ctx est un LiftScriptContext, lift un LiftWrapper.
LiftScriptContext (ctx)
getRenderManager(): RenderManager (référentiel = position de base de l’ascenseur + décalage, pivotant avec l’ascenseur) · getSoundManager(): SoundManager (même référentiel) · setDebugInfo(...).
LiftWrapper (lift) et Floor
| Membre | Description |
|---|---|
getMtrLift(): Lift · getId(): long | l’objet MTR de bas niveau et l’id unique |
getWidth() / getHeight() / getDepth() | dimensions de la cabine (mètres, configurées au rafraîchisseur) |
getOffset(): Vector3f · getPos() / getRawPos() | décalage ; position de base + décalage / position de base |
getAngleDegrees() / getAngleRadians() · isDoubleSided() | angle d’orientation et portes double face |
getDoorValue(): float · getDirection(): int | ouverture des portes 0..1 ; sens de déplacement : -1 descente / 0 arrêt / 1 montée |
isDoorway1Openable() / isDoorway1Open() / isDoorway2... | portes avant / arrière : ouvrables et ouvertes |
isClientPlayerRiding(): boolean · shouldRender(): boolean | à bord ; non éliminé par le culling d’occlusion |
getFloors(): List<Floor> | étages desservis ; chacun fournit getIndex() (0 = le plus bas) · getNumber() / getDescription() (configurés au pinceau d’étage) · getPos() · isCurrentFloor() / isTargetFloor() |
Scripts PIDS
Ce système est incompatible avec PIDS Layout Editor / PIDS Modularization / PIDS JSON (EpicPuppy) ainsi qu’avec la personnalisation des écrans d’information des versions de test initiales de MTR 4 — aucun import mutuel des formats : la marche d’entrée est plus haute, mais des mises en page bien plus complexes sont possibles.
Dans le tableau pids_images de joban_custom_resources.json, un objet de préréglage portant scriptFiles ou scriptTexts devient un préréglage scripté (scriptInput / scriptRateLimit ont la même signification que pour les autres types ; Yanyang accepte aussi d’y mêler du C++ via scriptLocations, voir Entrées mixtes) :
{
"pids_images": [
{
"id": "pids_demo",
"name": "Demo Scripted PIDS",
"scriptFiles": ["jsblock:scripts/pids_demo.js"],
"scriptInput": { "theme": "dark" },
"scriptRateLimit": 0.75
}
]
}
Le champ de preset outOfBound: boolean (amont v2.3.0-prerelease.3) maintient le rendu du preset lorsque l’écran sort du cadre de la caméra — indispensable aux presets projecteur (pids_projector) qui doivent tourner sans interruption.
Ce qui se dessine sur un PIDS, ce sont des polygones dans le monde, pas un canevas 2D — les éléments peuvent déborder de l’écran, et le clipping aux bornes de l’écran est à la charge du script (pour un clipping ou un traitement d’image vraiment complexes, dessinez dans une GraphicsTexture puis plaquez-la). L’ordre de dessin fait la hiérarchie : le dernier dessiné recouvre les précédents ; par défaut, chaque dessin est translaté d’un petit pas le long de z (0.0002) pour éviter le z-fighting — désactivable via ctx.setAutoZOrdering(false) pour passer à un zOrder() manuel (le pas se règle avec ctx.setZOrderStep(m)).
PIDSScriptContext (ctx) et PIDSBlockEntity (pids)
ctx.getRenderManager() / ctx.getSoundManager() (référentiel = position du bloc) · ctx.setAutoZOrdering(b) · ctx.setZOrderStep(m) · ctx.setDebugInfo(...).
| Membre de pids | Description |
|---|---|
type: String | le type d’écran : rv_pids / rv_pids_sil_1 / rv_pids_sil_2 / lcd_pids / pids_projector / pids_1a |
width / height / rows | largeur et hauteur utiles de l’écran, nombre de lignes d’arrivées |
isRowHidden(i) / getCustomMessage(i) | la ligne i est-elle masquée par la configuration du bloc ; texte personnalisé de la ligne (chaîne vide si non défini) |
getTargetPlatformIds() · isPlatformAutoDetected() · isPlatformNumberHidden() | liste des id de quai détectés ; détection automatique ou non ; numéro de quai masqué ou non |
blockPos() · isKeyBlock() · station(): Station? | coordonnées du bloc ; est-ce le bloc « clé » d’un écran double face ; station de rattachement (null si hors station ou inconnue du client) |
arrivals(): ArrivalEntries | les données d’arrivées, voir ci-dessous |
ArrivalEntries et ArrivalEntry
ArrivalEntries : get(i): ArrivalEntry? (au plus 10 arrivées par quai) · length(): int (nombre total d’arrivées, amont v2.3.0-prerelease.3) · mixedCarLength(): boolean · platforms(): List<Platform> · forEach(consumer) / forEach(platformId, consumer) (parcours entrée par entrée, dispense des tests de vacuité manuels).
ArrivalEntry : destination(): String · arrivalTime() / departureTime() (millisecondes Unix, convertissez en date JS avec new Date(v)) · arrived() / departed() · deviation(): long (par rapport à l’horaire théorique : positif = retard, négatif = avance) · realtime(): boolean (horaire théorique ou extrapolation temps réel) · departureIndex(): long · terminating(): boolean (terminus à cette station) · route(): SimplifiedRoute? / routeId() / routeName() / routeNumber() / routeColor() / circularState() · platform(): Platform? / platformId() / platformName() · carCount() / cars(): List<CarDetails>.
Text / Texture / Rect (le trio de dessin)
Tous trois sont des objets fluent « création → configuration en chaîne → draw(ctx) » ; Rect est un Texture dont la texture est codée en dur en PNG blanc pur :
| Commun | Description |
|---|---|
create() / create(comment) | création (le commentaire sert uniquement à la lisibilité du code) |
pos(x, y) · size(w, h) | position et taille (en pixels écran) |
color(rgb) | teinte (entier RGB) |
naturalLight() · renderType(str) · matrices(m) · zOrder(n) · draw(ctx) | luminosité selon la lumière du monde (pleine luminosité par défaut) ; type de rendu (les mêmes valeurs que QuadDrawCall) ; transformation additionnelle ; ordre z manuel (actif une fois l’ordre automatique désactivé) ; soumission du dessin |
| Propre à Text | Description |
|---|---|
text(str) · scale(i) · fontMC() / font(id) | contenu ; mise à l’échelle globale (1 par défaut, à vous de faire la conversion avec size) ; police native de MC / police de ressource (mtr:mtr par défaut, sans effet si l’option « polices personnalisées » de MTR est désactivée) |
leftAlign() / centerAlign() / rightAlign() | alignement (gauche par défaut) |
shadowed() · italic() · bold() · lineHeight(factor) | ombre / italique / gras ; facteur d’interligne (pour le passage à la ligne, 1 par défaut = 9 unités) |
stretchXY() / scaleXY() / wrapText() / marquee() / marquee(durationTick) / withMarqueeProgress(p) | quatre stratégies de débordement au choix : étirement sur les axes de débordement / mise à l’échelle homothétique / passage à la ligne / défilement marquee (période à préciser, ou progression 0..1 fournie directement) |
measureWidth(): int | mesure la largeur selon la configuration courante — retourne un int, la chaîne s’arrête là |
Propre à Texture : texture(id) (PNG ou mcmeta), uv(u1, v1, u2, v2) (0–1).
Un PIDS accepte lui aussi une GraphicsTexture : new GraphicsTexture(pids.width, pids.height) dans create ; dans render, dessinez, upload(), puis Texture.create().texture(state.tex.identifier).size(pids.width, pids.height).draw(ctx) ; close() dans dispose — la solution de repli quand la composition de texte ne suffit plus, au prix de performance et de mémoire.
Pas besoin de script ? Remplissez uniquement les champs JSON background / color / fonts / showWeather / showClock / hidePlatform / hideRow / topPadding / textOverflowMode, etc. dans pids_images : c’est un « préréglage JSON » — suffisant pour relooker simplement, l’alternative au préréglage scripté (jamais les deux à la fois).
Scripts PSD (portes palières)
Des scripts pour les vitres et bandeaux supérieurs des portes palières. Déclarez-les sous la catégorie psdScripts de mtr_custom_resources.json — même forme d’entrée que vehicleScripts (scriptLocations mélange librement JS et C++ avec détection par entrée), plus un champ type choisissant la surface :
"psdScripts": [
{
"id": "psd_glass_pids",
"type": "glass",
"scriptLocations": [
"mtr:my_pack/js/psd_glass_pids/main.js",
"mtr:my_pack/native/psd_glass_pids"
],
"dataFetchMode": "ALL"
}
]
En jeu, utilisez le nouvel objet Éditeur de scripts PSD (même onglet créatif que le pinceau de MTR) : clic droit sur une vitre de porte palière (y compris vitres d’extrémité et portillons APG à mi-hauteur — MTR, Tianjin Metro et tout addon bâti sur les classes PSD de MTR) ou un bloc psd_top, puis choisissez une entrée du type correspondant ou n’importe quel preset PIDS scripté (les scripts PIDS tournent tels quels ; le canevas s’adapte à la surface PSD). « Aucun script » supprime la liaison. Les liaisons sont stockées côté serveur et synchronisées à tous les joueurs de la dimension.
- Vitre : un fond opaque est ajouté — noir côté quai (écran sombre façon PIDS réel), blanc uni côté voies (les passagers depuis l’intérieur d’un train voient un panneau blanc uni, jamais le contenu en miroir). Le canevas du script se trouve côté quai, à l’intérieur du vitrage, et est découpé selon la fenêtre transparente du vitrage (dérivé du modèle et de l’alpha de la texture du pack — cadres et montants restent visibles devant et masquent le contenu) ; tout le tronçon de vitrage entre deux portes partage un seul canevas, et une colonne pleine hauteur sur deux blocs fusionne verticalement. Le canevas s’adapte à la fenêtre (96 px par bloc).
- Bandeau supérieur : toute la bande psd_top d’un même passage de portes partage un canevas unique — cette bande n’affiche plus la flèche / la ligne intégrée et son contenu est entièrement contrôlé par le script ; les autres bandes conservent le rendu d’origine. Canevas : 96 px de large par bloc (moins une marge de 0,125/16 de chaque côté) × 42 px de haut.
Une section continue de vitres ou de panneaux supérieurs d’au plus 64 blocs conserve un canevas complet, même si elle traverse une limite de grille. Les sections plus longues sont divisées en intervalles alignés de 64 blocs suivant la coordonnée mondiale signée dans le sens droit du canevas ; les intervalles aux extrémités peuvent être plus courts. L’association, la dissociation et le masquage du contenu original des panneaux utilisent tous la même sous-section.
Le dessin est identique aux scripts PIDS (Text / Texture / Rect + ctx.draw ; l’origine du canevas est le coin supérieur gauche de la face visible). L’API de données (argument JS psd / C++ mtr::Psd ; accesseurs complets dans Instantanés de données · Psd) :
| JS | Description |
|---|---|
psd.type() / psd.width() / psd.height() | Surface liée (« glass » / « top ») et taille du canevas |
psd.platform() | Quai : numéro / nom (number() / name()), station, temps d’arrêt |
psd.routes() | Toutes les lignes desservant le quai ; avec dataFetchMode: "ALL", chacune porte sa liste de stations complète (correspondances / sorties) |
psd.arrivals() | Trains à venir : terminus, ligne, nombre de voitures, décompte (arrivalCountdown()), les numéros de portes qui s’ouvriront (doorNumbers()), état temps réel / terminus / circulaire |
psd.stop() | Renseignements complets sur la gare (ABI 13) : nom, couleur (ARGB + hexadécimal), zones tarifaires 1–3, toutes les lignes desservant la gare, lignes des gares de correspondance (regroupées par nom de gare), noms des gares physiquement connectées, sorties avec destinations, numéros de quai + nombre — null en l’absence de quai / de gare. Contrepartie C++ : has_station() + les accesseurs mtr::PsdStation (voir Instantanés de données · Psd) |
psd.doors() / psd.doorValue() / psd.isDoorOpen() | Liste des unités de porte (numérotées à partir de 1, états ouverture / verrouillage en direct) et ouverture globale du mur |
Côté C++, c’est la cinquième classe de base mtr::PsdScript<State> (MTR_REGISTER_PSD_SCRIPT) avec l’instantané JcmPsdSnapshot (ABI 12 ; entrées de gare depuis l’ABI 13) ; voir native/examples/psd_glass.cpp.
DisplayHelper (l’assistant multi-écrans intégré)
La solution clé en main pour les écrans multiples embarqués : après include(Resources.id("mtrsteamloco:scripts/display_helper.js")) —
let slotCfg = {
"version": 1,
"texSize": [2048, 1024],
"slots": [
{ "name": "lcd_north", "texArea": [0, 0, 2048, 274],
"pos": [[[-0.59, 2.125, -1.75], [-0.755, 2.03, -1.75],
[-0.755, 2.03, -3.25], [-0.59, 2.125, -3.25]]],
"offsets": [[0, 0, -5], [0, 0, 0], [0, 0, 5], [0, 0, 10]] }
]
};
let dhBase = new DisplayHelper(slotCfg);
function create(ctx, state, vehicle) {
state.rl = new RateLimit(0.05);
state.dh = dhBase.create(); // instancie et lie la texture
}
function render(ctx, state, vehicle) {
if (state.rl.shouldUpdate()) {
let g = state.dh.graphicsFor("lcd_north"); // le canevas de l'emplacement (décalage automatique)
// ... dessin AWT ...
state.dh.upload();
}
for (let car of ctx.getMyCars())
ctx.getCarRenderManager(car).drawModel(state.dh.model, null); // à dessiner à chaque frame
}
function dispose(ctx, state, vehicle) {
state.dh.close();
}
- Tous les emplacements partagent une grande texture
texSize;texAreadésigne la zone qu’occupe chaque emplacement (x, y, w, h). posest un tableau à trois dimensions : les quatre coins de chaque écran (haut-gauche → bas-gauche → bas-droite → haut-droite), en mètres (les unités 1/16 de Blockbench se divisent par 16), l’origine au centre du train, à hauteur de plancher, +X vers la gauche / +Y vers le haut / +Z vers l’arrière.offsetsduplique les écrans de même contenu (économie de texture pour les rangées de petits écrans au-dessus des portes) ;graphicsFor(name)fournit le canevas déjà décalé,graphics()la texture entière.- Placez le téléversement dans le
RateLimit, et le dessin du modèle à l’extérieur — le modèle doit être soumis à chaque frame.
Ensuite : Extensions Yanyang — entrées mixtes JS+C++, mémoire du sens d’ouverture des portes, subdivision des types de transport et téléversement de pixels par écriture directe.