RTS Terrain Studio
Baue RTS-Karten im klassischen Stil mit Plateaus, scharfkantigen Klippen, Rampen, texturiertem Boden, Wäldern, Wasser und Dekor – im Unity Editor und direkt in deinem Spiel. Nebel des Krieges, Wegfindung und Terrain-Abfragen sind bereits eingebaut.
Überblick
RTS Terrain Studio ist ein Karteneditor für Echtzeitstrategiespiele im klassischen Stil, entwickelt für Unity 6.3 LTS und die Universal Render Pipeline (URP) 17.3. Damit modellierst du Plateaus mit scharfkantigen, senkrechten Klippen, texturierst den Boden, pflanzt Wälder, flutest Seen und Flüsse, platzierst Dekorobjekte, markierst die Startpositionen der Spieler und prüfst, wo Einheiten laufen und Gebäude stehen können. Wenn du fertig bist, ist dieselbe Karte direkt spielbereit: Nebel des Krieges, Wegfindung und Terrain-Abfragen sind vorhanden, und du brauchst dafür nie einen Collider.
Den Editor gibt es an zwei Stellen. Beide verwenden dieselben Werkzeuge und dasselbe Kartendateiformat (.rtsmap):
- Im Unity Editor über das Fenster und die Scene View. Hier baust du die Karten, die du mit deinem Spiel auslieferst.
- Im Spiel über die Komponente
RTSRuntimeEditor. Das ist ein vollständiger Editor mit einer Oberfläche auf Basis von UI Toolkit. Deine Spieler und Modder können damit in deinem fertigen Spiel Karten erstellen, speichern, laden und per Spieltest ausprobieren.
Das Paket enthält keine Grafiken von Drittanbietern. Stattdessen erzeugt ein einziger Menübefehl einen vollständigen Satz an Demo-Inhalten: Texturen, Low-Poly-Bäume und -Props, zwei Tilesets, drei Karten und drei spielbereite Szenen. Schon eine Minute nach dem Import hast du etwas zum Ausprobieren.
Dieses Handbuch richtet sich an alle – von deiner ersten Karte bis zur Erweiterung des Quellcodes. Wenn du neu dabei bist, folge unten dem Abschnitt Deine ersten fünf Minuten und lies danach die Kapitel der Reihe nach. Wenn du programmierst: Der Quellcode ist vollständig kommentiert, jeder öffentliche Typ hat eine XML-Dokumentation, und der Abschnitt Ordnerstruktur zeigt, wo alles liegt.
Wie eine Karte aufgebaut ist
Wenn du schon mit dem Karteneditor eines klassischen RTS-Spiels gearbeitet hast, werden dir die meisten dieser Konzepte vertraut vorkommen. Eine Karte ist ein Raster aus quadratischen Zellen (standardmäßig 2 m breit). Die Ecken der Zellen sind die Vertices, und die meisten Daten werden pro Vertex oder in hochauflösenden Texturen gespeichert, die die ganze Karte abdecken.
| Konzept | Bedeutung |
|---|---|
| Klippenstufe | Eine ganzzahlige Höhenstufe von 0 bis 15 für jeden Vertex. Benachbarte Vertices auf unterschiedlichen Stufen bilden eine senkrechte Klippenwand. Die Standard-Stufenhöhe beträgt 2 m, und neue Karten beginnen auf Stufe 2, damit du für Seen und Flüsse nach unten graben kannst. |
| Feinhöhe | Ein stufenloser Höhenversatz über der Klippenstufe für Hügel und Täler. Die Bodenhöhe eines Vertex ergibt sich aus CliffLevel × CliffLevelHeight + Height. |
| Rampe | Zellen, deren Ecken mit dem Rampen-Flag markiert sind, gehen weich von einer Stufe in die nächste über, sodass Einheiten hinauflaufen können. |
| Klippentyp | Das Material der Klippenwände (zum Beispiel Fels oder grasbewachsene Klippen). Ein Tileset enthält bis zu 4. |
| Bodenschicht | Eine von bis zu 16 aufgemalten Bodentexturen (Gras, Erde, Fels …), gespeichert in Splatmaps. |
| Wasser | Eine Höhe der Wasseroberfläche pro Vertex. Die Tiefe entscheidet, ob es sich um Flachwasser (begehbar) oder Tiefwasser (nicht begehbar) handelt. |
| Details und Bäume | Aufgemaltes Gras und Blumen (Dichte pro Texel) sowie einzeln platzierte Bäume. |
| Dekor | Jedes Prefab, das du auf der Karte platzierst: Felsen, Ruinen, Kisten, Zäune, Gebäude. |
| Pathing | Für jede Pathing-Zelle: begehbar, bebaubar, befliegbar, Flach- oder Tiefwasser, Klippe, blockiert oder steil. Das Pathing wird automatisch aus der Karte abgeleitet, und du kannst Overrides darübermalen. |
| Marker | Benannte Regionen (Rechtecke mit Schlüssel-Wert-Eigenschaften für deine Spiellogik), Startpositionen der Spieler und benannte Punkte wie Kamerapositionen. |
| Tileset | Ein RTSTileset-Asset, das das Aussehen festlegt: Bodenschichten, Klippentypen und den Wasserstil – wie die Tilesets „Summer“ oder „Winter“ klassischer RTS-Karteneditoren. |
| Bibliotheken | Eine RTSVegetationLibrary (Details und Bäume) und eine RTSDecorLibrary (Dekor-Prefabs). Sie sind die Paletten, aus denen du malst und platzierst. |
In einer Szene liegt eine Karte auf einem GameObject mit der Komponente RTSTerrain. Der Menübefehl
fügt ihm außerdem die Komponenten der Subsysteme hinzu
(RTSVegetationRenderer, RTSWaterRenderer und RTSDecorManager). Das Terrain liest die
Karte aus einer .rtsmap-Datei in deinem Projekt (einem RTSMapAsset). Die Chunk-Meshes, das Gras und das Wasser,
die es anzeigt, werden aus dieser Datei erzeugt. Sie werden nie in der Szene gespeichert, deshalb bleiben deine Szenen klein.
Deine ersten fünf Minuten
So siehst du am schnellsten, wie alles zusammenspielt. Außer dem Import des Pakets ist keine Einrichtung nötig.
- Öffne und klicke auf Fix All. Dadurch werden URP, das Renderer Feature für den Nebel des Krieges, die vom Wasser genutzten Kameratexturen und der Farbraum eingerichtet. Ein Bestätigungsdialog erklärt die Änderungen, bevor etwas angepasst wird.
- Führe aus. Während Texturen, Modelle, Tilesets, Karten und Szenen erzeugt werden, erscheint ein Fortschrittsbalken. Danach ist die Szene RTS Terrain Studio - Gameplay Demo im Project-Fenster ausgewählt.
- Doppelklicke auf diese Szene, um sie zu öffnen, und drücke dann auf Play.
- Wähle deine Einheiten mit der linken Maustaste aus (anklicken oder ein Auswahlrechteck aufziehen) und klicke mit der rechten Maustaste auf den Boden, um sie zu bewegen. Bewege die Kamera mit WASD oder den Pfeiltasten, zoome mit dem Mausrad und drücke F1, um die gesamte Steuerung anzuzeigen.
- Beende den Play Mode und öffne die Szene RTS Terrain Studio - Editor Playground. Öffne , wähle ein Werkzeug und male in der Scene View. Halte Shift gedrückt, um das Gegenteil zu bewirken (zum Beispiel absenken statt anheben). Drücke Ctrl+Z, um einen Schritt rückgängig zu machen.
- Öffne die Szene RTS Terrain Studio - In-Game Editor Demo und drücke auf Play. Das ist der Editor, den deine Spieler bekommen. Drücke F5, um die Karte mit Einheiten im Spiel zu testen, und F2, um den Editor ein- oder auszublenden.
Das Fenster öffnet sich einmal pro Projekt und zeigt dieselben Schritte als Schaltflächen. Über das Menü kannst du es jederzeit wieder öffnen.
Wo du anfängst
| Ich möchte … | Hier anfangen |
|---|---|
| Ein Projekt richtig einrichten | Einrichtung und |
| Schritt für Schritt meine erste Karte erstellen | Schnellstart und |
| Das Editorfenster und alle Werkzeuge kennenlernen | Editorfenster und Scene View |
| Spieler in meinem Spiel Karten bauen lassen | In-Game-Editor |
| Mein Spiel oder den Karteneditor im Webbrowser veröffentlichen | Web-Builds (WebGL) |
| Die Karte verbergen, bis Einheiten sie erkunden | Nebel des Krieges |
| Mirror, das A* Pathfinding Project oder Unity NavMesh verwenden | Integrationen |
| Das Terrain abfragen oder eigene Werkzeuge in C# schreiben | Scripting |
| Etwas beheben, das nicht richtig aussieht oder funktioniert | Fehlerbehebung |
Funktionen
Jedes der folgenden Bearbeitungswerkzeuge gibt es sowohl im Unity Editor als auch im In-Game-Editor, denn beide verwenden dieselben
Werkzeugklassen (Unterklassen von RTSTool). Die Namen in dieser Liste entsprechen denen, die du in der Benutzeroberfläche siehst.

Terrain und Klippen
- Karten mit 16 bis 1024 Zellen pro Seite, in Schritten von 16. Der New Map Wizard bietet die Presets Tiny (64), Small (96), Medium (128), Large (192), Huge (256), Epic (384) und Epic XL (512) oder eine beliebige eigene Größe. Die Zellgröße und die Stufenhöhe der Klippen lassen sich frei einstellen.
- 16 Klippenstufen (0–15) mit prozedural erzeugten, abgerundeten Klippenwänden. In jeder Klippenzelle wird auf einem Unterraster
eine Kontur nachgezogen, und Rauschen sowie Schrägstellung lassen die Wände wie natürlichen Fels aussehen (all das wird in
RTSTerrainSettingseingestellt). - Feinhöhe über den Stufen, sodass Plateaus Hügel haben können. Glatter Boden kann 1 bis 4 Quads pro Zellseite verwenden: 1 ergibt den klassischen RTS-Look, 2 bis 4 ergeben weichere Hügel.
- Werkzeuge der Kategorie Height: Raise / Lower, Smooth, Flatten, Set Height, Noise, Terrace, Erode.
- Werkzeuge der Kategorie Cliffs: Raise / Lower Level, Set Level, Ramp, Cliff Type, Clean Cliffs.
- Bis zu 4 Klippentypen pro Tileset, pro Vertex gemalt, mit Triplanar-Texturierung an den Wänden.
- Ein optionaler
MeshColliderpro Chunk auf einem Layer deiner Wahl. Abfragen ohne Physik funktionieren auch ohne ihn:GetHeight,GetNormal,Raycast,GetCliffLevel,IsWater,GetPathing,IsWalkableundIsBuildable. - Beliebig viele Terrains in einer Szene. Jedes Terrain hat seine eigene Position und Y-Rotation.
RTSTerrain.FindAt(worldPosition)undRTSTerrain.RaycastAll(ray, out hit)finden das richtige. - Ändere die Größe einer Karte in beide Richtungen (vergrößern oder verkleinern), ausgehend von einem Anker. Importiere und exportiere Heightmaps als 16-Bit-PNG oder 16-Bit-RAW, und exportiere die Splatmaps als PNG.
Malen
- 16 Bodenschichten pro Tileset, gemalt in vier RGBA-Splatmaps. Standardmäßig gibt es 4 Splat-Texel pro Zelle (wählbar von 1 bis 8), deshalb sind gemalte Grenzen viel feiner als das Zellraster.
- Malwerkzeuge: Paint Layer (weicher Pinsel), Tile Paint (hartes, an Zellen ausgerichtetes Malen im klassischen RTS-Stil), Fill / Replace, Smooth Paint und Auto Paint.
- Auto Paint malt nach Regeln, die in einem
RTSAutoPaintProfile-Asset gespeichert sind. Eine Regel kann Höhe, Neigung, Klippenstufe, Abstand zu Klippen, Abstand zu Wasser und Rauschen prüfen. Verwende das Werkzeug Auto Paint als Pinsel, oder lasse den Kartengenerator ein Profil auf die ganze Karte anwenden. - Höhenbasierte Überblendung zwischen Schichten, Anti-Tiling pro Schicht (verhindert sichtbare Wiederholungen gekachelter Texturen), eine Makrovariation für das ganze Tileset sowie Tönung, Glätte, Metallic-Wert und Normalenstärke pro Schicht.
- Eine Schicht kann das Pathing dort ändern, wo sie dominiert. Die Demo-Tilesets machen zum Beispiel Rock und Blight nicht bebaubar, wie die nicht bebaubaren Kacheln klassischer RTS-Spiele.
- Pinsel: Circle, Square, Diamond oder Custom mit einer beliebigen Textur als Stempel (
RTSBrushShape-Assets). Du kannst Radius, Stärke, Härte, eine optionale Abnahmekurve, Rotation, zufällige Rotation, Abstand und Streuung einstellen. - Symmetrisches Malen für faire Wettkampfkarten: Mirror X, Mirror Z, Mirror XZ, Rotate 2 (180°-Punktsymmetrie), Rotate 4 (90°, nur quadratische Karten) und Mirror Diagonal (nur quadratische Karten). Die Symmetrie funktioniert mit jedem Werkzeug, auch beim Platzieren von Objekten.
Vegetation
- Details wie Gras, Blumen und kleine Pflanzen, als Dichte pro Schicht gemalt und auf der GPU als Meshes oder gekreuzte Quads gezeichnet, mit Windbewegung.
- Bäume, einzeln platziert oder mit einem Pinsel verstreut. Ein Baum kann per GPU-Instancing gezeichnet (mit optionalem Collider-Proxy) oder als GameObject gespawnt werden, etwa für Gameplay wie das Abbauen von Holz.
- Bäume blockieren das Pathing automatisch über ihre Grundfläche.
- Vegetationswerkzeuge: Paint Details, Erase Details, Place Trees, Erase Trees.
- Der Shader
RTS Terrain Studio/Vegetationsorgt für Wind, Transluzenz und eine Tönung pro Instanz. Jedes andere Material mit GPU-Instancing funktioniert ebenfalls. - Qualitätseinstellungen in
RTSVegetationRenderer: Multiplikatoren für Detaildichte, Detaildistanz und Baumdistanz.
Wasser
- Eine Wasserhöhe pro Vertex, sodass Seen auf unterschiedlichen Höhen liegen können. Wasser wird als in Chunks aufgeteilte, transparente Meshes gezeichnet, die von Burst-Jobs gebaut werden.
- Flach- und Tiefwasser ergeben sich aus der Tiefe (
WaterProfile.shallowDepthLimit). Flachwasser ist begehbar, Tiefwasser nicht – wie in klassischen RTS-Spielen. - Der Shader
RTS Terrain Studio/Waterbietet tiefenabhängige Färbung, bewegte Normalen, Wellen, Lichtbrechung, Uferschaum und ein weiches Ausblenden am Ufer. Der Stil kommt aus demWaterProfiledes Tilesets. - Wasserwerkzeuge: Paint Water, Erase Water, Flood Fill.
Dekor (Doodads)
- Jedes Prefab kann Dekor sein. Prototypen liegen in einer
RTSDecorLibrarymit automatisch erzeugten Vorschaubildern. - Jeder Prototyp wird entweder als GameObject gespawnt (für Objekte, mit denen das Gameplay interagiert) oder per GPU-Instancing gezeichnet (für viele statische Props).
- Zufällige Rotation, Skalierung und Tönung, Ausrichtung am Hang, Einrasten am Raster und eine Pathing-Grundfläche (Kreis oder Rechteck).
- Dekorwerkzeuge: Place Decor (einzeln oder verstreut), Erase Decor, Select Decor (verschieben, drehen, skalieren, duplizieren, löschen).
Pathing und Wegfindung
- Eine Pathing-Karte, die automatisch abgeleitet und während der Bearbeitung aktuell gehalten wird. Sie berücksichtigt Klippen, Neigung, Wassertiefe, die Grundflächen von Bäumen und Dekor sowie Regeln der Bodenschichten.
- Pathing-Werkzeuge: Paint Pathing (Overrides wie nicht begehbar, nicht bebaubar oder erzwungen begehbar) und Clear Pathing. Pathing-Overlays in den Editoren zeigen das Ergebnis.
- Ein integrierter Pathfinder (
RTSPathfinder): Burst-kompilierte A*-Suche auf dem Pathing-Raster mit 8 Nachbarn, ohne Abschneiden von Ecken, mit Berücksichtigung des Einheitenradius (Freiraum) und mit Pfadglättung. Er braucht kein Paket von Drittanbietern. - Asynchrone Pfadanfragen für viele Einheiten über
RTSPathfindingService.
Nebel des Krieges
- Klassische RTS-Regeln: Jedes Team hat seine eigene Sicht, und erkundete Gebiete bleiben bekannt. Standardmäßig sieht eine Einheit
nur ihre eigene Klippenstufe und tiefere Stufen. Auch Bäume und Dekor können die Sicht blockieren
(
LineOfSightModeund die OptionblocksVisioneines Prototyps). - Bis zu 32 Teams, geteilte Sicht zwischen Verbündeten (
RTSFogOfWarManager.SetAlliance), eine glatte, weichgezeichnete Nebeltextur und Unterstützung für die Minikarte. - Ein Renderer Feature für URP (
RTSFogOfWarRendererFeature) verdunkelt alles auf dem Bildschirm, unabhängig vom verwendeten Material. Fehlt das Feature, wenden die Shader des Pakets den Nebel selbst an. - Einheiten mit
RTSFogOfWarRevealerdecken die Karte auf, und Objekte mitRTSFogOfWarHideablewerden ausgeblendet, solange sie nicht sichtbar sind. Gebäude und Props können nach ihrer Entdeckung als „Geisterbilder“ sichtbar bleiben. - Die Sichtbarkeit wird von Burst-Jobs berechnet.
In-Game-Editor
- Füge ihn über hinzu. Er bietet eine Oberfläche auf Basis von UI Toolkit mit einer Menüleiste, Kategorie- und Werkzeugleisten, einem Werkzeug-Panel mit Paletten und Vorschaubildern, einer Statusleiste, Tooltips und Benachrichtigungen.
- New, Open, Save und Save As mit einem Kartenbrowser. Karten werden standardmäßig unter
Application.persistentDataPathgespeichert, in Web-Builds im Browserspeicher. Mit Download und Import überträgst du sie auf den Computer und wieder zurück. Außerdem kannst du eine Karte generieren, die Karteninfo bearbeiten und die Kartengröße ändern. - Rückgängig und Wiederherstellen mit Speicherbudget, Tastenkürzel, Symmetrie sowie Overlays für Raster, Pathing und Marker.
- Ein Spieltest-Schalter (F5), der das Beispiel-Gameplay auf der gerade bearbeiteten Karte startet und danach zum Editor zurückkehrt.
- Eine eigene Editorkamera (
RTSRuntimeEditorCamera) und eigene Gizmos, dazu keine Abhängigkeit von derUnityEditor-API, deshalb funktioniert er auch in Player-Builds.
Kartengenerator
- Ein prozeduraler Generator (
RTSMapGenerator) mit den Stil-Presets Plains, Hills, Highlands, Islands, Canyon und Arena. Er arbeitet mit einem Seed, also einem Startwert für den Zufallsgenerator. - Er erzeugt Plateaus und Klippen, Rampen (mit Prüfung der Erreichbarkeit), Seen und Meere, einen Kartenrand, automatische Texturierung, Details, Wälder, Dekor und Startpositionen – in jedem Symmetriemodus.
- Derselbe Seed ergibt immer dieselbe Karte. Du kannst Einstellungen als
RTSMapGeneratorPreset-Assets speichern. - Du kannst ihn aus dem New Map Wizard, dem Editorfenster, dem In-Game-Editor oder deinem eigenen Code starten.
Beispiel-Gameplay
- Eine RTS-Kamera (Schwenken, Rand-Scrollen, Zoom, Rotation, Kartengrenzen), Einheitenauswahl (Klick, Rechteckauswahl, Shift, Doppelklick, Kontrollgruppen) und Befehle (Bewegen in Formation, Befehlswarteschlange, Stopp, Halten, Patrouillieren).
- Platzhaltereinheiten, die der Terrain-Höhe folgen, den Nebel des Krieges aufdecken und mit jedem Pfad-Provider funktionieren.
- Eine Minikarte, über die du die Kamera per Klick bewegst, ein HUD, eine Demo zur Gebäudeplatzierung, die das Bebaubarkeits-Flag beachtet, und eine Demo zum Abbauen von Bäumen.
RTSDemoGamerichtet all das auf jedem Terrain mit Startpositionen ein. Füge es über hinzu.
Integrationen
Alle Integrationen sind optional. Jede einzelne wird nur kompiliert, wenn ihre Abhängigkeit installiert ist. Deshalb führt das Entfernen der Abhängigkeit nie zu Fehlern in deinem Projekt.
| Integration | Was sie ergänzt |
|---|---|
| Mirror (Multiplayer) | Der Host streamt seine Karte an beitretende Clients, sodass Spieler selbst erstellte Karten online spielen können; heruntergeladene Karten werden zwischengespeichert. Einheiten sind serverautoritativ, jeder Client sieht den Nebel des Krieges seines eigenen Teams, und RTSFogOfWarInterestManagement sendet nie Gegner, die vom Nebel verborgen sind (Anti-Maphack). |
| A* Pathfinding Project | Grid Graphs, die aus der RTS-Pathing-Karte erzeugt und nach jeder Bearbeitung inkrementell aktualisiert werden, ein Graph pro Einheitengröße, und Einheiten, die von AIPath mit lokaler RVO-Kollisionsvermeidung bewegt werden. |
| Unity AI Navigation (NavMesh) | Ein NavMesh, das aus den Terrain-Chunks und der Pathing-Karte gebaut und nach Bearbeitungen inkrementell aktualisiert wird, sowie ein Pfad-Provider, der NavMesh-Pfade liefert oder NavMeshAgents steuert. |
Arbeitsablauf im Editor
- Ein andockbares Fenster RTS Terrain Studio mit Tabs für die 8 Werkzeugkategorien sowie Map und Settings. Es bietet große Werkzeugschaltflächen mit Icons, kontextabhängige Einstellungen, Paletten mit Vorschaubildern und eine Statusleiste.
- Integration in die Scene View. Der Pinsel wird vom Terrain-Shader gezeichnet und auf das Terrain projiziert. Dazu kommen Overlays für Raster und Pathing, Symmetrie-Hilfslinien und Tastenkürzel: [ / ] Pinselgröße, - / = Stärke, 1–8 Werkzeugkategorien, G Raster, P Pathing-Overlay und R zufällige Rotation. Du kannst sie unter (unter macOS ) neu belegen.
- Änderungen an der Karte sind Teil von Unitys Undo-Verlauf, deshalb funktionieren Ctrl+Z und Ctrl+Y wie gewohnt und zeigen die Namen der Schritte an. Das Speicherbudget für den Undo-Verlauf lässt sich einstellen.
- Eigene Inspectors für Tilesets (Schichten per Drag-and-drop, Vorschauen, automatischer Aufbau der Textur-Arrays), Vegetations- und
Dekorbibliotheken, Pinselformen, Auto-Paint-Profile und die Komponente
RTSTerrain. Der Importer für.rtsmap-Dateien zeigt eine Vorschau der Minikarte. - Ein Welcome-Fenster, ein New Map Wizard und ein Projektvalidator mit Korrekturen per Klick.
Performance
- Terrain-Meshes werden in Chunks von 16, 32 (Standard) oder 64 Zellen aufgeteilt. Sie werden mit
Mesh.MeshDataArrayin Burst-Jobs gebaut, und nur die Chunks, die du bearbeitest, werden neu gebaut. - Die rechenintensive Arbeit läuft in Burst-kompilierten Jobs (Unity Jobs System): Pinseloperationen, Mesh-Erzeugung, Ableitung des Pathings, Freiraum, A*, Nebel des Krieges, Vegetationsinstanzen, Wasser-Meshes, Kartengenerator, automatische Texturierung und das Backen der Minikarte.
- Details werden prozedural mit
Graphics.RenderMeshPrimitivesgezeichnet. Bäume und instanziertes Dekor verwendenGraphics.RenderMeshInstanced. Bei allen erfolgt das Culling pro Patch anhand von Frustum und Entfernung. - Die Shader sind mit dem SRP Batcher kompatibel, und alle Chunks teilen sich ein einziges Terrain-Material.
- Textur-Arrays werden für die aktive Plattform komprimiert (BC7, ASTC 6×6, ETC2 oder DXT5), in einer Auflösung deiner Wahl (256–2048).
- Keine Garbage-Allokationen pro Frame in den Hot Paths, und der gesamte native Speicher wird freigegeben, wenn Komponenten deaktiviert werden.
Designentscheidungen auf einen Blick
Hier findest du die häufigsten Fragen zu Terrain-Werkzeugen für RTS-Spiele und wie RTS Terrain Studio sie beantwortet.
| Frage | Antwort |
|---|---|
| Wie viele Bodentexturen kann eine Karte verwenden? | 16 Bodenschichten und 4 Klippentypen pro Tileset, und du kannst beliebig viele Tilesets anlegen. Diese Grenze ist technisch begründet: 16 Schichten passen genau in 4 RGBA-Splatmaps (ein Kanal pro Schicht), und alle 16 Bodenschichten und 4 Klippenschichten werden in zwei Textur-Arrays gepackt (Albedo/Höhe und Normalen, je 20 Slices). Dadurch rendert ein einziges Terrain-Material jeden Chunk. |
| Hat das Terrain Collider? Kann ich es ohne Physik abfragen? | Beides ist möglich. Jeder Chunk kann einen MeshCollider auf einem Layer deiner Wahl erhalten (RTSTerrainSettings.colliderMode).
Die Abfrage-API (GetHeight, GetNormal, Raycast, GetCliffLevel,
IsWater, TryGetWaterHeight, GetPathing, IsWalkable,
IsBuildable) verwendet überhaupt keine Physik. Sie funktioniert im Edit Mode, zur Laufzeit und bei
ausgeschalteten Collidern und liefert genau die Oberfläche, die du siehst. |
| Kann ich große Welten aus mehreren Karten aufbauen? | Ja. Eine Szene kann beliebig viele RTSTerrain-Komponenten enthalten, jede mit eigener Position und Y-Rotation.
RTSTerrain.All, RTSTerrain.FindAt und RTSTerrain.RaycastAll arbeiten über alle Terrains hinweg.
Dank des Chunk-basierten Renderings sind Karten mit 512×512 Zellen praxistauglich. |
| Landet der Pinsel genau unter dem Cursor, auch mit Bildschirmskalierung? | Ja. Der Unity Editor verwendet HandleUtility.GUIPointToWorldRay, das die DPI-Skalierung korrekt berücksichtigt, und der
In-Game-Editor verwendet Camera.ScreenPointToRay mit der Zeigerposition aus dem Input System. Den Pinselcursor
zeichnet der Terrain-Shader und projiziert ihn dabei auf die Oberfläche. Du malst also genau dort, wo du ihn siehst. |
| Welche Texturformate und -größen kann ich verwenden? | Gewöhnliche Unity-Texturen in beliebiger Größe und beliebigem Format. Der Tileset-Inspector skaliert sie für dich und packt sie in
Texture2DArray-Assets (256 bis 2048 Pixel pro Slice, komprimiert oder unkomprimiert). Die Kanalbelegung ist
dokumentiert: Albedo in RGB mit optionaler Höhe im Alphakanal oder in einer separaten Höhentextur, dazu eine Normal-Map. |
| Muss das Paket in einem festen Ordner liegen? | Nein. Der Editor findet den Stammordner des Pakets über dessen Runtime-Assembly-Definition. Du kannst
RTSTerrainStudio also an eine beliebige Stelle unter Assets verschieben (zum Beispiel nach Assets/Plugins/RTSTerrainStudio). |
| Kann ich die Kartengröße später ändern? | Ja. Wähle im New Map Wizard eine beliebige Größe und vergrößere oder verkleinere die Karte später mit Resize. Dabei wählst du einen Anker, der festlegt, an welchen Seiten Zellen hinzugefügt oder entfernt werden. |
| Kann ich den Code lesen und ändern? | Ja. Der Code ist in Assembly-Definitionen mit jeweils einem Namespace aufgeteilt. Die öffentliche API hat eine
XML-Dokumentation, die Interna sind kommentiert, die Burst-Jobs liegen in eigenen *Jobs.cs-Dateien, und
es gibt EditMode-Unit-Tests. |
Ein erster Blick auf die API
Damit Programmierer einen Eindruck von der API bekommen, folgt hier ein vollständiges Skript. Es fragt beim Terrain ab, was über den
Punkt bekannt ist, auf den du klickst, und plant mit dem integrierten Pathfinder einen Pfad dorthin. Es braucht weder Collider noch
zusätzliche Komponenten: Hänge es an ein beliebiges GameObject in einer Szene mit einem RTSTerrain.
using System.Collections.Generic;
using RTSTerrainStudio;
using UnityEngine;
/// <summary>Click on the ground: logs what the terrain knows about that point and plans a path to it.</summary>
public class ClickInfoExample : MonoBehaviour
{
// Reused for every click, so no new list is allocated each time.
readonly List<Vector3> path = new List<Vector3>();
void Update()
{
// RTSInput works with the Input System package and with the old Input Manager.
if (!RTSInput.GetMouseButtonDown(0) || Camera.main == null) return;
Ray ray = Camera.main.ScreenPointToRay(RTSInput.PointerPosition);
// Analytic ray cast against every RTSTerrain in the scene. Colliders are not needed.
if (!RTSTerrain.RaycastAll(ray, out RTSTerrainHit hit)) return;
RTSTerrain terrain = hit.terrain;
// Gameplay questions, answered from the map data and the pathing map.
int level = terrain.GetCliffLevel(hit.point);
bool buildable = terrain.IsBuildable(hit.point);
bool water = terrain.IsWater(hit.point, out float depth);
Debug.Log($"Cell {hit.cell}: cliff level {level}, buildable {buildable}, water {water} ({depth:0.00} m)");
// Built-in Burst A* on the pathing map, for a unit with a radius of 0.6 m.
PathStatus status = RTSPathfinder.FindPath(terrain, transform.position, hit.point,
PathRequestOptions.ForUnit(0.6f), path);
Debug.Log($"Path from {name}: {status}, {path.Count} waypoints");
}
}
PathStatus ist Success, Partial (der nächstgelegene erreichbare Punkt), NoPath oder
InvalidStart. Das Kapitel Scripting-Leitfaden erklärt die gesamte API.
Voraussetzungen
Unity und Render-Pipeline
| Voraussetzung | Version | Hinweise |
|---|---|---|
| Unity | 6.3 LTS (6000.3) oder neuer | Entwickelt und getestet mit 6000.3.3f1. |
| Universal Render Pipeline | 17.3 (die Version, die mit Unity 6.3 ausgeliefert wird) | Ein URP-Asset muss in den Einstellungen Graphics und Quality aktiv sein. Entwickelt mit dem Rendering-Pfad Forward+. Der Terrain-Shader hat außerdem einen GBuffer-Pass für den Deferred-Pfad. |
| Farbraum | Linear | Gamma funktioniert, aber Farben, Beleuchtung und Wasser wirken ausgewaschen. |
| Grafik | Shader Model 3.5 oder höher | Die Terrain-, Vegetations-, Detail- und Wasser-Shader sind auf #pragma target 3.5 ausgelegt und laufen daher auch unter WebGL 2.0 und OpenGL ES 3.0. Nur der GBuffer-Pass des Terrains (Rendering-Pfad Deferred) und der Structured-Buffer-Pfad des Detail-Shaders verwenden 4.5, sofern die GPU dies unterstützt. |
Die Built-in Render Pipeline und HDRP werden nicht unterstützt. Alle Shader sind für URP geschrieben, und das Renderer Feature für den Nebel des Krieges verwendet die RenderGraph-API von URP.
In ein Projekt ohne URP importiert? Dann bleibt das Paket inaktiv, statt die Console mit Fehlern zu füllen: Seine Skripte werden nicht kompiliert, seine Shader werden übersprungen, und dein Projekt funktioniert weiter wie bisher. Einmal pro Editorsitzung erklärt der Dialog RTS Terrain Studio requires URP, was zu tun ist:
- Built-in Render Pipeline: Klicke auf Install URP. Sobald Unity das Paket kompiliert hat, öffne und klicke auf Use URP. Dadurch wird ein URP-Asset erstellt und in den Einstellungen Graphics und Quality zugewiesen.
- URP älter als 17.3: Klicke auf Open Package Manager und aktualisiere die Universal Render Pipeline auf die Version, die mit deinem Editor ausgeliefert wird.
- Unity älter als 6.3: URP 17.3 benötigt Unity 6.3 LTS, daher bietet der Dialog nur Open Manual und Later an. Öffne das Projekt mit Unity 6.3 LTS oder neuer.
- HDRP oder eine eigene Render-Pipeline: Importiere das Paket stattdessen in ein URP-Projekt, zum Beispiel in ein neues Projekt aus der Projektvorlage Universal 3D.
zeigt den Dialog erneut an. Außerdem kann Unity Warnungen ausgeben, dass die Shader des Pakets nicht unterstützt werden. Sie verschwinden, sobald URP installiert ist.
Benötigte Pakete
Diese Pakete stammen aus der Unity Registry (). Das Fenster erkennt fehlende Pakete und installiert sie mit einem Klick (Prüfpunkt Burst, Collections and Mathematics).
| Paket | Name | Getestete Version | Verwendet für |
|---|---|---|---|
| Burst | com.unity.burst | 1.8.27 | Kompiliert die Jobs zu schnellem nativem Code. |
| Collections | com.unity.collections | 2.6.2 | Native Container für Kartendaten und Jobs. |
| Mathematics | com.unity.mathematics | 1.3.3 | Vektormathematik in Jobs und im Datenmodell. |
| Universal RP | com.unity.render-pipelines.universal | 17.3.0 | Rendering. |
Eingabeverarbeitung
Alle Eingaben zur Laufzeit laufen über die statische Klasse RTSInput. Sie unterstützt beide Eingabe-Backends:
- Paket Input System (
com.unity.inputsystem, getestet mit 1.17.0). Es wird verwendet, wenn das Paket installiert ist und Active Input Handling auf Input System Package (new) oder Both steht. - Input Manager (old). Er dient als Fallback, wenn das Projekt die alte Eingabeverarbeitung verwendet.
Diese Einstellung findest du unter . Der Prüfpunkt Input handling des Projektvalidators zeigt, welcher Modus aktiv ist; siehe Eingabeverarbeitung im Kapitel Projekteinrichtung.
Optionale Pakete für die Integrationen
| Integration | Abhängigkeit | Version | Erkennung |
|---|---|---|---|
| Mirror | Mirror (Asset Store oder GitHub) | 96 oder neuer | Scripting-Define MIRROR, das Mirror selbst hinzufügt. |
| A* Pathfinding Project | com.arongranberg.astar | Pro 5.4.4 bis (ausschließlich) 6.0, getestet mit 5.4.7 | Version-Define RTS_ASTAR. Füge RTS_ASTAR_DISABLED hinzu, um die Integration abzuschalten (nötig für die kostenlose Version, die denselben Paketnamen verwendet). |
| Unity NavMesh | AI Navigation, com.unity.ai.navigation | 2.0 oder neuer, getestet mit 2.0.9 | Version-Define RTS_AI_NAVIGATION. |
Die Integration für das A* Pathfinding Project benötigt dessen Pro-Version, weil sie
RVO-basierte lokale Kollisionsvermeidung und TraversalConstraint verwendet. Bevor du Mirror löschst, lies
Mirror entfernen: Mirror hinterlässt seine Define-Symbole, und RTS Terrain Studio enthält eine kleine
Editor-Assembly, die sie entfernt.
Empfohlene Projekteinstellungen
Der Projektvalidator prüft all diese Punkte und kann sie für dich korrigieren:
| Einstellung | Grund |
|---|---|
| URP-Asset aktiv | Die Shader des Pakets rendern nur in URP. |
| Depth Texture und Opaque Texture im URP-Asset | Wasser verwendet die Depth Texture, damit Tiefenfarbe, Schaum und der weiche Übergang am Ufer auch auf Objekte reagieren, die im Wasser stehen. Die Opaque Texture braucht es für die Lichtbrechung. Die eigene Einstellung einer Kamera hat Vorrang vor dem URP-Asset, und das Wasser richtet sich nach jeder Kamera einzeln. Die Demokameras schalten beide selbst ein, daher sehen die Demoszenen in der Game View auch ohne diese Einstellung richtig aus (die Scene View verwendet immer das URP-Asset). |
| Renderer Feature für den Nebel des Krieges im Renderer | Verdunkelt alles im Nebel, auch deine eigenen Materialien. Ohne das Renderer Feature zeigen nur die Shader des Pakets den Nebel. |
| Nebelmodi in Player-Builds beibehalten | Unity entfernt Shader-Varianten für Nebel, die keine Build-Szene verwendet, aber RTSEnvironment schaltet den atmosphärischen Nebel zur Laufzeit ein. Ohne beibehaltene Nebelmodi zeigen Karten mit Nebel in Builds keinen Nebel. |
| Linearer Farbraum | Korrekte Farben und Beleuchtung. |
| Shader-Referenzen und Textur-Arrays der Tilesets aktuell | Shader und komprimierte Arrays werden in Player-Builds aufgenommen. Vor jedem Player-Build aktualisiert das Paket automatisch das Shader-Referenz-Asset und erstellt veraltete Textur-Arrays neu. |
Plattformen
Der Laufzeitcode hat keine Abhängigkeiten, die nur im Editor existieren. Deshalb lassen sich Karten in Player-Builds auf jeder Plattform laden, rendern und bearbeiten, die URP unterstützt, auch in Web-Builds (WebGL 2.0). Die Shader sind auf Shader Model 3.5 ausgelegt, das niedrigste Shader Model mit Textur-Arrays. Der GBuffer-Pass des Terrains für den Rendering-Pfad Deferred benötigt Shader Model 4.5 und wird auf OpenGL-basierten APIs übersprungen, genau wie der GBuffer-Pass des Lit-Shaders von URP. Detailgras wählt zur Laufzeit, wie es seine Instanzen speichert: in Structured Buffers, wo die GPU sie unterstützt, und in einer Float-Textur unter WebGL 2.0 und OpenGL ES 3 (das Ergebnis sieht gleich aus).
Beim Backen der Textur-Arrays wird die Kompression passend zum aktiven Build-Ziel gewählt: BC7 für Desktop und Konsolen, ASTC 6×6 für Android, iOS, tvOS und visionOS, für Web die im Build eingestellte Texturkompression (DXT5, ASTC 6×6 oder ETC2) und DXT5 für alles andere. Der In-Game-Editor läuft auf allen Plattformen und ist für Maus und Tastatur ausgelegt. Seine Befehle zum Anzeigen des Kartenordners und zum Beenden der Anwendung erscheinen nur dort, wo die Plattform sie unterstützt: Nur Desktop-Player und der Editor können Ordner anzeigen, und WebGL, iOS und Android können die Anwendung nicht beenden. Web-Builds unterscheiden sich in einigen weiteren Punkten; siehe Web-Builds (WebGL).
Ordnerstruktur
Das Paket liegt in einem einzigen Ordner, standardmäßig Assets/RTSTerrainStudio. Du kannst diesen Ordner verschieben oder umbenennen:
Der Editor findet den Stammordner des Pakets über den Speicherort von RTSTerrainStudio.Runtime.asmdef, und
die Pfade zu Demo-Inhalten und Dokumentation werden relativ dazu bestimmt.
RTSTerrainStudio/
├── Runtime/ Core, rendering, tools and runtime systems (RTSTerrainStudio.Runtime)
├── Editor/ Editor window, Scene View, inspectors, setup, demo content (RTSTerrainStudio.Editor)
├── Shaders/ URP shaders and HLSL includes
│ ├── Hidden/ Internal shaders (fog blur, minimap fog, gizmos, texture array blit)
│ └── Include/ Shared HLSL (fog of war, brush cursor, wind, terrain, vegetation, water)
├── Samples/
│ ├── Scripts/ Sample gameplay: camera, units, selection, orders, minimap, HUD (RTSTerrainStudio.Samples)
│ └── Scenes/ Demo scenes (created by Create Demo Content)
├── Integrations/
│ ├── Mirror/ Multiplayer (compiled only with Mirror)
│ ├── AStarPathfinding/ A* Pathfinding Project (compiled only with A* Pro 5.4.4+)
│ └── UnityNavMesh/ Unity AI Navigation (compiled only with com.unity.ai.navigation 2.0+)
├── Content/ Generated demo content (empty until you run Create Demo Content)
├── Resources/
│ └── RTSTerrainStudio/ Runtime UI style sheets and theme, shader references, panel settings
├── Documentation/ This manual (index.html) and its images
└── Tests/
└── Editor/ EditMode unit tests (RTSTerrainStudio.Tests.Editor)
Ordner der obersten Ebene
| Ordner | Inhalt | Kann ich ihn löschen? |
|---|---|---|
Runtime | Alles, was im Spiel läuft: die Komponente RTSTerrain, Kartendaten, Rendering, alle Bearbeitungswerkzeuge, Vegetation, Wasser, Dekor, Nebel des Krieges, Pathing, Generierung, der In-Game-Editor, Icons und Eingabe. | Nein. |
Editor | Nur Funktionen für den Unity Editor. Nichts davon landet in Player-Builds. | Nein. |
Shaders | RTS Terrain Studio/Terrain, RTS Terrain Studio/Vegetation, RTS Terrain Studio/Water, Hidden/RTS Terrain Studio/Detail, Hidden/RTS Terrain Studio/FogOfWarFullscreen, die internen Shader in Hidden und die HLSL-Includes in Include (RTSCommon.hlsl enthält die globalen Variablen für Nebel des Krieges, Pinselcursor und Wind). | Nein. |
Samples | Gameplay-Beispielskripte und die Demoszenen. Die Editor-Assembly und die Integrationen referenzieren die Beispiel-Assembly (zum Beispiel für RTSUnit und RTSDemoGame). | Nur zusammen mit den Integrationen. Die Szenen kannst du jederzeit löschen. |
Integrations | Ein Ordner pro Drittanbieter-Integration, jeweils mit eigenen Assemblies, einer README.md und einem Editor-Ordner mit Skripten, die die Demoszenen erstellen. Demoszenen werden in einem Unterordner Demo angelegt. | Ja, jeden Integrationsordner einzeln. Lies vorher die Schritte für Mirror. |
Content | Texturen, Materialien, Meshes, Prefabs, Tilesets, Bibliotheken, Pinselformen, Karten und Render-Einstellungen für die Demo. Alles darin ist generiert. | Ja, du kannst ihn jederzeit neu generieren. Dann dürfen deine eigenen Karten aber nichts daraus verwenden. |
Resources/RTSTerrainStudio | UI/RTSRuntimeEditor.uss und UI/RTSRuntimeTheme.tss (Stil der In-Game-UI). Außerdem werden hier das Shader-Referenz-Asset (vom Editor automatisch erstellt und aktualisiert) und UI/RTSRuntimePanelSettings.asset (von Create Demo Content erstellt) abgelegt. Sie sorgen dafür, dass die Shader des Pakets und die Laufzeit-Shader von UI Toolkit in Builds aufgenommen werden. | Nein. |
Documentation | Dieses Handbuch. und jede Schaltfläche ? öffnen es. | Ja, aber dann können die Hilfe-Schaltflächen nichts mehr öffnen. |
Tests | EditMode-Tests für Kartendaten, Geometrie, Pinsel, Symmetrie, Rückgängigmachen, den Serializer, Pathing, den Generator, Terrain-Abfragen, die Werkzeug-Registry und Icons. Du führst sie unter aus. | Ja. |
Im Ordner Runtime
| Ordner | Inhalt |
|---|---|
Core | RTSTerrain (Hauptkomponente, Lebenszyklus der Karte, Änderungsverfolgung, Abfragen), MapData, MapTypes, MapGeometryView (die Referenzoberfläche für Meshes, Abfragen und Pathing), PathingMap, RTSTerrainSettings, Shader-Property-IDs, RTSShaderReferences, RTSRuntimeEditorEvents. |
Data | Asset-Typen: RTSTileset, RTSVegetationLibrary, RTSDecorLibrary, RTSMapAsset. |
Serialization | RTSMapSerializer: das binäre .rtsmap-Format (Dateiabschnitte, Deflate-Kompression) und Datei-Ein-/Ausgabe. |
Brushes | BrushSettings, RTSBrushShape, SymmetrySettings, BrushUtility. |
Tools | RTSTool (Basisklasse), RTSEditSession, MapUndo und ein Unterordner pro Kategorie: Height, Cliffs, Paint, Vegetation, Decor, Water, Pathing, Markers. |
Mesh | Erzeugung der Chunk-Geometrie und die zugehörigen Burst-Jobs. |
Rendering | Der Rendering-Teil von RTSTerrain, RTSTerrainChunk, Splat-Texturen, Schichtdaten und RTSTextureArrayBuilder. |
Vegetation | RTSVegetationRenderer, RTSTreeInstance. |
Water | RTSWaterRenderer, RTSWaterChunk, Jobs für das Wasser-Mesh. |
Decor | RTSDecorManager, RTSDecorInstance. |
FogOfWar | RTSFogOfWarManager, RTSFogOfWarRevealer, RTSFogOfWarHideable, RTSFogOfWarRendererFeature. |
Pathing | Der Pathing-Teil von RTSTerrain, RTSPathingSettings, PathingMapBuilder, ClearanceMap, RTSPathfinder, RTSPathfindingService, Pathing-Overlays. |
Generation | RTSMapGenerator, MapGeneratorSettings, RTSMapGeneratorPreset, RTSAutoPaintProfile, RTSAutoPaint, HeightmapIO und das Werkzeug Auto Paint. |
Minimap | RTSMinimapBaker. |
Environment | RTSEnvironment (wendet Sonne, Umgebungslicht und Nebel der Karte an). |
Validation | MapValidator (prüft eine Karte auf Probleme wie unerreichbare Startpositionen). |
RuntimeEditor | Der In-Game-Editor: RTSRuntimeEditor, RTSRuntimeEditorCamera, RuntimeToolGizmos und seine mit UI Toolkit gebaute Oberfläche. |
Input | RTSInput und RTSKey. |
UI | RTSIcons: prozedurale Icons, die der Editor und die In-Game-UI gemeinsam nutzen. |
Plugins/WebGL | JavaScript-Plugins (.jslib), die nur Web-Builds verwenden: Browserspeicher, Downloads, Importe und die Warnung beim Verlassen der Seite (RTSWebFileSystem.jslib), Tastaturlayouts und Browser-Tastenkürzel (RTSWebKeyboard.jslib) sowie die Erkennung, wenn der Mauszeiger die Seite verlässt (RTSPointer.jslib). |
link.xml | Sorgt dafür, dass die Bearbeitungswerkzeuge in Builds mit Code-Stripping erhalten bleiben (siehe Code-Stripping und eigene Werkzeuge). |
Im Ordner Editor
| Ordner | Inhalt |
|---|---|
Core | RTSEditorContext (die gemeinsame Bearbeitungssitzung und die Ansichtsoptionen), RTSEditorPaths, Speicherung der Werkzeugeinstellungen. |
Window | Das Fenster von RTS Terrain Studio (RTSTerrainStudioWindow) und seine Seiten. |
SceneView | Malen in der Scene View, Zeichnen von Pinsel und Gizmos, Overlays, Tastenkürzel und die Brücke zum Undo-System von Unity. |
Inspectors | Inspectors für Tileset, Bibliotheken, Pinselformen, Auto-Paint-Profile und Komponenten. |
Importers | Der .rtsmap-Importer, seine Vorschau und . |
Building | Das Backen der komprimierten Textur-Arrays, die Prüfung, die veraltete Textur-Arrays vor Player-Builds neu erstellt, der Vorschaubild-Generator und die Asset-Factory. |
Setup | Welcome-Fenster, New Map Wizard, Projektvalidator, das GameObject-Erstellungsmenü, die Aktualisierung der Shader-Referenzen und das Web-Template (WebGLTemplate) mit seinem Installer. |
Requirements | Die Hinweismeldung, die erscheint, wenn im Projekt URP 17.3 fehlt (siehe Unity und Render-Pipeline). Abgesehen von der Bereinigung der Mirror-Defines ist das der einzige Teil des Pakets, der ohne URP kompiliert. |
RuntimeEditorSupport | Das Menü . |
DemoContent | Der prozedurale Generator für Demo-Inhalte (RTSDemoContent). |
Assemblies
Der Code ist in Assembly-Definitionen aufgeteilt. So kompiliert er schnell, und die optionalen Teile lassen sich entfernen. Jede Assembly hat genau einen Namespace.
| Assembly (asmdef) | Ordner | Namespace | Plattformen | Kompiliert, wenn |
|---|---|---|---|---|
RTSTerrainStudio.Runtime | Runtime | RTSTerrainStudio | Alle | URP 17.3 oder neuer ist installiert (RTS_URP). Definiert RTS_INPUT_SYSTEM, wenn das Paket Input System installiert ist. |
RTSTerrainStudio.Samples | Samples/Scripts | RTSTerrainStudio.Samples | Alle | URP 17.3 oder neuer ist installiert. |
RTSTerrainStudio.Editor | Editor | RTSTerrainStudio.Editor | Editor | URP 17.3 oder neuer ist installiert. |
RTSTerrainStudio.Requirements.Editor | Editor/Requirements | RTSTerrainStudio.Requirements | Editor | Nur solange URP 17.3 oder neuer fehlt. Sie zeigt die Hinweismeldung, dass das Paket URP benötigt. |
RTSTerrainStudio.Tests.Editor | Tests/Editor | RTSTerrainStudio.Tests | Editor | UNITY_INCLUDE_TESTS ist definiert (Unity Test Framework). |
RTSTerrainStudio.Integrations.MirrorRTSTerrainStudio.Integrations.Mirror.Editor | Integrations/Mirror | RTSTerrainStudio.Integrations.Mirror | Alle / Editor | MIRROR ist definiert. |
RTSTerrainStudio.Integrations.Mirror.DefineCleanup.Editor | Integrations/Mirror/Editor/DefineCleanup | RTSTerrainStudio.Integrations.Mirror | Editor | Immer. Sie hängt nicht von Mirror ab und entfernt verwaiste Mirror-Defines, nachdem Mirror gelöscht wurde. |
RTSTerrainStudio.Integrations.AStarRTSTerrainStudio.Integrations.AStar.Editor | Integrations/AStarPathfinding | RTSTerrainStudio.Integrations.AStar | Alle / Editor | RTS_ASTAR (A* 5.4.4 bis unter 6.0 installiert) und nicht RTS_ASTAR_DISABLED. |
RTSTerrainStudio.Integrations.NavMeshRTSTerrainStudio.Integrations.NavMesh.Editor | Integrations/UnityNavMesh | RTSTerrainStudio.Integrations.NavMesh | Alle / Editor | RTS_AI_NAVIGATION (AI Navigation 2.0 oder neuer installiert). |
Die Test- und Integrations-Assemblies benötigen zusätzlich zur angegebenen Bedingung URP 17.3 oder neuer. Ohne URP werden nur die Bereinigung der Mirror-Defines und die Hinweismeldung zu den Voraussetzungen kompiliert. Das Paket verursacht daher keine Kompilierfehler.
RTSTerrainStudio.Runtime, RTSTerrainStudio.Samples und RTSTerrainStudio.Editor werden
automatisch referenziert. Skripte in der Standard-Assembly deines Projekts (Assembly-CSharp) können sie ohne
weitere Einrichtung verwenden. Hat dein Code eine eigene Assembly-Definition, füge eine Referenz auf RTSTerrainStudio.Runtime
hinzu (und auf RTSTerrainStudio.Samples, wenn du die Gameplay-Beispielklassen verwendest).
Der In-Game-Editor liegt in Runtime/RuntimeEditor. Wenn dein Spiel ihn nicht braucht, kannst du diesen
Ordner löschen: Die Werkzeuge im Unity Editor und der Generator für Demo-Inhalte kompilieren weiterhin, und die Szene
In-Game Editor Demo wird dann ohne In-Game-Editor erstellt.
Lieferumfang
Das Paket besteht aus Quellcode, Shadern und diesem Handbuch. Alles Visuelle (Texturen, Modelle, Tilesets, Karten und Szenen) erzeugt ein Skript auf deinem Rechner. Das hält den Download klein, und die Inhalte sind genau wie der Code lizenziert.
Demo-Inhaltsgenerator
führt RTSDemoContent.CreateAll aus.
Der Befehl erzeugt alle Demo-Assets prozedural mit festen Seeds, daher liefert jeder Durchlauf dasselbe Ergebnis:
- Der Generator schreibt nur in seine eigenen Ordner:
Content,Samples/Scenesund zwei Assets inResources/RTSTerrainStudio. Deine eigenen Assets werden nie angetastet. - Führst du ihn erneut aus, erzeugt er die Assets an Ort und Stelle neu, sodass Verweise darauf gültig bleiben. Kartendateien, deren Inhalt sich nicht geändert hat, werden nicht neu geschrieben, daher zeigt die Versionsverwaltung keine Änderungen.
- Er schließt nie deine geöffneten Szenen. Demoszenen werden während ihres Aufbaus additiv geöffnet.
- Am Ende bietet er an, die Demoszenen zu den Build-Einstellungen hinzuzufügen. Es wird nur dann etwas hinzugefügt, wenn du auf Add Scenes klickst.
| Erzeugt | Ort (unterhalb des Paketstammordners) | Inhalt |
|---|---|---|
| Texturen | Content/Textures/ (Ground, Cliffs, Water, Details, Props, Brushes) | Kachelbare Boden-, Klippen-, Wasser- und Prop-Texturen (512 px), Gras- und Blumen-Sprites sowie Pinselstempel (256 px), jeweils mit passenden Normal-Maps. |
| Materialien, Meshes, Prefabs | Content/Materials, Content/Meshes, Content/Prefabs | Low-Poly-Bäume, Felsen, Ruinen und Props mit Materialien. |
| Tilesets | Content/Tilesets/Summer.asset, Winter.asset | Zwei Tilesets mit je 10 Bodenschichten und 2 Klippentypen. Summer: Grass, Lush Grass, Dirt, Rocky Dirt, Sand, Cobblestone, Dark Forest Floor, Rock, Snow und Blight, dazu Rock Cliff und Grassy Cliff. Winter: die gefrorenen Varianten, mit Ice Cliff und Snowy Cliff. |
| Auto-Paint-Profil | Content/Tilesets/Summer Auto Paint.asset | Regeln für das Tileset Summer, zum Beispiel Erdflecken auf sanften Hängen und Sand entlang der Ufer. |
| Vegetationsbibliothek | Content/Libraries/Demo Vegetation.asset | Details: Grass, Tall Grass, Flowers Yellow, Flowers Purple, Fern. Bäume: Pine, Broadleaf, Dead Tree, Bush. |
| Dekorbibliothek | Content/Libraries/Demo Decor.asset | Rock A, Rock B, Rock C, Boulder, Ruined Pillar, Crate, Barrel, Fence, Tree Stump, Mushrooms. Es sind sowohl instanzierte als auch GameObject-Prototypen dabei, und sie haben Pathing-Grundflächen. |
| Pinselformen | Content/Brushes | Eigene Stempel: Noise Blob, Splatter, Star, Soft Square. |
| Karten | Content/Maps | Drei .rtsmap-Dateien (siehe unten). |
| Render-Einstellungen | Content/Settings | Ein Post-Processing-Volume-Profil, das die Demoszenen verwenden. |
| Szenen | Samples/Scenes | Drei Demoszenen (siehe unten). |
| Resources | Resources/RTSTerrainStudio | Das Shader-Referenz-Asset und UI/RTSRuntimePanelSettings.asset, damit die Shader und die UI in Builds enthalten sind. |
Demokarten
Alle drei Karten verwenden 2 m große Zellen, eine Stufenhöhe von 2 m und das Tileset Summer.
| Karte | Größe | Symmetrie | Beschreibung |
|---|---|---|---|
Two Rivers (1v1).rtsmap | 128 × 128 Zellen | Rotate 2 | Zwei Spieler, zwei Flüsse. Überall führen flache Furten durch die Flüsse, und der See in der Mitte ist tief. Rampen führen hinauf zu bewaldeten Plateaus mit Ruinen und verlassenen Lagern. |
Four Kingdoms (4p).rtsmap | 192 × 192 Zellen | Rotate 4 | Vier Königreiche, jedes auf seinem eigenen Hochland. Rampen führen hinab in das gemeinsame Tiefland, und in der Mitte liegt verseuchter Boden mit den Ruinen eines alten Tempels. |
Sandbox.rtsmap | 96 × 96 Zellen | None | Eine kleine, überwiegend flache Karte zum Ausprobieren der Werkzeuge. |
Demoszenen
| Szene | Karte | Was sie zeigt |
|---|---|---|
| RTS Terrain Studio - Gameplay Demo | Two Rivers (1v1) | Klicke auf Play, um zu spielen: Einheiten an den Startpositionen (die Gegner streifen umher), Nebel des Krieges, RTS-Kamera, Auswahl, Befehle, Minikarte, HUD, Gebäudeplatzierung (B) und Abbau von Bäumen (T). |
| RTS Terrain Studio - In-Game Editor Demo | Sandbox | Der In-Game-Editor im Play Mode. Bearbeite die Karte und drücke dann F5, um sie mit Einheiten im Spiel zu testen. |
| RTS Terrain Studio - Editor Playground | Four Kingdoms (4p) | Eine Karte ohne Gameplay, um die Werkzeuge im Unity Editor in der Scene View auszuprobieren. |
Jede Demoszene enthält eine Sonne, ein Post-Processing-Volume, eine Kamera, die die Depth- und die Opaque-Textur anfordert,
und ein Terrain mit RTSTerrain, RTSVegetationRenderer, RTSWaterRenderer,
RTSDecorManager und RTSEnvironment.
Jede Integration bringt einen eigenen Befehl zum Erstellen ihrer Demoszene mit. Er setzt die Demo-Inhalte voraus und legt die Szene im Ordner
Demo der Integration an:
- erstellt
RTS Mirror Demo.unity. - erstellt
RTS AStar Demo.unity. - erstellt
RTS NavMesh Demo.unity.
Beispielskripte
Der Ordner Samples/Scripts (RTSTerrainStudio.Samples) ist ein kleines, gut lesbares RTS-Framework,
von dem du lernen oder auf dem du aufbauen kannst:
| Bereich | Klassen |
|---|---|
| Initialisierung | RTSDemoGame, RTSUnitSpawner |
| Einheiten und Bewegung | RTSUnit, RTSUnitFactory, RTSUnitRegistry, RTSFormation, RTSWanderAI |
| Pfad-Provider | IRTSUnitPathProvider, RTSBuiltinPathProvider, RTSUnitPathProviderComponent, RTSUnitPathProviderRegistry |
| Kamera, Eingabe, Auswahl, Befehle | RTSCameraController, RTSGameInput, RTSSelectionManager, RTSCommandController |
| UI | RTSMinimapUI, RTSDemoHUD, RTSRuntimeUI |
| Gameplay-Demos | RTSBuildingPlacementDemo, RTSDemoBuilding, RTSTreeHarvestDemo, RTSFallingTreeEffect |
| Hilfsklassen | RTSMoveMarker, RTSGroundRing, RTSSampleMaterials, RTSSampleMeshes, RTSSampleUtility |
Icons
Alle Icons im Editorfenster, in den Inspectors und im In-Game-Editor zeichnet RTSIcons prozedural.
Dadurch bleiben sie in jeder Größe scharf und funktionieren in Builds ohne Textur-Assets.
Schnellstart
Dieses Kapitel führt dich von einem leeren Unity-Projekt zu einer selbst gebauten Karte, auf der du mit Einheiten, Nebel des Krieges und Minikarte gespielt hast. Du brauchst keine Programmierkenntnisse. Arbeite die nummerierten Schritte der Reihe nach ab. Jeder Schritt nennt dir genau das Menü, das Fenster oder die Schaltfläche, die du brauchst.
| Teil | Was du tust | Zeit |
|---|---|---|
| Installation und Einrichtung | Importiere das Paket, lasse den Projektvalidator die URP-Einstellungen korrigieren und erzeuge die Demo-Inhalte. | etwa 5 min |
| Demoszenen | Spiele eine fertige Karte, probiere den In-Game-Editor aus und erkunde den Editor Playground. | 5 min |
| Deine erste Karte | Erstelle ein Terrain, male Höhen, Klippen, Texturen, Bäume und Wasser, platziere Startpositionen und speichere. | 10 min |
| Dein erster Spieltest | Spiele deine Karte mit dem Demospiel, oder bearbeite und teste sie direkt im laufenden Spiel. | 5 min |
| 10-Minuten-Tutorial | Baue von Anfang bis Ende eine faire 1v1-Karte mit symmetrischem Malen. | 10 min |
Du kannst dieses Kapitel jederzeit über den Link Quick Start Guide im Welcome-Fenster öffnen (). Das vollständige Handbuch öffnest du mit .
Installation und Einrichtung
Voraussetzungen
| Element | Version | Hinweise |
|---|---|---|
| Unity | 6.3 LTS (6000.3) | Verwende die Editor-Version, für die das Paket erstellt wurde. |
| Universal Render Pipeline | 17.3 | Erforderlich. Alle Shader für Terrain, Wasser, Vegetation und Nebel des Krieges sind URP-Shader. Wenn du ein neues Projekt anlegst, beginne mit der Projektvorlage Universal 3D. Ohne URP bleibt das Paket inaktiv und zeigt eine Hinweismeldung an (siehe Voraussetzungen). |
| Burst, Collections, Mathematics | aus der Unity Registry | Erforderlich. Die rechenintensive Arbeit (Mesh-Aufbau, Pinsel, Pathing, Nebel des Krieges, Wegfindung) läuft in Burst-Jobs. |
| Input System | aus der Unity Registry | Empfohlen. Wird vom In-Game-Editor, der RTS-Kamera und den Beispielen verwendet. Der ältere Input Manager funktioniert als Ausweichlösung. |
| Mirror, A* Pathfinding Project, AI Navigation | optional | Nur für die jeweilige Integration nötig. Die Integrationen werden automatisch kompiliert, sobald das zugehörige Paket installiert ist. Siehe Integrationen. |
Das Paket importieren
- Öffne dein Projekt in Unity 6.3. Stelle sicher, dass es URP verwendet (neue Projekte aus der Vorlage Universal 3D tun das bereits).
- Öffne , wähle My Assets, suche RTS Terrain Studio und klicke auf Download und dann auf Import.
- Lasse im Importdialog alle Häkchen gesetzt und klicke auf Import. Das Paket landet in
Assets/RTSTerrainStudio. - Warte, bis Unity mit dem Kompilieren fertig ist. Das Fenster Welcome öffnet sich von selbst (siehe unten).
Du kannst den Ordner RTSTerrainStudio an eine beliebige Stelle innerhalb von Assets verschieben. Das Paket findet seine eigenen Dateien, egal wo der Ordner liegt. Wenn du dein Projekt umorganisierst, geht also nichts kaputt.
Zeigt die Console direkt nach dem Import Kompilierfehler, fehlt höchstwahrscheinlich eines der Pakete Burst, Collections oder Mathematics. Installiere sie über . Sobald sie installiert sind, verschwinden die Fehler.
Erscheint statt des Welcome-Fensters der Dialog RTS Terrain Studio requires URP, hat das Projekt kein URP 17.3. Das Paket bleibt dann inaktiv und verändert nichts. Folge den Schritten im Dialog oder lies Unity und Render-Pipeline.
Das Welcome-Fenster
Das Welcome-Fenster öffnet sich automatisch, wenn das Paket zum ersten Mal in einem Projekt geladen wird. Du kannst es jederzeit über erneut öffnen. Aktiviere unten Show at startup, wenn es bei jedem Start von Unity erscheinen soll. Diese Einstellung wird pro Benutzer und pro Projekt gespeichert und landet daher nie in der Versionsverwaltung.
Die linke Spalte ist eine Schnellstart-Liste mit einer Schaltfläche pro Schritt:
| Schritt | Schaltfläche | Funktion |
|---|---|---|
| 1. Validate & fix the project | Validate | Öffnet den Project Validator. Unter dem Schritt zeigt eine Statuszeile „Project is ready“ oder die Anzahl der gefundenen Fehler und Warnungen. |
| 2. Create the demo content | Create | Führt aus. |
| 3. Open a demo scene | Szenenliste + Open | Listet die Demoszenen auf, sobald sie existieren, und öffnet die ausgewählte (vorher wirst du gefragt, ob deine geöffneten Szenen gespeichert werden sollen). |
| 4. Create your own map | New Map | Öffnet den New Map Wizard. |
| 5. Start editing | Editor Window | Öffnet das Fenster von RTS Terrain Studio. |
Die rechte Spalte enthält unter Learn Links in dieses Handbuch (Documentation, Quick Start Guide, Scripting API, Integrations, Troubleshooting) sowie eine Liste Integrations, die zeigt, ob Mirror, das A* Pathfinding Project und Unity AI Navigation im Projekt installiert sind.
Das Projekt validieren
Der Project Validator prüft alles, was das Paket benötigt, und behebt die meisten Probleme mit einem Klick. Führe ihn einmal nach dem Import aus und immer dann erneut, wenn etwas nicht stimmt (pinke Materialien, flaches Wasser, Einheiten, die trotz Nebel des Krieges sichtbar sind).
- Öffne oder klicke im Welcome-Fenster auf Validate.
- Lies die Zusammenfassung oben. Jede Prüfung ist ein Panel mit einer Statusanzeige (OK, Info, Warning oder Error). Öffne auf einem Panel Details, um genau zu sehen, was gefunden wurde.
- Klicke auf Fix All. Ein Dialog listet jede Änderung auf, bevor irgendetwas verändert wird. Klicke auf Apply Fixes.
- Klicke auf Run Again und vergewissere dich, dass keine Fehler oder Warnungen mehr übrig sind. Info-Panels sind unbedenklich: Sie dienen nur zur Information.
Fix All arbeitet in mehreren Durchgängen. War URP noch nicht aktiv, stellt der erste Durchgang das Projekt auf URP um, und die Korrekturen für Kameratexturen und Nebel des Krieges, die URP benötigen, werden mit demselben Klick angewendet. Jedes Panel hat außerdem eine eigene Korrektur-Schaltfläche, falls du die Probleme lieber einzeln behebst.
| Prüfung | Schweregrad | Korrektur-Schaltfläche | Was die Korrektur bewirkt und warum sie wichtig ist |
|---|---|---|---|
| Rendering | |||
| Universal Render Pipeline is active | Error | Use URP | Weist ein URP-Asset des Projekts (oder ein neu erstelltes) als Standard-Render-Pipeline zu, ebenso jeder Qualitätsstufe, die eine andere Pipeline verwendet. Ohne URP werden Terrain, Wasser und Vegetation pink oder gar nicht gerendert. |
| Depth and Opaque Texture enabled | Warning | Enable Both | Schaltet Depth Texture und Opaque Texture in jedem URP-Asset ein, das eine Qualitätsstufe verwendet. Wasser nutzt die Depth Texture, damit sich Tiefenfarbe, Schaum und Uferlinie an Objekte anpassen, die im Wasser stehen. Die Opaque Texture dient der Lichtbrechung. Ohne diese Texturen wirkt das Wasser schlichter. Kameras können diese Einstellungen in ihrem Abschnitt Rendering weiterhin überschreiben. |
| Fog of War renderer feature | Warning | Add Feature | Fügt das Renderer Feature RTS Fog Of War zum Standard-Renderer jedes URP-Assets hinzu, das das Projekt verwendet, und weist ihm seinen Shader zu. Ohne dieses Feature zeigen nur die paketeigenen Shader den Nebel; Einheiten und Props mit anderen Materialien bleiben sichtbar. |
| Fog kept in player builds | Warning | Keep Fog Modes | Erscheint, wenn ein RTSEnvironment in einer Build-Szene oder einer geöffneten Szene atmosphärischen Nebel anwendet. Setzt auf Custom und behält die verwendeten Nebelmodi bei. Andernfalls entfernt Unity die Shader-Varianten für Nebel, und Karten mit Nebel zeigen in Builds keinen Nebel. Siehe Nebel in Player-Builds. |
| Project | |||
| Linear color space | Warning | Switch to Linear | Setzt auf Linear. Im Gamma-Farbraum wirken Farben, Beleuchtung und Wasser ausgewaschen. Durch die Umstellung werden alle Texturen neu importiert. |
| Burst, Collections and Mathematics | Error | Install | Fügt die fehlenden Pakete über den Package Manager hinzu. Ohne sie lassen sich die Skripte des Pakets nicht kompilieren. |
| Input handling | Info | keine | Meldet, ob das Paket Input System installiert ist und welche Eingabeverarbeitung aktiv ist. Der ältere Input Manager funktioniert ebenfalls. |
| WebGL (nur solange Web die aktive Build-Plattform ist) | |||
| Web quality levels have Depth and Opaque Texture | Warning | Enable Both | Schaltet Depth Texture und Opaque Texture in den URP-Assets der Qualitätsstufen ein, die Web-Builds verwenden. Der Editor rendert oft mit einer Desktop-Qualitätsstufe. Ohne diese Prüfung würde sich das schlichtere Wasser daher erst im Browser zeigen. |
| MSAA is off for Web builds | Error | Turn MSAA Off | Schaltet MSAA in den URP-Assets der Web-Qualitätsstufen aus, die zusätzlich die Depth Texture oder die Opaque Texture verwenden. URP kann diese Kombination unter WebGL 2 nicht rendern, sodass das Spiel im Browser schwarz bliebe. |
| Web template fills the browser window | Warning | Install Template | Installiert das Web-Template des Pakets und wählt es aus. Unitys Templates Default und Minimal zeigen das Spiel in einem kleinen Rahmen mit fester Größe, in dem In-Game-Editor, HUD und Minikarte schwer lesbar sind. Siehe Das Vollfenster-Template. |
| Content | |||
| Package shaders compile | Error | Reimport | Importiert Paket-Shader mit Kompilierfehlern neu (das hilft, wenn sie importiert wurden, bevor URP fertig geladen war). Fehlen Shader vollständig, importiere das Paket erneut aus dem Asset Store. |
| Shaders included in builds | Error | Update | Erstellt oder aktualisiert RTSShaderReferences.asset und das Material DecorPlaceholder im Ordner Resources des Pakets. Shader, die nur im Code erzeugt werden, werden aus Player-Builds entfernt, sofern nichts auf sie verweist, und das Terrain wäre in deinem Build pink. Dieses Asset wird außerdem automatisch aktualisiert: nach dem Neuladen von Skripten, bei Änderungen an Shadern und vor jedem Player-Build. |
| Tileset texture arrays built | Info | Build Arrays | Erstellt die komprimierten Textur-Arrays für Tilesets, die noch keine oder nur veraltete haben. Tilesets funktionieren auch ohne sie, aber dann erzeugt der Editor beim Laden der Karte unkomprimierte Arrays, was Speicher und Ladezeit kostet. Beim Erstellen eines Player-Builds werden veraltete Arrays automatisch neu erstellt; Tilesets, deren Arrays nie erstellt wurden, verwenden auch in Player-Builds unkomprimierte Arrays. |
| GPU instancing on instanced prototypes | Warning | Enable Instancing | Aktiviert Enable GPU Instancing bei Materialien, die von instanzierten Bäumen, Details oder Dekorobjekten verwendet werden. |
| Scene (nur geöffnete Szenen) | |||
| Terrains have a tileset | Warning | Assign '<tileset>' | Weist jedem Terrain ohne Tileset ein Tileset zu (lässt sich rückgängig machen). Ohne Tileset zeigt ein Terrain eine neutrale Platzhalterdarstellung. |
| Terrains save to a map file | Info | Create Map Files | Speichert die Karte jedes Terrains, das kein .rtsmap-Asset hat, in einer neuen Datei neben seiner Szene und weist sie zu. Ohne Kartendatei gehen Änderungen verloren, wenn die Szene geschlossen wird. |
Die Szenenkorrekturen werden im Play Mode nicht angeboten. Beende zuerst den Play Mode.
Die Demo-Inhalte erstellen
Das Paket wird ohne große binäre Grafikdateien ausgeliefert. Stattdessen erzeugt es einen vollständigen Satz Demo-Assets in deinem Projekt. So funktioniert alles ohne weitere Einrichtung, und du kannst sofort mit dem Malen beginnen.
- Wähle oder klicke im Welcome-Fenster auf Create.
- Ein Fortschrittsbalken durchläuft die Schritte: Textures, Materials, Meshes and prefabs, Tilesets, libraries and brushes, Shader references and UI settings, Demo maps, Demo scenes. Beim ersten Durchlauf kann das eine Weile dauern.
- Am Ende fragt ein Dialog, ob die Demoszenen zu den Build-Einstellungen hinzugefügt werden sollen. Klicke auf Not Now, außer du willst einen Player-Build mit den Demos erstellen (du kannst sie später unter hinzufügen). Nur Add Scenes ändert die Build-Einstellungen.
- Die Szene der Gameplay-Demo ist im Project-Fenster ausgewählt. Deine geöffneten Szenen bleiben offen; nichts wird ersetzt.
Was erzeugt wird:
| Ordner (im Paketordner) | Inhalt |
|---|---|
Content/Textures | Boden-, Klippen-, Wasser-, Detail-, Prop- und Pinseltexturen. |
Content/Materials, Content/Meshes, Content/Prefabs | Low-Poly-Bäume, Büsche, Felsen, Props und ihre Materialien. |
Content/Tilesets | Die Tilesets Summer und Winter sowie das Profil Summer Auto Paint. Summer hat die Bodenschichten Grass, Lush Grass, Dirt, Rocky Dirt, Sand, Cobblestone, Dark Forest Floor, Rock, Snow und Blight sowie die Klippentypen Rock Cliff und Grassy Cliff. |
Content/Libraries | Demo Vegetation (Gras, hohes Gras, Blumen, Farn; die Bäume Pine, Broadleaf, Dead Tree und Bush) und Demo Decor (Felsen, Findlinge, Baumstümpfe und andere Props). |
Content/Brushes | Stempel für Pinselformen. |
Content/Maps | Drei Karten: Two Rivers (1v1).rtsmap (128 × 128, punktsymmetrisch), Four Kingdoms (4p).rtsmap (192 × 192, vierfache Rotationssymmetrie) und Sandbox.rtsmap (96 × 96, überwiegend flach mit einem Plateau und einem Teich). |
Samples/Scenes | Die drei unten beschriebenen Demoszenen. |
Resources/RTSTerrainStudio | Shader-Referenzen und die Panel-Einstellungen der Laufzeit-UI. |
Führst du den Generator erneut aus, erscheint eine Rückfrage mit Regenerate / Cancel. Nur die erzeugten Demo-Assets und die Demoszenen werden überschrieben; deine eigenen Assets werden nie angetastet. Dateien, deren Inhalt sich nicht geändert hat, werden nicht neu geschrieben.
Der Generator fügt außerdem das Renderer Feature für den Nebel des Krieges zu deinem URP-Renderer hinzu, falls es fehlt. Die Einstellungen für Depth und Opaque Texture deines URP-Assets ändert er nicht: Die Demokameras fordern diese Texturen selbst an. Für Wasser in deinen eigenen Szenen führe Validate Project wie oben beschrieben aus.
Die Demoszenen öffnen
Die Demoszenen liegen in RTSTerrainStudio/Samples/Scenes. Öffne eine per Doppelklick im Project-Fenster, oder wähle sie in Schritt 3 des Welcome-Fensters aus und klicke auf Open.
| Szene | Karte | Inhalt | Zum Ausprobieren |
|---|---|---|---|
| RTS Terrain Studio - Gameplay Demo | Two Rivers (1v1) | RTS-Kamera, Nebel des Krieges und ein Objekt RTS Demo Game, das das Spiel automatisch startet. | Klicke auf Play. Deine Einheiten (Spieler 1) spawnen an deiner Startposition; die gegnerischen Einheiten streifen umher und tauchen im Nebel des Krieges auf und wieder unter. |
| RTS Terrain Studio - In-Game Editor Demo | Sandbox | Der In-Game-Karteneditor (Objekt RTS In-Game Editor) sowie dieselben Gameplay-Systeme, die ausgeschaltet bleiben, bis du einen Spieltest startest. | Klicke auf Play, male mit dem In-Game-Editor und klicke dann auf Play Test (F5). Drücke Esc, um zum Editor zurückzukehren. |
| RTS Terrain Studio - Editor Playground | Four Kingdoms (4p) | Nur Terrain, Licht und Kamera, ohne Gameplay. | Bleibe im Edit Mode, öffne und probiere die Werkzeuge an einer fertigen Karte aus. |
Steuerung in der Gameplay-Demo
| Eingabe | Aktion |
|---|---|
| W A S D / Pfeiltasten, Bildschirmrand, mit gedrückter mittlerer Maustaste ziehen | Kamera verschieben. Sind Einheiten ausgewählt, stoppt S sie, statt die Kamera zu verschieben; die Pfeiltasten verschieben die Kamera immer. |
| Mausrad | Zoomen |
| Q / E, Alt + mit gedrückter rechter Maustaste ziehen | Ansicht drehen |
| Linksklick / mit gedrückter linker Maustaste ziehen | Eine Einheit auswählen / ein Auswahlrechteck aufziehen |
| Shift + Klick | Einheiten hinzufügen oder entfernen |
| Doppelklick, Ctrl + Klick | Alle Einheiten dieses Typs auf dem Bildschirm auswählen |
| Rechtsklick | In Formation bewegen (Shift hängt den Befehl an die Warteschlange an) |
| S / H / P | Anhalten / Position halten / Patrouillieren |
| Ctrl + 0…9, Shift + 0…9, 0…9 | Kontrollgruppe speichern / Auswahl zur Gruppe hinzufügen / Gruppe abrufen (zweimal drücken, um die Kamera darauf zu zentrieren) |
| Minikarte | Linksklick bewegt die Kamera, Rechtsklick bewegt die ausgewählten Einheiten |
| B, Delete | Gebäude platzieren / Gebäude unter dem Cursor entfernen |
| T, Rechtsklick auf einen Baum | Baum unter dem Cursor fällen / die ausgewählten Arbeiter den Baum fällen lassen |
| Esc | Aktuellen Modus abbrechen oder Auswahl aufheben |
| F1 | Hilfe-Panel ein- oder ausblenden |
Deine erste Karte
Eine Karte befindet sich auf einem GameObject RTS Terrain in deiner Szene und wird in einer .rtsmap-Datei in deinem Projekt gespeichert. Die Szene verweist nur auf die Datei; deine Änderungen werden in der Datei gespeichert. Beginne mit einer neuen Szene, damit du frei experimentieren kannst:
- Wähle und eine URP-Szenenvorlage (zum Beispiel Basic (URP)).
- Speichere die Szene sofort mit Ctrl+S, zum Beispiel als
Assets/MyMaps/FirstMap.unity. Neue Kartendateien werden neben der gespeicherten Szene angelegt. So bleiben Karte und Szene zusammen.
Ein Terrain erstellen
Es gibt zwei Wege. Beide erstellen dasselbe Objekt RTS Terrain, bei dem die Komponenten RTSTerrain, RTSVegetationRenderer, RTSWaterRenderer und RTSDecorManager bereits eingerichtet sind.
| Weg | Menü | Geeignet, wenn |
|---|---|---|
| Ein Klick | Du sofort mit dem Malen loslegen willst. Du erhältst eine flache Karte mit 128 × 128 Zellen (Zellgröße 2 m, Klippenstufe 2), die das Standard-Tileset und die Standardbibliotheken des Projekts verwendet. Sie wird am Pivot der Scene View platziert und ausgewählt. Neben der Szene wird eine neue Kartendatei namens <Scene name> Map.rtsmap angelegt (oder Assets/Maps/New Map.rtsmap, wenn die Szene noch nicht gespeichert ist). | |
| Geführt | Du Name, Größe, Tileset und Basistextur selbst wählen oder eine zufällige, spielbare Karte generieren willst. |
Wenn das Projekt noch kein Tileset hat, bietet das Ein-Klick-Menü zuerst Create Demo Content an. Mit Continue Without erhält das Terrain ein neutrales Schachbrettmuster, bis du ein Tileset zuweist.
Der New Map Wizard hat fünf Schritte. Verwende Next und Back oder klicke in der Leiste oben auf einen Schritt:
| Schritt | Einstellungen |
|---|---|
| 1. Basics | Map Name (zugleich der Dateiname), Author (bleibt für die nächste Karte erhalten), Description, Players (1–16; Wettkampfkarten werden meist für 2, 4 oder 8 Spieler gebaut). |
| 2. Size | Größen-Panels: Tiny 64 (schnelle 1v1-Duelle und Tests), Small 96 (1v1 und 2v2), Medium 128 (2 bis 4 Spieler), Large 192 (4 bis 6 Spieler), Huge 256 (6 bis 8 Spieler), Epic 384 (8+ Spieler, Kampagnen), Epic XL 512 (sehr große Welten) oder Custom (Vielfache von 16). Außerdem Cell Size (m) (im Zweifel bei 2 m belassen), Cliff Level Height (m), Base Cliff Level (Standard 2, damit du wie auf klassischen RTS-Karten für Seen nach unten graben kannst) und ein Ausklappbereich Advanced mit den Auflösungen für Textur, Details und Pathing. |
| 3. Look | Tileset (klicke auf eine Bodentextur, um sie zur Schicht BASE zu machen, die zu Beginn die gesamte Karte bedeckt), Vegetation Library, Decor Library. Ohne Tileset wird eine Schaltfläche Create Demo Content angezeigt. |
| 4. Terrain | Flat (eine leere, flache Karte auf der Grundstufe, ideal zum Kennenlernen der Werkzeuge) oder Generate (Plateaus, Rampen, Wasser, Startpositionen, Vegetation und Dekor aus einem Preset und einem Seed, mit Symmetry, Start Locations, Scatter Vegetation, Scatter Decor und einer Schaltfläche Preview). |
| 5. Create | New terrain object oder Replace map of selected terrain, der Pfad unter Map File (Browse...), Add Environment (Sonne, Umgebungslicht und Nebel aus der Karte; erstellt ein Sun-Licht, falls die Szene keines hat), Open Editor Window und eine Zusammenfassung. Klicke auf Create Map. |
Der Assistent schreibt die Kartendatei, erstellt das Terrain, rückt es in der Scene View ins Bild und öffnet das Editorfenster. Jede Einstellung lässt sich später im Tab Map des Editorfensters ändern.
Das Editorfenster öffnen
Öffne (der Assistent und das Welcome-Fenster können es für dich öffnen, und im Inspector von RTS Terrain gibt es eine Schaltfläche Open RTS Terrain Studio). Docke es neben dem Inspector an, damit du gleichzeitig die Scene View siehst.

| Bereich | Anzeige |
|---|---|
| Kopfzeile | Terrain-Auswahl, Kartenname (ein Punkt steht für nicht gespeicherte Änderungen), Save, Save As…, Open…, Reload, Undo / Redo mit dem Namen des Schritts, eine Hilfe-Schaltfläche und der Schalter Editing On / Editing Off. |
| Tab-Leiste | Acht Werkzeugkategorien (Height, Cliffs, Paint, Vegetation, Decor, Water, Pathing, Markers), danach Map und Settings. Ein Punkt markiert die Kategorie des aktiven Werkzeugs. |
| Werkzeugseite | Große Werkzeugschaltflächen, ein Panel, das das aktive Werkzeug mit seinem Live-Status beschreibt, die Panels Brush und Symmetry sowie die eigenen Einstellungen und Paletten des Werkzeugs (Schichten, Klippenstufen, Spieler, Bäume, Dekor). |
| Statusleiste | Zelle unter dem Cursor, Klippenstufe, Höhe und Pathing unter der Maus sowie der Undo-Speicher. |
Die acht Kategorien und ihre Werkzeuge:
| Taste | Kategorie | Werkzeuge |
|---|---|---|
| 1 | Height | Raise / Lower, Smooth, Flatten, Set Height, Noise, Terrace, Erode |
| 2 | Cliffs | Raise / Lower Level, Set Level, Ramp, Cliff Type, Clean Cliffs |
| 3 | Paint | Paint Layer, Tile Paint, Fill / Replace, Smooth Paint, Auto Paint |
| 4 | Vegetation | Paint Details, Erase Details, Place Trees, Erase Trees |
| 5 | Decor | Place Decor, Erase Decor, Select Decor |
| 6 | Water | Paint Water, Erase Water, Flood Fill |
| 7 | Pathing | Paint Pathing, Clear Pathing |
| 8 | Markers | Start Locations, Regions, Markers |
Jedes Werkzeug wird in einem eigenen Kapitel ausführlich beschrieben (zum Beispiel Höhenwerkzeuge und Klippenwerkzeuge); das Fenster selbst wird unter Editorfenster beschrieben.
Grundlagen des Malens
- Klicke im Editorfenster auf eine Werkzeugschaltfläche. Dadurch wird die Bearbeitung eingeschaltet (in der Kopfzeile steht Editing On). Ein erneuter Klick auf das aktive Werkzeug schaltet die Bearbeitung wieder aus.
- Bewege die Maus in der Scene View über das Terrain. Der Pinselkreis wird direkt auf den Boden gezeichnet, genau dort, wo du malst.
- Halte die linke Maustaste gedrückt und ziehe, um zu malen.
| Eingabe (Scene View) | Aktion |
|---|---|
| Linke Maustaste | Einen Pinselstrich malen (oder klicken, bei Klickwerkzeugen wie Start Locations) |
| Shift + linke Maustaste | Umgekehrte Aktion: absenken, radieren, entfernen |
| Ctrl + Klick | Einen Wert unter dem Cursor aufnehmen (Höhe, Bodenschicht, Wasserhöhe …), bei Werkzeugen, die das unterstützen |
| [ / ] | Pinsel kleiner / größer |
| - / = | Stärke verringern / erhöhen |
| 1 … 8 | Zu einer Werkzeugkategorie wechseln (erneut drücken, um durch ihre Werkzeuge zu schalten) |
| G / P | Raster ein- und ausschalten / Pathing-Overlay durchschalten (Off, Walkability, Buildability, Combined) |
| R | Zufällige Pinseldrehung ein- und ausschalten |
| Ctrl+Z / Ctrl+Y | Terrain-Bearbeitungen rückgängig machen / wiederherstellen (das Menü Edit zeigt die Namen der Schritte) |
| Esc | Den laufenden Pinselstrich abbrechen oder die Bearbeitung beenden |
Die Scene View zeigt außerdem ein Overlay RTS Terrain Studio mit dem Bearbeitungsschalter, Schaltflächen für Kategorien und Werkzeuge, den Schiebereglern Size und Strength und der Einstellung Symmetry. Unten erscheint zusätzlich eine Statuszeile mit der Zelle unter der Maus, der Klippenstufe und der Höhe. Für den Großteil deiner Arbeit kannst du in der Scene View bleiben.
Die Kameranavigation in der Scene View funktioniert wie gewohnt (Ziehen mit der rechten Maustaste zum Fliegen, Alt + Ziehen mit der linken Maustaste zum Umkreisen, Ziehen mit der mittleren Maustaste zum Schwenken). Wenn Unitys Hand-Werkzeug (Q) aktiv ist, schwenkt Ziehen mit der linken Maustaste die Ansicht, statt zu malen; drücke W, um wieder zu malen. Alle Tastenkürzel von RTS Terrain Studio lassen sich unter (unter macOS ) in der Kategorie RTS Terrain Studio neu belegen (nützlich, wenn 2 in deinem Layout mit Unitys Schalter für den 2D-Modus kollidiert).
Höhen malen
- Öffne den Tab Height (1) und klicke auf Raise / Lower.
- Ziehe über das Terrain, um Hügel aufzubauen. Halte die Maustaste gedrückt, damit der Hügel weiter wächst. Halte Shift gedrückt, um abzusenken.
- Wechsle zu Smooth und ziehe über scharfe Stellen, um sie zu glätten.
- Probiere Noise für unebenen Boden und Flatten (Ctrl+Klick nimmt die Höhe auf) für Bauflächen aus.
Höhen sind Feinarbeit über den Klippenstufen: Ein Hügel auf einem Plateau bleibt auf diesem Plateau.
Klippen und Rampen malen
Klippen sind das Herzstück einer klassischen RTS-Karte. Die Karte besteht aus den Klippenstufen 0 bis 15 und beginnt auf der Grundstufe (standardmäßig 2). Jede Stufe bildet einen klar abgesetzten, senkrechten Klippenabsatz.
- Öffne den Tab Cliffs (2) und klicke auf Raise / Lower Level.
- Klicke auf das Terrain: Der Boden hebt sich um eine Klippenstufe. Halte die Maustaste gedrückt und ziehe, um das begonnene Plateau auf derselben Stufe zu erweitern.
- Klicke in das Plateau und ziehe erneut, um eine zweite Stufe darauf zu setzen.
- Halte Shift gedrückt, um um eine Stufe abzusenken. Wenn du unter die Grundstufe absenkst, gräbst du Becken für Seen und Flüsse.
- Klicke auf Ramp und male quer über eine Klippenkante mit einer Stufe Höhenunterschied, um sie in einen begehbaren Hang zu verwandeln. Alle vier Ecken einer Zelle müssen Rampe sein, male also großzügig über die Kante. Shift entfernt eine Rampe.
- Male mit Clean Cliffs über unsaubere Kanten, um Säulen und Löcher aus einzelnen Vertices zu entfernen. Cliff Type malt ein anderes Klippenmaterial (zum Beispiel Grassy Cliff statt Rock Cliff).
Texturen malen
- Öffne den Tab Paint (3) und klicke auf Paint Layer.
- Klicke in der Schichtpalette auf eine Bodenschicht, zum Beispiel Dirt.
- Ziehe über das Terrain, um Wege und Lichtungen zu malen. Shift radiert die Schicht weg; Ctrl+Klick nimmt die Schicht unter dem Cursor auf.
- Für hart abgegrenzte Kacheln im Stil klassischer RTS-Spiele verwende Tile Paint. Für große Flächen verwende Fill / Replace.
- Für ein schnelles, natürliches Ergebnis verwende im Tab Paint das Panel Auto Paint Whole Map und stelle Profile auf Summer Auto Paint: Dabei entsteht Erde in der Nähe von Klippen, Sand an Ufern, Fels an steilen Hängen usw.
Bäume und Gras platzieren
- Öffne den Tab Vegetation (4) und klicke auf Place Trees.
- Wähle unter Trees eine oder mehrere Baumarten, zum Beispiel Pine und Broadleaf. Mehrere Arten werden zufällig gemischt.
- Belasse Mode auf Scatter und ziehe entlang der Kartenränder, um Wälder zu pflanzen. Density bestimmt, wie viele Bäume ein Tupfer hinzufügt. Bäume werden weder auf Hängen platziert, die steiler als Max Slope sind, noch unter Wasser (Avoid Water).
- Stelle Mode auf Single, um pro Klick einen Baum zu platzieren. Shift entfernt Bäume.
- Klicke auf Paint Details, wähle Grass oder Flowers Yellow unter Details und male Wiesen. Target Density legt fest, wie dicht der Pinsel malt.
Die Vegetationswerkzeuge benötigen eine Vegetationsbibliothek, die dem Terrain zugewiesen ist. Terrains, die nach Create Demo Content erstellt werden, erhalten automatisch Demo Vegetation. Dekor funktioniert genauso, mit Place Decor im Tab Decor (5) und der Bibliothek Demo Decor.
Wasser hinzufügen
Wasser füllt Boden, der unter einer Zielhöhe für die Wasseroberfläche liegt. Am einfachsten gräbst du mit den Klippenwerkzeugen ein Becken und füllst es.
- Verwende im Tab Cliffs das Werkzeug Raise / Lower Level mit Shift, um einen See oder Fluss eine Stufe unter der Grundstufe zu graben (Stufe 1 auf einer Standardkarte).
- Öffne den Tab Water (6) und klicke auf Flood Fill.
- Behalte den Standardmodus von Water Height (Klippenstufe plus Offset) mit Level 1 bei. Der Standard-Offset legt die Wasseroberfläche knapp unter die Grundstufe, sodass sich das Becken füllt und das umliegende Land trocken bleibt.
- Klicke in das Becken. Es füllt sich mit einem Klick. Shift+Klick lässt zusammenhängendes Wasser ab.
- Verwende Paint Water, um Wasser von Hand zu malen, und Erase Water, um es zu entfernen.
Flach- und Tiefwasser werden automatisch aus der Wassertiefe abgeleitet und beeinflussen das Pathing: Bodeneinheiten können durch Flachwasser laufen (dort lässt sich nichts bauen, weil Gebäude trockenen Boden brauchen), Tiefwasser dagegen blockiert sie.
Wenn das Wasser keine Lichtbrechung zeigt und sein Ufer Objekte ignoriert, die im Wasser stehen, sind in URP die Depth Texture und die Opaque Texture ausgeschaltet. Führe aus und wende Enable Both an.
Startpositionen platzieren
Startpositionen sind die Spawn-Punkte der Spieler. Das Demospiel spawnt die Einheiten jedes Spielers dort.
- Öffne den Tab Markers (8) und klicke auf Start Locations.
- Wähle in der Spielerpalette P1 (Spieler 1, rot).
- Klicke auf die Stelle des Terrains, an der Spieler 1 starten soll. Jeder Spieler hat genau eine Startposition, ein erneuter Klick verschiebt sie also. Mit Yaw legst du die Blickrichtung fest.
- Wähle den nächsten Spieler und klicke erneut. Shift+Klick entfernt die nächstgelegene Startposition.
Bei eingeschalteter Symmetrie platziert ein Klick alle Spieler auf einmal: Die symmetrischen Kopien werden den folgenden Spielern (P1, P2, …) zugewiesen, solange Symmetry Assigns Next Players aktiviert ist. Siehe das 10-Minuten-Tutorial.
Platziere Startpositionen auf flachem, trockenem Boden abseits von Klippenkanten. Validate Map im Tab Map (Panel Validation) meldet unter anderem fehlende Startpositionen sowie Startpositionen an Klippenkanten oder in Tiefwasser.
Die Karte speichern
Deine Änderungen liegen im Speicher, bis du sie in die Datei .rtsmap speicherst:
- Klicke in der Kopfzeile des Editorfensters auf Save. Die Schaltfläche ist hervorgehoben, solange es nicht gespeicherte Änderungen gibt, und neben dem Kartennamen erscheint ein Punkt.
- Save As… schreibt eine neue Datei
.rtsmapan einen beliebigen Ort inAssetsund weist sie dem Terrain zu. - Beim Speichern der Szene (Ctrl+S) werden auch die Karten ihrer Terrains gespeichert. Du kannst das im Optionsmenü des Overlays in der Scene View ausschalten (Save Maps When Saving the Scene).
- Auch der Inspector von RTS Terrain und das Overlay-Menü in der Scene View haben die Einträge Save und Save As.
Wenn du mit nicht gespeicherten Kartenänderungen in den Play Mode wechselst, eine Szene schließt oder Unity beendest, fragt ein Dialog, ob du sie speichern willst. Wähle im Project-Fenster eine Datei .rtsmap aus, um ihre Minikarten-Vorschau und eine Schaltfläche Open In Scene zu sehen.
Ein Terrain ohne Kartendatei behält seine Karte nur im Speicher. Wenn du den Dialog „The map of '…' is not saved as a map asset yet“ siehst, wähle Save As…, um die Datei zu erstellen.
Dein erster Spieltest
Du kannst deine Karte auf zwei Arten spielen: mit dem Demo Game im Play Mode oder im laufenden Spiel mit dem In-Game-Editor und seiner Schaltfläche Play Test.
Mit dem Demo Game spielen
- Öffne die Szene mit deinem Terrain. Achte darauf, dass sie mindestens eine Startposition hat (sonst spawnen die Einheiten in zwei gegenüberliegenden Ecken).
- Wähle . Ein Objekt RTS Demo Game wird erstellt und ausgewählt.
- Klicke auf Play. Speichere die Karte, wenn du gefragt wirst.
- Das Demospiel richtet alles ein, was es braucht: Nebel des Krieges, RTS-Kamera, Auswahl, Bewegungsbefehle, Minikarte, HUD, Gebäudeplatzierung und das Abbauen von Bäumen. Deine Einheiten (Spieler 1) spawnen an deiner Startposition; die Einheiten der anderen Spieler wandern umher.
- Drücke F1 für das Hilfe-Panel. Die Steuerung ist unter Steuerung in der Gameplay-Demo aufgeführt.
- Beende den Play Mode. Während des Spiels gefällte Bäume werden wiederhergestellt, sodass die Karte so bleibt, wie du sie erstellt hast.
Die Komponente RTS Demo Game hat Optionen wie Local Team, Units Per Player, Unit Prefabs (wenn leer, werden Platzhaltereinheiten verwendet), Fog Of War, Minimap und Hud. Als Kamera dient die Hauptkamera der Szene.
Wenn Einheiten im Nebel des Krieges sichtbar bleiben, fehlt das Renderer Feature RTS Fog Of War. Führe Validate Project aus und wende Add Feature an.
Im Spiel bearbeiten und testen
Der In-Game-Editor bietet dieselben Werkzeuge in deinem laufenden Spiel, mit einer eigenen UI. Spieler und Modder können damit in einem fertigen Spiel Karten bauen und spielen.
- Öffne die Szene mit deinem Terrain und einer Kamera. Wenn sie noch kein RTS Demo Game hat, füge eines wie oben beschrieben hinzu.
- Wähle .
- Klicke auf Play. Der Editor öffnet sich mit einer oberen Leiste (File, Map, Rückgängig und Wiederherstellen, Symmetry, Grid, Pathing, Markers, Play Test), einer Kategorie- und Werkzeugleiste links, dem Werkzeug-Panel rechts und einer Statusleiste. Das Demospiel wartet, bis du einen Spieltest startest.
- Male wie im Unity Editor: linke Maustaste, Shift zum Umkehren, Ctrl+Klick zum Aufnehmen, [ ] und - = für den Pinsel, 1…8 für die Kategorien. Bewege die Kamera mit W A S D, durch Ziehen mit der rechten oder mittleren Maustaste, mit Q/E und dem Mausrad.
- Klicke auf Play Test oder drücke F5. Einheiten spawnen, der Nebel des Krieges und die RTS-Kamera werden eingeschaltet.
- Drücke Esc, um zum Editor zurückzukehren. Die Karte wird auf ihren Zustand vor dem Spieltest zurückgesetzt.
Nützliche Tasten im In-Game-Editor: F1 Hilfe, F2 den gesamten Editor ein- oder ausblenden, H Panels ausblenden, G Raster, P Pathing-Overlay, M Marker, F den Punkt unter dem Cursor fokussieren, Ctrl+Z / Ctrl+Y Rückgängig und Wiederherstellen.
Im In-Game-Editor schreiben (Ctrl+S) und (Ctrl+Shift+S) Kartendateien in den Kartenordner des Spiels (standardmäßig ein Ordner Maps in Application.persistentDataPath), genau wie ein fertiges Spiel. Wenn du nach dem Bearbeiten im Unity Editor den Play Mode beendest, fragt ein Dialog „Save map changes made in Play Mode?“: Save schreibt die Änderungen in das Asset .rtsmap des Terrains, Discard verwirft sie.
Der In-Game-Editor wird ausführlich unter In-Game-Editor behandelt.
Für Programmierer: auf einen Spieltest reagieren
Dein eigener Gameplay-Code kann dasselbe Event abonnieren, das auch das Demospiel verwendet. Dieses Beispiel protokolliert beim Start eines Spieltests, wo jeder Spieler startet:
using RTSTerrainStudio;
using UnityEngine;
// Add this component to any GameObject in a scene with an RTS Terrain and an in-game editor.
public class PlaytestStartLogger : MonoBehaviour
{
void OnEnable()
{
// Raised by the in-game editor when a play test starts (true) or stops (false).
RTSRuntimeEditorEvents.PlaytestChanged += OnPlaytestChanged;
}
void OnDisable()
{
RTSRuntimeEditorEvents.PlaytestChanged -= OnPlaytestChanged;
}
void OnPlaytestChanged(bool playing)
{
if (!playing) return;
// The first enabled terrain of the scene.
RTSTerrain terrain = RTSTerrain.Main;
if (terrain == null || terrain.Map == null) return;
foreach (StartLocation start in terrain.Map.StartLocations)
{
// Start locations are stored in map-local coordinates: convert to world space
// and move the point onto the ground.
var local = new Vector3(start.position.x, start.position.y, start.position.z);
Vector3 world = terrain.SnapToGround(terrain.LocalToWorld(local));
Debug.Log($"Player {start.player + 1} starts at {world}, facing {start.yaw:0} degrees");
}
}
}
Terrain-Abfragen, das Laden von Karten und die Werkzeug-API findest du unter Scripting.
10-Minuten-Tutorial: deine erste 1v1-Karte
In diesem Tutorial baust du eine kleine, faire Karte für zwei Spieler: Jeder Spieler erhält eine Basis auf einem Plateau mit Rampe, ein Fluss trennt die beiden Hälften, und Wälder rahmen die Karte ein. Weil du mit Symmetrie malst, sind beide Seiten identisch, und du musst nur eine Hälfte bauen.
Bevor du beginnst: Das Projekt ist validiert und die Demo-Inhalte sind vorhanden (siehe Installation und Einrichtung).
1. Die Karte erstellen (1 Minute)
- Wähle , nimm eine URP-Vorlage und speichere die Szene als
Assets/MyMaps/Duel.unity. - Öffne .
- Basics: Map Name
Duel, dein Name als Author, Players 2. Klicke auf Next. - Size: Klicke auf Small (96 × 96, 1v1 und 2v2). Behalte Cell Size 2 m und Base Cliff Level 2 bei. Klicke auf Next.
- Look: Tileset Summer, klicke auf Grass als Basisschicht, Vegetation Library Demo Vegetation, Decor Library Demo Decor. Klicke auf Next.
- Terrain: Wähle Flat. Klicke auf Next.
- Create: Behalte New terrain object bei, aktiviere Add Environment und Open Editor Window und klicke dann auf Create Map.
2. Symmetrie einschalten (10 Sekunden)
- Öffne im Editorfenster den Tab Cliffs. Das Panel Symmetry findest du unter dem Panel Brush.
- Wähle Rotate 180°. Belasse das Symmetriezentrum in der Kartenmitte (0.5, 0.5).
- Bewege die Maus über das Terrain: Du siehst jetzt einen zweiten Pinsel auf der gegenüberliegenden Seite der Karte, und die Symmetrie-Hilfslinien werden in der Scene View gezeichnet.
Die Symmetrie gilt für alle Werkzeuge gemeinsam, sie bleibt also eingeschaltet, wenn du das Werkzeug wechselst. Die Modi:
| Modus (Fenster) | Kopien | Typische Verwendung |
|---|---|---|
| Off | 1 | Kampagnen- oder Story-Karten |
| Mirror X | 2 | Links und rechts spiegeln |
| Mirror Z | 2 | Oben und unten spiegeln |
| Mirror XZ | 4 | An beiden Achsen spiegeln (4 Spieler) |
| Rotate 180° | 2 | Punktsymmetrie, das klassische 1v1-Layout (Ecke gegen Ecke) |
| Rotate 90° | 4 | Vierfache Rotationssymmetrie für 4 Spieler; erfordert eine quadratische Karte |
| Diagonal | 2 | An der Diagonalen spiegeln; erfordert eine quadratische Karte |
Das Overlay in der Scene View und der In-Game-Editor bieten dieselben Modi (das Overlay zeigt die Enum-Namen, zum Beispiel Rotate 2 für Rotate 180°).
3. Die Basen bauen (2 Minuten)
- Klicke auf Raise / Lower Level. Stelle mit [ und ] einen großen Pinsel ein.
- Klicke im unteren linken Viertel der Karte und ziehe, um ein Plateau zu malen, das etwa ein Viertel der Kartenbreite einnimmt. Dasselbe Plateau erscheint im oberen rechten Viertel.
- Klicke auf Ramp und male quer über die Plateaukante, die zur Kartenmitte zeigt. Mache die Rampe mindestens zwei bis drei Zellen breit, damit Gruppen von Einheiten durchpassen.
- Klicke auf Clean Cliffs und male einmal über den Umriss des Plateaus, um übrig gebliebene Buckel aus einzelnen Vertices zu entfernen.
4. Den Fluss graben (2 Minuten)
- Bleibe bei Raise / Lower Level, halte Shift gedrückt und ziehe einen Fluss von der oberen linken Ecke in Richtung Kartenmitte. Die Kopie setzt ihn von der unteren rechten Ecke aus fort, sodass der Fluss die gesamte Karte durchquert.
- Lasse in der Nähe der Mitte ein oder zwei Landbrücken stehen, damit die Spieler einander erreichen können.
- Öffne den Tab Water, klicke auf Flood Fill, behalte Level 1 bei und klicke in das Flussbett. Klicke erneut in jeden Teil, der noch nicht gefüllt ist.
5. Den Boden texturieren (2 Minuten)
- Öffne den Tab Paint. Stelle im Panel Auto Paint Whole Map die Einstellung Profile auf Summer Auto Paint (bleibt sie leer, werden eingebaute Regeln verwendet, die auf die Schichtnamen des Tilesets abgestimmt sind) und klicke auf Auto Paint Whole Map. Bestätige den Dialog; das komplette Neutexturieren ist ein einziger Undo-Schritt. Erde erscheint in der Nähe von Klippen, Sand an den Flussufern und Fels an steilen Hängen.
- Klicke auf Paint Layer, wähle Cobblestone und male auf jedem Plateau dort ein Quadrat, wo das Hauptgebäude stehen soll (ein Tupfer genügt, die Kopie bemalt die andere Basis).
- Wähle Dirt und male einen Weg von der Rampe der Basis zur Landbrücke.
- Optional: Verwende im Tab Height das Werkzeug Raise / Lower mit geringer Stärke (-), um ein paar sanfte Hügel zwischen den Basen hinzuzufügen, und Smooth, um sie zu glätten.
6. Wälder, Gras und Felsen (2 Minuten)
- Öffne den Tab Vegetation und klicke auf Place Trees. Wähle Pine und Broadleaf und behalte Scatter bei.
- Male Wälder entlang des Kartenrands und hinter jeder Basis. Halte Rampen, Wege und Landbrücken frei; Shift entfernt Bäume.
- Klicke auf Paint Details, wähle Grass und Flowers Yellow und male ein paar Wiesen.
- Öffne den Tab Decor, klicke auf Place Decor, wähle ein paar Felsen und klicke in die Nähe der Flussufer.
7. Die Startpositionen platzieren (30 Sekunden)
- Öffne den Tab Markers und klicke auf Start Locations.
- Wähle Spieler P1 und achte darauf, dass Symmetry Assigns Next Players aktiviert ist.
- Klicke einmal in die Mitte der unteren linken Basis. P1 wird dort platziert und P2 erscheint auf der gegenüberliegenden Basis; beide blicken zur Mitte, wenn du einen Yaw einstellst, der dorthin zeigt.
8. Validieren, speichern und spielen (1 Minute)
- Öffne den Tab Map und klicke im Panel Validation auf Validate Map. Behebe alle gemeldeten Fehler (zum Beispiel eine Startposition an einer Klippenkante).
- Klicke in der Kopfzeile des Fensters auf Save. Die Karte wird in
Assets/MyMaps/Duel.rtsmapgespeichert. - Wähle und klicke auf Play.
- Wähle deine Einheiten aus, schicke sie per Rechtsklick über die Landbrücke und beobachte, wie der Gegner aus dem Nebel des Krieges auftaucht.
Du willst vier Spieler? Erstelle eine quadratische Karte (zum Beispiel Medium 128 × 128), wähle Rotate 90° und setze Players auf 4. Ein Klick mit dem Werkzeug Start Locations platziert dann P1 bis P4. Oder überlasse es dem Generator: Wähle im Schritt Terrain des Assistenten Generate, stelle Symmetry und Start Locations ein, klicke auf Preview und verfeinere das Ergebnis anschließend von Hand.
Wie geht es weiter?
- Editorfenster: alle Tabs, Panels und Einstellungen im Detail, einschließlich des Tabs Map (Größenänderung, Kartengenerator, Import und Export von Heightmaps, Minikarte, Umgebung).
- Die Werkzeugkapitel, eines pro Kategorie: Height, Cliffs, Paint, Vegetation, Decor, Water, Pathing, Markers.
- In-Game-Editor: den Editor mit deinem Spiel ausliefern, damit Spieler Karten bauen können.
- Scripting: Höhen, Klippenstufen und Pathing aus Code abfragen, Karten zur Laufzeit laden, auf Bearbeitungen reagieren.
- Integrationen: Multiplayer mit Mirror, Wegfindung mit dem A* Pathfinding Project oder Unity NavMesh.
- Fehlerbehebung: pinke Materialien, flach wirkendes Wasser, Versatz beim Malen und andere häufige Probleme.
Projekteinrichtung
RTS Terrain Studio braucht einige Projekteinstellungen: die Universal Render Pipeline mit zwei Kameratexturen, das Renderer Feature für den Nebel des Krieges, den Farbraum Linear und drei Unity-Pakete. Das Fenster prüft all diese Punkte und behebt die meisten mit einem Klick. Deshalb brauchst du die manuellen Schritte in diesem Kapitel in der Regel nie. Lies weiter, wenn eine Prüfung rot bleibt, wenn du wissen willst, was eine Korrektur ändert, oder wenn du ein Projekt von Hand einrichtest.
Im Project Validator öffnet die Hilfe-Schaltfläche jedes Panels den passenden Abschnitt dieses Kapitels.
Der Project Validator
Öffne oder klicke im Welcome-Fenster auf Validate. Die Prüfungen starten, sobald sich das Fenster öffnet. Sie sind in fünf Kategorien unterteilt:
| Kategorie | Was geprüft wird |
|---|---|
| Rendering | URP ist aktiv, Depth Texture und Opaque Texture sind aktiviert, das Renderer Feature für den Nebel des Krieges ist installiert, Player-Builds behalten die Nebelmodi, die RTS Environment verwendet. |
| Project | Farbraum Linear; Burst, Collections und Mathematics sind installiert; welche Eingabeverarbeitung aktiv ist. |
| WebGL | Nur solange Web die aktive Build-Plattform ist: Die Qualitätsstufen von Web-Builds haben Depth Texture und Opaque Texture, und das Web-Template füllt das Browserfenster (siehe Web-Prüfungen im Project Validator). |
| Content | Die Shader des Pakets kompilieren und sind in Builds enthalten, die Textur-Arrays der Tilesets sind gebaut, bei instanzierten Prototypen ist GPU-Instancing aktiviert. |
| Scene | Jedes RTS Terrain in den geöffneten Szenen hat ein Tileset und eine Kartendatei. |
Jedes Ergebnis hat einen von vier Statuswerten:
| Status | Bedeutung |
|---|---|
| OK | Alles ist korrekt eingerichtet. |
| Info | Ein Hinweis oder eine Empfehlung. Nichts ist defekt. |
| Warning | Eine Funktion arbeitet nur eingeschränkt, zum Beispiel Wasser ohne Lichtbrechung. Eine Korrektur wird empfohlen. |
| Error | Eine erforderliche Einstellung fehlt. Teile des Pakets funktionieren nicht. |
Fix All wendet alle verfügbaren Korrekturen an. Zuvor listet ein Dialog alle Änderungen auf; geändert wird erst, wenn du auf Apply Fixes klickst. Korrekturen, die URP voraussetzen (die Kameratexturen und das Renderer Feature für den Nebel des Krieges), laufen automatisch, sobald die Pipeline zugewiesen ist. So genügt ein einziger Klick, um ein frisches Projekt einzurichten. Run Again wiederholt die Prüfungen. Die vollständige Liste der Prüfungen mit ihren Korrektur-Schaltflächen findest du unter Das Projekt validieren.
Der Project Validator prüft dein Projekt. Um eine einzelne Karte zu prüfen (Startpositionen, Erreichbarkeit, fehlende Prototypen, Performance-Grenzen), verwende den Kartenvalidator im Tab Map; siehe Kartenvalidierung.
Prüfungen per Code ausführen
RTSProjectValidator.RunChecks() liefert die Ergebnisse, ohne etwas zu ändern, und
RTSProjectValidator.FixAll() wendet die Korrekturen an. Du kannst beide in einem Build-Skript oder
einem Continuous-Integration-Job verwenden. Das Beispiel muss in einem Ordner Editor liegen.
using UnityEditor;
using UnityEngine;
using RTSTerrainStudio.Editor;
// Command line: Unity -batchmode -projectPath <path> -executeMethod ProjectSetupCheck.Run -quit
public static class ProjectSetupCheck
{
public static void Run()
{
// Runs every check. Nothing is modified.
var results = RTSProjectValidator.RunChecks();
// Applies every available fix without dialogs, then checks again.
int applied = RTSProjectValidator.FixAll(results, interactive: false);
if (applied > 0) results = RTSProjectValidator.RunChecks();
// Logs warnings and errors ("[Status] Title: Message").
foreach (var result in results)
if (result.IsProblem) Debug.LogWarning(result.ToString());
// Fails the job when a required setting is still missing.
if (RTSProjectValidator.WorstStatus(results) == RTSValidationStatus.Error)
EditorApplication.Exit(1);
}
}
Die Korrektur Install für fehlende Pakete startet nur eine Anfrage an den Package Manager. Bis diese abgeschlossen ist, können die Skripte des Pakets nicht kompilieren. Installiere Burst, Collections und Mathematics, bevor du das Paket im Batch-Modus ausführst.
Universal Render Pipeline
Alle Shader des Pakets sind für URP geschrieben. Ohne URP werden Terrain, Wasser und Vegetation pink oder gar
nicht dargestellt. Die Korrektur Use URP verwendet ein vorhandenes URP-Asset des Projekts (oder erstellt
Assets/Settings/RTS URP.asset mit einem Renderer) und weist es als Standard-Render-Pipeline sowie jeder
Qualitätsstufe zu, die eine andere Pipeline verwendet. So machst du dasselbe von Hand:
- Wenn das Projekt noch kein URP-Asset hat, erstelle eines im Project-Fenster über .
- Weise es unter als Default Render Pipeline zu.
- Öffne . Prüfe bei jeder Qualitätsstufe mit einem Override für Render Pipeline Asset, dass es sich ebenfalls um ein URP-Asset handelt.
Depth Texture und Opaque Texture
Wasser nutzt die Tiefentextur der Kamera, damit sich Tiefenfarbe, Schaum und der weiche Übergang am Ufer an Objekte anpassen, die im Wasser stehen. Die Opaque-Textur nutzt es für die Lichtbrechung. Ohne sie wirkt das Wasser schlichter. Wähle jedes URP-Asset aus, das deine Qualitätsstufen verwenden, und aktiviere in seinem Abschnitt Rendering die Optionen Depth Texture und Opaque Texture, oder klicke im Validator auf Enable Both.
Eine Kamera kann beide Einstellungen im Abschnitt Rendering ihres eigenen Inspectors überschreiben, und das Wasser richtet sich nach der Einstellung jeder Kamera, die es rendert. Wenn das Wasser in der Game View weiterhin schlichter wirkt als erwartet, prüfe, ob deine Spielkamera sie ausschaltet. Die Scene View verwendet immer das URP-Asset. Siehe auch die URP-Voraussetzungen für Wasser.
Das Renderer Feature für den Nebel des Krieges
Das Renderer Feature RTS Fog Of War (RTSFogOfWarRendererFeature) zeichnet den Nebel über
alles auf dem Bildschirm, auch über Einheiten und Props mit deinen eigenen Materialien. Ohne das Feature zeigen nur
die Shader des Pakets den Nebel. Add Feature fügt es dem Standard-Renderer jedes verwendeten
URP-Assets hinzu. Um es von Hand hinzuzufügen, wähle das Asset Universal Renderer Data aus, klicke auf
Add Renderer Feature und wähle RTS Fog Of War. Details findest du unter
Rendering: Renderer Feature und Shader-Fallback.
Farbraum
Das Paket ist für den Farbraum Linear ausgelegt, den Standard von URP. Im Farbraum Gamma wirken Farben, Beleuchtung und Wasser im Vergleich zu den Screenshots ausgewaschen. Stelle auf Linear, oder klicke im Validator auf Switch to Linear.
Beim Wechsel des Farbraums werden alle Texturen neu importiert. In einem großen Projekt kann das eine Weile dauern.
Benötigte Pakete
Die Laufzeit-Assembly verwendet Burst (com.unity.burst), Collections
(com.unity.collections) und Mathematics (com.unity.mathematics) für ihre
Jobs und ihr Datenmodell. Solange eines davon fehlt, kompilieren die Skripte des Pakets nicht. Installiere sie über
, oder klicke im Validator auf
Install. Der Validator fügt dann alle fehlenden Pakete mit einer einzigen Anfrage an den Package Manager hinzu.
Die getesteten Versionen stehen unter Benötigte Pakete.
Wenn der Import Kompilierfehler zeigt und das Menü fehlt, kann auch der Validator nicht laufen, weil er Teil des Pakets ist. Installiere die drei Pakete dann von Hand und öffne danach den Validator.
Eingabeverarbeitung
Alle Eingaben zur Laufzeit (für den In-Game-Editor, die Beispielkamera und die Beispieleinheiten) laufen über die
statische Klasse RTSInput. Wenn das Paket Input System (com.unity.inputsystem)
installiert ist, wird die Laufzeit-Assembly mit dem Define-Symbol RTS_INPUT_SYSTEM kompiliert und
nutzt das Input System. Andernfalls oder wenn das Projekt nur den alten Input Manager verwendet, fällt
RTSInput auf die alte API UnityEngine.Input zurück.
| Active Input Handling | Input System installiert | Ergebnis |
|---|---|---|
| Input System Package (new) | ja | OK. Das Input System wird verwendet. |
| Both | ja | OK. Das Input System wird verwendet. |
| Input Manager (old) | ja | Info. Der Fallback wird verwendet. |
| beliebig | nein | Info. Der Fallback wird verwendet. Er funktioniert, wird für neue Projekte aber nicht empfohlen. |
Die Einstellung findest du unter . Wenn du sie änderst, fordert Unity dich auf, den Editor neu zu starten. Die Prüfung Input handling ändert die Einstellung nie von sich aus, weil sie von deinem Spiel abhängt. Siehe auch Eingabeverarbeitung in den Voraussetzungen.
Shader und Inhalte
- Package shaders compile. Wenn ein Shader des Pakets Kompilierfehler hat (betroffene Objekte werden pink dargestellt), importiert Reimport ihn neu. Das hilft, wenn die Shader importiert wurden, bevor URP fertig geladen war. Fehlen benötigte Shader vollständig, importiere das Paket erneut aus dem Asset Store. Siehe Fehlerbehebung.
- Shaders included in builds. Siehe Einen Build erstellen.
- Tileset texture arrays built. Tilesets, deren gebaute Arrays fehlen oder veraltet sind, funktionieren trotzdem. Bis die Arrays neu gebaut sind, baut der Editor beim Laden der Karte aber unkomprimierte Arrays. Das kostet Speicher und Ladezeit. Build Arrays baut sie für jedes veraltete Tileset. Bei Player-Builds werden veraltete Arrays automatisch neu gebaut; Tilesets, die nie gebaut wurden, verwenden auch in Player-Builds unkomprimierte Arrays. Siehe Textur-Arrays und Textur-Arrays in Player-Builds.
- GPU instancing on instanced prototypes. Bei Materialien von instanzierten Bäumen, Details im Modus Mesh und instanziertem Dekor sollte Enable GPU Instancing aktiviert sein. Andernfalls werden sie über temporäre Kopien gezeichnet, und ihre Instancing-Varianten werden unter Umständen aus Player-Builds entfernt. Enable Instancing aktiviert die Option bei jedem aufgelisteten Material, das sich bearbeiten lässt. Materialien innerhalb eines Modells oder eines Pakets sind schreibgeschützt: Extrahiere sie zuerst. Siehe GPU-Instancing.
- Terrains have a tileset. Ein Terrain ohne Tileset erscheint in einer neutralen Platzhalterdarstellung. Die Korrektur weist jedem Terrain ohne Tileset ein Tileset des Projekts zu (lässt sich rückgängig machen). Siehe Tilesets.
Die Korrekturen für Szenen werden im Play Mode nicht angeboten, weil Änderungen an der Szene im Play Mode verloren gehen, sobald er endet.
Kartendateien für Terrains in Szenen
Ein RTS Terrain speichert seine Karte in einem .rtsmap-Asset (Feld Map Asset).
Ein Terrain ohne ein solches Asset hält seine Karte nur im Arbeitsspeicher, und deine Bearbeitungen gehen verloren, wenn die
Szene geschlossen wird. Die Prüfung Terrains save to a map file listet diese Terrains auf.
Create Map Files speichert die aktuelle Karte jedes aufgelisteten Terrains in einer neuen
.rtsmap-Datei im Ordner seiner Szene (oder in Assets/Maps, wenn die Szene noch nicht
gespeichert ist) und weist sie dem Terrain zu.
Terrains, die mit dem New Map Wizard oder den Demo-Inhalten erstellt wurden, haben bereits eine Kartendatei. Wie Karten-Assets, Save und Save As funktionieren, erfährst du unter Karten-Assets und Kartendateien.
Einen Build erstellen
Unity nimmt Shader nur dann in einen Player-Build auf, wenn ein Material im Build sie verwendet. RTS Terrain Studio
erzeugt die Materialien für Terrain, Wasser und Nebel des Krieges zur Laufzeit per Code. Deshalb werden die Shader
vom Asset Resources/RTSTerrainStudio/RTSShaderReferences.asset im Paketordner referenziert. Das Asset
referenziert außerdem das Material DecorPlaceholder, das die Instancing-Varianten von URP Lit im Build
hält. Das Asset wird nach jedem Neuladen der Skripte, bei jedem Import von Shadern und vor jedem Player-Build
automatisch geprüft und aktualisiert. Wenn die Prüfung Shaders included in builds rot ist, klicke auf
Update.
Gehe vor dem Build diese Liste durch:
- Führe aus und behebe alle Fehler und Warnungen.
- Prüfe, ob jede Qualitätsstufe deiner Zielplattform ein URP-Asset mit Depth Texture und Opaque Texture verwendet und ob dessen Renderer das Feature RTS Fog Of War hat, falls du den Nebel des Krieges nutzt.
- Baue die Textur-Arrays deiner Tilesets einmal (Build Arrays im Validator oder Build Texture Arrays im Tileset-Inspector), damit das Terrain sie nicht beim Laden unkomprimiert baut. Veraltete Arrays werden vor jedem Player-Build automatisch neu gebaut (siehe unten).
- Wenn deine Karten atmosphärischen Nebel nutzen, stelle sicher, dass die Prüfung Fog kept in player builds OK ist (siehe Nebel in Player-Builds).
- Validiere deine Karten im Tab Map (siehe Kartenvalidierung).
- Füge deine Szenen der Szenenliste deines Build-Profils hinzu. Karten, die einem RTS Terrain in einer Szene zugewiesen sind, werden wie jedes andere referenzierte Asset in den Build aufgenommen.
- Wenn du den In-Game-Editor mit auslieferst, lies Den In-Game-Editor mit dem Spiel ausliefern.
Karten, die Spieler zur Laufzeit speichern, landen im Ordner
MapsinRTSMapSerializer.StorageRoot(Application.persistentDataPath, in Web-Builds der Browserspeicher); siehe Kartendateien zur Laufzeit. - Wenn du die Einstellung Managed Stripping Level über Minimal anhebst und eigene Bearbeitungswerkzeuge geschrieben hast, lies Code-Stripping und eigene Werkzeuge.
- Lies für einen Web-Build außerdem Web-Builds (WebGL).
Du darfst RTSShaderReferences.asset und den Ordner Resources des Pakets weder
verschieben noch umbenennen: Das Paket lädt diese Assets zur Laufzeit mit Resources.Load.
Textur-Arrays in Player-Builds
Ein Player-Build verwendet die gebauten Textur-Arrays eines Tilesets so, wie sie sind, denn nur der Editor kann sie mit den Quelltexturen vergleichen. Damit ein Build nie veraltete Bodentexturen ausliefert, wird jedes Tileset, dessen Arrays schon einmal gebaut wurden, direkt vor dem Start eines Player-Builds geprüft:
- Arrays, die nicht mehr zu den Texturen, der Auflösung oder der Komprimierung passen oder die für eine andere Plattform oder Texturkomprimierung gebaut wurden, werden mit normaler Komprimierungsqualität neu gebaut. Währenddessen wird ein Fortschrittsbalken angezeigt.
- Tilesets, die nie gebaut wurden, bleiben unverändert. Player-Builds bauen für sie beim Laden einer Karte unkomprimierte Arrays.
- Wenn ein Neubau abgebrochen wird oder fehlschlägt und sich die Texturen oder Einstellungen geändert haben, bietet ein Dialog Try Again, Cancel Build und Build Anyway an. Arrays, die nur ein anderes Format verwenden, zeigen trotzdem die richtigen Texturen und erzeugen deshalb nur eine Warnung im Log.
- Ein Build im Batch-Modus bricht in diesem Fall ab. Füge der Befehlszeile
-rtsAllowOutdatedTextureArrayshinzu, um trotzdem zu bauen; dann wird pro Tileset eine Warnung ausgegeben. - Ein Editor ohne Grafikgerät (
-nographics, üblich auf Build-Servern) kann keine Arrays bauen. Der Build läuft dann mit einer Warnung pro veraltetem Tileset weiter, und Player-Builds verwenden die Arrays so, wie sie zuletzt gebaut wurden. Baue die Arrays vor einem solchen Build im Editor. - Arrays werden nur für die aktive Build-Plattform neu gebaut. Wenn du für eine andere Plattform baust, schreibt der Build stattdessen eine Warnung ins Log: Wechsle vorher zu dieser Plattform.
- Asset Bundles und Addressables-Inhalte, die du separat baust, durchlaufen diese Prüfung nicht. Baue die Arrays vorher.
Die Prüfung auf veraltete Arrays nutzt den Importzustand der Quelltexturen, der sich mit einem Unity-Upgrade ändern kann. Der erste Player-Build nach einem Upgrade baut deshalb unter Umständen die Arrays aller gebauten Tilesets neu, auch die der Demo-Tilesets.
Gebaute Arrays werden lesbar im Tileset-Asset gespeichert. Player-Builds geben diese CPU-Kopie frei, sobald die Arrays zum ersten Mal verwendet werden. Das spart etwa 14 MB pro Tileset bei 512 px und etwa 56 MB bei 1024 px.
Nebel in Player-Builds
RTSEnvironment schaltet den in einer Karte gespeicherten atmosphärischen Nebel ein, während das Spiel läuft. Unity
entfernt die Shader-Varianten von Nebelmodi, die es für unbenutzt hält. Steht
auf Automatic, behält Unity nur den Nebel von Build-Szenen, die mit eingeschaltetem Nebel
gespeichert sind. RTS Environment stellt vor jedem Speichern den eigenen Nebel der Szene wieder her. Sein Nebel
zählt deshalb nicht, und Karten mit Nebel würden im Build keinen Nebel zeigen.
Die Prüfung Fog kept in player builds liest die Build-Szenen des aktiven Build-Profils und die geöffneten Szenen und listet die Nebelmodi auf, die RTS Environment dort verwendet. Keep Fog Modes (auch Teil von Fix All) stellt Fog Modes auf Custom und behält genau diese Modi, dazu alle, die bereits behalten wurden. Jeder behaltene Modus fügt dem Build Shader-Varianten hinzu, deshalb werden nicht verwendete Modi nicht hinzugefügt. Wenn das aktive Build-Profil die Graphics-Einstellungen überschreibt, prüfe auch dort die Fog Modes.
Web-Builds (WebGL)
RTS Terrain Studio läuft im Webbrowser: Terrain, Vegetation, Wasser, Dekor, Nebel des Krieges, der In-Game-Editor und die Beispiele. Ein Browser funktioniert allerdings anders als ein Desktop-System: Es gibt keinen Ordner, den der Spieler öffnen kann, manche Tasten gehören dem Browser, der gesamte Code läuft auf einem einzigen Thread, und das ganze Spiel teilt sich einen Speicher-Heap begrenzter Größe. Dieser Abschnitt erklärt, wie das Paket damit umgeht und wie du einen Web-Build erstellst, testest und veröffentlichst.
Kurz zusammengefasst:
- Öffne , wähle Web und klicke auf Switch Platform (oder mache dein Web-Build-Profil zum aktiven Profil).
- Führe aus und klicke auf Fix All. Wenn Web aktiv ist, installiert der Validator außerdem das Vollfenster-Template und prüft die Qualitätsstufen von Web-Builds.
- Wenn deine Karten atmosphärischen Nebel nutzen, prüfe, ob Fog kept in player builds OK ist.
- Klicke auf Build And Run. Unity baut das Spiel und öffnet es in deinem Browser.
Was im Browser funktioniert
| Thema | Unterstützung |
|---|---|
| Grafik-API | WebGL 2.0, der Standard von Web-Builds (Auto Graphics API). Alle Shader des Pakets laufen dort. Dieser Abschnitt beschreibt WebGL 2.0; WebGPU, das Unity als Option anbietet, wird nicht behandelt. |
| Browser | Aktuelle Desktop-Browser, die der Web-Player von Unity unterstützt, bedient mit Maus und Tastatur. |
| Smartphones und Tablets | Die Seite lädt, aber der In-Game-Editor, die RTS-Kamera und die Beispieleinheiten sind für Maus und Tastatur gemacht. Touch-Eingabe wird nicht unterstützt. Das Template des Pakets zeigt auf Mobilgeräten einen Hinweis. |
| Rendering | Terrain, Vegetation, Wasser, Dekor und der Nebel des Krieges sehen genauso aus wie auf dem Desktop. Gras wird statt in einem Structured Buffer in einer Float-Textur gespeichert (siehe Rendering und Performance). |
| In-Game-Editor | Alle Werkzeuge, der Kartenbrowser und der Spieltest. Karten werden im Browserspeicher abgelegt und lassen sich herunterladen und importieren (siehe Karten im Browser). Für die Kartengröße und den Undo-Speicher gelten niedrigere Grenzen (siehe Der In-Game-Editor im Browser). |
| Threads | Web-Builds haben keine Worker-Threads, daher laufen alle Jobs nacheinander auf dem Hauptthread. |
Das Vollfenster-Template
Die Seite rund um einen Web-Build stammt aus einem Web-Template. Unitys eigene Templates Default und Minimal zeigen das Spiel in Desktop-Browsern in einem Rahmen fester Größe (960 × 600 Pixel, sofern du das nicht änderst). Der In-Game-Editor, das HUD und die Minikarte skalieren mit diesem Rahmen, sodass ihr Text schwer lesbar wird. Deshalb bringt das Paket ein eigenes Template mit:
- Das Spiel füllt das Browserfenster über einer schmalen Fußzeile mit dem Namen des Spiels und der Vollbild-Schaltfläche und passt seine Größe an das Fenster an. Auf Smartphones und Tablets füllt es die ganze Seite.
- Der Ladebalken (mit Prozentanzeige), die Vollbild-Schaltfläche und die Meldungsbanner von Unity bleiben erhalten.
- Auf hochauflösenden Bildschirmen ist die Render-Auflösung auf das 1.5-Fache der CSS-Pixelgröße begrenzt, was
die Framerate hoch hält. Um das anzupassen, ändere
maxDevicePixelRatioin derindex.htmldes installierten Templates (niedrigere Werte bis hinunter zu 1 sind schneller, höhere Werte schärfer). - Dateien, die das Spiel speichert, etwa Karten des In-Game-Editors, werden sofort in den Browserspeicher geschrieben.
- Die Seite stellt keine Anfragen an andere Server; ihre Icons sind Teil der Seite.
So installierst du es:
- Wähle oder klicke im Project Validator im Panel Web template fills the browser window auf Install Template.
- Das Template wird nach
Assets/WebGLTemplates/RTSTerrainStudiokopiert (Unity findet Templates nur inAssets/WebGLTemplates) und unter ausgewählt. - Ist das Template bereits installiert, fragt das Menü, ob du deine Kopie mit Keep and Select behalten und auswählen oder mit Replace durch die Version des Pakets ersetzen willst. Die Kopie gehört deinem Projekt: Bearbeite sie nach Belieben, das Paket überschreibt sie nie ohne Nachfrage.
Build-Profile: Ein Build-Profil kann die Player Settings überschreiben und behält dann seine eigene Template-Auswahl. Mache dein Web-Build-Profil zum aktiven Profil, bevor du das Template installierst, damit die Einstellung in diesem Profil landet. Wenn du es installiert hast, während eine andere Plattform aktiv war, wähle das Template RTSTerrainStudio von Hand in den Overrides der Player Settings deines Web-Build-Profils aus.
Das Template basiert auf dem Template Default von Unity und verwendet nur Unitys eigene Template-Variablen, deshalb
erscheint in den Player Settings nichts Neues. Auf Smartphones und Tablets zeigt es einige Sekunden lang den
Hinweis This game is best played on a computer with a mouse and keyboard. an; entferne diese Zeile aus
index.html, wenn dein Spiel Touch-Eingabe unterstützt.
Aus einem Editor-Skript (zum Beispiel einem Build-Skript) installiert
RTSWebGLTemplateInstaller.InstallAndSelect(false) das Template ohne Dialoge, behält eine vorhandene
Kopie und wählt sie aus. IsInstalled und IsSelected melden den aktuellen Zustand.
Web-Prüfungen im Project Validator
Solange Web die aktive Build-Plattform ist, zeigt der Validator eine Gruppe WebGL mit diesen drei Prüfungen.
| Prüfung | Korrektur | Was geprüft wird |
|---|---|---|
| Web quality levels have Depth and Opaque Texture | Enable Both | Die Qualitätsstufen, die in der Spalte Web von aktiviert sind (die Web-Standardstufe steht zuerst), und das URP-Asset, mit dem jede von ihnen rendert. Der Editor rendert meist mit einer Desktop-Qualitätsstufe, daher kann ein Web-Build schlichteres Wasser haben, das du im Editor nie siehst. Jede Textur kostet pro Frame eine Kopie des gesamten Bildschirms. Die allgemeine Prüfung Depth and Opaque Texture enabled meldet unter Umständen dasselbe Asset; Fix All behebt beides. |
| MSAA is off for Web builds | Turn MSAA Off | Ein Fehler, wenn eine Web-Qualitätsstufe mit einem URP-Asset rendert, das MSAA mit der Depth Texture oder der Opaque Texture kombiniert. URP kann diese Kombination unter WebGL 2 nicht rendern: Das Canvas-Element im Browser bleibt schwarz, und die Browserkonsole zeigt Render Graph Execution error. Die Korrektur schaltet MSAA in diesen Assets aus. Desktop-Qualitätsstufen, die ein solches Asset mitverwenden, verlieren MSAA ebenfalls. Gib der Web-Plattform daher eine Qualitätsstufe mit eigenem URP-Asset. |
| Web template fills the browser window | Install Template / Reinstall | Eine Warnung bei Unitys Templates Default und Minimal, ein Fehler, wenn das Template des Pakets ausgewählt ist, sein Ordner aber fehlt (der Build würde fehlschlagen), und OK für das Template des Pakets. Das Template PWA füllt das Fenster ebenfalls und ist OK, aber sein Service Worker speichert den Build im Cache: Erhöhe bei jedem Upload. Jedes andere eigene Template erhält den Status Info ohne Korrektur. |
Die Rendering-Prüfung Fog kept in player builds läuft für jede Plattform und ist auch für Web-Builds wichtig (siehe Nebel in Player-Builds).
Empfohlene Build-Einstellungen
| Einstellung | Ort | Empfehlung |
|---|---|---|
| Texture Compression | Dein Web-Build-Profil | Use Player Settings oder DXT für Desktop-Browser. Für einen Build, der auf mobile Browser zielt, wähle ASTC oder ETC2. Die Textur-Arrays der Tilesets folgen dieser Einstellung (DXT5, ASTC 6×6 oder ETC2 RGBA8) und werden vor dem Build neu gebaut. Kann ein Browser das gewählte Format nicht verwenden, dekomprimiert Unity die Texturen für ihn, was etwa den vierfachen Speicher braucht. |
| Maximum Memory Size | Behalte den Standardwert von 2048 MB bei. Die Grenzen des In-Game-Editors im Browser sind auf diesen Heap ausgelegt. | |
| Compression Format | Brotli ergibt den kleinsten Download, funktioniert aber nur über HTTPS. Gzip funktioniert auch über HTTP. Beide brauchen die richtigen Server-Header (siehe Auf einer Website veröffentlichen). | |
| Decompression Fallback | Schalte die Option ein, wenn du die Header des Servers nicht festlegen kannst, zum Beispiel auf Spieleportalen. Das Spiel lädt dann überall, startet aber etwas langsamer. | |
| Data Caching | An. Der Browser behält die heruntergeladenen Spieldaten, sodass der nächste Besuch schneller startet. | |
| Enable Exceptions | Explicitly Thrown Exceptions Only (der Standard) für Release-Builds. In diesem Modus wird eine Null-Referenz nicht als Exception gemeldet. Verwende daher einen Development-Build mit Full With Stacktrace, während du einen Fehler suchst. | |
| Managed Stripping Level | Für das Paket funktioniert jede Stufe. Höhere Stufen verkleinern den Download; eigene Bearbeitungswerkzeuge brauchen dann [Preserve] (siehe Code-Stripping und eigene Werkzeuge). | |
| Qualitätsstufen und URP-Asset | Deaktiviere die Spalte Web bei Desktop-Qualitätsstufen, die du im Browser nicht verwendest, damit ihre URP-Assets dem Build keine Shader-Varianten hinzufügen. Lass im URP-Asset für Web Light Cookies und Reflection Probe Blending ausgeschaltet, sofern deine Szenen sie nicht brauchen: WebGL 2.0 erlaubt nur 16 Texturen pro Draw Call (siehe Hinweise für Mobilgeräte). Lass Anti Aliasing (MSAA) in diesem Asset ausgeschaltet: Zusammen mit der Depth Texture oder der Opaque Texture verhindert es, dass URP unter WebGL 2 rendert. | |
| Web Template | RTSTerrainStudio (siehe Das Vollfenster-Template). |
Einen Web-Build auf deinem Computer testen
- Build And Run ist der einfachste Weg. Unity startet einen kleinen lokalen Webserver und öffnet das Spiel in deinem Standardbrowser.
- Ein Doppelklick auf die gebaute
index.htmlfunktioniert nicht. Browser führen Web-Builds nicht überfile://-Adressen aus, und die Seite zeigt nur eine Fehlermeldung. - Um einen fertigen Build-Ordner später erneut zu öffnen, stelle ihn über einen beliebigen lokalen Webserver bereit. Ein einfacher Server, der keine Komprimierungs-Header setzt, kann nur Builds mit eingeschaltetem Decompression Fallback oder mit deaktivierter Komprimierung ausliefern.
- Teste die Tasten einmal in deinem Zielbrowser: F1, F5, Ctrl+S, Alt+N und Esc im Vollbild (siehe Der In-Game-Editor im Browser).
Auf einer Website veröffentlichen
- Lade den gesamten Build-Ordner hoch:
index.html,BuildundTemplateData. - Komprimierte Builds brauchen zwei Header für die Dateien in
Build:Content-Encoding: brfür Brotli oderContent-Encoding: gzipfür Gzip sowie den richtigenContent-Type, zum Beispielapplication/wasmfür die.wasm-Datei. OhneContent-Encodingzeigt die Seite eine Fehlermeldung, und ein falscherContent-Typeverlangsamt den Start. Wenn du den Server nicht konfigurieren kannst, schalte Decompression Fallback ein. - Spieleportale wie itch.io zeigen das Spiel in einem
iframe. Lade eine ZIP-Datei hoch, in derindex.htmlauf oberster Ebene liegt. Einiframemit Sandbox brauchtallow-downloadsfür Download Map undallow-modals, damit der Browser Leave site? fragen kann. Prüfe beides auf der echten Seite. - Gespeicherte Karten gehören zur Website. Der Browser speichert sie pro Website-Adresse (Protokoll, Host und Port). Ein neuer Upload auf dieselbe Website behält sie; dasselbe Spiel auf einer anderen Website startet ohne Karten.
- Safari löscht unter Umständen die gespeicherten Daten einer Website, die sieben Tage lang nicht genutzt wurde, und Safari auf iPhone und iPad zeigt die Rückfrage Leave site? nicht an. Sage deinen Spielern, dass sie Karten, die sie behalten wollen, herunterladen sollen.
- In einem
iframeerhält das Spiel Tastendrücke erst, nachdem der Spieler hineingeklickt hat, und Esc beendet immer den Vollbildmodus des Browsers (siehe unten).
Karten im Browser
Im Browser ist Application.persistentDataPath ein Dateisystem im Arbeitsspeicher. Es wird nur auf
Anforderung in die Datenbank des Browsers (IndexedDB) kopiert. Das Template des Pakets stellt diese Anforderung
nach jeder Änderung. Bei anderen Templates stellt das Paket sie selbst nach jedem Speichern, Löschen und
Importieren, sodass gespeicherte Karten nach einem Neuladen noch vorhanden sind. Dein eigener Code erreicht
dasselbe mit RTSWebFileSystem.Flush() (siehe Scripting-API für Web-Builds).
persistentDataPath hängt von der Adresse der Seite ab. Ein neuer Upload unter einer anderen Adresse
(Spieleportale verwenden oft pro Upload eine neue) würde also ohne Karten starten. In Web-Builds liegen die
Karten deshalb in RTSMapSerializer.StorageRoot, einem Ordner im selben Browserspeicher, der nach dem
Firmen- und Produktnamen aus den Player Settings benannt ist. Auf allen anderen Plattformen ist
StorageRoot gleich persistentDataPath. Der Kartenordner des In-Game-Editors ist
der Unterordner Maps darin.
Spieler können diesen Speicher nicht als Ordner öffnen. Deshalb überträgt der In-Game-Editor Karten selbst zwischen Browser und Computer:
| Befehl | Ort | Funktion |
|---|---|---|
| Download Map | Menü File | Lässt den Browser die aktuelle Karte samt nicht gespeicherter Änderungen als .rtsmap-Datei herunterladen (meist in den Downloads-Ordner). |
| Import Map... | Menü File | Öffnet die Dateiauswahl des Browsers. Die gewählte .rtsmap-Datei wird den in diesem Browser gespeicherten Karten hinzugefügt und nach der üblichen Rückfrage zu nicht gespeicherten Änderungen geöffnet. |
| Import... | Kartenbrowser (Open) | Dasselbe wie Import Map, neben der Zeile mit dem Speicherort. |
| Download | Kartenbrowser (Open) | Lädt die ausgewählte Kartendatei herunter. |
| Ziehen und Ablegen | Spielfläche | Eine auf das Spiel gezogene .rtsmap-Datei wird importiert und geöffnet. Andere Dateien werden mit einer Meldung abgelehnt, damit der Browser die Seite nicht verlässt, um sie anzuzeigen. |
- Die ganze Datei wird vor dem Speichern geprüft, und Dateien bis 128 MB werden angenommen.
- Ein Import ersetzt nie eine andere Karte. Wird dieselbe Datei erneut importiert, wird sie wiederverwendet; eine andere Datei mit demselben Namen wird als Name (2), Name (3) usw. gespeichert. Beim Öffnen der Karte wird ein Vorschaubild geschrieben.
- Der Kartenbrowser zeigt statt eines Ordnerpfads Saved in this browser. Use Download to keep a copy on your computer. an. Show Maps Folder und Quit gibt es im Browser nicht.
- Beim Löschen der Browserdaten der Website gehen auch die gespeicherten Karten verloren.
Der In-Game-Editor im Browser
Den Tab mit nicht gespeicherten Änderungen schließen
Solange die Karte nicht gespeicherte Änderungen hat und Confirm Quit With Unsaved Changes eingeschaltet ist, fragt der Browser Leave site?, bevor der Tab geschlossen oder neu geladen wird. Den Text dieser Rückfrage legt der Browser fest. Mit Unitys eigenen Templates, die Dateien nicht sofort speichern, fragt er unabhängig von dieser Option auch, solange ein Speichervorgang noch in den Browserspeicher geschrieben wird.
Den Editor nach dem Schließen zurückholen
In Browsern sind die Funktionstasten oft Medientasten (auf Macs und vielen Laptops), und Smartphones haben gar keine. Wenn der Spieler den Editor mit seiner Schließen-Schaltfläche (×) schließt, erscheint daher unten links eine Schaltfläche Open Map Editor (F2), die ihn zurückholt. Ohne Tastatur oder wenn Toggle Key auf None steht, heißt die Schaltfläche Open Map Editor. Die Schaltfläche erscheint nur, nachdem die Schließen-Schaltfläche benutzt wurde; wenn dein Spielcode den Editor ausblendet, entscheidet dein Spiel, wie er zurückkommt. Im Browser ist die Schließen-Schaltfläche auch dann verfügbar, wenn Toggle Key auf None steht.
Tastatur
- New Map liegt auf Alt+N. Browser reservieren Ctrl+N für ein neues Fenster und geben es nie an die Seite weiter. Alt+N funktioniert auch auf Desktop-Plattformen. Auch Ctrl+T und Ctrl+W erreichen eine Seite nie.
- Browser-Tastenkürzel werden zurückgehalten. Solange der Editor aktiviert ist, reagiert der Browser nicht auf F1 bis F10 und auf Ctrl (oder Cmd) + S, O, D und A, sodass sie als Tastenkürzel des Editors funktionieren. F11, F12, Ctrl+F5, Ctrl+R, Kopieren, Einfügen und Tasten, die in Textfelder der Seite eingegeben werden, bleiben beim Browser.
- F5 startet und beendet den Spieltest, sodass du nicht auf Esc angewiesen bist.
- Esc im Vollbild: Browser verwenden Esc, um den Vollbildmodus zu beenden.
In Chrome und Edge erreicht auf einer sicheren Seite (HTTPS oder
localhost), die nicht in einemiframeliegt, ein kurzer Druck auf Esc das Spiel, und Gedrückthalten von Esc beendet den Vollbildmodus. Anderswo beendet bereits das erste Esc den Vollbildmodus. Die Alternativen: F5 oder Back to Editor beendet einen Spieltest, die Schaltfläche × schließt einen Dialog, und Ctrl+Z macht einen Pinselstrich rückgängig. Das Hilfefenster (F1) weist ebenfalls darauf hin. - Tastaturlayouts: Die Buchstaben-Tastenkürzel (Ctrl+Z, G, P …) richten sich nach dem Buchstaben, der auf der Taste aufgedruckt ist, auch auf deutschen (QWERTZ) und französischen (AZERTY) Tastaturen. Die Pinseltasten richten sich nach dem eingegebenen Zeichen, sodass [ auch dort funktioniert, wo es auf AltGr+8 liegt. Auf Desktop-Plattformen ist das genauso.
- Gameplay-Demo: Ctrl+1 bis Ctrl+9 (Kontrollgruppen) wechseln in den meisten Browsern die Browser-Tabs. Wähle für ein Webspiel andere Tasten.
Grenzen
Die Karte, ihre Texturen und der Undo-Verlauf teilen sich einen Speicher-Heap von höchstens 2 GB (der Standardwert von Maximum Memory Size), und dieser Heap schrumpft nie. Der Editor verwendet im Browser deshalb niedrigere Grenzen:
| Grenze | Browser | Andere Plattformen |
|---|---|---|
| Größte Karte in New Map und Resize | 2048 ÷ Splat-Auflösung Zellen pro Seite: 512 bei der Standard-Splat-Auflösung 4, 256 bei 8. Größere Presets werden nicht angeboten, und die Dialoge zeigen In the browser, maps can be up to N x N cells. Resize behält die Größe einer größeren Karte bei, die aus einer Datei geöffnet wurde, vergrößert sie aber nicht. | 1024 |
| Undo-Speicher | Höchstens 192 MB. Ein niedrigerer Wert von Undo Memory MB bleibt erhalten. | Undo Memory MB (Standard 512) |
| Importierte Kartendatei | 128 MB | Kein Importbefehl |
Weitere Unterschiede
- Die Benutzeroberfläche wird nie kleiner als 85 %, gemessen in CSS-Pixeln, damit sie auf hochauflösenden Bildschirmen und in kleinen Fenstern lesbar bleibt.
- Das Rand-Scrollen der Editorkamera und der Beispiel-RTS-Kamera stoppt, wenn die Maus die Seite oder das
iframedes Spiels verlässt.
Rendering und Performance
- Gras: WebGL 2.0 hat keine Structured Buffers. Der Detail-Renderer speichert die Instanzen jedes Patches stattdessen in einer Float-Textur (zwei Texel pro Instanz). Er wählt diesen Weg zur Laufzeit, und das Gras sieht gleich aus.
- Terrain: Der Forward-Pass verwendet 10 der 16 Texturen, die WebGL 2.0 pro Draw Call erlaubt. Halte das URP-Asset von Web-Builds schlank (siehe Hinweise für Mobilgeräte).
- Ein Thread: Ohne Worker-Threads dauern Operationen auf der ganzen Karte spürbar länger als auf dem Desktop, und die Seite reagiert währenddessen nicht: Generate, New Map, Resize, das Öffnen einer großen Karte und Apply to Whole Map von Auto Paint. Karten mit 128 bis 256 Zellen pro Seite bleiben angenehm bedienbar.
- Textur-Arrays: Verwende gebaute Arrays im Format deines Web-Builds (siehe Empfohlene Build-Einstellungen). Player-Builds geben die CPU-Kopie gebauter Arrays frei, was im begrenzten Speicher einer Seite wichtig ist.
- Render-Auflösung: Das Template begrenzt das Pixelverhältnis auf 1.5; senke es für eine höhere Framerate.
Code-Stripping und eigene Werkzeuge
Bearbeitungswerkzeuge werden per Reflection gefunden und erzeugt (RTSToolRegistry), daher verweist
kein Code direkt auf sie. Mit einem Managed Stripping Level über Minimal, wie es für
kleinere Web- und IL2CPP-Builds üblich ist, erkennt Unity nicht, dass sie verwendet werden, und würde sie
entfernen. Die Datei Runtime/link.xml des Pakets behält jede Klasse im Namespace
RTSTerrainStudio, deren Name auf Tool endet, sodass die eingebauten Werkzeuge immer
erhalten bleiben.
Markiere deine eigenen Werkzeugklassen mit [UnityEngine.Scripting.Preserve], das die Klasse und ihren
parameterlosen Konstruktor behält, oder liste sie in einer link.xml-Datei deines Projekts auf.
Findet ein Player-Build überhaupt keine Werkzeuge, schreibt er No editing tools were found in this build.
ins Log.
using RTSTerrainStudio;
using UnityEngine.Scripting;
// [Preserve] keeps the tool in builds with a high Managed Stripping Level.
[Preserve]
public sealed class CraterTool : RTSTool
{
// ...
}
Scripting-API für Web-Builds
| Member | Beschreibung |
|---|---|
RTSWebFileSystem.IsSupported | True in einem WebGL-Player. Auf anderen Plattformen und im Unity Editor tut die Klasse nichts. |
RTSWebFileSystem.Flush() | Kopiert das Dateisystem der Seite in den Browserspeicher, sodass mit System.IO geschriebene oder gelöschte Dateien ein Neuladen überstehen. Alle Aufrufe eines Frames werden zu einem Schreibvorgang zusammengefasst. RTSMapSerializer.SaveToFile ruft die Methode für dich auf. |
RTSWebFileSystem.DownloadFile(fileName, data, mimeType) | Lässt den Browser Bytes als Datei herunterladen. Auf anderen Plattformen oder wenn der Browser den Download abgelehnt hat, gibt die Methode false zurück. |
RTSMapSerializer.StorageRoot | Ordner für Dateien, die zwischen Sitzungen erhalten bleiben: Application.persistentDataPath, in Web-Builds ein Ordner im Browserspeicher, der nicht von der Adresse der Seite abhängt. |
RTSMapSerializer.DefaultMapsFolder | Maps in StorageRoot. |
RTSRuntimeEditor.RunsInBrowser | True in Web-Builds, in denen der Editor die Browser-Grenzen und Alt+N für New Map verwendet. |
RTSRuntimeEditor.StoresMapsInBrowser | True, wenn Karten im Browserspeicher abgelegt werden (WebGL-Player). |
RTSRuntimeEditor.HasReliableFunctionKeys | False in Webbrowsern und auf mobilen Plattformen, wo die Schließen-Schaltfläche die Schaltfläche zum erneuten Öffnen einblendet. |
editor.DownloadMap(), editor.ImportMap() | Die Befehle des Menüs File. Browser zeigen die Dateiauswahl nur als Reaktion auf einen Klick oder Tastendruck. Rufe ImportMap daher aus einem Klick-Handler auf. |
RTSInput.SetBrowserShortcutGuard(bool) | Unterdrückt die browsereigene Aktion für F1 bis F10 und Ctrl+S/O/D/A und lässt im Vollbild ein kurzes Esc zum Spiel durch (Chrome, Edge). Jeder Aufruf mit true braucht einen passenden Aufruf mit false. Der In-Game-Editor und RTSDemoHUD schalten diesen Schutz ein, solange sie aktiviert sind. Tut auf anderen Plattformen nichts. |
RTSInput.GetKeyDownByLabel(RTSKey) | Wie GetKeyDown, aber für die Taste, die im Tastaturlayout des Spielers diesen Buchstaben trägt. Verwende es für Buchstaben-Tastenkürzel wie Ctrl+Z. |
RTSInput.GetCharacterDown(char) | True, wenn das Zeichen in diesem Frame eingegeben wurde, unabhängig vom Tastaturlayout. Verwende es für Symboltasten wie [ ] - +. Zeichen, die in ein fokussiertes Textfeld eingegeben werden, kommen hier ebenfalls an. Prüfe daher zuerst den UI-Fokus. |
RTSInput.PointerInsideScreen | True, solange sich der Zeiger im Spielfenster befindet. In Web-Builds muss die Maus außerdem noch über der Seite sein. |
using System.IO;
using RTSTerrainStudio;
public static class MyGameFiles
{
// Kept between sessions on every platform; in Web builds in the storage of the browser.
static string SettingsPath => Path.Combine(RTSMapSerializer.StorageRoot, "settings.json");
public static void SaveSettings(string json)
{
Directory.CreateDirectory(RTSMapSerializer.StorageRoot);
File.WriteAllText(SettingsPath, json);
// Web builds: write the change to the browser storage, so it survives a reload.
// Does nothing on other platforms.
RTSWebFileSystem.Flush();
}
/// <summary>Lets the player keep a copy of the current map on the computer (Web builds only).</summary>
public static bool DownloadMap(RTSTerrain terrain)
{
if (!RTSWebFileSystem.IsSupported || terrain.Map == null) return false;
byte[] bytes = RTSMapSerializer.Serialize(terrain.Map);
return RTSWebFileSystem.DownloadFile("My Map" + RTSMapSerializer.FileExtension, bytes);
}
}
Grundkonzepte
Dieses Kapitel erklärt, wie RTS Terrain Studio eine Karte versteht: was gespeichert wird, in welchen Einheiten, wie die sichtbare Oberfläche aus diesen Daten entsteht und welche Assets (Tilesets, Bibliotheken, Kartendateien) dazugehören. Du musst es vor deiner ersten Karte nicht lesen. Kehre aber hierher zurück, wenn du wissen willst, warum sich etwas so verhält, oder wenn du anfängst, Code zu schreiben, der mit den Kartendaten arbeitet.
Kurz gesagt:
- Eine Karte ist ein Raster aus quadratischen Zellen. Höhen, Klippenstufen und Wasser liegen auf den Ecken (Vertices) der Zellen.
- Klippenstufen sind ganze Zahlen von 0 bis 15. Wo benachbarte Ecken unterschiedliche Stufen haben, entsteht eine senkrechte Klippenwand, es sei denn, alle vier Ecken der Zelle sind als Rampe markiert.
- Bodentexturen, Grasdichte und Pathing-Overrides sind Rasterschichten mit mehreren Texeln pro Zelle.
- Bäume, Dekor, Regionen, Startpositionen und Marker sind Objektlisten in kartenlokalen Koordinaten.
- Das Aussehen stammt aus einem Tileset (Boden-, Klippen- und Wassertexturen); die Objekte stammen aus einer Vegetationsbibliothek und einer Dekorbibliothek. Karten speichern nur Indizes und IDs, daher kannst du das Aussehen austauschen, ohne die Karte anzufassen.
- Alles wird in einer einzigen kompakten Binärdatei
.rtsmapgespeichert, die sowohl der Unity Editor als auch der In-Game-Editor verwenden.
Das Kartenmodell
Alle Kartendaten liegen in einem einzigen Objekt der Klasse MapData (Namespace RTSTerrainStudio). Eine
Komponente RTSTerrain besitzt genau ein MapData-Objekt (terrain.Map), stellt es als Chunk-Meshes dar,
hält die daraus abgeleitete Pathing-Karte aktuell und beantwortet Abfragen wie „Wie hoch ist der Boden hier?“.
Zellen und Vertices
Die Karte ist ein Raster aus Width × Height Zellen. Jede Zelle hat an ihren vier Ecken je einen
Vertex, und benachbarte Zellen teilen sich ihre Ecken. Eine Karte mit 4 × 2 Zellen hat deshalb 5 × 3 Vertices:
z
2 v10 ---- v11 ---- v12 ---- v13 ---- v14
| cell | cell | cell | cell |
| (0,1) | (1,1) | (2,1) | (3,1) |
1 v5 ----- v6 ----- v7 ----- v8 ----- v9
| cell | cell | cell | cell |
| (0,0) | (1,0) | (2,0) | (3,0) |
0 v0 ----- v1 ----- v2 ----- v3 ----- v4
x=0 1 2 3 4
vertex index = z * VertexCountX + x (VertexCountX = Width + 1)
- Vertex-Schichten (Feinhöhe, Klippenstufe, Flags, Klippentyp, Wasserhöhe) haben
(Width + 1) × (Height + 1)Einträge, Zeile für Zeile gespeichert. Verwendemap.VertexIndex(x, z). - Die Zellgröße (
map.CellSize, standardmäßig 2 m) ist die Weltgröße einer Zelle. Eine Karte mit 128 × 128 Zellen à 2 m deckt 256 × 256 m ab (map.WorldSize). - Eine Position gehört zur Zelle
floor(localXZ / CellSize);map.LocalToCell(localXZ)berechnet das und begrenzt das Ergebnis auf die Karte. - Zum Rendern werden die Zellen zu Chunks zusammengefasst (16, 32 oder 64 Zellen pro Seite, Einstellung Chunk Size der
Komponente
RTSTerrain, standardmäßig 32). Nur Chunks, die von einer Bearbeitung betroffen sind, werden neu aufgebaut. Die Chunk-Größe ändert die Kartendaten nicht.
Kartenabmessungen
Die Abmessungen werden beim Anlegen einer Karte festgelegt (im New Map Wizard, im In-Game-Dialog New Map oder über
MapCreationSettings im Code). Werte außerhalb des gültigen Bereichs korrigiert MapCreationSettings.Validate().
| Einstellung | Standard | Gültige Werte | Beschreibung |
|---|---|---|---|
width, height | 128 | 16–1024, auf ein Vielfaches von 16 gerundet | Anzahl der Zellen entlang X und Z. Der New Map Wizard bietet Presets von Tiny (64) bis Epic XL (512). |
cellSize | 2 | ≥ 0.1 | Weltgröße einer Zelle in Metern. |
cliffLevelHeight | 2 | ≥ 0.05 | Welthöhe einer Klippenstufe in Metern. |
baseCliffLevel | 2 | 0–15 | Anfängliche Klippenstufe jedes Vertex. Stufe 2 lässt nach unten Platz, um Seen und Flüsse auszuheben. |
baseGroundLayer | 0 | 0–15 | Bodenschicht, die anfangs die gesamte Karte bedeckt. |
splatResolution | 4 | 1, 2, 4, 8 | Texel der Bodentextur (Splat) pro Zellseite. |
detailResolution | 2 | 1, 2, 4 | Texel der Detaildichte (Gras) pro Zellseite. |
pathingResolution | 2 | 1, 2, 4 | Pathing-Zellen pro Seite einer Kartenzelle. |
name | "New Map" | beliebiger Text | Kartenname (wird in der Karteninfo gespeichert). |
Auflösungen, die keine Zweierpotenz sind, werden aufgerundet (aus 3 wird 4). Zellgröße, Stufenhöhe und die drei Rasterauflösungen
stehen fest, sobald die Karte existiert. Die Funktion Resize (RTSTerrain.ResizeMap) ändert nur die
Anzahl der Zellen: Sie behält den Inhalt, verschiebt ihn um einen Versatz und füllt neue Bereiche mit einer gewählten Klippenstufe und Bodenschicht.
MapData.Resize(newWidth, newHeight, offsetCells, fillCliffLevel, fillGroundLayer) gibt eine neue Karte zurück (das Original bleibt unverändert).
Bäume, Dekorobjekte und Startpositionen außerhalb der neuen Grenzen werden entfernt. Marker und Regionen werden verschoben, bleiben aber erhalten, auch wenn sie danach außerhalb liegen.
Die Größen werden auf 16–1024 begrenzt (MapData.MinSize–MaxSize), beim Aufruf aus dem Code aber nicht auf ein Vielfaches von 16 gerundet.
RTSTerrain.ResizeMap ersetzt die Karte (MapLoaded wird ausgelöst), markiert sie als nicht gespeichert und leert den Undo-Verlauf der Bearbeitungssitzungen.
Was eine Karte enthält
Für jede Datenart gibt es ein passendes Flag im Enum MapLayers. Die Flags werden für Änderungsbenachrichtigungen,
die Undo-Aufzeichnung und die Serialisierung verwendet.
| Daten | Gespeichert pro | Member in MapData | Typ | Flag in MapLayers |
|---|---|---|---|---|
| Feinhöhe | Vertex | Heights | NativeArray<float>, Meter | Heights |
| Klippenstufe | Vertex | CliffLevels | NativeArray<byte>, 0–15 | CliffLevels |
| Vertex-Flags (Rampe, Wasser) | Vertex | VertexFlags | NativeArray<byte> aus VertexFlags | VertexFlags |
| Klippentyp | Vertex | CliffTypes | NativeArray<byte>, 0–3 | CliffTypes |
| Höhe der Wasseroberfläche | Vertex | WaterHeights | NativeArray<float>, kartenlokales Y | Water |
| Gewichtungen der Bodenschichten | Splat-Texel | SplatMaps[0..3] | NativeArray<Color32> × 4 | Splat |
| Gras- und Pflanzendichte | Detail-Texel | DetailLayers | ein DetailLayer (ein Byte pro Texel) pro Prototyp | Details |
| Bäume | Instanz | Trees, TreePrototypeIds | List<TreeInstanceData> | Trees |
| Dekor (Doodads) | Instanz | Decor, DecorPrototypeIds | List<DecorInstanceData> | Decor |
| Manuelle Pathing-Overrides | Pathing-Zelle | PathingOverrides | NativeArray<byte> aus PathingOverride | PathingOverrides |
| Regionen | Liste | Regions | List<MapRegion> | Regions |
| Startpositionen | Liste | StartLocations | List<StartLocation> | StartLocations |
| Marker | Liste | Markers | List<MapMarker> | Markers |
| Karteninfo | Karte | Info | MapInfo | Info |
| Umgebung (Beleuchtung) | Karte | Environment | EnvironmentSettings | Environment |
Zwei kombinierte Flags sind praktisch: MapLayers.Geometry (Höhen, Stufen, Vertex-Flags, Klippentypen) und
MapLayers.PathingInputs (alles, was die Pathing-Karte beeinflusst). MapLayers.All umfasst alles.
Die Pathing-Karte (terrain.Pathing, Klasse PathingMap) besteht aus abgeleiteten Daten: Sie wird aus den
obigen Schichten berechnet und nie gespeichert. Siehe Pathing-Karte.
VertexFlags.User0 und VertexFlags.User1 sind für deinen eigenen Code reserviert. Das Paket verwendet sie nie, und
sie werden mit der Karte gespeichert. So kannst du besondere Vertices markieren (zum Beispiel „Creep-Spawnbereich“), ohne einen eigenen Dateiabschnitt anzulegen.
MapData belegt nativen Speicher. Eine Karte, die du selbst erstellst oder lädst (MapData.Create,
RTSMapSerializer.LoadFromFile, RTSMapAsset.LoadMap, map.Clone()), musst du mit
Dispose() freigeben – es sei denn, du übergibst sie mit terrain.SetMap(map) an ein Terrain. Dann gehört sie dem Terrain, und es gibt sie frei,
sobald die Karte ersetzt oder die Komponente deaktiviert wird.
Klippenstufen, Feinhöhen und Klippentypen
Klassisches RTS-Terrain kennt zwei Arten von Höhe: große, diskrete Stufen (Klippen) und kleine, sanfte Variationen (Hügel). RTS Terrain Studio speichert beide getrennt für jeden Vertex.
Klippenstufen
- Jeder Vertex hat eine Klippenstufe von 0 bis 15 (
MapData.MaxCliffLevel= 15). - Eine Stufe ist
CliffLevelHeightMeter hoch (standardmäßig 2 m). - Neue Karten beginnen auf Stufe 2. So bleiben zwei Stufen unter dem Boden für Seen, Flüsse und Schluchten und dreizehn darüber für Plateaus.
- Bearbeite die Stufen mit den Klippenwerkzeugen: Raise / Lower Level, Set Level, Ramp, Cliff Type und Clean Cliffs.
- Der Nebel des Krieges nutzt die Klippenstufen für die Sichtlinie: Einheiten sehen ihre eigene Stufe und alles darunter; höher gelegener Boden versperrt ihnen die Sicht.
Feinhöhen
Zusätzlich zur Stufe hat jeder Vertex eine Feinhöhe in Metern (Heights), positiv oder negativ. Die Höhenwerkzeuge
(Raise / Lower, Smooth, Flatten, Set Height, Noise, Terrace, Erode) ändern nur die Feinhöhe. So kannst du Hügel auf einem Plateau bauen,
ohne dass daraus Klippen werden. Die Bodenhöhe eines Vertex ergibt sich so:
vertex height = CliffLevels[i] × CliffLevelHeight + Heights[i] (map-local Y, metres)
Beispiel: Stufe 3 bei einer Stufenhöhe von 2 m und einer Feinhöhe von 0.4 m ergibt einen Vertex, der 6.4 m über dem Kartenursprung liegt.
map.GetVertexHeight(x, z) und map.GetVertexPosition(x, z) berechnen das für dich.
Klippentypen
Ein Tileset kann bis zu 4 Klippenschichten haben (zum Beispiel „Rock“ und „Grassy Cliff“). Jeder Vertex speichert, welche davon verwendet wird
(CliffTypes, 0–3, gemalt mit dem Werkzeug Cliff Type). Eine Klippenwand verwendet den Klippentyp der höchsten
Ecke ihrer Zelle. Hat ein Klippentyp-Index keine Schicht im Tileset, greift die Wand auf Klippenschicht 0 zurück.
Wie die Oberfläche entsteht (die Referenzoberfläche)
Das Struct MapGeometryView definiert die einzig gültige Oberfläche des Terrains. Die Chunk-Meshes, die Gameplay-Abfragen
(GetHeight, Raycast, …), die Pathing-Karte und der Nebel des Krieges folgen alle denselben Regeln. Was du siehst, ist also genau das, worauf die Einheiten laufen.
Jede Zelle wird anhand ihrer vier Ecken eingeordnet:
| Zellart | Bedingung | Oberfläche |
|---|---|---|
| Glatte Zelle | Alle vier Ecken haben dieselbe Klippenstufe. | Bilineare Fläche aus den vier Eckhöhen. |
| Rampenzelle | Alle vier Ecken tragen das Flag VertexFlags.Ramp (die Stufen dürfen sich unterscheiden). | Bilineare Fläche: Die Stufen gehen weich in einen Hang über. Siehe Rampen. |
| Klippenzelle | Die Ecken haben unterschiedliche Stufen, und die Zelle ist keine Rampenzelle. | Flache Terrassen, getrennt durch eine senkrechte Wand entlang einer abgerundeten Kontur. |
Abgerundete Konturen. Innerhalb einer Klippenzelle wird für jede Stufenschwelle t der Indikator „Eckstufe ≥ t“ (1 oder 0)
bilinear über die Zelle interpoliert. Ein Punkt gehört zur höchsten Stufe, deren interpolierter Indikator mindestens 0.5 beträgt
(MapGeometryView.LevelAt). Das ist eine Marching-Squares-Regel: Eine einzelne angehobene Ecke ergibt einen Viertelkreis statt einer
Diagonale, und lange Klippenlinien bleiben gerade. Die Wand selbst verläuft an der Kontur senkrecht. Die Feinhöhen werden immer bilinear interpoliert
und obendrauf addiert, auch innerhalb von Klippenzellen.
Das Aussehen der gerenderten Wände stellst du an der Komponente RTSTerrain ein (Settings):
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Surface Subdivisions | 2 | Quads pro Zellseite auf glattem Boden (1 = klassischer RTS-Look, 2–4 = weichere Hügel). |
| Cliff Subdivisions | 4 | Auflösung des Unterrasters, mit dem die abgerundete Kontur innerhalb einer Klippenzelle nachgezeichnet wird. |
| Cliff Noise | 0.18 | Zufällige horizontale Verschiebung der Wand-Vertices (Bruchteil einer Zelle) für natürlich wirkenden Fels. |
| Cliff Noise Scale | 1.6 | Skalierung des Klippenrauschens in Welteinheiten. |
| Cliff Slant | 0.12 | Wie weit die Oberkante einer Wand zurückgesetzt wird, sodass die Wände leicht geneigt sind (Bruchteil einer Zelle). |
| Cliff Wall Segments | 3 | Vertikale Segmente der Wandgeometrie pro Klippenstufe. |
Die Geometrieansicht erhältst du mit map.GetGeometryView(). Sie ist ein Struct aus nativen Arrays, daher kannst du sie auch an Burst-Jobs übergeben. Alle Member erwarten kartenlokale Positionen:
| Member | Beschreibung |
|---|---|
SampleHeight(localXZ) | Bodenhöhe (kartenlokales Y). |
SampleHeightInCell(cell, uv), SampleLevelHeightInCell(cell, uv) | Dasselbe innerhalb einer bekannten Zelle (uv 0–1), mit oder ohne Feinhöhe. |
SampleNormal(localXZ), SampleSlope(localXZ) | Oberflächennormale; Neigung in Grad. |
SampleCliffLevel(localXZ) | Klippenstufe an einer Position. |
SampleWater(localXZ, out waterHeight), SampleWaterDepth(localXZ) | Ob dort Wasser ist und wie hoch seine Oberfläche liegt; Wassertiefe oder 0 bei trockenem Boden. |
IsCliffCell(cell), IsRampCell(cell) | Ordnet eine Zelle ein (siehe Tabelle oben). |
LocateCell(localXZ, out cell, out uv) | Die Zelle einer Position und die Position innerhalb dieser Zelle (0–1). |
Raycast(origin, direction, maxDistance, out hitPoint, out hitDistance) | Analytischer Raycast gegen die Referenzoberfläche, ganz ohne Collider. |
static LevelAt(l00, l10, l01, l11, uv), static Bilerp(v00, v10, v01, v11, uv) | Die Stufenregel innerhalb einer Klippenzelle und bilineare Interpolation. |
width, height, cellSize, cliffLevelHeight, WorldSize | Kartengröße in Zellen, Zellgröße, Stufenhöhe und Weltgröße. |
Cliff Noise und Cliff Slant verschieben nur die gerenderten Wand-Vertices um einen Bruchteil einer Zelle. Abfragen verwenden dagegen die exakte Kontur. Direkt an einer Klippenkante können der sichtbare Fels und die Referenzoberfläche daher leicht voneinander abweichen. Die Pathing-Karte berücksichtigt das mit ihrer Einstellung Cliff Margin, die entlang jeder Wand einen schmalen, nicht begehbaren Streifen freihält.
Rampen
Über eine Rampe gelangen Einheiten von einer Klippenstufe zur nächsten. Die Regel ist einfach:
Eine Zelle ist eine Rampe, wenn alle vier Ecken als Rampe markiert sind. Fehlt das Flag auch nur an einer Ecke, bleibt die Zelle eine Klippenzelle mit senkrechter Wand.
- Das Flag heißt
VertexFlags.Rampund wird pro Vertex gesetzt. Das Werkzeug Ramp (IDcliff.ramp) malt es; halte Shift gedrückt, um es zu entfernen. - Eine Rampenzelle ist ein weicher, bilinearer Hang zwischen ihren Eckhöhen (Stufe plus Feinhöhe).
- Rampen funktionieren am besten über genau eine Stufe. Steilere Rampen sind erlaubt, aber die Statusleiste warnt vor Rampenzellen, die mehr als eine Stufe überspannen.
- Pathing: Bei einer gültigen Rampe über eine Stufe wird die Begehbarkeit nur anhand der Neigung ihrer Feinhöhen geprüft (der Stufenanstieg, den sie überbrückt, macht sie nicht „zu steil“). Rampen sind nie bebaubar.
GetCliffLevelliefert auf einer Rampe die interpolierte Stufe, gerundet: auf der unteren Hälfte die untere Stufe, auf der oberen Hälfte die obere.
- Hebe mit Raise / Lower Level ein Plateau um eine Stufe an.
- Wähle das Werkzeug Ramp und male dort über die Klippenkante, wo die Rampe entstehen soll. Achte darauf, dass der Pinsel mindestens die Vertices auf beiden Seiten der Wand abdeckt, damit jede Ecke der dazwischenliegenden Zellen markiert wird.
- Die Wand verschwindet, und ein Hang entsteht. Verbreitere die Rampe, indem du weitere Vertices entlang der Kante bemalst.
Bodenschichten und Splatmaps
Bodentexturen werden in Splatmaps gemalt: vier RGBA-Texturen (MapData.SplatMapCount = 4) mit
SplatWidth × SplatHeight Texeln. Jeder der 16 Kanäle enthält die Gewichtung einer Bodenschicht des Tilesets:
| Splatmap | R | G | B | A |
|---|---|---|---|---|
SplatMaps[0] | Schicht 0 | Schicht 1 | Schicht 2 | Schicht 3 |
SplatMaps[1] | Schicht 4 | Schicht 5 | Schicht 6 | Schicht 7 |
SplatMaps[2] | Schicht 8 | Schicht 9 | Schicht 10 | Schicht 11 |
SplatMaps[3] | Schicht 12 | Schicht 13 | Schicht 14 | Schicht 15 |
- Schicht i liegt in Splatmap
i / 4, Kanali % 4. Deshalb hat ein Tileset genau 16 Bodenschichten: 4 Splatmaps × 4 Kanäle. - Die Gewichtungen eines Texels ergeben über alle 16 Kanäle zusammen immer 255. Alle Malwerkzeuge halten diese Invariante ein; Code, der Splat-Daten schreibt, muss sie ebenfalls einhalten.
- Die Splat-Auflösung ist die Anzahl der Texel pro Zellseite (standardmäßig 4). Bei Zellen von 2 m ergibt das alle 0.5 m ein Texel.
- Die Mitte des Texels (x, z) liegt in kartenlokalen XZ-Koordinaten bei
((x + 0.5), (z + 0.5)) × CellSize / SplatResolution. Dieselbe Formel gilt auch für Detail- und Pathing-Texel, jeweils mit ihrer eigenen Auflösung. - Hilfsmethoden:
map.SplatIndex(x, z),map.GetSplatWeight(texelIndex, layer),map.GetDominantLayer(texelIndex),MapData.GetChannel/SetChannelundmap.FillSplat(layer).
Karten speichern Bodenschichten über ihren Index (0–15), nicht über ihren Namen. Wenn du Bodenschichten im Tileset umsortierst oder entfernst, ändert sich, was bereits bemalte Karten anzeigen. Füge neue Schichten am Ende hinzu, wenn bereits Karten das Tileset verwenden.
using RTSTerrainStudio;
using Unity.Mathematics;
using UnityEngine;
public static class SplatExample
{
/// <summary>Paints one map cell 100 % with a ground layer (like the Tile Paint tool, without undo).</summary>
public static void PaintCell(RTSTerrain terrain, int2 cell, int layer)
{
MapData map = terrain.Map;
if (map == null || !map.IsValidCell(cell.x, cell.y)) return;
layer = math.clamp(layer, 0, MapData.MaxGroundLayers - 1);
int res = map.SplatResolution;
for (int tz = cell.y * res; tz < (cell.y + 1) * res; tz++)
{
for (int tx = cell.x * res; tx < (cell.x + 1) * res; tx++)
{
int texel = map.SplatIndex(tx, tz);
for (int m = 0; m < MapData.SplatMapCount; m++)
{
// All channels 0, except the one of our layer: the sum stays exactly 255.
var c = new Color32(0, 0, 0, 0);
if (layer / 4 == m) MapData.SetChannel(ref c, layer % 4, 255);
map.SplatMaps[m][texel] = c;
}
}
}
// Tell the terrain which data changed and where (in cells). Textures and pathing update in LateUpdate.
terrain.NotifyChanged(MapLayers.Splat, new RectInt(cell.x, cell.y, 1, 1));
}
}
Wasser
- Ein Vertex liegt unter Wasser, wenn er das Flag
VertexFlags.Waterträgt. Die Höhe seiner Wasseroberfläche (kartenlokales Y, Meter) wird inWaterHeightsgespeichert; der Wert ist nur dort aussagekräftig, wo das Flag gesetzt ist. - Ein Punkt ist nass, wenn die bilinear interpolierte Abdeckung aus den Wasser-Flags der vier Ecken seiner Zelle mindestens 0.5 beträgt. Ecken ohne Wasser in einer nassen Zelle verwenden die höchste Wasserhöhe der Zelle, damit Uferlinien waagerecht bleiben.
- Wassertiefe = Wasseroberfläche − Bodenhöhe. Die Tiefe bestimmt das Pathing: Wasser, das tiefer ist als der Wert Shallow Depth Limit des Tilesets (standardmäßig 0.9 m), ist Tiefwasser (nicht begehbar); flacheres Wasser ist Flachwasser (begehbar, standardmäßig nicht bebaubar).
- Die Wasserwerkzeuge sind Paint Water, Erase Water und Flood Fill. Wasseränderungen melden nur
MapLayers.Water, deshalb werden beim Malen von Wasser keine Terrain-Chunks neu aufgebaut. - Abfragen:
terrain.IsWater(world, out depth)undterrain.TryGetWaterHeight(world, out waterWorldY).
Das Aussehen des Wassers (Farben, Wellen, Schaum) stammt aus dem Wasserprofil des Tilesets; siehe Wasserprofil.
Detaildichte (Gras und Kleinpflanzen)
- Details sind keine einzelnen Objekte. Jeder Detail-Prototyp, der auf der Karte verwendet wird, hat genau einen
DetailLayerinmap.DetailLayers: ein Byte pro Detail-Texel (DetailWidth×DetailHeight, standardmäßig 2 Texel pro Zellseite). - Eine Dichte von 0 bedeutet leer, 255 bedeutet Max Density Instanzen pro Texel (ein Feld des Detail-Prototyps, standardmäßig 6). Jedes
Texel erzeugt
density / 255 × maxDensityInstanzen. Der Renderer verteilt sie deterministisch, sodass dieselbe Karte immer gleich aussieht. - Schichten werden der Vegetationsbibliothek über die
prototypeIdzugeordnet.map.GetOrAddDetailLayer(id)legt bei Bedarf eine Schicht an;map.FindDetailLayer(id)gibt null zurück, wenn der Prototyp nicht verwendet wird. - Male Details mit Paint Details und entferne sie mit Erase Details.
Baum- und Dekorinstanzen
Bäume (TreeInstanceData) und Dekorobjekte (DecorInstanceData) werden als einfache Listen gespeichert. Statt eines Prototypnamens
speichert jede Instanz einen kleinen Palettenindex, der auf eine Liste von Bibliotheks-IDs in der Karte verweist:
map.TreePrototypeIds = [ "a3f0…", "77c1…" ] <- ids of TreePrototype entries in the vegetation library map.Trees[n].prototype = 1 <- this tree uses "77c1…"
- Mit
map.GetOrAddTreePrototype(id)/map.GetOrAddDecorPrototype(id)erhältst du den Palettenindex einer Bibliotheks-ID (fehlt sie, wird sie hinzugefügt). - Da Karten Prototypen über ihre ID referenzieren, kannst du eine Bibliothek gefahrlos umbenennen, umsortieren oder erweitern. Fehlt ein Prototyp
in der Bibliothek, überspringt ihn der Vegetation Renderer, und der Decor Manager zeigt ihn als magentafarbenen Platzhalterwürfel an; fügst du einen Prototyp mit derselben ID hinzu, ist er wieder da. Pathing: Bäume mit fehlendem Prototyp werden nicht gezeichnet, blockieren aber weiterhin einen Kreis mit dem Radius
PathingMapBuilder.DefaultTreeFootprintRadius(0.8 m) × Skalierung. Dekorplatzhalter tragen erst dann eine Grundfläche in die Pathing-Karte ein, wenn der Prototyp wieder vorhanden ist. - Jede Instanz hat innerhalb der Karte eine eindeutige, stabile
id. Neue IDs erhältst du mitmap.AllocateInstanceId(). - Positionen sind kartenlokal. Für Drehungen gilt überall dieselbe Konvention: Winkel in Grad, von oben gesehen im Uhrzeigersinn, 0 = +Z, 90 = +X (wie Unitys Y-Rotation).
Die Konvention für position.y
position.y ist ein zusätzlicher Versatz über dem Boden, normalerweise 0. Der Wert ist nicht die absolute Höhe.
Die Renderer tasten den Boden bei jeder Aktualisierung neu ab. So bleiben Bäume und Dekor auf dem Boden, wenn du das Terrain darunter bearbeitest.
| Objekt | Darstellungshöhe (kartenlokales Y) |
|---|---|
| Baum | Bodenhöhe + position.y |
| Dekor | Bodenhöhe + position.y + Y Offset des Prototyps |
Dekor mit DecorFlags.KeepHeight | position.y als absolute kartenlokale Höhe (folgt Änderungen am Terrain nicht) |
Kopiere den yOffset des Prototyps niemals in position.y; er wird beim Rendern addiert. DecorMath.ComputeLocalPosition
und DecorMath.ToStoredHeight rechnen zwischen gespeicherten und dargestellten Positionen um.
| Feld | TreeInstanceData | DecorInstanceData |
|---|---|---|
id | uint, eindeutig in der Karte | uint, eindeutig in der Karte |
prototype | ushort-Index in TreePrototypeIds | ushort-Index in DecorPrototypeIds |
variant | – | byte, 0 = Haupt-Prefab, 1… = Einträge aus variants |
flags | – | DecorFlags: IgnorePathing, Locked (Werkzeuge können das Objekt weder auswählen noch löschen), KeepHeight |
position | float3, kartenlokal, y = Versatz | float3, kartenlokal, y = Versatz (oder absolut bei KeepHeight) |
| Drehung | yaw in Grad | rotation, eine kartenlokale quaternion |
| Skalierung | scale, ein float (uniforme Skalierung) | scale, float3 |
tint | Color32, wird mit der Materialfarbe multipliziert | Color32, wird mit der Materialfarbe multipliziert |
using RTSTerrainStudio;
using Unity.Mathematics;
using UnityEngine;
public static class InstanceExample
{
/// <summary>Adds a tree of the first tree prototype of the terrain's vegetation library at a world position.</summary>
public static uint AddTree(RTSTerrain terrain, Vector3 worldPosition, float yawDegrees)
{
MapData map = terrain.Map;
RTSVegetationLibrary library = terrain.VegetationLibrary;
if (map == null || library == null || library.trees.Count == 0) return 0;
Vector3 local = terrain.WorldToLocal(worldPosition);
var tree = new TreeInstanceData
{
id = map.AllocateInstanceId(),
prototype = map.GetOrAddTreePrototype(library.trees[0].id), // palette index of the library id
position = new float3(local.x, 0f, local.z), // y = offset above the ground, normally 0
yaw = yawDegrees, // clockwise from above, 0 = +Z
scale = 1f,
tint = new Color32(255, 255, 255, 255)
};
map.Trees.Add(tree);
// Notify the cell that changed: vegetation, pathing (tree footprint) and fog blockers update.
int2 cell = map.LocalToCell(new float2(local.x, local.z));
terrain.NotifyChanged(MapLayers.Trees, new RectInt(cell.x, cell.y, 1, 1));
return tree.id;
}
/// <summary>Adds a decor object (by library id) standing upright on the ground.</summary>
public static uint AddDecor(RTSTerrain terrain, string decorPrototypeId, Vector3 worldPosition, float yawDegrees)
{
MapData map = terrain.Map;
if (map == null) return 0;
Vector3 local = terrain.WorldToLocal(worldPosition);
var decor = new DecorInstanceData
{
id = map.AllocateInstanceId(),
prototype = map.GetOrAddDecorPrototype(decorPrototypeId),
variant = 0, // main prefab
flags = DecorFlags.None,
position = new float3(local.x, 0f, local.z), // the prototype's yOffset is added at render time
rotation = DecorMath.Yaw(yawDegrees),
scale = new float3(1f, 1f, 1f),
tint = new Color32(255, 255, 255, 255)
};
map.Decor.Add(decor);
int2 cell = map.LocalToCell(new float2(local.x, local.z));
terrain.NotifyChanged(MapLayers.Decor, new RectInt(cell.x, cell.y, 1, 1));
return decor.id;
}
}
Wenn Code MapData direkt ändert, lassen sich diese Änderungen nicht rückgängig machen. Damit Änderungen aus Editor-Skripten rückgängig gemacht werden können,
führe sie über die Bearbeitungssitzung mit RTSEditSession.RunOperation(name, layers, operation) aus. Diese Methode zeichnet die angegebenen Schichten als einen einzigen Undo-Schritt auf.
Pathing-Karte
Die Pathing-Karte beantwortet die Frage: „Kann eine Einheit hier laufen, kann hier ein Gebäude stehen?“ Sie ist ein Raster aus Pathing-Zellen
(PathingResolution pro Seite einer Kartenzelle, standardmäßig 2, also 1 m große Pathing-Zellen bei einer Karte mit 2 m großen Zellen) mit je einem
PathingFlags-Byte. Sie wird automatisch abgeleitet und inkrementell neu aufgebaut, sobald sich eine Eingabe ändert
(Geometrie, Wasser, Bodenschichten, Bäume, Dekor, Overrides). Sie selbst wird nie gespeichert, sondern nur die manuellen Overrides.
PathingFlags | Bedeutung |
|---|---|
Walkable | Bodeneinheiten können hier laufen. |
Buildable | Hier können Gebäude platziert werden. |
Flyable | Lufteinheiten können hier fliegen. Ist überall gesetzt, es sei denn, ein Unflyable-Effekt entfernt es: ein gemalter Pathing-Override, die Bodenschichtregel des Tilesets oder der Footprint Effect eines Dekor-Prototyps. |
ShallowWater | Flachwasser (begehbar, standardmäßig nicht bebaubar). |
DeepWater | Tiefwasser (nur für See-Einheiten). |
Cliff | Die Zelle berührt eine Klippenwand. |
Blocked | Durch die Grundfläche eines Baums oder Dekorobjekts blockiert. |
Steep | Zu steil für Bodeneinheiten. |
Wie die Flags abgeleitet werden
- Terrain: Klippenwände (samt dem Klippenabstand Cliff Margin) setzen
Cliff; Neigungen über Max Walkable Slope (standardmäßig 42°) setzenSteep; Wasser setzt je nach TiefeShallowWateroderDeepWater;Flyablewird überall gesetzt;Buildable= begehbar, Neigung ≤ Max Buildable Slope (standardmäßig 18°) und trocken (oder Flachwasser, wenn Shallow Water Buildable aktiviert ist). Danach wird der Pathing Effect der dominanten Bodenschicht angewendet (zum Beispiel „Unbuildable“ auf rauem Fels). - Grundflächen: Bäume blockieren einen Kreis (Footprint Radius × Baumskalierung × Tree Footprint Scale); Dekor wendet seinen
Footprint Effect innerhalb eines Kreises oder eines gedrehten Rechtecks an, es sei denn, die Instanz hat
DecorFlags.IgnorePathing. - Manuelle Overrides, die mit Paint Pathing / Clear Pathing gemalt wurden, werden zuletzt angewendet.
Die Regeleinstellungen befinden sich in der Komponente RTSTerrain (RTSPathingSettings, Eigenschaft terrain.PathingSettings).
PathingOverride | Wirkung |
|---|---|
Unwalkable | Entfernt Walkable (und Buildable), setzt Blocked. |
Unbuildable | Entfernt Buildable. |
Unflyable | Entfernt Flyable. |
ForceWalkable | Setzt Walkable, auch wenn die abgeleiteten Regeln etwas anderes ergeben. |
ForceBuildable | Setzt Buildable, auch wenn die abgeleiteten Regeln etwas anderes ergeben. |
Dieselben PathingOverride-Flags werden für die Pathing-Effekte von Bodenschichten, die Grundflächeneffekte von Dekor und gemalte Overrides verwendet.
Frage das Ergebnis mit terrain.GetPathing(world), terrain.IsWalkable(world) und terrain.IsBuildable(world) ab, oder lies
terrain.Pathing.Flags direkt aus.
Regionen, Startpositionen und Marker
Es handelt sich um schlanke Objekte für deine Spiellogik. Im Spiel werden sie nicht gerendert; die Editoren zeichnen sie als Gizmos.
| Typ | Felder | Typische Verwendung |
|---|---|---|
MapRegion (Werkzeug Regions) | name, rect (kartenlokales XZ-Rechteck in Metern: rect.x = X, rect.y = Z), color, properties (Schlüssel-Wert-Liste) | Trigger, Spawn-Zonen, Creep-Camps. Contains(localXZ), GetProperty(key, fallback). |
StartLocation (Werkzeug Start Locations) | player (nullbasiert), position, yaw | Spawn-Punkte der Spieler. Der Einheiten-Spawner aus den Beispielen verwendet sie. |
MapMarker (Werkzeug Markers) | name, tag (Freitext wie „camera“ oder „goldmine“), position, rotation, properties | Kameras für Zwischensequenzen, Points of Interest, geskriptete Spawn-Punkte. |
Die Werkzeuge speichern für Startpositionen und Marker position.y = 0 und behandeln den Wert wie bei Bäumen als Versatz über dem Boden.
using RTSTerrainStudio;
using Unity.Mathematics;
using UnityEngine;
public static class RegionExample
{
/// <summary>Returns the "gold" property of the first region that contains the world position, or 0.</summary>
public static int GetGoldAt(RTSTerrain terrain, Vector3 worldPosition)
{
if (terrain.Map == null) return 0;
Vector3 local = terrain.WorldToLocal(worldPosition);
foreach (MapRegion region in terrain.Map.Regions)
{
// Regions are rectangles in map-local XZ.
if (region.Contains(new float2(local.x, local.z)) &&
int.TryParse(region.GetProperty("gold", "0"), out int gold))
return gold;
}
return 0;
}
}
Karteninfo und Umgebung
Feld in MapInfo | Standard | Beschreibung |
|---|---|---|
name | "New Map" | Anzeigename (wird auch im unkomprimierten Dateikopf gespeichert). |
author | "" | Name des Autors. |
description | "" | Freitext, der in Kartenbrowsern angezeigt wird. |
maxPlayers | 2 | Empfohlene Spielerzahl. |
tilesetId | "" | ID des Tilesets, für das die Karte erstellt wurde (RTSTileset.tilesetId). Wird automatisch ausgefüllt, wenn das Terrain ein Tileset hat. Der Inspector von RTSTerrain warnt, wenn das zugewiesene Tileset nicht passt. |
createdUtcTicks, modifiedUtcTicks | automatisch gesetzt | Erstellungszeit und Zeitpunkt der letzten Speicherung (UTC-Ticks). Die Änderungszeit setzt RTSMapSerializer.Write bei jeder Serialisierung der Karte: SaveToFile, Save im Editor, Serialize(map) und RTSMapAsset.CreateFromMap. |
EnvironmentSettings speichert die Beleuchtung einer Karte. Eine RTSEnvironment-Komponente
() wendet sie auf die Szene an; ohne diese Komponente werden die Werte zwar gespeichert, aber nicht verwendet.
Feld in EnvironmentSettings | Standard | Beschreibung |
|---|---|---|
sunColor | warmes Weiß (1, 0.956, 0.84) | Farbe des Sonnenlichts. |
sunIntensity | 1.2 | Intensität des Sonnenlichts. |
sunRotation | (50, −30) | Drehung der Sonne in Grad (x = Neigung, y = Drehwinkel). |
ambientColor | (0.45, 0.5, 0.58) | Farbe des Umgebungslichts. |
fog | aus | Aktiviert den Distanznebel. |
fogColor | (0.6, 0.68, 0.75) | Nebelfarbe. |
fogDensity | 0.004 | Nebeldichte. |
Änderungsbenachrichtigungen
Wenn du MapData per Code änderst, teile dem Terrain mit, was sich wo geändert hat – angegeben in Zellen:
terrain.NotifyChanged(MapLayers layers, RectInt cellRect)löstMapChangedsofort aus und stellt den Neuaufbau in die Warteschlange.terrain.NotifyChangedAll(layers)markiert die ganze Karte (nach Generatoren oder Importen).- Ausstehende Änderungen werden in
LateUpdateverarbeitet (außerhalb des Play Mode auch bei jedem Editor-Tick) oder sofort durch einen Aufruf vonterrain.ProcessPendingChanges(). Danach wirdChangesProcessedausgelöst: Meshes, Texturen und Pathing sind dann auf dem neuesten Stand. - Ein Vertex berührt bis zu vier Zellen. Ein Vertex-Rechteck rechnest du mit
BrushUtility.VertexRectToCellRect(map, vertexRect)in Zellen um, ein Texel-Rechteck mitBrushUtility.TexelRectToCellRect(map, texelRect, resolutionPerCell).
using RTSTerrainStudio;
using UnityEngine;
public static class CliffExample
{
/// <summary>Raises a square plateau by one cliff level around a world position.</summary>
public static void RaisePlateau(RTSTerrain terrain, Vector3 worldCenter, int radiusInVertices)
{
MapData map = terrain.Map;
if (map == null) return;
// Nearest vertex to the centre (vertices sit on multiples of the cell size).
Vector3 local = terrain.WorldToLocal(worldCenter);
int cx = Mathf.RoundToInt(local.x / map.CellSize);
int cz = Mathf.RoundToInt(local.z / map.CellSize);
int x0 = Mathf.Max(0, cx - radiusInVertices), x1 = Mathf.Min(map.Width, cx + radiusInVertices);
int z0 = Mathf.Max(0, cz - radiusInVertices), z1 = Mathf.Min(map.Height, cz + radiusInVertices);
for (int z = z0; z <= z1; z++)
{
for (int x = x0; x <= x1; x++)
{
int i = map.VertexIndex(x, z);
// Levels are bytes from 0 to 15: always clamp.
map.CliffLevels[i] = (byte)Mathf.Min(map.CliffLevels[i] + 1, MapData.MaxCliffLevel);
}
}
// Convert the changed vertices to the cells around them and notify the terrain.
var vertexRect = new RectInt(x0, z0, x1 - x0 + 1, z1 - z0 + 1);
RectInt cells = BrushUtility.VertexRectToCellRect(map, vertexRect);
terrain.NotifyChanged(MapLayers.CliffLevels, cells);
}
}
Koordinaten und mehrere Terrains
Alle Kartendaten werden in kartenlokalen Koordinaten gespeichert. Der Transform des RTSTerrain-GameObjects legt fest, wo dieses Koordinatensystem in der Welt liegt.
Kartenlokale Koordinaten
- Der Ursprung ist Vertex (0, 0), die Ecke von Zelle (0, 0). X und Z laufen von 0 bis
map.WorldSizein Metern, Y zeigt nach oben. - Vertex (x, z) liegt lokal bei
(x × CellSize, height, z × CellSize); der Mittelpunkt von Zelle (x, z) liegt bei((x + 0.5) × CellSize, …, (z + 0.5) × CellSize). - Lokales Y = 0 entspricht Stufe 0 mit einer Feinhöhe von 0. Bei einer neuen Karte mit Grundstufe 2 und 2 m hohen Stufen liegt der Boden bei lokalem Y = 4 m.
- Die Chunk-GameObjects liegen unter einem verborgenen Kindobjekt RTS Chunks des Terrains (
RTSTerrain.ChunkRoot). Dieses Wurzelobjekt hat keinen lokalen Versatz und keine lokale Drehung und gleicht die Skalierung des Terrains aus; jeder Chunk hat einen lokalen Identitäts-Transform. Deshalb entspricht der Objektraum der Chunk-Meshes den kartenlokalen Koordinaten. Das ist wichtig, wenn du Shader für das Terrain schreibst.
Umrechnung zwischen Welt- und Kartenkoordinaten
Methode (RTSTerrain) | Beschreibung |
|---|---|
WorldToLocal(Vector3) / LocalToWorld(Vector3) | Positionen zwischen Weltkoordinaten und kartenlokalen Koordinaten. |
WorldToLocalDirection / LocalToWorldDirection | Richtungen (Strahlen, Normalen). |
WorldToCell(Vector3) | Zelle, die eine Weltposition enthält (auf die Karte begrenzt). |
CellCenterToWorld(int2) | Weltposition einer Zellmitte auf dem Boden. |
VertexToWorld(int2) | Weltposition eines Vertex. |
Contains(Vector3) | True, wenn die Weltposition innerhalb der XZ-Fläche der Karte liegt. |
WorldBounds | Ungefähre Begrenzung der gesamten Karte in Weltkoordinaten, einschließlich der höchstmöglichen Klippenstufe. |
GetHeight, SnapToGround, GetNormal, GetCliffLevel, IsWater, TryGetWaterHeight, GetPathing, Raycast | Abfragen, die Werte in Weltkoordinaten entgegennehmen und zurückgeben; die Umrechnung erfolgt intern. |
GetGeometry() | Eine MapGeometryView für schnelle Abfragen in kartenlokalen Koordinaten (auch in Burst-Jobs). |
using RTSTerrainStudio;
using Unity.Mathematics;
using UnityEngine;
/// <summary>Add to any GameObject and use the context menu to see the map data under it.</summary>
public class TerrainProbe : MonoBehaviour
{
[ContextMenu("Probe Terrain")]
void Probe()
{
// Several terrains can exist: find the one below this object.
RTSTerrain terrain = RTSTerrain.FindAt(transform.position);
if (terrain == null || terrain.Map == null) { Debug.Log("Not above an RTS Terrain."); return; }
MapData map = terrain.Map;
// World -> map-local (origin at vertex 0,0; position and Y rotation only).
Vector3 local = terrain.WorldToLocal(transform.position);
int2 cell = terrain.WorldToCell(transform.position);
// Nearest vertex.
int vx = Mathf.Clamp(Mathf.RoundToInt(local.x / map.CellSize), 0, map.Width);
int vz = Mathf.Clamp(Mathf.RoundToInt(local.z / map.CellSize), 0, map.Height);
int v = map.VertexIndex(vx, vz);
// Splat texel below the object (SplatResolution texels per cell side).
int tx = Mathf.Clamp((int)(local.x / map.CellSize * map.SplatResolution), 0, map.SplatWidth - 1);
int tz = Mathf.Clamp((int)(local.z / map.CellSize * map.SplatResolution), 0, map.SplatHeight - 1);
int dominantLayer = map.GetDominantLayer(map.SplatIndex(tx, tz));
Debug.Log($"Cell {cell}, vertex ({vx},{vz}): level {map.CliffLevels[v]}, fine height {map.Heights[v]:0.00} m, " +
$"vertex height {map.GetVertexHeight(vx, vz):0.00} m. Ground at world Y {terrain.GetHeight(transform.position):0.00}, " +
$"cliff level {terrain.GetCliffLevel(transform.position)}, dominant ground layer {dominantLayer}, " +
$"pathing {terrain.GetPathing(transform.position)}.");
}
}
Regeln für Position, Drehung und Skalierung
| Teil des Transforms | Unterstützt | Hinweise |
|---|---|---|
| Position | ja | Verschiebt die gesamte Karte. Damit platzierst du mehrere Terrains nebeneinander oder auf unterschiedlichen Höhen. |
| Y-Rotation | ja | Dreht die Karte um ihren Ursprung (Vertex 0, 0). Abfragen, Werkzeuge, Pinselcursor und die Drehungen von Instanzen folgen ihr. |
| X-/Z-Rotation | nein | Vorgesehen ist nur die Y-Rotation. Der Inspector von RTSTerrain warnt und bietet Fix an: Y bleibt erhalten, X und Z werden zurückgesetzt. |
| Skalierung | ignoriert | Die Chunks gleichen jede Skalierung des Terrains oder seiner Elternobjekte aus. Der Inspector warnt und bietet Reset Scale an. Für eine größere Karte verwendest du mehr Zellen oder eine größere Zellgröße. |
Mehrere Terrains
Eine Szene kann beliebig viele RTSTerrain-Komponenten enthalten. Jede davon hat eine eigene Karte, ein eigenes Tileset, eigene Bibliotheken und Chunks sowie eine eigene Pathing-Karte.
RTSTerrain.Alllistet alle aktivierten Terrains auf;RTSTerrain.Maingibt das erste zurück (praktisch für Spiele mit nur einer Karte; temporäre Vorschau-Terrains werden übersprungen).RTSTerrain.FindAt(worldPosition)gibt das Terrain zurück, dessen XZ-Fläche eine Position enthält.RTSTerrain.RaycastAll(ray, out hit)führt einen Raycast gegen alle Terrains aus und gibt den nächstgelegenen Treffer zurück (hit.terrainverrät dir, welches Terrain getroffen wurde).RTSTerrain.TerrainEnabled/TerrainDisabledsind statische Events für Systeme, die Terrains verfolgen.- Terrains sind unabhängig voneinander: Karten werden an ihren Rändern nicht zusammengefügt, und die Wegfindung (
RTSPathfinder.FindPath(terrain, …)) arbeitet immer nur auf einem Terrain. Damit zwei Karten nahtlos aneinanderstoßen, gib ihren Randvertices dieselben Höhen.
using RTSTerrainStudio;
using UnityEngine;
public static class TerrainLayout
{
/// <summary>Places "east" directly next to "west" (along west's local +X), with the same rotation.</summary>
public static void PlaceNextTo(RTSTerrain west, RTSTerrain east)
{
if (west.Map == null) return;
float widthMetres = west.Map.WorldSize.x; // map-local X extent in metres
east.transform.SetPositionAndRotation(
west.transform.position + west.transform.rotation * new Vector3(widthMetres, 0f, 0f),
west.transform.rotation);
}
}
Karten-Assets und Kartendateien
Eine Karte wird als .rtsmap-Datei gespeichert. Der Unity Editor (als Asset in deinem Projekt) und der In-Game-Editor
(als Datei auf dem Datenträger) verwenden dasselbe Binärformat. Deshalb kannst du von Spielern erstellte Karten in Unity öffnen und umgekehrt.
.rtsmap-Dateien im Projekt
Findet Unity eine .rtsmap-Datei im Ordner Assets, wandelt der Importer RTSMapImporter sie in ein
RTSMapAsset um. Das Asset speichert die komprimierten Dateibytes, einen kleinen Header (Name, Autor, Größe, Spieler, Tileset-ID) und eine
Minikarten-Vorschau, die auch als Vorschaubild im Project-Fenster dient.
Ein Karten-Asset erstellen
- : geführte Einrichtung (Grundlagen, Größe, Aussehen, Terrain); schreibt die Datei und erstellt dafür ein Terrain oder weist sie dem ausgewählten Terrain zu.
- : erstellt im ausgewählten Ordner eine flache Karte der Größe 128 × 128 mit Standardeinstellungen.
- Save As… im Fenster von RTS Terrain Studio: schreibt die aktuelle Karte eines Terrains in eine neue Datei.
- Kopiere eine mit dem In-Game-Editor erstellte
.rtsmap-Datei in den OrdnerAssets; sie wird wie jede andere Datei importiert.
Der Inspector des Karten-Assets
Wenn du ein Karten-Asset auswählst, siehst du Vorschau, Name, Autor, Beschreibung, Größe, Spieler, Tileset und Formatversion sowie Statistiken (Zellen, Weltgröße, Zellgröße, Stufenhöhe, Startpositionen, Bäume, Dekor, Regionen, Marker, Detailschichten, Dateigröße, Speicher, letzte Änderung).
| Bedienelement | Beschreibung |
|---|---|
| Open In Scene | Weist die Karte dem ausgewählten Terrain zu (oder dem einzigen Terrain; bei mehreren wird nachgefragt). Erstellt ein Terrain, wenn die Szene keines hat. Im Play Mode deaktiviert. |
| Menü ▾ | Open In New Terrain oder Open On für ein bestimmtes Terrain der Szene. |
| Show In Explorer (Reveal In Finder unter macOS) | Zeigt die Datei im Dateibrowser des Betriebssystems an. |
| Tileset Id (Copy, Select) | Die in der Karte gespeicherte Tileset-ID. Select wählt das passende Tileset-Asset aus. |
| Preview Size | Längste Seite der gebackenen Vorschau: 128, 256 (Standard) oder 512 Pixel. |
| Preview Tileset | Tileset für die Farben der Vorschau. Leer = das Tileset, dessen ID in der Karte gespeichert ist (wird automatisch gefunden). |
| Re-bake Preview, Apply / Revert | Erstellt die Vorschau neu / übernimmt geänderte Importeinstellungen. |
Die Vorschau wird automatisch neu gebacken, wenn das Tileset mit der ID der Karte hinzugefügt oder entfernt wird oder seine Minikartenfarben ändert. Das Neuerstellen der Textur-Arrays importiert Karten nicht neu. Eine beschädigte Datei wird als leeres Asset mit Fehler-Icon importiert, und in der Console erscheint ein Importfehler.
Bearbeiten, Save und Save As
Weise ein Karten-Asset dem Feld Map Asset eines RTSTerrain zu. Sobald das Terrain aktiviert ist, lädt es die Karte in den
Speicher, und alle Änderungen erfolgen an dieser Kopie im Speicher. Die Szenendatei speichert nur den Verweis auf das Asset, nie die Kartendaten selbst.
- Save (in der Kopfzeile des Fensters von RTS Terrain Studio oder Save Map im Optionsmenü des Overlays RTS Terrain Studio in der Scene View)
schreibt die Karte zurück in ihre
.rtsmap-Datei und importiert sie neu. Save ist hervorgehoben, solange nicht gespeicherte Änderungen vorliegen. - Save As… schreibt eine neue Datei und weist sie dem Terrain zu, ohne neu zu laden. Dein Undo-Verlauf bleibt dabei erhalten.
- Open… lädt ein anderes Karten-Asset; Reload lädt die Karte neu aus ihrem Asset und verwirft nicht gespeicherte Änderungen.
- Ein Terrain ohne Karten-Asset erstellt eine Karte aus seinen Default Map Settings. Diese Karte existiert nur im Speicher, bis du Save As verwendest.
- Beim Speichern der Szene (Ctrl+S) werden auch die bearbeiteten Karten der Terrains dieser Szene gespeichert. Für neue Karten ohne Datei wird Save As angeboten. Beide Verhaltensweisen kannst du im Optionsmenü des Overlays in der Scene View ausschalten (Save Maps When Saving the Scene, Offer Save As for New Maps).
- Wenn du mit nicht gespeicherten Kartenänderungen den Play Mode startest, eine Szene schließt oder Unity beendest, wirst du gefragt, ob du sie speichern möchtest. Auch Änderungen, die du im Play Mode vornimmst (mit den Werkzeugen in der Scene View oder im In-Game-Editor), werden dir beim Verlassen des Play Mode zum Speichern angeboten.
Kartenänderungen sind nicht Teil der Szene. Unitys eigener Dialog „Save scene?“ erfasst sie nicht, die Abfragen von RTS Terrain Studio dagegen schon. Ist eine
.rtsmap-Datei schreibgeschützt oder durch die Versionsverwaltung gesperrt, zeigt Save einen Dialog an, statt zu schreiben.
Kartendateien zur Laufzeit
Im fertigen Spiel sind Karten gewöhnliche Dateien auf dem Datenträger:
RTSMapSerializer.DefaultMapsFolderist der OrdnerMapsinRTSMapSerializer.StorageRoot, also inApplication.persistentDataPath. Unter Windows ist der Ordner daher typischerweise%USERPROFILE%\AppData\LocalLow\<Company>\<Product>\Maps. In Web-Builds istStorageRootein Ordner im Browserspeicher (siehe Karten im Browser).- Der In-Game-Editor (
RTSRuntimeEditor) speichert und öffnet Karten in seinem Maps Folder (leer = der oben genannte Standardordner; relative Pfade beziehen sich aufRTSMapSerializer.StorageRoot). Ist Write Preview Images aktiviert, schreibt er neben jede gespeicherte Karte ein kleines MinikartenbildName.preview.pngfür seinen Kartenbrowser. - Um eine Karte weiterzugeben, kopiere die
.rtsmap-Datei in den Kartenordner eines anderen Spielers oder in ein Unity-Projekt. In einem Webbrowser verwende stattdessen die Befehle Download Map und Import Map des In-Game-Editors. - Karten, die zum Lieferumfang deines Spiels gehören, können ganz normale Karten-Assets sein, auf die ein Terrain oder ein Skript verweist. Sie werden wie jedes andere Asset in den Build aufgenommen.
| API | Beschreibung |
|---|---|
terrain.LoadFromFile(path) / terrain.SaveToFile(path) | Lädt eine Datei in ein Terrain / speichert die Karte des Terrains. Beide setzen terrain.CurrentFilePath. |
terrain.MapAsset = asset | Weist ein Karten-Asset zu und lädt es sofort, falls das Terrain aktiviert ist. |
terrain.SetMap(mapData) | Zeigt ein von dir erstelltes oder geladenes MapData-Objekt an; es geht in den Besitz des Terrains über. |
terrain.HasUnsavedChanges | True, wenn es seit dem letzten Laden oder Speichern Änderungen gab. |
RTSMapAsset.LoadMap() | Deserialisiert das Asset in ein neues MapData-Objekt (es geht in deinen Besitz über). |
RTSMapAsset.CreateFromMap(map, preview) | Erstellt ein Karten-Asset im Speicher, zum Beispiel um eine Karte zwischen Szenen weiterzugeben. |
RTSMapSerializer.Serialize(map) / Deserialize(bytes) | Bytes, zum Beispiel um eine Karte über das Netzwerk zu senden. |
RTSMapSerializer.TryReadHeaderFromFile(path, out header) | Liest nur den unkomprimierten Header (schnell, für Kartenlisten). |
RTSMapSerializer.TryReadHeader(bytes, out header) | Dasselbe für Kartenbytes im Speicher (zum Beispiel über das Netzwerk empfangene). |
terrain.CreateNewMap(MapCreationSettings) | Erstellt eine neue flache Karte und zeigt sie an – das Gegenstück im Code zum Dialog New Map. Übernimmt Info.tilesetId aus dem Tileset des Terrains, leert CurrentFilePath und markiert die Karte als nicht gespeichert. |
terrain.LoadFromAsset() | Lädt die Karte neu aus MapAsset und verwirft nicht gespeicherte Änderungen. Kann das Asset nicht gelesen werden, protokolliert die Methode einen Fehler und greift auf eine Karte zurück, die aus DefaultMapSettings erstellt wird. |
terrain.SaveToMapAsset() | Nur im Editor: schreibt die Karte zurück in ihr .rtsmap-Asset. Gibt false zurück, wenn das Terrain kein Karten-Asset hat. |
terrain.PendingLayers | Schichten mit gemeldeten, aber noch nicht verarbeiteten Änderungen. |
RTSMapAsset.Header / Preview / Data | Der Karten-Header (gelesen, ohne die Karte zu dekomprimieren), die Minikarten-Vorschau (kann null sein) und die rohen Dateibytes. |
RTSMapAsset.SetData(bytes, preview) | Ersetzt den Inhalt eines Karten-Assets (der Header wird aus den Bytes gelesen). |
using System.IO;
using RTSTerrainStudio;
using UnityEngine;
public class SimpleMapList : MonoBehaviour
{
[SerializeField] RTSTerrain terrain;
/// <summary>Logs all maps in the default folder without loading them, then opens the first one.</summary>
[ContextMenu("List And Open Maps")]
void ListAndOpen()
{
string folder = RTSMapSerializer.DefaultMapsFolder;
if (!Directory.Exists(folder)) { Debug.Log("No maps folder yet: " + folder); return; }
string[] files = Directory.GetFiles(folder, "*" + RTSMapSerializer.FileExtension);
foreach (string path in files)
{
// The header is stored uncompressed, so this is fast even for big maps.
if (RTSMapSerializer.TryReadHeaderFromFile(path, out RTSMapHeader h))
Debug.Log($"{h.name} by {h.author}: {h.width} x {h.height} cells, {h.maxPlayers} players, saved {h.Modified}");
}
if (files.Length > 0 && terrain != null)
{
try
{
terrain.LoadFromFile(files[0]); // the terrain owns and disposes the loaded map
}
catch (InvalidDataException e)
{
Debug.LogError("The map file is damaged: " + e.Message);
}
}
}
}
Tilesets
Ein Tileset (RTSTileset) legt das Aussehen einer Karte fest: bis zu 16 Bodenschichten, bis zu 4 Klippenschichten und
den Stil des Wassers – vergleichbar mit den Sommer-, Winter- oder Wüsten-Tilesets klassischer RTS-Karteneditoren. Du kannst beliebig viele Tilesets anlegen und das Tileset
eines Terrains jederzeit wechseln: Texturierte Karten speichern nur Schichtindizes, deshalb kann dieselbe Karte als Sommer- und als Winterkarte erscheinen.
- Erstelle ein Tileset über .
- Ziehe deine Bodentexturen auf den Abschnitt Ground Layers im Inspector. Pro Albedo-Textur wird eine Schicht angelegt. Passende Normal-Maps und
Höhentexturen werden am Suffix im Dateinamen erkannt (zum Beispiel
_n,_normal,_nrm,_normalgl,_h,_height,_disp). Roughness-, AO- und Metallic-Maps werden ignoriert, sodass du einen ganzen heruntergeladenen Texturordner ablegen kannst. - Füge auf dieselbe Weise Klippenschichten unter Cliff Layers hinzu.
- Weise das Tileset dem Feld Tileset deines
RTSTerrainzu. - Klicke im Abschnitt Texture Arrays einmal auf Build Texture Arrays (siehe Textur-Arrays).
Tileset-Felder
| Einstellung | Standard | Beschreibung |
|---|---|---|
Tileset Id (tilesetId) | generiert | Stabile Kennung, die in Kartendateien gespeichert wird. So wissen Karten, für welches Tileset sie erstellt wurden. Wird schreibgeschützt mit der Schaltfläche Copy angezeigt. |
Display Name (displayName) | "New Tileset" | Name, der in den Editoren angezeigt wird. |
Ground Layers (groundLayers) | leer | Bis zu 16 GroundLayer-Einträge. Überzählige Einträge werden entfernt. |
Cliff Layers (cliffLayers) | leer | Bis zu 4 CliffLayer-Einträge. |
Water (water) | blaugrün | Das WaterProfile, siehe unten. |
Resolution (arrayResolution) | 1024 | Größe der Slices in den Textur-Arrays: 256, 512, 1024 oder 2048. Quelltexturen werden automatisch skaliert. |
Compress (compressArrays) | an | Komprimiert die erstellten Arrays (BC7 / ASTC). Schalte die Option für möglichst schnelles Iterieren aus. |
Material Override (materialOverride) | keine | Optionales Material anstelle des Standard-Terrain-Materials. Es muss den Shader RTS Terrain Studio/Terrain oder einen kompatiblen Shader verwenden. |
Macro Variation (macroVariation) | 0.25 | Variation im großen Maßstab, die Texturwiederholungen auf großen Flächen aufbricht. |
Triplanar Sharpness (triplanarSharpness) | 6 | Schärfe der Triplanar-Überblendung an Klippenwänden (1–16). |
Felder der Bodenschichten
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Name | "Layer" | Wird in den Paletten angezeigt. |
| Albedo | keine | Grundfarbe (RGB). Beliebige Größe und beliebiges Format. |
| Normal | keine | Normal-Map im Tangentenraum (importiere sie als Normal Map). |
| Height Map | keine | Optionale Höhe (R-Kanal) für die höhenbasierte Überblendung. Hat Vorrang vor dem Albedo-Alpha. |
| Height From Albedo Alpha | aus | Verwendet das Albedo-Alpha als Höhe, wenn keine Höhentextur zugewiesen ist. |
| Tint | weiß | Farbe, die mit dem Albedo multipliziert wird. |
| Tile Size | 6 | Weltgröße einer Texturwiederholung in Metern (≥ 0.1). |
| Normal Strength | 1 | 0–2. |
| Smoothness | 0.1 | 0–1. |
| Metallic | 0 | 0–1. |
| Height Blend | 0.5 | Stärke der höhenbasierten Überblendung mit benachbarten Schichten (0 = lineare Überblendung). Benötigt eine Höhenquelle. |
| Anti Tiling | 0.3 | Verhindert sichtbare Wiederholungen gekachelter Texturen durch ein zweites, variiertes Sample (0–1). |
| Pathing Effect | None | Pathing-Regeln, die dort gelten, wo diese Schicht dominiert, zum Beispiel Unbuildable für zerklüfteten Fels oder Unwalkable für Lava. |
| Minimap Color | schwarz | Farbe auf der Minikarte und in Kartenvorschauen. Schwarz = aus dem Albedo berechnet. |
Felder der Klippenschichten
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Name | "Cliff" | Wird in der Palette Cliff Type angezeigt. |
| Albedo | keine | Wandfarbe. Ohne sie zeigen Klippen dieses Typs einen dunkelgrauen Platzhalter. |
| Normal | keine | Normal-Map im Tangentenraum. |
| Tint | weiß | Farbe, die mit dem Albedo multipliziert wird. |
| Tile Size | 4 | Weltgröße einer Texturwiederholung in Metern. |
| Normal Strength | 1 | 0–2. |
| Smoothness | 0.15 | 0–1. |
| Top Ground Layer | −1 (keine) | Bodenschicht, mit der die Oberseite von Klippen dieses Typs automatisch texturiert wird. Wird von Auto Paint verwendet. |
| Edge Ground Layer | −1 (keine) | Bodenschicht, mit der die Klippenwerkzeuge die Kanten neuer Klippen dieses Typs texturieren, zum Beispiel für einen steinigen Rand (siehe Automatische Klippenkanten). Erscheint im Klippen-Panel als Edge Layer. −1 = keine automatische Kante. |
| Minimap Color | schwarz | Minikartenfarbe. Schwarz = aus dem Albedo berechnet. |
Klippenwände werden per Triplanar-Mapping in kartenlokalen Koordinaten texturiert. Klippentexturen brauchen daher kein spezielles UV-Layout.
Wasserprofil
Das Wasserprofil ist Teil des Tilesets. Es wird vom Wasser-Renderer, von der Pathing-Karte und von der Minikarte verwendet.
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Shallow Color | (0.22, 0.55, 0.6, 0.55) | Farbe von Flachwasser; Alpha = Deckkraft. |
| Deep Color | (0.03, 0.16, 0.27, 0.95) | Farbe von Tiefwasser; Alpha = Deckkraft. |
| Color Depth | 3 | Tiefe in Metern, ab der die Farbe für Tiefwasser vollständig erreicht ist. |
| Shallow Depth Limit | 0.9 | Wasser, das tiefer als dieser Wert ist, gilt als Tiefwasser (nicht begehbar). Flacheres Wasser ist begehbar, wie in klassischen RTS-Spielen. |
| Normal Map | keine | Normal-Map für die Wellen. |
| Normal Strength | 0.6 | 0–2. |
| Normal Tile Size | 8 | Weltgröße einer Wiederholung der Normal-Map in Metern. |
| Flow Speed | (0.03, 0.02) | Scrollgeschwindigkeit der Normal-Map. |
| Wave Height | 0.05 | 0–1. |
| Smoothness | 0.92 | 0–1. |
| Refraction | 0.03 | 0–0.2. |
| Foam Color | weiß, Alpha 0.8 | Farbe des Uferschaums. |
| Foam Distance | 0.6 | Reichweite des Uferschaums in Metern. |
| Foam Texture | keine | Optionales Schaummuster. |
| Minimap Color | (0.1, 0.3, 0.55) | Wasserfarbe auf der Minikarte. |
Tilesets per Code bearbeiten
Tilesets sind ScriptableObjects. Rufe nach dem Ändern von Feldern per Code tileset.NotifyChanged() auf, damit Terrains ihre Materialien aktualisieren.
tileset.GetGroundLayer(index) und GetCliffLayer(index) geben für unbelegte Slots null zurück.
Textur-Arrays
Der Terrain-Shader zeichnet alle 20 Schichten in einem einzigen Durchgang. Deshalb werden alle Schichttexturen in zwei Textur-Arrays auf der GPU gepackt. Du erstellst diese Arrays nie selbst: Hinein kommen normale Unity-Texturen beliebiger Größe und beliebigen Formats, heraus kommen die Arrays.
Aufbau
| Array | Slices | Kanäle | Farbraum |
|---|---|---|---|
_RTS_AlbedoArray | 0–15 Bodenschichten, 16–19 Klippenschichten | RGB = Albedo, A = Höhe für die höhenbasierte Überblendung | sRGB |
_RTS_NormalArray | 0–15 Bodenschichten, 16–19 Klippenschichten | RGB = Normale im Tangentenraum, auf 0–1 umgerechnet (n × 0.5 + 0.5) | linear |
- Das erste Klippen-Slice ist
RTSTileset.CliffSliceOffset(16), die Gesamtzahl istRTSTileset.TotalSlices(20). - Die Höhe (Alpha) stammt aus der Height Map der Schicht (R-Kanal), sonst aus dem Albedo-Alpha, wenn Height From Albedo Alpha aktiviert ist, andernfalls ist sie konstant 0.5.
- Normalen werden beim Erstellen der Arrays entpackt. Deine Normal-Maps können daher wie gewohnt Unitys Normal-Map-Import (DXT5nm / BC5) verwenden. Schichten ohne Normal-Map erhalten eine flache Normale.
- Schichten ohne Albedo erhalten ein neutralgraues Slice mit dezentem Muster; der Shader wendet die Tönung trotzdem an.
- Jedes Slice hat eine vollständige Mip-Kette und nutzt trilineare Filterung. Quellen beliebiger Größe werden auf die gewählte Resolution skaliert.
Erstellte Arrays und der Laufzeit-Fallback
Die Arrays können auf zwei Wegen entstehen:
| Erstellte Arrays (empfohlen) | Laufzeit-Fallback | |
|---|---|---|
| Erstellt durch | Schaltfläche Build Texture Arrays im Inspector des Tilesets | Der Renderer, automatisch, wenn keine aktuellen erstellten Arrays vorhanden sind |
| Gespeichert | Als Sub-Assets im Tileset-Asset (in Player-Builds enthalten) | Nur im GPU-Speicher, wird beim Laden neu erstellt |
| Format | BC7 (Desktop, Konsolen), ASTC 6×6 (Android, iOS, tvOS, visionOS), für Web die Texturkomprimierung des Builds (DXT5, ASTC 6×6 oder ETC2 RGBA8), DXT5 (andere Plattformen); RGBA32, wenn Compress ausgeschaltet ist | Unkomprimiert (4 Byte pro Pixel) |
| Speicher bei 1024 px | etwa 56 MB mit BC7 für beide Arrays | etwa 224 MB für beide Arrays |
Der Abschnitt Texture Arrays zeigt eine Statusanzeige:
- Not built: Das Terrain verwendet Laufzeit-Arrays. Klicke einmal auf Build Texture Arrays.
- Up to date: Format, Größe, Anzahl der Mips und Speicherbedarf werden angezeigt.
- Outdated: Texturen, Auflösung oder Komprimierung haben sich geändert, oder die Arrays wurden für eine andere Plattform oder Texturkomprimierung erstellt. Erstelle sie neu, oder überlasse das dem nächsten Player-Build.
- Building: Eine Erstellung läuft oder ist geplant.
Der Abschnitt zeigt außerdem das Zielformat für die aktive Build-Plattform sowie die geschätzte Größe und Erstellungsdauer. Vor Erstellungen über 256 MB fragt er nach einer Bestätigung. Clear entfernt die erstellten Arrays.
Player-Builds
Player-Builds verwenden die erstellten Arrays unverändert, weil nur der Editor sie mit den Quelltexturen vergleichen kann. Veraltete Arrays werden deshalb
direkt vor jedem Player-Build automatisch neu erstellt. Tilesets, für die nie Arrays erstellt wurden, verwenden weiterhin Laufzeit-Arrays. Die Details, auch zum Batch-Modus und zu
-rtsAllowOutdatedTextureArrays, findest du unter Textur-Arrays in Player-Builds. Player-Builds geben außerdem die CPU-Kopie der
erstellten Arrays bei der ersten Verwendung frei. Das spart etwa so viel Speicher, wie die Arrays auf der GPU belegen.
Per Code: tileset.HasValidBuiltArrays gibt an, ob die komprimierten Arrays verwendet werden. Im Editor müssen sie existieren und zu
ComputeContentHash() passen. Ein Player-Build vertraut den erstellten Arrays, bis dein Code zur Laufzeit die Texturen der Schichten, die Auflösung oder die Komprimierung ändert.
Dann greift er auf Laufzeit-Arrays zurück. Werte von ComputeContentHash() sind nur innerhalb des Editors oder innerhalb einer einzelnen Sitzung eines Player-Builds vergleichbar, weil
der Editor den Importzustand der Texturen in den Hash einbezieht. Ist HasValidBuiltArrays false,
erstellt RTSTerrain unkomprimierte Laufzeit-Arrays (RTSTextureArrayBuilder.Acquire). tileset.BuiltAlbedoArray / BuiltNormalArray stellen die
erstellten Arrays bereit. Verwende in deinem eigenen Laufzeitcode RTSShaderReferences.FindShader(name) (und FindMaterial(name)) statt Shader.Find, damit die Suche auch in Player-Builds funktioniert.
Auto Build
Ist Auto Build aktiviert (Standard; eine Editor-Einstellung für alle Tilesets), werden bereits erstellte Arrays eine Sekunde nach einer Änderung von Texturen, Auflösung oder Komprimierung neu erstellt, und zwar mit schneller Komprimierung. Die erste Erstellung startest du immer von Hand. Eine abgebrochene oder fehlgeschlagene automatische Erstellung wird erst wiederholt, wenn sich der Inhalt oder das Zielformat ändert. Die Schaltfläche Build Texture Arrays komprimiert dagegen mit normaler Qualität, was etwas besser aussieht. Klicke deshalb vor der Veröffentlichung darauf.
Die Größenschätzung lässt sich leicht nachvollziehen: Auflösung² × 20 Slices × 2 Arrays × 4/3 für Mips × Byte pro Pixel (1 bei BC7, etwa 0.44 bei ASTC 6×6, 4 unkomprimiert). Senke die Auflösung für Mobilgeräte auf 512: Das braucht ein Viertel des Speichers.
Für Player-Builds müssen die Shader RTS Terrain Studio/Terrain und Hidden/RTS Terrain Studio/TextureArrayBlit (vom Laufzeit-Fallback verwendet)
enthalten sein. erstellt das Asset RTSShaderReferences in
Resources/RTSTerrainStudio, das dafür sorgt, dass alle Shader des Pakets in Builds enthalten bleiben.
Vegetationsbibliothek
Eine Vegetationsbibliothek (RTSVegetationLibrary) listet das Gras, die Pflanzen und die Bäume auf, die du malen kannst. Erstelle eine über
und weise sie dem Feld Vegetation Library des
RTSTerrain zu. Damit Vegetation angezeigt wird, braucht das Terrain außerdem eine Komponente RTSVegetationRenderer (der Inspector von RTSTerrain bietet an, sie hinzuzufügen).
Der Inspector hat zwei Tabs, Details und Trees, mit einem Raster aus Vorschaubildern. Lege Prefabs (Bäume), Texturen (Gras-Quads) oder Meshes
(Detail-Meshes) darauf ab, um Einträge hinzuzufügen. Jeder Eintrag erhält eine stabile id, die von Karten verwendet wird; duplizierte Einträge erhalten automatisch neue IDs.
Detail-Prototypen
Details sind kleine Pflanzen in großer Zahl, die du über ihre Dichte malst (siehe Detaildichte). Sie werden in Blöcken von 16 × 16 Zellen erzeugt und
mit dem internen prozeduralen Shader Hidden/RTS Terrain Studio/Detail gezeichnet, der Wind, Nebel des Krieges und Farbvariation übernimmt.
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Id | generiert | Stabile Kennung, die in Kartendateien gespeichert wird. |
| Name | "Grass" | Wird in der Palette angezeigt. |
| Render Mode | Crossed Quads | CrossedQuads: gekreuzte Quads, texturiert mit Texture (klassisches Gras). Mesh: verwendet Mesh, texturiert mit der Haupttextur und der Farbe von Material. |
| Mesh | keine | Mesh für den Modus Mesh. |
| Material | keine | Material für den Modus Mesh. Seine Haupttextur, seine Farbe und sein Alpha-Cutoff werden vom Detail-Shader verwendet. |
| Texture | keine | Textur mit Alpha für den Modus Crossed Quads. |
| Healthy Color / Dry Color | hellgrün / strohfarben | Instanzen werden per Rauschen zwischen beiden Farben überblendet. |
| Min / Max Width | 0.6 / 1.0 | Zufälliger Breitenbereich in Metern. |
| Min / Max Height | 0.4 / 0.8 | Zufälliger Höhenbereich in Metern. |
| Max Density | 6 | Instanzen pro Detail-Texel bei voller Dichte (255). Bereich 1–32. |
| Color Noise Scale | 12 | Skalierung des Rauschens, das zwischen gesunder und trockener Farbe überblendet. |
| Wind Strength | 0.5 | 0–1. Windrichtung und Windgeschwindigkeit stellst du am RTSVegetationRenderer ein. |
| Render Distance | 70 | Instanzen, die weiter als diese Distanz von der Kamera entfernt sind, werden nicht mehr gezeichnet (vorher schrumpfen sie sanft weg). |
| Cast Shadows | aus | Schatten von Details sind teuer; lasse die Option bei Gras ausgeschaltet. |
| Align To Ground | an | Neigt Instanzen entsprechend der Bodennormale. |
| Max Slope | 40 | Auf Hängen, die steiler als dieser Wert (in Grad) sind, wird nichts gespawnt. |
| Avoid Water | an | Unter Wasser wird nichts gespawnt. |
| Icon | keine | Paletten-Icon; wird im Editor erzeugt, wenn das Feld leer ist. |
Baum-Prototypen
Bäume sind einzelne Instanzen (siehe Bäume und Dekorinstanzen), die du mit dem Werkzeug Place Trees platzierst.
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Id | generiert | Stabile Kennung, die in Kartendateien gespeichert wird. |
| Name | "Tree" | Wird in der Palette angezeigt. |
| Prefab | keine | Prefab mit MeshRenderers und einer optionalen LODGroup (LOD0 und LOD1 werden verwendet). |
| Spawn Mode | Instanced | Instanced: per GPU-Instancing gezeichnet, keine GameObjects, am schnellsten. GameObject: eine Prefab-Instanz pro Baum, für Gameplay wie das Abbauen von Bäumen. |
| Min / Max Scale | 0.85 / 1.2 | Bereich für die zufällige gleichmäßige Skalierung. |
| Random Rotation | an | Zufälliger Drehwinkel beim Platzieren. |
| Tint A / Tint B | weiß / blassgrün | Zufälliger Tönungsbereich pro Instanz. |
| Footprint Radius | 0.8 | Radius in Metern, der in der Pathing-Karte blockiert wird (skaliert mit dem Baum). |
| Blocks Vision | an | Bäume blockieren die Sichtlinie für den Nebel des Krieges, wie in klassischen RTS-Spielen. |
| Min Spacing | 1.6 | Mindestabstand zu anderen Bäumen beim Verteilen. |
| Colliders | an | Modus Instanced: Es werden gepoolte Kapsel-Collider als Stellvertreter erzeugt. Modus GameObject: Die eigenen Collider des Prefabs werden verwendet. |
| Collider Radius / Height | 0.4 / 4 | Größe der Kapsel-Stellvertreter (Modus Instanced). |
| Render Distance | 250 | Bäume, die weiter als diese Distanz von der Kamera entfernt sind, werden nicht mehr gezeichnet. |
| Cast Shadows | an | Schattenwurf. |
| Icon | keine | Paletten-Icon; wird im Editor erzeugt, wenn das Feld leer ist. |
GPU-Instancing
Instanzierte Bäume werden per GPU-Instancing gezeichnet. Deshalb sollte bei ihren Materialien Enable GPU Instancing eingeschaltet sein. Ist die Option bei einem Material aus, verwendet der Renderer automatisch eine versteckte Instancing-Kopie davon (die beim Bearbeiten mit dem Original synchron bleibt). Der Inspector der Bibliothek zeigt dann eine Infomeldung mit der Schaltfläche Enable, die das Material selbst korrigiert. Bei Details muss Instancing in den Materialien nicht aktiviert sein, weil sie immer mit dem prozeduralen Detail-Shader gezeichnet werden.
Verwende für die Materialien von Baum- und Pflanzen-Prefabs den Shader RTS Terrain Studio/Vegetation, um Wind, Transluzenz, Nebel des Krieges und die Tönung pro Instanz zu erhalten.
Andere URP-Materialien mit Instancing-Unterstützung funktionieren ebenfalls, aber ohne die Tönung.
Per Code entfernst du Bauminstanzen mit RTSVegetationRenderer.RemoveTree(id) (zum Beispiel, wenn ein Arbeiter einen Baum abbaut) und findest sie mit
TryFindNearestTree. Ihr GameObject oder ihren Collider-Stellvertreter liefert GetTreeObject(id).
Bibliotheken per Code bearbeiten
Karten speichern eine Palette von Prototyp-IDs. Löse einen Palettenindex daher über die Bibliothek auf:
- Bäume:
vegetationLibrary.FindTree(map.TreePrototypeIds[tree.prototype]). - Detailschichten:
vegetationLibrary.FindDetail(layer.prototypeId). - Dekor:
decorLibrary.Find(map.DecorPrototypeIds[d.prototype]). Das Prefab einer Variante erhältst du mitprototype.GetPrefab(d.variant): 0 ist das Haupt-Prefab, und ungültige oder leere Varianten fallen auf das Haupt-Prefab zurück.VariantCountzählt das Haupt-Prefab mit. decorLibrary.GetCategories()listet die Kategorien in Anzeigereihenfolge auf (Prototypen ohne Kategorie erscheinen unter Misc).vegetationLibrary.EnsureUniqueIds()vergibt neue IDs an duplizierte Einträge.
Rufe nach dem Ändern einer Bibliothek per Code library.NotifyChanged() auf (die Methode löst das Event Changed der Bibliothek aus). Dann löst RTSTerrain das Event LibrariesChanged aus, und die Renderer aktualisieren sich. Es gilt dieselbe Regel wie für Tilesets.
Dekorbibliothek
Eine Dekorbibliothek (RTSDecorLibrary) listet die Props (Doodads) auf, die du platzieren kannst: Felsen, Ruinen, Zäune, Kisten, Gebäude oder jedes andere Prefab.
Erstelle eine über und weise sie dem Feld Decor Library von RTSTerrain zu.
Eine Komponente RTSDecorManager auf dem Terrain hält die Szene mit den platzierten Instanzen synchron.
Der Inspector zeigt die Prototypen als Raster mit automatisch erzeugten Vorschaubildern und einem Tab pro Kategorie (plus All). Ziehe Prefabs darauf, um sie der aktuellen Kategorie hinzuzufügen; mit einem Rechtsklick auf einen Kategorie-Tab benennst du ihn um. Prototypen ohne Kategorie erscheinen unter Misc.
Felder der Dekor-Prototypen
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Id | generiert | Stabile Kennung, die in Kartendateien gespeichert wird. |
| Name | "Decor" | Wird in der Palette angezeigt. |
| Category | "Props" | Paletten-Tab, in dem der Prototyp erscheint. |
| Prefab | keine | Das zu platzierende Objekt. |
| Variants | leer | Optionale alternative Prefabs; beim Platzieren wird eine zufällige Variante gewählt. Die Instanz speichert den Variantenindex (0 = Haupt-Prefab). |
| Spawn Mode | GameObject | GameObject: ein GameObject pro Instanz (Skripte, Collider, Animationen). Instanced: GPU-Instancing, keine GameObjects, nur statische Props, am schnellsten. |
| Min / Max Scale | 0.9 / 1.1 | Bereich für eine zufällige, einheitliche Skalierung. |
| Random Yaw | an | Zufällige Drehung um die Hochachse. |
| Yaw Step | 0 | Rastet den Drehwinkel auf Vielfache dieses Winkels ein (0 = frei), zum Beispiel 90 für Mauern und Zäune. |
| Max Random Tilt | 0 | Zufällige Neigung in Grad (0–45). |
| Align To Normal | 0 | Richtet die Hochachse an der Bodennormalen aus (0 = aufrecht, 1 = vollständig ausgerichtet). |
| Snap To Grid | aus | Rastet die Position auf Zellmitten ein. |
| Y Offset | 0 | Vertikaler Versatz in Metern, der beim Rendern addiert wird (negative Werte versenken das Objekt im Boden). |
| Tint A / Tint B | weiß / weiß | Bereich für eine zufällige Einfärbung pro Instanz. |
| Footprint Shape | Circle | None, Circle oder Box. |
| Footprint Size | (0.75, 0.75) | Circle: X ist der Radius. Box: halbe Ausdehnung entlang der X- und Z-Achse des Objekts. In Metern, skaliert mit der Instanz. |
| Footprint Effect | Unwalkable, Unbuildable | PathingOverride-Flags, die innerhalb der Grundfläche geschrieben werden. |
| Blocks Vision | aus | Blockiert die Sichtlinie für den Nebel des Krieges. |
| Min Spacing | 1 | Mindestabstand zu anderen Dekorobjekten beim Verstreuen. |
| Cast Shadows | an | Schattenwurf. |
| Icon | keine | Paletten-Icon; wird im Editor erzeugt, wenn das Feld leer ist. |
Grundflächen
- Bei einer kreisförmigen Grundfläche dient der X-Wert von Footprint Size als Radius, multipliziert mit der größten horizontalen Skalierung der Instanz.
- Eine rechteckige Grundfläche folgt der Drehung der Instanz (ihren auf den Boden projizierten X- und Z-Achsen) und ihrer X/Z-Skalierung. Eine gedrehte Mauer blockiert also genau ihre eigene Fläche.
- Es wird keine Grundfläche geschrieben, wenn die Form
Noneist, der EffektNoneist oder die Instanz das FlagDecorFlags.IgnorePathinghat. - Verwende für flache Dekorationen, über die Einheiten hinweglaufen dürfen (Blumen, Schutt), nur
Unbuildableund für massive ObjekteUnwalkable.
Spawn-Modi
| GameObject | Instanced | |
|---|---|---|
| Szenenobjekte | Eines pro Instanz, unter einem Kindobjekt namens „RTS Decor“ (wird nicht mit der Szene gespeichert) | Keine |
| Skripte, Collider, Animation | Ja (jedes Wurzelobjekt erhält eine Komponente RTSDecorInstance) | Nein |
| Rendering | Die normalen Renderer des Prefabs | Graphics.RenderMeshInstanced mit LOD0, Culling pro Patch |
| Ideal für | Interaktive Props, Goldminen, zerstörbare Objekte | Tausende statische Felsen und Pflanzen |
Instanziertes Dekor verhält sich beim GPU-Instancing genauso wie instanzierte Bäume: Materialien ohne Enable GPU Instancing werden über eine verborgene Kopie mit aktiviertem Instancing gezeichnet.
Gespawnte Objekte werden in einem Pool wiederverwendet; RTSDecorManager.GetInstanceObject(id) liefert das gespawnte Objekt einer Instanz, und
RTSDecorInstance.FromCollider(collider) ordnet einen Physik-Treffer wieder seiner Instanz zu.
Das Dateiformat .rtsmap
Dieser Abschnitt richtet sich an Programmierer, die Karten mit anderen Werkzeugen lesen, Konverter schreiben oder eigene Daten hinzufügen möchten. Um das Paket zu verwenden, brauchst du ihn nicht:
RTSMapSerializer erledigt alles.
Binärer Aufbau
Alle Zahlen werden im Little-Endian-Format gespeichert. Strings verwenden das .NET-Format von BinaryWriter: eine 7-Bit-codierte Länge, gefolgt von UTF-8-Bytes.
| Teil | Typ | Inhalt |
|---|---|---|
| Magic-Kennung | 4 Byte | ASCII RTSM |
| Formatversion | int32 | RTSMapSerializer.CurrentVersion (derzeit 1) |
| Header-Länge | int32 | Anzahl der folgenden Header-Bytes |
| Header | Bytes | string name, string author, string description, int32 width, int32 height, float cellSize, int32 maxPlayers, string tilesetId, int64 modifiedUtcTicks |
| Datenteil | roher Deflate-Stream (RFC 1951, ohne zlib- oder gzip-Wrapper) bis zum Dateiende | Eine Folge von Abschnitten, abgeschlossen durch ein int32-Tag mit dem Wert 0 |
Der Header ist nicht komprimiert, damit Kartenbrowser Name, Autor, Größe und Spielerzahl anzeigen können, ohne die Karte zu entpacken
(RTSMapSerializer.TryReadHeader, TryReadHeaderFromFile, Struct RTSMapHeader). Leseroutinen springen anhand der
Header-Länge an das Ende des Headers, sodass künftige Versionen weitere Header-Felder anhängen können.
Dateiabschnitte
Jeder Abschnitt hat den Aufbau [int32 tag][int32 length][payload]. Tags sind FourCC-Codes, die als Little-Endian gespeichert werden; das erste Zeichen steht also im niedrigsten Byte
(DIMS = 0x534D4944). Arrays mit Rasterdaten werden als int32-Elementanzahl geschrieben, gefolgt von den rohen Elementen.
| Tag | Wert | Nutzdaten |
|---|---|---|
DIMS | 0x534D4944 | int32 width, int32 height, float cellSize, float cliffLevelHeight, int32 splatResolution, int32 detailResolution, int32 pathingResolution, uint32 nextInstanceId. Muss der erste Abschnitt sein. |
INFO | 0x4F464E49 | string name, author, description; int32 maxPlayers; string tilesetId; int64 createdUtcTicks, modifiedUtcTicks |
HGHT | 0x54484748 | Feinhöhen: float pro Vertex |
LEVL | 0x4C56454C | Klippenstufen: byte pro Vertex |
VFLG | 0x474C4656 | Vertex-Flags: byte pro Vertex |
CTYP | 0x50595443 | Klippentypen: byte pro Vertex |
WATR | 0x52544157 | Höhen der Wasseroberfläche: float pro Vertex |
SPLT | 0x544C5053 | int32 Anzahl der Splatmaps (4), danach pro Splatmap ein Array mit RGBA-Bytes pro Texel |
DETL | 0x4C544544 | int32 Anzahl der Schichten, danach pro Schicht: string prototypeId und ein Byte-Array mit den Dichten pro Detail-Texel |
TREE | 0x45455254 | Liste der Prototyp-IDs (int32 Anzahl + Strings), int32 Anzahl der Bäume, danach pro Baum: uint32 id, uint16 prototype, float3 position, float yaw, float scale, 4 Byte tint (30 Byte) |
DECO | 0x4F434544 | Liste der Prototyp-IDs, int32 Anzahl, danach pro Instanz: uint32 id, uint16 prototype, byte variant, byte flags, float3 position, float4 rotation (x, y, z, w), float3 scale, 4 Byte tint (52 Byte) |
PATH | 0x48544150 | Manuelle Pathing-Overrides: byte pro Pathing-Zelle |
REGN | 0x4E474552 | int32 Anzahl, danach pro Region: string name, float x, z, width, depth, float4 colour, Eigenschaften |
STRT | 0x54525453 | int32 Anzahl, danach pro Startposition: int32 player, float3 position, float yaw |
MARK | 0x4B52414D | int32 Anzahl, danach pro Marker: string name, string tag, float3 position, float4 rotation, Eigenschaften |
ENVR | 0x52564E45 | float4 sunColor, float sunIntensity, float2 sunRotation, float4 ambientColor, bool fog, float4 fogColor, float fogDensity |
„Eigenschaften“ werden als int32-Anzahl geschrieben, gefolgt von Schlüssel/Wert-Paaren aus Strings. Die abgeleitete Pathing-Karte wird nicht gespeichert, sondern nur die manuellen Overrides.
Unbekannte Abschnitte, beschädigte Dateien und Versionierung
- Unbekannte Abschnitte werden übersprungen, und zwar anhand ihrer Länge. Ältere Versionen des Pakets können deshalb neuere Dateien mit zusätzlichen Abschnitten lesen, und deine eigenen Abschnitte führen bei anderen Leseroutinen nie zu Fehlern.
- Hat eine Datei eine neuere Formatversion als die Leseroutine, wird eine Warnung protokolliert und die Datei trotzdem gelesen; unbekannte Daten werden übersprungen.
- Passt die Elementanzahl einer Rasterschicht nicht zur Kartengröße, wird eine Warnung protokolliert; was passt, wird gelesen, der Rest übersprungen.
- Abgeschnittene oder beschädigte Dateien lösen immer denselben Ausnahmetyp aus:
System.IO.InvalidDataException. Bei einer Datei mit falscher Magic-Kennung lautet die Meldung „Not an RTS Terrain Studio map file“. RTSMapSerializer.SaveToFileschreibt zuerst in<path>.tmpund ersetzt dann die Datei. Ein Absturz während des Speicherns hinterlässt deshalb nie eine halb geschriebene Karte.- Kompatibilitätsregel für das Format: Neue Daten kommen als neue Abschnitte oder als angehängte Header-Felder hinzu, nie durch Änderungen an bestehenden Abschnitten. Halte es mit deinen eigenen Abschnitten genauso und speichere eine Versionsnummer in deinen Nutzdaten.
Eigene Dateiabschnitte
Du kannst eigene Daten in Kartendateien speichern, etwa Spielregeln, Trigger oder Questdaten. Möglich machen das zwei statische Hooks von RTSMapSerializer:
| Hook | Typ | Aufruf |
|---|---|---|
CustomSectionWriter | Action<MapData, Action<int, byte[]>> | Einmal pro Speichervorgang, nach den eingebauten Abschnitten. Rufe die übergebene Action für jeden Abschnitt, den du schreiben möchtest, mit deinem Tag und deinen Nutzdaten auf. |
CustomSectionReader | Action<MapData, int, byte[]> | Für jeden Abschnitt, den die Leseroutine nicht kennt, mit dessen Tag und Nutzdaten. Das Objekt MapData existiert zu diesem Zeitpunkt bereits (DIMS kommt zuerst). |
- Wähle Tags, die nicht mit den eingebauten kollidieren; beginne sie mit
X(zum BeispielXGLD). Verwende nie 0, denn dieser Wert markiert das Ende der Abschnitte. - Beide Hooks sind einfache Delegates: Kombiniere Handler mit
+=und entferne sie mit-=. So können mehrere Systeme Abschnitte beisteuern. - Prüfe in deiner Leseroutine das Tag und ignoriere Abschnitte, die nicht von dir stammen.
MapDatabietet keinen Platz für deine Daten. Verwalte sie neben der Karte (das Beispiel verwendet eineConditionalWeakTable). So abgelegte Daten kopierenMapData.Clone()undResizeMapnicht mit, und sie werden auch nicht im Undo-Verlauf erfasst.- Registriere die Hooks, bevor eine Karte geladen oder gespeichert wird. Im Unity Editor liest auch der Importer Karten, und Save schreibt sie; registriere die Hooks deshalb auch für den Editor
(das Beispiel verwendet
InitializeOnLoadMethod).
using System;
using System.IO;
using System.Runtime.CompilerServices;
using RTSTerrainStudio;
using UnityEngine;
/// <summary>Stores a "starting gold" value in every map file, in a custom section "XGLD".</summary>
public static class StartingGoldSection
{
// FourCC, little-endian like the built-in tags: the first character is in the lowest byte.
const int Tag = 'X' | ('G' << 8) | ('L' << 16) | ('D' << 24);
const int PayloadVersion = 1;
/// <summary>Our data for one map.</summary>
public sealed class Data
{
public int startingGold = 500;
}
// One Data object per MapData. Entries disappear automatically together with their map.
static readonly ConditionalWeakTable<MapData, Data> s_Data = new ConditionalWeakTable<MapData, Data>();
/// <summary>Returns the data of a map (created with defaults when the map has none).</summary>
public static Data Get(MapData map) => s_Data.GetOrCreateValue(map);
#if UNITY_EDITOR
[UnityEditor.InitializeOnLoadMethod] // editor: importer and Save
#endif
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] // player and Play Mode
static void Register()
{
// Remove first so a second registration does not add the handlers twice.
RTSMapSerializer.CustomSectionWriter -= Write;
RTSMapSerializer.CustomSectionWriter += Write;
RTSMapSerializer.CustomSectionReader -= Read;
RTSMapSerializer.CustomSectionReader += Read;
}
static void Write(MapData map, Action<int, byte[]> writeSection)
{
if (!s_Data.TryGetValue(map, out Data data)) return; // nothing to save for this map
using (var stream = new MemoryStream())
{
using (var writer = new BinaryWriter(stream))
{
writer.Write(PayloadVersion); // our own version, so the payload can grow later
writer.Write(data.startingGold);
}
writeSection(Tag, stream.ToArray());
}
}
static void Read(MapData map, int tag, byte[] payload)
{
if (tag != Tag) return; // another system's section: ignore it
using (var reader = new BinaryReader(new MemoryStream(payload)))
{
int version = reader.ReadInt32();
if (version >= 1) Get(map).startingGold = reader.ReadInt32();
}
}
}
Im Spielcode greifst du mit StartingGoldSection.Get(terrain.Map).startingGold darauf zu. Wenn du den Wert im Editor änderst, markiere die Karte anschließend als geändert (zum Beispiel mit
terrain.NotifyChanged(MapLayers.Info, terrain.Map.CellBounds) oder indem du terrain.HasUnsavedChanges = true setzt), damit sie gespeichert wird.
Bearbeiten im Unity Editor
Dieses Kapitel behandelt alles, womit du im Unity Editor eine Karte baust: das Fenster von RTS Terrain Studio, das Malen in der Scene View, Tastenkürzel, Pinsel, Symmetrie, Rückgängigmachen, jedes Bearbeitungswerkzeug mit all seinen Einstellungen, Auto Paint und die Komponente RTS Terrain selbst.
Dieselben Werkzeuge sowie die Pinsel- und Symmetrieeinstellungen stecken auch im In-Game-Editor (siehe In-Game-Editor). Was du hier lernst, funktioniert also auch dort.
Deine erste Bearbeitung in fünf Schritten. Wenn du das Paket noch nie verwendet hast, probiere Folgendes:
- Erstelle ein Terrain über . Hat das Projekt noch kein Tileset, wird dir angeboten, zuerst die Demo-Inhalte zu erstellen. Nimm das Angebot an.
- Öffne .
- Klicke im Tab Height auf Raise / Lower. Die Bearbeitung wird automatisch eingeschaltet.
- Bewege die Maus in der Scene View über das Terrain und halte die linke Maustaste gedrückt. Es wächst ein Hügel. Halte zusätzlich Shift gedrückt, um stattdessen zu graben.
- Drücke 2 für die Klippenwerkzeuge, klicke auf den Hügel, um ein Plateau anzuheben, und drücke Esc, wenn du fertig bist. Mit Ctrl+Z machst du jeden Schritt rückgängig.
Das Fenster von RTS Terrain Studio
Das Fenster ist die Schaltzentrale des Editors. Es hat keinen eigenen Bearbeitungszustand: Es zeigt und ändert die gemeinsame Bearbeitungssitzung, die auch die Scene View, das Overlay der Scene View und der Inspector von RTS Terrain verwenden. Was du an einer Stelle änderst, erscheint sofort auch an den anderen.
Du kannst es auf folgende Arten öffnen:
- über die Schaltfläche Open RTS Terrain Studio im Inspector von RTS Terrain
- über die Fenster-Schaltfläche im Overlay von RTS Terrain Studio in der Scene View

Das Fenster hat vier Bereiche: oben die Kopfzeile, die Tab-Leiste, die scrollbare Seite und unten die Statusleiste.
Kopfzeile
Die Kopfzeile zeigt, welches Terrain und welche Karte du bearbeitest, und enthält die Datei- und Undo-Befehle.
| Bedienelement | Funktion |
|---|---|
| Terrain-Auswahl | Legt fest, welches Terrain das Fenster und die Werkzeuge der Scene View bearbeiten. Sie listet jedes RTS Terrain in den geöffneten Szenen auf und bietet einen Eintrag Create New Terrain. Auch wenn du ein Terrain im Hierarchy-Fenster auswählst oder in der Scene View auf ein anderes Terrain klickst, wechselt das bearbeitete Terrain. Noch offene Texteingaben werden vor dem Wechsel übernommen. |
| Schaltfläche Select | Wählt das GameObject des Terrains im Hierarchy-Fenster aus und hebt es dort kurz hervor. |
| Kartenname und Punkt | Der Name der Karte (aus dem Tab Map). Ein Punkt daneben bedeutet, dass die Karte nicht gespeicherte Änderungen hat. Darunter siehst du die Kartengröße und den Namen des .rtsmap-Assets. |
| Save | Schreibt die Karte in ihr .rtsmap-Asset. Ohne Karten-Asset verhält sich die Schaltfläche wie Save As. Solange es nicht gespeicherte Änderungen gibt, ist sie hervorgehoben. Ist die Datei schreibgeschützt oder gesperrt, erklärt ein Dialog das Problem und bietet Save As an. |
| Save As… | Speichert die Karte als neues .rtsmap-Asset im Projekt und weist es dem Terrain zu. |
| Open… | Öffnet ein anderes .rtsmap-Asset des Projekts auf diesem Terrain. Dateien außerhalb des Ordners Assets musst du zuerst ins Projekt kopieren. |
| Reload | Lädt die Karte neu aus ihrem Asset und verwirft nicht gespeicherte Änderungen. Erfordert ein Karten-Asset. |
| Undo / Redo | Macht die letzte Terrain-Bearbeitung rückgängig bzw. stellt sie wieder her. Die Schaltflächen zeigen den Namen des Schritts, zum Beispiel Undo Raise / Lower. Siehe Rückgängig und Wiederherstellen. |
| ? | Öffnet dieses Handbuch im Kapitel über das Editorfenster. |
| Editing On / Editing Off | Schaltet die Terrain-Bearbeitung in der Scene View ein oder aus. Ist noch kein Werkzeug ausgewählt, wird beim Einschalten das erste Werkzeug des gerade angezeigten Tabs gewählt. |
Open… und Reload fragen nach, bevor sie eine Karte mit nicht gespeicherten Änderungen oder Undo-Verlauf ersetzen, denn das Laden einer Karte löscht den Undo-Verlauf. Im Play Mode erinnert dich eine Warnung daran, dass Änderungen beim Verlassen des Play Mode verloren gehen, sofern du die Karte nicht speicherst.
Hat die Szene noch kein Terrain, zeigt die Kopfzeile stattdessen die Schaltfläche Create Terrain. Sie erstellt ein GameObject mit RTS Terrain und den Komponenten für Vegetation, Wasser und Dekor; dafür nutzt sie den Menüeintrag .
Tab-Leiste
Die Tab-Leiste hat zehn Tabs: die acht Werkzeugkategorien Height, Cliffs, Paint, Vegetation, Decor, Water, Pathing und Markers, gefolgt von Map und Settings. Ein kleiner Punkt markiert die Kategorie des Werkzeugs, mit dem du gerade malst. Das Fenster merkt sich den zuletzt verwendeten Tab.
Solange das Fenster den Tastaturfokus hat, wechselst du mit 1 bis 8 (auch auf dem Ziffernblock) zwischen den Kategorie-Tabs. Während du in ein Textfeld tippst, werden diese Tasten ignoriert.
Werkzeugseiten
Jeder Kategorie-Tab zeigt mehrere untereinander gestapelte Panels. Jedes Panel lässt sich mit einem Klick auf seine Kopfzeile einklappen.
| Panel | Inhalt |
|---|---|
| Kategorie Tools | Das Werkzeugraster. Klicke auf ein Werkzeug, um damit in der Scene View zu malen. Klicke erneut auf das aktive Werkzeug, um die Bearbeitung zu beenden. Fahre mit der Maus über ein Werkzeug, um seine Beschreibung und seine Zusatztasten zu lesen. Eigene Werkzeuge, die du schreibst, erscheinen hier automatisch. |
| Werkzeug-Panel | Name, Beschreibung und Hinweis des ausgewählten Werkzeugs, eine Schaltfläche ?, die den passenden Abschnitt dieses Handbuchs öffnet, und eine Live-Statuszeile. Bewege die Maus über das Terrain, um sie zu sehen, zum Beispiel Ground 4.25 m | Fine +0.25 m | Level 2 bei Höhenwerkzeugen oder Level 2 → 3 bei den Klippenwerkzeugen. |
| Getting Started | Wird angezeigt, wenn kein Werkzeug dieser Kategorie ausgewählt ist. Malst du gerade mit einem Werkzeug einer anderen Kategorie, steht dort Active tool: X, und das Panel bietet Go to Tool an. |
| Brush | Form, Radius, Stärke, Härte, Abnahmekurve, Drehung, zufällige Drehung, Abstand und Streuung. Alle Pinselwerkzeuge teilen sich diese Einstellungen. Bei Werkzeugen, die keinen Pinsel verwenden, ist das Panel ausgeblendet. Siehe Pinsel. |
| Symmetry | Symmetriemodus und Symmetriezentrum (bei Ziehwerkzeugen nicht angezeigt). Siehe Symmetrie. |
| Werkzeug Settings | Die Einstellungen des ausgewählten Werkzeugs, automatisch aus dem Code des Werkzeugs erzeugt. Sie bleiben über Editorsitzungen hinweg erhalten. Die Tabellen in der Werkzeugreferenz führen jede Einstellung auf. |
| Kategorieaktionen | Zusätzliche Panels für einige Kategorien: Auto Paint Whole Map (Paint), Selection (Decor, solange Select Decor aktiv ist), Water at Cursor (Water) und Pathing Overlay (Pathing). |
Solange die Bearbeitung pausiert ist, zeigt ein Banner Editing is paused – the Scene View uses Unity's tools. Mit der Schaltfläche Resume darin schaltest du die Bearbeitung wieder ein.

Werkzeugeinstellungen und Paletten
Das Einstellungs-Panel wählt für jede Einstellung anhand ihres Typs ein Bedienelement:
| Einstellungstyp | Bedienelement |
|---|---|
| Zahl mit Wertebereich | Schieberegler mit Wertefeld |
| An/Aus-Wert | Schalter |
| Auswahl (Enum) | Dropdown-Menü |
| Farbe / Text | Farbfeld / Textfeld |
| Bodenschicht | Palette der Bodenschichten des Tilesets mit Textur-Vorschaubildern. Leere Plätze bleiben sichtbar und erklären in ihrem Tooltip, wie du sie füllst. |
| Klippentyp | Palette der Klippenschichten des Tilesets. |
| Klippenstufe | Schaltflächen für die Stufen 0 bis 15. Jeder Tooltip zeigt die Bodenhöhe der jeweiligen Stufe, und die Palette zeigt die Stufe unter dem Cursor. |
| Spieler | Die 16 Spielerfarben (Red, Blue, Teal, Purple, Yellow, Orange, Green, Pink, …). |
| Detail-, Baum- oder Dekor-Prototypen | Raster mit den Prototypen der Bibliothek samt Vorschau, einem Suchfeld und bei Dekor zusätzlich Kategoriefiltern. Ein Klick wählt einen Prototyp aus, Ctrl+Klick fügt einen zur Auswahl hinzu oder entfernt ihn daraus, Shift+Klick wählt einen Bereich aus. None hebt die Auswahl auf; Platzierungswerkzeuge verwenden dann den ersten Prototyp der Bibliothek. |
| Asset-Referenz | Objektfeld (zum Beispiel für das Auto-Paint-Profil). Szenenobjekte werden akzeptiert, wo der Typ es erlaubt. |
Braucht eine Palette ein Asset, das dem Terrain fehlt, erklärt das Panel, was fehlt, und bietet eine Schaltfläche an, um es zu erstellen: Create Tileset, Create Vegetation Library oder Create Decor Library. Du kannst auch im Feld daneben ein vorhandenes Asset zuweisen. Jede Palette hat außerdem eine Schaltfläche Edit, die das Tileset oder die Bibliothek auswählt, damit du im Inspector Einträge hinzufügen kannst.
Nimmst du in der Scene View mit Ctrl+Klick einen Wert auf, ändert sich die Einstellung auch im Fenster, zum Beispiel die ausgewählte Bodenschicht oder die Höhe für Flatten.
Statusleiste
Die Statusleiste unten im Fenster zeigt von links nach rechts:
- den Punkt unter der Maus: Zelle, Weltposition, Klippenstufe, Bodenhöhe, Wassertiefe (an nassen Stellen) und den Namen der dominanten Bodenschicht;
- die Pathing-Flags an diesem Punkt (begehbar, bebaubar usw.);
- den Pinsel, zum Beispiel Brush 6.0 m · 50%;
- den Undo-Verlauf, zum Beispiel Undo 12 (+2) · 48 MB: Schritte, die du rückgängig machen kannst, Schritte, die du wiederherstellen kannst, und den Speicher, den sie belegen.
Tab Map
Der Tab Map enthält kartenweite Einstellungen und Operationen. Jede Änderung lässt sich rückgängig machen, sofern der zugehörige Dialog nichts anderes angibt. Die Schaltfläche ? oben öffnet diesen Abschnitt.
Map
| Einstellung | Beschreibung |
|---|---|
| Name | Name der Karte, der in Kartenbrowsern und Lobbys angezeigt wird. Er erscheint auch in der Kopfzeile des Fensters. |
| Author | Wer die Karte erstellt hat. |
| Description | Kurzbeschreibung, die Spielern angezeigt wird. |
| Max Players | Empfohlene Spielerzahl (1–16). |
| Map Asset | Das .rtsmap-Asset, das dieses Terrain lädt und speichert. Weist du ein anderes Asset zu, wird dessen Karte geöffnet – mit derselben Rückfrage wie bei Open…. |
Unter den Feldern zeigt das Panel die Kartengröße, die Auflösung der Splat-, Detail- und Pathing-Schichten pro Zelle sowie den Speicherbedarf der Karte. Eingaben in diese Felder erzeugen beim Verlassen des Feldes einen einzigen Undo-Schritt (Edit Map Info).
New Map
Ersetzt die Karte des Terrains durch eine neue, flache Karte.
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Größen-Presets | – | Schaltflächen für quadratische Karten mit 64, 96, 128, 192, 256, 384 und 512 Zellen. |
| Width / Height | aus der Default Map des Terrains (128) | Größe in Zellen. Vielfaches von 16, von 16 bis 1024. |
| Cell Size | 2 m | Größe einer Zelle in Metern. Klassische RTS-Karten verwenden etwa 2 m. |
| Cliff Level Height | 2 m | Höhe einer Klippenstufe in der Spielwelt. |
| Base Cliff Level | 2 | Anfängliche Klippenstufe der gesamten Karte. Mit 2 bleibt Platz, um Seen und Täler zu graben. |
| Base Ground Layer | 0 | Bodenschicht, die anfangs die gesamte Karte bedeckt. |
| Splat | 4 | Texel der Bodentexturierung pro Zelle (1, 2, 4 oder 8). 4 ist ein guter Standardwert; 8 ergibt eine sehr feine Texturierung. |
| Details | 2 | Texel für die Grasdichte pro Zelle (1, 2 oder 4). |
| Pathing | 2 | Pathing-Zellen pro Kartenzelle (1, 2 oder 4). Mit höheren Werten lassen sich Einheiten genauer platzieren. |
| Name | New Map | Name der neuen Karte. |
Eine Übersichtszeile zeigt dir die resultierende Weltgröße, bevor du auf Create New Map klickst.
Das Erstellen einer neuen Karte löscht den Undo-Verlauf und lässt sich nicht rückgängig machen. Das alte Karten-Asset wird vom Terrain getrennt, aber nicht verändert. Speichere die neue Karte mit Save As… in einer eigenen Datei.
Resize
Vergrößert oder verkleinert die Karte und behält dabei ihren Inhalt.
- Gib New Width und New Height in Zellen ein (Vielfache von 16).
- Klicke auf eine der neun Anker-Schaltflächen. Die Pfeile zeigen, in welche Richtung die Karte wächst; der vorhandene Inhalt bleibt an der verankerten Seite.
- Wähle für neue Bereiche Fill Level (Klippenstufe) und Fill Layer (Bodenschicht).
- Klicke auf Resize Map. Der Hinweis über der Schaltfläche zeigt die neue Weltgröße und wie weit sich der Inhalt verschiebt.
Beim Verkleinern werden Terrain, Bäume, Dekor und Marker außerhalb des neuen Bereichs entfernt. Eine Größenänderung löscht den Undo-Verlauf.
Generator
Erzeugt aus einem Seed ein komplettes Karten-Layout: Plateaus, Rampen, Wasser, Bodentexturierung, Vegetation, Dekor und Startpositionen. Derselbe Seed mit denselben Einstellungen ergibt immer dieselbe Karte.
| Bedienelement | Beschreibung |
|---|---|
| Seed, Style, Symmetry | Der Seed, der Stil (Plains, Hills, Highlands, Islands, Canyon, Arena) und die Symmetrie des Layouts (standardmäßig Rotate 2, das 180°-Layout; siehe Symmetrie). |
| Noise, Cliffs, Ramps, Water, Border, Paint, Vegetation, Decor, Start Locations | Ausklappbereiche mit den detaillierten Generator-Einstellungen der einzelnen Phasen. |
| Randomize Seed | Wählt einen neuen zufälligen Seed. |
| Apply Style Preset | Lädt die Werte für Rauschen, Klippen, Rampen und Wasser des ausgewählten Stils. |
| Reset | Setzt alle Generator-Einstellungen auf die Standardwerte des ausgewählten Stils zurück. |
| Generate | Ersetzt Höhen, Klippen, Wasser, Bodentexturierung, Vegetation, Dekor und Startpositionen. Die Kartengröße bleibt erhalten. Das Ganze ist ein einziger Undo-Schritt. |
Der Generator arbeitet mit dem Terrain, das in der Kopfzeile des Fensters eingestellt ist. Ist ein anderes Terrain ausgewählt, fordert dich ein Dialog auf, zuerst dieses Terrain in der Kopfzeile auszuwählen. Während er läuft, wird ein Fortschrittsbalken angezeigt, und unter den Schaltflächen erscheint eine Zusammenfassung der erzeugten Inhalte.
Heightmap
Importiert oder exportiert die Terrain-Höhe als 16-Bit-PNG- oder RAW-Datei, zum Beispiel für die Arbeit mit World Machine, Gaea oder Photoshop.
| Einstellung | Beschreibung |
|---|---|
| Mode (Import) | Fine Heights Only: fügt Hügel über den vorhandenen Klippenstufen hinzu. Cliff Levels And Fine Heights: teilt das Bild in Klippenstufen und Feinhöhen auf, sodass der Boden exakt dem Bild entspricht (ideal für terrassierte Heightmaps; sanfte Hänge, die eine Stufe überqueren, werden zu kleinen Klippen). Absolute Heights Keep Levels: Das Bild gibt die absolute Bodenhöhe an, die Klippen bleiben aber erhalten. Verwende diesen Modus, um einen Export mit der Quelle Ground Height, den du in einem anderen Programm bearbeitet hast, erneut zu importieren. |
| Height Scale | Meter, die der volle 16-Bit-Bereich abbildet. 0 = automatisch. |
| Height Offset | Import: Meter, die zu jeder Höhe addiert werden. Export: die Höhe, die als 0 geschrieben wird. |
| Flip Vertically | Spiegelt das Bild entlang Z. Verwende diese Option, wenn die Karte gespiegelt erscheint. |
| RAW Width / RAW Height | Größe von RAW-Dateien. 0 = aus der Dateigröße ableiten (nur bei quadratischen Dateien). |
| RAW Little Endian | Bytereihenfolge von RAW-Dateien. Windows und die meisten Programme verwenden Little Endian. |
| Source (Export) | Ground Height: Klippenstufen plus Feinhöhe. Fine Heights: nur die Feinhöhe über den Stufen. |
Zur Verfügung stehen die Schaltflächen Import PNG… (PNG in 8- oder 16-Bit-Graustufen, JPG oder EXR), Import RAW…, Export PNG…, Export RAW… und Export Splat PNG… (ein Bild der Bodentexturierung in den Farben der dominanten Schichten). Bilder mit abweichender Größe werden neu abgetastet. Eine Karte hat in jeder Richtung einen Vertex mehr als Zellen, eine 256er-Karte verwendet also 257 × 257 Samples. Jeder Import ist ein Undo-Schritt.
Minimap
Backt ein Minikartenbild der Karte. Wähle mit Size die Länge der längeren Seite (128, 256, 512 oder 1024 px) und passe die Bake Options an: Bodenfarben (dominante oder überblendete Schichten), Helligkeit der Klippenstufen, Reliefschattierung, Klippen, Wasser, Bäume, Dekor und Startpositionen. Bake Preview zeigt das Ergebnis im Panel; Save PNG… backt das Bild und speichert es als Datei. Wenn du die Karte nach dem Backen bearbeitest, weist dich das Panel darauf hin, erneut zu backen.
Validation
Validate Map prüft die Karte auf Probleme wie fehlende Texturen, unerreichbare Startpositionen oder Performance-Grenzen. Jedes Problem wird als Fehler, Warnung oder Info aufgeführt, zusammen mit einem Hinweis, wie du es behebst. Klicke auf ein Problem mit Ortsangabe, um es in der Scene View anzuzeigen. Wenn du die Karte nach der Validierung bearbeitest, weist das Panel darauf hin, dass die Liste den Stand der Karte vor den letzten Änderungen beschreibt.
Environment
Sonne, Umgebungslicht und Nebel werden in der Karte gespeichert und von der Komponente RTS Environment auf die Szene angewendet.
| Einstellung | Beschreibung |
|---|---|
| Sun Colour | Farbe des Sonnenlichts. |
| Sun Intensity | Intensität des Sonnenlichts (0–8). |
| Sun Rotation | Grad: x = Neigung (Höhe über dem Horizont), y = Drehwinkel. |
| Ambient Colour | Farbe des Umgebungslichts (Himmelslicht). |
| Fog | Aktiviert den Distanznebel. |
| Fog Colour / Fog Density | Farbe und Dichte (0–0.05) des exponentiellen Nebels. |
Capture From Scene übernimmt die aktuelle Szenenbeleuchtung in die Karte (ein Undo-Schritt). Apply wendet die Umgebung der Karte sofort auf die Szene an, und Select Component wählt die Komponente RTS Environment aus. Hat das Terrain keine Komponente RTS Environment, bietet das Panel Add Environment Component an.
Tab Settings
| Panel | Inhalt |
|---|---|
| View | Show Grid (das Zellraster auf dem bearbeiteten Terrain, auch G), Show Markers (Startpositionen, Regionen und Marker, auch wenn kein Marker-Werkzeug aktiv ist), Pathing Overlay (Off, Walkability, Buildability, Flyability, Water, Combined) und Overlay Opacity. |
| Terrain Component | Der vollständige Inspector der bearbeiteten Komponente RTS Terrain (siehe Die Komponente RTS Terrain) und eine Schaltfläche Select Terrain. |
| Undo History | Max Steps (10–1000, Standard 100) und Memory Limit (MB) (32–4096, Standard 512), der aktuelle Speicherverbrauch und Clear History. Siehe Grenzen und Speicher. |
| Shortcuts | Die Tastenkürzel der Scene View mit ihren aktuellen Tastenbelegungen, ausgelesen aus Unitys Shortcut Manager. Edit Shortcuts… öffnet den Shortcut Manager; Refresh liest die Belegungen erneut ein, nachdem du sie geändert hast. |
| Help & Maintenance | Open Documentation, Validate Project (führt aus) und Reset All Tool Settings. Letzteres setzt den Pinsel, die Symmetrie und die Einstellungen aller Werkzeuge auf ihre Standardwerte zurück und löscht den Undo-Verlauf des Terrains. |
| About | Produktversion und Unity-Version. |
Malen in der Scene View
Die gesamte Bearbeitung findet in der Scene View statt. Wenn die Terrain-Bearbeitung eingeschaltet und ein Werkzeug ausgewählt ist, malt die linke Maustaste mit dem Werkzeug, statt Objekte auszuwählen. Du schaltest die Bearbeitung mit der Schaltfläche Editing im Fenster, der Schaltfläche Start Editing im Overlay, der Schaltfläche Edit Terrain im Inspector oder durch einen Klick auf ein Werkzeug ein. Ausschalten kannst du sie mit Esc.
Maus und Zusatztasten
| Eingabe | Aktion |
|---|---|
| Linke Maustaste | Malt mit dem aktiven Werkzeug (drücken, ziehen, loslassen). Ein Pinselstrich ist ein Undo-Schritt. Viele Werkzeuge wirken weiter, während du die Maustaste gedrückt hältst, ohne die Maus zu bewegen. So baut Raise / Lower zum Beispiel den Hügel immer weiter auf. |
| Shift (gedrückt gehalten) | Kehrt das Werkzeug um: absenken statt anheben, radieren statt malen, entfernen statt platzieren. Der Pinselcursor färbt sich rötlich, und das Status-Panel zeigt (inverted). |
| Ctrl+Klick | Nimmt einen Wert unter dem Cursor auf, statt zu malen: eine Höhe, eine Bodenschicht, eine Klippenstufe, einen Klippentyp oder eine Wasserhöhe. Werkzeuge ohne Aufnahmefunktion zeigen This tool has nothing to pick und malen nicht. |
| Alt+Ziehen | Lässt die Kamera wie gewohnt kreisen. Dabei wird nie gemalt. |
| Rechte / mittlere Maustaste | Behalten ihre Funktion aus Unity (Fliegen, Schwenken). |
| Hand-Werkzeug (Q) | Ist Unitys Hand-Werkzeug aktiv, schwenkst du die Kamera durch Ziehen mit gedrückter linker Maustaste; dabei wird nicht gemalt. Das Status-Panel zeigt Hand tool (Q) active: press W to paint, or drag to pan. |
| Esc | Führt die erste zutreffende Aktion aus: bricht das laufende Ziehen eines Dekor-Handles ab, bricht den laufenden Pinselstrich ab (seine Änderungen werden zurückgenommen), hebt die Dekorauswahl auf (Select Decor) oder beendet die Bearbeitung. Esc wirkt in der Scene View, die den Tastaturfokus hat (klicke zuerst hinein); nur mit der Maus darüberzufahren reicht nicht. Hat das Fenster von RTS Terrain Studio den Fokus, bricht Esc den Pinselstrich ab und schaltet die Bearbeitung aus. |
| Klick auf ein anderes Terrain | Macht dieses Terrain zum bearbeiteten Terrain (Benachrichtigung Editing X). Solange du mit der Maus darüberfährst, zeigt das Status-Panel Click to edit 'X'. |
Unter macOS funktioniert Cmd überall dort, wo dieses Handbuch Ctrl nennt.
Wo der Mauszeiger das Terrain trifft, wird rein mathematisch berechnet, daher sind keine Collider nötig. Der Terrain-Shader zeichnet den Pinselcursor genau dort, wo du malst. Sind mehrere Scene Views geöffnet, malt nur die Ansicht unter der Maus, und ein Pinselstrich bleibt in der Ansicht, in der er begonnen hat.
Die Bearbeitung funktioniert nur bei einem Terrain, das aktiviert ist, eine geladene Karte hat und zur aktuellen Stage gehört. Bearbeite im Prefab Mode das Terrain innerhalb des Prefabs.
Das Overlay von RTS Terrain Studio
Sobald die Szene ein RTS Terrain enthält, erscheint in der Scene View das kompakte Overlay RTS Terrain Studio. Es bietet dir die meistgenutzten Bedienelemente, ohne dass du das ganze Fenster brauchst:
- die Schaltfläche Start Editing / Editing (Esc to stop), eine Schaltfläche, die das Fenster öffnet, und ein Optionsmenü;
- das bearbeitete Terrain (Terrain: X • unsaved, wenn es nicht gespeicherte Änderungen hat);
- die acht Kategorieschaltflächen (Tasten 1–8) und die Werkzeuge der ausgewählten Kategorie;
- Size (ein Schieberegler mit Feinsteuerung für kleine Pinsel, dazu ein Feld für den exakten Wert) und Strength;
- das Dropdown-Menü Symmetry, mit einer Warnung, wenn der Modus eine quadratische Karte erfordert;
- die Schalter Grid und Markers sowie das Dropdown-Menü Pathing für das Pathing-Overlay.
Das Optionsmenü enthält Show Status Panel, Save Map, Save Map As…, Save Maps When Saving the Scene, Offer Save As for New Maps, Shortcuts… und Documentation. Wie jedes Unity-Overlay lässt es sich verschieben, andocken oder einklappen, und Unity merkt sich seine Position.
Das Tools-Overlay von Unity
Solange ein RTS Terrain ausgewählt ist, enthält Unitys eigenes Tools-Overlay das Werkzeug RTS Terrain Studio. Wenn du es aktivierst, wird das ausgewählte Terrain zum bearbeiteten Terrain, und die Bearbeitung wird eingeschaltet. Wechselst du zu einem anderen Unity-Werkzeug, wird die Bearbeitung wieder ausgeschaltet.
Die Ausnahme ist Select Decor: Solange es aktiv ist, bestimmen Unitys Werkzeuge Move, Rotate, Scale und Transform das Handle für die Dekorauswahl, daher bleibt die Bearbeitung eingeschaltet. Siehe Handles der Dekorauswahl.
Status-Panel und Benachrichtigungen
Ein Status-Panel am unteren Rand der Scene View zeigt zwei Zeilen:
- das aktive Werkzeug, (inverted), solange Shift gedrückt ist, Pinselgröße und -stärke (nur bei Pinselwerkzeugen) und den Symmetriemodus, mit einer Warnung, wenn der Modus eine quadratische Karte erfordert;
- die Zelle unter der Maus, Klippenstufe und Höhe, den Werkzeugstatus oder -hinweis und was Esc gerade bewirkt (Esc: cancel stroke, Esc: cancel edit, Esc: clear selection oder Esc: stop editing).
Mit Show Status Panel im Optionsmenü des Overlays blendest du es aus oder ein. Tastenkürzel bestätigen ihre Wirkung mit einer kurzen Benachrichtigung in der Scene View, zum Beispiel Brush 8.0 m, Strength 55%, Grid on oder Stroke cancelled. War die Bearbeitung beim Start von Unity aus deiner letzten Sitzung noch eingeschaltet, erscheint einmalig der Hinweis Terrain editing is on (Esc to stop).
Vorschauen und Hilfslinien
- Pinselcursor: wird in der Farbe des Werkzeugs auf das Terrain projiziert, dazu ein weiterer für jede Symmetriekopie.
- Platzierungsvorschau: Im Modus Single zeigen Place Trees und Place Decor am Cursor eine halbtransparente Vorschau des nächsten Objekts.
- Symmetrie-Hilfslinien: magentafarbene Achsen und der Mittelpunkt, solange ein Symmetriemodus aktiv ist.
- Marker: Startpositionen (in Spielerfarben), Regionen und Marker; sie werden gezeichnet, solange Show Markers eingeschaltet oder ein Marker-Werkzeug aktiv ist.
- Raster (G) und Pathing-Overlay (P): werden vom Terrain-Shader gezeichnet.
Handles der Dekorauswahl
Ist Select Decor aktiv, klickst du Objekte an, um sie auszuwählen, ziehst ein Rechteck für eine Rechteckauswahl auf und hältst Shift gedrückt, um Objekte zur Auswahl hinzuzufügen oder ihren Auswahlstatus umzuschalten. Die Auswahl erhält Handles im Unity-Stil in der Mitte der ausgewählten Objekte:
| Unity-Werkzeug | Handle |
|---|---|
| Move (W) | Positions-Handle. |
| Rotate (E) | Drehscheibe: Dekor dreht sich nur um die Hochachse. Halte Ctrl gedrückt, um in Schritten einzurasten. |
| Scale (R) | Handle für gleichmäßige Skalierung. Ist Select Decor aktiv, wählt das Tastenkürzel R dieses Werkzeug aus, statt die zufällige Drehung umzuschalten. |
| Transform (Y) | Positions-Handle und Drehscheibe. |
| Rect (T) | Positions-Handle. |
| Hand (Q) | Kein Handle; Ziehen mit der linken Maustaste schwenkt die Kamera. |
Delete entfernt die Auswahl. Ctrl+D dupliziert die Auswahl, um eine Zelle in X und Z versetzt, damit die Kopien sichtbar sind; verschiebe sie dann mit dem Handle. Eine Benachrichtigung wie Duplicated 3 objects bestätigt den Vorgang. Ctrl+A wählt alle nicht gesperrten Dekorobjekte aus, und F rückt die Auswahl ins Bild. Das sind Unitys eigene Befehle Delete, Duplicate, Select All und Frame Selected, daher folgen sie deinen Belegungen im Shortcut Manager. Jedes Ziehen eines Handles ist ein Undo-Schritt. Esc während des Ziehens nimmt es zurück, ohne einen Schritt anzulegen. Weitere Befehle findest du im Panel Selection des Tabs Decor (siehe Dekorwerkzeuge).
Speichern und Schutz vor Datenverlust
Während der Bearbeitung liegt die Karte im Arbeitsspeicher, außerhalb der Szenendatei. Unitys eigene Abfragen Save scene? decken sie daher nicht ab. RTS Terrain Studio ergänzt eigene Schutzmechanismen:
| Zeitpunkt | Was passiert |
|---|---|
| Speichern einer Szene | Die Karten ihrer Terrains werden automatisch in ihre .rtsmap-Assets gespeichert (Overlay-Option Save Maps When Saving the Scene, standardmäßig an). Für ein Terrain ohne Karten-Asset wird einmal pro Editorsitzung Save As… angeboten, mit den Auswahlmöglichkeiten Not Now und Never Ask Again (Overlay-Option Offer Save As for New Maps). |
| Wechsel in den Play Mode | Save map changes before entering Play Mode? mit Save, Cancel (bleibt im Edit Mode) und Don't Save. Unity lädt die Szene neu und damit auch die Karte aus ihrem Asset. Die Abfrage entfällt, wenn die Enter Play Mode Options sowohl das Neuladen der Szene als auch das der Domain deaktivieren. |
| Verlassen des Play Mode | Save map changes made in Play Mode? mit Save oder Discard. Die Frage erscheint, wenn die Karte während des Play Mode mit den Werkzeugen der Scene View bearbeitet wurde, oder immer dann, wenn die Szene einen In-Game-Editor (RTSRuntimeEditor) enthält und die Karte nicht gespeicherte Änderungen hat. In anderen Szenen lösen Gameplay-Änderungen wie abgebaute Bäume sie nicht aus. |
| Schließen einer Szene, Beenden von Unity | Fragt, ob die geänderten Karten gespeichert werden sollen. |
Bevor das Paket eine Kartendatei überschreibt, bittet es deine Versionsverwaltung, die Datei auszuchecken. Schlägt das fehl, erklärt ein Dialog, was zu tun ist. Im Batch-Modus und während ein Player-Build erstellt wird, zeigen die Schutzmechanismen nie Dialoge.
Build-Skripte und Editor-Tests können den Schutz vorübergehend ausschalten:
using RTSTerrainStudio.Editor;
using UnityEditor;
static class MyBuildSteps
{
[MenuItem("Tools/My Project/Run Map Batch")]
static void RunMapBatch()
{
// No save prompts while this scope is open. Scopes can be nested.
using (RTSMapSaveGuard.Suppress())
{
// ... open scenes, edit or generate maps, enter and leave Play Mode ...
}
}
}
Tastenkürzel
Die Tastenkürzel der Scene View sind in Unitys Shortcut Manager in der Kategorie RTS Terrain Studio registriert. Sie sind nur aktiv, wenn drei Bedingungen erfüllt sind: Die Terrain-Bearbeitung ist eingeschaltet, eine Scene View oder eines der Editorfenster von RTS Terrain Studio hat den Tastaturfokus (oder die Maus befindet sich über einer Scene View), und du tippst gerade nicht in ein Text- oder Zahlenfeld. Außerhalb der Bearbeitung behalten Tasten wie R oder die Zifferntasten ihre normale Funktion in Unity.
| Standardtaste | Eintrag im Shortcut Manager | Aktion |
|---|---|---|
| [ | RTS Terrain Studio/Brush Smaller | Pinselradius × 0.87. |
| ] | RTS Terrain Studio/Brush Larger | Pinselradius × 1.15. |
| - | RTS Terrain Studio/Strength Down | Pinselstärke − 5 %. |
| = | RTS Terrain Studio/Strength Up | Pinselstärke + 5 %. |
| R | RTS Terrain Studio/Toggle Random Rotation | Schaltet die zufällige Pinseldrehung um. Ist Select Decor aktiv, wählt die Taste stattdessen Unitys Werkzeug Scale aus. |
| G | RTS Terrain Studio/Toggle Grid | Blendet das Zellraster ein oder aus. |
| P | RTS Terrain Studio/Cycle Pathing Overlay | Wechselt reihum Off → Walkability → Buildability → Combined → Off. |
| 1 | RTS Terrain Studio/Height Tools | Wählt das erste Werkzeug der Kategorie aus. Drücke die Taste erneut, um reihum durch die Werkzeuge dieser Kategorie zu schalten. |
| 2 | RTS Terrain Studio/Cliff Tools | |
| 3 | RTS Terrain Studio/Paint Tools | |
| 4 | RTS Terrain Studio/Vegetation Tools | |
| 5 | RTS Terrain Studio/Decor Tools | |
| 6 | RTS Terrain Studio/Water Tools | |
| 7 | RTS Terrain Studio/Pathing Tools | |
| 8 | RTS Terrain Studio/Marker Tools |
Diese Tasten und Mausaktionen verarbeitet das Paket selbst; sie lassen sich nicht neu belegen:
| Taste | Ort | Aktion |
|---|---|---|
| Shift (gedrückt gehalten) | Scene View | Werkzeug umkehren (absenken, radieren, entfernen). |
| Ctrl+Klick (Cmd+Klick unter macOS) | Scene View | Wert aufnehmen (Höhe, Schicht, Stufe, Klippentyp, Wasserhöhe). |
| Esc | Scene View mit Tastaturfokus | Ziehen des Handles oder Pinselstrich abbrechen, Dekorauswahl aufheben oder Bearbeitung beenden. |
| 1–8 (auch Ziffernblock) | Fenster von RTS Terrain Studio | Kategorie-Tab wechseln. |
| Esc | Fenster von RTS Terrain Studio | Pinselstrich abbrechen und Bearbeitung beenden. |
| Tab, Enter, Space | Fenster von RTS Terrain Studio | Fokus zwischen Werkzeugen und Tabs verschieben; das fokussierte Element aktivieren. |
| Ctrl+Klick, Shift+Klick | Prototyp-Raster | Einen Prototyp hinzufügen oder entfernen; einen Bereich auswählen. |
Die folgenden Tasten sind Unitys eigene Tastenkürzel und Befehle. RTS Terrain Studio reagiert auf sie, sie folgen aber dem, was du ihnen in unter Unitys Kategorien (Main Menu/Edit, Tools) zuweist:
| Standardtaste | Ort | Aktion |
|---|---|---|
| Ctrl+Z / Ctrl+Y | Überall in Unity | Terrain-Bearbeitungen über Unitys Edit-Menü rückgängig machen / wiederherstellen. |
| W E Y | Scene View, Select Decor | Unitys Werkzeuge Move, Rotate und Transform: Sie bestimmen das Handle für die Dekorauswahl. |
| Delete, Ctrl+D, Ctrl+A, F | Scene View, Select Decor | Delete, Duplicate, Select All (nicht gesperrtes Dekor) und Frame Selected. |
R ist bei Select Decor der paketeigene Eintrag RTS Terrain Studio/Toggle Random Rotation: Solange Select Decor aktiv ist, wechselt er zu Unitys Werkzeug Scale, statt die zufällige Drehung umzuschalten.
Tastenkürzel neu belegen
- Öffne (unter macOS ) oder klicke im Tab Settings auf Edit Shortcuts….
- Wähle die Kategorie RTS Terrain Studio aus.
- Weise den oben aufgeführten Einträgen neue Tasten zu.
- Klicke im Tab Settings auf Refresh, um die neuen Belegungen im Fenster zu sehen.
Kollidiert eine Taste in deiner Konfiguration mit einem anderen Tastenkürzel, zum Beispiel mit Toggle 2D Mode der Scene View auf 2, fragt Unity einmal, welches verwendet werden soll. Du kannst jedes der beiden im Shortcut Manager neu belegen.
Pinsel
Alle Pinselwerkzeuge teilen sich denselben Pinsel. Du stellst ihn im Panel Brush jeder Werkzeugseite, im Overlay (Size und Strength) oder mit den Tastenkürzeln ein. Der Pinsel bleibt zwischen Editorsitzungen erhalten.
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Shape | Circle, Square, Diamond, Custom | Circle | Umriss des Pinsels. Square und Diamond lassen sich drehen. Custom verwendet ein Asset vom Typ Brush Shape. |
| Custom Shape | – | keine | Das Asset vom Typ Brush Shape, das die Form Custom verwendet. Klicke auf die Formschaltfläche Custom: Das Panel zeigt dann jedes Asset vom Typ Brush Shape im Projekt als Kachel, dazu ein Feld Custom Shape. Klicke auf eine Kachel (oder weise im Feld ein Asset zu), um es zu verwenden. |
| Radius | 0.25–128 m | 6 m | Pinselradius in Metern. Der Schieberegler ist logarithmisch, damit sich kleine Pinsel fein einstellen lassen. Tastenkürzel [ und ]. |
| Strength | 0.01–1 | 0.5 | Wirkung jedes Tupfers. Tastenkürzel - und =. |
| Hardness | 0–1 | 0.35 | 0 = weicher Rand über den gesamten Radius, 1 = harter Rand. Deaktiviert, solange Falloff Curve eingeschaltet ist. |
| Falloff Curve | an/aus | aus | Verwendet statt Hardness eine eigene Kurve. |
| Curve | – | Ease-in-out von 1 nach 0 | Gewichtung von der Pinselmitte (x = 0) bis zum Rand (x = 1). |
| Rotation | 0–360° | 0 | Drehung quadratischer, rautenförmiger und eigener Formen, im Uhrzeigersinn von oben gesehen. Deaktiviert bei der Form Circle und solange Random Rotation eingeschaltet ist. |
| Random Rotation | an/aus | aus | Dreht jeden Tupfer zufällig. Ideal für eigene Stempel. Tastenkürzel R. |
| Spacing | 0.05–2 | 0.2 | Abstand zwischen den Tupfern relativ zum Radius. Kleinere Werte ergeben gleichmäßigere Pinselstriche. |
| Jitter | 0–1 | 0 | Zufälliger Versatz jedes Tupfers relativ zum Radius. |
Wie ein Tupfer gewichtet wird
Jeder Tupfer weist jedem Vertex, Splat-Texel oder Objekt eine Gewichtung zwischen 0 und 1 zu, die das Werkzeug mit Strength multipliziert. Mit Hardness ist die Gewichtung innerhalb von hardness × radius gleich 1 und fällt dann zum Rand hin sanft auf 0 ab. Ist Falloff Curve eingeschaltet, bestimmt stattdessen die Kurve die Gewichtung: x ist der Abstand von der Mitte (0 = Mitte, 1 = Rand), y ist die Gewichtung. Bei quadratischen und rautenförmigen Formen folgt der „Abstand“ dem Umriss, sodass ein quadratischer Pinsel entlang seiner Seiten gleichmäßig stark wirkt.
Manche Werkzeuge verwenden den Pinsel als harte Auswahl: Klippen-, Wasser- und Pathing-Werkzeuge ändern nur, was eine Gewichtung von mindestens 0.5 hat, oder den Vertex unter der Pinselmitte. Weiche Ränder bedeuten dann „etwas kleiner“ statt „schwächer“.
Abstand, Streuung und Wiederholung
- Spacing: Während du ziehst, wird entlang des Mauswegs alle
radius × spacingMeter ein Tupfer gesetzt; der Abstand beträgt aber mindestens ein Zehntel einer Zelle. Werkzeuge, die an Vertices einrasten, setzen Tupfer mit mindestens einer halben Zelle Abstand. - Jitter: Jeder Tupfer wird um eine zufällige Distanz von bis zu
jitter × radiusverschoben. Jitter wirkt nur bei Werkzeugen, die den Pinsel verwenden. - Wiederholung: Viele Werkzeuge setzen weiter Tupfer, während du die Maustaste gedrückt hältst, ohne die Maus zu bewegen, zum Beispiel 30 Tupfer pro Sekunde bei Raise / Lower. Die Werkzeugtabellen geben an, welche Werkzeuge das tun.
Konvention für Drehungen
Alle Drehungen in RTS Terrain Studio (Pinseldrehung, Drehwinkel von Bäumen und Dekor, Richtung von Startpositionen und Markern) folgen derselben Konvention: Grad, im Uhrzeigersinn von oben gesehen, wobei 0° entlang +Z (Norden) und 90° entlang +X (Osten) zeigt. Deshalb werden Symmetriekopien eines gedrehten Pinsels oder Objekts genau wie ihre Positionen gedreht und gespiegelt. Ist Random Rotation eingeschaltet, wird pro Tupfer ein Zufallswinkel bestimmt, den alle seine Symmetriekopien teilen. So bleiben symmetrische Pinselstriche symmetrisch.
Eigene Pinselformen
Ein Asset vom Typ Brush Shape macht aus jeder Graustufentextur einen Pinselstempel: Weiße Bereiche wirken voll, schwarze gar nicht.
- Importiere eine Graustufentextur, zum Beispiel den Umriss eines Felsens oder einen Spritzer. Sie muss nicht lesbar sein.
- Erstelle das Asset mit und weise die Textur zu.
- Prüfe die Vorschau im Inspector. Sie zeigt die gebackene Maske genau so, wie die Pinsel sie verwenden, dazu ihre Abdeckung.
- Klicke im Panel Brush auf die Formschaltfläche Custom. Das Panel zeigt nun jedes Asset vom Typ Brush Shape als Kachel; klicke auf die Kachel der neuen Form.
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Texture | keine | Graustufen- oder Alpha-Textur. Weiß = volle Wirkung. |
| Use Red Channel | an | Liest den Rotkanal. Schalte die Option aus, um stattdessen den Alphakanal zu lesen. |
| Invert | aus | Kehrt die Maske um. |
| Resolution | 64 | Auflösung der gebackenen Maske (16–256). |
Die Maske wird nach jeder Änderung automatisch gebacken. Bake Mask backt sie sofort. Ein eigener Stempel deckt das Quadrat um die Pinselmitte ab und wird trotzdem noch mit der Härte bzw. der Abnahmekurve multipliziert. Verringere die Härte für weiche Stempelränder, oder setze sie auf 1, um den Stempel unverändert zu verwenden.
Symmetrie
Symmetrie wiederholt jeden Tupfer, jeden Klick und jedes platzierte Objekt an gespiegelten oder gedrehten Positionen. So baust du faire Wettkampfkarten: Jeder Spieler bekommt dieselben Hügel, Klippen, Rampen, Bäume und Goldminen. Wähle den Modus im Panel Symmetry oder im Overlay. Magentafarbene Hilfslinien in der Scene View zeigen die Achsen und das Zentrum.
Symmetrie gilt für Pinsel- und Klickwerkzeuge. Ziehwerkzeuge (Regions und Select Decor) ignorieren sie, und das Fenster blendet das Panel Symmetry aus, solange eines davon aktiv ist. Gespiegelte Regionen legst du von Hand an.
| Name im Fenster | Name im Overlay / im Code | Kopien | Funktion | Verwendung |
|---|---|---|---|---|
| Off | None | 1 | Nur der Pinsel selbst malt. | Frei gestaltete Karten, Kampagnenkarten, Details, die sich nicht wiederholen sollen. |
| Mirror X | Mirror X (MirrorX) | 2 | Spiegelt links ↔ rechts an der senkrechten Achse durch das Zentrum. | Zwei Spieler oder Teams, die sich West gegen Ost gegenüberstehen. |
| Mirror Z | Mirror Z (MirrorZ) | 2 | Spiegelt oben ↔ unten an der waagerechten Achse. | Zwei Spieler oder Teams, die sich Nord gegen Süd gegenüberstehen. |
| Mirror XZ | Mirror XZ (MirrorXZ) | 4 | Spiegelt an beiden Achsen. | Vier Basen in den Ecken oder 2v2-Karten, auf denen jedes Team eine Seite hält. |
| Rotate 180° | Rotate 2 (Rotate2) | 2 | Punktsymmetrie um das Zentrum. | Das klassische 1v1-Layout mit Basen in gegenüberliegenden Ecken. Anders als eine Spiegelung lässt eine Drehung links und rechts für beide Spieler gleich: Ein Pfad zur Linken des einen Spielers liegt auch zur Linken des anderen. |
| Rotate 90° | Rotate 4 (Rotate4) | 4 | Rotationssymmetrie in Vierteldrehungen. Needs a square map. | Das klassische Jeder-gegen-jeden mit vier Spielern. |
| Diagonal | Mirror Diagonal (MirrorDiagonal) | 2 | Spiegelt an der Diagonalen (x ↔ z). Needs a square map. | 1v1-Karten mit Basen in der oberen linken und der unteren rechten Ecke. |
Centre ist das Symmetriezentrum in normierten Kartenkoordinaten: (0.5, 0.5) ist die Kartenmitte und der Standardwert. Reset setzt es dorthin zurück. Wenn du das Zentrum verschiebst, können einige Kopien außerhalb der Karte landen und werden dann übersprungen; das Panel zeigt eine Warnung.
Rotate 90° und Diagonal vertauschen die X- und die Z-Achse. Auf einer nicht quadratischen Karte landen Kopien außerhalb der Karte oder fallen ungleichmäßig aus. Das Panel Symmetry, das Overlay und das Status-Panel warnen dich. Ändere die Kartengröße auf ein quadratisches Format oder wähle einen anderen Modus.
Wie sich Kopien verhalten
- Kopien, die näher als eine halbe Zelle (oder ein Zehntel des Pinselradius, je nachdem, was größer ist) an einer anderen Kopie liegen, werden zusammengeführt. Malen auf einer Symmetrieachse wendet die Wirkung daher nie doppelt an.
- Platzierte Bäume, Dekorobjekte, Startpositionen und Marker werden mit ihrer Kopie gedreht. Gespiegelte Kopien erhalten gespiegelte Richtungen.
- Gedrehte und gespiegelte Pinsel und eigene Stempel drehen sich mit ihrer Kopie (siehe Konvention für Drehungen).
- Start Locations: Ist Symmetry Assigns Next Players eingeschaltet (Standard), platziert ein Klick die Startposition des ausgewählten Spielers, und seine Kopien platzieren die der folgenden Spieler. Mit Rotate 90° und ausgewähltem Spieler 1 platziert ein Klick die Startpositionen der Spieler 1 bis 4.
- Raise / Lower Level berechnet seine Zielstufe aus dem ursprünglichen Tupfer, sodass jede Kopie dieselbe Stufe malt.
- Fill / Replace wirkt in den Modi Replace Dominant Layer und Fill All auf die ganze Karte und läuft nur einmal. Fill Connected füllt von jeder Kopie aus.
Baue die Grundform der Karte (Klippen, Rampen, Wasser, Startpositionen) mit eingeschalteter Symmetrie und schalte sie dann für kleine Dekorationen aus, damit die Karte nicht wie kopiert und eingefügt wirkt. Mit dem Pathing-Overlay (P) prüfst du schnell, ob jede Basis dieselben Wege hat.
Rückgängig und Wiederherstellen
Jede Terrain-Bearbeitung wird im eigenen Undo-Verlauf der Karte aufgezeichnet, der mit Unitys Undo-System verknüpft ist. Ctrl+Z, Ctrl+Y und das Menü funktionieren für Terrain-Bearbeitungen genau wie für jede andere Änderung, und das Edit-Menü zeigt den Namen des Schritts an, zum Beispiel Undo Raise / Lower. Die Schaltflächen Undo und Redo in der Kopfzeile des Fensters tun dasselbe und halten Unitys Verlauf synchron. Machst du zwischendurch eine Änderung an einem anderen Objekt rückgängig, bringt das den Terrain-Verlauf nicht durcheinander.
| Aktion | Undo-Schritt |
|---|---|
| Ein Pinselstrich (vom Drücken bis zum Loslassen) oder ein Klick | Ein Schritt, benannt nach dem Werkzeug, zum Beispiel Paint Layer. Alle Symmetriekopien gehören zum selben Schritt. |
| Ziehen eines Dekor-Handles | Ein Schritt (Move Decor, Rotate Decor, Scale Decor). |
| Schaltflächen im Panel Selection (Delete, Duplicate, Lock …) | Ein Schritt pro Klick. |
| Generate, Heightmap-Import, Auto Paint Whole Map, Capture From Scene | Je ein Schritt (Generate Map, Import Heightmap, Auto Paint Map …). |
| Felder für Karteninfo und Umgebung | Ein Schritt, sobald du das Feld verlässt (Edit Map Info, Edit Environment). |
| Mit Esc abgebrochener Pinselstrich | Kein Schritt. Die Änderungen des Pinselstrichs werden zurückgenommen. |
| New Map, Resize, Open, Reload, Wechsel zu einem anderen Terrain | Nicht rückgängig zu machen. Diese Aktionen löschen den Verlauf (ihre Dialoge weisen darauf hin). |
Grenzen und Speicher
Jeder Schritt speichert nur die Schichten, die er geändert hat. Rasterschichten (Höhen, Klippenstufen, Wasser, Splatmaps, Details, Pathing-Overrides) werden in Kacheln von 16 × 16 Zellen rund um den bearbeiteten Bereich gespeichert. Objektschichten (Bäume, Dekor, Regionen, Startpositionen, Marker, Karteninfo, Umgebung) werden als Kopie der gesamten Liste gespeichert. Ein einzelner Pinselstrich mit einem Baumwerkzeug auf einer Karte mit sehr vielen Bäumen kostet daher Speicher im Verhältnis zur Gesamtzahl der Bäume auf der Karte. Ein kleiner Pinselstrich mit einem Höhen- oder Malwerkzeug kostet also einige Kilobyte, während das Generieren einer ganzen Karte jede Schicht der Karte speichert. Die Statusleiste und das Panel Undo History zeigen den aktuell belegten Speicher.
Im Tab Settings legst du zwei Grenzen fest. Wird eine davon überschritten, werden zuerst die ältesten Schritte verworfen; der neueste Schritt bleibt immer erhalten.
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Max Steps | 10–1000 | 100 | Anzahl der Undo-Schritte, die behalten werden. |
| Memory Limit (MB) | 32–4096 | 512 | Speicher, den der Undo-Verlauf belegen darf. |
Die Grenzen werden pro Benutzer in den Editor-Einstellungen gespeichert und gelten auch, wenn das Fenster geschlossen ist. Clear History löscht alle Undo- und Redo-Schritte des Terrains, zum Beispiel um nach einer großen Generierung Speicher freizugeben.
Rückgängig machbare Bearbeitungen aus eigenen Editor-Skripten
RTSEditSession.RunOperation zeichnet die angegebenen Schichten der gesamten Karte auf, führt deinen Code aus und legt das Ergebnis als einen einzigen Schritt im Undo-Verlauf ab. Wie bei jedem Werkzeug lässt er sich dann mit Ctrl+Z rückgängig machen.
using RTSTerrainStudio;
using RTSTerrainStudio.Editor;
using UnityEditor;
static class MyTerrainCommands
{
[MenuItem("Tools/My Project/Raise Whole Map 1 m")]
static void RaiseWholeMap()
{
// The terrain edited by the RTS Terrain Studio window and the Scene View tools.
RTSTerrain terrain = RTSEditorContext.Terrain;
if (terrain == null || terrain.Map == null) return;
RTSEditSession session = RTSEditorContext.Session;
// Records the Heights layer, runs the code and pushes one undo step named "Raise Whole Map".
session.RunOperation("Raise Whole Map", MapLayers.Heights, ctx =>
{
var heights = ctx.Map.Heights; // fine height per vertex, on top of the cliff level
for (int i = 0; i < heights.Length; i++)
heights[i] = TerrainToolUtility.ClampFineHeight(ctx.Map, heights[i] + 1f);
// Tell the terrain which layers changed so meshes, water and pathing are rebuilt.
ctx.Terrain.NotifyChangedAll(MapLayers.Heights);
});
}
}
Der In-Game-Editor verwendet denselben Typ für den Undo-Verlauf (MapUndoStack) und hat eigene Schaltflächen Undo und Redo. Die Anbindung an Unitys Ctrl+Z gibt es nur im Unity Editor.
Werkzeugreferenz
Die folgenden Abschnitte beschreiben alle Werkzeuge der acht Kategorien. Alle Werkzeuge funktionieren im Unity Editor und im In-Game-Editor gleich. Einige Konventionen gelten für alle:
- ID ist die eindeutige Kennung des Werkzeugs. Du verwendest sie im Code, zum Beispiel
RTSEditorContext.ActivateTool("height.raise"). - Eingabe: Pinselwerkzeuge malen, während du ziehst. Klickwerkzeuge wirken einmal pro Klick. Ziehwerkzeuge arbeiten mit Drücken, Ziehen und Loslassen (Rechtecke, Rechteckauswahl).
- Wiederholungen: Tupfer pro Sekunde, während du die Maustaste gedrückt hältst, ohne die Maus zu bewegen.
- Shift kehrt die Wirkung um und Ctrl+Klick nimmt einen Wert auf, sofern die Tabelle das angibt.
- Jede Einstellung ist mit Bereich und Standardwert aufgeführt. Die Einstellungen bleiben über Editorsitzungen hinweg erhalten. Reset All Tool Settings im Tab Settings stellt die Standardwerte wieder her.
Klippenstufen und Feinhöhe
Die Bodenhöhe jedes Vertex besteht aus zwei Teilen. Die Klippenstufe (0–15) wird mit den Klippenwerkzeugen gesetzt und erzeugt senkrechte Klippenwände; eine Stufe ist Cliff Level Height Meter hoch (standardmäßig 2 m). Die Feinhöhe wird mit den Höhenwerkzeugen gesetzt und fügt über der Stufe sanfte Hügel und Senken hinzu. Feinhöhen sind auf ±8 Klippenstufen begrenzt (±16 m bei Stufen von 2 m). Gerade weil beides getrennt bleibt, kannst du scharfe Klippen im Stil klassischer RTS-Spiele auf welligen Boden malen.
Eigene Werkzeuge hinzufügen
Jede Klasse, die von RTSTool (oder einer der Kategorie-Basisklassen wie HeightTool) abgeleitet ist und einen öffentlichen, parameterlosen Konstruktor hat, erscheint automatisch im Fenster, im Overlay und im In-Game-Editor. Öffentliche Felder, die mit [ToolSetting] markiert sind, erhalten Bedienelemente. Dieses Beispiel hebt oder senkt den Boden pro Klick um einen festen Wert:
using RTSTerrainStudio;
// Appears on the Height tab after the built-in tools (Order 100).
public sealed class StepTool : HeightTool
{
public override string Id => "mygame.step";
public override string DisplayName => "Step";
public override string Description => "Raises the ground under the brush by a fixed step per click.";
public override string Hint => "Shift: step down";
public override int Order => 100;
// One dab per click; keep the brush panel visible for the radius and falloff.
public override ToolInputMode InputMode => ToolInputMode.Click;
public override bool UsesBrush => true;
[ToolSetting("Step", Min = 0.05f, Max = 4f, Order = 0, Tooltip = "Height change per click in metres at full strength.")]
public float step = 0.5f;
// Called once per click and once for every symmetry copy.
public override void ApplyDab(RTSToolContext ctx, in BrushDab dab)
{
var map = ctx.Map;
// Computes the vertex weights and records the Heights layer for undo.
bool any = BeginVertexEdit(ctx, dab, out var w, out var cells);
using (w) // the weights must always be disposed
{
if (!any) return;
float sign = dab.invert ? -1f : 1f; // Shift inverts
var heights = map.Heights;
for (int z = w.rect.yMin; z < w.rect.yMax; z++)
for (int x = w.rect.xMin; x < w.rect.xMax; x++)
{
float weight = w[x, z];
if (weight <= 0f) continue;
int i = map.VertexIndex(x, z);
heights[i] = TerrainToolUtility.ClampFineHeight(map, heights[i] + sign * step * dab.strength * weight);
}
}
// Rebuilds the chunks, water and pathing of the changed cells.
EndVertexEdit(ctx, cells);
}
}
Die vollständige Werkzeug-API (Ziehwerkzeuge, Aufnehmen von Werten, Gizmos und Statustext) findest du unter Scripting. Bei Builds mit hohem Managed Stripping Level markiere dein Werkzeug mit [UnityEngine.Scripting.Preserve] (siehe Code-Stripping und eigene Werkzeuge).
Höhenwerkzeuge
Höhenwerkzeuge formen die Feinhöhe: Hügel, Täler, Straßen und natürliche Details über den Klippenstufen. Sie ändern nie die Klippenstufen, daher bleiben Klippenwände scharf. Die Statuszeile zeigt Bodenhöhe, Feinhöhe und Klippenstufe unter dem Cursor.
| Werkzeug | ID | Funktion | Eingabe und Zusatztasten |
|---|---|---|---|
| Raise / Lower | height.raise | Hebt den Boden unter dem Pinsel an; halte die Maustaste gedrückt, um Hügel immer weiter aufzubauen. | Pinsel, 30 Wiederholungen/s. Shift: absenken. |
| Smooth | height.smooth | Glättet Unebenheiten und scharfe Kanten des Bodens, ohne Klippenwände zu verwischen. | Pinsel, 20 Wiederholungen/s. |
| Flatten | height.flatten | Ebnet den Boden auf eine einheitliche Höhe ein, ideal für Bauplätze und Straßen. | Pinsel, 20 Wiederholungen/s. Ctrl+Klick: Höhe aufnehmen. |
| Set Height | height.set | Setzt den Boden auf eine exakte Höhe; bei voller Stärke erreicht die Pinselmitte sie mit einem einzigen Tupfer. | Pinsel, 20 Wiederholungen/s. Ctrl+Klick: Höhe aufnehmen. |
| Noise | height.noise | Fügt mit Seed-gesteuertem prozeduralem Rauschen natürlich wirkende Unebenheiten, Hügel oder Grate hinzu. | Pinsel, 10 Wiederholungen/s. Shift: Rauschen abziehen. |
| Terrace | height.terrace | Verwandelt Hänge in flache Terrassen mit abgerundeten Kanten, wie bei Reisfeldern oder geschichtetem Fels. | Pinsel, 15 Wiederholungen/s. |
| Erode | height.erode | Simuliert Material, das steile Hänge hinabrutscht, und verleiht Hügeln ein natürliches, verwittertes Aussehen. | Pinsel, 15 Wiederholungen/s. |
Einstellungen von Raise / Lower
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Speed | 0.01–2 | 0.15 | Höhenänderung pro Tupfer in Metern bei voller Pinselstärke. |
Einstellungen von Smooth
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Filter Radius | 1–4 | 1 | Größe der Nachbarschaft in Vertices, über die gemittelt wird. Größere Werte entfernen größere Strukturen. |
| Iterations | 1–8 | 2 | Glättungsdurchläufe pro Tupfer. |
Einstellungen von Flatten
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Target | Stroke Start, Fixed Height | Stroke Start | Stroke Start verwendet die Höhe unter dem Cursor zu Beginn des Pinselstrichs. Fixed Height verwendet Target Height. |
| Target Height | −32–32 m | 0 | Feste Feinhöhe relativ zur Klippenstufe. Ctrl+Klick nimmt sie auf. Werte jenseits der Feinhöhengrenze der Karte (±8 Klippenstufen, ±16 m bei Stufen von 2 m) werden beim Anwenden begrenzt. |
| Mode | Flatten, Raise Only, Lower Only | Flatten | In beide Richtungen einebnen, nur Boden unterhalb des Zielwerts auffüllen oder nur Boden oberhalb davon abtragen. |
Einstellungen von Set Height
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Height | −32–64 m | 0 | Zu setzende Höhe; wie sie interpretiert wird, legt Height Space fest. Ctrl+Klick nimmt sie auf. Werte jenseits der Feinhöhengrenze der Karte (±8 Klippenstufen, ±16 m bei Stufen von 2 m) werden beim Anwenden begrenzt. |
| Height Space | Fine Height, Ground Height | Fine Height | Feinhöhe relativ zur Klippenstufe oder absolute Bodenhöhe. |
Einstellungen von Noise
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Noise Type | Fbm, Ridged, Billow | Fbm | Fbm: sanft gewellte Hügel. Ridged: scharfe Grate. Billow: rundliche Buckel. |
| Scale | 2–256 m | 24 | Größe der größten Strukturen. |
| Amplitude | 0.01–4 m | 0.25 | Pro Tupfer hinzugefügte Höhe bei voller Stärke. |
| Octaves | 1–8 | 4 | Anzahl der Detailebenen. Mehr Oktaven fügen feinere Details hinzu. |
| Roughness | 0.2–0.8 | 0.5 | Amplitudenverhältnis zwischen den Oktaven. Höhere Werte ergeben raueren Boden. |
| Seed | 0–9999 | 1 | Ändert das Muster. Derselbe Seed erzeugt immer dasselbe Muster. |
Einstellungen von Terrace
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Step Height | 0.1–8 m | 1 | Höhe einer Terrassenstufe. |
| Sharpness | 0–1 | 0.7 | 0 = weiche, abgerundete Terrassenstufen. 1 = scharfe, steile Absätze. |
| Offset | −4–4 m | 0 | Verschiebt alle Terrassenstufen nach oben oder unten. |
Einstellungen von Erode
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Talus Angle | 5–70° | 30 | Hänge, die steiler als dieser Winkel sind, erodieren. |
| Erosion Strength | 0.05–1 | 0.5 | Anteil des überschüssigen Materials, der pro Durchlauf bewegt wird. |
| Iterations | 1–50 | 8 | Erosionsdurchläufe pro Tupfer. |
Für einen Bauplatz verwendest du Flatten mit Target = Stroke Start: Beginne den Pinselstrich an der Stelle, deren Höhe du übernehmen willst, und male dann über die Fläche. Für eine Straße auf exakter Höhe nimmst du mit Ctrl+Klick die Höhe eines Punkts auf, stellst Target auf Fixed Height um und malst.
Klippenwerkzeuge
Klippenwerkzeuge ändern die Klippenstufe von Vertices (0–15) und das Aussehen der Klippenwände zwischen den Stufen. Der Pinsel rastet an Vertices ein und wirkt als harte Auswahl: Du malst immer ganze Vertices, nie halbe Stufen. Die Statuszeile zeigt die Stufe unter dem Cursor und die Stufe, die du gleich malst, zum Beispiel Level 2 → 3.
| Werkzeug | ID | Funktion | Eingabe und Zusatztasten |
|---|---|---|---|
| Raise / Lower Level | cliff.raise | Hebt den Boden um eine Klippenstufe an; ziehe, um das Plateau zu erweitern, auf dem du begonnen hast. | Pinsel. Shift: um eine Stufe absenken. |
| Set Level | cliff.set | Malt eine exakte Klippenstufe, nützlich für große Plateaus, Becken und schnelle Rohentwürfe. | Pinsel. Ctrl+Klick: Stufe aufnehmen. |
| Ramp | cliff.ramp | Verwandelt Klippenkanten in begehbare Hänge; male quer über eine Klippe mit einer Stufe Höhenunterschied, sodass alle vier Ecken einer Zelle zur Rampe gehören. | Pinsel. Shift: Rampe entfernen. |
| Cliff Type | cliff.type | Ändert das Material der Klippenwände, zum Beispiel Felsklippen neben grasbewachsenen Klippen. | Pinsel. Ctrl+Klick: Klippentyp aufnehmen. |
| Clean Cliffs | cliff.smoothEdges | Entfernt isolierte Säulen und Gruben aus einzelnen Vertices, damit die Klippenumrisse sauber aussehen. | Pinsel. |
Einstellungen von Raise / Lower Level
Die Zielstufe ergibt sich aus dem Vertex unter dem Cursor zu Beginn des Pinselstrichs: eine Stufe höher (oder mit Shift eine Stufe tiefer). Beim Ziehen wird genau diese Stufe gemalt, sodass du ein Plateau erweitern kannst, ohne Stufen übereinanderzustapeln. Das Werkzeug hebt Vertices nur an (bzw. senkt sie nur ab).
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Break Ramps | an/aus | an | Entfernt das Rampen-Flag von Vertices, deren Stufe sich ändert. |
| Paint Cliff Edges | an/aus | an | Malt am Ende des Pinselstrichs die Kantenschicht des Klippentyps entlang neuer Klippenkanten (siehe Automatische Klippenkanten). |
| Edge Width | 0.25–4 Zellen | 1 | Breite des gemalten Rands, gemessen von der Klippenwand aus. |
| Edge Strength | 0.1–1 | 0.9 | Wie stark die Kantenschicht den Boden direkt an der Wand bedeckt. |
| Edge Sides | Both / Top / Bottom | Both | Malt den Plateaurand, den Klippenfuß oder beides. |
| Clean Up Old Edges | an/aus | an | Entfernt die Kantenschicht dort, wo nach dem Pinselstrich keine Kante mehr existiert (verschobene Klippen, neue Rampen). |
Einstellungen von Set Level
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Level | 0–15 (Palette) | 2 | Zu malende Klippenstufe. |
| Break Ramps | an/aus | an | Entfernt das Rampen-Flag von Vertices, deren Stufe sich ändert. |
| Kanteneinstellungen | wie oben | Paint Cliff Edges, Edge Width, Edge Strength, Edge Sides und Clean Up Old Edges funktionieren wie bei Raise / Lower Level. Ramp und Clean Cliffs haben dieselben fünf Einstellungen. | |
Ramp
Ramp hat keine Einstellungen. Eine Zelle wird zur Rampe, wenn alle vier Eckvertices mit dem Rampen-Flag markiert sind und die Zelle einen Höhenunterschied von genau einer Stufe überbrückt. Die Statuszeile hilft dabei: Sie zählt die gültigen Rampenzellen unter dem Pinsel, weist auf markierte Zellen ohne Stufenunterschied hin und warnt, wenn eine Rampe mehr als eine Stufe überbrückt (sie würde dann gestreckt).
Einstellungen von Cliff Type
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Cliff Type | 0–3 (Palette) | 0 | Zu malende Klippenschicht des Tilesets. |
Einstellungen von Clean Cliffs
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Strictness | 5–8 | 8 | Wie viele der 8 Nachbarn die andere Stufe haben müssen, bevor ein Vertex sie übernimmt. Bei 8 werden nur vollständig isolierte Vertices entfernt; niedrigere Werte runden auch gezackte Ecken ab. Vertices am Kartenrand haben weniger Nachbarn und brauchen davon denselben Anteil. |
| Break Ramps | an/aus | an | Entfernt das Rampen-Flag von Vertices, deren Stufe sich ändert. |
Automatische Klippenkanten
In klassischen RTS-Karteneditoren verändert das Anheben einer Klippe auch den Boden daneben. Die Klippenwerkzeuge machen es genauso: Am Ende eines Pinselstrichs wird der Boden entlang jeder neuen Klippenkante mit der Kanten-Bodenschicht des Klippentyps bemalt, zum Beispiel mit einem steinigen Rand aus „Rocky Dirt“. Der Rand folgt der abgerundeten Klippenkontur, läuft über die mit Edge Width eingestellte Breite aus und hat einen leicht unregelmäßigen Umriss. So wirkt er natürlich statt wie aus Zellen zusammengesetzt.
- Wähle das Tileset aus und öffne den Bereich Cliff Layers. Wähle auf jedem Klippen-Panel unter Edge Layer eine Schicht aus (die Demo-Tilesets verwenden Rocky Dirt).
- Wähle Raise / Lower Level, Set Level, Ramp oder Clean Cliffs und lasse Paint Cliff Edges eingeschaltet.
- Male Klippen wie gewohnt. Der Rand erscheint, sobald du die Maustaste loslässt.
- Ein Undo-Schritt: Stufenänderung und Kantenbemalung werden gemeinsam rückgängig gemacht.
- Symmetrie: Alle Symmetriekopien erhalten ihre Kanten im selben Durchgang.
- Rampen: Rampenzellen erhalten nie einen Rand. Wenn du eine Rampe über eine Kante malst, wird der Rand dort entfernt, sofern Clean Up Old Edges eingeschaltet ist.
- Verschobene Klippen: Ist Clean Up Old Edges eingeschaltet, wird der Rand einer verschwundenen Kante (abgesenktes oder erweitertes Plateau) wieder entfernt. Dabei wird die Kantenschicht auch dann entfernt, wenn du sie dort von Hand gemalt hast.
- Optional: Schalte Paint Cliff Edges für ein Werkzeug aus oder setze für das ganze Tileset die Einstellung Edge Layer des Klippentyps auf None. Ist die Option eingeschaltet, aber keine Kantenschicht festgelegt, weist die Statuszeile darauf hin.
Wenn du Klippenstufen im Code selbst änderst (zum Beispiel in einem eigenen Generator), rufe danach CliffEdgePainter.PaintEdges(map, tileset, cellRect, CliffEdgeOptions.Default) und anschließend terrain.NotifyChanged(MapLayers.Splat, returnedRect) auf.
Ein typischer Arbeitsablauf: Lege Plateaus grob mit Set Level an, verfeinere ihre Kanten mit Raise / Lower Level, fahre mit Clean Cliffs über die Umrisse und lege dann mit Ramp Rampen an, wo Einheiten hinauflaufen sollen. Prüfe das Ergebnis mit dem Pathing-Overlay (P).
Malwerkzeuge
Malwerkzeuge tragen die Bodenschichten des Tilesets auf (bis zu 16). Jedes Splat-Texel speichert die Gewichtungen aller Schichten, die zusammen immer 100 % ergeben. Fehlt die ausgewählte Schicht im Tileset oder hat sie keine Textur, weist die Statuszeile darauf hin, statt stillschweigend mit einem Ersatz zu malen.
| Werkzeug | ID | Funktion | Eingabe und Zusatztasten |
|---|---|---|---|
| Paint Layer | paint.layer | Malt eine Bodenschicht mit weichen Pinselrändern. | Pinsel, 10 Wiederholungen/s. Shift: Schicht entfernen. Ctrl+Klick: Schicht unter dem Cursor aufnehmen. |
| Tile Paint | paint.tile | Malt ganze Zellen mit einer Bodenschicht, wie in klassischen RTS-Karteneditoren. Jede Zelle, deren Mitte innerhalb des Pinselumrisses liegt, wird zu 100 % bemalt. | Pinsel, rastet an Zellmitten ein. Shift: Schicht aus Zellen entfernen. Ctrl+Klick: aufnehmen. |
| Fill / Replace | paint.fill | Ersetzt mit einem Klick eine Bodenschicht überall, füllt eine zusammenhängende Fläche oder die ganze Karte. | Klick. Ctrl+Klick: aufnehmen. |
| Smooth Paint | paint.smooth | Zeichnet Übergänge zwischen Bodenschichten unter dem Pinsel weich und sorgt so für sanftere Überblendungen. | Pinsel, 15 Wiederholungen/s. |
| Auto Paint | paint.auto | Malt Bodenschichten anhand von Höhe, Neigung, Klippen und Wasser mithilfe eines Auto-Paint-Profils. | Pinsel, 8 Wiederholungen/s. Shift: Basisschicht des Profils malen. |
Einstellungen von Paint Layer
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Layer | Palette | 0 | Zu malende Bodenschicht des Tilesets. |
| Opacity | 0.05–1 | 1 | Höchste Deckung, die die Schicht erreichen kann. Niedrigere Werte überblenden die Schicht mit dem darunterliegenden Boden. |
Einstellungen von Tile Paint
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Layer | Palette | 0 | Zu malende Bodenschicht des Tilesets. |
| Soft Border | an/aus | aus | Überblendet ein Texel rund um die bemalten Zellen für etwas weichere Kanten. |
Einstellungen von Fill / Replace
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Layer | Palette | 0 | Bodenschicht, mit der die Fläche gefüllt wird. |
| Mode | Replace Dominant Layer, Fill Connected, Fill All | Fill Connected | Replace Dominant Layer: jedes Texel der Karte, dessen dominante Schicht die angeklickte Schicht ist. Fill Connected: Flächenfüllung der zusammenhängenden Texel, deren dominante Schicht die angeklickte ist. Fill All: die ganze Karte. |
| Preserve Blends | an/aus | an | Tauscht nur die angeklickte Schicht gegen die neue aus und erhält Überblendungen mit anderen Schichten (Replace und Flood Fill). |
Jeder Klick ist ein Undo-Schritt, der nur den tatsächlich geänderten Bereich aufzeichnet.
Einstellungen von Smooth Paint
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Blur Radius | 1–3 | 1 | Größe des Weichzeichnungskernels in Splat-Texeln. Größere Werte glätten schneller. |
Einstellungen von Auto Paint
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Profile | keine | Das Asset vom Typ Auto Paint Profile, mit dem der Pinsel malt. Leer = integrierte Regeln, die anhand der Schichtnamen des Tilesets zugeordnet werden. Siehe Auto Paint. |
Der Tab Paint zeigt außerdem das Panel Auto Paint Whole Map, das die gesamte Karte in einem Schritt anhand des Profils neu bemalt.
Vegetationswerkzeuge
Vegetationswerkzeuge malen Gras und Kleinpflanzen (Details) und platzieren Bäume aus der Vegetation Library des Terrains. Hat das Terrain keine, bietet die Werkzeugseite die Schaltfläche Create Vegetation Library an. Wähle Prototypen im Raster aus; ist keiner ausgewählt, wird der erste Prototyp der Bibliothek verwendet.
| Werkzeug | ID | Funktion | Eingabe und Zusatztasten |
|---|---|---|---|
| Paint Details | veg.details | Malt Gras, Blumen und andere Kleinpflanzen nach Dichte. | Pinsel, 10 Wiederholungen/s. Shift: ausgewählte Details entfernen. |
| Erase Details | veg.detailsErase | Entfernt Gras und andere Detailvegetation unter dem Pinsel. | Pinsel, 10 Wiederholungen/s. |
| Place Trees | veg.trees | Verteilt Bäume mit dem Pinsel oder platziert einzelne Bäume per Klick. | Scatter: Pinsel, Shift entfernt Bäume. Single: Klick, Shift+Klick entfernt den nächstgelegenen Baum. |
| Erase Trees | veg.treesErase | Entfernt Bäume unter dem Pinsel. Bei geringerer Stärke werden nur Bäume nahe der Pinselmitte entfernt. | Pinsel. |
Einstellungen von Paint Details
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Details | Prototyp-Raster | keine | Zu malende Detail-Prototypen. |
| Target Density | 0–1 | 0.6 | Dichte, auf die der Pinsel hinarbeitet (1 = maximale Dichte des Prototyps). |
| Mode | Raise, Set | Raise | Raise erhöht nur die Dichte. Set dünnt zusätzlich dichtere Bereiche auf den Zielwert aus. |
Einstellungen von Erase Details
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Only Selected | an/aus | aus | Entfernt nur die ausgewählten Detail-Prototypen statt aller Details. |
| Details | Prototyp-Raster | keine | Zu entfernende Detail-Prototypen, wenn Only Selected eingeschaltet ist. |
Einstellungen von Place Trees
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Trees | Prototyp-Raster | keine | Baum-Prototypen, die zufällig platziert werden. |
| Mode | Scatter, Single | Scatter | Scatter malt Bäume mit dem Pinsel. Single platziert einen Baum pro Klick und zeigt eine halbtransparente Vorschau. |
| Density | 0.05–5 | 0.6 | Bäume pro 100 m², die jeder Tupfer bei voller Stärke erzeugt. Der Mindestabstand der Prototypen gilt weiterhin. |
| Max Slope | 0–90° | 35 | Auf steileren Hängen werden keine Bäume verteilt. |
| Avoid Water | an/aus | an | Verteilt keine Bäume unter Wasser. |
Einstellungen von Erase Trees
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Only Selected | an/aus | aus | Entfernt nur Bäume der ausgewählten Prototypen. |
| Trees | Prototyp-Raster | keine | Zu entfernende Baum-Prototypen, wenn Only Selected eingeschaltet ist. |
Dekorwerkzeuge
Mit den Dekorwerkzeugen platzierst, löschst und bearbeitest du Props wie Felsen, Ruinen und Zäune aus der Decor Library des Terrains. Hat das Terrain keine, bietet die Werkzeugseite die Schaltfläche Create Decor Library an. Das Prototyp-Raster hat ein Suchfeld und Kategoriefilter.
| Werkzeug | ID | Funktion | Eingabe und Zusatztasten |
|---|---|---|---|
| Place Decor | decor.place | Platziert Props, Felsen und andere Dekorobjekte einzeln oder mit dem Pinsel verstreut. | Single: Klick, Shift+Klick entfernt das nächstgelegene Objekt. Scatter: Pinsel, Shift entfernt Dekor unter dem Pinsel. |
| Erase Decor | decor.erase | Entfernt Dekorobjekte unter dem Pinsel. Gesperrte Objekte bleiben erhalten. Bei geringerer Stärke wird nur nahe der Pinselmitte gelöscht. | Pinsel. |
| Select Decor | decor.select | Wählt Dekorobjekte per Klick oder durch Aufziehen eines Rechtecks aus, um sie zu verschieben, zu drehen, zu skalieren oder zu löschen. | Ziehen. Klick: auswählen. Ziehen: Rechteckauswahl. Shift: hinzufügen / umschalten. |
Einstellungen von Place Decor
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Mode | Single, Scatter | Single | Single platziert pro Klick ein Objekt mit halbtransparenter Vorschau. Scatter malt Objekte mit dem Pinsel. |
| Decor | Prototyp-Raster | keine | Dekor-Prototypen, die zufällig platziert werden. Ist keiner ausgewählt, wird der erste der Bibliothek verwendet. |
| Density | 0.05–5 | 0.4 | Scatter: Objekte pro 100 m², die jeder Tupfer bei voller Stärke spawnt. |
| Use Prototype Defaults | an/aus | an | Übernimmt zufälligen Drehwinkel, Ausrichtung am Boden und Einrasten am Raster vom Prototyp. |
| Random Yaw | an/aus | an | Zufällige Drehung um die Hochachse (wenn die Standardwerte des Prototyps nicht verwendet werden). |
| Yaw | 0–360° | 0 | Drehung um die Hochachse, wenn Random Yaw ausgeschaltet ist. |
| Scale | 0.1–5 | 1 | Faktor, der auf die zufällige Skalierung des Prototyps angewendet wird. |
| Align To Ground | 0–1 | 0 | 0 = aufrecht, 1 = folgt der Neigung des Bodens (wenn die Standardwerte des Prototyps nicht verwendet werden). |
| Snap To Grid | an/aus | aus | Platziert Objekte in den Zellmitten. |
| Random Variant | an/aus | an | Verwendet eine zufällige Prefab-Variante des Prototyps. |
| Max Slope | 0–90° | 90 | Scatter: Lässt Hänge aus, die steiler als dieser Wert sind. 90 = keine Begrenzung. |
| Avoid Water | an/aus | aus | Scatter: Platziert keine Objekte unter Wasser. |
Einstellungen von Erase Decor
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Only Selected | an/aus | aus | Entfernt nur Objekte der ausgewählten Prototypen. |
| Decor | Prototyp-Raster | keine | Dekor-Prototypen, die entfernt werden, wenn Only Selected eingeschaltet ist. |
Einstellungen von Select Decor
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Pick Radius | 0.25–10 m | 1 | Mindestabstand, innerhalb dessen ein Klick ein Objekt noch auswählt (größere Objekte werden innerhalb ihrer Grundfläche ausgewählt). |
| Select Locked | an/aus | aus | Erlaubt das Auswählen gesperrter Objekte, etwa um sie zu entsperren. |
Panel Selection
Während Select Decor aktiv ist, zeigt der Tab Decor das Panel Selection mit der Anzahl der ausgewählten Objekte. Jede Bearbeitungsschaltfläche (Delete, Duplicate, Snap to Ground, Rotate 90°, Lock, Unlock) erzeugt genau einen Undo-Schritt. Select All und Clear ändern nur die Auswahl.
| Schaltfläche | Aktion |
|---|---|
| Select All | Wählt alle Dekorobjekte der Karte aus (gesperrte Objekte werden übersprungen). |
| Clear | Hebt die Auswahl auf. |
| Delete | Löscht die ausgewählten Objekte. |
| Duplicate | Dupliziert die Auswahl, diagonal um eine Zelle versetzt (+X und +Z). Die Kopien werden zur neuen Auswahl und sind entsperrt. |
| Snap to Ground | Setzt die ausgewählten Objekte wieder auf den Boden. |
| Rotate 90° | Dreht die Auswahl um 90° im Uhrzeigersinn um ihren Mittelpunkt. |
| Lock / Unlock | Gesperrte Objekte können von den Werkzeugen nicht mehr ausgewählt oder gelöscht werden. Schalte Select Locked ein, um sie auszuwählen und zu entsperren. Auch wenn sie auf diese Weise ausgewählt sind, bleiben gesperrte Objekte von Delete und den Handles unberührt. |
Das Verschieben, Drehen und Skalieren mit Handles ist unter Handles der Dekorauswahl beschrieben.
Wasserwerkzeuge
Wasser wird pro Vertex gespeichert: Ein Vertex ist entweder nass – dann hat er eine Höhe für die Wasseroberfläche – oder trocken. Wie die Klippenwerkzeuge arbeiten die Wasserwerkzeuge mit einer harten Auswahl. Das Panel Water at Cursor zeigt Wasserstand und Wassertiefe unter dem Mauszeiger und ob die Stelle als Flachwasser (begehbar) oder Tiefwasser gilt, gemessen an der Flachwassergrenze des Tilesets.
| Werkzeug | ID | Funktion | Eingabe und Zusatztasten |
|---|---|---|---|
| Paint Water | water.paint | Malt Seen und Flüsse auf der Zielhöhe; Boden oberhalb der Wasseroberfläche bleibt trocken. | Pinsel. Shift: Wasser löschen. Ctrl+Klick: Höhe aufnehmen. |
| Erase Water | water.erase | Entfernt Wasser von den Vertices unter dem Pinsel. | Pinsel. |
| Flood Fill | water.flood | Füllt das Becken unter dem Cursor mit einem Klick bis zur Zielhöhe mit Wasser. | Klick. Shift+Klick: zusammenhängendes Wasser ablassen. Ctrl+Klick: Höhe aufnehmen. |
Einstellungen der Wasserhöhe (Paint Water und Flood Fill)
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Water Height | Cliff Level Plus Offset, Absolute Height, Picked Height | Cliff Level Plus Offset | Legt fest, wie die Höhe der Wasseroberfläche bestimmt wird. |
| Level | 0–15 (Palette) | 1 | Klippenstufe, auf der die Wasseroberfläche basiert (Modus Cliff Level Plus Offset). |
| Offset | −8–16 m | 0.6 Stufen | Meter über der Klippenstufe. Solange du den Wert nicht änderst, ergibt er sich automatisch aus 0.6 × Stufenhöhe der Karte (1.2 m bei 2 m hohen Stufen). |
| Absolute Height | −16–64 m | 3.2 | Höhe der Wasseroberfläche in kartenlokalen Metern (Modus Absolute Height). |
| Picked Height | −16–64 m | 3.2 | Mit Ctrl+Klick aufgenommene Höhe (Modus Picked Height). Beim Aufnehmen wird die Höhe der Wasseroberfläche übernommen, wenn der Punkt nass ist, sonst die Bodenhöhe; außerdem schaltet der Modus auf Picked Height um. |
Einstellungen von Paint Water
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Force | an/aus | aus | Markiert auch Vertices, deren Boden über der Wasseroberfläche liegt, etwa für Wasserfälle oder Sümpfe. |
Einstellungen von Flood Fill
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Max Vertices | 100–1 000 000 | 20 000 | Sicherheitsgrenze: Ein Füll- oder Ablassvorgang, der mehr Vertices erfassen würde, wird ohne Änderungen abgebrochen. |
Der Standardwert (Stufe 1 + 0.6 Stufen) füllt Seen, die eine Stufe unter die Grundstufe 2 einer neuen Karte gegraben wurden. Grabe mit Raise / Lower Level und Shift ein Becken und klicke dann mit Flood Fill hinein.
Pathing-Werkzeuge
Die Pathing-Karte (begehbar, bebaubar, befliegbar, Wasser) wird automatisch aus Neigung, Klippen, Wasser, Bodenschichten, Bäumen und Dekor abgeleitet. Die Pathing-Werkzeuge malen manuelle Overrides darüber und verwenden dabei eine harte Auswahl in Pathing-Zellen. Schalte das Overlay im Panel Pathing Overlay oder mit P ein, um das Ergebnis zu sehen.
| Werkzeug | ID | Funktion | Eingabe und Zusatztasten |
|---|---|---|---|
| Paint Pathing | pathing.paint | Malt unsichtbare Pathing-Regeln wie Blocker, Bauverbotszonen oder erzwungen begehbare Bereiche. | Pinsel. Shift: ausgewählten Override entfernen. |
| Clear Pathing | pathing.clear | Entfernt alle gemalten Pathing-Overrides, sodass wieder das automatische Pathing gilt. | Pinsel. |
Einstellungen von Paint Pathing
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Override | siehe unten | Blocker | Welcher Pathing-Override gemalt wird. |
| Override | Wirkung |
|---|---|
| Blocker | Nicht begehbar und nicht bebaubar: eine unsichtbare Wand, typisch für Kartenränder und dekorative Bereiche. |
| Unwalkable | Bodeneinheiten können hier nicht laufen. |
| Unbuildable | Hier können keine Gebäude platziert werden, etwa um Wege und Expansionen freizuhalten. |
| Unflyable | Lufteinheiten können hier nicht fliegen. |
| Force Walkable | Bodeneinheiten können hier laufen, auch wenn Neigung, Wasser oder Grundflächen es blockieren würden. |
| Force Buildable | Gebäude können hier platziert werden, auch wenn die abgeleiteten Regeln es verbieten würden. |
Farben des Pathing-Overlays
| Overlay | Farben |
|---|---|
| Walkability | Grün = begehbar, Rot = blockiert, Dunkelrot = Klippe, Orange = zu steil, Blau = Tiefwasser, Cyan = Flachwasser. |
| Buildability | Grün = bebaubar, Gelb = begehbar, aber nicht bebaubar, Rot / Dunkelrot = nicht begehbar. |
| Flyability | Hellblau = befliegbar, Magenta = nicht befliegbar. |
| Water | Cyan = Flachwasser, Blau = Tiefwasser. |
| Combined | Alles auf einmal, wie die Pathing-Ansicht klassischer RTS-Karteneditoren. |
Die Regeln für den automatischen Teil (maximale Neigungen, Klippenabstand usw.) stellst du in der Komponente RTS Terrain ein; siehe Pathing Rules.
Marker-Werkzeuge
Marker-Werkzeuge ergänzen die Karte um Gameplay-Informationen: wo die Spieler starten, benannte Regionen für Trigger und benannte Punkte wie Kamerapositionen. Marker werden in der Scene View gezeichnet, solange ein Marker-Werkzeug aktiv oder Show Markers eingeschaltet ist.
| Werkzeug | ID | Funktion | Eingabe und Zusatztasten |
|---|---|---|---|
| Start Locations | markers.start | Platziert den Spawn-Punkt eines Spielers. Klicke erneut, um ihn zu verschieben. | Klick. Shift+Klick: nächstgelegene Startposition entfernen. |
| Regions | markers.region | Ziehe Rechtecke auf, um benannte Regionen für die Spiellogik anzulegen. | Ziehen. Shift+Klick: Region unter dem Cursor löschen. |
| Markers | markers.marker | Platziert benannte Punkte mit einer Richtung, etwa für Kameras oder Wegpunkte. | Klick. Shift+Klick: nächstgelegenen Marker entfernen. |
Einstellungen von Start Locations
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Player | Player 1–16 (Palette) | Player 1 (Red) | Spieler, dessen Startposition platziert wird. Jeder Spieler hat genau eine Startposition. |
| Yaw | 0–360° | 0 | Blickrichtung der Startposition. |
| Symmetry Assigns Next Players | an/aus | an | Bei aktiver Symmetrie platzieren die Symmetriekopien die Startpositionen der folgenden Spieler. Ist die Option ausgeschaltet, platziert oder verschiebt nur der ursprüngliche Klick die Startposition. |
Einstellungen von Regions
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Color | Farbe | durchscheinendes Blau | Farbe neuer Regionen. |
| Snap To Cells | an/aus | an | Richtet die Grenzen von Regionen am Zellraster aus. |
Neue Regionen erhalten automatisch einen eindeutigen Namen.
Einstellungen von Markers
| Einstellung | Bereich | Standard | Beschreibung |
|---|---|---|---|
| Name | Text | Marker | Name neuer Marker. Ist der Name bereits vergeben, wird eine Nummer angehängt. |
| Tag | Text | leer | Freitext, über den dein Spielcode Marker findet, zum Beispiel camera oder waypoint. |
| Yaw | 0–360° | 0 | Blickrichtung neuer Marker. |
Auto Paint
Auto Paint malt die Bodenschichten anhand der Form des Terrains: Gras auf flachem Boden, Fels an steilen Hängen und entlang von Klippen, Sand am Ufer und durch Rauschen aufgelockerte Erdflecken. So lässt du eine frisch modellierte oder generierte Karte am schnellsten fertig aussehen. Anschließend kannst du von Hand nacharbeiten.
Es gibt drei Möglichkeiten, Auto Paint zu nutzen:
- das Pinselwerkzeug Auto Paint im Tab Paint: Es malt die Regeln innerhalb des Pinsels und überblendet sie entsprechend Pinselgewichtung und Stärke mit der vorhandenen Texturierung. Mit Shift malt es stattdessen die Basisschicht und entfernt damit, was die Regeln gemalt haben;
- das Panel Auto Paint Whole Map im Tab Paint: Es texturiert jedes Bodentexel der Karte in einem einzigen Undo-Schritt neu. Es hat ein Feld Profile und eine Schaltfläche Create Profile;
- die Schaltfläche Apply To Map im Inspector des Profils sowie die Paint-Phase des Generators.
Ohne Profil werden eingebaute Standardregeln verwendet. Sie wählen ihre Schichten anhand der Namen der Bodenschichten im Tileset.
Auto-Paint-Profile
- Klicke im Tab Paint im Panel Auto Paint Whole Map auf Create Profile. Dadurch wird ein Profil mit den Standardregeln für das Tileset des Terrains erstellt und zugewiesen. Alternativ verwendest du .
- Wähle das Profil aus. Weise im Inspector ein Preview Tileset zu, damit die Regel-Panels Schichtnamen und Vorschaubilder anzeigen. Es dient nur der Anzeige; das Profil funktioniert mit jedem Tileset.
- Bearbeite die Regeln und klicke dann auf Apply To Map, um das ausgewählte oder bearbeitete Terrain neu zu texturieren. Gibt es mehrere Terrains und ist keines ausgewählt, wählst du eines in einem Menü aus.
- Klicke auf Use In Auto Paint Tool, damit das Pinselwerkzeug mit diesem Profil malt.
| Profileinstellung | Standard | Beschreibung |
|---|---|---|
| Base Layer | 0 | Schicht, die jedes Texel bedeckt, bevor die Regeln angewendet werden. |
| Start From Existing | aus | Startet mit den aktuellen Splat-Gewichtungen statt mit der Basisschicht. So bleibt von Hand Gemaltes erhalten, wo keine Regel zutrifft. |
| Rules | – | Werden von oben nach unten angewendet; spätere Regeln übermalen frühere. Jedes Regel-Panel hat einen Schalter zum Aktivieren, einen Namen, die zugehörige Schicht, ▲ / ▼ zum Umsortieren, Schaltflächen zum Duplizieren und Löschen sowie einen Ausklappbereich Conditions. Add Rule und Reset To Defaults befinden sich unter der Liste. |
Für jedes Splat-Texel beginnt Auto Paint mit 100 % der Basisschicht (oder den vorhandenen Gewichtungen), wendet die aktivierten Regeln von oben nach unten an und normalisiert das Ergebnis. Jede aktivierte Bedingung einer Regel liefert eine Gewichtung zwischen 0 und 1, mit einem weichen Übergang an den Bereichsgrenzen. Die Gewichtungen werden multipliziert, durch Rauschen aufgelockert und mit der Stärke der Regel skaliert.
Regeleinstellungen
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Name | Rule | Anzeigename der Regel. |
| Layer | 0 | Bodenschicht (Tileset-Index), die diese Regel malt. |
| Enabled | an | Deaktivierte Regeln werden übersprungen. |
| Use Height, Min Height, Max Height | aus, 0, 20 | Malt nur dort, wo die kartenlokale Bodenhöhe (in Metern) im Bereich liegt. |
| Height Falloff | 0.5 | Weicher Übergang außerhalb des Höhenbereichs in Metern (0 = harte Kante). |
| Use Slope, Min Slope, Max Slope | aus, 0, 90 | Malt nur dort, wo die Neigung (in Grad) im Bereich liegt. |
| Slope Falloff | 5 | Weicher Übergang außerhalb des Neigungsbereichs in Grad. |
| Use Cliff Level, Min Cliff Level, Max Cliff Level | aus, 0, 15 | Malt nur auf diesen Klippenstufen (harte Kante). |
| Near Cliff Distance | 0 | Malt nur bis zu diesem Abstand (in Zellen) von einer Klippenwand (0 = aus). Läuft über die äußere Hälfte weich aus. |
| Near Water Distance | 0 | Malt nur bis zu diesem Abstand (in Zellen) vom Wasser (0 = aus). Läuft über die äußere Hälfte weich aus. |
| Noise Scale | 16 | Strukturgröße des Rauschens in Metern. |
| Noise Amount | 0 | Wie stark das Rauschen die Regel auflockert (0 = kein Rauschen, 1 = Flecken). |
| Noise Seed | 0 | Regeln mit unterschiedlichen Seeds erzeugen unterschiedliche Flecken. |
| Strength | 1 | Gesamtstärke der Regel (0–1). |
| Blend Mode | Replace | Wie die Regel mit den Regeln über ihr kombiniert wird (siehe unten). |
| Blend Mode | Wirkung |
|---|---|
| Add | Fügt die Schicht der Regel obenauf hinzu. Vorhandene Schichten werden beim Normalisieren des Ergebnisses anteilig verringert. |
| Replace | Überblendet jede Schicht entsprechend der Regelgewichtung in Richtung der Schicht der Regel. Die übliche Wahl. |
| Multiply | Multipliziert die vorhandene Gewichtung der Schicht der Regel mit der Regelmaske. Dadurch wird die Schicht dort entfernt, wo die Regel nicht zutrifft. |
Standardregeln
Die eingebauten Regeln verwenden als Basisschicht die Schicht, deren Name grass, meadow oder green enthält, und suchen die Schichten für dirt, rock und sand über ähnliche Schlüsselwörter (ohne Tileset die Schichten 0–3).
| Regel | Malt | Bedingungen |
|---|---|---|
| Dirt Patches | Erde | Rauschflecken (Skalierung 14 m), Stärke 0.55. |
| Sand Shore | Sand | Bis zu 3 Zellen vom Wasser entfernt, leicht durch Rauschen aufgelockert. |
| Rock Slopes | Fels | Hänge ab 28° Neigung (Abnahme über 8°). |
| Rock Near Cliffs | Fels | Bis zu 1.6 Zellen von einer Klippenwand entfernt, durch Rauschen aufgelockert, Stärke 0.85. |
Auto Paint per Code
using RTSTerrainStudio;
using UnityEngine;
// Repaints the whole map with a profile, for example after your own procedural edits.
public class RepaintWithProfile : MonoBehaviour
{
public RTSTerrain terrain;
public RTSAutoPaintProfile profile;
void Start()
{
if (terrain == null || terrain.Map == null || profile == null) return;
// Evaluates the rules for every splat texel inside the rectangle (here: the whole map).
RTSAutoPaint.Apply(terrain.Map, profile, terrain.Map.CellBounds);
// Let the terrain upload the new splat maps and update dependent systems (pathing ground rules).
terrain.NotifyChangedAll(MapLayers.Splat);
terrain.ProcessPendingChanges();
}
}
Die Komponente RTS Terrain
Die Komponente RTS Terrain stellt eine Karte dar. Sie baut das Terrain-Mesh in Chunks auf, rendert Boden und Klippen mit dem Tileset, hält die Pathing-Karte aktuell und ermöglicht Abfragen wie Höhe, Wasser und Pathing an einer Weltposition. Erstelle sie über oder füge sie mit zu einem beliebigen GameObject hinzu.
Der Menüeintrag erstellt ein sofort bearbeitbares Terrain: ein GameObject RTS Terrain mit den Komponenten RTS Terrain, Vegetation Renderer, Water Renderer und Decor Manager, dem Standard-Tileset und den Standardbibliotheken des Projekts sowie einer neuen, flachen .rtsmap-Datei neben der aktiven Szene (oder in Assets/Maps). Hat das Projekt noch kein Tileset, wird dir angeboten, zuerst die Demo-Inhalte zu erstellen.
Inspector
| Bereich | Inhalt |
|---|---|
| Kopfzeile | Kartenname, Weltgröße, Zellgröße und Karten-Asset, ein Badge Unsaved changes sowie Live-Statistiken: Cells, Chunks, Map Memory, Trees, Decor, Detail Layers, Regions, Start Locations und Markers. |
| Aktionen | Open RTS Terrain Studio, Edit Terrain (schaltet die Bearbeitung in der Scene View für dieses Terrain ein oder aus), Save, Save As… und Reload. |
| Warnungen | Probleme, wo möglich mit Korrektur per Klick: URP nicht aktiv, fehlende Shader, kein Tileset (mit einer Schaltfläche, die das Tileset verwendet, für das die Karte erstellt wurde), Textur-Arrays nicht gebaut oder veraltet (Build), eine Karte, die für ein anderes Tileset erstellt wurde, Vegetation oder Dekor in der Karte ohne Bibliothek, ein um X oder Z gedrehtes (Fix) oder skaliertes Terrain (Reset Scale) sowie fehlende Komponenten von Subsystemen (Add). |
| Assets | Die Felder Map, Tileset, Vegetation und Decor, jeweils mit einer Schaltfläche New, die ein neues Asset erstellt und zuweist. |
| Chunks & Geometry, Cliff Shape, Physics, Rendering | Die Terrain-Einstellungen (Tabelle unten). |
| Pathing Rules | Wie die Pathing-Karte abgeleitet wird (Tabelle unten). Sie wird nach Änderungen automatisch neu aufgebaut. |
| Default Map | Größe und Standardwerte der Karte, die erstellt wird, wenn kein Karten-Asset zugewiesen ist. |
| Rebuild Terrain | Zerstört alle Chunks, Texturen und die Pathing-Karte und erstellt sie neu; auch Vegetation, Wasser und Dekor werden neu aufgebaut. Verwende diese Schaltfläche, nachdem du Einstellungen per Code geändert hast oder wenn etwas veraltet aussieht. |
Derselbe Inspector ist im Tab Settings des Fensters unter Terrain Component eingebettet. Das Zuweisen eines Tilesets und die Textur-Arrays werden unter Tilesets behandelt, Kartendateien unter Karten-Assets.
Das Terrain verwendet nur die Y-Rotation seines Transforms und ignoriert die Skalierung. Um eine Karte zu vergrößern, füge mit Resize (RTSTerrain.ResizeMap) Zellen hinzu oder erstelle eine neue Karte mit größerer Cell Size (MapCreationSettings.cellSize).
Terrain-Einstellungen
Diese Felder gehören zu RTSTerrainSettings (Eigenschaft RTSTerrain.Settings).
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Chunks & Geometry | ||
| Chunk Size | 32 | Zellen pro Chunk-Seite (16, 32 oder 64). Kleinere Chunks werden beim Bearbeiten schneller neu aufgebaut; größere Chunks bedeuten weniger Draw Calls. |
| Surface Subdivisions | 2 | Quads pro Zellseite auf glattem Boden (1–4). 1 = klassischer RTS-Look, 2–4 = weichere Hügel. |
| Cliff Subdivisions | 4 | Auflösung des Unterrasters, mit dem abgerundete Klippenkonturen innerhalb einer Klippenzelle nachgezeichnet werden (2–8). |
| Cliff Shape | ||
| Cliff Noise | 0.18 | Zufällige horizontale Verschiebung der Vertices von Klippenwänden, als Bruchteil einer Zelle (0–0.5). Sorgt für eine natürliche Felsoptik. |
| Cliff Noise Scale | 1.6 | Skalierung des Klippenrauschens in Welteinheiten (mindestens 0.1). |
| Cliff Slant | 0.12 | Wie weit die Oberkante einer Klippe zurückversetzt wird (als Bruchteil einer Zelle, 0–0.4), damit die Wände leicht schräg stehen. |
| Cliff Wall Segments | 3 | Vertikale Segmente pro Klippenstufe für die Wandgeometrie (1–8). |
| Physics | ||
| Collider Mode | Mesh Collider | None: keine Collider; verwende für Abfragen den Raycast des Terrains. Mesh Collider: ein MeshCollider pro Chunk, der nach Bearbeitungen neu aufgebaut wird. |
| Chunk Layer | Default (0) | Layer der Chunk-GameObjects, den Collider und Kameras verwenden. |
| Rendering | ||
| Cast Shadows | On | Schattenwurfmodus der Chunks. |
| Receive Shadows | an | Ob die Chunks Schatten empfangen. |
| Material Override | keine | Optionales Material. Ist das Feld leer, wird ein Material mit dem Shader RTS Terrain Studio/Terrain erstellt. |
| Show Grid | aus | Zeigt das Zellraster an, auch im Play Mode und in Builds. Der Schalter G des Editors ist davon unabhängig und wirkt sich nur beim Bearbeiten aus. |
| Grid Opacity | 0.35 | Deckkraft des Rasters (0–1). |
Pathing Rules
Diese Felder gehören zu RTSPathingSettings (Eigenschaft RTSTerrain.PathingSettings). Sie legen fest, wie aus Neigung, Klippen, Wasser und Bodenschichten begehbare und bebaubare Bereiche werden. Mit den Pathing-Werkzeugen gemalte Overrides werden darüber angewendet.
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Max Walkable Slope | 42° | Boden, der steiler als dieser Winkel ist, ist nicht begehbar. |
| Max Buildable Slope | 18° | Boden, der steiler als dieser Winkel ist, ist nicht bebaubar. |
| Cliff Margin | 0.25 | Wie weit der blockierte Bereich über eine Klippenwand hinausreicht, als Bruchteil einer Pathing-Zelle (0–1). Größere Werte halten Einheiten weiter von Klippenkanten fern. |
| Shallow Water Buildable | aus | Erlaubt Gebäude im Flachwasser. |
| Tree Footprint Scale | 1 | Faktor für den Radius der Pathing-Grundfläche jedes Baums. |
| Use Ground Layer Rules | an | Wendet den Pathing-Effekt der dominanten Bodenschicht an, zum Beispiel Unbuildable auf rauem Fels. |
Default Map
Die Karte, die erstellt wird, wenn kein Karten-Asset zugewiesen ist. Das Panel New Map im Tab Map startet mit diesen Werten.
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Width / Height | 128 / 128 | Größe in Zellen, ein Vielfaches von 16 (16–1024). |
| Cell Size | 2 | Größe einer Zelle in Welteinheiten. |
| Cliff Level Height | 2 | Höhe einer Klippenstufe in Welteinheiten. |
| Base Cliff Level | 2 | Anfängliche Klippenstufe jedes Vertex (0–15). |
| Base Ground Layer | 0 | Bodenschicht, die die Karte anfangs bedeckt. |
| Splat Resolution | 4 | Splat-Texel pro Zelle (1, 2, 4 oder 8). |
| Detail Resolution | 2 | Texel der Detaildichte pro Zelle (1, 2 oder 4). |
| Pathing Resolution | 2 | Pathing-Zellen pro Kartenzelle (1, 2 oder 4). |
Subsystem-Komponenten
Das Terrain zeichnet nur den Boden und die Klippen. Andere Teile der Karte werden von Komponenten dargestellt, die auf demselben GameObject oder an anderer Stelle in der Szene liegen. Der Inspector des Terrains warnt, wenn eine der ersten vier fehlt, und bietet eine Schaltfläche Add an.
| Komponente | Vom Erstellungsmenü hinzugefügt | Zweck |
|---|---|---|
| Vegetation Renderer | ja | Rendert das gemalte Gras, die gemalten Pflanzen und die Bäume, per GPU-Instancing oder als Prefab-Instanzen. Erzeugt nur die Bereiche neu, die sich geändert haben. |
| Water Renderer | ja | Baut und rendert die Wasseroberfläche pro Chunk, mit dem Erscheinungsbild aus dem Wasserprofil des Tilesets. Für die beste Optik aktivierst du im URP Asset (oder an der Kamera) die Optionen Depth Texture und Opaque Texture. |
| Decor Manager | ja | Zeigt die Dekorobjekte der Karte an und hält sie auf dem Boden, wenn sich die Höhen ändern. |
| Environment | durch den New Map Wizard (optional) oder über | Überträgt Sonne, Umgebungslicht und Nebel der Karte auf die Szene. Beim Deaktivieren wird die ursprüngliche Szenenbeleuchtung wiederhergestellt; die Kartenbeleuchtung wird nie in die Szenendatei geschrieben. |
| Fog of War Manager | Berechnet den Nebel des Krieges für das lokale Team. Einer pro Szene. | |
| Pathfinding Service | oder bei Bedarf automatisch durch RTSPathfindingService.GetOrCreate() erstellt | Führt die integrierten Pfadsuchen auf der Pathing-Karte des Terrains aus. |
Terrain-Einstellungen per Code ändern
using RTSTerrainStudio;
using UnityEngine;
public class TerrainQualityOptions : MonoBehaviour
{
public RTSTerrain terrain;
// Called from a graphics options menu, for example.
public void SetSmoothHills(bool smooth)
{
// Settings fields can be changed directly, but the chunks are only rebuilt on request.
// Use 1, 2 or 4: an odd value such as 3 rounds Cliff Subdivisions up to a multiple of 6 (expensive).
terrain.Settings.surfaceSubdivisions = smooth ? 2 : 1;
terrain.RebuildAll();
}
public void UseStricterSlopes()
{
// Assigning new pathing settings rebuilds the pathing map automatically.
RTSPathingSettings rules = terrain.PathingSettings.Clone();
rules.maxWalkableSlope = 35f;
rules.maxBuildableSlope = 12f;
terrain.PathingSettings = rules;
}
}
Wenn du die Felder der aktuellen Instanz von PathingSettings änderst, statt eine neue zuzuweisen, rufe danach terrain.RebuildPathing() auf.
In-Game-Editor
Der In-Game-Editor bringt den Karteneditor von RTS Terrain Studio in dein fertiges Spiel. Spieler und Modder
können Karten ohne Unity erstellen, bemalen, speichern, laden und im Spiel testen – genau wie mit den Karteneditoren, die
klassischen RTS-Spielen beilagen. Er basiert auf UI Toolkit, verwendet dieselben Werkzeuge wie das Editorfenster in Unity (Height, Cliffs, Paint, Vegetation,
Decor, Water, Pathing und Markers) und schreibt dieselben .rtsmap-Dateien. Eine in deinem Spiel erstellte Karte kannst du
in ein Unity-Projekt kopieren und dort unverändert öffnen – und umgekehrt.

Alles steckt in einer einzigen Komponente: RTSRuntimeEditor. Füge sie einer Szene mit einem RTS Terrain und einer
Kamera hinzu, drücke Play – und du hast einen funktionierenden Editor. Dieses Kapitel behandelt die Einrichtung, jeden Teil der Oberfläche,
die komplette Tastatur- und Maussteuerung, die Schaltfläche Play Test, wie dein Spielcode darauf reagiert, wo Karten gespeichert werden,
wie du die UI umgestaltest und was du vor der Veröffentlichung prüfen solltest.
Im Unity Editor öffnet die Schaltfläche Open Manual im Fenster Shortcuts & Help (F1 oder die Schaltfläche ? in der oberen Leiste) dieses Kapitel. In einem Build öffnet die Schaltfläche die Seite, die du unter Manual Url einträgst, zum Beispiel deinen eigenen Modding-Leitfaden. Ist das Feld leer, wird die Schaltfläche in Builds ausgeblendet.
In zwei Minuten ausprobieren
Am schnellsten lernst du den In-Game-Editor mit der Demoszene kennen, die dem Paket beiliegt.
- Wähle . Dadurch werden die Demo-Texturen, -Bibliotheken, -Karten und -Szenen erzeugt. Das musst du nur einmal tun.
- Öffne im Project-Fenster
Samples/Scenes/RTS Terrain Studio - In-Game Editor Demo. Die Szene enthält die Karte Sandbox, ein Objekt RTS In-Game Editor, ein Objekt RTS Demo Game und einen Manager für den Nebel des Krieges. - Drücke Play. Der Editor öffnet sich mit einem Willkommenshinweis. Wähle links ein Werkzeug (standardmäßig ist das Höhenwerkzeug Raise / Lower aktiv) und male mit der linken Maustaste auf das Terrain.
- Halte beim Malen Shift gedrückt, um den Boden abzusenken. Mit [ und ] änderst du die Pinselgröße. Ziehe mit gedrückter rechter Maustaste, um die Kamera zu bewegen, und zoome mit dem Mausrad.
- Drücke F5 (oder Play Test in der oberen Leiste). Das Demospiel spawnt Einheiten an den Startpositionen und schaltet den Nebel des Krieges ein. Wähle Einheiten aus und bewege sie per Rechtsklick.
- Drücke Esc oder F5, um zum Editor zurückzukehren. Die Karte ist danach wieder genau so wie vor dem Spieltest.
- Drücke Ctrl+S, gib einen Namen ein und drücke Enter, um deine Karte zu speichern.
Den In-Game-Editor zu deiner Szene hinzufügen
Voraussetzungen
- Ein RTS Terrain mit einer Karte in der Szene ().
- Eine Kamera. Standardmäßig verwendet der Editor
Camera.main, also die Kamera mit dem Tag MainCamera. Du kannst aber auch jede beliebige Kamera zuweisen. - Der Ordner
Resources/RTSTerrainStudio/UIaus dem Paket. Er enthält das UI-Theme und das Stylesheet.
Schritte
- Öffne deine Szene und wähle . Dadurch
entsteht ein Objekt namens RTS In-Game Editor mit der Komponente
RTSRuntimeEditor. Wenn du im Hierarchy-Fenster mit der rechten Maustaste auf ein Objekt klickst und das Menü dort verwendest, wird das neue Objekt diesem Objekt als Kind untergeordnet. - Warnt die Console, dass kein RTS Terrain vorhanden ist, füge eines hinzu. Warnt sie, dass keine Kamera das Tag MainCamera trägt, versieh deine Kamera mit diesem Tag oder weise sie unter Target Camera zu.
- Weise optional Terrain und Kamera explizit zu und passe die übrigen Einstellungen an (siehe Inspector-Referenz).
- Drücke Play. Der Editor erscheint. Hast du Start Visible ausgeschaltet, öffnest du ihn mit F2.
Im Play Mode ist der Menüeintrag deaktiviert. Enthält die Szene bereits einen In-Game-Editor, fragt Unity, ob du einen weiteren erstellen (Create Another) oder den vorhandenen auswählen (Select Existing) möchtest. Ein Editor pro Szene genügt. Du kannst die Komponente auch über Add Component ▸ RTS Terrain Studio ▸ In-Game Editor hinzufügen; pro GameObject kann es nur eine davon geben.
Was die Komponente zur Laufzeit erstellt
Du musst weder eine UI noch ein Kamera-Rig selbst bauen. Sobald RTSRuntimeEditor aktiviert ist, erstellt die Komponente:
| Objekt | Zweck |
|---|---|
RTSEditSession | Die Bearbeitungssitzung, erreichbar über Session. Sie enthält Werkzeuge, Pinsel, Symmetrie, Auswahl und Undo-Verlauf und ist dieselbe Klasse, die auch das Editorfenster in Unity verwendet. |
| Kindobjekt RTS In-Game Editor UI | Enthält ein UIDocument mit der kompletten Editoroberfläche. Es wird wieder zerstört, wenn die Komponente deaktiviert wird. |
RTSRuntimeEditorCamera | Wird der Zielkamera hinzugefügt, wenn Use Built In Camera eingeschaltet ist. Hat die Kamera bereits eine solche Komponente, wird diese verwendet. Eine Kamerakomponente, die der Editor selbst hinzugefügt hat, entfernt er wieder, wenn er zerstört wird. |
RuntimeToolGizmos | Zeichnet Pinselumrisse, Auswahlrechtecke, Dekorvorschauen, Startpositionen und Symmetrie-Hilfslinien in der Game View. Erreichbar über Gizmos. |
PanelSettings | Wird erstellt, wenn du Panel Settings leer lässt. Verwendet das RTS-Theme und skaliert mit dem Bildschirm. Weist du Panel Settings zu, die mit der Bildschirmgröße skalieren und nicht in eine Textur rendern, verwendet der Editor stattdessen eine Laufzeitkopie davon, es sei denn, Enforce Minimum UI Scale ist ausgeschaltet (siehe Die Oberfläche anpassen). |
Wie Terrain und Kamera gefunden werden
- Terrain: Ein zugewiesenes Terrain wird verwendet, sobald es aktiviert ist. Ist das Feld leer, verwendet der Editor
RTSTerrain.Main, das erste RTS Terrain in der Szene. Solange kein Terrain gefunden wird, sucht der Editor zweimal pro Sekunde erneut und zeigt die Hinweismeldung No RTS Terrain found. - Mehrere Terrains: Enthält die Szene mehr als ein RTS Terrain, listet das Menü Map sie unter Edited terrain auf. Der Wechsel zu einem anderen Terrain löscht den Undo-Verlauf.
- Kamera: Ist Target Camera leer, verwendet der Editor
Camera.mainund sucht fortlaufend danach. Solange keine Kamera existiert, zeigt er die Hinweismeldung No camera found.
Inspector-Referenz
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Scene | ||
| Terrain | leer | Das zu bearbeitende Terrain. Bleibt das Feld leer, wird das erste RTS Terrain in der Szene verwendet (RTSTerrain.Main). |
| Target Camera | leer | Die Kamera für das Picking, die Gizmos und die integrierte Editorkamera. Bleibt das Feld leer, wird Camera.main verwendet. |
| Use Built In Camera | an | Fügt der Zielkamera eine RTSRuntimeEditorCamera hinzu und steuert sie, solange der Editor sichtbar ist. Während Spieltests ist diese Komponente ausgeschaltet. Schalte die Option aus, wenn dein eigenes Kameraskript die Kamera beim Bearbeiten bewegen soll. |
| Behaviour | ||
| Start Visible | an | Zeigt den Editor beim Start der Szene an. Ist die Option aus, öffnet der Spieler ihn mit der unter Toggle Key festgelegten Taste, oder dein Code setzt EditorVisible. |
| Toggle Key | F2 | Blendet den gesamten Editor ein oder aus. Wähle None, um die Taste abzuschalten. Die Schließen-Schaltfläche in der oberen Leiste wird dann ausgeblendet, sodass dein Spiel den Editor selbst öffnen und schließen muss. In Webbrowsern sowie auf Smartphones und Tablets bleibt die Schließen-Schaltfläche sichtbar, weil die Schaltfläche Open Map Editor den Editor dort zurückholt. |
| Start Tool | height.raise | Die ID des Werkzeugs, das beim Start aktiv ist, zum Beispiel paint.layer, veg.trees, decor.place oder markers.start. Gibt es die ID nicht, wird das erste Werkzeug verwendet. |
| Show First Time Hint | an | Zeigt beim ersten Öffnen des Editors den Willkommenshinweis an, bis der Spieler auf Got it klickt oder ihn schließt. Diese Entscheidung wird in PlayerPrefs gespeichert. |
| Show Quit Command | an | Fügt den Eintrag File ▸ Quit hinzu, der die Anwendung beendet. Im Unity Editor beendet er den Play Mode. Unter WebGL, iOS und Android wird er nie angezeigt. |
| Confirm Quit With Unsaved Changes | an | Fragt nach, bevor die Anwendung beendet wird, wenn die Karte nicht gespeicherte Änderungen hat – auch wenn die Aufforderung zum Beenden von außerhalb des Editors kommt (Schließen-Schaltfläche des Fensters, Alt+F4, Application.Quit). Dabei wird zuerst der Editor eingeblendet und ein laufender Spieltest beendet. Web-Builds lassen sich nicht beenden; dort fragt der Browser Leave site?, bevor die Seite geschlossen oder neu geladen wird. |
| Undo Memory MB | 512 | Der maximale Speicher, den der Undo-Verlauf belegen darf, in Megabyte (mindestens 16). Ist dieses Limit erreicht, werden die ältesten Schritte verworfen. Außerdem speichert der Verlauf höchstens 100 Schritte. Web-Builds verwenden höchstens 192 MB, weil der Browser dem gesamten Spiel nur einen begrenzten Speicher-Heap zur Verfügung stellt. |
| Manual Url | leer | Die Webseite, die Open Manual im Hilfefenster in einem Build öffnet. Ist das Feld leer, wird die Schaltfläche in Builds ausgeblendet. Im Unity Editor öffnet die Schaltfläche bei leerem Feld dieses Handbuch. |
| Files | ||
| Maps Folder | leer | Der Ordner, in dem Karten aufgelistet, geladen und gespeichert werden. Bleibt das Feld leer, wird der Ordner Maps im Speicherordner RTSMapSerializer.StorageRoot verwendet. Ein relativer Pfad wie UserMaps bezieht sich auf den Speicherordner. Ein absoluter Pfad wird unverändert übernommen. Der Speicherordner ist Application.persistentDataPath; in Web-Builds ist es ein Ordner im Browserspeicher, der gleich bleibt, wenn sich die Adresse der Seite ändert (siehe Karten im Browser). |
| Write Preview Images | an | Schreibt neben jede gespeicherte Karte ein kleines Minikartenbild (192 × 192 Pixel). Der Kartenbrowser zeigt es an. |
| User Interface | ||
| Panel Settings | leer | Optionale Panel Settings für UI Toolkit. Ist das Feld leer, erstellt der Editor zur Laufzeit Panel Settings mit dem RTS-Theme. Sie skalieren mit der Bildschirmgröße (Referenz 1920 × 1080), aber nie unter 85 %, sodass Text auch bei 1280 × 720 lesbar bleibt. |
| Enforce Minimum UI Scale | an | Hält den Editor in kleinen Fenstern und kleinen Browser-Canvas-Elementen lesbar. Zugewiesene Panel Settings, die mit der Bildschirmgröße skalieren, werden zur Laufzeit kopiert (das Asset selbst wird nie verändert), und die Kopie skaliert die UI nie unter 85 %. Schalte die Option aus, um das zugewiesene Asset unverändert zu verwenden, zum Beispiel wenn sich der Editor ein Panel mit dem HUD deines Spiels teilt. |
| Ui Scale | 1 | Ein zusätzlicher Skalierungsfaktor von 0.5 bis 2. Er wirkt auf die Panel Settings, die der Editor zur Laufzeit erstellt, und auf die Laufzeitkopie zugewiesener Panel Settings. |
| Sorting Order | 1000 | Die Sortierreihenfolge der Editor-UI. Durch den hohen Standardwert liegt der Editor über dem HUD deines Spiels. |
Ein Rundgang durch die Oberfläche
Der Bildschirm ist in fünf Bereiche aufgeteilt. Jede Schaltfläche, jedes Feld und jedes Statuselement zeigt einen Tooltip, wenn du den Mauszeiger kurz darüber hältst – das gilt auch für deaktivierte Schaltflächen. Bist du dir nicht sicher, was etwas bewirkt, fahre mit der Maus darüber.
| Bereich | Position | Inhalt |
|---|---|---|
| Obere Leiste | oberer Rand | Menüs File und Map, Rückgängig und Wiederherstellen, Symmetrie, Ansichtsschalter, Play Test, Hilfe, Panels ausblenden, Schließen. |
| Werkzeugleiste | linker Rand | Acht Kategorieschaltflächen (Tasten 1 bis 8) und die Werkzeuge der ausgewählten Kategorie. |
| Werkzeug-Panel | rechter Rand | Name und Beschreibung des aktiven Werkzeugs, Pinsel, Symmetrie, die Einstellungen des Werkzeugs und zusätzliche Befehle. Das Panel lässt sich einklappen. |
| Statusleiste | unterer Rand | Informationen zum Punkt unter dem Mauszeiger, Werkzeugstatus, Pinsel, Undo-Speicher und Kartenname. |
| Overlays | über der Karte | Toast-Meldungen, Tooltips, Dialoge, der Willkommenshinweis, die Spieltest-Leiste und Hinweismeldungen. |
Obere Leiste
| Bedienelement | Funktion |
|---|---|
| File | Öffnet das Menü File (siehe unten). |
| Map | Öffnet das Menü Map (siehe unten). |
| Undo / Redo | Rückgängig (Ctrl+Z) und Wiederherstellen (Ctrl+Y). Ihre Tooltips zeigen den Namen des Schritts, zum Beispiel Undo "Raise / Lower". Die Schaltflächen sind ausgegraut, wenn es nichts rückgängig zu machen oder wiederherzustellen gibt. |
| Symmetry: Off | Öffnet das Symmetriemenü. Ist die Symmetrie eingeschaltet, wird jeder Pinselstrich gespiegelt oder gedreht – so lassen sich faire Wettkampfkarten leicht erstellen. Die Schaltfläche zeigt den aktuellen Modus an, zum Beispiel Symmetry: 180°. |
| Grid | Blendet das Zellraster ein oder aus (G). |
| Pathing | Öffnet das Menü des Pathing-Overlays. Ist ein Overlay eingeschaltet, zeigt die Schaltfläche dessen Modus an, zum Beispiel Pathing: Walkability. P schaltet das Overlay mit dem zuletzt verwendeten Modus ein bzw. wieder aus. |
| Markers | Blendet Startpositionen, Regionen und Marker ein oder aus, während andere Werkzeuge aktiv sind (M). Standardmäßig eingeschaltet. |
| Play Test | Startet einen Spieltest (F5). |
| Help (?) | Öffnet das Fenster Shortcuts & Help (F1). |
| Eye | Blendet die Panels aus, damit du freie Sicht auf die Karte hast (H). Bearbeiten funktioniert weiterhin. Eine kleine Schaltfläche Panels hidden - press H to show them holt sie zurück. |
| Close (×) | Blendet den Editor aus und zeigt eine Toast-Meldung mit der Taste, die ihn wieder öffnet. Die Schaltfläche ist ausgeblendet, wenn Toggle Key auf None steht. In Webbrowsern sowie auf Smartphones und Tablets, wo Funktionstasten unzuverlässig sind, blendet sie zusätzlich unten links eine Schaltfläche Open Map Editor ein. Dort ist sie auch dann verfügbar, wenn unter Toggle Key keine Taste festgelegt ist (siehe Der In-Game-Editor im Browser). |
Menü File
| Element | Tastenkürzel | Funktion |
|---|---|---|
| New Map... | Ctrl+N (Alt+N in Webbrowsern) | Öffnet den Dialog New Map. Hat die aktuelle Karte nicht gespeicherte Änderungen, fragt der Editor vorher nach. Alt+N funktioniert auf allen Plattformen. |
| Open... | Ctrl+O | Öffnet den Kartenbrowser, in dem du eine Karte auswählst. Hat die aktuelle Karte nicht gespeicherte Änderungen, fragt der Editor vorher nach. |
| Save | Ctrl+S | Speichert in die Datei, aus der die Karte stammt. Beim ersten Mal öffnet sich Save Map As und fragt nach einem Namen. |
| Save As... | Ctrl+Shift+S | Öffnet den Kartenbrowser, damit du unter einem neuen Namen speichern kannst. |
| Download Map | Nur in Web-Builds. Speichert eine Kopie der Karte einschließlich nicht gespeicherter Änderungen als .rtsmap-Datei auf dem Computer des Spielers (meist im Ordner Downloads). Siehe Karten im Browser. | |
| Import Map... | Nur in Web-Builds. Öffnet eine .rtsmap-Datei vom Computer des Spielers. Sie wird zu den in diesem Browser gespeicherten Karten hinzugefügt und geöffnet. | |
| Show Maps Folder | Öffnet den Kartenordner im Dateibrowser des Betriebssystems. Diesen Eintrag gibt es nur unter Windows, macOS und Linux, im Unity Editor und in Desktop-Playern. | |
| Quit | Beendet die Anwendung und fragt vorher nach nicht gespeicherten Änderungen. Wird nur angezeigt, wenn Show Quit Command eingeschaltet ist, und nie unter WebGL, iOS oder Android. |
Menü Map
| Element | Funktion |
|---|---|
| Map Info... | Name, Autor, Beschreibung und empfohlene Spielerzahl. |
| Resize... | Vergrößert oder verkleinert die Karte. |
| Generate... | Erzeugt aus einem Seed ein zufälliges Karten-Layout. Du kannst das in einem Schritt rückgängig machen. |
| Frame Whole Map | Bewegt die Editorkamera so, dass die ganze Karte sichtbar ist. Nur mit der integrierten Kamera verfügbar. |
| Edited terrain | Wird nur angezeigt, wenn die Szene mehr als ein RTS Terrain enthält. Hier wählst du, welches Terrain du bearbeitest. Ein Wechsel löscht den Undo-Verlauf. |
Symmetriemenü
| Modus | Kopien | Typische Verwendung |
|---|---|---|
| Off | 1 | Malen ohne Symmetrie. |
| Mirror Left / Right | 2 | Spiegelt an der senkrechten Mittellinie. |
| Mirror Top / Bottom | 2 | Spiegelt an der waagerechten Mittellinie. |
| Mirror 4-Way | 4 | Spiegelt an beiden Achsen. |
| Rotate 180° | 2 | Punktsymmetrie um das Zentrum, typisch für 1v1-Karten. |
| Rotate 90° (4 players) | 4 | Rotationssymmetrie für Karten mit vier Spielern. Erfordert eine quadratische Karte; bei einer nicht quadratischen Karte ist der Eintrag ausgegraut. |
| Mirror Diagonal | 2 | Spiegelt an der Diagonalen. Erfordert eine quadratische Karte; bei einer nicht quadratischen Karte ist der Eintrag ausgegraut. |
Reset Centre setzt das Symmetriezentrum zurück in die Mitte der Karte. Im Abschnitt Symmetry des Werkzeug-Panels verschiebst du das Zentrum mit den Schiebereglern Centre X und Centre Z. Violette Hilfslinien auf dem Terrain zeigen die Symmetrieachsen.
Menü des Pathing-Overlays
Das Overlay färbt das Terrain danach ein, was Einheiten dort tun können. Wähle Off, Walkability (grün = begehbar, rot = blockiert), Buildability, Flyability, Water (Flach- und Tiefwasser) oder Combined (begehbare, bebaubare, Wasser- und blockierte Zellen in einer Ansicht). Die Einträge unter Opacity setzen die Deckkraft des Overlays auf 30 %, 55 % (Standard) oder 80 %.
Werkzeugleiste (links)
Die obere Spalte enthält eine Schaltfläche pro Werkzeugkategorie. Die kleine Zahl auf jeder Schaltfläche ist die zugehörige Taste. Die untere Spalte listet die Werkzeuge der ausgewählten Kategorie mit Icon und Namen auf. Fahre mit der Maus über ein Werkzeug, um seine Beschreibung und seinen Hinweis zu sehen.
| Taste | Kategorie | Beschreibung | Werkzeuge |
|---|---|---|---|
| 1 | Height | Boden anheben, absenken, glätten und formen. | Raise / Lower, Smooth, Flatten, Set Height, Noise, Terrace, Erode |
| 2 | Cliffs | Klippenstufen im Stil klassischer RTS-Spiele, Rampen und Klippenmaterialien. | Raise / Lower Level, Set Level, Ramp, Cliff Type, Clean Cliffs |
| 3 | Paint | Bodentexturen malen. | Paint Layer, Tile Paint, Fill / Replace, Smooth Paint, Auto Paint |
| 4 | Vegetation | Gras, Blumen und Bäume. | Paint Details, Erase Details, Place Trees, Erase Trees |
| 5 | Decor | Objekte (Doodads) platzieren, löschen und auswählen. | Place Decor, Erase Decor, Select Decor |
| 6 | Water | Seen, Flüsse und Meere. | Paint Water, Erase Water, Flood Fill |
| 7 | Pathing | Festlegen, wo Einheiten laufen, bauen und fliegen können. | Paint Pathing, Clear Pathing |
| 8 | Markers | Startpositionen, Regionen und benannte Marker. | Start Locations, Regions, Markers |
Der Editor merkt sich in jeder Kategorie das zuletzt verwendete Werkzeug. Wählst du eine Kategorie über ihre Schaltfläche oder Zahlentaste,
ist dieses Werkzeug wieder aktiv. Drückst du die Zahlentaste der Kategorie, in der du dich bereits befindest, wechselst du zum nächsten Werkzeug darin.
Eigene Werkzeuge, die du registrierst, erscheinen hier automatisch – mit einer Einstellungs-UI, die aus ihren
[ToolSetting]-Feldern erzeugt wird.
Werkzeug-Panel (rechts)
Die Kopfzeile zeigt Icon und Namen des aktiven Werkzeugs. Fahre mit der Maus über den Namen, um die Beschreibung des Werkzeugs zu lesen. Die Pfeilschaltfläche klappt das Panel ein. Unter der Kopfzeile folgen einklappbare Abschnitte. Abschnitte, die du einklappst, bleiben auch beim Werkzeugwechsel eingeklappt.
| Abschnitt | Angezeigt für | Inhalt |
|---|---|---|
| (Info) | jedes Werkzeug | Beschreibung und Bedienhinweis des Werkzeugs, zum Beispiel Click: place / move · Shift+click: remove the nearest. |
| Brush | Pinselwerkzeuge | Shape (Circle, Square, Diamond sowie Stamp, wenn ein eigener Stempel gesetzt ist), Size (ein logarithmischer Schieberegler plus ein Feld in Metern), Strength, Hardness, Rotation, Random Rotation, Spacing, Jitter und eine Schaltfläche Reset in der Kopfzeile des Abschnitts. Alle Werkzeuge teilen sich denselben Pinsel. |
| Symmetry | jedes Werkzeug | Symmetriemodus, Centre X und Centre Z sowie eine Warnung, wenn der Modus eine quadratische Karte erfordert. |
| Settings | Werkzeuge mit Optionen | Die Optionen des aktiven Werkzeugs, etwa Zielhöhe, Texturpaletten, Vorschaubilder für Bäume und Dekor, Klippenstufe oder Spieler. |
| Actions | einige Werkzeuge | Zusätzliche Befehle: Apply to Whole Map (Auto Paint), New Random Layout (Place Trees, Place Decor) und Ähnliches. |
| Selection | Select Decor | Delete, Duplicate, Drehen (−15°, +15°, +90°), Verschieben um eine halbe Zelle (−X, +X, −Z, +Z), Smaller / Larger (10 %), auf den Boden setzen (Snap), Lock / Unlock, Select All, Clear. |
Hat das Terrain keine Karte, zeigen sowohl das Panel als auch die Statusleiste There is no map to edit. an.
Statusleiste (unten)
| Element | Beispiel | Bedeutung |
|---|---|---|
| Zelle | Cell 42, 17 | Die Zelle unter dem Mauszeiger (x, z). |
| Höhe | Height 3.5 m | Bodenhöhe unter dem Mauszeiger in Metern. |
| Stufe | Level 2 | Die Klippenstufe unter dem Mauszeiger (0 bis 15). |
| Pathing | Walkable, Buildable | Was Einheiten hier tun können, zum Beispiel Shallow water, Cliff, Too steep, Blocked oder No flying. |
| Wasser | Water 1.2 m oder Dry | Wassertiefe unter dem Mauszeiger. |
| Werkzeugstatus | Informationen des aktiven Werkzeugs oder sein Hinweis. | |
| Pinsel | Brush 8.0 m 50 % | Pinselradius und -stärke. |
| Undo | Undo 12 4.2 MB | Anzahl der Undo-Schritte und der Speicher, den sie belegen. |
| Kartenname | ● Sandbox | Der Name der Karte. Ein Punkt bedeutet, dass es nicht gespeicherte Änderungen gibt; Ctrl+S speichert sie. |
Toast-Meldungen, Tooltips und Hinweismeldungen
- Toast-Meldungen sind kurze Nachrichten, die über der Statusleiste erscheinen und von selbst wieder ausgeblendet werden, zum Beispiel Saved "Sandbox" oder Undo: Raise / Lower. Es gibt vier Arten: Info und Success bleiben etwa 3 Sekunden sichtbar, Warning 5 Sekunden und Error 6.5 Sekunden. Klicke auf eine Toast-Meldung, um sie zu schließen. Es werden höchstens vier gleichzeitig angezeigt. Toast-Meldungen bleiben sichtbar, während der Editor ausgeblendet ist, damit die Nachricht Editor closed. Press F2 to open it again. lesbar ist.
- Open Map Editor: in Webbrowsern sowie auf Smartphones und Tablets eine Schaltfläche unten links, die erscheint, nachdem die Schließen-Schaltfläche verwendet wurde, und den Editor wieder öffnet. Mit Tastatur zeigt sie die unter Toggle Key festgelegte Taste an, zum Beispiel Open Map Editor (F2).
- Tooltips erscheinen nach etwa einer halben Sekunde über jedem Bedienelement – auch über deaktivierten und während eines Spieltests. Ein Klick blendet den Tooltip aus, bis der Mauszeiger das Bedienelement verlässt.
- Willkommenshinweis: ein kleines Panel mit den vier wichtigsten Bedienhinweisen und den Schaltflächen All Shortcuts (F1) und Got it. Mit Open Welcome Hint im Hilfefenster holst du ihn zurück.
- Hinweismeldungen: No RTS Terrain found oder No camera found erscheinen in der Bildschirmmitte, wenn der Editor nicht arbeiten kann, zusammen mit einer kurzen Erklärung, was zu beheben ist.
- Spieltest-Leiste: Während eines Spieltests zeigt eine kleine Leiste oben in der Mitte Play test running an und bietet die Schaltfläche Back to Editor (Esc or F5).
Dialoge
Dialoge sind modal: Solange einer geöffnet ist, lässt sich die Karte nicht bearbeiten und die Tastenkürzel sind deaktiviert. Enter löst die hervorgehobene (primäre) Schaltfläche aus, Esc schließt den Dialog.
| Dialog | Geöffnet über | Inhalt |
|---|---|---|
| New Map | File ▸ New Map... | Name; Size mit den Presets Tiny (64 × 64), Small (96), Medium (128), Large (192), Huge (256), Epic (384), Giant (512); Width und Depth (16 bis 1024 Zellen, auf Vielfache von 16 gerundet; in Webbrowsern ist die Grenze niedriger, siehe Grenzen); Cell Size 1, 2 oder 4 m (2 m entspricht klassischen RTS-Karten); Base Level (Klippenstufe 0 bis 15; bei 2 bleibt Platz, um für Seen und Flüsse nach unten zu graben); Base Layer (die Bodentextur). Eine Zeile darunter zeigt die resultierende Größe in Metern. Das Anlegen der neuen Karte lässt sich nicht rückgängig machen – speichere also vorher. |
| Generate Map | Map ▸ Generate... | Style (setzt die Formoptionen zurück), Seed mit einer Schaltfläche Random, Symmetry; Abschnitt Terrain: Cliffs, Plateaus, Ramps, Ramp Count, Water, Lake Coverage, Island, Border; Abschnitt Content: Textures, Vegetation, Forests, Decor, Decor Count, Start Locations, Players. Ersetzt den Inhalt der aktuellen Karte in einem einzigen Undo-Schritt. Die Einstellungen bleiben bis zum nächsten Öffnen des Dialogs erhalten. |
| Map Info | Map ▸ Map Info... | Name, Author, Description (wird im Kartenbrowser angezeigt), Players (1 bis 16) sowie Statistiken: Größe, Startpositionen, Bäume, Objekte, Regionen. Das Übernehmen zählt als ein Undo-Schritt. |
| Resize Map | Map ▸ Resize... | Width und Depth (Vielfache von 16, im Browser mit derselben Grenze wie im Dialog New Map), Anchor (ein Raster aus 3 × 3 Feldern: legt fest, welcher Teil der Karte an seiner Stelle bleibt), Fill Level und Fill Layer für neue Bereiche. Die Größenänderung löscht den Undo-Verlauf. Beim Verkleinern wird Inhalt außerhalb des neuen Bereichs entfernt. |
| Open Map / Save Map As | File ▸ Open... / Save As... | Der Kartenbrowser. |
| Unsaved Changes | New, Open, Quit bei nicht gespeicherten Änderungen | Save (speichert zuerst und fragt nach einem Namen, falls die Karte noch keinen hat), Don't Save, Cancel. |
| Shortcuts & Help | F1 oder die Schaltfläche ? | Die komplette Maus- und Tastatursteuerung (siehe Steuerung), Open Welcome Hint, Open Manual (wenn ein Handbuch verfügbar ist) und Close. |
Der Kartenbrowser
File ▸ Open... (Ctrl+O) und File ▸ Save As...
(Ctrl+Shift+S) öffnen denselben Browser mit dem Titel Open Map bzw.
Save Map As. Er listet alle .rtsmap-Dateien im Kartenordner auf.
| Teil | Beschreibung |
|---|---|
| Ordnerzeile | Der vollständige Pfad des Kartenordners. Refresh liest den Ordner neu ein, zum Beispiel nachdem du Karten hineinkopiert hast. In Web-Builds steht in der Zeile Saved in this browser. Use Download to keep a copy on your computer. Beim Öffnen von Karten enthält sie außerdem eine Schaltfläche Import... (siehe Karten im Browser). |
| Kartenliste | Eine Zeile pro Karte mit Namen, Größe, Spielerzahl und Datum der letzten Änderung; die neueste steht oben. Dateien, die sich nicht lesen lassen, werden als Unreadable or damaged file markiert und lassen sich nicht öffnen. Fahre mit der Maus über eine Zeile, um ihren Dateipfad zu sehen (in Web-Builds nur den Dateinamen). |
| Details | Die Minikarten-Vorschau, Name, Größe in Zellen und Metern, Autor, Spieler, Änderungsdatum und Beschreibung der ausgewählten Karte. Die Vorschau gibt es nur, wenn Write Preview Images beim Speichern der Karte eingeschaltet war. |
| Map Name | Nur bei Save As. Der Dateiname wird aus diesem Namen gebildet. Ein Klick auf eine Karte in der Liste kopiert ihren Namen in das Feld, sodass du sie überschreiben kannst. |
| Delete | Löscht nach einer Rückfrage die ausgewählte Kartendatei und ihr Vorschaubild. Das lässt sich nicht rückgängig machen. |
| Download | Nur in Web-Builds und nur beim Öffnen von Karten. Speichert eine Kopie der ausgewählten Karte als .rtsmap-Datei auf dem Computer des Spielers. |
| Cancel / Open / Save | Open lädt die ausgewählte Karte. Ein Doppelklick auf eine Zeile oder ein Druck auf Enter bewirkt dasselbe. Save speichert die Karte unter dem eingegebenen Namen. |
Der Browser liest nur den kleinen Kopfbereich jeder Datei. Deshalb öffnet er sich schnell, auch wenn der Ordner viele große Karten enthält.
Unter einem neuen Namen speichern
- Drücke Ctrl+Shift+S. Im Namensfeld steht bereits der aktuelle Kartenname.
- Gib einen neuen Namen ein, zum Beispiel
Glacier Pass (2v2), und drücke Enter. - Gibt es bereits eine andere Datei mit diesem Namen, fragt der Editor mit Replace Map? nach. Da Enter standardmäßig Cancel auslöst, musst du auf Replace klicken, um die Datei zu überschreiben.
- Die Karte und ein Vorschaubild werden gespeichert, und die Toast-Meldung Saved "Glacier Pass (2v2)" bestätigt den Vorgang. Danach speichert Ctrl+S ohne Nachfrage in diese Datei.
Wenn du unter einem anderen Namen speicherst, ändert sich auch der Name der Karte selbst (angezeigt in Map Info). Diese Umbenennung ist ein normaler Undo-Schritt namens Rename Map.
Steuerung und Tastenkürzel
Dieselbe Liste rufst du im Editor mit F1 auf. Unter macOS funktioniert Cmd überall dort, wo Ctrl angegeben ist. Mit Zifferntasten ist die Zahlenreihe der Haupttastatur gemeint. Buchstaben-Tastenkürzel richten sich nach dem Buchstaben, der auf der Taste aufgedruckt ist. Ctrl+Z verwendet also auch auf deutschen (QWERTZ) und französischen (AZERTY) Tastaturen die Taste mit der Aufschrift Z. Die Pinseltasten richten sich nach dem Zeichen, das sie eingeben. [ und ] funktionieren deshalb unabhängig davon, wo sie auf deiner Tastatur liegen, zum Beispiel mit AltGr. In Webbrowsern verhalten sich einige Tasten anders (siehe Der In-Game-Editor im Browser).
Maus
| Eingabe | Aktion |
|---|---|
| Linke Maustaste | Malen oder das aktive Werkzeug anwenden. Für einen Pinselstrich gedrückt halten und ziehen. Ein ganzer Pinselstrich ist ein einziger Undo-Schritt. |
| Shift + linke Maustaste | Die umgekehrte Aktion: absenken statt anheben, radieren statt malen, entfernen statt platzieren. |
| Ctrl + Linksklick | Einen Wert vom Terrain aufnehmen (Höhe, Bodenschicht, Klippenstufe …), sofern das Werkzeug dies unterstützt. |
| Mit gedrückter rechter oder mittlerer Maustaste ziehen | Die Kamera verschieben („den Boden greifen“). |
| Alt + mit gedrückter linker oder rechter Maustaste ziehen | Die Kamera drehen. Solange Alt gedrückt ist, malt die linke Maustaste nicht. |
| Mausrad | Hinein- und herauszoomen, ausgerichtet auf den Punkt unter dem Mauszeiger. |
Kamera (integrierte Editorkamera)
| Taste | Aktion |
|---|---|
| W A S D oder Pfeiltasten | Verschieben. Halte Shift gedrückt, um schneller zu verschieben. |
| Q / E | Nach links / rechts drehen. |
| F | Die Kamera auf den Punkt unter dem Mauszeiger fokussieren. |
Pinsel und Werkzeuge
| Taste | Aktion |
|---|---|
| [ / ] | Kleinerer / größerer Pinsel (etwa 15 % pro Tastendruck). |
| - / = (oder +) | Geringere / höhere Stärke (5 % pro Tastendruck). - und + auf dem Ziffernblock funktionieren ebenfalls. |
| 1 bis 8 | Eine Werkzeugkategorie wählen (Height, Cliffs, Paint, Vegetation, Decor, Water, Pathing, Markers). Erneutes Drücken wählt das nächste Werkzeug der Kategorie. |
| Esc | Den laufenden Pinselstrich abbrechen. Das Terrain wird auf den Stand zurückgesetzt, den es beim Drücken der Maustaste hatte. |
Datei und Verlauf
| Taste | Aktion |
|---|---|
| Ctrl+N oder Alt+N | Neue Karte. In Webbrowsern funktioniert nur Alt+N, weil der Browser Ctrl+N für ein neues Fenster reserviert. |
| Ctrl+O | Eine Karte öffnen (Kartenbrowser). |
| Ctrl+S | Speichern. Fragt beim ersten Mal nach einem Namen. |
| Ctrl+Shift+S | Speichern unter. |
| Ctrl+Z | Rückgängig machen. |
| Ctrl+Y oder Ctrl+Shift+Z | Wiederherstellen. |
Ansicht und Editor
| Taste | Aktion |
|---|---|
| G | Das Zellraster ein- oder ausblenden. |
| P | Das Pathing-Overlay einschalten (zuletzt verwendeter Modus, anfangs Combined) oder ausschalten. |
| M | Startpositionen, Regionen und Marker ein- oder ausblenden. |
| H | Die Editor-Panels aus- oder einblenden. |
| F1 | Shortcuts & Help. |
| F5 | Einen Spieltest starten oder den laufenden Spieltest beenden. |
| Esc (während eines Spieltests) | Den Spieltest beenden und zum Editor zurückkehren. |
| F2 (eingestellt unter Toggle Key) | Den gesamten Editor ein- oder ausblenden. Während eines Spieltests bewirkt diese Taste nichts. |
Dekorauswahl (nur mit dem Werkzeug Select Decor)
| Eingabe | Aktion |
|---|---|
| Klicken / Rechteck aufziehen | Objekte auswählen. |
| Shift + Klick / Rechteck | Objekte zur Auswahl hinzufügen oder aus ihr entfernen. |
| Ein ausgewähltes Objekt ziehen | Die Auswahl verschieben. Der gesamte Ziehvorgang ist ein Undo-Schritt. Esc während des Ziehens setzt die Objekte zurück. Gesperrte Objekte lassen sich nicht ziehen. |
| Auf ein Objekt klicken, das Teil einer Gruppenauswahl ist | Nur dieses Objekt auswählen. |
| Delete / Backspace | Die ausgewählten Objekte löschen. |
| Ctrl+D | Die Auswahl um eine Zelle diagonal versetzt duplizieren (+X und +Z). |
| Ctrl+A | Alle nicht gesperrten Objekte auswählen. |
| Esc | Die Auswahl aufheben. |
Was Esc bewirkt
Esc führt jeweils die erste zutreffende Aktion dieser Liste aus:
- Schließt ein offenes Popup-Menü.
- Nimmt den Tastaturfokus von einem Textfeld oder Schieberegler.
- Schließt den obersten Dialog.
- Bricht das Ziehen von Dekorobjekten ab (die Objekte kehren an ihren Platz zurück).
- Bricht den Pinselstrich ab, den du gerade malst (Toast-Meldung Stroke cancelled).
- Hebt die Dekorauswahl auf (Werkzeug Select Decor).
- Blendet die Panels wieder ein, wenn sie mit H ausgeblendet wurden.
Tastenkürzel funktionieren nicht? Solange ein Textfeld den Tastaturfokus hat, landen Buchstabentasten im Feld. Solange ein Dialog offen ist, funktionieren nur Enter und Esc. Klicke auf die Karte oder drücke Esc und versuche es dann erneut. Malen und Zoomen mit dem Mausrad sind außerdem blockiert, solange sich der Mauszeiger über einem Panel befindet.
Die integrierte Editorkamera
Wenn Use Built In Camera eingeschaltet ist, fügt der Editor der Zielkamera eine RTSRuntimeEditorCamera
hinzu. Diese Orbit-Kamera kreist um einen Fokuspunkt auf dem Boden. Sie bleibt innerhalb der Terrain-Grenzen samt einem zusätzlichen Rand,
taucht nie unter den Boden und neigt sich umso stärker nach unten, je weiter du herauszoomst. Sie verwendet ungeskalierte Zeit und
funktioniert deshalb auch, während dein Spiel pausiert ist (Time.timeScale = 0). Der Editor schaltet die Kamera ein, solange er
sichtbar ist, und schaltet sie aus, solange er ausgeblendet ist oder ein Spieltest läuft. Nach einem Spieltest kehrt sie an ihre
vorherige Position zurück.
Um ihr Verhalten zu ändern, füge RTSRuntimeEditorCamera selbst deiner Kamera hinzu
(Add Component ▸ RTS Terrain Studio ▸ In-Game Editor Camera) und passe die Felder an. Ist die
Komponente bereits vorhanden, verwendet der Editor sie, statt eine neue hinzuzufügen.
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Movement | ||
| Pan Speed | 18 | Verschiebegeschwindigkeit per Tastatur in Metern pro Sekunde bei geringster Höhe. Sie nimmt mit der Höhe zu. |
| Fast Multiplier | 2.5 | Multiplikator für die Verschiebegeschwindigkeit, solange Shift gedrückt ist. |
| Edge Pan | aus | Verschieben, wenn der Mauszeiger den Bildschirmrand berührt. |
| Edge Pan Margin | 12 | Breite des Bildschirmrands für das Rand-Scrollen, in Pixeln. |
| Rotate Speed | 100 | Drehgeschwindigkeit von Q / E in Grad pro Sekunde. |
| Mouse Rotate Speed | 0.25 | Drehung bei Alt + Ziehen mit der Maus in Grad pro Pixel. |
| Zoom | ||
| Min Height | 8 | Geringste Kamerahöhe über dem Fokuspunkt, in Metern. |
| Max Height | 120 | Größte Kamerahöhe über dem Fokuspunkt, in Metern. |
| Zoom Step | 0.14 | Höhenänderung pro Raste des Mausrads, als Anteil der aktuellen Höhe. |
| Zoom Towards Cursor | an | In Richtung des Punkts unter dem Mauszeiger zoomen statt zur Bildschirmmitte. |
| Min Pitch | 40 | Kameraneigung in Grad unterhalb des Horizonts bei geringster Höhe. |
| Max Pitch | 70 | Kameraneigung bei größter Höhe. |
| Pitch Curve | Ease-in-out | Übergang von Min Pitch (0) zu Max Pitch (1) in Abhängigkeit von der normalisierten Höhe. |
| Limits & Smoothing | ||
| Ground Clearance | 3 | Kleinster zulässiger Abstand zwischen der Kamera und dem Boden darunter, in Metern. |
| Bounds Padding | 8 | Zusätzlicher Raum um das Terrain, in den sich der Fokuspunkt bewegen darf, in Metern. |
| Smooth Time | 0.1 | Glättungszeit in Sekunden (0 = keine Glättung). |
Im Code liefert RTSRuntimeEditor.EditorCamera die Komponente oder null, wenn die integrierte Kamera ausgeschaltet ist:
| Member | Beschreibung |
|---|---|
void Focus(Vector3 worldPosition, bool instant = false) | Verschiebt den Fokus auf eine Position in Weltkoordinaten – geglättet, sofern instant nicht true ist. |
void FrameTerrain(bool instant = false) | Zentriert die Kamera über dem Terrain und zoomt so weit heraus, dass der Großteil davon zu sehen ist. Neue Karten und Karten mit geänderter Größe werden automatisch so ins Bild gerückt. |
void SnapToTargets() | Springt ohne Glättung zu Zielposition, Zieldrehung und Zielhöhe. |
Vector3 FocusPoint, float Yaw, float Height | Der aktuelle Orbit (schreibgeschützt). |
bool IsDragging, bool WantsAltDrag | True, solange die Kamera gezogen wird bzw. solange Alt gedrückt ist. |
bool PointerBlocked, bool KeyboardBlocked | Werden vom Editor in jedem Frame gesetzt, solange der Mauszeiger über der UI liegt, ein Textfeld den Fokus hat oder ein Dialog offen ist. |
RTSTerrain Terrain | Das Terrain, das für die Grenzen und die Bodenhöhe verwendet wird. |
Eigenes Kameraskript während der Bearbeitung verwenden: Schalte Use Built In Camera aus. Der
Editor nutzt die Zielkamera dann nur noch für Picking und Gizmos. Achte darauf, dass deine Kamera Eingaben ignoriert, solange
RTSRuntimeEditor.IsPointerOverUI true ist, damit das Scrollen in einem Panel nicht gleichzeitig die Kamera zoomt.
Play Test: die Karte in deinem Spiel ausprobieren
Play Test (F5) verwandelt den Editor für einen Moment in dein Spiel: Die Editor-UI macht Platz, dein Gameplay-Code spawnt Einheiten und schaltet seine Kamera ein, und der Spieler kann die Karte ausprobieren. Esc, F5 oder Back to Editor beendet den Test, und die Karte kehrt exakt in ihren vorherigen Zustand zurück. Einheiten und Gebäude verschwinden, abgebaute Bäume und geändertes Pathing werden zurückgesetzt.

Der Editor weiß nichts von deinem Spiel. Er meldet Beginn und Ende eines Spieltests über die statische
Klasse RTSRuntimeEditorEvents, und dein Spiel entscheidet, was zu tun ist. Die Beispielkomponente RTSDemoGame
() reagiert bereits darauf. Füge sie deiner
Editorszene hinzu, und du bekommst während Spieltests Einheiten, Nebel des Krieges, Minikarte und HUD, ohne Code schreiben zu müssen.
Was Schritt für Schritt passiert
| Zeitpunkt | Der Editor … |
|---|---|
| Play Test wird gedrückt |
|
| Während des Spieltests | Ignoriert alle Bearbeitungseingaben, Undo- und Dateibefehle (diese melden dann Stop the play test first.) sowie die Taste zum Ein- und Ausblenden des Editors. Das Gameplay darf die Karte beliebig verändern. |
| Esc, F5 oder Back to Editor |
|
Undo-Verlauf: Der Editor kopiert den Snapshot zurück in dieselbe Karteninstanz, sodass dein Undo-Verlauf Spieltests übersteht. Hat das Gameplay die Karte während des Tests ersetzt, neu geladen oder in der Größe geändert, wird der Snapshot stattdessen als neue Karte geladen. Die Toast-Meldung enthält dann (undo history was reset).
Referenz zu RTSRuntimeEditorEvents
RTSRuntimeEditorEvents liegt im Runtime-Kern (Runtime/Core). Dein Gameplay-Code
kompiliert und funktioniert also auch dann, wenn du den In-Game-Editor aus einem Projekt entfernst.
| Member | Beschreibung |
|---|---|
static bool IsPlaytesting | True, solange ein Spieltest läuft. |
static event Action<bool> PlaytestChanged | Wird ausgelöst, wenn ein Spieltest beginnt (true) oder endet (false). |
static event Action<RTSTerrain> MapOpened | Wird ausgelöst, nachdem der In-Game-Editor eine Karte aus einer Datei geöffnet oder eine neue Karte erstellt hat. |
static void ConsumeEscape() | Rufe diese Methode in einem Frame auf, in dem dein Gameplay-Code selbst auf Esc reagiert hat, zum Beispiel um eine Gebäudeplatzierung abzubrechen. Der Editor beendet den Spieltest in diesem Frame dann nicht. |
static bool EscapeConsumedThisFrame | True, wenn ConsumeEscape im aktuellen Frame aufgerufen wurde. |
static void SetPlaytesting(bool playtesting) | Setzt IsPlaytesting und löst PlaytestChanged aus. Der Editor ruft diese Methode auf. Um einen Test aus deinem eigenen Code zu starten, rufe stattdessen RTSRuntimeEditor.StartPlaytest() auf, denn nur der Editor erstellt den Snapshot der Karte. |
static void RaiseMapOpened(RTSTerrain terrain) | Löst MapOpened aus. Der Editor ruft diese Methode auf. |
Die Events werden beim Start des Play Mode zurückgesetzt. Abonniere sie in OnEnable (oder Awake /
Start) eines Szenenobjekts, nicht in einem statischen Konstruktor oder einem Editor-Skript, und melde dich in
OnDisable wieder ab.
Beispiel: Einheiten für einen Spieltest spawnen
Diese Komponente spawnt beim Start eines Spieltests eine Einheit pro Startposition und schaltet deine Gameplay-Systeme ein (RTS-Kamera, Nebel des Krieges, HUD). Wenn der Test endet, entfernt sie die Einheiten und schaltet die Systeme wieder aus. Füge sie einem beliebigen Objekt in der Editorszene hinzu und weise ihr ein Einheiten-Prefab zu.
using System.Collections.Generic;
using UnityEngine;
using RTSTerrainStudio;
/// <summary>Starts a tiny "game" whenever the in-game editor runs a play test.</summary>
public class MyPlaytestGame : MonoBehaviour
{
[Tooltip("Spawned once at every start location of the map.")]
[SerializeField] GameObject unitPrefab;
[Tooltip("Gameplay components that should only run during a play test, " +
"for example your RTS camera script, the fog of war manager or your HUD.")]
[SerializeField] Behaviour[] gameplaySystems = new Behaviour[0];
readonly List<GameObject> spawned = new List<GameObject>();
void OnEnable()
{
// Static event: always pair += in OnEnable with -= in OnDisable.
RTSRuntimeEditorEvents.PlaytestChanged += HandlePlaytestChanged;
SetSystemsEnabled(RTSRuntimeEditorEvents.IsPlaytesting);
}
void OnDisable()
{
RTSRuntimeEditorEvents.PlaytestChanged -= HandlePlaytestChanged;
DespawnAll();
}
void HandlePlaytestChanged(bool playtesting)
{
if (playtesting) StartGame();
else StopGame();
}
void StartGame()
{
var terrain = RTSTerrain.Main; // the first RTS Terrain in the scene
if (terrain == null || terrain.Map == null) return;
foreach (var start in terrain.Map.StartLocations)
{
// Start locations are stored in map-local space: convert to world space
// and put the unit on the ground.
Vector3 position = terrain.LocalToWorld(start.position);
position.y = terrain.GetHeight(position);
Quaternion rotation = Quaternion.Euler(0f, terrain.transform.eulerAngles.y + start.yaw, 0f);
var unit = Instantiate(unitPrefab, position, rotation);
unit.name = "Player " + (start.player + 1) + " Unit";
spawned.Add(unit);
}
SetSystemsEnabled(true);
}
void StopGame()
{
// Called BEFORE the editor restores the map, so everything is still in place.
DespawnAll();
SetSystemsEnabled(false);
}
void DespawnAll()
{
foreach (var unit in spawned)
if (unit != null) Destroy(unit);
spawned.Clear();
}
void SetSystemsEnabled(bool enabled)
{
foreach (var system in gameplaySystems)
if (system != null) system.enabled = enabled;
}
}
StartLocation.position ist ein Unity.Mathematics.float3, das automatisch in
Vector3 umgewandelt wird. Wenn dein Skript in einer eigenen Assembly Definition liegt, füge
Referenzen auf RTSTerrainStudio.Runtime und Unity.Mathematics hinzu.
Beispiel: die Esc-Taste mit deinem Spiel teilen
Viele RTS-Spiele verwenden Esc, um einen Befehl oder eine Gebäudeplatzierung abzubrechen. Rufe
ConsumeEscape() in dem Frame auf, in dem du die Taste auswertest, dann beendet der Editor den
Spieltest nicht. Der Editor prüft das in LateUpdate, daher spielt die Ausführungsreihenfolge
deiner Skripte keine Rolle.
using UnityEngine;
using RTSTerrainStudio;
public class MyBuildingPlacement : MonoBehaviour
{
bool placing; // true while a building ghost follows the cursor
void Update()
{
if (placing && RTSInput.GetKeyDown(RTSKey.Escape))
{
placing = false; // cancel the placement ...
RTSRuntimeEditorEvents.ConsumeEscape(); // ... and keep the play test running
}
}
}
Die Beispielskripte RTSBuildingPlacementDemo, RTSCommandController und
RTSSelectionManager machen das bereits. In der Demo hebt der erste Druck auf Esc die
Auswahl auf, der nächste kehrt zum Editor zurück.
Spieltest-Checkliste für dein eigenes Spiel
- Lasse dein RTS-Kameraskript, deinen Manager für den Nebel des Krieges und dein HUD in der Szene
deaktiviert und aktiviere sie in
PlaytestChanged(true). Die Demoszene macht dasselbe mitRTSCameraControllerundRTSFogOfWarManager. - Die eingebaute Kamera des Editors ist während des Tests ausgeschaltet, sodass dein Kameraskript die volle Kontrolle über die Kamera hat.
- Entferne in
PlaytestChanged(false)alles, was du gespawnt hast. Kartenänderungen musst du nicht rückgängig machen, denn der Editor stellt die Karte wieder her. - Im Multiplayer (Mirror-Integration) werden Kartenänderungen, die während eines laufenden Spiels vorgenommen werden,
nicht live übertragen. Rufe
RTSNetworkMapSync.SendMapToAll()auf, um die Karte erneut zu senden.
Karten in Builds speichern und laden
Wo Karten gespeichert werden
Karten werden in RTSRuntimeEditor.MapsFolder gespeichert. Standardmäßig ist das der Ordner
Maps in RTSMapSerializer.StorageRoot; diesen Pfad liefert auch
RTSMapSerializer.DefaultMapsFolder. StorageRoot entspricht auf allen Plattformen außer
dem Web Application.persistentDataPath. Der Ordner wird beim ersten Speichern angelegt.
Unity legt persistentDataPath je nach Plattform fest.
Unter Windows liegt er unter %USERPROFILE%\AppData\LocalLow\<Company>\<Product>; Firmen-
und Produktname stammen aus den Player Settings. Auf Desktop-Plattformen öffnet
File ▸ Show Maps Folder den Ordner, und der Kartenbrowser zeigt den vollständigen Pfad an.
In Web-Builds liegen die Karten im Speicher des Browsers. File ▸ Download Map und File ▸ Import Map... übertragen sie vom Browser auf den Computer des Spielers und zurück. Siehe Karten im Browser.
Zu jeder Karte gibt es bis zu zwei Dateien:
| Datei | Beispiel | Beschreibung |
|---|---|---|
| Karte | Glacier Pass (2v2).rtsmap | Die vollständige Karte: Höhen, Klippen, Texturen, Wasser, Vegetation, Dekor, Pathing, Startpositionen, Regionen, Marker, Karteninfo und Umgebung. Sie verwendet dasselbe Format wie Karten im Unity Editor. |
| Vorschau | Glacier Pass (2v2).preview.png | Ein 192 × 192 Pixel großes Minikartenbild für den Kartenbrowser. Es wird nur geschrieben, wenn Write Preview Images eingeschaltet ist, und zusammen mit der Karte gelöscht. |
Der Dateiname wird aus dem Kartennamen gebildet. Zeichen, die in Dateinamen nicht erlaubt sind, werden durch
_ ersetzt, Punkte am Ende werden entfernt, und aus einem leeren Namen wird New Map.
Nicht gespeicherte Änderungen
- Solange es nicht gespeicherte Änderungen gibt, zeigt die Statusleiste einen Punkt vor dem Kartennamen.
- New Map, Open und Quit fragen zuerst nach: Save, Don't Save oder Cancel.
- Wenn der Spieler das Spielfenster schließt, während die Karte nicht gespeicherte Änderungen hat, bricht der Editor das Beenden ab und fragt ebenfalls nach, sofern Confirm Quit With Unsaved Changes nicht ausgeschaltet ist. Im Webbrowser fragt stattdessen der Browser selbst Leave site?, bevor der Tab geschlossen oder neu geladen wird; auch das richtet sich nach dieser Option.
- Ein Spieltest ändert nichts daran, ob die Karte als nicht gespeichert gilt. Der Editor stellt diesen Zustand zusammen mit der Karte wieder her.
- Die Karte, mit der die Szene startet (das dem Terrain zugewiesene Karten-Asset), hat noch keine Datei im Kartenordner. Deshalb wirst du beim ersten Ctrl+S nach einem Namen gefragt.
Testen im Unity Editor: Wenn du eine Karte im Play Mode mit dem In-Game-Editor bearbeitest und
den Play Mode dann beendest, fragt Unity: Save map changes made in Play Mode? Save
schreibt die Änderungen in das Karten-Asset des Terrains in deinem Projekt. Mit Ctrl+S
gespeicherte Karten landen dagegen im Kartenordner. Du kannst eine solche .rtsmap-Datei in deinen
Assets-Ordner kopieren, um sie als Karten-Asset zu verwenden.
Karten mit deinem Spiel ausliefern
Der Kartenbrowser zeigt nur Karten aus dem Kartenordner an. Wenn Spieler deine eigenen Karten als Ausgangspunkt bekommen
sollen, kopiere sie beim ersten Start dorthin. Dieses Beispiel kopiert alle Dateien aus
StreamingAssets/Maps (Karten und Vorschaubilder) und überschreibt nie eine vom Spieler bearbeitete
Version:
using System.IO;
using UnityEngine;
using RTSTerrainStudio;
/// <summary>Copies bundled maps into the in-game editor's maps folder (desktop platforms).</summary>
public class InstallBundledMaps : MonoBehaviour
{
[SerializeField] RTSRuntimeEditor editor;
void Awake()
{
// Put your .rtsmap files (and optional .preview.png files) into Assets/StreamingAssets/Maps.
string source = Path.Combine(Application.streamingAssetsPath, "Maps");
// On Android and WebGL, StreamingAssets is not a normal folder: use UnityWebRequest there instead.
if (!Directory.Exists(source)) return;
string target = editor.MapsFolder; // StorageRoot/Maps unless you changed it
Directory.CreateDirectory(target);
foreach (string file in Directory.GetFiles(source))
{
if (file.EndsWith(".meta")) continue; // only matters when running in the Unity editor
string destination = Path.Combine(target, Path.GetFileName(file));
if (!File.Exists(destination)) File.Copy(file, destination);
}
}
}
Von Spielern erstellte Karten in deinem Spiel spielen
Dein Spiel kann Karten auch ohne den Editor aus dem Kartenordner laden, zum Beispiel in einer eigenen Spiellobby.
RTSMapSerializer.TryReadHeaderFromFile liest Name, Größe, Autor und Spielerzahl, ohne die ganze Karte
zu laden. Geladen wird sie dann mit RTSTerrain.LoadFromFile.
using System;
using System.IO;
using UnityEngine;
using RTSTerrainStudio;
public static class MapLobby
{
/// <summary>Logs every map in the default maps folder.</summary>
public static void ListMaps()
{
string folder = RTSMapSerializer.DefaultMapsFolder;
if (!Directory.Exists(folder)) return;
foreach (string path in Directory.GetFiles(folder, "*" + RTSMapSerializer.FileExtension))
{
// Reads only the small header: fast, even for large maps.
if (RTSMapSerializer.TryReadHeaderFromFile(path, out RTSMapHeader header))
Debug.Log(header.name + " by " + header.author + ": " + header.width + " x " + header.height +
" cells, " + header.maxPlayers + " players, modified " + header.Modified);
}
}
/// <summary>Loads a map file into the main terrain. Returns false when the file cannot be read.</summary>
public static bool Play(string path)
{
var terrain = RTSTerrain.Main;
if (terrain == null) return false;
try
{
terrain.LoadFromFile(path); // throws when the file is missing or damaged
return true;
}
catch (Exception e)
{
Debug.LogError("Could not load " + path + ": " + e.Message);
return false;
}
}
}
Eine Karte speichert ihre Inhalte als IDs (Bodenschichten, Vegetations- und Dekor-Prototypen). Von Spielern erstellte Karten sehen nur dann richtig aus, wenn dasselbe Tileset, dieselbe Vegetationsbibliothek und dieselbe Dekorbibliothek verwendet werden wie beim Terrain der Editorszene. Weise dem Terrain in deinen Spielszenen deshalb dieselben Assets zu.
Den Editor per Code steuern
Alles, was die Oberfläche bietet, kannst du auch per Code über RTSRuntimeEditor (Namespace
RTSTerrainStudio) erledigen. Die wichtigsten Member:
| Member | Beschreibung |
|---|---|
| Zustand | |
bool EditorVisible { get; set; } | Blendet den gesamten Editor ein oder aus (wie die Taste zum Ein- und Ausblenden). Beim Ausblenden wird ein laufender Pinselstrich beendet, offene Dialoge werden geschlossen und die Terrain-Ansicht geht an das Spiel zurück. |
bool PanelsVisible { get; set; } | Blendet nur die Panels aus (H). Das Bearbeiten funktioniert weiterhin. |
bool IsPlaytesting { get; } | True, solange ein von diesem Editor gestarteter Spieltest läuft. |
RTSKey ToggleKey { get; set; } | Die Taste zum Ein- und Ausblenden. RTSKey.None schaltet sie ab und blendet die Schließen-Schaltfläche aus (außer in Webbrowsern sowie auf Smartphones und Tablets, siehe HasReliableFunctionKeys). |
RTSTerrain Terrain { get; set; } | Das bearbeitete Terrain. Beim Setzen wird das Terrain gewechselt und der Undo-Verlauf gelöscht. |
Camera TargetCamera { get; set; } | Die Kamera, über die ermittelt wird, was unter dem Mauszeiger liegt, und mit der die Gizmos gezeichnet werden. |
RTSRuntimeEditorCamera EditorCamera { get; } | Die integrierte Kamera oder null. |
RTSEditSession Session { get; } | Die Bearbeitungssitzung: Werkzeuge, Pinsel, Symmetrie, Auswahl, Rückgängigmachen. |
ToolCategory ActiveCategory { get; } | Die Kategorie des aktiven Werkzeugs. |
bool HasHover, Vector3 HoverWorldPosition | Ob sich der Mauszeiger über dem Terrain befindet, und die Weltposition darunter. |
bool IsPointerOverUI { get; } | True, solange sich der Mauszeiger über einem Editor-Panel befindet. |
bool ShowGrid, bool ShowMarkers | Die Schalter für G und M. |
PathingOverlayMode? PathingOverlay, float OverlayOpacity | Das Pathing-Overlay (null = aus) und seine Deckkraft (0 bis 1). |
string MapsFolder { get; set; } | Der Kartenordner. Ist der Wert leer, wird RTSMapSerializer.DefaultMapsFolder verwendet. Ein relativer Pfad bezieht sich auf RTSMapSerializer.StorageRoot. |
bool ShowQuitCommand, string ManualUrl | Der Menüeintrag File ▸ Quit und der Link zum Handbuch (siehe Inspector). |
bool HasManual { get; } | True, wenn es für Open Manual eine Seite zum Öffnen gibt. |
static bool CanShowFolders, static bool CanQuitApplication | Plattformprüfungen: Ein Desktop-Dateibrowser ist verfügbar bzw. das Beenden der Anwendung ist möglich (nicht unter WebGL, iOS oder Android). |
static bool RunsInBrowser, static bool StoresMapsInBrowser | RunsInBrowser ist in Web-Builds true. Dort gelten die Einschränkungen des Browsers, und der Befehl New Map liegt auf Alt+N. StoresMapsInBrowser ist true, wenn Karten im Browserspeicher abgelegt werden (WebGL-Player). Siehe Web-Builds (WebGL). |
static bool HasReliableFunctionKeys | False in Webbrowsern und auf mobilen Plattformen. Dort blendet die Schließen-Schaltfläche die Schaltfläche Open Map Editor ein. |
VisualElement RootElement, UIDocument Document, RuntimeToolGizmos Gizmos | Das Wurzelelement der Editor-UI, ihr UI-Dokument und der Gizmo-Renderer. Alle sind null, solange die Komponente noch nicht aktiviert wurde. |
| Befehle | |
bool SetActiveTool(string toolId) | Aktiviert ein Werkzeug anhand seiner ID, zum Beispiel "paint.layer". Gibt false zurück, wenn es die ID nicht gibt. |
void SelectCategory(ToolCategory category) | Wie die Zahlentasten: Aktiviert das zuletzt verwendete Werkzeug der Kategorie oder, wenn die Kategorie bereits aktiv ist, das nächste Werkzeug. |
RTSTool GetRememberedTool(ToolCategory category) | Das zuletzt verwendete Werkzeug einer Kategorie oder ihr erstes Werkzeug. |
void Undo(), void Redo() | Rückgängig machen und wiederherstellen. Während eines Spieltests bewirken beide nichts. |
bool OpenMap(string path) | Lädt eine .rtsmap-Datei. Nicht gespeicherte Änderungen werden ohne Rückfrage verworfen. |
bool SaveMap(string path) | Speichert unter dem angegebenen Pfad, ergänzt bei Bedarf die Endung .rtsmap und schreibt das Vorschaubild. |
bool SaveCurrent() | Wie Ctrl+S. Gibt true zurück, wenn die Karte sofort geschrieben wurde. Hat die Karte noch keine Datei, öffnet die Methode den Dialog Save As und gibt false zurück. |
void NewMap(MapCreationSettings settings) | Erstellt eine neue flache Karte, ohne wegen nicht gespeicherter Änderungen nachzufragen. |
bool DeleteMapFile(string path) | Löscht eine Kartendatei und ihr Vorschaubild. |
ShowNewMapDialog(), ShowOpenDialog(), ShowSaveAsDialog(), ShowGenerateDialog(), ShowMapInfoDialog(), ShowResizeDialog(), ShowHelp() | Öffnen die Dialoge mit demselben Verhalten wie über die Menüs. New und Open fragen bei nicht gespeicherten Änderungen zuerst nach. |
void StartPlaytest(), void StopPlaytest() | Starten bzw. beenden einen Spieltest. |
void TogglePathingOverlay() | Wie P. |
void DeleteSelectedDecor(), void DuplicateSelectedDecor() | Löschen bzw. duplizieren die Dekorauswahl. Jeder Aufruf ist ein Undo-Schritt. |
void ShowMapsFolder(), void OpenManual(), void Quit() | Die Befehle aus den Menüs File und Help. Quit fragt bei nicht gespeicherten Änderungen nach. |
bool DownloadMap(), void ImportMap() | Nur in Web-Builds: File ▸ Download Map und File ▸ Import Map.... Browser zeigen die Dateiauswahl nur als Reaktion auf einen Klick oder Tastendruck an. Rufe ImportMap daher aus einem Klick-Handler auf. |
void ShowToast(string message, ToastType type = ToastType.Info) | Zeigt eine Toast-Meldung an. ToastType ist Info, Success, Warning oder Error. |
void ShowToast(string message, ToastType type, string key) | Ist noch eine Toast-Meldung mit demselben Schlüssel sichtbar, wird diese aktualisiert, und es kommt keine neue hinzu. |
| Events | |
event Action<string> MapSaved | Nachdem eine Karte gespeichert wurde (vollständiger Dateipfad). |
event Action<string> MapLoaded | Nachdem eine Karte aus einer Datei geöffnet (vollständiger Pfad) oder neu erstellt wurde (null). |
event Action<bool> VisibilityChanged | Wenn sich EditorVisible ändert. |
MapLoaded und RTSRuntimeEditorEvents.MapOpened werden bei File ▸ New und Open ausgelöst.
Um auf jede Kartenänderung zu reagieren, auch auf Resize und auf Karten, die dein eigener Code lädt, abonniere
RTSTerrain.MapLoaded am Terrain.
Beispiel: den Editor aus deinem eigenen Menü öffnen
Viele Spiele öffnen den Karteneditor über das Hauptmenü statt über eine Taste. Schalte die Taste zum Ein- und Ausblenden ab, öffne den Editor über eine Schaltfläche und füge der obersten Ebene des Editors eine Schaltfläche „Back to Menu“ hinzu:
using UnityEngine;
using UnityEngine.UIElements;
using RTSTerrainStudio;
public class MapEditorLauncher : MonoBehaviour
{
[SerializeField] RTSRuntimeEditor editor; // with "Start Visible" turned off
[SerializeField] GameObject mainMenu; // your own menu UI
void Start()
{
// No F2 key: the game decides when the editor is shown (on desktop this also hides the close button).
editor.ToggleKey = RTSKey.None;
// RootElement exists once the editor component is enabled (it is created in its OnEnable).
// If you disable and enable the editor component later, its UI is rebuilt: add the button again.
var back = new Button(BackToMenu) { text = "Back to Menu" };
back.AddToClassList("rts-button"); // use the editor's button style
back.style.position = Position.Absolute;
back.style.right = 16;
back.style.bottom = 56; // just above the status bar
editor.RootElement.Add(back); // hidden together with the editor
editor.VisibilityChanged += visible => mainMenu.SetActive(!visible);
}
/// <summary>Hook this up to a "Map Editor" button in your main menu.</summary>
public void OpenEditor() => editor.EditorVisible = true;
void BackToMenu()
{
if (editor.IsPlaytesting) editor.StopPlaytest();
editor.EditorVisible = false;
editor.ShowToast("Map editor closed", ToastType.Info);
}
}
Beispiel: das HUD deines Spiels beim Bearbeiten ausblenden
using UnityEngine;
using RTSTerrainStudio;
public class HudVisibility : MonoBehaviour
{
[SerializeField] RTSRuntimeEditor editor;
[SerializeField] GameObject hud;
void OnEnable()
{
editor.VisibilityChanged += HandleVisibilityChanged;
RTSRuntimeEditorEvents.PlaytestChanged += HandlePlaytestChanged;
Refresh();
}
void OnDisable()
{
editor.VisibilityChanged -= HandleVisibilityChanged;
RTSRuntimeEditorEvents.PlaytestChanged -= HandlePlaytestChanged;
}
void HandleVisibilityChanged(bool visible) => Refresh();
void HandlePlaytestChanged(bool playtesting) => Refresh();
// The editor stays "visible" during a play test (only its panels make room),
// so both states are checked.
void Refresh() => hud.SetActive(!editor.EditorVisible || RTSRuntimeEditorEvents.IsPlaytesting);
}
Die Oberfläche anpassen
Wo die Styles liegen
Datei (in Resources/RTSTerrainStudio/UI) | Geladen als | Zweck |
|---|---|---|
RTSRuntimeTheme.tss | RTSRuntimeEditor.ThemeResourcePath = "RTSTerrainStudio/UI/RTSRuntimeTheme" | Das Theme-Stylesheet. Es importiert Unitys Standard-Laufzeit-Theme (Schieberegler, Schalter, Textfelder, Scroll-Ansichten) und RTSRuntimeEditor.uss. |
RTSRuntimeEditor.uss | RTSRuntimeEditor.StyleSheetResourcePath = "RTSTerrainStudio/UI/RTSRuntimeEditor" | Alle Styles des Editors. Jeder Klassenname beginnt mit rts-, und Typselektoren wie Label gelten nur unterhalb von .rts-root. So verändert der Editor nie das Aussehen deiner Spiel-UI, selbst wenn dein Panel dieses Theme verwendet. |
Wenn deine Panel Settings ein anderes Theme verwenden, fügt der Editor RTSRuntimeEditor.uss seinem
eigenen Wurzelelement hinzu. So sieht er in beiden Fällen gleich aus.
Farb-Tokens
Alle Farben sind Variablen, die auf .rts-root definiert sind. Ändere sie, um dem gesamten Editor ein neues Aussehen zu geben:
| Variable | Standard | Verwendet für |
|---|---|---|
--rts-panel | rgba(20, 24, 30, 0.92) | Panel-Hintergründe (obere Leiste, Werkzeugleiste, Werkzeug-Panel, Statusleiste). |
--rts-panel-solid | rgb(22, 26, 33) | Deckende Hintergründe (Dialoge, Popups). |
--rts-raised / --rts-hover / --rts-pressed | Weiß mit 6 % / 11 % / 3 % Deckkraft | Zustände von Schaltflächen. |
--rts-border / --rts-border-strong | Weiß mit 8 % / 16 % Deckkraft | Rahmen und Trennlinien. |
--rts-input | rgba(0, 0, 0, 0.32) | Hintergründe von Textfeldern und Schiebereglern. |
--rts-text / --rts-text-dim / --rts-text-faint | rgb(226, 231, 238) / rgb(150, 160, 174) / rgb(104, 114, 128) | Text, Sekundärtext, Hinweise. |
--rts-accent / --rts-accent-hover / --rts-accent-dim / --rts-accent-text | Blautöne rund um rgb(61, 169, 252) | Ausgewählte Werkzeuge, primäre Schaltflächen, Play Test. |
--rts-danger / --rts-danger-dim | rgb(239, 83, 80) | Schaltflächen zum Löschen und Toast-Meldungen für Fehler. |
--rts-warning / --rts-success | rgb(255, 183, 77) / rgb(102, 187, 106) | Toast-Meldungen für Warnungen und Erfolge. |
--rts-radius / --rts-radius-small | 6px / 4px | Eckenradius. |
Neues Aussehen, ohne Paketdateien anzufassen
Bearbeite die Dateien im Ordner Resources des Pakets nicht direkt. Ein Paket-Update überschreibt sie.
Lege deine Änderungen stattdessen in einem eigenen Stylesheet ab.
- Erstelle ein Stylesheet in deinem Projekt, zum Beispiel
Assets/UI/MyEditorSkin.uss, und überschreibe die Tokens. Der Selektor:root .rts-rootist spezifischer als.rts-rootaus dem Paket, deshalb setzen sich deine Werte durch:/* MyEditorSkin.uss: an orange accent and warmer panels */ :root .rts-root { --rts-accent: rgb(255, 160, 50); --rts-accent-hover: rgb(255, 190, 110); --rts-accent-dim: rgba(255, 160, 50, 0.22); --rts-accent-text: rgb(255, 210, 160); --rts-panel: rgba(30, 24, 20, 0.94); } - Füge das Stylesheet beim Start dem UI-Dokument des Editors hinzu:
using UnityEngine; using UnityEngine.UIElements; using RTSTerrainStudio; public class EditorSkin : MonoBehaviour { [SerializeField] RTSRuntimeEditor editor; [SerializeField] StyleSheet skin; // drag MyEditorSkin.uss here void Start() { // The document root holds the editor and its toast layer, so both get the new colours. editor.Document.rootVisualElement.styleSheets.Add(skin); } }
Auf dieselbe Weise kannst du auch einzelne Teile umgestalten. Die wichtigsten Klassen sind .rts-topbar,
.rts-leftbar, .rts-toolpanel, .rts-statusbar, .rts-dialog,
.rts-popup, .rts-toast (mit den Varianten --success, --warning und
--error), .rts-hint, .rts-playtest-bar, .rts-button und
.rts-selected. Der UI Toolkit Debugger (Window ▸ UI Toolkit ▸ Debugger) zeigt die Klassen jedes
Elements an, während das Spiel läuft.
Panel Settings, Skalierung und Sortierung
- Panel Settings leer (Standard): Der Editor erstellt eigene Panel Settings mit dem RTS-Theme. Die UI skaliert mit der Bildschirmhöhe (Referenz 1920 × 1080), aber nie unter 85 %. Ui Scale multipliziert das Ergebnis. Das ist nützlich für 4K-Bildschirme oder für Spieler, die größeren Text möchten.
- Eigene Panel Settings: Weise ein Asset zu, um Theme und Skalierung selbst zu steuern.
erstellt ein fertiges Asset unter
Resources/RTSTerrainStudio/UI/RTSRuntimePanelSettings.asset(RTS-Theme, Skalierung mit der Bildschirmgröße, 1920 × 1080). Die Szene In-Game Editor Demo verwendet es. - Enforce Minimum UI Scale (standardmäßig an): Wenn dein Asset mit der Bildschirmgröße skaliert und nicht in eine Textur rendert, arbeitet der Editor mit einer Laufzeitkopie davon. Das Asset selbst wird nie verändert. Die Kopie skaliert auf großen Bildschirmen genau wie das Asset, wird in kleinen Fenstern nie kleiner als 85 %, und Ui Scale multipliziert sie. Ihre Sortierreihenfolge liegt einen halben Schritt über der des Assets. So bleibt der Editor über einem Spiel-HUD, das dasselbe Asset verwendet (das HUD erhält dann ein eigenes Panel). Schalte die Option aus, um das Asset unverändert zu verwenden, zum Beispiel um ein Panel mit deiner Spiel-UI zu teilen. Ui Scale und das Minimum von 85 % werden dann nicht angewendet.
- Web-Builds: Das Minimum von 85 % wird in CSS-Pixeln gemessen. So behält die UI auf hochauflösenden Bildschirmen dieselbe Größe.
- Sorting Order: Standardmäßig 1000, damit der Editor dein HUD überdeckt. Senke den Wert, wenn Teile deiner UI über dem Editor erscheinen müssen.
- Eigene Bedienelemente hinzufügen: Füge Elemente zu
RootElementhinzu (siehe das Launcher-Beispiel oben). Sie werden zusammen mit dem Editor ausgeblendet. Befindet sich der Mauszeiger über ihnen, blockiert das wie bei jedem anderen Panel das Malen.
Den In-Game-Editor mit dem Spiel ausliefern
Arbeite diese Liste ab, bevor du einen Build deines Spiels mit dem In-Game-Editor erstellst.
- Führe aus und behebe die gemeldeten Probleme. Für Builds ist die Prüfung Shader references am wichtigsten (siehe nächsten Schritt).
- Shader werden über
RTSShaderReferenceseingebunden. Unity lässt jeden Shader aus dem Build weg, den kein Material im Build verwendet. Das Paket erstellt die meisten seiner Materialien zur Laufzeit (Terrain, Wasser, Nebel des Krieges und die Gizmo-Shader des EditorsHidden/RTS Terrain Studio/GizmoLinesundHidden/RTS Terrain Studio/GizmoGhost). Deshalb hinterlegt es die Shader im AssetResources/RTSTerrainStudio/RTSShaderReferences. Dieses Asset wird automatisch aktualisiert, nachdem Skripte kompiliert wurden, wenn Shader importiert werden und vor jedem Player-Build. Fehlt es oder ist es veraltet, verwende die Korrektur Update des Validators. Ohne diese Shader erscheinen im Build weder die Pinselumrisse noch die Dekorvorschauen. - Behalte den Ordner
Resources/RTSTerrainStudiomitUI/RTSRuntimeTheme.tss,UI/RTSRuntimeEditor.ussundRTSShaderReferences.asset. Der Editor lädt diese Dateien über ihren Resources-Pfad. Fehlt das Theme, meldet die Console Controls will be unstyled. - Panel Settings: Wenn du ein Asset vom Typ Panel Settings zugewiesen hast, referenziert die Szene es, und es landet automatisch im Build. Hast du das Feld leer gelassen, laden die zur Laufzeit erstellten Panel Settings das Theme aus Resources (Schritt 3).
- Eingabe: Der Editor funktioniert mit dem Paket Input System und mit dem alten Input Manager. Siehe Eingabeverarbeitung.
- Lege die Optionen für die Spieler fest: Manual Url (Link zu deinem Modding-Leitfaden; leer lassen, um die Schaltfläche auszublenden), Show Quit Command (ausschalten, wenn dein Spiel ein eigenes Menü hat), Toggle Key und Start Visible.
- Kartenordner: Der Standardordner
MapsinRTSMapSerializer.StorageRoot(persistentDataPath) ist auf allen Desktop-Plattformen beschreibbar. Wenn du Spielern Beispielkarten mitgeben willst, kopiere sie beim ersten Start dorthin (siehe Karten mit deinem Spiel ausliefern). - Web-Builds: Installiere das Vollfenster-Template und lies den Abschnitt Web-Builds (WebGL). Er behandelt Browserspeicher, Downloads, Tasten und Hosting.
- Eigene Werkzeuge und Code-Stripping: Steht die Einstellung Managed Stripping
Level über Minimal, markiere deine eigenen Werkzeuge mit
[Preserve](siehe Code-Stripping und eigene Werkzeuge). - Inhalte: Spieler können nur das Tileset, die Vegetationsbibliothek und die Dekorbibliothek verwenden, die dem Terrain in der Editorszene zugewiesen sind. Sie landen im Build, weil die Szene sie referenziert.
- Teste einen Development Build: Probiere darin je einmal Malen, Speichern, Öffnen, einen Spieltest und das Beenden mit nicht gespeicherten Änderungen aus.
Du lieferst den In-Game-Editor nicht mit aus? Sein Code liegt in Runtime/RuntimeEditor,
die Menüunterstützung in Editor/RuntimeEditorSupport. Ein Projekt, das ihn nicht braucht, kann den
Ordner Runtime/RuntimeEditor löschen. RTSRuntimeEditorEvents bleibt in Runtime/Core,
sodass Gameplay-Code, der auf Spieltests reagiert, weiterhin kompiliert.
Fehlerbehebung
| Problem | Ursache und Lösung |
|---|---|
| Die Hinweismeldung No RTS Terrain found erscheint. | Die Szene enthält kein aktiviertes RTS Terrain, oder das zugewiesene Terrain ist deaktiviert. Füge über eines hinzu oder aktiviere es. |
| Die Hinweismeldung No camera found erscheint. | Keine Kamera hat das Tag MainCamera, und Target Camera ist leer. Gib deiner Kamera dieses Tag oder weise sie zu. |
| Klicks auf die Karte bewirken nichts. | Der Mauszeiger befindet sich über einem Panel, Alt ist gedrückt (Kameradrehung), ein Dialog ist geöffnet, oder kein Werkzeug ist aktiv. Ist kein Werkzeug aktiv, zeigt die Statusleiste Pick a tool on the left an. |
| Tastenkürzel bewirken nichts. | Ein Textfeld hat den Tastaturfokus, oder ein Dialog ist geöffnet. Drücke Esc oder klicke auf die Karte. |
| F5 oder F2 bewirkt nichts. | F5 funktioniert nur, solange der Editor sichtbar ist und kein Dialog offen ist. Die Taste zum Ein- und Ausblenden wird während eines Spieltests ignoriert (beende ihn zuerst mit Esc oder F5), ebenso während du in ein Feld tippst. Auf Macs und vielen Laptops sind die Funktionstasten Medientasten: Halte Fn gedrückt, oder verwende die Schaltfläche Play Test und in Web-Builds die Schaltfläche Open Map Editor. |
| Esc beendet den Spieltest, obwohl mein Spiel die Taste verwendet hat. | Rufe in diesem Frame RTSRuntimeEditorEvents.ConsumeEscape() auf (siehe das Beispiel). |
| Während eines Spieltests passiert nichts. | Kein Skript reagiert auf RTSRuntimeEditorEvents.PlaytestChanged. Füge RTSDemoGame oder eine eigene Komponente hinzu (siehe Play Test). |
| Nach einem Spieltest erscheint die Toast-Meldung undo history was reset. | Dein Gameplay-Code hat die Karte während des Tests ersetzt, neu geladen oder in der Größe geändert. Die Karte selbst wird korrekt wiederhergestellt. |
| Die UI ist zu klein oder zu groß. | Ändere Ui Scale. Wenn du eigene Panel Settings zugewiesen hast, die nicht mit der Bildschirmgröße skalieren, oder Enforce Minimum UI Scale ausgeschaltet hast, ändere stattdessen die Skalierung in diesem Asset. Verwendet ein Web-Build ein Template mit fester Größe, installiere das Vollfenster-Template (siehe Das Vollfenster-Template). |
| Mein HUD verdeckt den Editor, oder der Editor verdeckt ein Menü. | Passe Sorting Order an (Standard 1000). |
| Bedienelemente erscheinen ohne Styling (weißes Standardaussehen von Unity). | Das Theme wurde in keinem Resources-Ordner gefunden. Stelle Resources/RTSTerrainStudio/UI wieder her. |
| Pinselumrisse und Vorschauen fehlen im Build. | Die Gizmo-Shader wurden nicht in den Build aufgenommen. Führe aus und aktualisiere die Shader-Referenzen. |
| Der Willkommenshinweis erscheint nie wieder. | Der Spieler hat ihn geschlossen. Verwende Open Welcome Hint im F1-Fenster oder lösche den PlayerPrefs-Schlüssel RTSTerrainStudio.InGameEditor.HintDismissed. |
| Meine Karten erscheinen nicht im Kartenbrowser. | Der Browser listet nur .rtsmap-Dateien auf, die direkt in dem Kartenordner liegen, der oben im Browser angezeigt wird. Kopiere die Dateien dorthin und klicke auf Refresh. |
| Eine Karte zeigt Unreadable or damaged file an. | Die Datei ist keine gültige .rtsmap-Datei, oder sie ist unvollständig (zum Beispiel durch einen abgebrochenen Kopiervorgang). |
| In einem Web-Build gespeicherte Karten sind verschwunden. | Der Browser speichert sie pro Website. Unter einer anderen Adresse oder nach dem Löschen der Browserdaten sind daher keine Karten vorhanden. Außerdem kann Safari die Daten von Websites löschen, die sieben Tage lang nicht genutzt wurden. Verwende File ▸ Download Map, um Kopien aufzubewahren (siehe Karten im Browser). |
| Der Editor hat in einem Build keine Werkzeuge. | Code-Stripping hat sie entfernt. Behalte die Datei Runtime/link.xml des Pakets und markiere deine eigenen Werkzeuge mit [Preserve] (siehe Code-Stripping und eigene Werkzeuge). |
Rendering und Weltsysteme
Dieses Kapitel erklärt, wie aus einer Karte Pixel werden: wie das Terrain-Mesh aufgebaut und schattiert wird und wie Vegetation, Wasser, Dekor und Szenenbeleuchtung gezeichnet werden. Jeder Abschnitt beginnt mit dem, was du siehst, und mit den Einstellungen, die du ändern kannst. Weiter unten folgen die Scripting-API, der Shader-Vertrag und die Performance-Zahlen, die Programmierer brauchen.
Die Rendering-Komponenten
Jedes System liest dieselben MapData eines RTSTerrain und baut nur neu auf, was eine Bearbeitung verändert hat. Die Systeme sind eigene Komponenten am GameObject des Terrains. fügt sie alle für dich hinzu.
| Komponente | Zeichnet | Erzeugte Objekte | Abschnitt |
|---|---|---|---|
RTSTerrain | Boden, Klippen, Rampen (Chunk-Meshes, ein gemeinsames Material), das Zellraster, Overlays und den Pinselcursor | Wurzelobjekt „RTS Chunks“ mit einem Kindobjekt pro Chunk | Terrain-Rendering, Terrain-Shader |
RTSVegetationRenderer | Gemaltes Gras und Pflanzen (Details), Bäume | Verborgenes Objekt „RTS Vegetation Objects“ (Collider-Stellvertreter, Bäume als GameObjects) | Vegetation |
RTSWaterRenderer | Transparente Wasseroberfläche mit Tiefenfarbe, Wellen, Lichtbrechung und Schaum | Verborgenes Wurzelobjekt „RTS Water“ mit einem Kindobjekt pro Chunk, der Wasser enthält | Wasser |
RTSDecorManager | Props und Doodads, als GameObjects oder per GPU-Instancing | Container „RTS Decor“ für gespawnte Objekte | Dekor |
RTSEnvironment (optional) | Selbst nichts. Die Komponente überträgt Sonne, Umgebungslicht und Nebel der Karte auf die Szene | Keine | Umgebung |
Erzeugte Objekte sind mit DontSave markiert. Sie werden nie in deine Szenendatei geschrieben: Die .rtsmap-Datei ist die einzige maßgebliche Quelle, und beim Öffnen der Szene wird alles daraus neu aufgebaut. Klickst du in der Scene View auf einen erzeugten Chunk, einen Baum oder eine Wasseroberfläche, wird stattdessen das zugehörige Terrain ausgewählt.
Fehlt eine der Renderer-Komponenten, zeigt der Inspector von RTSTerrain eine Warnung (zum Beispiel „No Water Renderer: painted water is not shown.“) mit einer Schaltfläche Add an.
Terrain-Rendering
Chunks
Die Karte ist in quadratische Chunks mit 16, 32 (Standard) oder 64 Zellen Seitenlänge aufgeteilt. Jeder Chunk ist ein GameObject mit einem MeshFilter, einem MeshRenderer, einem optionalen MeshCollider und einer Komponente RTSTerrainChunk. Alle Chunks teilen sich eine Materialinstanz (RTSTerrain.TerrainMaterial). Dadurch bleibt das Terrain mit dem SRP Batcher kompatibel.
- Chunks werden zeilenweise gespeichert: Index =
z * ChunkCount.x + x. Die letzte Zeile und die letzte Spalte können kleiner sein, wenn die Kartengröße kein Vielfaches der Chunk-Größe ist. - Eine Bearbeitung baut nur die Chunks neu auf, die sie berührt. Jedes geänderte Rechteck wird um eine Zelle erweitert, weil eine Vertex-Änderung auch die Normalen und Klippenformen der Nachbarzellen beeinflusst. Wenn du nahe an einer Chunk-Grenze malst, wird deshalb auch der benachbarte Chunk neu aufgebaut.
- Das Malen von Bodentexturen baut keine Meshes neu auf. Nur die Splat-Texturen werden aktualisiert (siehe Splat-Auflösung).
- Auch das Malen von Wasser baut keine Terrain-Chunks neu auf. Nur der Wasser-Renderer reagiert darauf.
Das Chunk-Mesh liegt in kartenlokalen Koordinaten: Chunk-Objekte haben unter dem verborgenen Wurzelobjekt RTS Chunks des Terrains eine lokale Identitätstransformation. Dieses Wurzelobjekt gleicht die Skalierung des Terrains aus, sodass der Objektraum den kartenlokalen Koordinaten entspricht (X/Z von 0 bis zur Weltgröße der Karte, Y nach oben). Der Transform des Terrains darf verschoben und um Y gedreht werden. Eine Skalierung wird ignoriert.
Mesh-Erzeugung
RTSChunkMeshBuilder erzeugt die Chunk-Meshes mit Burst-Jobs und schreibt sie über Mesh.MeshDataArray:
- Die angeforderten Chunks werden in Batches von bis zu
RTSChunkMeshBuilder.MaxChunksPerBatch(256) Chunks gesammelt. Das begrenzt den Spitzenspeicherbedarf auf sehr großen Karten. - Müssen nur wenige Chunks neu aufgebaut werden (typisch beim Malen), wird jeder Chunk in Zeilenstreifen aufgeteilt. Die Streifen werden auf verschiedenen Worker-Threads erzeugt und anschließend zusammengeführt. So nutzt selbst die Bearbeitung eines einzelnen Chunks alle CPU-Kerne.
BuildChunkGeometryJob(Burst, parallel) erzeugt die Dreiecke. Glatte Zellen und Rampenzellen werden zu einem regelmäßigen Raster aus S × S Quads (S = Surface Subdivisions). Klippenzellen werden auf einem feineren Unterraster (Cliff Subdivisions) mit Marching Squares nachgezeichnet. Das ergibt abgerundete Konturen, einen flachen Boden für jede Stufe und senkrechte Wände dazwischen.- Collider-Daten werden parallel mit
Physics.BakeMeshgebacken und denMeshCollider-Komponenten der Chunks zugewiesen. Buildschließt alle Arbeit ab, bevor die Methode zurückkehrt. So ist eine Bearbeitung noch im selben Frame sichtbar.
Benachbarte Zellen und Chunks erzeugen immer bitidentische Randvertices, weil jede Vertex-Position nur von ihrer Kartenposition abhängt. Deshalb gibt es keine Risse zwischen Chunks. Der Generator folgt der Referenzoberfläche MapGeometryView, die auch von Höhenabfragen, dem Pathing und dem Wasser-Renderer verwendet wird. Der gerenderte Boden ist daher exakt der Boden, den das Gameplay sieht.
Das Vertex-Format ist RTSTerrainVertex (28 Byte: Position, Normale und eine gepackte Farbe). Die Farbe transportiert Daten für den Shader: r = Klippenwandmaske, g = Klippentyp, b = gebackene Ambient Occlusion. Es gibt weder UVs noch Tangenten: Der Shader bildet die Texturen anhand der Vertex-Position ab.
Formgebung der Klippenwände
Klippen werden prozedural erzeugt und brauchen daher keine von Artists modellierten Klippenteile. Vier Einstellungen steuern das Aussehen der Wände:
| Einstellung | Standard | Bereich | Beschreibung |
|---|---|---|---|
| Cliff Noise | 0.18 | 0–0.5 | Zufällige horizontale Verschiebung der Vertices von Klippenwänden, als Anteil einer Zelle. Gibt den Wänden ein natürliches Felsaussehen. 0 = vollkommen glatte Wände. |
| Cliff Noise Scale | 1.6 | ≥ 0.1 | Strukturgröße des Klippenrauschens in Welteinheiten. Kleine Werte ergeben feinen, zerklüfteten Fels, große Werte breite Ausbuchtungen. |
| Cliff Slant | 0.12 | 0–0.4 | Wie weit die Oberkante einer Klippe zurückgezogen wird, als Anteil einer Zelle. Macht die Wände leicht schräg statt perfekt senkrecht. |
| Cliff Wall Segments | 3 | 1–8 | Vertikale Segmente pro Klippenstufe. Mehr Segmente geben dem Rauschen mehr Vertices zum Arbeiten (mehr Relief), kosten aber auch mehr Vertices. |
Rauschen und Schrägung zusammen können einen Vertex nie weiter als 45 % einer Zelle verschieben (RTSChunkMeshParams.MaxDisplacementFraction). Ergeben beide Einstellungen in Summe mehr, werden sie gemeinsam herunterskaliert, sodass sich Bodendreiecke neben einer Wand nie überlappen. In der Nähe von Rampen und am Kartenrand läuft die Verschiebung aus, damit diese Kanten gerade bleiben.
Außerdem wird die Wandgeometrie an ihrem Fuß durch die gebackene Ambient Occlusion (Vertex-Farbe b) abgedunkelt. Wo zwei Wände an einer Zellkante aufeinandertreffen, verwenden beide dieselbe Helligkeit. So wirken gestapelte und verschmolzene Klippen nahtlos.
Collider und Abfragen
Mit Collider Mode = MeshCollider (Standard) erhält jeder Chunk einen MeshCollider auf dem Chunk Layer. Er wird nach jeder Bearbeitung zusammen mit dem Mesh neu aufgebaut. Verwende diese Einstellung, wenn dein Spiel auf Unity-Physik angewiesen ist (Physik-Raycasts, Rigidbodies, Backen des NavMesh).
Mit ColliderMode.None werden keine Collider erzeugt. Das Terrain lässt sich trotzdem ohne Physik abfragen: RTSTerrain.Raycast, GetHeight, GetNormal, GetCliffLevel, IsWater und GetPathing arbeiten mit der analytischen Referenzoberfläche, im Edit Mode und zur Laufzeit. Das spart den Speicherbedarf und die Cooking-Zeit der Collider.
using RTSTerrainStudio;
using UnityEngine;
/// Finds the terrain point under the mouse, with or without chunk colliders.
public class TerrainPointer : MonoBehaviour
{
[SerializeField] Camera viewCamera;
void Update()
{
if (viewCamera == null) return;
Ray ray = viewCamera.ScreenPointToRay(RTSInput.PointerPosition);
// 1. Physics-free: works even when Collider Mode is None.
// RaycastAll tests every RTSTerrain in the scene and returns the nearest hit.
if (RTSTerrain.RaycastAll(ray, out RTSTerrainHit hit))
{
Debug.Log($"Cell {hit.cell} at {hit.point}, cliff level {hit.terrain.GetCliffLevel(hit.point)}");
}
// 2. Unity physics: needs Collider Mode = MeshCollider.
// RTSTerrainChunk.GetTerrain maps the collider of a chunk back to its terrain.
if (Physics.Raycast(ray, out RaycastHit physicsHit, 1000f))
{
RTSTerrain terrain = RTSTerrainChunk.GetTerrain(physicsHit.collider);
if (terrain != null) Debug.Log($"Physics hit on terrain '{terrain.name}'");
}
}
}
Rendering-Einstellungen des Terrains
Diese Einstellungen findest du im Inspector von RTSTerrain, gruppiert in Chunks & Geometry, Cliff Shape, Physics und Rendering. Wenn du eine davon im Inspector änderst, wird das Terrain automatisch neu aufgebaut. Per Code änderst du terrain.Settings und rufst danach terrain.RebuildAll() auf.
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Chunk Size | 32 | Zellen pro Chunk-Seite (16, 32 oder 64). Kleinere Chunks werden beim Bearbeiten schneller neu aufgebaut, größere Chunks bedeuten weniger Draw Calls. Siehe Die Chunk-Größe wählen. |
| Surface Subdivisions | 2 | Quads pro Zellseite auf glattem Boden und Rampen (1–4). 1 = klassischer RTS-Look, 2–4 = weichere Hügel. |
| Cliff Subdivisions | 4 | Auflösung des Unterrasters, mit dem abgerundete Klippenkonturen innerhalb einer Klippenzelle nachgezeichnet werden (2–8). Der Wert wird auf eine gerade Zahl aufgerundet, die zugleich ein Vielfaches von Surface Subdivisions ist (siehe Unterteilungen). |
| Cliff Noise, Cliff Noise Scale, Cliff Slant, Cliff Wall Segments | siehe Formgebung der Klippenwände | Aussehen der Klippenwände. |
| Collider Mode | MeshCollider | None oder MeshCollider (einer pro Chunk, nach Bearbeitungen neu aufgebaut). |
| Chunk Layer | 0 (Default) | GameObject-Layer der Chunks. Er wird von den Collidern und den Culling-Masken der Kameras verwendet. Gemalte Details werden ebenfalls auf diesem Layer gezeichnet. |
| Cast Shadows | On | Schattenwurfmodus der Chunk-Renderer. |
| Receive Shadows | an | Ob die Chunks Schatten empfangen. |
| Material Override | leer | Optionales Material, das anstelle des Standardmaterials verwendet wird (siehe Materialien). |
| Show Grid | aus | Zeichnet das Zellraster dauerhaft (vor allem zum Bearbeiten). |
| Grid Opacity | 0.35 | Deckkraft des Rasters, wenn Show Grid eingeschaltet ist. |
Die Schaltfläche Rebuild Terrain unten im Inspector (RTSTerrain.RebuildAll()) zerstört alle Chunks, Texturen und die Pathing-Karte und erzeugt sie neu. Vegetation, Wasser und Dekor werden dabei ebenfalls neu aufgebaut.
Materialien, Overlays und das Raster
Standardmäßig erzeugt das Terrain ein eigenes Material aus dem Shader RTS Terrain Studio/Terrain (RTSTerrain.TerrainShaderName). Die Quelle des Materials wählt das Terrain in dieser Reihenfolge:
- Material Override in den Terrain-Einstellungen (
RTSTerrainSettings.materialOverride). - Material Override des Tilesets (
RTSTileset.materialOverride), sodass alle Terrains, die dieses Tileset verwenden, gleich aussehen. - Ein neues Material mit
RTS Terrain Studio/Terrain.
Das Terrain arbeitet immer mit einer Kopie des Quellmaterials und füllt sie mit den Splatmaps, Textur-Arrays und Kartenparametern. Ein Override-Material muss den Terrain-Shader oder einen Shader verwenden, der dieselben Eigenschaften deklariert (siehe Schnittstelle der Shader-Eigenschaften).
Da das Terrain eine Kopie rendert, werden spätere Änderungen am Asset des Override-Materials nicht automatisch übernommen. Klicke nach einer Änderung auf Rebuild Terrain (oder rufe RebuildAll() auf). Um einen Wert zur Laufzeit ohne Neuaufbau zu ändern, setze ihn direkt auf terrain.TerrainMaterial.
Wird der Terrain-Shader nicht gefunden, etwa in einem Player-Build, der ihn nicht enthält, wird das Terrain mit einem schlichten grauen URP-Lit-Material gezeichnet. Dabei wird folgende Warnung ausgegeben: „Shader 'RTS Terrain Studio/Terrain' was not found.“ Führe aus. Die dortige Prüfung „Shaders included in builds“ erzeugt das Asset RTSShaderReferences, das dafür sorgt, dass alle Shader des Pakets in Builds enthalten bleiben (siehe Einen Build erstellen).
Overlay. SetOverlayTexture(texture, opacity) legt eine beliebige Textur über die ganze Karte, vom ersten Vertex bis MapData.WorldSize. Die Terrain-Farbe wird mit Textur-Alpha × Deckkraft in Richtung des RGB-Werts der Textur überblendet. Der Editor nutzt das für die Pathing-Ansicht. Du kannst es für Heatmaps, Territorien oder Vorschauen der Gebäudeplatzierung verwenden. Das Overlay ist Teil der Oberflächenfarbe und wird daher wie das Terrain beleuchtet und beschattet.
Raster. SetGridVisible(visible, opacity) erzwingt die Anzeige des kantengeglätteten Zellrasters per Code. Jede 4. Linie wird dicker gezeichnet. In der Scene View schaltet der Editor das Raster während der Bearbeitung mit G um. IsGridVisible ist true, wenn das Raster per Code oder durch Show Grid angezeigt wird.
using RTSTerrainStudio;
using Unity.Mathematics;
using UnityEngine;
/// Shows unwalkable cells in red on top of the terrain (one overlay pixel per cell).
public class WalkabilityOverlay : MonoBehaviour
{
[SerializeField] RTSTerrain terrain;
Texture2D overlay;
public void Show()
{
MapData map = terrain != null ? terrain.Map : null;
if (map == null) return;
// One texel per cell. Point filtering keeps hard cell borders.
overlay = new Texture2D(map.Width, map.Height, TextureFormat.RGBA32, false)
{
filterMode = FilterMode.Point,
wrapMode = TextureWrapMode.Clamp
};
var pixels = new Color32[map.Width * map.Height];
for (int z = 0; z < map.Height; z++)
{
for (int x = 0; x < map.Width; x++)
{
// Texture row 0 is the map edge at z = 0, like the map itself.
Vector3 world = terrain.CellCenterToWorld(new int2(x, z));
bool walkable = terrain.IsWalkable(world);
// Alpha 0 leaves the terrain untouched, alpha 160 tints it red.
pixels[z * map.Width + x] = walkable ? new Color32(0, 0, 0, 0) : new Color32(255, 40, 40, 160);
}
}
overlay.SetPixels32(pixels);
overlay.Apply(false, false);
terrain.SetOverlayTexture(overlay, 0.8f);
}
public void Hide()
{
if (terrain != null) terrain.SetOverlayTexture(null, 0f); // null hides the overlay
if (overlay != null) Destroy(overlay);
overlay = null;
}
void OnDisable() => Hide();
}
Die Rendering-API von RTSTerrain auf einen Blick:
| Member | Beschreibung |
|---|---|
Material TerrainMaterial | Die Materialinstanz, die alle Chunks gemeinsam nutzen (null, bevor das Terrain aktiviert ist). |
Transform ChunkRoot | Das Objekt „RTS Chunks“, das die Chunk-GameObjects enthält. |
IReadOnlyList<RTSTerrainChunk> Chunks, int2 ChunkCount, int ChunkSizeInCells | Das Chunk-Raster. |
RTSTerrainChunk GetChunk(int2 chunkCoord), GetChunkAtCell(int2 cell) | Sucht einen Chunk. Gibt außerhalb der Karte null zurück. |
bool IsRenderingReady | True, sobald Chunks, Texturen und das Material existieren. |
Texture GetSplatTexture(int index) | Splat-Textur 0–3 (Schicht i liegt in Splatmap i / 4, Kanal i % 4). |
void SetOverlayTexture(Texture tex, float opacity), OverlayTexture, OverlayOpacity | Kartenweites Overlay. Übergib null oder 0, um es auszublenden. |
void SetGridVisible(bool visible, float opacity), IsGridVisible | Zellraster per Code. |
void RebuildChunkMeshes(RectInt cellRect) | Baut Meshes und Collider der betroffenen Chunks sofort neu auf. Normalerweise nicht nötig: NotifyChanged erledigt das. |
void RefreshMaterial() | Überträgt Tileset, Textur-Arrays und Kartenparameter sofort erneut auf das Material. |
RTSTerrainChunk: Terrain, ChunkCoord, CellRect, Mesh, MeshRenderer, MeshCollider, LocalBounds, static GetTerrain(Collider) | Daten pro Chunk. GetTerrain ordnet einen Physik-Treffer seinem Terrain zu. |
Der Terrain-Shader
RTS Terrain Studio/Terrain (Shaders/RTSTerrain.shader) ist ein handgeschriebener Lit-Shader für URP 17.3. Er unterstützt Forward-, Forward+- und Deferred-Rendering, den SRP Batcher, Schatten, SSAO, Decals, Light Layers, Light Cookies und Lightmaps. Sein Material bearbeitest du nie von Hand: Alle Eigenschaften füllt das Terrain aus der Karte und ihrem Tileset.
Funktionen
| Funktion | Wirkung | Gesteuert über |
|---|---|---|
| 16 Bodenschichten | Bis zu 16 per Splatmap überblendete Schichten, abgetastet mit planarem XZ-Mapping. An einem Pixel werden nur Schichten mit sichtbarer Gewichtung abgetastet (dynamische Verzweigungen). Mehr gemalte Schichten auf der Karte machen also nicht jeden Pixel teurer. | Bodenschichten des Tilesets; Tile Size, Tint, Normal Strength, Smoothness, Metallic |
| Höhenbasierte Überblendung | In einem Übergang gewinnt die Schicht mit der höheren Texturhöhe. So wächst Gras zwischen Steinen, statt sie per Überblendung zu überdecken. Kontrast = Height Blend² × 16; 0 ergibt eine einfache lineare Überblendung. | Pro Schicht Height Blend (0–1) plus eine Höhenquelle: eine Höhentextur (R-Kanal) oder das Albedo-Alpha (Height From Albedo Alpha) |
| Anti-Tiling | In Bereichen, die ein niederfrequentes Rauschen auswählt, blendet die Textur zu einem zweiten Sample mit gedrehten und skalierten UVs über. Das bricht die sichtbare Wiederholung auf. Bei voller Stärke nutzt etwa die Hälfte des Bodens das zweite Sample. In diesen Bereichen kostet das ein zusätzliches Textur-Sample. | Pro Schicht Anti Tiling (0–1) |
| Makrovariation | Eine großflächige Helligkeitsvariation und eine dezente Farbtonvariation zwischen warm und kühl über die ganze Karte. Sie kaschiert Wiederholungen aus der Entfernung. Klippen erhalten die halbe Stärke. | Macro Variation des Tilesets (0–1, Standard 0.25) |
| Triplanare Klippen | Bis zu 4 Klippentypen werden entlang aller drei Achsen projiziert, sodass senkrechte Wände nie gestreckt aussehen. Die Normalen werden per Whiteout-Blend gemischt. Wo zwei Klippentypen aufeinandertreffen, blenden sie über einen schmalen Streifen ineinander über. | Klippenschichten des Tilesets; Triplanar Sharpness (1–16, Standard 6) |
| Fels an Hängen | Boden, der steiler als etwa 50° ist, geht allmählich in das Klippenmaterial über (ab etwa 63° vollständig Fels), sodass steile Hügel nie gestrecktes Gras zeigen. Der Fels bricht dort durch den Boden, wo seine Höhentextur hohe Werte hat. | Automatisch |
| Okklusion | Gebackene Vertex-AO am Fuß von Klippen, multipliziert mit einem Kavitätsterm aus der Höhentextur der Klippe. | Automatisch |
| Zellraster | Kantengeglättete Linien (alle 4 Zellen dicker). Sie blenden in der Ferne aus, bevor sie Moiré-Muster erzeugen könnten. | Show Grid / SetGridVisible / G in der Scene View |
| Overlay | Kartenweites RGBA-Overlay (Pathing-Ansicht, Heatmaps). | SetOverlayTexture; P schaltet in der Scene View reihum durch die Modi des Pathing-Overlays |
| Pinselcursor | Umriss und Abnahme des Pinsels werden im Shader an der exakten Oberflächenposition projiziert. Du malst also genau dort, wo du den Cursor siehst (unabhängig von der Anzeigeskalierung). Bis zu 4 Positionen für symmetrisches Malen. Der Cursor wird zuletzt gezeichnet und bleibt deshalb im Nebel des Krieges sichtbar. | RTSBrushPreview (von den Editorwerkzeugen gesetzt) |
| Nebel des Krieges (Fallback) | Wenn das Renderer Feature für den Nebel des Krieges nicht installiert ist, verdunkelt der Shader unerkundete und nicht sichtbare Bereiche selbst. | RTSFogOfWarManager (siehe Nebel des Krieges) |
| Fallback ohne Tileset | Ohne Tileset rendert das Terrain neutralgrauen Boden und dunkler graue Klippen, statt mit einem Fehler abzubrechen. | Automatisch |
| Beleuchtungsoptionen | Schalte Glanzlichter und Umgebungsreflexionen ab, um das Shading günstiger zu machen. | Material-Schalter Specular Highlights / Environment Reflections (auf einem Override-Material) |
Die meisten dieser Werte sind Tileset-Einstellungen. Wie du die Schichten anlegst, erfährst du unter Tilesets.
Passes: ForwardLit (UniversalForward), GBuffer (UniversalGBuffer, nicht unter GLES/GLCore), ShadowCaster, DepthOnly, DepthNormals und Meta. Alle Passes außer GBuffer sind für Shader Model 3.5 ausgelegt, daher wird das Terrain auch unter WebGL 2.0 und OpenGL ES 3.0 gerendert; der GBuffer-Pass benötigt 4.5. Die Render-Queue ist Geometry-100, sodass das große Terrain den Tiefenpuffer füllt, bevor andere opake Objekte gezeichnet werden. LOD-Cross-Fade wird nicht verwendet.
Glanzlichter und Reflexionen abschalten
Für leistungsschwache Geräte oder für einen flacheren, gemalten Look verwendest du ein Override-Material mit ausgeschalteten Beleuchtungsschaltern:
- Wähle im Project-Fenster und benenne das Material, zum Beispiel
Terrain Low End. - Stelle im Inspector des Materials den Shader auf .
- Deaktiviere unten im Inspector des Materials Specular Highlights und/oder Environment Reflections. Lasse die übrigen Eigenschaften unverändert; das Terrain überschreibt sie.
- Weise das Material dem Feld Material Override des Tilesets zu (gilt für alle Terrains, die es verwenden) oder dem eines einzelnen Terrains (Abschnitt Rendering im Inspector von
RTSTerrain). - Wenn sich das Terrain nicht sofort ändert, klicke auf Rebuild Terrain.
Verwende dafür ein Material-Asset, kein per Code erzeugtes Material. Unity behält nur die Shader-Varianten von Material-Keywords, die ein Material-Asset im Build verwendet. Ein Keyword, das nur per Code gesetzt wird, kann daher aus Player-Builds entfernt werden.
Schnittstelle der Shader-Eigenschaften
Alle Eigenschaften setzt der Rendering-Code von RTSTerrain über die IDs in RTSShaderIDs. Ein eigener Shader muss die Eigenschaften, die er verwendet, unter denselben Namen deklarieren. Skalare und Vektoren liegen im Konstantenpuffer UnityPerMaterial (SRP Batcher).
| Eigenschaft | Typ | Inhalt |
|---|---|---|
_RTS_SplatMap0 … _RTS_SplatMap3 | Texture2D, lineares RGBA | Splat-Gewichtungen. Schicht i liegt in Splatmap i / 4, Kanal i % 4. Alle Gewichtungen ergeben zusammen 1. UV = positionOS.xz * _RTS_MapSize.zw. Bilinear abgetastet, geklemmt. |
_RTS_AlbedoArray | Texture2DArray, sRGB | RGB = Albedo, A = Höhe (0.5, wenn eine Schicht keine Höhe hat). Slices 0–15 = Bodenschichten, 16–19 = Klippenschichten (RTSTileset.CliffSliceOffset). |
_RTS_NormalArray | Texture2DArray, linear | RGB = Normale im Tangentenraum × 0.5 + 0.5 (beim Erstellen des Arrays bereits aus DXT5nm/BC5 entpackt). |
_RTS_LayerData | Texture2D 20 × 4, float, gelesen mit Load | Eine Spalte pro Array-Slice, siehe Tabelle unten. |
_RTS_LayerCount | float | Anzahl der Bodenschichten des Tilesets, mindestens 1 (auf 1–16 begrenzt). Ohne Tileset übergibt RTSTerrain den Wert 1, neutrale Schichtdaten und Platzhalter-Textur-Arrays (RTSTextureArrayBuilder.FallbackResolution = 128), sodass das Terrain grauen Boden und dunkler graue Klippen zeigt. Der Shader behandelt außerdem 0 als „keine Daten“ und zeichnet dann RTS_FALLBACK_GROUND_ALBEDO / RTS_FALLBACK_CLIFF_ALBEDO, ohne _RTS_LayerData zu lesen. Das passiert nur bei einem eigenen Material, das außerhalb von RTSTerrain verwendet wird. |
_RTS_MapSize | float4 | (Weltgröße X, Weltgröße Z, 1 / X, 1 / Z). |
_RTS_MapParams | float4 | (Zellgröße, Stufenhöhe, Makrovariation, Triplanar-Schärfe). Der mitgelieferte Shader verwendet die Stufenhöhe nicht. Sie ist für Anpassungen gedacht, etwa eine höhenabhängige Einfärbung. |
_RTS_OverlayTex, _RTS_OverlayOpacity | Texture2D RGBA, float | Kartenweites Overlay. Deckkraft 0 = aus. |
_RTS_GridParams | float4 | (aktiviert 0/1, Deckkraft, ungenutzt, ungenutzt). |
_SpecularHighlights, _EnvironmentReflections | float-Schalter | Keywords _SPECULARHIGHLIGHTS_OFF, _ENVIRONMENTREFLECTIONS_OFF. |
_RTS_LayerParams[20], _RTS_LayerTints[20], _RTS_LayerParams2[20] | float4-Arrays | Dieselben Werte pro Schicht als float4-Arrays, für eigene Shader, die mit Arrays arbeiten. Der mitgelieferte Shader liest nur _RTS_LayerData. |
RTSShaderIDs.WorldToMap (_RTS_WorldToMap) ist reserviert und wird von RTSTerrain nicht gesetzt. Verwende den Objektraum (positionOS), der bei Chunk-Meshes den kartenlokalen Koordinaten entspricht.
Aufbau von _RTS_LayerData (Spalte = Array-Slice):
| Zeile | x | y | z | w |
|---|---|---|---|---|
| 0 | 1 / Kachelgröße | Normalenstärke | Glätte | Höhenüberblendung |
| 1 | Tönung (RGB) als lineare Farbe (Color.linear) | Metallic-Wert | ||
| 2 | Anti-Tiling | hat Höhe (0/1) | Schicht verwendet (0/1) | 0 |
| 3 | Minikartenfarbe (wird vom Shader nicht gelesen) | |||
Ein Klippen-Slot (16–19) mit „Schicht verwendet“ = 0 wird wie Klippen-Slot 16 gerendert. Klippentypen, die über die Anzahl der Klippenschichten des Tilesets hinaus gemalt werden, zeigen trotzdem Fels.
Mesh-Eingabe: POSITION (kartenlokal), NORMAL (geglättet, Objektraum), COLOR (r = Klippenwandmaske, g = Klippentyp × 85 / 255, sodass die Typen 0–3 auf 0, 1/3, 2/3 und 1 abgebildet werden, b = Ambient Occlusion, a = ungenutzt). Keine UVs, keine Tangenten.
Globale Variablen (aus Shaders/Include/RTSCommon.hlsl, von allen Shadern des Pakets gemeinsam genutzt):
| Globale Variable | Gesetzt von | Inhalt |
|---|---|---|
_RTS_FogOfWarTex, _RTS_FogOfWarParams, _RTS_FogOfWarUnexplored, _RTS_FogOfWarExplored, _RTS_FogOfWarEnabled | RTSFogOfWarManager | Sichtbarkeitstextur (R = sichtbar, G = erkundet), Koordinatenabbildung und Tönungen. HLSL: RTS_SampleFogOfWar(positionWS), RTS_ApplyFogOfWar(color, positionWS). |
_RTS_BrushPositions[4], _RTS_BrushRotations[4], _RTS_BrushCount, _RTS_BrushParams, _RTS_BrushColor, _RTS_BrushMask | RTSBrushPreview | Pinselcursor. HLSL: RTS_BrushCursor(positionWS) liefert Farbe (rgb) und Deckung (a). |
_RTS_Wind | RTSVegetationRenderer | xy = Richtung, z = Stärke, w = Geschwindigkeit. |
Einen eigenen Terrain-Shader schreiben
Die gesamte Oberflächenlogik des Terrains steckt in einer einzigen Include-Datei, Shaders/Include/RTSTerrainInput.hlsl. Die Pass-Dateien setzen die resultierende Oberfläche nur noch in Beleuchtung um. Um die Optik zu ändern, beginnst du mit einer Kopie des Paket-Shaders:
- Kopiere
Shaders/RTSTerrain.shaderund den gesamten OrdnerShaders/Includein einen Ordner deines eigenen Projekts (zum BeispielAssets/MyGame/Shaders) und behalte dabei die relative Ordnerstruktur bei. Der Shader bindet seine Pass-Dateien alsInclude/…ein, und die Includes verweisen über relative Pfade aufeinander. - Benenne den Shader in der ersten Zeile deiner Kopie um, zum Beispiel in
Shader "MyGame/Terrain", damit er nicht mit dem Original kollidiert. - Nimm deine Änderungen vor, meist in
RTS_EvaluateTerrainSurfacedeiner Kopie vonRTSTerrainInput.hlsl. - Erstelle ein Material mit deinem Shader und weise es als Material Override zu (im Tileset oder im Terrain). Das Material-Asset sorgt außerdem dafür, dass der Shader in Player-Builds landet.
- Klicke auf Rebuild Terrain.
Da du mit Kopien arbeitest, überschreibt ein Paket-Update nie deine Änderungen. Wenn du die neuesten Korrekturen haben möchtest, vergleiche deine Kopie nach einem Update mit den neuen Paketdateien.
Beispiel: Schnee auf hohen Plateaus. Füge Folgendes am Ende von RTS_EvaluateTerrainSurface in deiner Kopie ein, direkt vor return surface;:
// Snow on everything above cliff level 6. positionOS.y is the map-local ground height, and
// _RTS_MapParams.y the height of one cliff level, so their ratio is the (fractional) cliff level.
half level = (half)(positionOS.y / max(_RTS_MapParams.y, 1e-3));
// Soft border between level 5.5 and 6.5; cliff walls (cliffBlend = 1) stay rocky.
half snow = smoothstep(5.5h, 6.5h, level) * (1.0h - surface.cliffBlend);
surface.albedo = lerp(surface.albedo, half3(0.92h, 0.94h, 0.97h), snow);
surface.smoothness = lerp(surface.smoothness, 0.35h, snow);
Die wichtigsten Bausteine von RTSTerrainInput.hlsl:
| Funktion / Typ | Zweck |
|---|---|
struct RTSTerrainSurface | Ergebnis der Oberflächenauswertung: Albedo, Metallic-Wert, Glätte, Okklusion, Pixel- und Geometrienormale (Objektraum), Tangentenbasis, cliffBlend (0 = Boden, 1 = Klippe). |
RTS_EvaluateTerrainSurface(positionOS, normalOS, vertexColor, dPdx, dPdy) | Die vollständige Oberfläche: Klippenabdeckung, Boden, Klippen, höhenbasierter Übergang, Makrovariation, Okklusion. |
RTS_EvaluateGround(positionXZ, dPdx, dPdy, detileNoise), RTS_AccumulateGroundLayer(…) | Per Splatmap überblendete Bodenschichten mit höhenbasierter Überblendung und Anti-Tiling (float-Akkumulator RTSGroundAccumulator). |
RTS_EvaluateCliff(cliffTypeValue, positionOS, dPdx, dPdy, normalOS), RTS_SampleCliffLayer(…), RTS_TriplanarWeights(normalOS, sharpness) | Triplanares Klippenmaterial und Überblendung zwischen Klippentypen. |
RTS_LoadLayerData(slice) → RTSLayerData | Liest eine Spalte von _RTS_LayerData. |
RTS_DecodeArrayNormal(encoded, strength) | Dekodiert ein Texel des Normalen-Arrays. |
RTS_EvaluateNoise(positionXZ), RTS_ValueNoise, RTS_Hash21 | Prozedurales Rauschen (ohne Textur), stabil auf mobilen GPUs. |
RTS_GridMask(…), RTS_ApplyEditorOverlays(…) | Rasterlinien und Overlay-Textur. |
RTS_CreateSurfaceData(surface) | Wandelt die Oberfläche in die SurfaceData von URP um. |
| Konstanten | RTS_MAX_GROUND_LAYERS (16), RTS_CLIFF_SLICE_OFFSET (16), RTS_MAX_CLIFF_TYPES (4), RTS_MIN_LAYER_WEIGHT (1/255), RTS_FALLBACK_GROUND_ALBEDO, RTS_FALLBACK_CLIFF_ALBEDO |
Texturen werden mit expliziten Gradienten abgetastet (SampleGrad). Die Gradienten dPdx/dPdy werden einmal am Anfang des Fragment-Shaders berechnet, außerhalb jeder Verzweigung. Behalte das bei, wenn du eigene Samples innerhalb von Verzweigungen hinzufügst, sonst entstehen an den Grenzen der Verzweigungen Mipmap-Nähte.
Der Forward-Pass arbeitet in dieser Reihenfolge: Oberflächenauswertung, Editor-Overlays, URP-Beleuchtung (UniversalFragmentPBR), atmosphärischer Nebel von Unity, Nebel des Krieges und Pinselcursor.
Vegetation
RTSVegetationRenderer zeichnet anhand der Kartendaten zwei Arten von Vegetation:
- Details: Gras, Blumen und Kleinpflanzen, die als Dichte gemalt werden. Burst-Jobs erzeugen sie pro Patch, gezeichnet werden sie mit prozeduralem GPU-Instancing. Sie existieren nie als GameObjects.
- Bäume: einzeln platzierte Instanzen, entweder per GPU-Instancing gezeichnet (am schnellsten) oder als Prefab-GameObjects gespawnt (für Gameplay wie das Abbauen).
Beide sind in Patches aus RTSVegetationRenderer.PatchCells (16) × 16 Zellen organisiert. Patches sind die Einheit für Culling und inkrementelle Neuaufbauten: Eine Bearbeitung erzeugt nur die Patches neu, die sie berührt hat. Der Renderer arbeitet im Edit Mode und im Play Mode, für Kameras der Game View und der Scene View.
Einrichtung
- Wähle das Terrain-GameObject aus. Wenn es keinen Vegetation Renderer hat, klicke im Inspector von
RTSTerrainneben der Warnung auf Add (oder verwende ). - Weise im Abschnitt Assets des Inspectors von
RTSTerraineine Vegetation Library zu. Ohne Bibliothek warnt der Inspector des Vegetation Renderer: „The terrain has no vegetation library, so nothing can be drawn.“ - Male Details und platziere Bäume mit den Vegetationswerkzeugen (Taste 4 in der Scene View während der Bearbeitung).
- Aktiviere für instanziert gezeichnete Bäume Enable GPU Instancing in ihren Materialien. listet Materialien auf, bei denen die Option ausgeschaltet ist, und kann sie mit einem Klick korrigieren.
Die Bibliothek definiert die Prototypen (Meshes, Texturen, Farben, Größen, Distanzen). Alle Felder der Prototypen findest du unter Vegetationsbibliothek.
Der Inspector des Vegetation Renderer zeigt Live-Statistiken (Visible Details, Total Details, Visible Trees, Tree Objects, Colliders) und hat eine Schaltfläche Rebuild (RebuildAll()) sowie eine Schaltfläche Select Library.
Renderer-Einstellungen
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Quality | ||
| Detail Density Multiplier | 1 | Multipliziert die Anzahl der Detailinstanzen pro Texel (0–2, 0 = keine Details). Eine Änderung erzeugt alle Details neu. |
| Detail Distance Multiplier | 1 | Multipliziert die Render-Distanz jedes Detail-Prototyps (0.1–3). |
| Tree Distance Multiplier | 1 | Multipliziert die Render-Distanz jedes instanzierten Baum-Prototyps (0.1–3). |
| Wind | ||
| Wind Direction | (1, 0.35) | Horizontale Windrichtung in Weltkoordinaten (x = Welt-X, y = Welt-Z). |
| Wind Strength | 0.6 | Globale Windstärke (0–3). Jeder Prototyp und jedes Material skaliert sie zusätzlich. |
| Wind Speed | 1.6 | Geschwindigkeit der Windanimation (0–10). |
| Rendering | ||
| Draw Details | an | Zeichnet per Dichte gemalte Details. |
| Draw Trees | an | Zeichnet die per GPU-Instancing gerenderten Bäume. |
| Draw In Scene View | an | Zeichnet die Vegetation auch in Kameras der Scene View. |
| Crossed Quad Count | 3 | Anzahl der Quads des generierten Gras-Meshes für Details im Modus Crossed Quads (2–3). Eine Änderung baut die Vegetation neu auf. |
| Collider Layer | 0 | Layer der Collider-Proxys, die für instanzierte Bäume erzeugt werden. |
Details
Für jede gemalte Detailschicht und jeden Patch verteilt DetailScatterJob (Burst) Instanzen anhand der Dichtekarte. Eine Dichte von 255 ergibt pro Detail-Texel so viele Instanzen, wie Max Density des Prototyps angibt, multipliziert mit dem Detail Density Multiplier. Instanzen überspringen Hänge, die steiler als Max Slope sind, und mit Avoid Water auch überfluteten Boden. Sie können sich mit dem Boden neigen (Align To Ground) und erhalten über Rauschen eine Farbe zwischen Healthy Color und Dry Color.
Die Instanzen eines Patches werden in einen strukturierten GraphicsBuffer hochgeladen und mit Graphics.RenderMeshPrimitives über den internen Shader Hidden/RTS Terrain Studio/Detail gezeichnet. WebGL 2.0 unterstützt keine Structured Buffers, und OpenGL ES 3 garantiert sie in Vertex-Shadern nicht. Auf diesen Geräten landen die Instanzen daher stattdessen in einer Float-Textur (zwei Texel pro Instanz). Der Renderer wählt den Weg zur Laufzeit, und das Gras sieht in beiden Fällen gleich aus. Der Shader liest jede Instanz aus dem Buffer oder der Textur und übernimmt Wind, ein weiches Herausschrumpfen über die letzten 25 % der Render-Distanz, das Abdunkeln an der Wurzel, Transluzenz und den Fallback für den Nebel des Krieges. Jeder Patch durchläuft pro Kamera ein Frustum- und Distanz-Culling.
| Render-Modus | Mesh | Material |
|---|---|---|
CrossedQuads | Generiertes Gras-Mesh mit 2 oder 3 gekreuzten Quads (Crossed Quad Count) | Aus der Texture des Prototyps erzeugt (Alpha-Clipping bei 0.5) |
Mesh | Das Mesh des Prototyps (alle Sub-Meshes) | Wenn das Material bereits Hidden/RTS Terrain Studio/Detail verwendet, wird es unverändert genutzt. Andernfalls wird aus seiner Haupttextur und Farbe ein Detail-Material erzeugt. Den Alpha-Cutoff des Quellmaterials übernimmt das Detail-Material nur, wenn das Quellmaterial Alpha-Clipping nutzt. |
Andere Shader können die Instanzdaten nicht lesen. Details im Modus Mesh werden daher immer mit dem Detail-Shader gerendert. Eigene Shader-Funktionen des Quellmaterials (Vertex-Animation, spezielle Beleuchtung) werden für Details nicht verwendet. Wenn du sie brauchst, verwende stattdessen einen Baum-Prototyp.
Details werfen nur dann Schatten, wenn Cast Shadows des Prototyps eingeschaltet ist (standardmäßig aus). Sie werden auf dem Chunk Layer des Terrains gezeichnet, ohne Light Probes oder Reflection Probes.
Bäume: instanziert oder als GameObject
Für jeden Baum-Prototyp wählst du einen Spawn Mode:
Instanced (Standard) | GameObject | |
|---|---|---|
| Rendering | Graphics.RenderMeshInstanced, bis zu 1023 Instanzen pro Draw Call, Culling pro Patch nach Frustum und Render Distance × Tree Distance Multiplier | Eine Prefab-Instanz pro Baum, von Unity wie jedes GameObject gerendert |
| LOD | LOD0 und LOD1 einer LODGroup, pro Patch-Gruppe nach der LODGroup-Regel von Unity gewählt (berücksichtigt QualitySettings.lodBias). Prefabs ohne LODGroup verwenden alle ihre aktiven MeshRenderer-Komponenten. | Die eigene LODGroup des Prefabs |
| Tönung pro Instanz | Ja, für Materialien mit RTS Terrain Studio/Vegetation (zufällig zwischen Tint A und Tint B) | Nein |
| Collider | Gepoolte Kapsel-Proxys (siehe unten) | Die Collider des Prefabs |
| Skripte auf dem Prefab | Werden nicht ausgeführt (nichts wird instanziiert) | Laufen normal |
| Am besten für | Wälder, Kulissen, sehr viele Bäume | Bäume mit Gameplay-Funktion: Abbauen, Animation, eigene Skripte |
In beiden Modi werden die Bäume auf Bodenhöhe + position.y platziert, um ihren Drehwinkel gedreht und gleichmäßig skaliert. Wenn das Terrain bearbeitet wird, setzen sie sich automatisch wieder auf den Boden. Gespawnte Bäume und Collider-Proxys liegen unter einem versteckten Kindobjekt „RTS Vegetation Objects“ des Terrains und folgen dessen Transform.
Instanzierte Bäume brauchen Enable GPU Instancing in ihren Materialien. Ist die Option ausgeschaltet, zeichnet der Renderer über eine versteckte Instancing-Kopie des Materials (das Projekt-Asset wird nie verändert). Im Edit Mode gleicht sich die Kopie etwa 4-mal pro Sekunde mit der Quelle ab. In Player-Builds können die Instancing-Varianten solcher Materialien entfernt werden. Aktiviere deshalb das Kontrollkästchen (der Projektvalidator kann das für dich erledigen).
Der Renderer übernimmt für jedes Teil den Schattenwurfmodus und die Rendering Layer Mask. Teile, die auf Shadows Only stehen (Schatten-Proxys), funktionieren wie erwartet. Ein Prototyp mit ausgeschaltetem Cast Shadows schaltet die Schatten für alle seine Teile ab.
Baum-Collider
Wenn bei einem Baum-Prototyp Colliders aktiviert ist:
- Modus Instanced: Jeder Baum erhält ein unsichtbares GameObject „Tree Collider“ mit einem
CapsuleCollider(Collider Radius, Collider Height, mit dem Baum skaliert) und einer KomponenteRTSTreeInstance(IsColliderProxy= true). Die Proxys werden gepoolt und wiederverwendet und nutzen den Collider Layer des Renderers. - Modus GameObject: Die Collider des Prefabs werden verwendet. Der Renderer fügt jedem gespawnten Baum eine
RTSTreeInstancehinzu.
In beiden Fällen verrät dir hit.collider.GetComponentInParent<RTSTreeInstance>(), welchen Baum eine Physik-Abfrage getroffen hat. Pathing und Nebel des Krieges brauchen keine Collider: Sie verwenden Footprint Radius und Blocks Vision des Prototyps.
Wind
Der Renderer übergibt in jedem Frame den globalen Vektor _RTS_Wind (Richtung, Stärke, Geschwindigkeit) an alle Shader. Wie stark eine Pflanze insgesamt schwankt, ergibt sich so:
- Details: globale Wind Strength × Wind Strength des Prototyps (0–1).
- Bäume und Pflanzen mit
RTS Terrain Studio/Vegetation: globale Wind Strength × Wind Strength des Materials, gewichtet über Wind Mode. Mit Object Height biegt sich die Pflanze nach oben hin stärker, bezogen auf Wind Reference Height. Vertex Color R nutzt die rote Vertex-Farbe als Gewichtung pro Vertex (male 0 auf den Stamm und 1 auf die Astspitzen). Leaf Flutter legt zusätzlich eine schnelle, kleine Bewegung darüber.
Setze Wind Strength am Renderer auf 0, um den Wind ganz abzuschalten, zum Beispiel für eine windstille Nachtszene.
Der Vegetation-Shader
RTS Terrain Studio/Vegetation ist ein Lit-Shader für Blattwerk, gedacht für die Materialien von Baum- und Pflanzen-Prefabs. Er unterstützt den SRP Batcher, GPU-Instancing mit Tönung pro Instanz (_RTS_InstanceTint), Forward+, DBuffer-Decals, den Rendering Debugger, APV und Lightmaps sowie den Fallback für den Nebel des Krieges. Andere Instancing-fähige Materialien (etwa URP Lit) funktionieren ebenfalls, allerdings ohne Tönung pro Instanz, Transluzenz und Wind.
| Eigenschaft | Standard | Beschreibung |
|---|---|---|
| Albedo, Color | weiß | Basistextur (Alpha wird für das Clipping verwendet) und Farbe. |
| Alpha Clipping, Alpha Cutoff | an, 0.5 | Alpha-Test für Blätter. Das Clipping ist ohne Keyword aktiv; das invertierte Keyword _ALPHATEST_OFF schaltet es aus. |
| Use Normal Map, Normal Map, Normal Strength | aus, –, 1 | Optionale Normal-Map (_NORMALMAP). |
| Smoothness, Specular Color | 0.2, dunkelgrau | Spekulare Reflexion. |
| Wrap Lighting | 0.35 | Macht den Lichtabfall auf Blättern weicher (0–1). |
| Translucency, Translucency Color | 0.6, hellgrün | Licht, das bei Blick in Richtung Sonne durch die Blätter scheint. |
| Wind Mode | Object Height | Off, Vertex Color R oder Object Height. |
| Wind Strength, Wind Reference Height, Leaf Flutter | 1, 6, 0.5 | Windskalierung auf Materialseite (multipliziert mit dem globalen Wind des Renderers). |
| Cull | Off | Face Culling. Mit Off werden beide Seiten der Blattflächen gerendert. |
| Receive Shadows | an | Ist die Option aus, wird das Keyword _RECEIVE_SHADOWS_OFF gesetzt. |
Für Code, der Vegetationsmaterialien erzeugt: Alpha Clipping ist standardmäßig aktiv. Aktiviere das Keyword _ALPHATEST_OFF (RTSVegetationShaderIDs.VegetationAlphaTestOffKeyword) nur, wenn du das Clipping deaktivieren willst.
Qualitätsmultiplikatoren
Die drei Qualitätsfelder skalieren die Werte der Bibliothek, ohne die Bibliothek selbst zu ändern. Sie eignen sich ideal für Grafikoptionen im Menü deines Spiels:
using RTSTerrainStudio;
using UnityEngine;
/// Applies a vegetation quality level (0 = low, 1 = medium, 2 = high) to every terrain in the scene.
public static class VegetationQuality
{
public static void Apply(int level)
{
foreach (RTSTerrain terrain in RTSTerrain.All)
{
if (!terrain.TryGetComponent(out RTSVegetationRenderer vegetation)) continue;
switch (level)
{
case 0:
vegetation.detailDensityMultiplier = 0.4f; // fewer grass blades (regenerates details)
vegetation.detailDistanceMultiplier = 0.6f; // grass disappears earlier
vegetation.treeDistanceMultiplier = 0.7f; // instanced trees culled earlier
vegetation.crossedQuadCount = 2; // 2 quads per grass tuft instead of 3
break;
case 1:
vegetation.detailDensityMultiplier = 0.75f;
vegetation.detailDistanceMultiplier = 0.8f;
vegetation.treeDistanceMultiplier = 1f;
vegetation.crossedQuadCount = 3;
break;
default:
vegetation.detailDensityMultiplier = 1f;
vegetation.detailDistanceMultiplier = 1f;
vegetation.treeDistanceMultiplier = 1.2f;
vegetation.crossedQuadCount = 3;
break;
}
// The renderer notices the changed values on its next update and rebuilds what is needed.
}
}
}
Baum-API und Abbau
| Member | Beschreibung |
|---|---|
bool RemoveTree(uint id) | Entfernt den Baum aus der Karte, deaktiviert sofort sein GameObject oder seinen Collider-Proxy, benachrichtigt das Terrain (MapLayers.Trees, damit das Pathing die Grundfläche wieder freigibt und der Nebel des Krieges den Sichtblocker entfernt) und löst TreeRemoved aus. Dabei wird kein Undo-Schritt aufgezeichnet. Gibt false zurück, wenn kein Baum diese ID hat. |
event Action<RTSVegetationRenderer, TreeInstanceData> TreeRemoved | Wird von RemoveTree mit den Daten des entfernten Baums ausgelöst. |
bool TryFindNearestTree(Vector3 worldPosition, float maxDistance, out TreeInstanceData tree) | Nächstgelegener Baum, gemessen auf der Bodenebene (XZ). Mit Mathf.Infinity wird die ganze Karte durchsucht. |
GameObject GetTreeObject(uint id) | Die gespawnte Prefab-Instanz (Modus GameObject) oder der Collider-Proxy (Instanced mit Collidern). Sonst null. |
Vector3 GetTreeWorldPosition(in TreeInstanceData tree) | Weltposition des Baumfußes (Bodenhöhe + Offset). |
void RebuildAll(), void ProcessChanges() | Vollständiger Neuaufbau; ausstehende Kartenänderungen sofort anwenden (normalerweise automatisch in jedem Frame). |
VisibleDetailInstances, VisibleTrees, TotalDetailInstances, SpawnedTreeObjects, TreeColliderCount | Statistiken. Die „Visible“-Werte gelten für die zuletzt gerenderte Game- oder Scene-Kamera. |
RTSTreeInstance: TreeId, Owner, IsColliderProxy, TryGetData(out TreeInstanceData), Remove() | Komponente auf gespawnten Bäumen und Collider-Proxys. Remove() entspricht Owner.RemoveTree(TreeId). |
using RTSTerrainStudio;
using UnityEngine;
/// A minimal lumberjack: chops the nearest tree in reach, or the tree under the mouse.
public class Lumberjack : MonoBehaviour
{
[SerializeField] RTSVegetationRenderer vegetation;
[SerializeField] Camera viewCamera;
[SerializeField] float reach = 2f;
public int wood;
void OnEnable()
{
if (vegetation != null) vegetation.TreeRemoved += HandleTreeRemoved;
}
void OnDisable()
{
if (vegetation != null) vegetation.TreeRemoved -= HandleTreeRemoved;
}
/// Physics-free: finds the nearest tree around this unit on the ground plane.
public bool ChopNearestTree()
{
if (vegetation == null) return false;
if (!vegetation.TryFindNearestTree(transform.position, reach, out TreeInstanceData tree)) return false;
return vegetation.RemoveTree(tree.id);
}
/// Physics based: needs "Colliders" on the tree prototype.
public bool ChopTreeUnderMouse()
{
if (viewCamera == null) return false;
Ray ray = viewCamera.ScreenPointToRay(RTSInput.PointerPosition);
if (!Physics.Raycast(ray, out RaycastHit hit, 500f)) return false;
// Works for collider proxies (instanced trees) and for colliders inside spawned tree prefabs.
var tree = hit.collider.GetComponentInParent<RTSTreeInstance>();
return tree != null && tree.Remove();
}
void HandleTreeRemoved(RTSVegetationRenderer renderer, TreeInstanceData tree)
{
wood += 10;
Vector3 position = renderer.GetTreeWorldPosition(tree);
Debug.Log($"Tree {tree.id} felled at {position}. Wood: {wood}");
}
}
Die Beispielskripte enthalten eine vollständige Abbau-Demo (RTSTreeHarvestDemo: Arbeiter laufen zu einem Baum, fällen ihn und spielen mit RTSFallingTreeEffect eine Fallanimation ab). Darin findest du ein komplettes Gameplay-Beispiel, das auch zeigt, wie abgebaute Bäume am Ende eines Spieltests wiederhergestellt werden.
Das Entfernen eines Baums verändert die Karte. Im In-Game-Editor oder im Unity Editor wird die Karte danach als Karte mit nicht gespeicherten Änderungen gekennzeichnet. Spielcode arbeitet meist mit einer Karte, die für das Match geladen wurde, und speichert sie nie zurück.
Wasser
RTSWaterRenderer rendert das gemalte Wasser als transparente Meshes, aufgeteilt in Chunks. Jeder als Wasser markierte Vertex hat eine eigene Oberflächenhöhe, sodass Seen auf verschiedenen Klippenstufen möglich sind. Die Meshes folgen MapGeometryView.SampleWater – also genau dem, was auch Gameplay-Abfragen und das Pathing verwenden. Wasser-Chunks verwenden die Chunk-Größe des Terrains, werden von Burst-Jobs erzeugt (WaterChunkMeshJob) und existieren nur dort, wo es Wasser gibt. Neu aufgebaut werden nur die Chunks, die von einer Bearbeitung betroffen sind.
Einrichtung
- Stelle sicher, dass das Terrain-GameObject einen Water Renderer hat ( oder die Schaltfläche Add im Inspector von
RTSTerrain). - Öffne das aktive URP-Asset, aktiviere Depth Texture und Opaque Texture und setze Opaque Downsampling auf None (siehe unten). prüft und korrigiert das. Eine Kamera kann beide Texturen auch für sich selbst einschalten.
- Male Wasser mit den Wasserwerkzeugen (Taste 6 in der Scene View während der Bearbeitung).
- Ändere das Aussehen im Abschnitt Water des Tileset-Inspectors (das Wasserprofil des Tilesets).
URP-Voraussetzungen
| Einstellung (URP-Asset oder Kamera) | Benötigt für | Ohne sie |
|---|---|---|
| Depth Texture: an | Tiefenabhängige Farbe, Uferschaum und weiche Uferlinie rund um Objekte, die im Wasser stehen, sowie eine Lichtbrechung, die Objekte vor dem Wasser ausspart (_CameraDepthTexture) | Das Wasser verwendet die in seinem Mesh gespeicherte Tiefe. Tiefenabhängige Farbe, Schaum und weiches Ufer bleiben erhalten, ignorieren aber Objekte, die im Wasser stehen. |
| Opaque Texture: an | Lichtbrechung (_CameraOpaqueTexture) | Einfaches Alpha-Blending ohne Lichtbrechung. |
| Opaque Downsampling: None (URP-Asset) | Scharfer Seegrund und scharfe Objekte unter Wasser | Die Unterwasserszene wirkt unscharf (der URP-Standard ist 2x Bilinear). |
URP entscheidet pro Kamera: Die eigenen Einstellungen Depth Texture und Opaque Texture einer Kamera (im Abschnitt Rendering ihres Inspectors) überschreiben das URP-Asset, und die Scene View verwendet immer das Asset. Das Wasser passt sein Material an jede Kamera an, unmittelbar bevor diese rendert. So bekommt jede Ansicht das bestmögliche Wasser. Kameras, die den Layer des Wassers nicht rendern, werden übersprungen, und Overlay-Kameras erhalten nie die Opaque Texture.
Der Inspector der Komponente Water Renderer zeigt für jede fehlende Einstellung eine Meldung mit der Schaltfläche Select URP Asset. Im Play Mode berücksichtigt er außerdem die eigenen Einstellungen der Hauptkamera. Eine fehlende Textur führt nie zu fehlerhaftem Wasser: Ansichten ohne diese Textur verwenden den oben beschriebenen Fallback. Mit beiden Texturen sieht das Wasser allerdings deutlich besser aus.
Im Code: RTSWaterMaterialUtility.CheckPipelineSupport(Camera camera, out bool depthTexture, out bool opaqueTexture) gibt an, was URP für eine einzelne Kamera erzeugt, und wertet die Einstellungen dabei genauso aus wie URP selbst (übergib null, um nur das URP-Asset zu prüfen). CheckPipelineSupport(out bool depthTexture, out bool opaqueTexture) prüft die Ansichten, die das Wasser zeigen: in Builds die Hauptkamera (oder das URP-Asset, wenn es keine gibt), im Editor zusätzlich das URP-Asset, weil die Scene View es verwendet. Beide geben false zurück, wenn URP nicht aktiv ist. RTSWaterMaterialUtility.IsOpaqueTextureDownsampled() prüft das Downsampling.
Renderer-Einstellungen
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Material Template | leer | Optionales Material. Sein Shader und seine Eigenschaften werden kopiert, danach wird das Wasserprofil des Tilesets darüber angewendet. Damit stellst du Werte ein, die das Profil nicht abdeckt: Reflection Strength, Specular Strength, Foam Tile Size, Foam Lines, Shore Fade. |
| Cast Shadows | aus | Wasseroberflächen empfangen normalerweise nur Schatten. |
| Receive Shadows | an | Schatten des Hauptlichts auf dem Wasser. Das funktioniert auch für das Laufzeitmaterial in Player-Builds. |
| Render Layer | 4 (Water) | GameObject-Layer der Wasser-Chunks (RTSWaterRenderer.DefaultLayer ist Unitys eingebauter Layer Water). |
| Wave Amplitude Multiplier | 1 | Skaliert die Wave Height des Profils (0–4, 0 = stilles Wasser). |
| Cell Subdivisions | 2 | Quads pro Zellseite (1–4). Höhere Werte ergeben glattere Uferlinien und Wellen, kosten aber mehr Vertices. |
| Light Probe Usage | Off | Off beleuchtet alle Chunks mit der Ambient Probe. Würden die Probes überblendet, bekäme jeder Chunk eine leicht andere Umgebungsfarbe, und entlang der Chunk-Grenzen wären Stufen zu sehen. |
| Reflection Probe Usage | Blend Probes | In Forward+ werden Probes pro Pixel ausgewertet. In Forward wählt jeder Chunk die Probes anhand seiner eigenen Bounds, sodass bei mehreren Probes benachbarte Chunks unterschiedliche Probes spiegeln können. |
Schaltflächen im Inspector: Rebuild (RebuildAll()) und Refresh Material (RefreshMaterial(), wendet Vorlage und Profil erneut an). Der Inspector zeigt außerdem das Chunk-Raster, die Chunks mit Wasser, die Gesamtzahl der Vertices und die Herkunft des Profils.
Wasserprofil (Tileset)
Aussehen und Pathing-Regeln stammen aus RTSTileset.water (einem WaterProfile). Ohne Tileset wird ein gemeinsames Standardprofil verwendet. Behandle es als schreibgeschützt und verwende WaterMath.CreateDefaultProfile() oder WaterProfile.Clone(), wenn du eine bearbeitbare Kopie brauchst.
| Feld | Standard | Beschreibung |
|---|---|---|
| Shallow Color | blaugrün, Alpha 0.55 | Farbe des Flachwassers (Alpha = Deckkraft). |
| Deep Color | dunkelblau, Alpha 0.95 | Farbe, die bei Color Depth erreicht wird (Alpha = Deckkraft). |
| Color Depth | 3 m | Tiefe, bei der die Tiefwasserfarbe vollständig erreicht ist. |
| Shallow Depth Limit | 0.9 m | Wasser, das tiefer ist als dieser Wert, gilt als Tiefwasser (nicht begehbar). Flacheres Wasser ist begehbar, wie in klassischen RTS-Spielen. |
| Normal Map, Normal Strength, Normal Tile Size | keine, 0.6, 8 m | Kräuselung der Oberfläche. Ohne Normal-Map nutzt die Oberfläche nur die Vertex-Wellen. |
| Flow Speed | (0.03, 0.02) | Scrollgeschwindigkeit der Normal-Map. |
| Wave Height | 0.05 m | Amplitude der Vertex-Wellen (multipliziert mit dem Multiplikator des Renderers). |
| Smoothness | 0.92 | Glätte der Oberfläche. |
| Refraction | 0.03 | Stärke des Versatzes durch Lichtbrechung (0–0.2). |
| Foam Color, Foam Distance, Foam Texture | weiß 0.8, 0.6 m, keine | Uferschaum. Foam Distance beruhigt außerdem die Wellen im Flachwasser. |
| Minimap Color | blau | Farbe des Wassers auf der Minikarte. |
Der Water-Shader
RTS Terrain Studio/Water (RTSWaterMaterialUtility.ShaderName) ist ein transparenter URP-Shader. Die Chunk-Meshes liefern kartenlokale Positionen sowie Wasserdaten pro Vertex (Tiefe, Abdeckung und Abstand zum Ufer, bis zu ShoreSearchRadius = 4 Zellen). Funktionen:
- Tiefenabhängige Farbe von flach bis tief, mit weichen Uferlinien, die dort auslaufen, wo das Wasser auf den Boden trifft.
- Vier gerichtete Vertex-Wellen, die im Flachwasser beruhigt werden, damit sich die Oberfläche nie über den Strand hebt.
- Scrollende Kräuselung per Normal-Map, Lichtbrechung der Unterwasserszene, Sonnenglanzlichter und Reflection Probes.
- Uferschaum (prozedurale Linien oder eine Schaumtextur).
- Haupt- und Zusatzlichter (auch Zusatzlichter pro Vertex), Light Cookies, Light Layers, empfangene Schatten und der Fallback für den Nebel des Krieges. Kompatibel mit dem SRP Batcher.
Das Material wird zur Laufzeit erzeugt (WaterMaterial, von allen Chunks gemeinsam genutzt) und mit RTSWaterMaterialUtility.ApplyProfile befüllt. Der Shader wird über ein serialisiertes Feld der Komponente referenziert und ist daher in Builds enthalten.
Werden Zusatzlichter pro Vertex berechnet, liefern sie nur diffuses Licht (keine Glanzlichter, keine Schatten) – genau wie die Vertex-Beleuchtung von URP selbst.
API des Wasser-Renderers
| Member | Beschreibung |
|---|---|
Material WaterMaterial | Laufzeitmaterial, das alle Chunks gemeinsam nutzen (null, solange die Komponente deaktiviert ist). |
Material MaterialTemplate { get; set; } | Optionale Vorlage. Beim Setzen wird das Material aktualisiert. |
WaterProfile Profile | Profil des Tilesets oder das gemeinsame Standardprofil. |
Transform ChunkRoot, ChunkCountX, ChunkCountZ, ActiveChunkCount, VertexCount, bool HasWater | Chunk-Raster und Statistiken. |
MeshRenderer GetChunkRenderer(Vector2Int chunkCoord) | Renderer eines Chunks oder null, wenn der Chunk kein Wasser enthält. |
void RebuildAll(), void RebuildCells(RectInt cellRect), void RefreshMaterial() | Vollständiger Neuaufbau, teilweiser Neuaufbau, Aktualisierung des Materials. |
event Action<RTSWaterRenderer> MeshesRebuilt | Wird nach einem vollständigen oder teilweisen Neuaufbau ausgelöst. |
RTSWaterChunk: Owner, Terrain, ChunkCoord, CellRect | Marker-Komponente auf den verborgenen Chunk-Objekten. |
WaterMath: Wasser für das Gameplay
WaterMath beantwortet Fragen zum Wasser ohne Physik. Alle Positionen sind in Weltkoordinaten angegeben.
| Methode | Rückgabe |
|---|---|
GetWaterHeight(RTSTerrain, Vector3, out float height), GetWaterHeight(Vector3, out float height) | Welt-Y der ruhenden Wasseroberfläche. False, wenn die Stelle trocken ist oder außerhalb des Terrains liegt. Die zweite Überladung findet das Terrain mit RTSTerrain.FindAt. |
GetWaterDepth(RTSTerrain, Vector3, out float depth) | Wasseroberfläche minus Boden in Metern. False, wenn die Stelle trocken ist. |
Classify(RTSTerrain, Vector3, out float depth) | WaterDepthClass.Dry, Shallow oder Deep (anhand von Shallow Depth Limit). |
IsShallowWater(…), IsDeepWater(…), GetShallowDepthLimit(RTSTerrain) | Kurzformen für die Klassifizierung. |
GetWaterSurface(RTSTerrain, Vector3, float time, out float height, out Vector3 normal) | Die animierte Oberfläche genau so, wie sie gerendert wird: Ruhehöhe + Wellen, in Ufernähe beruhigt, einschließlich des Wave Amplitude Multiplier des Renderers. |
float ShaderTime | Die Zeitbasis, mit der der Shader animiert (Time.time im Play Mode und in Player-Builds, Time.realtimeSinceStartup im Edit Mode). Übergib sie als time, damit CPU- und GPU-Wellen übereinstimmen. |
SampleWaveOffset(Vector3, float waveHeight, float time) | Wellenversatz ohne Beruhigung am Ufer. |
EvaluateWaves(float2, float time), EvaluateWaveSurface(float2, float time, float amplitude, out float offset, out float3 normal), WaveAttenuation(float depth, float foamDistance) | Burst-kompatible Bausteine für Job-Systeme (zum Beispiel Tausende schwimmender Einheiten). |
GetProfile(RTSTerrain), CreateDefaultProfile() | Das verwendete Profil; ein neues Standardprofil, das du gefahrlos ändern kannst. |
using RTSTerrainStudio;
using UnityEngine;
/// Makes a boat float on the animated water surface and keeps it out of shallow water.
public class FloatingBoat : MonoBehaviour
{
[SerializeField] float draft = 0.15f; // how deep the hull sits in the water
[SerializeField] float heading; // yaw in degrees, set by your movement code
[SerializeField] float tiltSmoothing = 4f;
void LateUpdate()
{
Vector3 p = transform.position;
RTSTerrain terrain = RTSTerrain.FindAt(p);
if (terrain == null) return;
// ShaderTime is the clock the water shader uses, so the boat bobs in sync with the rendered waves.
if (!WaterMath.GetWaterSurface(terrain, p, WaterMath.ShaderTime, out float height, out Vector3 normal))
return; // dry ground: leave the boat alone
transform.position = new Vector3(p.x, height - draft, p.z);
// Tilt with the wave normal, keep our own heading.
Quaternion target = Quaternion.FromToRotation(Vector3.up, normal) * Quaternion.Euler(0f, heading, 0f);
transform.rotation = Quaternion.Slerp(transform.rotation, target, 1f - Mathf.Exp(-tiltSmoothing * Time.deltaTime));
}
/// Boats need deep water: use this before accepting a move order.
public static bool CanSailTo(Vector3 world)
{
RTSTerrain terrain = RTSTerrain.FindAt(world);
return terrain != null && WaterMath.Classify(terrain, world, out _) == WaterDepthClass.Deep;
}
}
Dekor
RTSDecorManager hält die Szene mit dem Dekor der Karte synchron (Doodads: Felsen, Kisten, Ruinen, Zäune, beliebige Prefabs). Er reagiert auf die Änderungs-Events des Terrains und arbeitet inkrementell. Eine Bearbeitung betrifft nur die Instanzen, die hinzugefügt, entfernt oder geändert wurden. Objekte werden wiederverwendet, wenn sich nur ihr Transform ändert, und beim Entfernen in einem Pool abgelegt. Höhenänderungen am Terrain setzen die Instanzen im geänderten Bereich neu auf den Boden. Das Laden einer Karte oder eine Änderung der Dekorbibliothek baut alles neu auf.
Spawn-Modi
Jeder Prototyp in der Dekorbibliothek legt einen Spawn Mode fest:
GameObject (Standard) | Instanced | |
|---|---|---|
| Darstellung | Eine Kopie des Prefabs (bzw. der Variante) unter dem Kindobjekt „RTS Decor“, mit einer Komponente RTSDecorInstance | Gezeichnet mit Graphics.RenderMeshInstanced (erste LOD-Stufe des Prefabs), bis zu 1023 Instanzen pro Draw Call |
| Culling | Unitys eigenes | Pro Patch (Frustum + Max Draw Distance) |
| Skripte, Collider, Animation | Ja | Nein (es gibt keine Objekte) |
| Tönung | Über einen MaterialPropertyBlock (_BaseColor und _Color) | Nur bei Shadern, die das unterstützen (siehe Tönung pro Instanz) |
| Ideal für | Interaktive oder zerstörbare Props, einige hundert bis einige tausend Objekte | Statische Szenerie in großer Zahl |
Platzierungsregel: Position = LocalToWorld(x, ground + y + prototype yOffset, z) oder, mit dem Flag KeepHeight, die gespeicherte absolute Höhe. Rotation = Terrain-Rotation × Instanzrotation, Skalierung = Instanzskalierung. Gespawnte Prefabs behalten zusätzlich die Rotation und Skalierung, die an ihrem Root-Objekt festgelegt sind.
Instanziertes Dekor benötigt bei seinen Materialien die Option Enable GPU Instancing. Ist sie aus, wird eine Laufzeitkopie mit Instancing verwendet, und eine Warnung fordert dich auf, das Kontrollkästchen zu aktivieren. Mit dem Projektvalidator behebst du das für alle Materialien auf einmal.
Die Felder der Prototypen (Varianten, zufällige Rotation und Skalierung, Tönung, Grundfläche, Sichtblockierung, Schatten) sind unter Dekorbibliothek beschrieben.
Manager-Einstellungen
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Show In Scene View | an | Zeichnet instanziertes Dekor in den Kameras der Scene View. Gespawnte GameObjects sind immer sichtbar. |
| Max Draw Distance | 0 | Instanziertes Dekor, das weiter als dieser Wert von der Kamera entfernt ist, wird nicht gezeichnet (0 = keine Grenze). |
| Shadow Culling Margin | 12 m | Zusätzlicher Rand um die Patches beim Frustum Culling, damit Props knapp außerhalb des Sichtfelds trotzdem Schatten hineinwerfen. |
| Instanced Tint | an | Übergibt Tönungen pro Instanz an instanziertes Dekor, dessen Shader das unterstützt. |
| Patch Size | 16 | Seitenlänge eines Patches für Culling und Abfragen, in Zellen (4–64). Eine Änderung baut das Dekor neu auf. |
| Pool Capacity Per Prefab | 64 | Entferntes GameObject-Dekor wird inaktiv zur Wiederverwendung aufbewahrt, bis zu dieser Anzahl Objekte pro Prefab. |
| Log Missing Prototypes | an | Gibt eine Warnung pro fehlender Prototyp-ID, fehlender Bibliothek oder zerstörtem Prefab aus. |
Der Inspector der Komponente Decor Manager zeigt Instances, GameObjects, Instanced und Sync (ob Änderungen ausstehen) sowie die Schaltflächen Rebuild (RebuildAll(): zerstört alles und erzeugt es neu, leert die Pools, liest die Prefabs neu ein), Sync Now (SyncNow()) und Select Library.
Platzhalter
Wenn eine Karte auf eine Prototyp-ID verweist, die in der Bibliothek fehlt, wenn das Terrain gar keine Dekorbibliothek hat oder wenn ein Prefab gelöscht wurde, wird die Instanz als kleiner magentafarbener Würfel angezeigt. Dabei geht nichts verloren: Die Instanz bleibt in der Karte und erscheint wieder normal, sobald der Prototyp zurück ist. IsPlaceholder(id) verrät dir, ob eine Instanz ein Platzhalter ist. Platzhalter zählen als instanziert.
Magentafarbene Würfel nach dem Öffnen einer fremden Karte bedeuten meist, dass das Terrain eine andere Dekorbibliothek verwendet als die, mit der die Karte erstellt wurde. Weise im Inspector von RTSTerrain die richtige Bibliothek zu.
Tönung pro Instanz mit eigenen Shadern
Instanziertes Dekor wird nur dann pro Instanz getönt, wenn der Shader die Tönung aus dem Instancing-Puffer lesen kann. URP Lit, Simple Lit und Unlit behalten ihre Materialfarbe. Shader aktivieren die Unterstützung über ein SubShader-Tag:
| Tag | Konstante | Der Shader muss |
|---|---|---|
"RTSInstanceTint" = "True" | RTSDecorManager.InstanceTintShaderTag | seine Albedo mit dem instanzierten _RTS_InstanceTint multiplizieren (rgb = Tönung, a = 1). Der Shader RTS Terrain Studio/Vegetation des Pakets wird automatisch erkannt. |
"RTSInstancedColor" = "True" | RTSDecorManager.InstancedColorShaderTag | _BaseColor und _Color mit UNITY_ACCESS_INSTANCED_PROP aus einem UNITY_INSTANCING_BUFFER lesen. Geschrieben wird jeweils Materialfarbe × Tönung. |
Dekor-API: Picking, Abfragen und Events
Picking und Abfragen benötigen keine Collider. Jede Instanz wird als orientierte Box geprüft, die aus den Mesh-Bounds des Prefabs gebildet wird. Deshalb funktionieren sie in beiden Spawn-Modi, sogar bei instanziertem Dekor ohne GameObject.
| Member | Beschreibung |
|---|---|
bool TryPick(Ray worldRay, float maxDistance, out uint instanceId) (+ Überladung mit out float hitDistance) | Die nächstgelegene Instanz, die ein Strahl in Weltkoordinaten trifft. |
int QueryRadius(Vector3 world, float radius, List<uint> results) | Fügt die IDs innerhalb eines Radius auf der Bodenebene hinzu. Die Liste wird vorher nicht geleert. Arbeitet allokationsfrei. Eine Überladung mit IEnumerable<uint> legt eine neue Liste an. |
int QueryLocalRect(Rect localRect, List<uint> results) | Fügt die IDs hinzu, deren Pivot in einem kartenlokalen XZ-Rechteck liegt (Rechteckauswahl). |
GameObject GetInstanceObject(uint id) | Das gespawnte Objekt oder null (bei instanziertem Dekor oder unbekannter ID). |
ContainsInstance, TryGetInstance(uint, out DecorInstanceData), IsInstanced, IsPlaceholder | Nachschlagen von Instanzen (Daten auf dem Stand der letzten Synchronisierung). |
TryGetWorldBounds(uint, out Bounds), TryGetObjectMatrix(uint, out Matrix4x4) | Bounds in Weltkoordinaten; Matrix des Objekt-Roots einschließlich der im Prefab festgelegten Rotation und Skalierung des Roots. |
Matrix4x4 GetWorldMatrix(in DecorInstanceData), GetWorldMatrix(in DecorInstanceData, string prototypeId) | Weltmatrix des Pivots, auch für Daten, die noch nicht in der Karte sind (Platzierungsvorschauen). |
SpawnedCount, InstancedCount, InstanceCount, Container, PatchSize, HasPendingChanges | Zustand und Statistiken. |
InstancesRebuilt, InstancesChanged | Nach einem vollständigen Neuaufbau; nach einer inkrementellen Aktualisierung. |
InstanceSpawned, InstanceDespawning | Ein Objekt wurde gespawnt oder aus dem Pool genommen; ein Objekt wird gleich in den Pool zurückgelegt oder zerstört. Objekte, die einen weichen Neuaufbau überstehen, lösen InstanceSpawned nicht erneut aus. |
RTSDecorInstance: InstanceId (0, solange im Pool), PrototypeId, Variant, Owner, Prototype, IsLive, TryGetData, static FromCollider(Collider) | Komponente auf dem Root jedes gespawnten Objekts. Ihre Felder werden gefüllt, bevor Awake/OnEnable des Prefabs selbst laufen. Deine Prefab-Skripte können sie dort also schon lesen. |
using System.Collections.Generic;
using RTSTerrainStudio;
using Unity.Mathematics;
using UnityEngine;
/// Click a decor object to destroy it; Shift+click destroys everything within 5 m of the click.
public class DecorDemolisher : MonoBehaviour
{
[SerializeField] RTSDecorManager decor;
[SerializeField] Camera viewCamera;
// Reused every click: QueryRadius appends to it and never allocates.
readonly List<uint> nearby = new List<uint>();
void Update()
{
if (decor == null || viewCamera == null || !RTSInput.GetMouseButtonDown(0)) return;
Ray ray = viewCamera.ScreenPointToRay(RTSInput.PointerPosition);
// Works for GameObject and instanced decor alike, without colliders.
if (!decor.TryPick(ray, 500f, out uint id, out float distance)) return;
if (RTSInput.Shift)
{
nearby.Clear();
decor.QueryRadius(ray.GetPoint(distance), 5f, nearby);
foreach (uint other in nearby) Remove(other);
}
else
{
Remove(id);
}
}
void Remove(uint id)
{
RTSTerrain terrain = decor.Terrain;
MapData map = terrain.Map;
int index = map.FindDecorIndex(id);
if (index < 0) return;
DecorInstanceData data = map.Decor[index];
map.Decor.RemoveAt(index);
// Tell the terrain what changed. The decor manager removes (pools) the object,
// and the pathing map reopens the footprint automatically.
int2 cell = map.LocalToCell(data.position.xz);
terrain.NotifyChanged(MapLayers.Decor, new RectInt(cell.x, cell.y, 1, 1));
}
}
using RTSTerrainStudio;
using UnityEngine;
/// Gives every spawned decor object of the category "Destructible" some hit points.
public class DestructibleDecorSetup : MonoBehaviour
{
[SerializeField] RTSDecorManager decor;
void OnEnable()
{
if (decor != null) decor.InstanceSpawned += HandleSpawned;
}
void OnDisable()
{
if (decor != null) decor.InstanceSpawned -= HandleSpawned;
}
void HandleSpawned(RTSDecorManager manager, RTSDecorInstance instance)
{
// Prototype looks up the library entry, e.g. its category or footprint.
DecorPrototype prototype = instance.Prototype;
if (prototype == null || prototype.category != "Destructible") return;
// Pooled objects come back here when they are reused, so add the component only once.
if (!instance.TryGetComponent(out Health health)) health = instance.gameObject.AddComponent<Health>();
health.hitPoints = 100;
}
}
/// Example gameplay component.
public class Health : MonoBehaviour
{
public int hitPoints;
}
Nur Prototypen im GameObject-Modus lösen InstanceSpawned aus, weil instanziertes Dekor keine Objekte erzeugt.
Umgebung
Jede Karte speichert ihre eigene Beleuchtung in MapData.Environment: Farbe, Intensität und Rotation der Sonne, die Farbe des Umgebungslichts und den Nebel. Eine verschneite Karte kann deshalb kaltes Licht mitbringen, eine Wüstenkarte warmes. Die Komponente RTSEnvironment überträgt diese Werte auf die Szene, sobald eine Karte geladen wird, und jedes Mal, wenn sich die Umgebung ändert.
Einrichtung
- Wähle . Dadurch entsteht ein GameObject „Environment“ mit
RTSEnvironmentund zusätzlich ein Directional Light „Sun“, falls die Szene noch keins hat. Enthalten die geöffneten Szenen bereits eine Umgebung, kannst du stattdessen diese auswählen. Mehrere Umgebungen würden um dieselben Beleuchtungseinstellungen konkurrieren. - Alternativ fügst du die Komponente über dem Terrain-GameObject hinzu (im Inspector von
RTSTerraingibt es dafür die Schaltfläche Add). - Bearbeite die Werte im Tab Map des Editorfensters (), im Panel Environment: Sun Colour, Sun Intensity, Sun Rotation (x = Höhenwinkel über dem Horizont, y = Drehwinkel), Ambient Colour, Fog, Fog Colour, Fog Density. Änderungen lassen sich rückgängig machen und werden mit der Karte gespeichert.
- Oder beleuchte die Szene von Hand und klicke auf Capture From Scene (Editorfenster) bzw. Use Current Lighting (Inspector der Komponente). So speicherst du die aktuelle Sonne, das Umgebungslicht und den Nebel als einen einzigen Undo-Schritt in der Karte.
Die angewendete Beleuchtung ist eine Vorschau. Sie wird nicht im Undo-Verlauf erfasst und markiert die Szene nicht als geändert. Unmittelbar vor dem Speichern der Szene wird die ursprüngliche Szenenbeleuchtung wiederhergestellt und danach die Kartenbeleuchtung erneut angewendet. Die Kartenbeleuchtung landet deshalb nie in deiner Szenendatei. Restore Scene Lighting im Inspector holt die Beleuchtung zurück, die vor dem ersten Anwenden aktiv war.
Einstellungen der Komponente
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Terrain | leer | Terrain, dessen Kartenbeleuchtung angewendet wird. Leer = das RTSTerrain auf diesem GameObject, sonst das Haupt-Terrain (RTSTerrain.Main). Ein später aktiviertes Terrain wird automatisch übernommen. |
| Sun | leer | Directional Light, das als Sonne dient. Leer = RenderSettings.sun oder das hellste aktive Directional Light. |
| Apply In Edit Mode | an | Wendet die Kartenbeleuchtung während der Bearbeitung an. Ist die Option aus, wird sie nur im Play Mode angewendet. |
| Apply Sun | an | Wendet Farbe, Intensität und Rotation der Sonne an (in Weltkoordinaten). |
| Apply Ambient | an | Wendet das Umgebungslicht an. |
| Ambient Mode | Trilight | Flat = eine einzige Umgebungsfarbe. Trilight = Verlauf Himmel / Äquator / Boden, abgeleitet aus der Umgebungsfarbe der Karte. |
| Sky Factor | 1.2 | Trilight: Himmelsfarbe = Umgebungsfarbe × dieser Faktor (0–3). |
| Ground Factor | 0.55 | Trilight: Bodenfarbe = Umgebungsfarbe × dieser Faktor (0–3). |
| Apply Fog | an | Wendet den Nebelzustand (an/aus), die Nebelfarbe und die Nebeldichte an. |
| Fog Mode | Exponential Squared | Abnahmemodus, mit dem der Nebel angewendet wird (die Karte speichert nur Farbe und Dichte). |
| Restore On Disable | an | Stellt beim Deaktivieren der Komponente die Szenenbeleuchtung wieder her, die vor dem ersten Anwenden aktiv war. |
Standardwerte einer neuen Karte: Sonnenfarbe warmes Weiß (1, 0.956, 0.84), Intensität 1.2, Rotation (50, −30), Umgebungslicht (0.45, 0.5, 0.58), Nebel aus, Nebelfarbe (0.6, 0.68, 0.75), Dichte 0.004.
Für einen Tag-Nacht-Zyklus in deinem Spiel steuerst du das Sonnenlicht direkt aus deinem eigenen Skript. RTSEnvironment wendet die Kartenwerte nur dann erneut an, wenn eine Karte geladen wird oder sich ihre Umgebung ändert. Alternativ deaktivierst du Apply Sun und übernimmst nur Umgebungslicht und Nebel aus der Karte.
Umgebungs-API
| Member | Beschreibung |
|---|---|
void Apply() | Wendet die Umgebung der Karte sofort auf die Szene an (berücksichtigt Apply In Edit Mode). |
void CaptureFromScene(RTSEditSession session) | Schreibt die Szenenbeleuchtung als einen Undo-Schritt der Sitzung in die Karte. |
void CaptureFromScene(RTSToolContext ctx = null) | Schreibt die Szenenbeleuchtung in die Karte. Ein Undo-Schritt wird nur innerhalb von RTSEditSession.RunOperation aufgezeichnet. |
void RestoreSceneState() | Stellt die Beleuchtung wieder her, die vor dem ersten Anwenden gesichert wurde. |
RTSTerrain Terrain { get; set; }, Light Sun { get; set; }, bool CanApply | Zugeordnetes Terrain, Sonnenlicht und ob das Anwenden gerade erlaubt ist. |
static Light FindSun() | RenderSettings.sun, wenn es ein Directional Light ist, sonst das hellste aktive Directional Light. |
using RTSTerrainStudio;
using UnityEngine;
/// Switches the current map to a stormy mood. RTSEnvironment applies it immediately.
public static class StormLighting
{
public static void Apply(RTSTerrain terrain)
{
MapData map = terrain != null ? terrain.Map : null;
if (map == null) return;
// Work on a copy, then assign it: this is how the editor tools change the environment too.
EnvironmentSettings env = map.Environment.Clone();
env.sunColor = new Color(0.7f, 0.75f, 0.85f);
env.sunIntensity = 0.45f;
env.ambientColor = new Color(0.3f, 0.33f, 0.4f);
env.fog = true;
env.fogColor = new Color(0.45f, 0.48f, 0.52f);
env.fogDensity = 0.012f;
map.Environment = env;
// MapLayers.Environment tells RTSEnvironment (and the in-game editor) that the lighting changed.
terrain.NotifyChanged(MapLayers.Environment, map.CellBounds);
}
}
Performance-Leitfaden
Mit den Standardeinstellungen laufen typische RTS-Karten (128–256 Zellen pro Seite) auf dem Desktop gut. Dieser Abschnitt erklärt, was jede Einstellung kostet, damit du große Karten und mobile Builds gezielt abstimmen kannst.
Kurze Checkliste
- Lasse das Burst-Paket installiert und die Burst-Kompilierung aktiviert ().
- Erstelle die Textur-Arrays des Tilesets im Editor bei aktivierter Option Compress Arrays, und zwar für die Plattform, auf der du dein Spiel veröffentlichst (der Inspector des Tilesets zeigt das Format und eine geschätzte Größe).
- Aktiviere Enable GPU Instancing in jedem Material instanzierter Bäume, Details und Dekorobjekte (der Projektvalidator erledigt das mit einem Klick).
- Verwende den Spawn-Modus
Instancedfür Bäume und Props, die nur Kulisse sind. Behalte den ModusGameObjectfür Objekte mit Gameplay-Funktion bei. - Wähle Splat-Auflösung und Array-Auflösung passend zur Plattform (siehe unten). Beide kosten vor allem Speicher.
- Führe Validate Map aus (Tab Map ▸ Validation). Die Validierung warnt vor zu vielen Bäumen (20 000), GameObject-Bäumen (1 500), Dekorobjekten (6 000), GameObject-Dekorobjekten (2 000), Detailschichten (8) und vor großen Karten (über 512 Zellen).
Die Chunk-Größe wählen
| Chunk Size | Draw Calls (256 × 256-Karte) | Neuaufbaukosten pro Bearbeitung | Verwendung |
|---|---|---|---|
| 16 | 256 Chunks | Am geringsten: schnellste Rückmeldung bei Pinselstrichen | Kleine Karten, viel Bearbeitung (In-Game-Editor auf langsameren Rechnern) |
| 32 (Standard) | 64 Chunks | Ausgewogen | Die meisten Karten |
| 64 | 16 Chunks | Am höchsten: Jede Bearbeitung baut bis zu 64 × 64 Zellen neu auf, an Rändern zusätzlich die Nachbarn | Große Karten, die selten bearbeitet werden (ausgelieferte Karten, die Spieler nur ansehen) |
Jeder Chunk kostet einen Draw Call pro gerenderter Ansicht (die Hauptkamera und jede Schattenkaskade). Der SRP Batcher fasst sie günstig zusammen, weil sie sich ein Material teilen. Chunks sind außerdem die Einheit für das Culling: Mit kleineren Chunks arbeitet das Culling präziser, wenn die Kamera nah am Boden ist.
Unterteilungen
Die Vertexanzahl des glatten Bodens wächst quadratisch mit Surface Subdivisions:
| Surface Subdivisions | Vertices eines flachen 32 × 32-Chunks | Effektive Cliff Subdivisions beim Standardwert 4 (bei 8) |
|---|---|---|
| 1 | 1 089 | 4 (8) |
| 2 (Standard) | 4 225 | 4 (8) |
| 3 | 9 409 | 6 (12) |
| 4 | 16 641 | 4 (8) |
Cliff Subdivisions wird auf eine gerade Zahl aufgerundet, die zugleich ein Vielfaches von Surface Subdivisions ist. So teilen sich Klippenzellen und glatte Zellen jeden Randvertex. Bei 3 Oberflächenunterteilungen wird das Unterraster der Klippen zu einem Vielfachen von 6, und das ist teuer. Bevorzuge 1, 2 oder 4. Jede Klippenzelle kostet ungefähr (Klippenunterteilungen)² × 2 Dreiecke plus ihre Wände (so viele Reihen pro Stufe, wie Cliff Wall Segments angibt). Karten mit vielen Klippen profitieren deshalb am meisten von moderaten Klippeneinstellungen.
Ein Chunk-Vertex belegt 28 Byte. Die Indizes sind 16-Bit-Werte, solange ein Chunk unter 65 536 Vertices bleibt. Eine flache 256 × 256-Karte mit 2 Unterteilungen hat rund 260 000 Vertices, also etwa 10 MB Vertex- und Indexdaten. Klippen kommen noch hinzu, und Chunk-Collider brauchen ihre eigenen gecookten Daten.
Splat-Auflösung
Die Splat-Auflösung (Texel pro Zelle: 1, 2, 4 oder 8, Standard 4) legst du beim Erstellen einer Karte fest (im New Map Wizard). Sie bestimmt, wie fein sich Bodentexturen malen lassen. Splat-Daten bestehen immer aus 4 Splatmaps im Format RGBA32 (16 Byte pro Texel) und liegen dreimal vor: in den Kartendaten, in der CPU-Kopie der Texturen und auf der GPU (die beiden letzten mit Mipmaps, +33 %).
| Kartengröße (Zellen) | Splat-Auflösung 2 | Splat-Auflösung 4 (Standard) | Splat-Auflösung 8 |
|---|---|---|---|
| 128 × 128 | 1 MiB Daten + 1.3 MiB GPU | 4 MiB + 5.3 MiB | 16 MiB + 21 MiB |
| 256 × 256 | 4 MiB + 5.3 MiB | 16 MiB + 21 MiB | 64 MiB + 85 MiB |
| 512 × 512 | 16 MiB + 21 MiB | 64 MiB + 85 MiB | 256 MiB + 341 MiB |
| 1024 × 1024 | 64 MiB + 85 MiB | 256 MiB + 341 MiB | 1 GiB + 1.3 GiB |
(Für die CPU-Kopie der Texturen rechnest du den GPU-Wert noch einmal hinzu.) Das Malen kostet wenig: Nur die geänderten Zeilen werden kopiert. Bei Texturen mit mehr als 1 Million Texel wird bei einem geänderten Bereich bis 512 × 512 Texel nur dieser Bereich hochgeladen. Seine Mip-Kette wird auf der CPU von einem Burst-Job (SplatMipDownsampleJob) neu berechnet und auf der GPU an die passende Stelle kopiert.
Auflösung 2 reicht für den harten Kachel-Look klassischer RTS-Spiele (Tile Paint). Auflösung 4 ergibt weiche, detaillierte Übergänge. Auflösung 8 ist nur für sehr kleine Zellgrößen oder nah herangezoomte Kameras gedacht.
Auflösung der Textur-Arrays
Jedes Tileset packt seine Boden- und Klippentexturen in zwei Texture2DArrays (Albedo/Höhe und Normalen) mit je 20 Slices, und zwar in der Array Resolution des Tilesets (256, 512, 1024 als Standard, 2048). Speicherbedarf beider Arrays zusammen, einschließlich Mipmaps:
| Array Resolution | Unkomprimiert RGBA32 | BC7 / DXT5 (Desktop, Konsolen) | ASTC 6×6 (Android, iOS) |
|---|---|---|---|
| 256 | 13 MiB | 3.3 MiB | 1.5 MiB |
| 512 | 53 MiB | 13 MiB | 5.9 MiB |
| 1024 (Standard) | 213 MiB | 53 MiB | 24 MiB |
| 2048 | 853 MiB | 213 MiB | 95 MiB |
Ist Compress Arrays aktiviert, backt der Inspector des Tilesets komprimierte Arrays für die aktive Build-Plattform: BC7 für Windows, macOS, Linux und Konsolen, ASTC 6×6 für Android, iOS, tvOS und visionOS, für Web die Texturkomprimierung des Builds (DXT5, ASTC 6×6 oder ETC2 RGBA8), sonst DXT5. Fehlen gebaute Arrays oder sind sie im Editor veraltet, erstellt der Renderer stattdessen unkomprimierte RGBA32-Arrays auf der GPU. Für schnelles Iterieren ist das in Ordnung, es kostet aber viermal so viel Speicher wie BC7. Player-Builds erstellen veraltete Arrays vor dem Build neu (siehe Textur-Arrays in Player-Builds). Alle Terrains mit demselben Tileset teilen sich die Arrays.
Kann eine GPU oder ein Browser das gebackene Format nicht samplen, dekomprimiert Unity die Arrays beim Laden. Sie belegen dann etwa viermal so viel Speicher. Player-Builds geben die CPU-Kopie gebauter Arrays bei der ersten Verwendung frei. In einem Player-Build belegen die gebauten Arrays daher nur den in der Tabelle angegebenen GPU-Speicher.
Um zu prüfen, welche Arrays ein laufendes Spiel verwendet, erstelle im Memory Profiler einen Snapshot und suche nach Render Textures namens „RTS Albedo Array (…)“ und „RTS Normal Array (…)“. Findest du sie, sind die zur Laufzeit erstellten unkomprimierten Arrays im Einsatz.
Detaildistanz und Vegetation
- Details (Gras und Kleinpflanzen) sind auf der GPU der wahrscheinlichste Engpass, weil Gras aus vielen kleinen Dreiecken mit Alpha Clipping besteht. Die sichtbare Anzahl skaliert mit Render Distance² × Dichte. Halbierst du die Detaildistanz, sinkt die Menge des gezeichneten Grases auf etwa ein Viertel.
- Speicher für Details: Jeder bemalte Patch hält seine Instanzen unabhängig von der Kamera auf der GPU, mit 32 Byte pro Instanz. Beispiel: Eine Detailschicht mit voller Dichte (Max Density 6, Detailauflösung 2 = 4 Texel pro Zelle) über eine ganze 256 × 256-Karte ergibt etwa 1.6 Millionen Instanzen, also rund 50 MB. Male Gras dort, wo es darauf ankommt, oder senke Max Density.
- Detailschatten sind standardmäßig aus. Schaltest du sie ein, wird das gesamte Gras für jede Schattenkaskade erneut gerendert.
- Bäume: Instanzierte Bäume kosten einen Draw Call pro 1 023 Instanzen, je Teil und je Patch-Gruppe. Verwende eine LODGroup mit einem günstigen LOD1. GameObject-Bäume kosten jeweils ein GameObject (bleibe unter etwa 1 500).
- Dekor: Lege für instanziertes Dekor in großen Karten Max Draw Distance fest. GameObject-Dekor wird von Unity gecullt, kostet aber trotzdem jeweils ein GameObject (bleibe unter etwa 2 000).
- Wasser: Cell Subdivisions 1 viertelt die Wasser-Vertices gegenüber dem Standardwert 2.
Burst und Jobs
Alle rechenintensiven Schleifen laufen in Burst-kompilierten Jobs: Chunk-Meshes (BuildChunkGeometryJob, CopyChunkPartsJob), Splat-Mips (SplatMipDownsampleJob), Wasser-Meshes (WaterChunkMeshJob), Verteilung der Details (DetailScatterJob), Baummatrizen (TreeMatrixJob) und Dekorplatzierung (DecorPlacementJob, DecorPartMatricesJob). Pinsel, Pathing, Nebel des Krieges, der Pathfinder und der Minikarten-Baker nutzen ebenfalls Burst. Chunk-Collider werden parallel auf Worker-Threads gebacken.
- Die Mesh-Jobs werden synchron kompiliert. So übernimmt beim ersten Aufbau nach einem Domain Reload kein langsamer Managed-Fallback die Arbeit.
- Miss die Performance in einem Player-Build oder mit ausgeschalteten Burst-Safety-Checks (). Safety Checks im Editor und der Jobs Debugger machen Jobs spürbar langsamer.
- Ist die Burst-Kompilierung deaktiviert, funktioniert weiterhin alles, aber Mesh-Neuaufbauten und Pinselstriche werden um ein Vielfaches langsamer.
- Web-Builds haben keine Worker-Threads, daher laufen alle Jobs nacheinander auf dem Hauptthread. Vorgänge, die die ganze Karte betreffen, dauern dort spürbar länger (siehe Rendering und Performance bei Web-Builds).
Profiling
Öffne und suche auf dem Main Thread nach diesen Markern:
| Marker | Was er misst |
|---|---|
RTSVegetation.Update | Übertragen von Kartenänderungen auf die Vegetation (enthält die beiden Build-Marker darunter). |
RTSVegetation.BuildDetails | Neuerzeugung veränderter Detail-Patches. |
RTSVegetation.BuildTrees | Neuaufbau instanzierter Baumgruppen. |
RTSVegetation.Draw | Culling und Absetzen der Vegetations-Draw-Calls für eine Kamera. |
Schalte im CPU-Modul auf Timeline um, dann siehst du die oben genannten Job-Namen auf den Worker-Threads. Für die GPU zählst du mit dem Frame Debugger () die Draws von Terrain-Chunks, Details und instanzierten Objekten. Die Inspectors von Vegetation Renderer, Water Renderer und Decor Manager zeigen live die Anzahl der Instanzen und Vertices. Der Inspector von RTSTerrain zeigt die Anzahl der Chunks und den Speicherbedarf der Karte.
Speicherbedarf in Zahlen
Die Kartendaten selbst (der Wert Map Memory im Inspector von RTSTerrain, MapData.GetMemoryUsage()) belegen:
- 11 Byte pro Vertex (Höhe, Klippenstufe, Flags, Klippentyp, Wasserhöhe),
- 16 Byte pro Splat-Texel,
- 1 Byte pro Pathing-Texel,
- 1 Byte pro Detail-Texel und Detailschicht.
| Kartengröße (Standardwerte: Splat 4, Detail 2, Pathing 2, 1 Detailschicht) | Kartendaten | Splat-Texturen (GPU) | Textur-Arrays (1024, BC7) |
|---|---|---|---|
| 128 × 128 (256 m bei Zellen von 2 m) | ≈ 4.3 MiB | ≈ 5.3 MiB | 53 MiB pro Tileset |
| 256 × 256 | ≈ 17 MiB | ≈ 21 MiB | 53 MiB pro Tileset |
| 512 × 512 | ≈ 69 MiB | ≈ 85 MiB | 53 MiB pro Tileset |
| 1024 × 1024 | ≈ 275 MiB | ≈ 341 MiB | 53 MiB pro Tileset |
Hinzu kommen die Chunk-Meshes und -Collider (siehe Unterteilungen), die Detailinstanzen (je 32 Byte), die Pathing-Karte und die Texturen für den Nebel des Krieges. Der Undo-Verlauf des Editors hat ein eigenes Speicherbudget (siehe die Einstellungen des Editorfensters).
Empfohlene Kartengrößen
Karten können 16 bis 1024 Zellen pro Seite groß sein, in Schritten von 16. Standard sind 128 × 128 Zellen zu je 2 m.
| Größe (Zellen) | Typische Verwendung | Hinweise |
|---|---|---|
| 64–128 | 1v1- und kleine Gefechtskarten, Mobilgeräte | Alles wird sofort neu aufgebaut. Eine gute Größe für den In-Game-Editor auf jedem Gerät. |
| 128–256 | 2–4 Spieler | Der ideale Bereich. Alle Standardwerte sind darauf abgestimmt. |
| 256–512 | Große Karten, 4–8 Spieler | Desktop. Erwäge Splat-Auflösung 2–4 und Chunk Size 32–64. |
| über 512 | Sehr große Karten | Die Kartenvalidierung zeigt einen Info-Hinweis: Bearbeitung und Pathing-Neuaufbau dauern länger. Behalte den Splat-Speicher im Blick (Tabelle oben) und erwäge, die Welt auf mehrere RTSTerrains aufzuteilen. |
Mehrere Terrains lassen sich nebeneinander platzieren, jedes mit eigenem Transform. RTSTerrain.FindAt und RTSTerrain.RaycastAll finden das passende Terrain für eine Weltposition.
Hinweise für Mobilgeräte
- Textur-Arrays: Erstelle sie bei aktiver Android- bzw. iOS-Plattform, damit sie als ASTC 6×6 gebacken werden (sobald sie einmal erstellt sind, halten Player-Builds sie für die aktive Plattform aktuell). Auf Smartphones reicht meist eine Array Resolution von 512.
- Splat-Auflösung: 2 Texel pro Zelle vierteln den Splat-Speicher gegenüber dem Standardwert 4.
- Schichten: Jede an einem Pixel sichtbare Bodenschicht kostet Textur-Samples. Verwende weniger Schichten pro Bereich und setze Anti Tiling bei Schichten, deren Kachelung nicht auffällt, auf 0 (das spart das zweite Sample).
- Beleuchtung: Verwende ein Override-Material mit ausgeschalteten Specular Highlights und Environment Reflections (siehe oben).
- OpenGL ES und WebGL 2.0: Die Shader sind für Shader Model 3.5 ausgelegt und laufen daher auf Geräten mit OpenGL ES 3.0 und im Browser. Diese APIs garantieren nur 16 Fragment-Texturen pro Draw Call, und der Forward Pass des Terrains belegt selbst schon 10 davon. Das URP Asset Mobile aus den URP-Projektvorlagen von Unity (Schatten des Hauptlichts, Reflection Probe Blending) fügt bereits 4 weitere hinzu, und mit Light Cookies ist die Grenze erreicht. Eine Shader-Variante, die mehr Texturen braucht, lässt sich nicht laden, und das Terrain verschwindet oder wird pink dargestellt. Schalte im URP Asset für GLES-Geräte und Web-Builds Reflection Probe Blending aus, wenn deine Szenen keine Probes überblenden. Lasse außerdem Light Cookies, Decals, SSAO, Schatten zusätzlicher Lichter und Adaptive Probe Volumes deaktiviert, solange du sie nicht brauchst. Der Kommentar Texture slot budget in
Shaders/Include/RTSTerrainInput.hlsllistet die Kosten jeder URP-Funktion auf. - Wasser: Schaltest du im mobilen URP Asset Depth Texture oder Opaque Texture aus, um Bandbreite zu sparen, wechselt das Wasser auf seinen Fallback: Ohne Opaque Texture gibt es keine Lichtbrechung, und ohne Depth Texture ignorieren die Ufer Objekte im Wasser. Eine Kamera, die die beiden Texturen für sich selbst einschaltet, bekommt weiterhin das vollständige Wasser. Verwende Cell Subdivisions 1.
- Vegetation: niedrigere Multiplikatoren für Detail Density und Detail Distance, Crossed Quad Count 2, Detailschatten aus.
- Physik: Wenn dein Spiel auf dem Terrain keine Unity-Physik nutzt, setze Collider Mode auf
Noneund verwende für das PickingRTSTerrain.Raycast/RaycastAll. - Geometrie: Surface Subdivisions 1 (der klassische RTS-Look) und Cliff Wall Segments 2 reduzieren die Anzahl der Vertices deutlich.
Ein Low-End-Preset per Code
using RTSTerrainStudio;
using UnityEngine;
/// Applies cheap settings to a terrain and its systems, e.g. from a "Low" graphics option.
public static class LowEndPreset
{
public static void Apply(RTSTerrain terrain)
{
if (terrain == null) return;
// Terrain mesh: fewer vertices, larger chunks, no colliders (picking uses RTSTerrain.Raycast).
RTSTerrainSettings s = terrain.Settings;
s.surfaceSubdivisions = 1;
s.cliffSubdivisions = 4;
s.cliffWallSegments = 2;
s.chunkSize = ChunkSize._64;
s.colliderMode = ColliderMode.None;
// Settings changed from code are applied by a rebuild (the inspector does this automatically).
terrain.RebuildAll();
if (terrain.TryGetComponent(out RTSVegetationRenderer vegetation))
{
vegetation.detailDensityMultiplier = 0.4f;
vegetation.detailDistanceMultiplier = 0.5f;
vegetation.treeDistanceMultiplier = 0.7f;
vegetation.crossedQuadCount = 2;
}
if (terrain.TryGetComponent(out RTSWaterRenderer water))
{
water.cellSubdivisions = 1; // rebuilt automatically when the value changes
}
if (terrain.TryGetComponent(out RTSDecorManager decor))
{
decor.maxDrawDistance = 120f; // instanced props beyond 120 m are skipped
}
}
}
ColliderMode.None entfernt die Chunk-Collider. Danach funktioniert in deinem Spiel nichts mehr, was Physics.Raycast gegen das Terrain einsetzt. Dasselbe gilt für Rigidbodies, die darauf stehen, und für das Backen eines NavMesh aus den Chunk-Collidern. Stelle nur dann um, wenn du die physikfreien Abfragen verwendest.
Gameplay-Systeme
RTS Terrain Studio ist mehr als ein Karteneditor. Deine Karten bringen die Systeme mit, die ein klassisches RTS-Spiel braucht: einen Nebel des Krieges, der Klippenstufen und Bäume berücksichtigt, eine Pathing-Karte mit eingebauter Wegfindung, fertige Gameplay-Beispielskripte (Kamera, Einheiten, Auswahl, Befehle, Minikarte, HUD) und einen Demo-Inhaltsgenerator, der Texturen, Modelle, Karten und spielbare Demoszenen erzeugt.
Dieses Kapitel stellt die Systeme nacheinander vor. Jeder Abschnitt zeigt zuerst, was du ohne Code erreichst, listet dann die Komponenten und ihre Einstellungen auf und endet mit der Scripting-API für Programmierer.
Willst du einfach nur spielen? Führe aus, doppelklicke im Project-Fenster auf die dabei ausgewählte Szene RTS Terrain Studio - Gameplay Demo und drücke Play. Siehe Demoszenen.

Nebel des Krieges
Der Nebel des Krieges verbirgt, was ein Spieler nicht sehen kann. Er folgt den klassischen RTS-Regeln:
- Pro Team. Die Sichtbarkeit wird für bis zu 32 Teams erfasst (Teamindizes 0 bis 31). Jede Einheit und jedes Gebäude gibt seinem eigenen Team Sicht.
- Drei Zustände. Boden, den ein Team noch nie gesehen hat, ist schwarz (unerkundet). Boden, den es schon gesehen hat, aber gerade nicht sieht, ist abgedunkelt (erkundet). Boden, den es gerade sieht, wird normal dargestellt (sichtbar).
- Erkundungsgedächtnis. Jedes Team merkt sich, was es erkundet hat, bis du die Erkundung zurücksetzt.
- Sichtlinie mit Klippenstufen. Eine Einheit sieht ihre eigene Klippenstufe und alles darunter. Höher gelegener Boden ist von unten nie sichtbar und verdeckt die Zellen dahinter.
- Blocker. Bäume (und Dekor, das du als sichtblockierend markierst) verdecken die Zellen dahinter.
- Allianzen und geteilte Sicht. Teams können ihre Sicht in eine Richtung oder in beide Richtungen teilen.
- Verborgene Gegner. Gegnerische Einheiten verschwinden, solange du sie nicht sehen kannst. Gebäude bleiben als „Geisterbilder“ sichtbar, sobald du sie entdeckt hast.
Die Sichtbarkeit wird einige Male pro Sekunde von Burst-Jobs berechnet und in einer Textur gespeichert. Diese Textur wird zeitlich geglättet und auf der GPU weichgezeichnet. So bewegen sich die Nebelränder sanft, statt von Zelle zu Zelle zu springen.
Schnellstart: Nebel des Krieges zu deiner Szene hinzufügen
- Öffne eine Szene, die ein RTS Terrain enthält.
- Wähle . Dadurch wird ein GameObject
Fog of Warmit einer KomponenteRTSFogOfWarManagererstellt und ausgewählt. - Wenn dein URP-Renderer das Renderer Feature für den Nebel des Krieges noch nicht hat, bietet ein Dialog an, es hinzuzufügen. Klicke auf Add Feature. (Du kannst das auch später über oder von Hand erledigen, siehe Rendering.)
- Füge jeder Einheit und jedem Gebäude, das sehen soll, eine Komponente
RTSFogOfWarRevealerhinzu (). Stelle dort Team und Radius ein. - Füge jedem Objekt, das Gegner nicht sehen sollen, eine Komponente
RTSFogOfWarHideablehinzu (). Stelle dort Team ein. Verwende Hide When Not Visible für Einheiten und Ghost When Explored für Gebäude. - Drücke Play. Die Karte ist schwarz, außer rund um die Sichtquellen des unter Local Team eingestellten Teams (standardmäßig Team 0).
Der Nebel läuft nur im Play Mode. Um ihn schon beim Bearbeiten zu sehen, aktiviere am Manager Preview In Edit Mode (oder klicke in seinem Inspector auf Enable Preview). Verbergbare Objekte werden nur im Play Mode ausgeblendet; im Edit Mode bleibt alles sichtbar.
Die Beispieleinheit RTSUnit setzt die Komponenten für Sichtquelle und verbergbares Objekt bereits voraus und konfiguriert sie. Wenn du die Beispiele (oder RTSDemoGame) verwendest, musst du diese Komponenten nicht von Hand hinzufügen. RTSDemoGame erstellt sogar den Manager für dich, wenn die Szene noch keinen hat.
Wie die Sichtbarkeit berechnet wird
Die Karte ist in Nebelzellen unterteilt. Mit dem Standardwert 2 für Resolution Per Cell und der Standard-Zellgröße der Karte von 2 m ist eine Nebelzelle 1 m breit. Für jede Sichtquelle geht der Manager so vor:
- Er bestimmt die Augenhöhe: die Klippenstufe an der genauen Position der Sichtquelle plus ihren Height Bonus.
- Er durchläuft jede Nebelzelle innerhalb des unter Radius eingestellten Sichtradius.
- Je nach Einstellung von Line Of Sight zieht er eine Linie von der Sichtquelle zur Zelle. Die Zelle ist sichtbar, wenn ihre eigene Klippenstufe nicht höher als die Augenhöhe ist und keine Zelle auf dem Weg höher als die Augenhöhe liegt oder (mit Blockern) einen Baum oder sichtblockierendes Dekor enthält. Die blockierende Zelle selbst ist sichtbar. Du siehst also den Rand eines Waldes, aber nicht, was dahinter liegt.
- Er markiert die Zelle als sichtbar und erkundet – für das Team der Sichtquelle und für jedes Team, das die Sicht dieses Teams erhält.
| Modus von Line Of Sight | Verhalten | Verwendung |
|---|---|---|
None | Sichtquellen sehen einen vollen Kreis. Klippen und Bäume blockieren nicht. Günstigster Modus. | Spiele ohne Sichtlinie, sehr viele Einheiten. |
CliffLevels | Klassische RTS-Regel: Eine Sichtquelle sieht ihre eigene Klippenstufe und alles darunter. Höher gelegener Boden blockiert die Sicht. | Offene Karten oder wenn Bäume die Sicht nicht blockieren sollen. |
CliffLevelsAndBlockers (Standard) | Wie CliffLevels, zusätzlich verdecken Bäume und sichtblockierendes Dekor die Zellen dahinter. | Das volle klassische RTS-Erlebnis. |
Welche Objekte die Sicht blockieren, legst du in den Bibliotheken fest:
- Bäume: die Option Blocks Vision eines Baum-Prototyps in der Vegetationsbibliothek (standardmäßig an). Der blockierte Bereich ist der Footprint Radius des Baums mal die Skalierung der Instanz.
- Dekor: die Option Blocks Vision eines Dekor-Prototyps in der Dekorbibliothek (standardmäßig aus). Der blockierte Bereich ist die Grundfläche des Prototyps (Kreis oder gedrehtes Rechteck), skaliert mit der Instanz.
Wachtürme, Hügel und fliegende Späher: Gib ihrer Sichtquelle einen Height Bonus von einer oder mehreren Stufen, damit sie über höhere Klippen blicken können, oder aktiviere für Lufteinheiten Ignore Line Of Sight, damit nichts ihre Sicht blockiert.
Kartenänderungen werden automatisch erkannt (im Editor, im In-Game-Editor und bei Gameplay-Code, der RTSTerrain.NotifyChanged aufruft). Bei Änderungen an Klippenstufen (MapLayers.CliffLevels / VertexFlags) werden nur die geänderten Zellen plus ein Rand von einer Zelle neu abgetastet. Nach Änderungen an Bäumen und Dekor wird das Sichtblocker-Raster der ganzen Karte bei der nächsten Aktualisierung neu aufgebaut. Ein Aufruf von NotifyChangedAll baut das Klippenraster vollständig neu auf.
RTSFogOfWarManager
Ein Manager pro Szene berechnet und rendert den Nebel für ein Terrain. Erstelle ihn über oder . Sind mehrere Manager aktiviert, wird nur der erste (RTSFogOfWarManager.Active) verwendet, und der Inspector zeigt eine Warnung.
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Map | ||
| Terrain | leer | Terrain, das der Nebel abdeckt. Leer bedeutet RTSTerrain.Main (das erste aktivierte Terrain). |
| Resolution Per Cell | 2 | Nebelzellen pro Kartenzelle und Achse: 1, 2 oder 4. Höhere Werte ergeben weichere Ränder, kosten aber mehr. Der Wert wird automatisch gesenkt, damit das Nebelraster höchstens 2048 Zellen pro Achse hat. Eine Änderung baut das Raster neu auf und setzt die Erkundung zurück. |
| Simulation | ||
| Update Interval | 0.1 | Sekunden zwischen zwei Sichtbarkeitsaktualisierungen. Bei 0 wird jeden Frame aktualisiert. |
| Local Team | 0 | Team, dessen Sicht gerendert und von den verbergbaren Objekten verwendet wird (0 bis 31). Wechsle es im Inspector, um zu prüfen, was andere Spieler sehen. |
| Shared Vision | alle 0 | Für jedes empfangende Team eine Bitmaske der Teams, die ihre Sicht mit ihm teilen (Bit t = Team t). Wird meist per Code gesetzt, siehe Allianzen. |
| Line Of Sight | CliffLevelsAndBlockers | Wie Klippen und Objekte die Sicht blockieren. Siehe Wie die Sichtbarkeit berechnet wird. |
| Compute All Teams | aus | Berechnet die Sichtbarkeit für jedes Team, nicht nur für das lokale. Nötig für Server (Interest Management) und für KI-Code, der abfragt, was ein anderes Team sehen kann. |
| Start Explored | aus | Die ganze Karte ist von Anfang an erkundet: keine schwarzen Bereiche, nur der abgedunkelte Nebel. Wird beim Erstellen des Nebelrasters angewendet. |
| Reveal All | aus | Debug: Für jedes Team ist alles sichtbar und erkundet. |
| Look | ||
| Unexplored Color | Schwarz, Alpha 1 | Farbe von Bereichen, die nie erkundet wurden. Alpha bestimmt die Stärke. |
| Explored Color | (0.3, 0.32, 0.38), Alpha 0.6 | Farbton, mit dem erkundete, gerade nicht sichtbare Bereiche multipliziert werden. Alpha bestimmt die Stärke. |
| Fade Speed | 4 | Wie schnell der Nebel ein- und ausblendet (vollständige Überblendungen pro Sekunde). 0 bedeutet sofort. |
| Blur Iterations | 1 | Weichzeichner-Durchgänge auf der GPU für die Nebeltextur (0 bis 4, 0 = aus). |
| Blur Upscale | 2 | Auflösungsfaktor der weichgezeichneten Textur (1 bis 4). Höhere Werte ergeben rundere Ränder. Wird automatisch gesenkt, damit die weichgezeichnete Textur höchstens 2048 Pixel groß ist. |
| Editor | ||
| Preview In Edit Mode | aus | Lässt den Nebel des Krieges im Edit Mode als Vorschau laufen. |
| Shaders | ||
| Blur Shader, Minimap Shader | automatisch zugewiesen | Hier referenziert, damit die versteckten Shader in Player-Builds enthalten sind. Lasse sie unverändert. |
Der Inspector zeigt außerdem Live-Statistiken (Zustand, Größe des Nebelrasters, Speicher, aktive Sichtquellen, Anzahl der Aktualisierungen), eine Live-Vorschau der Nebeltextur (Rot = gerade sichtbar, Grün = erkundet, Schwarz = nie gesehen) und diese Schaltflächen:
| Schaltfläche | Funktion |
|---|---|
| Explore All | Markiert die ganze Karte für das lokale Team als erkundet. |
| Reset Exploration | Vergisst die Erkundung aller Teams. |
| Update Now | Berechnet die Sichtbarkeit sofort neu. |
| Rebuild Terrain Data | Liest Klippenstufen und Blocker erneut aus dem Terrain. |
RTSFogOfWarRevealer
Gibt seinem Team Sicht rund um das GameObject. Füge die Komponente Einheiten, Gebäuden, Wachposten und allem anderen hinzu, was sehen soll. Die Sichtquelle meldet sich beim Aktivieren selbst beim Manager an. Der Manager liest ihre Position bei jeder Aktualisierung, daher kostet die Komponente pro Frame nichts. Wähle sie aus, um ihren Sichtradius als gelben Kreis in der Scene View zu sehen.
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Team | 0 | Team, das die Sicht erhält (0 bis 31). |
| Radius | 12 | Sichtradius in Metern. |
| Height Bonus | 0 | Zusätzliche Klippenstufen, über die die Sichtquelle hinwegsehen kann (Wachtürme, Hügel, fliegende Späher). |
| Ignore Line Of Sight | aus | Sieht über Klippen und Blocker hinweg (Lufteinheiten). |
| Reveal When Disabled | aus | Deckt weiter auf, während diese Komponente oder ihr GameObject deaktiviert ist, bis sie zerstört wird. |
Sichtquellen mit Reveal When Disabled auf GameObjects, die beim Laden der Szene bereits inaktiv sind, werden einmalig eingesammelt, wenn der Manager aktiviert wird. Objekte, die du später inaktiv instanziierst (Objekt-Pools, additive Szenen), führen nie Awake aus. Registriere sie daher selbst mit RTSFogOfWarManager.RegisterRevealer(revealer) oder setze RevealWhenDisabled = true per Code.
RTSFogOfWarHideable
Blendet die Renderer (und optional Canvases und Collider) eines gegnerischen Objekts aus, solange das lokale Team es nicht sehen kann. Objekte des lokalen Teams und der Teams, die ihre Sicht mit ihm teilen, werden nie ausgeblendet. Wird das Objekt wieder sichtbar, werden nur die Komponenten wieder aktiviert, die beim Ausblenden aktiviert waren. So funktioniert deine eigene Logik zum Aktivieren und Deaktivieren weiterhin.
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Team | 0 | Team, dem das Objekt gehört (0 bis 31). |
| Mode | HideWhenNotVisible | HideWhenNotVisible: wird nur angezeigt, solange seine Position gerade sichtbar ist (Einheiten). GhostWhenExplored: bleibt nach der Entdeckung angezeigt, auch wenn der Bereich wieder im Nebel liegt (Gebäude und neutrale Props, wie in klassischen RTS-Spielen). |
| Require Sighting | an | Nur im Geisterbild-Modus: Das Objekt muss einmal gesehen worden sein. Ist die Option aus, reicht ein erkundeter Bereich, um es anzuzeigen. |
| Hide Canvases | an | Deaktiviert auch Komponenten vom Typ Canvas (Lebensbalken, Namensschilder). |
| Hide Colliders | aus | Deaktiviert im verborgenen Zustand auch Collider, damit verborgene Gegner nicht angeklickt werden können. Lasse die Option auf autoritativen Servern und Hosts aus: Deren Physikabfragen müssen auch Einheiten treffen, die der lokale Spieler nicht sieht. |
| Pivot | leer | Optionaler Transform, dessen Position geprüft wird. Leer bedeutet: der eigene Transform. |
Die Komponente löst das Event VisibilityChanged(bool) aus, wenn das Objekt ein- oder ausgeblendet wird. Rufe RefreshComponents() auf, nachdem du zur Laufzeit untergeordnete Renderer hinzugefügt oder entfernt hast.
Rendering: Renderer Feature und Shader-Fallback
Der Nebel kann auf zwei Arten gezeichnet werden. Die Wahl fällt pro Kamera, sodass der Nebel nie doppelt angewendet wird.
| Weg | Wann er verwendet wird | Was abgedunkelt wird |
|---|---|---|
| RTS Fog Of War renderer feature (empfohlen) | Die Kamera rendert mit einem Universal Renderer, der das Feature enthält. | Alles auf dem Bildschirm: Terrain, Vegetation, Wasser, Dekor, Einheiten und beliebige eigene Materialien. Der Himmel bleibt unverändert. |
| Shader-Fallback | Der Renderer der Kamera enthält das Feature nicht. | Nur die paketeigenen Terrain-, Wasser- und Vegetations-Shader. Einheiten und Props mit anderen Materialien werden nicht abgedunkelt (gegnerische Einheiten werden als verbergbare Objekte trotzdem ausgeblendet). |
Renderer Feature von Hand hinzufügen:
- Wähle dein Asset vom Typ Universal Renderer Data aus (den Renderer, den deine Spielkameras verwenden).
- Klicke in seinem Inspector auf Add Renderer Feature und wähle RTS Fog Of War.
- Wiederhole das für jeden Renderer, den Spielkameras verwenden, damit das Ergebnis überall gleich ist.
Oder lasse das beheben: Es fügt das Feature dem Standard-Renderer jedes verwendeten URP-Assets hinzu.
| Feature-Einstellung | Standard | Beschreibung |
|---|---|---|
| Show In Scene View | an | Zeichnet den Nebel auch in Kameras der Scene View. Ist die Option aus, zeigen Scene-View-Kameras dieses Renderers die Karte ohne Nebel. |
| Render Pass Event | AfterRenderingTransparents | Wann der Nebel gezeichnet wird. Der Standardwert liegt vor dem Post-Processing, sodass Bloom und Tonemapping über dem Nebel angewendet werden. |
Das Feature fordert die Tiefentextur der Kamera selbst an und rekonstruiert damit die Weltposition jedes Pixels. RTSFogOfWarRendererFeature.IsActive gibt an, ob in einem Renderer des aktiven URP-Assets ein aktiviertes Feature vorhanden ist; der Wert dient nur zur Information.
Eigene Shader (fortgeschritten). Der Manager stellt den Nebel als globale Shader-Variablen bereit: _RTS_FogOfWarTex (R = sichtbar, G = erkundet), _RTS_FogOfWarParams (min X, min Z, 1/Größe X, 1/Größe Z in Weltkoordinaten), _RTS_FogOfWarUnexplored, _RTS_FogOfWarExplored und _RTS_FogOfWarEnabled. Die Shader des Pakets binden Shaders/Include/RTSCommon.hlsl ein (nach Core.hlsl von URP) und wenden dessen Hilfsfunktion auf die beleuchtete Farbe an. Ein eigener URP-Shader kann für den Fallback-Weg dasselbe tun:
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
// Include RTSCommon.hlsl after Core.hlsl. Adjust the path if you moved the package folder.
#include "Assets/RTSTerrainStudio/Shaders/Include/RTSCommon.hlsl"
half4 frag(Varyings input) : SV_Target
{
half3 color = /* your lit colour */ half3(1, 1, 1);
// Darkens unexplored and fogged areas. Does nothing while the renderer feature handles this camera.
color = RTS_ApplyFogOfWar(color, input.positionWS);
return half4(color, 1);
}
Nebel des Krieges per Skript steuern
Alle Typen für den Nebel des Krieges liegen im Namespace RTSTerrainStudio. Den Manager der Szene erhältst du über RTSFogOfWarManager.Active (abonniere das statische Event ActiveChanged, wenn Manager hinzukommen und wegfallen).
| Member | Beschreibung |
|---|---|
| Abfragen | |
bool IsVisible(Vector3 world, int team) | True, wenn das Team die Position gerade sieht (eigene und geteilte Sicht). False für Teams, die nicht berechnet werden (siehe IsTeamComputed). |
bool IsExplored(Vector3 world, int team) | True, wenn das Team die Position mindestens einmal gesehen hat. |
bool IsVisibleToLocalTeam(Vector3), bool IsExploredByLocalTeam(Vector3) | Kurzformen für das lokale Team. |
uint GetVisibilityMask(Vector3 world) | Bitmaske der berechneten Teams, die die Position sehen (Bit t = Team t). Ideal für Interest Management auf dem Server. |
uint GetExploredMask(Vector3 world) | Bitmaske der Teams, die die Position erkundet haben. |
bool IsTeamComputed(int team) | Mit Compute All Teams immer true, sonst nur für das lokale Team. |
bool TryGetFogCell(Vector3, out int2), Vector3 FogCellToWorld(int2) | Rechnen zwischen Weltpositionen und Nebelzellen um. |
bool IsCellVisible(int2, int team), bool IsCellExplored(int2, int team) | Zellbasierte Varianten der Abfragen. |
int GetCellLevel(int2), bool IsCellBlocked(int2) | Klippenstufe und Blocker-Zustand, die für die Sichtlinie verwendet werden. |
Vector2 SampleDisplayedFog(Vector2 normalizedMapPosition) | Geglättete Werte, die für das lokale Team auf dem Bildschirm angezeigt werden (x = sichtbar, y = erkundet). Gibt (1, 1) zurück, wenn kein Nebel aktiv ist. |
| Aufdecken und Erkundung | |
void RevealArea(Vector3 world, float radius, int team, float duration, bool ignoreLineOfSight = false) | Deckt einen Kreis für ein Team auf (Leuchtkugeln, Fähigkeiten wie „Kartenbereich aufdecken“). duration zählt im Play Mode in Spielzeit, in der Vorschau im Edit Mode in Echtzeit. Bei 0 oder weniger wird nur für eine Aktualisierung aufgedeckt. |
void ClearTemporaryReveals() | Entfernt alle mit RevealArea aufgedeckten Bereiche. |
void ExploreAll(int team), void ExploreAllTeams() | Markieren die ganze Karte als erkundet. |
void ResetExploration(), void ResetExploration(int team) | Vergessen die Erkundung (gerade sichtbare Bereiche gelten bei der nächsten Aktualisierung wieder als erkundet). |
| Allianzen und geteilte Sicht | |
void SetAlliance(int teamA, int teamB, bool allied) | Teilt die Sicht in beide Richtungen oder hebt das Teilen auf. |
void SetSharedVision(int sourceTeam, int receiverTeam, bool shared) | Lässt receiverTeam sehen, was sourceTeam sieht (eine Richtung). |
void SetSharedVisionMask(int receiverTeam, uint sourceTeams), uint GetSharedVision(int receiverTeam) | Ersetzen oder lesen die ganze Maske eines empfangenden Teams. |
bool HasSharedVision(int sourceTeam, int receiverTeam), uint GetViewMask(int team) | Lesen die geteilte Sicht aus; die Sichtmaske enthält auch das Team selbst. |
bool IsFriendlyToLocalTeam(int team) | True für das lokale Team und für Teams, die ihre Sicht mit ihm teilen. Deren Objekte werden nie ausgeblendet. |
| Steuerung der Aktualisierung | |
void RequestUpdate(), void ForceUpdate() | Berechnen bei der nächsten Aktualisierung oder sofort neu. |
void MarkTerrainDirty() | Baut das Klippenstufen-Raster vollständig neu auf. Kartenänderungen werden automatisch erkannt; du brauchst das selten. |
void MarkBlockersDirty() | Baut das Blocker-Raster neu auf, nachdem du Bäume oder Dekor per Code verschoben hast, ohne RTSTerrain.NotifyChanged aufzurufen. |
event Action Updated | Wird nach jeder Sichtbarkeitsaktualisierung ausgelöst, nachdem die verbergbaren Objekte aktualisiert wurden. |
| Zustand | |
IsSimulating, IsRunning, IsRenderingFog, HasGrid, HasVisibilityData | Ob der Nebel läuft, ein Raster hat und mindestens einmal berechnet wurde. |
FogTexture, RawFogTexture, FogWidth, FogHeight, FogCellSize, Version, ActiveRevealerCount, MemoryUsage | Informationen zu Raster und Textur. |
int EffectiveResolutionPerCell | Tatsächlich verwendete Nebelzellen pro Kartenzelle. Auf riesigen Karten kleiner als ResolutionPerCell, weil das Raster dort auf MaxFogGridSize (2048) begrenzt wird. |
RTSTerrain Terrain, RTSTerrain BoundTerrain | Das abzudeckende Terrain (null = RTSTerrain.Main) und das Terrain, für das das Raster gerade aufgebaut ist. |
| Einstellungen (wie im Inspector) | |
ResolutionPerCell, LocalTeam, LineOfSight, ComputeAllTeams, RevealAll, FadeSpeed, BlurIterations, BlurUpscale | Setzbare Eigenschaften. Eine Änderung von ResolutionPerCell baut das Raster neu auf und setzt die Erkundung zurück. |
const int MaxTeams (32), const int MaxFogGridSize (2048) | Höchstzahl an Teams und größte Seitenlänge des Nebelrasters. |
| Registries | |
static IReadOnlyList<RTSFogOfWarRevealer> Revealers, static IReadOnlyList<RTSFogOfWarHideable> Hideables | Alle aktivierten Sichtquellen und verbergbaren Objekte aller Manager. |
static RegisterRevealer / UnregisterRevealer, RegisterHideable / UnregisterHideable | Werden von den Komponenten selbst aufgerufen; du brauchst sie nur für eigene Komponenten. |
| RTSFogOfWarHideable und RTSFogOfWarRevealer | |
bool IsVisible, bool HasBeenSighted (verbergbares Objekt) | Aktueller Zustand und ob das Objekt je gesehen wurde (Geisterbild-Modus). |
Vector3 TestPosition (verbergbares Objekt) | Die Position, die gegen den Nebel geprüft wird (der Pivot oder der Transform). |
void Reevaluate(), void ResetSighting() (verbergbares Objekt) | Jetzt neu prüfen; vergessen, dass das Objekt gesichtet wurde. |
event Action<bool> VisibilityChanged (verbergbares Objekt) | Wird ausgelöst, wenn das Objekt für das lokale Team erscheint oder verschwindet. |
bool IsRevealing (Sichtquelle) | True, solange die Sichtquelle zählt (aktiviert oder Reveal When Disabled ist an). |
Beispiel: eine Leuchtkugel-Fähigkeit und eine Sichtbarkeitsprüfung.
using RTSTerrainStudio;
using UnityEngine;
// Reveals an area for a few seconds and tells whether a target can be attacked.
public class ScoutAbilities : MonoBehaviour
{
[SerializeField] int team = 0; // the team that casts the flare
[SerializeField] float flareRadius = 15f;
[SerializeField] float flareSeconds = 5f;
// Call this with the world position the player clicked.
public void CastFlare(Vector3 worldPosition)
{
var fog = RTSFogOfWarManager.Active;
if (fog == null) return; // no fog of war in this scene
// ignoreLineOfSight: true lets the flare see over cliffs and trees, like an air unit.
fog.RevealArea(worldPosition, flareRadius, team, flareSeconds, ignoreLineOfSight: true);
}
// True when our team may target something at this position.
public bool CanTarget(Vector3 targetPosition)
{
var fog = RTSFogOfWarManager.Active;
// Without a running fog, everything is visible.
if (fog == null || !fog.IsRunning) return true;
// For teams other than the local one, enable "Compute All Teams" on the manager,
// otherwise IsVisible returns false for them.
return fog.IsVisible(targetPosition, team);
}
}
Beispiel: Allianzen und eine per Code gespawnte Einheit.
using RTSTerrainStudio;
using UnityEngine;
public class TeamSetup : MonoBehaviour
{
[SerializeField] GameObject unitPrefab;
void Start()
{
var fog = RTSFogOfWarManager.Active;
if (fog != null)
{
fog.SetAlliance(0, 2, true); // teams 0 and 2 see what the other sees
fog.SetSharedVision(3, 0, true); // team 0 also sees what team 3 sees (one way)
}
SpawnUnit(1, new Vector3(40f, 0f, 40f));
}
// Creates an enemy unit that sees and can be hidden by the fog of war.
void SpawnUnit(int team, Vector3 position)
{
var go = Instantiate(unitPrefab, position, Quaternion.identity);
// Reuse components the prefab may already have (RTSFogOfWarHideable allows only one per object).
if (!go.TryGetComponent(out RTSFogOfWarRevealer revealer))
revealer = go.AddComponent<RTSFogOfWarRevealer>();
revealer.Team = team;
revealer.Radius = 12f; // metres
if (!go.TryGetComponent(out RTSFogOfWarHideable hideable))
hideable = go.AddComponent<RTSFogOfWarHideable>();
// Subscribe BEFORE setting Team and Mode: in Play Mode both setters re-test the object immediately,
// so a unit spawned in the fog is hidden (and the event raised) right away.
// React when the unit appears or disappears for the local player (sounds, minimap pings ...).
hideable.VisibilityChanged += visible => Debug.Log(go.name + (visible ? " spotted" : " lost"));
hideable.Team = team;
hideable.Mode = FogOfWarHideMode.HideWhenNotVisible;
// The current state after configuring.
Debug.Log(go.name + (hideable.IsVisible ? " starts visible" : " starts hidden"));
}
}
Minikarte mit Nebel des Krieges
RTSFogOfWarMinimapUtility dunkelt ein Bild der Minikarte mit dem Nebel des lokalen Teams ab, wie auf der Minikarte klassischer RTS-Spiele. Die Minikarte muss genau den Kartenbereich abdecken, mit UV (0, 0) an der Ursprungsecke der Karte und UV (1, 1) an der gegenüberliegenden Ecke. Mit RTSMinimapBaker gebackene Bilder haben dieses Layout. Das Beispiel RTSMinimapUI verwendet diese Hilfsklasse.
| Methode | Beschreibung |
|---|---|
static bool Composite(Texture minimap, RenderTexture destination, RTSFogOfWarManager manager = null) | GPU-Pfad, ideal für eine laufend aktualisierte Minikarte. Rufe die Methode nach dem Event Updated oder alle paar Frames auf. Ohne aktiven Nebel wird die Minikarte unverändert kopiert. Gibt false zurück, wenn der Shader nicht verfügbar ist. |
static bool CompositeCpu(Texture2D source, Texture2D destination, RTSFogOfWarManager manager = null) | CPU-Pfad (Burst-Job), der ohne Render Textures auskommt, zum Beispiel für Screenshots. source muss lesbar sein; destination muss eine lesbare RGBA32-Textur derselben Größe sein (es darf auch dieselbe Textur sein). |
static bool CompositeCpu(NativeArray<Color32> source, NativeArray<Color32> destination, int width, int height, RTSFogOfWarManager manager = null) | CPU-Pfad auf rohen Pixelpuffern. |
static void ReleaseResources() | Zerstört das gecachte Material und den Command Buffer. Wird vor Domain Reloads automatisch aufgerufen; rufe die Methode selbst auf, wenn die Minikarte endgültig geschlossen wird. |
using RTSTerrainStudio;
using UnityEngine;
// Bakes the map once and keeps a fogged copy in a render texture.
// Show Output with a RawImage (uGUI) or a UI Toolkit Image.
public class FoggedMinimap : MonoBehaviour
{
[SerializeField] int size = 256; // longest side in pixels
Texture2D baked;
RenderTexture output;
RTSFogOfWarManager fog;
public RenderTexture Output => output;
void OnEnable()
{
var terrain = RTSTerrain.Main;
if (terrain == null) return;
baked = RTSMinimapBaker.Bake(terrain, size); // map image with cliffs, water, trees ...
if (baked == null) return;
output = new RenderTexture(baked.width, baked.height, 0, RenderTextureFormat.ARGB32);
fog = RTSFogOfWarManager.Active;
if (fog != null) fog.Updated += Refresh; // refresh after each visibility update
Refresh();
}
void OnDisable()
{
if (fog != null) fog.Updated -= Refresh;
if (output != null) { output.Release(); Destroy(output); }
if (baked != null) Destroy(baked);
}
void Refresh()
{
if (baked != null && output != null)
RTSFogOfWarMinimapUtility.Composite(baked, output, fog);
}
}
Hinweise für Multiplayer
- Jeder Client berechnet einen eigenen Nebel des Krieges für sein Local Team. Der Nebel wird nie über das Netzwerk gesendet.
- Aktiviere auf einem autoritativen Server Compute All Teams und entscheide mit
GetVisibilityMask(position), welche Clients welche Einheiten erhalten. Eine einzelne Abfrage pro Einheit ist sehr günstig. - Lasse Hide Colliders bei verbergbaren Objekten auf dem Server und auf einem Listen-Server-Host ausgeschaltet, damit serverseitige Raycasts weiterhin Einheiten treffen, die der Host-Spieler nicht sieht. Filtere die Auswahl des Spielers stattdessen mit
IsVisibleToLocalTeam. - Allianzen sind lokale Einstellungen. Rufe
SetAllianceauf dem Server und auf den Clients auf, wenn die Clients die Sicht von Verbündeten anzeigen sollen.
Die Mirror-Integration erledigt all das für dich: RTSNetworkFogOfWar konfiguriert den Manager (der Server berechnet alle Teams, ein Client nur sein eigenes), und RTSFogOfWarInterestManagement sendet nie gegnerische Einheiten, die vom Nebel verborgen sind (Anti-Maphack). Siehe das Kapitel zur Mirror-Integration.
Performance und Grenzen
- Die Sichtbarkeit wird von Burst-Jobs alle Update Interval Sekunden berechnet (standardmäßig 0.1), nicht in jedem Frame. Sichtquellen und verbergbare Objekte werden in internen Listen geführt, die Szene wird also nicht in jedem Frame durchsucht.
- Der Speicherbedarf liegt bei etwa 18 Byte pro Nebelzelle. Eine Karte mit 128 × 128 Zellen und Resolution Per Cell 2 hat ein Raster von 256 × 256 Zellen, also etwa 1.2 MB.
- Große Karten: Das Raster ist auf 2048 Zellen pro Achse begrenzt, und die Auflösung pro Zelle wird automatisch gesenkt, um innerhalb dieser Grenze zu bleiben.
- None ist der günstigste Sichtlinienmodus; CliffLevelsAndBlockers ist der teuerste, aber Sichtlinien werden nur für Sichtquellen verfolgt, in deren Nähe eine höhere Zelle oder ein Blocker liegt.
- Die Nebeltextur ist achsparallel zur Karte ausgerichtet. Bei einem gedrehten Terrain wird eine Warnung ausgegeben.
- Nach Bearbeitungen an Bäumen oder Dekor wird das Raster der Sichtblocker für die gesamte Karte neu aufgebaut; Bearbeitungen an Klippen aktualisieren nur den geänderten Bereich.
Nichts liegt im Nebel? Prüfe, ob der Manager aktiviert ist und du dich im Play Mode befindest (oder Preview In Edit Mode eingeschaltet ist), ob Reveal All ausgeschaltet ist und ob das Terrain eine Karte hat. Wenn nur das Terrain im Nebel liegt, Einheiten und Props aber nicht, füge das Renderer Feature RTS Fog Of War hinzu. Wenn alles schwarz ist, ist keine Sichtquelle des lokalen Teams (oder eines Teams, das seine Sicht mit ihm teilt) aktiv. Die Meldung „No revealers exist“ im Inspector erscheint nur, wenn die Szene überhaupt keine Sichtquelle enthält. Vergleiche daher auch das Team deiner Sichtquellen mit Local Team.
Pathing und Wegfindung
Jedes RTS Terrain führt eine Pathing-Karte: ein Raster, das für jedes kleine Quadrat der Karte angibt, ob Bodeneinheiten dort laufen können, ob Gebäude platziert werden können, ob Lufteinheiten fliegen können und ob dort Wasser ist. Sie wird automatisch aus dem Terrain abgeleitet und während der Bearbeitung aktualisiert. Darauf aufbauend enthält das Paket einen schnellen integrierten Pathfinder (Burst A*), den du ganz ohne Drittanbieterpaket verwenden kannst.
Wenn du lieber das A* Pathfinding Project oder Unitys NavMesh verwendest, bauen die Integrationen ihre Graphen aus derselben Pathing-Karte; siehe die jeweiligen Kapitel.
Die Pathing-Karte
Die Pathing-Karte hat PathingResolution Pathing-Zellen pro Kartenzelle und Achse (1, 2 oder 4, beim Erstellen der Karte festgelegt; Standard 2). Bei Kartenzellen in der Standardgröße von 2 m ist eine Pathing-Zelle 1 m breit. Jede Zelle speichert eine Kombination von PathingFlags:
| Flag | Bedeutung |
|---|---|
Walkable | Bodeneinheiten können hier laufen. |
Buildable | Hier können Gebäude platziert werden. |
Flyable | Lufteinheiten können hier fliegen. Wird überall gesetzt, sofern kein Unflyable-Effekt das Flag entfernt: ein gemalter Pathing-Override, die Bodenschichtregel des Tilesets oder die Einstellung Footprint Effect eines Dekor-Prototyps. |
ShallowWater | Flachwasser: begehbar, standardmäßig nicht bebaubar. |
DeepWater | Tiefwasser: nur für See-Einheiten. |
Cliff | Die Zelle berührt eine Klippenwand. |
Blocked | Die Zelle wird von der Grundfläche eines Baums oder Dekorobjekts blockiert. |
Steep | Die Zelle ist für Bodeneinheiten zu steil. |
So werden die Flags abgeleitet. Die Regeln werden in dieser Reihenfolge als Burst-Jobs angewendet:
- Terrain. Klippenwände (verbreitert um Cliff Margin) werden zu
Cliff. Hänge mit einer Neigung über Max Walkable Slope werden zuSteep. Wasser, das tiefer ist als die Flachwasser-Grenztiefe des Tilesets, wird zuDeepWater, sonst zuShallowWater.Flyablewird überall gesetzt.Buildable= begehbar, flacher als Max Buildable Slope und trocken (oder Flachwasser mit Shallow Water Buildable). Danach wird der Pathing-Effekt der dominanten Bodenschicht angewendet (zum Beispiel „Unbuildable“ auf rauem Fels). Eine gültige Rampe zwischen zwei Klippenstufen wird nur anhand ihrer Feinhöhe geprüft und bleibt daher begehbar; Rampen sind nie bebaubar. - Grundflächen. Bäume blockieren einen Kreis (Footprint Radius des Baum-Prototyps × Instanzskalierung × Tree Footprint Scale): Sie setzen
Blockedund entfernen Walkable und Buildable. Dekor wendet den Footprint Effect seines Prototyps (standardmäßig Unwalkable und Unbuildable) in einem Kreis oder einem gedrehten Rechteck an. Dekorinstanzen mit dem FlagIgnorePathingwerden übersprungen. - Manuelle Overrides, die mit den Pathing-Werkzeugen gemalt werden (siehe unten). Ein Unwalkable-Effekt entfernt auch Buildable, sofern nicht Force Buildable gesetzt ist.
Die Karte wird inkrementell neu aufgebaut: Wenn du einen Bereich bearbeitest, wird nur dieser Bereich (plus ein kleiner Rand) neu abgeleitet.
Pathing-Regeln (RTSPathingSettings)
Wähle das RTS Terrain aus und öffne im Inspector den Abschnitt Pathing Rules. Wenn du einen Wert änderst, wird die Pathing-Karte neu aufgebaut.
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Max Walkable Slope | 42 | Boden mit einer Neigung über diesem Winkel (in Grad) ist nicht begehbar. |
| Max Buildable Slope | 18 | Boden mit einer Neigung über diesem Winkel (in Grad) ist nicht bebaubar. |
| Cliff Margin | 0.25 | Wie weit (als Anteil einer Pathing-Zelle) der blockierte Bereich über eine Klippenwand hinausreicht. Größere Werte halten Einheiten weiter von Klippenkanten fern. |
| Shallow Water Buildable | aus | Erlaubt Gebäude im Flachwasser. |
| Tree Footprint Scale | 1 | Multiplikator für den Radius der Pathing-Grundfläche jedes Baums. |
| Use Ground Layer Rules | an | Wendet den Pathing-Effekt der dominanten Bodenschicht an (pro Schicht im Tileset festgelegt, z. B. „Unbuildable“ auf rauem Fels). |
Im Code weist du mit terrain.PathingSettings = settings neue Einstellungen zu (das löst einen Neuaufbau aus), oder du änderst Felder der aktuellen Instanz und rufst terrain.RebuildPathing() auf.
Pathing von Hand malen
Manchmal brauchst du etwas anderes, als die automatischen Regeln ergeben: eine unsichtbare Wand am Kartenrand, eine Bauverbotszone, die eine Expansion freihält, oder eine begehbare Brücke. Verwende dafür die Pathing-Werkzeuge (mit 7 wählst du in der Scene View die Kategorie Pathing aus):
| Werkzeug / Override | Wirkung |
|---|---|
| Paint Pathing: Blocker | Nicht begehbar und nicht bebaubar: eine unsichtbare Wand (Kartenränder, Dekorationsbereiche). |
| Paint Pathing: Unwalkable | Bodeneinheiten können hier nicht laufen. |
| Paint Pathing: Unbuildable | Hier können keine Gebäude platziert werden (um Wege und Expansionen freizuhalten). |
| Paint Pathing: Unflyable | Lufteinheiten können hier nicht fliegen. |
| Paint Pathing: Force Walkable | Bodeneinheiten können hier laufen, auch wenn Neigung, Wasser oder Grundflächen das verhindern würden. |
| Paint Pathing: Force Buildable | Hier können Gebäude platziert werden, auch wenn die abgeleiteten Regeln es verbieten. |
| Clear Pathing | Entfernt alle gemalten Overrides unter dem Pinsel, sodass wieder das automatische Pathing gilt. |
Halte bei Paint Pathing Shift gedrückt, um den ausgewählten Override zu entfernen. Wenn du einen Modus malst, wird der Override entfernt, der ihm widerspricht (malst du Force Walkable, wird Unwalkable entfernt), sodass eine Zelle nie widersprüchliche Regeln enthält.
Pathing-Overlays
Ein Overlay färbt das Terrain nach seinem Pathing ein, wie die Pathing-Ansicht klassischer RTS-Karteneditoren. Du schaltest es so ein:
- über das Dropdown-Menü Pathing im Overlay der Scene View (Pathing: Off, Walkability, Buildability, Flyability, Water, Combined);
- über das Dropdown-Menü Pathing Overlay und den Schieberegler für die Deckkraft im Fenster von RTS Terrain Studio;
- mit P in der Scene View, womit du Off → Walkability → Buildability → Combined → Off durchschaltest (Tastenkürzel
RTS Terrain Studio/Cycle Pathing Overlay, neu belegbar unter , unter macOS ); - über die Pathing-Ansicht des In-Game-Editors.
| Modus | Farben |
|---|---|
| Walkability | Grün = begehbar, Rot = blockiert, Dunkelrot = Klippe, Orange = zu steil, Blau = Tiefwasser, Cyan = Flachwasser. |
| Buildability | Grün = bebaubar, Gelb = begehbar, aber nicht bebaubar, Rot / Dunkelrot = nicht begehbar. |
| Flyability | Hellblau = befliegbar, Magenta = nicht befliegbar. |
| Water | Cyan = Flachwasser, Blau = Tiefwasser. |
| Combined | Alles gleichzeitig (Pathing-Ansicht im Stil klassischer RTS-Spiele). |
In deinem eigenen Spiel zeigst du ein Overlay mit einem PathingOverlayController an. Er hält die Textur aktuell, wenn sich das Pathing ändert:
using RTSTerrainStudio;
using UnityEngine;
// Shows the buildability overlay on the terrain while this component is enabled (e.g. in build mode).
public class BuildModeOverlay : MonoBehaviour
{
[SerializeField] RTSTerrain terrain;
[SerializeField, Range(0f, 1f)] float opacity = 0.6f;
PathingOverlayController overlay;
void OnEnable()
{
if (terrain == null) terrain = RTSTerrain.Main;
if (terrain == null) return;
overlay = new PathingOverlayController(terrain, PathingOverlayMode.Buildability);
// The texture can be replaced (for example after a map resize): re-assign it when that happens.
overlay.TextureChanged += texture => terrain.SetOverlayTexture(texture, opacity);
terrain.SetOverlayTexture(overlay.Texture, opacity);
}
void OnDisable()
{
if (terrain != null) terrain.SetOverlayTexture(null, 0f);
overlay?.Dispose(); // destroys the overlay texture
overlay = null;
}
}
Wechsle die angezeigte Information mit overlay.Mode = PathingOverlayMode.Walkability (das zeichnet die Textur neu), oder erzwinge mit overlay.Refresh() ein vollständiges Neuzeichnen; du musst keinen neuen Controller erstellen.
Für einmalige Texturen verwendest du PathingOverlay.CreateOrUpdate(map, mode, ref texture), PathingOverlay.UpdateRegion(...) und PathingOverlay.DestroyTexture(ref texture); PathingOverlay.GetColor(flags, mode) gibt die Legendenfarbe einer Flag-Kombination zurück.
Die Pathing-Karte per Code auslesen
Member (von RTSTerrain) | Beschreibung |
|---|---|
PathingFlags GetPathing(Vector3 world) | Flags an einer Weltposition (None außerhalb der Karte oder bevor das Pathing aufgebaut ist). |
bool IsWalkable(Vector3 world), bool IsBuildable(Vector3 world) | Kurzformen für die zwei meistgenutzten Flags. |
PathingMap Pathing | Das Pathing-Raster: Width, Height, CellSize, Flags (ein Byte pro Zelle, Index = z × Width + x), Get(x, z), Version. |
event Action<RTSTerrain, RectInt> PathingChanged | Wird nach einem vollständigen oder teilweisen Neuaufbau ausgelöst und übergibt das geänderte Rechteck in Pathing-Zellen. Bleibt beim Neuladen einer Karte erhalten. Die Beispieleinheiten nutzen es, um neu zu planen. |
RTSPathingSettings PathingSettings, void RebuildPathing() | Regeln und vollständiger Neuaufbau. |
ClearanceMap GetPathingClearance() | Die Freiraumkarte, auf den aktuellen Stand gebracht (siehe Einheitengröße). |
void RegisterPathingReader(JobHandle), void CompletePathingReaders() | Für eigene Jobs, die Flags oder den Freiraum lesen: Das Terrain wartet auf sie, bevor es Pathing-Daten schreibt. |
Einen Pfad finden: RTSPathfinder
RTSPathfinder sucht mit einem Burst-kompilierten A* einen Pfad auf der Pathing-Karte: 8 Richtungen ohne Abschneiden von Ecken, Freiraum für große Einheiten und String Pulling für glatte Pfade. Ein blockierter Start oder ein blockiertes Ziel wird durch die nächstgelegene freie Zelle ersetzt. Der Pathfinder läuft synchron auf dem Hauptthread. Für einige Anfragen pro Frame und im Edit Mode ist das kein Problem.
using System.Collections.Generic;
using RTSTerrainStudio;
using UnityEngine;
public class PathDebug : MonoBehaviour
{
[SerializeField] Transform target;
readonly List<Vector3> path = new List<Vector3>();
void OnDrawGizmos()
{
var terrain = RTSTerrain.Main;
if (terrain == null || target == null) return;
// A unit with a radius of 0.6 m: the path keeps that distance from obstacles.
var options = PathRequestOptions.ForUnit(0.6f);
PathResult result = RTSPathfinder.FindPathDetailed(terrain, transform.position, target.position, options, path);
// Success = reached the goal, Partial = closest reachable point.
Gizmos.color = result.status == PathStatus.Success ? Color.green : Color.yellow;
for (int i = 1; i < path.Count; i++) Gizmos.DrawLine(path[i - 1], path[i]);
}
}
| Methode | Beschreibung |
|---|---|
static PathStatus FindPath(RTSTerrain, Vector3 startWorld, Vector3 endWorld, List<Vector3> resultWorld) | Verwendet PathRequestOptions.Default. |
static PathStatus FindPath(RTSTerrain, Vector3, Vector3, PathRequestOptions, List<Vector3>) | resultWorld wird geleert und erhält die Wegpunkte, einschließlich Start und Ende. |
static PathResult FindPathDetailed(RTSTerrain, Vector3, Vector3, PathRequestOptions, List<Vector3>) | Gibt zusätzlich Länge und Statistiken zurück und meldet, ob Start oder Ziel verschoben wurde. |
static void DisposeSharedBuffers() | Gibt die Suchpuffer frei (geschieht auch automatisch beim Domain Reload und beim Beenden). |
PathRequestOptions (gehe von PathRequestOptions.Default oder PathRequestOptions.ForUnit(radius) aus):
| Feld | Standard | Beschreibung |
|---|---|---|
unitRadius | 0 | Radius der Einheit in Welteinheiten. Pfade halten diesen Abstand zu Hindernissen. Der Freiraum wird nur berücksichtigt, wenn required das Flag Walkable enthält: Bei Anfragen für See-Einheiten (DeepWater) oder Lufteinheiten (Flyable) hat unitRadius keine Wirkung. Die Freiraumkarte misst Abstände zu nicht begehbaren Zellen, und der Kartenrand zählt als Hindernis. Dasselbe gilt für PathingQueries.FindNearest, LineOfSight und Raycast. |
required | Walkable | Flags, die jede Zelle des Pfads haben muss (None wird als Walkable behandelt). |
allowShallowWater | true | Erlaubt das Durchqueren von Flachwasser (Furten). |
maxSearchNodes | 500000 | Maximale Anzahl expandierter Knoten, bevor die Suche aufgibt (0 = unbegrenzt). |
smoothPath | true | Entfernt unnötige Wegpunkte mithilfe der Sichtlinie im Raster (String Pulling). |
snapToGround | true | Setzt die Y-Koordinate jedes Wegpunkts auf die Bodenhöhe. |
allowPartial | true | Gibt bei unerreichbarem Ziel einen Pfad zur nächstgelegenen erreichbaren Zelle zurück. |
goalSearchRadius | 16 | Wenn die Zielzelle blockiert ist, wird in diesem Umkreis (in Welteinheiten) nach der nächstgelegenen freien Zelle gesucht. |
| PathStatus | Bedeutung |
|---|---|
Success | Ein Pfad zum Ziel (oder zur nächstgelegenen freien Zelle eines blockierten Ziels) wurde gefunden. |
Partial | Das Ziel ist unerreichbar; der Pfad führt zu der erreichbaren Zelle, die ihm am nächsten liegt. |
NoPath | Es existiert kein Pfad (oder das Suchlimit wurde erreicht), und Teilpfade waren nicht erlaubt. |
InvalidStart | Der Start liegt außerhalb der Karte, es gibt keine Pathing-Daten, oder in der Nähe des Starts gibt es keine passierbare Zelle. |
Felder von PathResult: requestId, terrain, status, points (Wegpunkte in Weltkoordinaten), length (horizontal, in Welteinheiten), searchedNodes, startAdjusted, goalAdjusted, userData und HasPath (Success oder Partial mit mindestens einem Wegpunkt).
Viele Einheiten: RTSPathfindingService
Für viele Einheiten verwendest du RTSPathfindingService. Der Service sammelt die Anfragen eines Frames, führt sie in LateUpdate als parallele Burst-Jobs aus (nachdem das Terrain seine Bearbeitungen angewendet hat) und liefert die Ergebnisse im Update des nächsten Frames über Callbacks. Füge ihn über hinzu, oder rufe im Play Mode RTSPathfindingService.GetOrCreate() auf (das erstellt ein Service-Objekt, das Szenenwechsel übersteht). Der Service läuft nur im Play Mode; im Edit Mode verwendest du RTSPathfinder.
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Max Requests Per Frame | 64 | Pfadsuchen, die pro Frame gestartet werden. Weitere Anfragen warten in einer Warteschlange. |
| Max Parallel Jobs | 4 | Parallel laufende Jobs (1 bis 16). Jeder Job braucht eigene Suchpuffer (etwa 24 Byte pro Pathing-Zelle). |
| Memory Budget MB | 256 | Obergrenze für den Speicher der Suchpuffer. Auf sehr großen Rastern werden weniger parallele Jobs verwendet; eine Warnung wird ausgegeben, wenn schon ein einzelner Puffersatz diese Grenze überschreitet. |
| Complete In Same Frame | aus | Schließt die Jobs am Ende des Frames ab, in dem sie eingeplant wurden (geringere Latenz, weniger Parallelität). |
| Member | Beschreibung |
|---|---|
int RequestPath(RTSTerrain, Vector3 startWorld, Vector3 endWorld, PathRequestOptions, Action<PathResult> callback, List<Vector3> resultBuffer = null, object userData = null) | Stellt eine Anfrage in die Warteschlange und gibt ihre ID zurück. Mit resultBuffer werden die Wegpunkte in deine Liste geschrieben (die vorher geleert wird). Das vermeidet eine Allokation pro Anfrage. |
int RequestPath(RTSTerrain, Vector3, Vector3, Action<PathResult>) | Dasselbe mit den Standardoptionen. |
bool Cancel(int requestId) | Bricht eine Anfrage ab, die in der Warteschlange steht, gerade läuft oder fertig, aber noch nicht zugestellt ist. Ihr Callback wird nicht ausgeführt. |
void CancelAll(), void CompleteAll() | Bricht alles ab oder schließt sofort alles ab (die Callbacks laufen, bevor die Methode zurückkehrt). |
PendingCount, InFlightCount | Statistiken zu Warteschlange und Jobs. |
static Instance, static GetOrCreate() | Der erste aktivierte Service oder ein neuer. |
using RTSTerrainStudio;
using UnityEngine;
// Asks the service for a path and replaces the previous request when a new order arrives.
public class AsyncMover : MonoBehaviour
{
[SerializeField] float radius = 0.5f;
int requestId; // 0 = no request pending
public void OrderMove(Vector3 target)
{
var terrain = RTSTerrain.Main;
var service = RTSPathfindingService.GetOrCreate(); // null outside Play Mode
if (terrain == null || service == null) return;
if (requestId != 0) service.Cancel(requestId); // the old callback will not run
requestId = service.RequestPath(terrain, transform.position, target,
PathRequestOptions.ForUnit(radius), OnPathFound);
}
void OnPathFound(PathResult result)
{
requestId = 0;
if (!result.HasPath) { Debug.Log("Can't reach that place."); return; }
// result.points holds the waypoints. Copy them if you keep them.
Debug.Log($"{result.points.Count} waypoints, {result.length:0.0} m, status {result.status}");
}
}
Einheitengröße: Freiraum
Große Einheiten dürfen sich nicht durch Lücken zwängen, die für kleine gedacht sind. Das Terrain führt dafür eine Freiraumkarte: Sie enthält für jede begehbare Zelle den Abstand zur nächsten nicht begehbaren Zelle (begrenzt auf 64 Zellen). Wenn eine Anfrage einen unitRadius hat, verwendet der Pathfinder nur Zellen, in die die Einheit passt. Die Freiraumkarte wird nach Bearbeitungen inkrementell aktualisiert.
PathingQueries.GetClearance(terrain, world)gibt den freien Radius an einer Position in Welteinheiten zurück (negativ auf nicht begehbaren Zellen).ClearanceMap(austerrain.GetPathingClearance()) bietetGetClearanceCells(x, z),GetClearanceMetres(x, z)undClearanceMap.RequiredValue(unitRadius, cellSize). In der Nähe diagonaler Hindernisecken kann der Wert um bis zu etwa 0.13 Zellen zu großzügig sein. Dadurch können Einheiten mit einem Radius von 0.3 bis 0.5 Zellen weiterhin einzellige Korridore nutzen, durch die sie physisch passen.
Gameplay-Abfragen: PathingQueries
PathingQueries beantwortet die Fragen, die im RTS-Gameplay ständig anfallen. Alle Positionen sind in Weltkoordinaten angegeben, Größen und Radien in Welteinheiten.
| Methode | Beschreibung |
|---|---|
bool FindNearestWalkable(RTSTerrain, Vector3 world, float maxRadius, float unitRadius, out Vector3 result) | Begehbare Position, die world am nächsten liegt und an der eine Einheit mit unitRadius Platz hat (Spawn-Punkte, Sammelpunkte). |
bool FindNearest(RTSTerrain, Vector3 world, float maxRadius, float unitRadius, PathingFlags required, out Vector3 result) | Dasselbe für beliebige Flags, z. B. DeepWater für Schiffe. |
Vector3 SnapToBuildGrid(RTSTerrain, Vector3 world, Vector2 sizeMetres) | Richtet eine Gebäudeposition so aus, dass ihre Grundfläche ganze Pathing-Zellen abdeckt (gerade Größen auf einer Zellecke, ungerade Größen auf einer Zellmitte). Das Ergebnis liegt auf dem Boden. |
bool IsAreaBuildable(RTSTerrain, Vector3 worldCenter, Vector2 sizeMetres) | Liefert true, wenn jede Pathing-Zelle der Grundfläche Buildable ist. |
bool IsAreaClear(RTSTerrain, Vector3 worldCenter, Vector2 sizeMetres, PathingFlags required) | Dasselbe für beliebige Flags. Außerhalb der Karte liefert die Methode false. |
bool TryGetAreaCells(RTSTerrain, Vector3 worldCenter, Vector2 sizeMetres, out RectInt cells) | Pathing-Zellen, die von einer Grundfläche abgedeckt werden. Liefert false, wenn die Grundfläche nicht vollständig innerhalb der Karte liegt. |
float GetClearance(RTSTerrain, Vector3 world) | Freier Radius um eine Position. |
bool LineOfSight(RTSTerrain, Vector3 fromWorld, Vector3 toWorld, PathingFlags required = Walkable, float unitRadius = 0) | Liefert true, wenn ein gerader Weg möglich ist (Sichtlinie im Raster, kein Abschneiden von Ecken). Hier geht es um Bewegung, nicht um die Sicht im Nebel des Krieges. |
bool Raycast(RTSTerrain, Vector3 fromWorld, Vector3 toWorld, PathingFlags required, float unitRadius, out Vector3 lastFreeWorld) | Läuft die Strecke ab und meldet die letzte freie Position vor einem Hindernis (Sturmangriffe, Rückstöße). |
Gebäudeplatzierung und Pathing-Änderungen zur Laufzeit
Gebäudeplatzierung im Stil klassischer RTS-Spiele besteht aus drei Schritten: die Grundfläche am Raster ausrichten, sie prüfen und sie auf der Pathing-Karte blockieren. Zum Blockieren schreibst du manuelle Overrides in MapData.PathingOverrides und teilst das dem Terrain mit. Das Terrain baut den Bereich inkrementell neu auf und löst PathingChanged aus. Einheiten, die gerade unterwegs sind, planen ihren Weg um das neue Gebäude herum neu.
using RTSTerrainStudio;
using UnityEngine;
public static class BuildingFootprint
{
// Tries to place a footprint of sizeMetres (X/Z) at the cursor and blocks it for walking and building.
// Returns the covered pathing cells and their previous override bytes, so Remove can restore them
// (cells may already hold painted overrides, for example Force Walkable on a bridge).
public static bool TryPlace(RTSTerrain terrain, Vector3 cursorWorld, Vector2 sizeMetres,
out RectInt pathingCells, out byte[] previous)
{
pathingCells = default;
previous = null;
if (terrain == null || terrain.Map == null) return false;
// 1. Align the footprint with the pathing grid.
Vector3 center = PathingQueries.SnapToBuildGrid(terrain, cursorWorld, sizeMetres);
// 2. Every cell must be buildable.
if (!PathingQueries.IsAreaBuildable(terrain, center, sizeMetres)) return false;
if (!PathingQueries.TryGetAreaCells(terrain, center, sizeMetres, out pathingCells)) return false;
// 3. Keep the old bytes, then write Unwalkable | Unbuildable overrides (one byte per pathing cell).
MapData map = terrain.Map;
var overrides = map.PathingOverrides;
previous = new byte[pathingCells.width * pathingCells.height];
byte block = (byte)(PathingOverride.Unwalkable | PathingOverride.Unbuildable);
int n = 0;
for (int z = pathingCells.yMin; z < pathingCells.yMax; z++)
for (int x = pathingCells.xMin; x < pathingCells.xMax; x++, n++)
{
int i = map.PathingIndex(x, z);
previous[n] = overrides[i];
overrides[i] = (byte)(overrides[i] | block);
}
Notify(terrain, pathingCells);
return true;
}
// Restores the override bytes stored by TryPlace (call it when the building is destroyed).
public static void Remove(RTSTerrain terrain, RectInt pathingCells, byte[] previous)
{
if (terrain == null || terrain.Map == null || previous == null) return;
MapData map = terrain.Map;
var overrides = map.PathingOverrides;
int n = 0;
for (int z = pathingCells.yMin; z < pathingCells.yMax; z++)
for (int x = pathingCells.xMin; x < pathingCells.xMax; x++, n++)
overrides[map.PathingIndex(x, z)] = previous[n];
Notify(terrain, pathingCells);
}
// 4. NotifyChanged expects map cells, so convert the pathing rectangle.
static void Notify(RTSTerrain terrain, RectInt pathingCells)
{
int res = Mathf.Max(1, terrain.Map.PathingResolution);
int xMin = pathingCells.xMin / res, zMin = pathingCells.yMin / res;
int xMax = Mathf.CeilToInt(pathingCells.xMax / (float)res);
int zMax = Mathf.CeilToInt(pathingCells.yMax / (float)res);
terrain.NotifyChanged(MapLayers.PathingOverrides, new RectInt(xMin, zMin, xMax - xMin, zMax - zMin));
}
}
Um ein Gebäude zu entfernen, übergib die gespeicherten Zellen und Bytes an BuildingFootprint.Remove: Die Methode schreibt die vorherigen Override-Bytes zurück und ruft erneut NotifyChanged auf. Die Beispielkomponente RTSDemoBuilding macht genau das, siehe Demo zur Gebäudeplatzierung.
Bäume entfernst du auf die gleiche Weise über den Vegetations-Renderer: RTSVegetationRenderer.RemoveTree(id) benachrichtigt das Terrain, sodass die Pathing-Karte die Grundfläche des Baums freigibt und der Nebel des Krieges den Sichtblocker automatisch entfernt. Siehe Demo zum Baumabbau.
Fortgeschritten: Pathing-Daten in eigenen Jobs
Für eigene Burst-Jobs (Flow Fields, Influence Maps) erstellst du mit new PathingGrid(terrain.Pathing, terrain.GetPathingClearance()) eine schreibgeschützte Ansicht. Sie bietet IsPassable, LineOfSight, Trace und FindNearestPassable mit Pathing-Zellen als Maßeinheit, gefiltert durch einen PathingFilter (PathingFilter.ForUnit(required, allowShallowWater, unitRadius, cellSize)). Registriere das Handle deines Jobs mit terrain.RegisterPathingReader(handle), damit das Terrain den Abschluss des Jobs abwartet, bevor es Pathing-Daten ändert.
PathingMapBuilder baut Pathing-Karten für Objekte vom Typ MapData, die nicht von einem Terrain angezeigt werden (zum Beispiel auf einem Server, der Karten ohne Rendering lädt): PathingMapBuilder.Build(map, pathing, settings, tileset, vegetation, decor).
Gameplay-Beispielskripte
Der Ordner Samples/Scripts enthält ein kleines, aber vollständiges RTS-Gameplay-Gerüst: eine RTS-Kamera, Einheiten, die dem Terrain folgen, Auswahl mit Kontrollgruppen, Bewegungsbefehle mit Formationen, eine Minikarte, ein HUD und zwei Demos, die die Karte zur Laufzeit verändern. Die Skripte liegen in der Assembly RTSTerrainStudio.Samples und im Namespace RTSTerrainStudio.Samples.
Nutze sie, um deine Karten sofort auszuprobieren, als Ausgangspunkt für dein eigenes Spiel oder als Referenz dafür, wie du die Laufzeit-API verwendest. Jedes Skript ist kommentiert, und seine Unity-Messages sind, wo es sinnvoll ist, protected virtual, sodass du eigene Klassen davon ableiten kannst.
Alle Beispielkomponenten findest du unter . Eingaben werden über RTSInput gelesen, daher funktionieren die Beispiele sowohl mit dem Paket Input System als auch mit dem alten Input Manager. Die UI basiert auf UI Toolkit.
Schnellstart: jede Karte spielbar machen
- Öffne eine Szene mit einem RTS Terrain, dessen Karte Startpositionen hat (platziere sie mit den Marker-Werkzeugen).
- Wähle . Dadurch entsteht ein Objekt
RTS Demo Gamemit der KomponenteRTSDemoGame. - Klicke auf Play. Das Demospiel erstellt den Nebel des Krieges, den Kamera-Controller, Auswahl, Befehle, Minikarte und HUD, spawnt Einheiten an jeder Startposition (du bist Spieler 1, die anderen streifen umher) und bewegt die Kamera zu deiner Basis.
- Drücke F1, um die Steuerungshilfe ein- oder auszublenden.
Standardsteuerung
| Eingabe | Aktion |
|---|---|
| W A S D / Pfeiltasten | Kamera verschieben. Solange Einheiten ausgewählt sind, stoppt S sie, statt die Kamera zu verschieben (die Pfeiltasten verschieben die Kamera immer). |
| Bildschirmrand, Ziehen mit mittlerer Maustaste | Kamera verschieben / den Boden greifen und ziehen. |
| Mausrad | Zoomen (je näher, desto flacher der Blickwinkel). |
| Q / E, Alt + Ziehen mit rechter Maustaste | Ansicht drehen. |
| Linksklick / Ziehen | Einheit auswählen / Auswahlrechteck aufziehen. |
| Shift + Klick | Einheiten hinzufügen oder entfernen. |
| Doppelklick, Ctrl + Klick | Alle Einheiten dieses Typs auf dem Bildschirm auswählen. |
| Esc | Aktuellen Modus abbrechen, danach die Auswahl aufheben. Bei einem Spieltest im In-Game-Editor führt das nächste Esc zurück zum Editor. |
| Ctrl+1…9, 0 | Kontrollgruppe speichern (Shift+Zahl fügt der Gruppe Einheiten hinzu). |
| 1…9, 0 | Gruppe abrufen (zweimal drücken zentriert die Kamera auf die Gruppe). |
| Rechtsklick | In Formation bewegen (Shift reiht den Befehl ein und setzt die Formation fort). |
| S / H / P | Stoppen / Position halten / Patrouillieren (danach den zweiten Patrouillenpunkt per Linksklick setzen). |
| Minikarte | Linksklick oder Ziehen: Kamera bewegen. Rechtsklick: ausgewählte Einheiten bewegen. |
| B | Gebäude platzieren (Pathing-Änderung zur Laufzeit). |
| Delete | Gebäude unter dem Cursor entfernen. |
| T | Baum unter dem Cursor fällen. |
| Rechtsklick auf einen Baum | Ausgewählte Arbeiter gehen hin und fällen ihn. |
| F1 | Hilfe-Panel ein- oder ausblenden. |
Alle Tasten sind Felder vom Typ RTSKey in den Komponenten (Kamera, Auswahl, Befehle, Demos, HUD), sodass du sie im Inspector neu belegen kannst.
RTSDemoGame: der Einstiegspunkt
Macht aus jeder Szene mit einem RTS Terrain ein kleines spielbares RTS. Beim Spielstart erledigt die Komponente Folgendes:
- sie stellt sicher, dass ein
RTSFogOfWarManager(lokales Team = Local Team, Erkundung zurückgesetzt) und der Wegfindungsdienst vorhanden sind; - sie sucht Kamera-Controller, Auswahl, Befehle, Minikarte, HUD sowie die Gebäude- und die Baumdemo und erstellt die fehlenden;
- sie spawnt Einheiten an jeder Startposition (Team = Spielerindex; Spieler 1 ist Team 0) und gibt gegnerischen Einheiten eine Komponente
RTSWanderAI, sodass du sie im Nebel auftauchen und verschwinden siehst; - sie bewegt die Kamera zur Startposition des lokalen Spielers.
In-Game-Editor: Die Komponente reagiert auf RTSRuntimeEditorEvents.PlaytestChanged. Startest du im In-Game-Editor einen Spieltest, startet das Spiel. Beim Beenden des Spieltests werden die Einheiten despawnt, Demo-Gebäude entfernt, abgebaute Bäume wiederhergestellt (optional), die Gameplay-Systeme deaktiviert und die Kamera wieder an den Editor übergeben. Ist Auto Start aktiviert und befindet sich kein In-Game-Editor in der Szene, startet das Spiel sofort.
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Start | ||
| Auto Start | an | Startet, sobald die Szene läuft und kein In-Game-Editor vorhanden ist (sonst startet das Spiel mit dem Spieltest des Editors). |
| Local Team | 0 | Team des lokalen Spielers (0 = Spieler 1). Setzt du den Wert per Code, werden auch der Nebel des Krieges und die Auswahl aktualisiert. |
| Terrain | leer | Terrain, auf dem gespielt wird. Leer = RTSTerrain.Main. |
| Units | ||
| Unit Prefabs | leer | Einheiten-Prefabs, reihum verwendet. Leer = prozedurale Platzhaltereinheiten. |
| Units Per Player | 8 | Anzahl gespawnter Einheiten pro Spieler (1 bis 60). |
| Max Players | 8 | Maximale Anzahl an Spielern, die Einheiten erhalten. |
| Enemy Wander AI | an | Gegnerische Einheiten streifen umher. |
| Wander Radius | 30 | Radius in Metern, in dem die gegnerische KI umherstreift. |
| Systems | ||
| Create Missing Systems | an | Erstellt Kamera-Controller, Auswahl, Befehle, Minikarte, HUD und Demos, falls sie fehlen. Der Einheiten-Spawner wird immer erstellt. |
| Fog Of War | an | Aktiviert den Nebel des Krieges während des Spiels. |
| Minimap | an | Zeigt die Minikarte an. |
| Hud | an | Zeigt das HUD an (Info-Panel, Aktionsleiste, Hilfe über F1). |
| Show Help On Start | an | Zeigt beim Spielstart das Hilfe-Panel an. |
| Building Demo | an | Aktiviert die Demo zur Gebäudeplatzierung (B). |
| Tree Harvest Demo | an | Aktiviert die Demo zum Baumabbau (T, Arbeiter). |
| Restore Map On Stop | an | Stellt abgebaute Bäume wieder her, wenn das Spiel endet, sodass die bearbeitete Karte nach einem Spieltest unverändert ist. |
| References | ||
| Camera Controller, Selection Manager, Command Controller, Minimap UI, Demo HUD, Spawner, Building Placement, Tree Harvest, Fog Of War Manager | leer | Zu verwendende Systeme. Leer = in der Szene gesucht oder neu erstellt. |
API: StartGame(), StopGame(), IsRunning, AutoStart, LocalTeam, die Events GameStarted und GameStopped sowie die Eigenschaften Spawner, CameraController, SelectionManager, CommandController. Überschreibe protected virtual bool DetectInGameEditor(), wenn du einen eigenen In-Game-Editor verwendest.
Du baust dein eigenes Spiel? Nutze RTSDemoGame als Vorlage, nicht als Abhängigkeit: Übernimm die Reihenfolge seiner Einrichtungsschritte in deinen eigenen Game Manager und lasse weg, was du nicht brauchst. Für Multiplayer mit Mirror füge RTSDemoGame nicht hinzu: Die Einheiten spawnt der Network Manager.
RTSCameraController
Eine klassische RTS-Kamera: Verschieben per Tastatur, Bildschirmrand und Ziehen, Zoom mit Neigungskurve (je näher, desto flacher der Blickwinkel), Drehung, weiches Folgen der Bodenhöhe und Kartengrenzen. Sie läuft in LateUpdate. RTSCameraController.Main ist der erste aktivierte Controller.
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Target Camera | leer | Kamera, die bewegt wird. Leer = die Camera-Komponente auf diesem GameObject, sonst Camera.main. |
| Terrain | leer | Terrain für Bodenhöhe und Grenzen. Leer = RTSTerrain.Main. |
| Keyboard Pan, Pan Forward/Back/Left/Right Key, Arrow Keys | an, W/S/A/D, an | Verschieben per Tastatur. |
| Yield Keys To Orders | an | Verschiebetasten, die zugleich Befehlstasten des Befehls-Controllers sind (S = Stopp), verschieben die Kamera nicht, solange Einheiten ausgewählt sind. |
| Pan Speed / Fast Pan Multiplier | 35 / 2 | Meter pro Sekunde beim Standardzoom (skaliert mit dem Zoomabstand); Multiplikator, solange Shift gedrückt ist. |
| Edge Scrolling, Edge Size, Edge Speed Multiplier | an, 12 px, 1 | Verschiebt die Kamera, wenn die Maus den Bildschirmrand berührt. Das Rand-Scrollen pausiert, solange das Spielfenster keinen Fokus hat oder sich der Mauszeiger außerhalb des Fensters befindet. In Web-Builds gilt das auch, wenn die Maus die Seite oder ihr iframe verlassen hat. Auf Touchscreens pausiert es ebenfalls. |
| Middle Mouse Drag | an | Halte die mittlere Maustaste gedrückt, um den Boden zu greifen und zu verschieben. |
| Min / Max Distance | 12 / 75 | Abstand zum Drehpunkt bei maximal hinein- / herausgezoomter Ansicht (Meter). |
| Zoom Step, Start Zoom | 0.08, 0.6 | Zoomänderung pro Mausrad-Raste; anfänglicher Zoom (0 = am nächsten, 1 = am weitesten entfernt). |
| Distance Curve, Pitch Curve | - | Bilden den Zoom (0 bis 1) auf den Abstand und auf die Kameraneigung in Grad ab (etwa 55° beim Standardzoom). |
| Keyboard Rotate, Rotate Left/Right Key, Rotate Speed | an, Q/E, 100 | Drehen per Tastatur, in Grad pro Sekunde. |
| Alt Right Drag Rotate, Drag Rotate Sensitivity | an, 0.3 | Drehen mit Alt + Ziehen mit rechter Maustaste. |
| Snap Back Rotation, Snap Back Delay, Default Yaw | aus, 0.25, 0 | Kehrt ohne Dreheingabe zum Standardwinkel zurück (klassisches RTS-Verhalten). Bei einem Drehwinkel von 0 blickt die Kamera entlang +Z (Norden). |
| Follow Ground Height, Height Smooth Time | an, 0.3 | Hält den Drehpunkt auf Bodenhöhe, geglättet, damit Klippen die Ansicht nicht springen lassen. |
| Clamp To Terrain, Bounds Margin | an, 4 | Hält den Drehpunkt innerhalb der Karte (negative Randabstände erlauben den Blick über den Kartenrand hinaus). |
| Min Camera Clearance | 2 | Mindesthöhe der Kamera über dem Boden darunter. |
| Pan / Zoom / Rotation Smooth Time | 0.08 / 0.12 / 0.08 | Glättung in Sekunden (0 = sofort). |
| Ignore Mouse Over UI | an | Ignoriert Mausrad, Rand-Scrollen und Ziehen, solange sich der Mauszeiger über der UI befindet. |
| Use Unscaled Time | an | Die Kamera bewegt sich auch, während das Spiel pausiert ist. |
API: JumpTo(Vector3), FocusOn(Vector3), FocusOn(IReadOnlyList<RTSUnit>, bool instant = false), SetZoom(float, bool instant = false), SetYaw(float, bool instant = false), GetViewFootprint(Vector3[4]) (Bildschirmecken auf dem Boden, von der Minikarte genutzt), InitializeFromCamera(), InputEnabled sowie die schreibgeschützten Eigenschaften Pivot, Zoom, Yaw, Pitch, Distance, IsDragging.
RTSUnit und Hilfsklassen für Einheiten
Eine einfache RTS-Einheit: Team, Bewegung auf dem Terrain, Pfadverfolgung mit weichen Kurven, lokale Kollisionsvermeidung und Sicht im Nebel des Krieges. Sie fragt ihren Pfad-Provider nach einem Pfad und folgt ihm mit einem Vorausschaupunkt. Sie bremst bei der Ankunft ab, hält Abstand zu anderen Einheiten, betritt nie unbegehbare Zellen und plant ihren Pfad neu, wenn sie feststeckt oder sich die Pathing-Karte unter ihrem Pfad ändert (Gebäude platziert, Bäume gefällt, Terrain im In-Game-Editor bearbeitet). Sie benötigt und konfiguriert einen RTSFogOfWarRevealer (Team, Sichtradius) und einen RTSFogOfWarHideable (Team).
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Team | 0 | Team- bzw. Spielerindex (0 = Spieler 1). Einheiten des lokalen Teams lassen sich auswählen und befehligen. |
| Use Team Color, Player Color | an, Rot | Übernimmt bei jedem Teamwechsel die Farbe aus der klassischen Spielerfarben-Tabelle; genutzt für Auswahlkreise, Punkte auf der Minikarte und Renderer für die Teamfarbe. |
| Unit Type | "Unit" | Typname für „Doppelklick wählt alle desselben Typs aus“ (z. B. Footman, Worker). Die Baumdemo verwendet „Worker“. |
| Move Speed, Turn Speed, Acceleration | 5, 540, 25 | Meter pro Sekunde, Grad pro Sekunde, Meter pro Sekunde². |
| Radius, Height | 0.5, 1.8 | Kollisionsradius (Freiraum bei der Wegfindung, Kollisionsvermeidung, Auswahlkreis) und Höhe (Anklicken, Auswahlrechteck). |
| Align To Ground, Ground Align Speed | aus, 8 | Neigt die Einheit passend zum Boden (Fahrzeuge, Tiere). Ausgeschaltet bleibt Infanterie aufrecht. |
| Look Ahead, Arrival Distance, Slow Down Distance | 1.5, 0.15, 1.5 | Pfadverfolgung (Meter). |
| Stuck Check Interval | 0.6 | Sekunden zwischen Fortschrittsprüfungen. Eine Einheit ohne Fortschritt plant ihren Pfad neu. |
| Path Request Timeout | 3 | Sekunden, die auf einen Pfad gewartet wird, bevor die Anfrage einmal erneut gesendet wird; die Bewegung schlägt fehl, wenn auch die zweite Anfrage ins Timeout läuft. |
| Avoidance, Separation Strength, Personal Space | an, 8, 0.1 | Einfache Abstandshaltung. Untätige Einheiten weichen sich bewegenden Einheiten aus. |
| Sight Radius | 14 | Sichtradius für den Nebel des Krieges in Metern. |
| Selectable | an | Der Spieler kann diese Einheit auswählen. |
| Terrain | leer | Terrain, auf dem die Einheit läuft. Leer = das Terrain unter der Einheit (oder RTSTerrain.Main). |
| Path Provider | leer | Optionale Komponente, die IRTSUnitPathProvider implementiert. Leer = der globale Standard (siehe Pfad-Provider). |
| Team Color Renderers, Team Color Mode | keine, PropertyBlock | Renderer, die die Spielerfarbe zeigen, entweder über einen MaterialPropertyBlock (beliebiges Material) oder über ein gemeinsames Material pro Farbe (bestes Batching). |
| Draw Path Gizmos | an | Zeichnet den aktuellen Pfad in der Scene View. |
Befehle: MoveTo(Vector3, bool queue = false), Stop(), HoldPosition(), Patrol(Vector3 a, Vector3 b), Warp(Vector3, float? yaw = null), FaceTowards(Vector3), Repath(). Events: OnArrived, OnPathFailed (kein Pfad, wegen Blockade aufgegeben oder am nächstgelegenen erreichbaren Punkt angehalten), OnStateChanged. Zustand: State (Idle, Moving, Patrolling, Holding), IsMoving, IsWaitingForPath, IsPathPartial, LastMoveReachedDestination, Destination, CurrentPath, QueuedDestinations, PlannedEndPosition, Velocity, Position, Yaw, IsSelected, IsVisibleToLocalTeam.
using RTSTerrainStudio;
using RTSTerrainStudio.Samples;
using UnityEngine;
// Spawns a knight for player 2 and listens to its movement events.
public class KnightSpawner : MonoBehaviour
{
[SerializeField] Transform target;
void Start()
{
var terrain = RTSTerrain.Main;
// Nearest walkable spot where a unit of radius 0.75 m fits.
Vector3 position = RTSUnitFactory.FindSpawnPosition(terrain, transform.position, 0.75f);
RTSUnit knight = RTSUnitFactory.CreatePlaceholder(1, position, 180f, RTSUnitFactory.Knight);
knight.OnArrived += unit => Debug.Log(unit.name + " arrived.");
knight.OnPathFailed += unit => Debug.Log(unit.name + " can't reach that place.");
if (target != null) knight.MoveTo(target.position);
}
}
Hilfsklassen:
| Klasse | Zweck |
|---|---|
RTSUnitFactory | Prozedurale Platzhaltereinheiten: Stile Footman, Worker, Scout, Knight und DefaultMix; CreatePlaceholder(team, position, yaw, style, parent), Spawn(prefab, team, position, yaw, parent) (fügt einem Prefab RTSUnit hinzu, falls es fehlt), FindSpawnPosition(terrain, position, radius, searchRadius = 16). |
RTSUnitSpawner | Spawnt Einheiten pro Spieler an den Startpositionen der Karte (oder an den Custom Spawn Points oder in gegenüberliegenden Ecken). Einstellungen: Unit Prefabs, Units Per Player (6), Mix Placeholder Types, Terrain, Use Start Locations, Custom Spawn Points, Max Players, Spacing (2 m), Start Offset (5 m, Platz für ein Rathaus), Container, Spawn On Start. API: SpawnAll(), SpawnForPlayer(...), SpawnUnit(team, position, yaw, variant), DespawnAll(), TryGetStartLocation(player, out position, out yaw). |
RTSWanderAI | Lässt eine Einheit zu zufälligen Punkten rund um ihren Ausgangspunkt laufen. Einstellungen: Wander Radius (25 m), Min/Max Wait (2 / 7 s), Home Is Start Position, Home Position. |
RTSUnitRegistry | Alle Einheiten und ein räumlicher Hash: All, GetTeam(team), QueryRadius(...), QueryScreenRect(...), PickAtScreen(...), Events UnitRegistered, UnitUnregistered, UnitTeamChanged. |
Pfad-Provider: IRTSUnitPathProvider
Einheiten sind nicht an eine bestimmte Wegfindungslösung gebunden. Sie fragen einen IRTSUnitPathProvider, der in dieser Reihenfolge gewählt wird: der eigene Path Provider der Einheit (oder PathProviderOverride), sonst RTSUnitPathProviderRegistry.Default, sonst der eingebaute Provider RTSBuiltinPathProvider (das Burst-A* des Pakets, im Play Mode über RTSPathfindingService). Hier docken die Integrationen für das A* Pathfinding Project und NavMesh an.
Es gibt zwei Arten von Providern:
- Pfad-Provider (
HandlesMovement= false) liefern überRequestPathWegpunkte zurück; die Einheit folgt ihnen selbst (Steuerung, Kollisionsvermeidung, Bodenausrichtung). - Bewegungs-Provider (
HandlesMovement= true) bewegen die Einheit selbst (zum Beispiel ein NavMeshAgent). Die Einheit leitet ihre Befehle überOnUnitMoveCommandundOnUnitStopweiter; der Provider meldet das Ergebnis mitunit.NotifyArrived()oderunit.NotifyPathFailed(). Solange ein solcher Provider für eine Einheit zuständig ist, verändert die Einheit ihren Transform nie selbst.
Der Vertrag: Rufe den Callback onComplete genau einmal auf, und zwar auf dem Hauptthread, sofort oder später. Übergib bei Erfolg die Wegpunkte (einschließlich Start und Ziel) und true, bei einem Fehlschlag eine leere Liste oder null und false. Du darfst auch einen Pfad zum nächstgelegenen erreichbaren Punkt mit true zurückgeben; die Einheit erkennt, dass er vor dem Ziel endet. Der Provider, der eine Bewegung erhalten hat, behält sie, bis die Bewegung endet. Wechselt der Provider während einer Bewegung oder wird er zerstört, erhält der alte OnUnitStop, und der Befehl geht an den neuen. Eine Anfrage, die innerhalb der unter Path Request Timeout eingestellten Zeit keine Antwort erhält, wird einmal wiederholt und schlägt dann fehl.
Leite von RTSUnitPathProviderComponent ab, um eine Szenenkomponente zu erstellen, die sich selbst als Standard registrieren kann (Make Default, standardmäßig an) oder die du in das Feld Path Provider einer Einheit ziehst:
using System;
using System.Collections.Generic;
using RTSTerrainStudio;
using RTSTerrainStudio.Samples;
using UnityEngine;
// A path provider for heavy units that must never cross water (not even shallow fords).
// Turn off "Make Default" and drag it into the Path Provider field of the units that should use it.
public class NoWaterPathProvider : RTSUnitPathProviderComponent
{
[SerializeField] int maxSearchNodes = 200000;
readonly List<Vector3> path = new List<Vector3>(64);
// The unit follows the waypoints itself.
public override bool HandlesMovement => false;
public override void RequestPath(RTSUnit unit, Vector3 from, Vector3 to, Action<List<Vector3>, bool> onComplete)
{
RTSTerrain terrain = unit != null ? unit.Terrain : RTSTerrain.Main;
if (terrain == null) { onComplete(null, false); return; }
var options = PathRequestOptions.ForUnit(unit != null ? unit.Radius : 0.5f);
options.allowShallowWater = false;
options.maxSearchNodes = maxSearchNodes;
// Synchronous search; the list is reused for the next request, the unit copies what it keeps.
PathStatus status = RTSPathfinder.FindPath(terrain, from, to, options, path);
onComplete(path, status == PathStatus.Success || status == PathStatus.Partial);
}
}
Im Code schaltet RTSUnitPathProviderRegistry.Default = myProvider; alle Einheiten ohne eigenen Provider um; Default = null kehrt zum eingebauten Pathfinder zurück. RTSBuiltinPathProvider stellt Options (der darin enthaltene unitRadius wird durch den Radius der jeweiligen Einheit ersetzt) und UseAsyncService bereit.
RTSSelectionManager
Auswahl per Klick, Rechteck, Shift und Doppelklick, Kontrollgruppen sowie Hover- und Auswahlringe. Nur Einheiten des lokalen Teams lassen sich auswählen. RTSSelectionManager.Main ist der erste aktivierte Manager.
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Target Camera | leer | Kamera zum Anklicken von Einheiten. Leer = Camera.main. |
| Use Fog Of War Local Team | an | Verwendet das lokale Team des aktiven Managers für den Nebel des Krieges. |
| Local Team | 0 | Team, dessen Einheiten auswählbar sind (wenn es nicht aus dem Nebel des Krieges übernommen wird). |
| Drag Threshold, Double Click Time | 6 px, 0.3 s | Ab wann ein Klick zum Auswahlrechteck wird; Zeitfenster für Doppelklicks. |
| Max Selection | 0 | Maximale Anzahl ausgewählter Einheiten (0 = unbegrenzt; 12 ist eine übliche Grenze in klassischen RTS-Spielen). |
| Pick Tolerance | 6 px | Zusätzlicher Klickradius, damit sich kleine Einheiten leicht anklicken lassen. |
| Keep Selection On Empty Click | an | Ein Klick auf leeren Boden oder ein Auswahlrechteck ohne eigene Einheiten behält die Auswahl bei, wie in klassischen RTS-Spielen. |
| Clear Selection Key | Escape | Hebt die Auswahl auf. |
| Control Groups, Group Double Tap Time | an, 0.35 s | Ctrl+Zahl speichert, Shift+Zahl fügt hinzu, die Zahl allein ruft ab, zweimal gedrückt zentriert sie die Kamera. Es gibt zehn Gruppen (RTSSelectionManager.GroupCount = 10) auf den Tasten 1–9 und 0; der Gruppenindex i von StoreGroup, RecallGroup und GetGroup entspricht der Zahlentaste i (Index 0 ist Taste 0). |
| Show Selection Circles, Show Hover, Use Team Color For Circles, Selection Color, Circle Scale, Circle Width, Box Color | - | Darstellung der Ringe und des Auswahlrechtecks. |
API: Selection, Count, Select(unit, additive), Deselect, ToggleSelection, SetSelection(units, additive), ClearSelection(), SelectAllOfTypeOnScreen(unit, additive), GetSelectionCenter(), StoreGroup(i), AddToGroup(i), RecallGroup(i), GetGroup(i), ClearGroups(), HoveredUnit, Events SelectionChanged und GroupRecalled.
RTSCommandController und Formationen
Wandelt Rechtsklicks und Befehlstasten in Befehle für die ausgewählten Einheiten um. RTSCommandController.Main ist der erste aktivierte Controller.
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Selection, Target Camera | leer | Leer = RTSSelectionManager.Main / Camera.main. |
| Stop Key, Hold Key, Patrol Key, Cancel Key | S, H, P, Escape | Befehlstasten. |
| Use Formation | an | Bewegt Gruppen in einer Rasterformation (aus = jede Einheit läuft zum angeklickten Punkt). |
| Spacing Multiplier, Extra Spacing, Width To Depth | 1.15, 0.3, 1.5 | Abstand der Formationsplätze als Vielfaches des größten Einheitendurchmessers, zusätzliche Lücke in Metern, bevorzugtes Verhältnis von Spalten zu Reihen. |
| Show Marker, Move Marker Color, Patrol Marker Color | an | Animierter Ring am Befehlsziel. |
API: IssueMove(Vector3 target, bool queue = false), IssueMove(IReadOnlyList<RTSUnit> units, Vector3 target, bool queue = false), IssuePatrol(Vector3), IssueStop(), IssueHold(), BeginPatrolTargeting(), CancelTargeting(), IsOrderKeyActive(RTSKey), Events MoveIssued, CommandIssued, TargetingChanged.
RTSFormation.ComputeGrid(units, target, slots, spacingMultiplier, extraSpacing, widthToDepth, fromPlannedEnd) berechnet einen Formationsplatz pro Einheit und weist jeder Einheit den Platz zu, der ihrer Position in der Gruppe am nächsten liegt, sodass sich Pfade selten kreuzen. Die Methode gibt die Blickrichtung zurück. Mit fromPlannedEnd (verwendet für per Shift eingereihte Befehle) setzt die Formation dort an, wo die Einheiten nach ihren aktuellen Befehlen stehen werden.
Eingabekoordination. RTSGameInput sorgt dafür, dass jeder Klick von genau einem System verarbeitet wird: Ein Modus wie die Gebäudeplatzierung beansprucht den Zeiger (Acquire, Release, CanUsePointer), und ein System, das einen Klick oder eine Taste verarbeitet hat, ruft Consume / ConsumeKey auf. Die Beispiele laufen in einer festen Reihenfolge (Gebäudeplatzierung, Baumabbau, Befehle, Auswahl, Einheiten, Kamera, UI), die durch die Konstanten RTSGameInput.Order… festgelegt ist. Nutze denselben Mechanismus in deinen eigenen Modi, damit sie mit den Beispielen zusammenarbeiten.
RTSMinimapUI
Eine Minikarte auf Basis von UI Toolkit für das laufende Spiel. Das Kartenbild wird mit RTSMinimapBaker gebacken und bei Kartenänderungen (gedrosselt) neu gebacken. Darüber wird der Nebel des Krieges des lokalen Teams gelegt. Einheiten erscheinen als Punkte in ihrer Spielerfarbe (Gegner nur, solange sie sichtbar sind), und der Sichtbereich der Kamera wird als Umriss gezeichnet. Ein Linksklick oder Ziehen bewegt die Kamera, ein Rechtsklick schickt die ausgewählten Einheiten an diese Stelle.
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Terrain, Camera Controller, Command Controller | leer | Leer = RTSTerrain.Main, RTSCameraController.Main, RTSCommandController.Main. |
| Corner | BottomLeft | Bildschirmecke (klassische RTS-Spiele nutzen die Ecke unten links). |
| Size, Margin | 248, 16 | Rahmengröße und Abstand zu den Bildschirmrändern in UI-Pixeln (Referenzauflösung 1920 × 1080). |
| Resolution | 256 | Auflösung des gebackenen Kartenbilds (längste Seite). |
| Rebake Delay, Min Rebake Interval | 0.35 s, 1 s | Mehrere Änderungen werden zu einem einzigen Backvorgang zusammengefasst. |
| Bake Options | - | Aussehen des gebackenen Bilds (Bodenmodus, Reliefschattierung, Klippen, Wasser, Bäume, Dekor, Startpositionen, Spielerfarben). |
| Show Fog, Fog Refresh Interval | an, 0.1 s | Dunkelt unerkundete und vom Nebel verdeckte Bereiche ab, wie in der 3D-Ansicht. |
| Show Units, Unit Dot Size, Show Camera View, Camera View Color, Overlay Rate | an, 4, an, weiß, 20 | Punkte, Kameraumriss und wie oft sie neu gezeichnet werden. |
| Left Click Moves Camera, Right Click Moves Units | an, an | Interaktion. |
API: Visible, Terrain, MapTexture, BakeOptions, Rebake().
RTSDemoHUD und RTSRuntimeUI
RTSDemoHUD zeigt ein Info-Panel für die Auswahl, eine Aktionsleiste (Stop, Hold, Patrol, Build), die Framerate, kurze Rückmeldungen („Can't reach that place.“, „Can't build there.“, „Can't build in unexplored land.“) und das F1-Hilfe-Panel. Einstellungen: Help Key (F1), Show Help On Start, Show Fps, Show Feedback Messages, Message Duration (2.5 s) sowie Referenzen auf Auswahl, Befehle und Demos. API: Visible, HelpVisible, SetMode(string), ShowMessage(string). Solange die Komponente aktiviert ist, schaltet sie RTSInput.SetBrowserShortcutGuard ein. In Web-Builds öffnet F1 deshalb die HUD-Hilfe statt der Hilfeseite des Browsers. Der Schutz unterdrückt außerdem die browsereigene Aktion für die übrigen Funktionstasten bis F10 (darunter F5, das die Seite neu lädt) und für Ctrl+S/O/D/A (siehe Der In-Game-Editor im Browser). Auf anderen Plattformen hat er keine Wirkung.
RTSRuntimeUI ist der kleine Baukasten auf Basis von UI Toolkit, den die Beispiele gemeinsam nutzen: CreateDocument, CreatePanel, CreateLabel, CreateButton(text, tooltip, action), AttachTooltip, IsScreenPointOverUI und die Farbpalette der Beispiele. Damit kannst du eigene Panels im gleichen Stil hinzufügen.
Alle Oberflächen der Beispiele teilen sich ein Panel (RTSRuntimeUI.PanelSettings), das mit der Bildschirmgröße skaliert (Referenzauflösung 1920 × 1080). Wie beim In-Game-Editor skaliert es nie unter RTSRuntimeUI.MinimumScale (Standard 0.85; in Web-Builds in CSS-Pixeln gemessen). So bleiben HUD, Minikarte und Auswahl auch in kleinen Fenstern und Browser-Canvas-Elementen lesbar. Große Bildschirme sind davon nicht betroffen. Setze den Wert auf 0, um das Minimum auszuschalten.
RTSBuildingPlacementDemo
Zeigt, wie Spielcode die Pathing-Karte zur Laufzeit verändert. Drücke B: Eine halbtransparente Vorschau der Grundfläche folgt dem Cursor und rastet am Pathing-Raster ein; jede Zelle wird grün (bebaubar) oder rot (blockiert) gezeichnet. Ein Linksklick platziert ein einfaches Gebäude in der Teamfarbe (halte Shift gedrückt, um mehrere zu platzieren). Die Grundfläche wird in die Pathing-Overrides geschrieben, das Terrain baut den Bereich neu auf, und Einheiten in Bewegung planen ihren Weg um das Gebäude herum neu. Ein Klick auf eine blockierte Stelle zeigt einen roten Marker und eine Meldung. Ein Rechtsklick oder Esc bricht ab; Delete entfernt das Gebäude unter dem Cursor und stellt das Pathing wieder her.
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Size In Cells | 2 × 2 | Größe der Grundfläche in Kartenzellen (X × Z). |
| Building Height | 3 | Höhe des prozeduralen Gebäudes in Metern. |
| Building Prefab | leer | Optionales Prefab anstelle des prozeduralen Gebäudes. Sein Pivot wird in der Mitte der Grundfläche auf den Boden gesetzt. |
| Team | -1 | Besitzerteam. -1 = das lokale Team des Auswahl-Managers (oder des Nebels des Krieges). |
| Sight Radius | 10 | Sichtradius von Gebäuden (0 = keine Sicht). Außerdem werden Gebäude zu verbergbaren Objekten im Geisterbild-Modus. |
| Footprint Flags | Unwalkable, Unbuildable | Pathing-Overrides, die für die Grundfläche geschrieben werden. |
| Push Units Out | an | Einheiten, die auf der Grundfläche stehen, gehen aus dem Weg. |
| Require Explored | an | Erlaubt das Bauen nur auf Boden, den das lokale Team erkundet hat (solange ein Nebel des Krieges aktiv ist). |
| Place Key, Cancel Key, Demolish Key | B, Escape, Delete | Tasten. |
| Valid Color, Invalid Color | grün, rot | Farben der Grundflächenzellen. |
| Terrain, Target Camera | leer | Leer = das Terrain unter dem Cursor / Camera.main. |
API: BeginPlacement(), CancelPlacement(), IsPlacing, GetFootprintSize(terrain), CanPlaceAt(terrain, worldCenter), GetPlacementError(terrain, worldCenter) (eine Meldung für den Spieler oder null), PlaceBuilding(terrain, worldCenter, owner), RemoveBuilding(building), RemoveAllBuildings(), Buildings, die Events BuildingPlaced, BuildingRemoved, PlacementModeChanged, PlacementFailed(Vector3, string). Jedes platzierte Gebäude hat eine Komponente RTSDemoBuilding (Team, PathingCells, ContainsPoint, Remove()), die die vorherigen Overrides speichert und sie wiederherstellt, wenn das Gebäude entfernt oder zerstört wird.
Um daraus echte Gebäude zu machen, weise der Einstellung Building Prefab dein Prefab zu, abonniere BuildingPlaced, um die Kosten abzuziehen und den Bau zu starten, und rufe GetPlacementError auf, um dem Spieler den Grund anzuzeigen, wenn er nicht bauen kann.
RTSTreeHarvestDemo
Zeigt, wie Bäume im laufenden Spiel entfernt werden. Bewege den Cursor über einen Baum (er bekommt einen gelben Ring) und drücke T, um ihn sofort zu fällen. Oder wähle Arbeiter aus (Einheitentyp „Worker“) und klicke mit der rechten Maustaste auf einen Baum: Sie gehen hin, drehen sich ihm zu, hacken einen Moment lang (ein Ring zieht sich um den Stamm zusammen) und fällen ihn. Der Baum wird mit RTSVegetationRenderer.RemoveTree entfernt, sodass das Pathing die Stelle wieder freigibt und der Nebel des Krieges den Sichtblocker aufhebt. An der alten Position wird eine Fallanimation (RTSFallingTreeEffect) abgespielt.
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Harvest Key | T | Fällt den Baum unter dem Cursor. |
| Workers Harvest On Right Click | an | Ein Rechtsklick auf einen Baum schickt die ausgewählten Arbeiter los, um ihn zu fällen. |
| Worker Type | "Worker" | Einheitentyp, der Bäume abbauen kann. |
| Pick Radius | 1.5 | Maximaler Abstand zwischen dem Cursorstrahl und einem Baum, damit dieser ausgewählt wird. |
| Chop Time, Work Distance | 1.5 s, 1.2 m | Wie lange ein Arbeiter hackt und wie nah er am Stamm steht. |
| Fall Duration, Fade Duration | 1.1 s, 0.5 s | Fallanimation und abschließendes Schrumpfen. |
| Highlight Color, Show Chop Progress, Chop Progress Color | - | Ringe unter dem Baum, über dem der Cursor steht, und unter dem Baum, der gerade gefällt wird. |
| Target Camera | leer | Kamera, mit der Bäume per Cursor ausgewählt werden. |
API: TryGetHoveredTree(out RTSVegetationRenderer, out TreeInstanceData), HarvestTree(vegetation, treeId, fallAwayFrom), OrderHarvest(workers, vegetation, tree), RestoreHarvestedTrees(), CancelAllJobs(), HarvestedCount, das Event TreeHarvested.
using RTSTerrainStudio;
using UnityEngine;
// Cuts the tree nearest to a point, e.g. when a catapult hits a forest.
public static class TreeCutter
{
public static bool CutNearest(RTSTerrain terrain, Vector3 worldPoint, float maxDistance)
{
var vegetation = terrain != null ? terrain.GetComponent<RTSVegetationRenderer>() : null;
if (vegetation == null) return false;
if (!vegetation.TryFindNearestTree(worldPoint, maxDistance, out TreeInstanceData tree)) return false;
// Removes the tree from the map and notifies the terrain:
// pathing and fog of war blockers update automatically.
return vegetation.RemoveTree(tree.id);
}
}
Kleine Hilfsklassen
| Klasse | Zweck |
|---|---|
RTSMoveMarker | Animierter Ring am Zielpunkt eines Befehls: RTSMoveMarker.Spawn(position, color, radius, seconds). |
RTSGroundRing | Ein Ring, der sich an das Terrain anschmiegt (Auswahlkreise, Reichweiten): RTSGroundRing.Create(name, color, radius, width, parent), danach Place(position). |
RTSSampleMaterials, RTSSampleMeshes | Zwischengespeicherte URP-Materialien pro Farbe und einfache Grundform-Meshes für Platzhalter. |
RTSSampleUtility | Ermitteln des zuständigen Terrains und des Bodenpunkts an einer Bildschirmposition, XZ-Abstände und PathingRectToCellRect(map, pathingRect). |
Demo-Inhaltsgenerator
Das Paket enthält keine Grafiken von Drittanbietern. Stattdessen erzeugt ein Generator alle Demo-Inhalte prozedural direkt in deinem Projekt: kachelbare Boden-, Klippen- und Wassertexturen, Gras- und Blumen-Sprites, Low-Poly-Bäume und -Props, Materialien, Prefabs, zwei Tilesets, Vegetations- und Dekorbibliotheken, ein Auto-Paint-Profil, Pinselformen, drei Demokarten und drei Demoszenen. Die Ausgabe ist deterministisch (feste Seeds), daher erzeugt jeder Durchlauf dieselben Inhalte.

Demo-Inhalte erstellen
- Stelle sicher, dass du dich im Edit Mode befindest und deine Szene gespeichert hast (Unity erlaubt nur eine unbenannte Szene; der Generator fordert dich bei Bedarf zum Speichern auf).
- Wähle . Ein Fortschrittsbalken zeigt die einzelnen Schritte. Das dauert einen kurzen Moment.
- Wenn du gefragt wirst, ob die Demoszenen zu den Build-Einstellungen hinzugefügt werden sollen, wähle Not Now, es sei denn, du willst einen Player-Build mit den Demos erstellen (du kannst sie später unter hinzufügen).
- Der Generator wählt die Szene
RTS Terrain Studio - Gameplay Demoim Project-Fenster aus. Doppelklicke darauf, um sie zu öffnen.
Der Generator läuft auch, wenn du im Abschnitt zu den Demo-Inhalten im Welcome-Fenster () auf Create klickst oder wenn du das Angebot annimmst, das erscheint, sobald du ein RTS Terrain in einem Projekt ohne Tileset erstellst. Er schließt oder ersetzt nie deine geöffneten Szenen: Die Demoszenen werden zusätzlich angelegt.
Der Generator führt diese Schritte aus (jeden davon kannst du auch aus Code aufrufen):
| Schritt | Methode | Was erzeugt wird |
|---|---|---|
| Texturen | CreateTextures() | Boden-, Klippen-, Wasser-, Baum- und Prop-Texturen (512 px), Gras- und Blumen-Sprites sowie Pinselstempel (256 px). |
| Materialien | CreateMaterials() | Baum- und Pflanzenmaterialien (Shader RTS Terrain Studio/Vegetation, Wind), Prop-Materialien (URP Lit), die von den Beispielen genutzten Vorlagenmaterialien, das Himmelsmaterial und das Post-Processing-Profil. |
| Meshes und Prefabs | CreateModels() | Low-Poly-Bäume, Büsche, Felsen und Props sowie deren Prefabs. |
| Tilesets, Bibliotheken und Pinsel | CreateDataAssets() | Die Tilesets Summer und Winter, die Bibliotheken Demo Vegetation und Demo Decor (mit Paletten-Icons), das Profil Summer Auto Paint und Pinselformen. |
| Shader-Referenzen und UI-Einstellungen | UpdateResources() | Sorgt dafür, dass alle Shader des Pakets und die Runtime-Panel-Einstellungen von UI Toolkit in Player-Builds enthalten bleiben. |
| Demokarten | CreateMaps() | Drei .rtsmap-Dateien (siehe unten). |
| Demoszenen | CreateScenes(bool interactive) | Drei Szenen (siehe Demoszenen). Fügt außerdem das Renderer Feature für den Nebel des Krieges zu deinem URP-Renderer hinzu. |
Was du bekommst
- Tileset Summer (ID
rts-demo-summer) mit zehn Bodenschichten: Grass, Lush Grass, Dirt, Rocky Dirt, Sand, Cobblestone, Dark Forest Floor, Rock, Snow und Blight. Rock und Blight sind auf Unbuildable gesetzt – ein gutes Beispiel für Pathing-Regeln von Bodenschichten. - Tileset Winter (ID
rts-demo-winter): dieselben Texturen mit kalten Farbtönen und derselben Schichtreihenfolge, sodass jede Demokarte zwischen Summer und Winter wechseln kann. - Summer Auto Paint: Flecken aus sattem Gras und Erde auf den Grasflächen, Erde und steinige Erde entlang der Klippenkanten, Sand an den Ufern, Fels an steilen Hängen und Schnee in hohen Lagen.
- Die Bibliotheken Demo Vegetation und Demo Decor: Gras, hohes Gras, gelbe und lila Blumen und Farne; Kiefern, Laubbäume, abgestorbene Bäume und Büsche; Felsen, ein Findling, eine verfallene Säule, Kisten, Fässer, Zäune, Baumstümpfe und Pilze.
- Drei Karten:
- Two Rivers (1v1): 128 × 128 Zellen, punktsymmetrisch. Zwei mäandernde Flüsse treffen sich in einem See in der Mitte; über flache Furten können Einheiten sie überqueren.
- Four Kingdoms (4p): 192 × 192 Zellen, vierfache Rotationssymmetrie, gestapelte Plateaus und ein verseuchtes Zentrum mit Ruinen.
- Sandbox: 96 × 96 Zellen, größtenteils flach mit einem Plateau und einem Teich, zum Ausprobieren der Werkzeuge.
Wo die Dateien landen
Der Generator schreibt nur in seine eigenen Ordner unterhalb des Paketordners (Assets/RTSTerrainStudio, sofern du ihn nicht verschoben hast; das Paket funktioniert in jedem Ordner):
| Ordner | Inhalt |
|---|---|
Content/Textures | Generierte Texturen in den Unterordnern Ground, Cliffs, Water, Details, Props und Brushes. |
Content/Materials, Content/Meshes, Content/Prefabs | Materialien, Meshes und Prefabs der Bäume und Props. |
Content/Tilesets | Summer.asset, Winter.asset und Summer Auto Paint.asset. |
Content/Libraries | Demo Vegetation.asset, Demo Decor.asset und ihre Icons. |
Content/Brushes | Pinselformen. |
Content/Maps | Two Rivers (1v1).rtsmap, Four Kingdoms (4p).rtsmap, Sandbox.rtsmap. |
Content/Settings | Render-Einstellungen (Post-Processing-Profil). |
Samples/Scenes | Die drei Demoszenen. |
Resources/RTSTerrainStudio | Shader-Referenzen und UI/RTSRuntimePanelSettings.asset. |
Neu generieren
Führe erneut aus und bestätige mit Regenerate. Vorhandene Demo-Assets werden an Ort und Stelle überschrieben, sodass Karten, Szenen und Prefabs, die auf sie verweisen, weiter funktionieren. Deine eigenen Assets werden nie angerührt. Kartendateien, deren Inhalt sich nicht geändert hat, werden nicht neu geschrieben; so tauchen in der Versionsverwaltung keine unnötigen Änderungen auf.
Beim Neugenerieren werden alle Änderungen überschrieben, die du an den generierten Assets selbst vorgenommen hast (zum Beispiel ein angepasstes Demo-Tileset oder eine bearbeitete Demokarte). Um Demo-Inhalte anzupassen, dupliziere zuerst das Asset (Ctrl+D) oder speichere die Karte unter einem neuen Namen, und bearbeite dann die Kopie.
Aus Code (Editor-Assembly, Namespace RTSTerrainStudio.Editor):
| Member | Beschreibung |
|---|---|
RTSDemoContent.CreateAll(bool openScene) | Führt alle Schritte mit Fortschrittsbalken aus. Gibt false zurück, wenn abgebrochen wurde oder ein Schritt fehlgeschlagen ist. |
RTSDemoContent.CreateAllNonInteractive() | Zeigt keine Dialoge und öffnet keine Szene; für Automatisierung und den Batch-Modus. Gibt eine kurze Zusammenfassung zurück. |
RTSDemoContent.IsCreated | True, wenn die wichtigsten Demo-Assets vorhanden sind. |
RTSDemoContent.ContentFolder, ScenesFolder, SummerTilesetPath, TwoRiversMapPath, GameplayScenePath, … | Pfade der generierten Ordner und Assets. |
RTSDemoContent.RenderPreview(scenePath, pngPath, width, height) | Rendert ein Vorschaubild einer Szene. |
// Editor script (place it in an Editor folder): create the demo content from a build pipeline or a CI job.
using RTSTerrainStudio.Editor;
using UnityEngine;
public static class BuildDemoContent
{
// Run with: Unity -batchmode -executeMethod BuildDemoContent.Run -quit
public static void Run()
{
if (RTSDemoContent.IsCreated) return; // nothing to do
Debug.Log(RTSDemoContent.CreateAllNonInteractive()); // logs where the content was written
}
}
Demoszenen
Der Generator erstellt drei Szenen in Samples/Scenes. Jede davon enthält ein Licht Sun, ein Volume Post Processing, ein RTS Terrain (mit Komponenten für Vegetation, Wasser, Dekor und Umgebung) und eine Main Camera, die die für Wasser nötigen Depth- und Opaque-Texturen einschaltet.
| Szene | Karte | Inhalt |
|---|---|---|
RTS Terrain Studio - Gameplay Demo | Two Rivers (1v1) | RTS-Kamera, Nebel des Krieges und RTSDemoGame mit Auto Start. Klicke auf Play, und das Spiel beginnt. |
RTS Terrain Studio - In-Game Editor Demo | Sandbox | Der In-Game-Editor. Nebel des Krieges und RTS-Kamera bleiben während der Bearbeitung aus; Einheiten spawnen, wenn du einen Spieltest startest. |
RTS Terrain Studio - Editor Playground | Four Kingdoms (4p) | Kein Gameplay. Eine große Karte zum Ausprobieren der Werkzeuge im Unity Editor. |
Gameplay Demo: Das kannst du ausprobieren
- Klicke auf Play. Deine Einheiten (Spieler 1) stehen auf dem Platz deiner Startposition; der Rest der Karte ist schwarz.
- Wähle mit einem Auswahlrechteck ein paar Einheiten aus und klicke mit der rechten Maustaste auf die andere Seite des Flusses. Beobachte, wie sie in Formation eine Furt durch das Flachwasser wählen.
- Bewege Einheiten neben eine Klippe. Beachte, dass Einheiten unterhalb das Plateau darüber nicht sehen können und dass ein Wald verdeckt, was hinter ihm liegt. Führe sie dann eine Rampe hinauf und schaue hinunter.
- Ziehe deine Einheiten aus einem Gebiet ab: Es bleibt abgedunkelt (erkundet), statt wieder schwarz zu werden. Gegnerische Einheiten, die dorthin wandern, verschwinden, sobald du sie aus den Augen verlierst.
- Drücke B und platziere ein Gebäude im Weg laufender Einheiten. Sie gehen darum herum. Drücke Delete, während der Cursor darüber steht, um es zu entfernen.
- Bewege den Cursor über einen Baum und drücke T, oder wähle Arbeiter aus und klicke mit der rechten Maustaste auf einen Baum. Die Lücke im Wald wird begehbar, und du kannst hindurchsehen.
- Speichere eine Gruppe mit Ctrl+1, scrolle weg und drücke zweimal 1, um zurückzuspringen. Nutze die Minikarte, um die Kamera zu bewegen und Einheiten loszuschicken.
- Beende den Play Mode, wähle das Objekt Fog of War und probiere für Line Of Sight andere Modi aus, oder ändere in RTS Demo Game die Einstellung Local Team, um als anderer Spieler zu spielen.
In-Game Editor Demo: Das kannst du ausprobieren
- Klicke auf Play. Der In-Game-Editor öffnet sich mit der Karte Sandbox; die ganze Karte ist sichtbar.
- Bearbeite die Karte: Hebe ein Plateau an, texturiere den Boden, pflanze einen Wald und platziere Startpositionen mit den Marker-Werkzeugen.
- Klicke auf Play Test (F5).
RTSDemoGamespawnt Einheiten an den Startpositionen und schaltet den Nebel des Krieges und die RTS-Kamera ein. Deine Änderungen wirken sofort: Klippen blockieren die Sicht, Wälder blockieren Wege. - Drücke Esc (nachdem du jeden aktiven Modus abgebrochen und die Auswahl aufgehoben hast) oder F5, oder klicke auf Back to Editor (Esc or F5). Die Einheiten verschwinden, und die Karte wird auf ihren Zustand vor dem Spieltest zurückgesetzt.
Editor Playground: Das kannst du ausprobieren
- Öffne die Szene und das Fenster von RTS Terrain Studio ().
- Wähle das RTS Terrain aus und probiere die Werkzeuge auf der Karte Four Kingdoms aus: Klippen, Rampen, Texturieren, Vegetation und Dekor.
- Drücke in der Scene View P, um die Pathing-Overlays durchzuschalten, und sieh dir an, wie Klippen, Hänge, Wasser, Bäume und die Bodenschicht Blight die Begehbarkeit und Bebaubarkeit beeinflussen.
- Willst du sie spielen? Füge hinzu und klicke auf Play: Einheiten spawnen an den Startpositionen aller vier Spieler.
Die Integrationen haben eigene Demoszenen, die bei Bedarf erstellt werden: , und . Sie benötigen die Demo-Inhalte und das passende Drittanbieterpaket.
Die Demo sieht falsch aus oder hat keine Texturen? Führe aus und wende die vorgeschlagenen Korrekturen an (URP-Asset, Renderer Feature, Kameratexturen). Fehlt eine Demoszene, führe den Generator erneut aus: Er erstellt fehlende Szenen neu und lässt deine eigenen Szenen unverändert.
Kartengenerierung und Kartenwerkzeuge
Der Tab Map im Fenster von RTS Terrain Studio (, letzter Tab vor Settings) enthält die Panels, die auf die gesamte Karte wirken: Map, New Map, Resize, Generator, Heightmap, Minimap, Validation und Environment. Dieses Kapitel erklärt den prozeduralen Generator, den Import und Export von Heightmaps, das Backen der Minikarte und den Kartenvalidator. All das kannst du auch aus deinem eigenen Code nutzen; die Beispiele kompilieren mit dem Paket im Auslieferungszustand.
Kartengenerator
Der Generator (RTSMapGenerator) erzeugt aus einem Seed (Startwert für den Zufallsgenerator) ein vollständiges klassisches RTS-Layout: durch saubere Klippen getrennte Plateaus, Rampen dazwischen, Seen oder Inseln, einen optionalen Kartenrand, Bodentexturierung, Gras und Wälder, Dekor und Startpositionen der Spieler. Er arbeitet mit der vorhandenen Karte und behält deren Größe bei. Derselbe Seed mit denselben Einstellungen und derselben Kartengröße erzeugt immer dieselbe Karte, sodass du statt einer Datei auch nur einen Seed weitergeben kannst.
Beim Generieren werden immer Höhen, Klippenstufen, Rampen, Klippentypen, Wasser und alle Pathing-Overrides ersetzt (nur der Rand wird wieder eingemalt, daher werden gemalte Overrides auch dann gelöscht, wenn Border auf None steht). Bodentexturierung, Detailschichten, Bäume, Dekor und Startpositionen werden nur ersetzt, wenn ihre Option aktiv ist: Paint Mode ist nicht KeepExisting; Vegetation bzw. Decor ist Enabled, das Terrain hat eine Vegetations- bzw. Dekorbibliothek, und Clear Existing ist eingeschaltet; Start Locations ist Enabled (Max Players wird aktualisiert, wenn Update Max Players eingeschaltet ist). Andernfalls bleibt der vorhandene Inhalt erhalten, und bei ausgeschaltetem Clear Existing kommen neue Bäume und Dekorobjekte zu den vorhandenen hinzu. Regionen, Marker und die Umgebungseinstellungen bleiben immer unberührt. Der gesamte Durchlauf ist ein einziger Undo-Schritt, du kannst also gefahrlos experimentieren.
Deine erste generierte Karte (Unity Editor)
- Wenn dein Projekt noch kein Tileset und keine Bibliotheken hat, führe einmal aus. Der Generator texturiert mit dem Tileset und platziert Pflanzen und Dekor aus den Bibliotheken des Terrains; ohne Bibliotheken überspringt er Vegetation und Dekor.
- Wähle dein RTS Terrain aus (oder erstelle eines mit ) und öffne .
- Klicke auf den Tab Map und klappe das Panel Generator auf.
- Wähle unter Style einen Stil (zum Beispiel Highlands) und klicke auf Apply Style Preset. Damit werden die Rausch-, Klippen-, Rampen- und Wasserwerte geladen, mit denen der Stil richtig aussieht.
- Wähle unter Symmetry eine Symmetrie: Rotate2 für eine faire 1v1-Karte, Rotate4 für 4 Spieler auf einer quadratischen Karte.
- Öffne Start Locations und setze Count auf die Anzahl der Spieler.
- Klicke auf Randomize Seed, dann auf Generate, und bestätige den Dialog.
- Lies den Bericht unter den Schaltflächen: Anzahl der Plateaustufen, Rampen, Startpositionen, Bäume und Dekorobjekte sowie die benötigte Zeit. Warnungen (zum Beispiel only 3 of 4 start locations found) stehen dort und in der Console.
- Nicht zufrieden? Drücke Ctrl+Z, klicke auf Randomize Seed und generiere erneut. Wenn dir das Ergebnis gefällt, speichere die Karte (Save in der Kopfzeile des Fensters oder im Inspector des RTS Terrain).
Die übrigen Schaltflächen des Panels:
| Schaltfläche | Funktion |
|---|---|
| Randomize Seed | Wählt einen neuen zufälligen Seed. Alles andere bleibt unverändert. |
| Apply Style Preset | Lädt die Rausch-, Klippen-, Rampen- und Wasserwerte des ausgewählten Stils. Seed, Symmetrie, Rand, Texturierung, Vegetation, Dekor und die Einstellungen der Startpositionen bleiben erhalten. |
| Reset | Setzt alle Generator-Einstellungen auf die Standardwerte des ausgewählten Stils zurück. |
| Generate | Fragt nach einer Bestätigung und generiert die Karte als einen einzigen Undo-Schritt mit dem Namen Generate Map. |
Die Generator-Einstellungen des Fensters werden in den Editor-Voreinstellungen gespeichert, sodass sie auch nach einem Neustart von Unity noch da sind.
Eine ganz neue Karte generieren (New Map Wizard)
erstellt eine neue Kartendatei. Wähle im Schritt Terrain die Option Generate statt Flat, lege ein Preset (die sechs Stile sowie jedes Asset vom Typ RTSMapGeneratorPreset in deinem Projekt, aufgelistet als Preset: name), einen Seed und eine Symmetrie fest und klicke auf Preview, um die Minikarte des Ergebnisses zu sehen, bevor etwas gespeichert wird. Der Assistent übernimmt die Spielerzahl aus seiner eigenen Spielereinstellung und schreibt sie in die Startpositionen.
Generieren im In-Game-Editor
Spieler können auch in einem fertigen Spiel Karten generieren. Öffne im In-Game-Editor in der Werkzeugleiste Map ▸ Generate.... Der Dialog bietet eine vereinfachte Auswahl an Optionen:
| Option | Entspricht |
|---|---|
| Style | Wendet das Stil-Preset an (setzt die Formoptionen darunter zurück). |
| Seed / Random | seed |
| Symmetry | symmetry |
| Terrain: Cliffs, Plateaus, Ramps, Ramp Count, Water, Lake Coverage, Island, Border | cliffs.enabled, cliffs.plateauCount, ramps.enabled, ramps.count, water.enabled, water.lakeCoverage, water.islandMode, border.mode |
| Content: Textures, Vegetation, Forests, Decor, Decor Count, Start Locations, Players | paint.mode, vegetation.enabled, vegetation.forestCoverage, decor.enabled, decor.count, startLocations.enabled, startLocations.count |
Drücke Enter (oder klicke auf Generate), um zu generieren, und Esc, um abzubrechen. Das Ergebnis ist ein einziger Undo-Schritt (Ctrl+Z), und der Dialog merkt sich seine Einstellungen, bis das Spiel beendet wird.
Stil-Presets
Ein Stil (TerrainStylePreset) verändert die Form des Basis-Höhenfelds und lädt über MapGeneratorSettings.ApplyPreset passende Standardwerte.
| Stil | Aussehen | Stufen / Plateaus | Rampen | Seenanteil |
|---|---|---|---|---|
| Plains | Größtenteils flaches Land mit ein paar niedrigen Plateaus. Eine klassische offene Melee-Karte. | 2–3 / 2 | 3 | 0.05 |
| Hills (Standard) | Sanfte Hügel (starke Feinhöhe) mit einigen Plateaus und Rampen. Ein guter Ausgangspunkt für alle Zwecke. | 2–3 / 2 | 4 | 0.06 |
| Highlands | Viele gestapelte Plateaus und Klippen mit Rampen dazwischen. | 2–6 / 5 | 8 | 0.04 |
| Islands | Landmassen, umgeben von Wasser (Inselmodus, tieferes Wasser, breitere Ufer). | 2–4 / 3 | 5 | 0.38 |
| Canyon | Ridged-Rauschen, geformt zu gewundenen Schluchten zwischen hohen Plateaus. | 2–5 / 3 | 6 | 0.03 |
| Arena | Eine tiefliegende zentrale Arena, umgeben von einem Ring aus Anhöhen. | 2–4 / 3 | 6 | 0.03 |
Eigene Presets speichern
Du hast Einstellungen gefunden, die dir gefallen? Speichere sie als Asset: erstellt ein RTSMapGeneratorPreset mit einer Description und einem vollständigen Satz Settings (es startet mit den Werten von Hills). Der New Map Wizard listet jedes Preset-Asset des Projekts auf, und dein Code kann preset.settings an den Generator übergeben (siehe Generieren aus Code). Presets sind normale Assets und können daher mit deinem Spiel ausgeliefert werden, zum Beispiel als Auswahl wie „Canyon für 2 Spieler“ oder „Inseln für 4 Spieler“ in einem Skirmish-Menü.
Symmetrie: faire Karten für Wettkampfspiele
Mit Symmetrie erzeugt der Generator einen Sektor der Karte und kopiert ihn, sodass jeder Spieler dasselbe Terrain, dieselben Rampen, dieselbe Menge an Bäumen und Dekor und eine gleichwertige Startposition erhält. Geneigte Dekorobjekte neigen sich in gespiegelten Kopien entsprechend in die gespiegelte Richtung.
| Symmetrie | Kopien | Typische Verwendung | Quadratische Karte nötig |
|---|---|---|---|
None | 1 | Kampagnenkarten oder frei gestaltete Karten. | Nein |
MirrorX | 2 | Spiegelung links ↔ rechts (2 Spieler, die sich horizontal gegenüberstehen). | Nein |
MirrorZ | 2 | Spiegelung oben ↔ unten. | Nein |
MirrorXZ | 4 | Spiegelung an beiden Achsen (4 Ecken). | Nein |
Rotate2 (Standard) | 2 | 180°-Punktsymmetrie, das klassische 1v1-Layout. | Nein |
Rotate4 | 4 | 90°-Rotationssymmetrie, das klassische Layout für 4 Spieler. | Ja (sonst Rückfall auf Rotate2) |
MirrorDiagonal | 2 | Spiegelung an der Diagonalen (x ↔ z). | Ja (sonst Rückfall auf MirrorX) |
Rotate2). Andernfalls warnt der Generator, dass das Layout nicht fair sein kann. MapGenerationReport.symmetryUsed verrät dir, welche Symmetrie tatsächlich verwendet wurde.Referenz der Generator-Einstellungen (MapGeneratorSettings)
Das Panel des Generators zeigt alle Felder von MapGeneratorSettings, in Ausklappbereiche gegliedert. Die Spalte Standard zeigt die Werte von new MapGeneratorSettings(); sie entsprechen dem Stil Hills. Die Gruppen Noise, Cliffs, Ramps und Water ändern sich mit dem gewählten Stil. Werte außerhalb des gültigen Bereichs werden vor dem Generieren von Validate() auf diesen Bereich begrenzt.
General
| Einstellung | Standard | Beschreibung |
|---|---|---|
Seed (seed) | 12345 | Startwert für den Zufallsgenerator. Derselbe Seed mit denselben Einstellungen erzeugt immer dieselbe Karte. |
Style (style) | Hills | Form des Terrains. Klicke auf Apply Style Preset (oder rufe ApplyPreset auf), um passende Standardwerte zu laden. |
Symmetry (symmetry) | Rotate2 | Generiert einen Sektor und kopiert ihn, damit die Karte fair ist (siehe Symmetrie). |
Noise (noise, GeneratorNoiseSettings)
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Scale | 85 m | Größe der großen Terrain-Formen in Metern. Größere Werte ergeben größere Plateaus. |
| Octaves | 4 | Anzahl der Rauschschichten (1–8). Mehr Oktaven fügen den Umrissen kleinere Details hinzu. |
| Persistence | 0.5 | Amplitudenfaktor pro Oktave (0.5 = jede Oktave ist halb so stark). |
| Lacunarity | 2 | Frequenzfaktor pro Oktave. |
| Ridged Amount | 0 | Überblendung hin zu Ridged-Rauschen (scharfe Grate und Schluchten). Der Stil Canyon verwendet 1. |
| Warp | 0.4 | Stärke des Domain Warping. Verbiegt die Umrisse zu natürlicheren, fließenden Formen. |
| Contrast | 1 | Höhere Werte verschieben mehr Fläche auf die niedrigsten und die höchsten Plateaus. |
| Smoothing | 2 | Weichzeichnungsdurchgänge, bevor das Feld in Klippenstufen umgewandelt wird (entfernt winzige, verrauschte Strukturen). |
| Fine Height Amplitude | 1.6 m | Amplitude des Feinhöhen-Rauschens (Hügel auf den Plateaus). |
| Fine Height Scale | 22 m | Strukturgröße des Feinhöhen-Rauschens. |
| Fine Height Octaves | 3 | Anzahl der Oktaven des Feinhöhen-Rauschens. |
| Cliff Edge Fade | 4 Zellen | Strecke, über die die Feinhöhe zu den Klippenkanten hin ausläuft, damit die Oberseiten der Klippen sauber bleiben. |
Cliffs (cliffs, GeneratorCliffSettings)
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Enabled | an | Generiert Klippenstufen. Ist die Einstellung aus, liegt die ganze Karte auf der Stufe von Min Level, und nur die Feinhöhen variieren. |
| Min Level | 2 | Klippenstufe des niedrigsten Plateaus (0–15). Stufe 2 lässt Platz, um darunter Seen zu graben. |
| Max Level | 3 | Klippenstufe des höchsten Plateaus. |
| Plateau Count | 2 | Anzahl unterschiedlicher Plateaustufen zwischen Min Level und Max Level. |
| Hysteresis | 0.04 | Band um jeden Stufenschwellwert (Anteil am Höhenbereich). Vertices innerhalb dieses Bands übernehmen die Stufe ihrer Nachbarn; so bleiben die Klippenlinien glatt. |
| Min Feature Width | 8 Zellen | Kleinste Breite jedes Plateaus, jeder Terrasse, jedes Tals und jedes Sees. Schmalere Teile (dünne Terrassen zwischen zwei Klippen, Grate, Gräben, winzige Plateaus) werden der benachbarten Stufe zugeschlagen, und jedes Plateau hält zur Klippe darunter einen Abstand von etwa drei Vierteln dieser Breite. Seen dürfen ein Viertel schmaler sein. Die Presets verwenden 6–10. |
| Cleanup Passes | 2 | Durchgänge, die isolierte Vertices und Spitzen entfernen. |
| Cliff Type | 0 | Klippentyp (Klippenschicht 0–3 des Tilesets), der in alle Vertices geschrieben wird. |
Ramps (ramps, GeneratorRampSettings)
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Enabled | an | Platziert Rampen zwischen benachbarten Plateaus. |
| Count | 4 | Anzahl der Rampen im generierten Sektor (die Symmetriekopien kommen noch hinzu). |
| Width | 3 Zellen | Breite einer Rampe entlang der Klippe. |
| Length | 2 Zellen | Länge einer Rampe pro Klippenstufe, quer zur Klippe. 2 ergibt eine sanfte Neigung. |
| Ensure Connectivity | an | Fügt bei Bedarf zusätzliche Rampen hinzu, damit jedes Plateau von jedem anderen Plateau aus erreichbar ist. |
| Min Spacing | 12 Zellen | Mindestabstand zwischen zwei Rampen. Überlappende Rampen werden ausgelassen. |
| Min Plateau Size | 40 | Die Erreichbarkeitsprüfung ignoriert Plateaus mit weniger Vertices. |
Water (water, GeneratorWaterSettings)
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Enabled | an | Generiert Seen oder Meer samt ihren Wasseroberflächen. |
| Sea Level Offset | −0.25 | Höhe der Wasseroberfläche in Klippenstufen, relativ zum niedrigsten Plateau (−0.9 bis 2). Negative Werte lassen das niedrigste Plateau trocken, positive Werte überfluten es. |
| Lake Coverage | 0.06 | Anteil der Karte (die tiefsten Bereiche), der zu Seen oder Meer wird (0–0.7). |
| Max Depth | 2.5 m | Tiefster Punkt der Seen unter der Wasseroberfläche. |
| Shore Width | 4 Zellen | Breite des abfallenden Ufers von der Uferlinie bis zur vollen Tiefe. |
| Island Mode | aus | Senkt das Terrain zu den Kartenrändern hin ab, sodass die Karte zu einer oder mehreren Inseln wird. Inselkarten bekommen immer mindestens 15 % Meer (der Bericht warnt, wenn der Anteil angehoben wurde). |
| Border Falloff | 0.25 | Im Island Mode: Breite des Abnahmebereichs zum Kartenrand hin, als Anteil der Kartengröße. |
Border (border, GeneratorBorderSettings)
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Mode | None | None: kein besonderer Rand. Unwalkable: malt nicht begehbare und nicht bebaubare Pathing-Overrides entlang des Rands. HighCliffs: errichtet entlang des Rands einen Ring hoher Klippen (ebenfalls nicht begehbar). |
| Width | 2 Zellen | Breite des Rands. |
| Cliff Level | 0 | Klippenstufe des Rands im Modus HighCliffs. 0 = automatisch (zwei Stufen über dem höchsten Plateau). |
Paint (paint, GeneratorPaintSettings)
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Mode | AutoPaint | KeepExisting: lässt die Bodentexturierung unverändert. FillBaseLayer: füllt die ganze Karte mit der unter Base Layer gewählten Schicht. AutoPaint: texturiert den Boden mit einem Auto-Paint-Profil anhand von Höhe, Neigung, Klippenstufe und Wasser. |
| Profile | keine | Ein RTSAutoPaintProfile (). Leer = eingebaute Standardregeln, die anhand der Schichtnamen des Tilesets zugeordnet werden. |
| Base Layer | 0 | Bodenschicht, die FillBaseLayer verwendet. |
Vegetation (vegetation, GeneratorVegetationSettings)
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Enabled | an | Generiert Detailschichten und Wälder (benötigt eine Vegetationsbibliothek auf dem Terrain). |
| Use Whole Library When Empty | an | Sind die Listen unten leer, werden alle Prototypen der Vegetationsbibliothek verwendet. |
| Clear Existing | an | Entfernt vor dem Generieren vorhandene Detailschichten und Bäume. |
| Details | leer | Zu generierende Detailschichten (Gras), siehe nächste Tabelle. |
| Tree Prototype Ids | leer | IDs der Baum-Prototypen, die für Wälder verwendet werden. |
| Forest Coverage | 0.22 | Anteil des geeigneten Bodens, der von Wald bedeckt wird. |
| Forest Noise Scale | 55 m | Strukturgröße des Wald-Rauschens. |
| Tree Spacing | 2.6 m | Abstand zwischen den Bäumen. |
| Tree Density | 0.85 | Wahrscheinlichkeit, dass ein freier Platz in einem Wald einen Baum erhält (niedrigere Werte ergeben lichte Wälder). |
| Max Tree Slope | 26° | Keine Bäume an steileren Hängen. |
| Start Location Clearance | 14 m | Hält diesen Abstand rund um die Startpositionen frei von Bäumen und Details. |
Jeder Eintrag in Details (GeneratorDetailEntry):
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Prototype Id | "" | ID des Detail-Prototyps in der Vegetationsbibliothek. |
| Density | 0.6 | Dichte in den bedeckten Bereichen (1 = maximale Dichte des Prototyps). |
| Coverage | 0.55 | Anteil des geeigneten Bodens, der bedeckt wird. |
| Noise Scale | 28 m | Strukturgröße des Rauschens für die Bedeckung. |
| Max Slope | 32° | Keine Details an steileren Hängen. |
Decor (decor, GeneratorDecorSettings)
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Enabled | an | Verteilt Dekor (benötigt eine Dekorbibliothek auf dem Terrain). |
| Use Whole Library When Empty | an | Ist die Liste unten leer, werden alle Prototypen der Dekorbibliothek verwendet. |
| Clear Existing | an | Entfernt vor dem Generieren vorhandenes Dekor. |
| Prototype Ids | leer | IDs der zu verteilenden Dekor-Prototypen. |
| Count | 60 | Anzahl der Dekorobjekte im generierten Sektor (die Symmetriekopien kommen noch hinzu). |
| Min Spacing | 3 m | Mindestabstand zwischen Dekorobjekten (ist der Abstand des Prototyps selbst größer, gilt dieser). |
| Max Slope | 30° | Kein Dekor an steileren Hängen. |
| Start Location Clearance | 12 m | Hält diesen Abstand rund um die Startpositionen frei von Dekor. |
Generiertes Dekor berücksichtigt die Einstellungen des jeweiligen Dekor-Prototyps: zufälliger Drehwinkel und Drehwinkel-Schrittweite, Skalierungsbereich, Tönungen, Varianten, Ausrichtung an der Normalen und maximale zufällige Neigung.
Start Locations (startLocations, GeneratorStartLocationSettings)
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Enabled | an | Platziert Startpositionen. Ist die Einstellung aus, bleiben die vorhandenen Startpositionen erhalten. |
| Count | 2 | Anzahl der Startpositionen (1–16). |
| Flat Radius | 5 Zellen | Radius um eine Startposition, der flach sein muss (eine Stufe, keine Klippen, Rampen oder Wasser). |
| Edge Margin | 8 Zellen | Mindestabstand zum Kartenrand. |
| Update Max Players | an | Schreibt den Wert von Count in die Einstellung Max Players der Karte (MapInfo.maxPlayers). |
Generieren per Code
Drei Einstiegspunkte decken alle Anwendungsfälle ab:
| Methode | Verwendung |
|---|---|
RTSMapGenerator.Generate(RTSTerrain, MapGeneratorSettings, RTSToolContext ctx = null) | Spielcode. Generiert in die Karte des Terrains, aktualisiert das Terrain und protokolliert Warnungen. Lässt sich nur rückgängig machen, wenn du den ctx eines Aufrufs von RTSEditSession.RunOperation übergibst. |
RTSMapGenerator.Generate(RTSEditSession, MapGeneratorSettings) | Editorwerkzeuge und Erweiterungen des In-Game-Editors: ein Undo-Schritt namens Generate Map. |
RTSMapGenerator.GenerateInto(MapData, settings, tileset, vegetation, decor) | Generieren in ein MapData-Objekt, das nicht angezeigt wird (Vorschauen, Stapelgenerierung, Tests). Es gibt keine Benachrichtigung und keinen Undo-Schritt. Gibt einen MapGenerationReport zurück. |
using RTSTerrainStudio;
using UnityEngine;
// Generating maps from game code, e.g. for a "Random map" button in a skirmish menu.
public class GeneratorExample : MonoBehaviour
{
[SerializeField] RTSTerrain terrain;
[SerializeField] RTSMapGeneratorPreset preset; // optional: settings saved as an asset
// Generates a random 4-player layout into the current map (the map size is kept). No undo.
public void GenerateRandomMap(int seed)
{
// Start from a saved preset or from a style preset, then change what you need.
MapGeneratorSettings settings = preset != null
? preset.settings.Clone() // never modify the asset itself
: MapGeneratorSettings.CreatePreset(TerrainStylePreset.Highlands);
settings.seed = seed; // same seed + settings + map size = same map
settings.symmetry = SymmetryMode.Rotate4; // needs a square map, otherwise Rotate2 is used
settings.startLocations.count = 4; // a multiple of the symmetry copies is fairest
settings.water.lakeCoverage = 0.1f;
settings.Validate(); // clamps every value into its valid range
RTSMapGenerator.Generate(terrain, settings); // generates, refreshes the terrain, logs warnings
}
// Generates a map in memory without showing it (previews, batch generation, tests).
public static MapData GenerateOffscreen(RTSTerrain template, int seed)
{
MapCreationSettings creation = MapCreationSettings.Default; // 128 x 128 cells of 2 m
creation.width = 192;
creation.height = 192;
MapData map = MapData.Create(creation);
MapGeneratorSettings settings = MapGeneratorSettings.CreatePreset(TerrainStylePreset.Islands);
settings.seed = seed;
// Pass the tileset and libraries so painting, vegetation and decor can be generated.
MapGenerationReport report = RTSMapGenerator.GenerateInto(map, settings,
template.Tileset, template.VegetationLibrary, template.DecorLibrary);
foreach (string warning in report.warnings) Debug.LogWarning(warning);
Debug.Log(report); // plateaus, ramps, start locations, trees, decor and time
// You own the map: Dispose() it, or give it to a terrain with terrain.SetMap(map).
return map;
}
}
MapGenerationReport enthält symmetryUsed, plateauLevels, ramps, startLocations, detailLayers, trees, decor, milliseconds und die Liste warnings; ToString() liefert eine einzeilige Zusammenfassung. RTSMapGenerator.GeneratedLayers enthält alle MapLayers-Flags, die der Generator schreibt. Genau diese Flags übergibst du an RunOperation, wenn du GenerateInto selbst kapselst.
Heightmap-Import und -Export
Das Panel Heightmap im Tab Map importiert und exportiert die Terrain-Höhe als 16-Bit-PNG oder 16-Bit-RAW. Damit übernimmst du Terrain aus World Machine, Gaea, Photoshop oder einer Quelle realer Höhendaten, oder du gibst die Karte an ein solches Werkzeug weiter und holst sie anschließend zurück.
Wie Heightmaps auf das Terrain abgebildet werden
- Eine Heightmap deckt das Vertex-Raster ab: Eine Karte mit 256 × 256 Zellen hat 257 × 257 Vertices, eine ideale Heightmap hat also 257 × 257 Pixel. Bilder jeder anderen Größe werden bilinear neu abgetastet.
- Pixelwerte werden nach
height = offset + value / 65535 × scaleumgerechnet. 8-Bit-Bilder funktionieren ebenfalls, sie haben nur weniger Abstufungen. - Der Standardwert von Height Scale deckt alle 16 Klippenstufen ab:
16 × Cliff Level Height(32 m bei der Standard-Stufenhöhe von 2 m).HeightmapIO.DefaultHeightScale(map)liefert diesen Wert. - Zeile 0 einer RAW-Datei und die unterste Zeile eines PNG entsprechen Z = 0 (der ersten Vertex-Reihe). Erscheint deine Karte gespiegelt, aktiviere Flip Vertically.
- Jeder Import ist ein Undo-Schritt namens Import Heightmap.
Importieren
- Öffne den Tab Map und klappe das Panel Heightmap auf.
- Wähle unter Mode den Importmodus (Tabelle unten). Für eine typische externe Heightmap verwende Cliff Levels And Fine Heights; um Hügel auf vorhandene Plateaus zu setzen, verwende Fine Heights Only.
- Belasse Height Scale auf 0 (automatisch), es sei denn, du weißt, wie vielen Metern dein Bild entspricht.
- Klicke auf Import PNG… (PNG, JPG oder EXR; Graustufen mit 8 oder 16 Bit, verwendet wird der Rotkanal) oder auf Import RAW… (
.raw/.r16, 16 Bit). - Prüfe das Ergebnis. Drücke Ctrl+Z, um den Import rückgängig zu machen, und probiere einen anderen Modus oder eine andere Skalierung.
| Importmodus | Schreibt | Beschreibung |
|---|---|---|
| Fine Heights Only | Höhen | Nur die Feinhöhe (Feinhöhe = Wert × Skalierung + Offset). Die Klippenstufen bleiben erhalten, und ihre Höhe kommt hinzu; das Bild setzt also Hügel auf die vorhandenen Plateaus. |
| Cliff Levels And Fine Heights | Höhen, Klippenstufen | Das Bild ist die absolute Bodenhöhe. Es wird in Klippenstufen (auf die nächste Stufe gerundet, isolierte Vertices bereinigt) und Feinhöhen aufgeteilt, sodass die Bodenhöhe exakt dem Bild entspricht. Am besten geeignet für terrassierte Heightmaps; sanfte Hänge, die über eine Stufengrenze verlaufen, werden zu kleinen Klippen. |
| Absolute Heights Keep Levels | Höhen | Das Bild ist die absolute Bodenhöhe, aber die vorhandenen Klippenstufen bleiben erhalten (Feinhöhe = Höhe − Stufe × Stufenhöhe). Damit importierst du einen in einem externen Werkzeug bearbeiteten Export mit der Quelle Ground Height erneut, ohne die Klippen zu verändern. |
| Importeinstellung | Standard | Beschreibung |
|---|---|---|
| Mode | siehe oben | Wie das Bild in Stufen und Höhen umgewandelt wird. |
| Height Scale | 0 (automatisch) | Meter, die dem vollen 16-Bit-Bereich entsprechen. 0 = 16 × Stufenhöhe. |
| Height Offset | 0 | Meter, die zu jeder importierten Höhe addiert werden. |
| Flip Vertically | aus | Spiegelt das Bild entlang Z (aktiviere die Option, wenn die Karte gespiegelt erscheint). |
| RAW Width / RAW Height | 0 | Werte pro Zeile und Anzahl der Zeilen von RAW-Dateien. 0 = aus der Dateigröße schätzen (nur bei quadratischen Dateien). |
| RAW Little Endian | an | Byte-Reihenfolge von RAW-Dateien. Windows und die meisten Werkzeuge schreiben Little Endian. |
Exportieren
| Exporteinstellung / Schaltfläche | Standard | Beschreibung |
|---|---|---|
| Source | Ground Height | Ground Height: Klippenstufen plus Feinhöhe (absolut). Fine Heights: nur die Feinhöhe über den Stufen. |
| Height Scale | 0 (automatisch) | Meter, die dem Wert 65535 entsprechen. |
| Height Offset | 0 | Höhe, die als 0 geschrieben wird. |
| RAW Little Endian | an | Byte-Reihenfolge exportierter RAW-Dateien. |
| Export PNG… | 16-Bit-Graustufen-PNG. | |
| Export RAW… | 16-Bit-RAW-Datei. | |
| Export Splat PNG… | Ein Bild der Bodentexturierung (Farben der dominanten Schicht) für die Dokumentation oder externe Werkzeuge. |
Export und Reimport: Exportiere mit Ground Height, bearbeite das Bild extern und importiere es mit Absolute Heights Keep Levels und derselben Skalierung sowie demselben Offset erneut.
Heightmaps per Code
HeightmapIO bietet ImportPng, ImportRaw16 und ImportHeights (normalisierte Werte als float[] oder NativeArray<float>, zum Beispiel aus deinem eigenen Rauschen) für Terrains, ApplyHeights für ein MapData ohne Terrain, ExportPng16, ExportRaw16 und ExportSplatPng (Modi DominantLayerColor und SplatWeights) sowie TryGuessRawSize und GetImportLayers(mode) für die Undo-Aufzeichnung. Jeder Exporter hat eine Überladung mit byte[] und eine mit Dateipfad.
using RTSTerrainStudio;
using UnityEngine;
// Heightmap import and export from code.
public static class HeightmapExample
{
public static bool Import(RTSTerrain terrain, string pngPath)
{
// Imports need a height scale > 0. DefaultHeightScale = 16 cliff levels (all levels + headroom).
float scale = HeightmapIO.DefaultHeightScale(terrain.Map);
var options = new HeightmapImportOptions { heightOffset = 0f, flipZ = false };
// The image is resampled to the vertex grid (cells + 1 per side) and the terrain is refreshed.
// Pass an RTSToolContext (inside RTSEditSession.RunOperation) as the 5th argument to make it undoable.
return HeightmapIO.ImportPng(terrain, pngPath, HeightmapImportMode.CliffLevelsAndFineHeights, scale,
null, options);
}
public static bool Export(RTSTerrain terrain, string pngPath)
{
// 16-bit grayscale PNG of the absolute ground height. Scale 0 = DefaultHeightScale.
return HeightmapIO.ExportPng16(terrain.Map, pngPath, 0f, 0f, HeightmapExportSource.GroundHeight);
}
}
heightScale unverändert. Übergib immer einen Wert größer als 0, zum Beispiel HeightmapIO.DefaultHeightScale(map). Bei Exporten steht 0 für automatisch.Backen der Minikarte
RTSMinimapBaker rendert eine Draufsicht der Karte: Bodenfarben, Reliefschattierung, Klippen, nach Tiefe eingefärbtes Wasser, Bäume, Dekor, Startpositionen und Regionen. Überall im Paket wird derselbe Baker verwendet:
- das Panel Minimap im Tab Map (Vorschau und PNG-Export),
- die Vorschau von
.rtsmap-Assets im Project-Fenster und im Inspector sowie die Vorschau des New Map Wizard, - der Kartenbrowser des In-Game-Editors (neben jeder Karte wird ein kleines Vorschaubild gespeichert, gesteuert über die Einstellung Write Preview Images von
RTSRuntimeEditor), - das Beispiel
RTSMinimapUI(Minikarte im Spiel mit Nebel des Krieges, Einheitenpunkten, Kamerarahmen, Klick zum Verschieben der Kamera und Rechtsklick zum Bewegen von Einheiten). Ändert sich die Karte, backt es die Minikarte automatisch und gedrosselt neu.
Backen im Editor
- Öffne den Tab Map und klappe das Panel Minimap auf.
- Wähle unter Size eine Größe (128, 256, 512 oder 1024 px für die längere Seite; nicht quadratische Karten behalten ihr Seitenverhältnis).
- Passe bei Bedarf die Bake Options an (Tabelle unten).
- Klicke auf Bake Preview, um das Ergebnis im Panel zu sehen, oder auf Save PNG…, um es in eine Datei zu schreiben, zum Beispiel für einen Ladebildschirm oder ein Kartenauswahlmenü.
Backoptionen (MinimapBakeOptions)
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Ground Mode | BlendedLayers | BlendedLayers mischt die Farben aller gemalten Schichten; DominantLayer verwendet pro Pixel die stärkste Schicht (schärfer, im Stil klassischer RTS-Spiele). Die Schichtfarben stammen aus dem Tileset (die Minikartenfarbe der Schicht oder die Durchschnittsfarbe ihrer Textur). |
| Level Brightness | 0.05 | Helligkeitsänderung pro Klippenstufe relativ zu Stufe 2 (höhere Plateaus wirken heller). |
| Hill Shading / Strength | an / 1 | Verdunkelt Hänge, die vom Licht abgewandt sind. |
| Light Direction | (−0.55, 0.75, 0.45) | Richtung zum Licht in kartenlokalen Koordinaten (x = Osten, y = oben, z = Norden). |
| Use Map Sun Direction | aus | Verwendet die Sonnenrichtung aus der Umgebung der Karte statt Light Direction. |
| Draw Cliffs / Cliff Color / Cliff Opacity | an / schwarz / 0.8 | Färbt Klippenzellen ein. Schwarz = Klippenfarbe des Tilesets verwenden. |
| Draw Water | an | Wasser, nach Tiefe eingefärbt (Wasserprofil des Tilesets). |
| Draw Trees / Tree Color / Tree Dot Size | an / dunkelgrün / 0.75 px | Bäume als Punkte (Werte unter 1 zeichnen einzelne Pixel). |
| Draw Decor / Decor Color / Decor Dot Size | aus / braun / 0.75 px | Dekorobjekte als Punkte. |
| Draw Start Locations / Start Location Size | an / 4 px | Marker in Spielerfarbe. |
| Scale Markers | an | Skaliert die Markergrößen mit der Texturgröße (Referenzgröße 256). |
| Player Colors | 16 klassische RTS-Spielerfarben | Farbe pro Spielerindex. Für fehlende Einträge gilt MinimapBakeOptions.DefaultPlayerColors. |
| Draw Regions | aus | Zeichnet die Umrisse der Regionen in ihrer Regionsfarbe. |
Minikarten per Code
RTSMinimapBaker.Bake(terrain, size, options) gibt eine neue, lesbare Texture2D im sRGB-Format zurück (zerstören musst du sie selbst). Update(existing, terrain, size, options) backt erneut in eine vorhandene Textur und erstellt nur dann eine neue, wenn sich die alte nicht wiederverwenden lässt. BakePng(terrain, size) gibt direkt PNG-Bytes zurück, und die Überladungen für MapData backen Karten, die gerade nicht angezeigt werden.
using RTSTerrainStudio;
using UnityEngine;
using UnityEngine.UIElements;
// Keeps a minimap image up to date in a UI Toolkit Image named "minimap".
public class MinimapExample : MonoBehaviour
{
[SerializeField] RTSTerrain terrain;
[SerializeField] UIDocument document;
[SerializeField] int size = 256; // longer side in pixels
Texture2D minimap;
void OnEnable()
{
if (terrain == null) return;
terrain.MapLoaded += OnMapUpdated;
terrain.ChangesProcessed += OnChangesProcessed;
if (terrain.Map != null) Refresh();
}
void OnDisable()
{
if (terrain == null) return;
terrain.MapLoaded -= OnMapUpdated;
terrain.ChangesProcessed -= OnChangesProcessed;
}
void OnMapUpdated(RTSTerrain t) => Refresh();
// Called after every processed edit. While someone paints, throttle this (the RTSMinimapUI sample
// waits a moment after the last change and limits the bakes per second).
void OnChangesProcessed(RTSTerrain t, System.Collections.Generic.IReadOnlyList<DirtyRegion> regions) => Refresh();
void Refresh()
{
var options = new MinimapBakeOptions { drawDecor = true, hillShadingStrength = 1.2f };
// Update reuses the texture when it can. When it returns a new texture, destroy the old one.
Texture2D result = RTSMinimapBaker.Update(minimap, terrain, size, options);
if (minimap != null && result != minimap) Destroy(minimap);
minimap = result;
Image target = document != null ? document.rootVisualElement?.Q<Image>("minimap") : null;
if (target != null) target.image = minimap;
}
void OnDestroy()
{
if (minimap != null) Destroy(minimap);
}
}
Kartenvalidierung
Der Kartenvalidator (MapValidator) prüft eine Karte auf Probleme, die leicht zu übersehen sind: fehlende Texturen, unerreichbare Startpositionen, defekte Objektverweise und Performance-Grenzen. Führe ihn aus, bevor du eine Karte auslieferst oder für Spieler veröffentlichst.
- Öffne den Tab Map und klappe das Panel Validation auf.
- Klicke auf Validate Map. Die Zusammenfassung zeigt die Anzahl der Fehler, Warnungen und Infos oder die Meldung No problems found.
- Klicke auf ein Problem, um seine Position in der Scene View anzuzeigen (bei Problemen, die zu einem Ort gehören, etwa zu einer Startposition).
- Behebe die Probleme und validiere erneut.
Was geprüft wird
| Bereich | Prüfung | Schweregrad |
|---|---|---|
| Tileset | Kein Tileset zugewiesen (das Terrain wird mit neutralen Ersatzfarben gerendert) | Warning |
| Das Tileset hat keine Bodenschichten | Warning | |
| Die Karte speichert keine Tileset-ID (wird beim Speichern mit einem Tileset automatisch gesetzt) | Info | |
| Die Karte wurde für ein anderes Tileset erstellt als das zugewiesene | Warning | |
| Geometrie | Vertices mit ungültigen Höhen (NaN oder unendlich) | Error |
| Klippenzellen verwenden einen Klippentyp, den das Tileset nicht definiert | Warning | |
| Die Karte hat Klippen, aber das Tileset hat keine Klippenschichten | Warning | |
| Bodentexturierung | Splat-Texel, deren Gewichtungen nicht zusammen 255 ergeben (beschädigte Texturierung) | Warning |
| Eine gemalte Bodenschicht ist im Tileset nicht vorhanden | Warning | |
| Eine gemalte Bodenschicht hat keine Albedo-Textur | Info | |
| Startpositionen | Die Karte hat keine Startpositionen | Error |
| Weniger Startpositionen als Max Players | Warning | |
| Ein Spieler hat mehr als eine Startposition | Warning | |
| Eine Startposition liegt außerhalb der Karte | Error | |
| Eine Startposition liegt auf einer Klippenkante | Warning | |
| Eine Startposition ist von Tiefwasser bedeckt / liegt in Flachwasser | Error / Info | |
| Zwei Startpositionen liegen sehr nah beieinander (näher als Min Start Distance Cells) | Warning | |
| Eine Startposition ist nicht begehbar | Error | |
| Erreichbarkeit | Ein Spieler kann nicht zu einem anderen Spieler laufen (Flächenfüllung über die Pathing-Karte, beginnend auf der eigenen Klippenstufe der Startposition) | Error |
| Ein großer Teil des Kartenrands ist begehbar (erwäge einen nicht begehbaren Rand) | Info | |
| Bäume, Dekor, Details | Bäume, Dekor oder Detailschichten sind vorhanden, aber es ist keine Vegetations- bzw. Dekorbibliothek zugewiesen | Warning |
| Objekte verweisen auf einen Prototyp-Index, der nicht in der Palette der Karte enthalten ist | Error | |
| Objekte oder Detailschichten verwenden einen Prototyp, der in der Bibliothek fehlt | Warning | |
| Ein Prototyp hat kein Prefab (seine Objekte sind unsichtbar) | Warning | |
| Objekte außerhalb der Karte, doppelte Instanz-IDs oder ein Zähler für Instanz-IDs, der kleiner ist als vorhandene IDs | Warning | |
| Regionen | Eine Region hat keine Fläche; mehrere Regionen haben denselben Namen (Spielcode, der Regionen über ihren Namen sucht, findet womöglich die falsche) | Info |
| Performance | Zu viele Bäume, Dekorobjekte oder Detailschichten | Warning |
| Zu viele Bäume oder Dekorobjekte im Spawn-Modus GameObject | Warning | |
| Große Karte (Speicherverbrauch und Build-Zeiten) | Info |
Schwellenwerte (MapValidationSettings)
| Einstellung | Standard | Beschreibung |
|---|---|---|
maxTrees | 20000 | Übersteigt die Anzahl der Bäume diesen Wert, wird eine Performance-Warnung angezeigt. |
maxGameObjectTrees | 1500 | Übersteigt die Anzahl der Bäume im Spawn-Modus GameObject diesen Wert, wird eine Warnung angezeigt. |
maxDecor | 6000 | Übersteigt die Anzahl der Dekorobjekte diesen Wert, wird eine Warnung angezeigt. |
maxGameObjectDecor | 2000 | Übersteigt die Anzahl der Dekorobjekte im Spawn-Modus GameObject diesen Wert, wird eine Warnung angezeigt. |
maxDetailLayers | 8 | Übersteigt die Anzahl der Detailschichten diesen Wert, wird eine Warnung angezeigt. |
largeMapSize | 512 | Karten, die größer als dieser Wert sind (Zellen pro Seite), erhalten eine Info zu Speicherverbrauch und Build-Zeiten. |
minStartDistanceCells | 12 | Startpositionen, die näher als dieser Wert beieinanderliegen, erzeugen eine Warnung. |
walkableEdgeFraction | 0.1 | Übersteigt der Anteil begehbarer Zellen im äußeren Zellring diesen Wert, wird die Info zum begehbaren Kartenrand angezeigt. |
checkReachability | true | Prüft, ob alle Startpositionen einander erreichen können. |
Validieren per Code
MapValidator.Validate(terrain) gibt eine List<MapIssue> zurück. Jedes MapIssue enthält einen Schweregrad Severity (Info, Warning, Error), eine Meldung Message, optional eine WorldPosition und die betroffenen Layers. Überladungen akzeptieren MapValidationSettings oder direkt ein MapData-Objekt samt Tileset, Bibliotheken und Pathing-Karte.
using RTSTerrainStudio;
using UnityEngine;
// Checks a map before it is published (for example in a "Publish map" button of your game).
public static class ValidationExample
{
public static bool CanPublish(RTSTerrain terrain)
{
// Optional thresholds; null uses the defaults.
var settings = new MapValidationSettings { maxTrees = 10000, checkReachability = true };
var issues = MapValidator.Validate(terrain, settings);
foreach (MapIssue issue in issues)
{
// WorldPosition is set for issues that belong to a place on the map (e.g. a start location).
string where = issue.WorldPosition.HasValue ? " at " + issue.WorldPosition.Value : "";
switch (issue.Severity)
{
case MapIssueSeverity.Error: Debug.LogError(issue.Message + where, terrain); break;
case MapIssueSeverity.Warning: Debug.LogWarning(issue.Message + where, terrain); break;
default: Debug.Log(issue.Message + where, terrain); break;
}
}
return MapValidator.Count(issues, MapIssueSeverity.Error) == 0;
}
}
Integrationen
RTS Terrain Studio funktioniert eigenständig: Es bringt einen eingebauten Burst-Pathfinder, einen eigenen Nebel des Krieges und Beispiel-Gameplay mit. Drei optionale Integrationen verbinden es mit beliebten Paketen. Sie gehören zum Paket (Ordner Integrations) und schalten sich selbst ein, sobald das benötigte Paket installiert ist.
| Integration | Voraussetzung | Was du bekommst | Menü für die Demoszene |
|---|---|---|---|
| Mirror | Mirror 96 oder neuer | Online-Spiel auf jeder Karte: Karten-Streaming zu den Clients, serverautoritative Einheiten, Nebel des Krieges pro Team, Interest Management gegen Maphacks. | |
| A* Pathfinding Project | A* Pathfinding Project Pro 5.4.4 oder neuer, unter 6.0 | Grid Graphs, die aus der Pathing-Karte erzeugt und inkrementell aktualisiert werden; Einheiten bewegen sich mit AIPath und lokaler Kollisionsvermeidung per RVO. | |
| Unity NavMesh | AI Navigation (com.unity.ai.navigation) 2.0 oder neuer | Ein NavMesh, das aus den Terrain-Chunks und der Pathing-Karte gebaut und während der Kartenbearbeitung aktuell gehalten wird; Einheiten folgen NavMesh-Pfaden oder bewegen sich per NavMeshAgent. |
Das Fenster Welcome () zeigt den Status jeder Integration: Active (kompiliert und bereit), Installed (die Abhängigkeit wurde gefunden, aber die Integration ist nicht kompiliert, meist wegen einer nicht passenden Version) oder Not installed.
So funktionieren die optionalen Assemblies
Jede Integration liegt in eigenen Assembly Definitions. Unity kompiliert eine Assembly nur, wenn alle ihre Define Constraints erfüllt sind. Die Symbole stammen aus zwei Quellen:
- Version Defines (
versionDefinesin der.asmdef): Die Assembly definiert für sich selbst ein Symbol, wenn ein Paket mit passender Version installiert ist. A* und NavMesh nutzen das und reagieren so automatisch darauf, dass das Paket installiert oder entfernt wird. - Scripting Define Symbols der Player Settings: Mirror wird meist als Asset-Store-Ordner importiert, nicht als Paket. Deshalb verwendet seine Assembly das Symbol
MIRROR, das Mirror selbst zu den Player Settings hinzufügt.
| Assembly | Version Define | Define Constraints |
|---|---|---|
RTSTerrainStudio.Integrations.Mirror und .Mirror.Editor | keine | MIRROR |
RTSTerrainStudio.Integrations.Mirror.DefineCleanup.Editor | keine | keine (wird immer kompiliert; entfernt veraltete Mirror-Symbole, siehe Integrationen entfernen) |
RTSTerrainStudio.Integrations.AStar und .AStar.Editor | com.arongranberg.astar in [5.4.4,6.0.0) → RTS_ASTAR | RTS_ASTAR, !RTS_ASTAR_DISABLED |
RTSTerrainStudio.Integrations.NavMesh und .NavMesh.Editor | com.unity.ai.navigation 2.0.0 oder neuer → RTS_AI_NAVIGATION | RTS_AI_NAVIGATION |
Das Ergebnis: Ohne die Abhängigkeit wird die Integration schlicht übersprungen, nichts geht kaputt, und die Einheiten verwenden den eingebauten Pathfinder. Für A* und NavMesh musst du keine Defines von Hand verwalten.
RTSUnit) fragt einen IRTSUnitPathProvider nach ihren Pfaden. Standardmäßig wird der eingebaute Provider verwendet; die Provider für A* und NavMesh registrieren sich als RTSUnitPathProviderRegistry.Default, wenn Make Default eingeschaltet ist, oder du weist einzelnen Einheiten im Feld Path Provider einen Provider zu. Deine eigene Wegfindungslösung lässt sich auf dieselbe Weise einbinden, indem sie IRTSUnitPathProvider implementiert.RTSTerrainStudio.Samples (für RTSUnit sowie die Auswahl- und Befehls-Controller). Behalte den Ordner Samples/Scripts, solange du eine Integration verwendest.Multiplayer mit Mirror
Spiele Karten von RTS Terrain Studio online mit Mirror (Version 96 oder neuer):
- Karten-Streaming: Der Server sendet seine Karte an jeden Client, der beitritt. So können Spieler Partien auf Karten beitreten, die sie selbst nicht haben, einschließlich Karten aus dem In-Game-Editor. Heruntergeladene Karten werden zwischengespeichert, sodass die nächste Partie auf derselben Karte sofort startet.
- Serverautoritative Einheiten: Wegfindung und Bewegung laufen nur auf dem Server. Clients senden Befehle, und der Server prüft, ob die Einheiten dem Spieler gehören.
- Nebel des Krieges pro Team auf jedem Client.
- Interest Management gegen Maphacks: Einheiten, die der Nebel des Krieges verbirgt, werden nie an gegnerische Clients gesendet.
Schnellstart: die Demoszene
- Importiere Mirror (Asset Store oder GitHub). Mirror fügt das Scripting Define Symbol
MIRRORhinzu, und die Integration wird kompiliert. - Führe aus (einmal pro Projekt).
- Führe aus. Der Befehl erstellt in
Integrations/Mirror/Demo: die SzeneRTS Mirror Demo.unity, die PrefabsPrefabs/RTS Network Unit.prefab(eine Platzhalter-Kapsel mitRTSUnit,RTSNetworkUnit,NetworkIdentityundNetworkTransformReliable) undPrefabs/RTS Network Player.prefab, die Einheitenmaterialien und – wenn das Projekt keine Karte mit mindestens zwei Startpositionen hat – eine generierte Karte für 4 Spieler namensRTS Mirror Demo Map.rtsmap. - Wenn du gefragt wirst, lasse den Builder das Renderer Feature für den Nebel des Krieges hinzufügen und die Szene an die erste Stelle der Szenenliste des Builds setzen.
- Drücke Play und klicke im Mirror-HUD auf Host (Server + Client). Du spielst als Team 1 (rot), und deine Einheiten spawnen an deiner Startposition.
- Starte eine zweite Instanz (siehe unten), klicke auf Client und behalte die Adresse
localhostbei. Der Client lädt die Karte herunter (dabei wird ein Fortschrittsbalken angezeigt), tritt als Team 2 (blau) bei und erhält eigene Einheiten. Gegnerische Einheiten erscheinen erst, wenn deine Einheiten sie sehen können.
Host und Client testen
| Methode | Vorgehen |
|---|---|
| Player-Build (am zuverlässigsten) | Öffne . Stelle sicher, dass RTS Mirror Demo die erste Szene ist, und erstelle dann den Build. Starte den Build zweimal oder den Build und den Editor. Klicke in der einen Instanz auf Host, in der anderen auf Client. |
| ParrelSync (kostenlos, GitHub) | Öffne , erstelle einen Klon und öffne ihn in einem neuen Editor. Drücke in beiden Editoren Play und klicke dann im Original auf Host, im Klon auf Client. Der Klon teilt sich den Assets-Ordner mit dem Original. Führe den Demo-Builder deshalb nur im Original aus. |
Multiplayer Play Mode (com.unity.multiplayer.playmode) | Öffne . Aktiviere einen virtuellen Spieler und drücke Play. Klicke im Haupteditor auf Host und im virtuellen Spieler auf Client. |
| Über ein Netzwerk | Gib die IP-Adresse des Hosts in das Adressfeld des Clients ein. KCP verwendet UDP-Port 7777; öffne ihn in der Firewall des Hosts. |
| Dedizierter Server | Erstelle den Build mit einem Build-Profil Dedicated Server. Der Network Manager verwendet Headless Start Mode = Auto Start Server, daher startet der Server von selbst. Clients verbinden sich über Client. |
Jeder Rechner speichert heruntergeladene Karten in Application.persistentDataPath/MapCache. Verwende oder , um einen frischen Download zu testen. Der Editor und ein Windows-Player desselben Projekts teilen sich diesen Ordner.
RTSNetworkMapSync des Clients Reuse Loaded Map und Use Cache aus, oder ändere die Karte auf dem Host, bevor du hostest (zum Beispiel mit dem In-Game-Editor).Multiplayer zu deiner eigenen Szene hinzufügen
| Komponente | Hinzufügen zu | Zweck |
|---|---|---|
RTSNetworkManager | Network-Manager-Objekt | Weist Teams zu, wartet, bis der Client die Karte hat, und spawnt dann den Spieler und seine Starteinheiten an seiner Startposition. Despawnt die Einheiten, wenn ein Spieler die Partie verlässt. |
RTSNetworkMapSync | Dasselbe Objekt (wird automatisch hinzugefügt) | Streamt die Karte in Chunks zu den Clients, mit Prüfsumme, Cache und Fortschritts-Events. |
RTSFogOfWarInterestManagement | Dasselbe Objekt | Interest Management von Mirror: Jeder Client erhält nur die Einheiten, die sein Team sehen kann. |
RTSNetworkFogOfWar | Dasselbe Objekt | Konfiguriert den RTSFogOfWarManager: Der Server berechnet alle Teams, ein Client nur sein eigenes Team. |
RTSNetworkStatusUI | Dasselbe Objekt (optional) | Fortschrittsbalken für den Download und „You are Player N“ (IMGUI). |
RTSNetworkPlayer | Spieler-Prefab | SyncVar für das Team. Richtet den lokalen Spieler ein (Nebel-Team, Auswahl, Kamera) und sendet Befehle als Commands. |
RTSNetworkUnit | Einheiten-Prefabs | SyncVar für das Team. Die Bewegung läuft nur auf dem Server; Clients zeigen die Einheit nur an. |
RTSNetworkCommandRelay | Wird auf Clients automatisch hinzugefügt | Sendet die Befehle des Beispiel-Controllers RTSCommandController an den Server. |
- Baue eine normale Szene mit RTS Terrain Studio auf: ein
RTSTerrainmit einer Karte, die Startpositionen hat, einRTSFogOfWarManager, einRTSCameraController, einRTSSelectionManagerund einRTSCommandController. Füge keinRTSDemoGamehinzu: Die Einheiten spawnt stattdessen der Network Manager. - Erstelle ein leeres Objekt und füge
KcpTransport,RTSNetworkManager(er fügtRTSNetworkMapSynchinzu),RTSFogOfWarInterestManagement,RTSNetworkFogOfWarund bei BedarfNetworkManagerHUDhinzu. Weise den Transport dem Feld Transport des Managers zu. - Erstelle ein Spieler-Prefab mit
NetworkIdentityundRTSNetworkPlayerund weise es dem Feld Player Prefab zu. - Erstelle ein Einheiten-Prefab mit
NetworkIdentity,RTSUnit,RTSNetworkUnitundNetworkTransformReliable(Sync Direction Server To Client, Sync Scale aus). Weise es dem Feld Unit Prefab zu; es wird automatisch zu den spawnbaren Prefabs hinzugefügt. Jedes andere vernetzte Prefab, das du spawnst, muss in Registered Spawnable Prefabs eingetragen sein. - Lege Units Per Player fest (Standard 5) und bei Bedarf Max Players Override (0 = so viele Spieler, wie die Karte Startpositionen hat).
- Trage KI- oder andere Logikkomponenten der Einheiten-Prefabs (zum Beispiel
RTSWanderAIoder deine eigenen KI-Skripte) in Server Only Behaviours vonRTSNetworkUnitein, damit Clients sie nicht ausführen.
| Einstellung | Standard | Beschreibung |
|---|---|---|
| RTSNetworkManager | ||
| Terrain | leer | Terrain, auf dem gespielt wird. Leer = RTSTerrain.Main. |
| Unit Prefab | leer | Starteinheit jedes Spielers (benötigt NetworkIdentity, RTSUnit und RTSNetworkUnit). |
| Units Per Player | 5 | Anzahl der Starteinheiten (0–64). |
| Max Players Override | 0 | Weniger Spieler, als die Karte Plätze hat (0 = alle Startpositionen). |
| Unit Spacing | 2 m | Abstand zwischen den Starteinheiten. |
| Spawn Offset | 4 m | Abstand der ersten Reihe der Starteinheiten von der Startposition, in Richtung Kartenmitte. |
| Despawn Units On Disconnect | an | Zerstört die Einheiten eines Spielers, der die Partie verlässt. |
| Reserve Team Of Kept Units | an | Wenn die Einheiten bleiben: Hält den Team-Platz reserviert, solange noch eine von ihnen lebt. |
| Reject Disconnect Delay | 0.5 s | Sekunden zwischen der Ablehnungsnachricht und dem Trennen der Verbindung, damit die Nachricht ankommt. |
| RTSNetworkUnit | ||
| Server Only Behaviours | leer | Behaviours, die Clients deaktivieren, weil der Server die Logik der Einheit ausführt. |
| RTSNetworkPlayer | ||
| Jump Camera To Start | an | Bewegt die RTS-Kamera zu Spielbeginn zur Startposition. |
| Create Command Relay | an | Reine Clients: Fügt ein RTSNetworkCommandRelay hinzu, wenn die Szene keines hat, damit der Beispiel-Befehls-Controller seine Befehle an den Server sendet. |
| Max Units Per Command, Max Commands Per Second | 256, 20 | Serverseitige Validierung von Befehlen (siehe Serverautoritative Einheiten). |
| Formation (Server) | - | Formationseinstellungen, die der Server verwendet; halte sie mit dem RTSCommandController synchron. |
Spielbeitritt und Karten-Streaming
client connects
server: team = first free player slot of the map (rejected when the map is full)
server -> client: map header (name, size, CRC-32)
client: same map already loaded, or in the cache? yes -> "no data needed"
no -> download the chunks
client: check the CRC-32 -> terrain.SetMap(RTSMapSerializer.Deserialize(bytes)) -> "map ready"
client: NetworkClient.Ready() + AddPlayer()
server: spawns the RTSNetworkPlayer and the starting units (owned by the connection)
Der Server ignoriert Ready und AddPlayer eines Clients, bis dieser die richtige Kartenprüfsumme gemeldet hat. So erhält ein Client nie Weltobjekte für eine Karte, die er nicht hat. Im Host-Modus verwendet der lokale Client das Terrain des Servers und tritt sofort bei.
- Als Nutzdaten wird die
.rtsmap-Datei übertragen (bereits mit Deflate komprimiert). Eine 128 × 128 große Karte mit Vegetation umfasst meist einige Hundert KB. - Wenn die Karte des Servers seit dem Laden aus ihrem Asset oder ihrer Datei unverändert ist, werden die ursprünglichen Dateibytes gesendet. Sie sind in jeder Sitzung identisch, daher funktioniert der Client-Cache sitzungsübergreifend. Eine bearbeitete oder generierte Karte wird bei Bedarf serialisiert.
- Der Server akzeptiert nur die Bestätigung des ältesten unbestätigten Chunks, sodass ein manipulierter Client nicht die ganze Karte auf einmal abrufen kann.
- Szenenwechsel (
ServerChangeSceneoder die Online-Szene von Mirror) senden die Karte der neuen Szene erneut an jeden Spieler; Clients treten der neuen Szene erst bei, wenn sie diese Karte haben.
Einstellung von RTSNetworkMapSync | Standard | Beschreibung |
|---|---|---|
| Terrain | leer | Terrain, dessen Karte gesendet (Server) bzw. ersetzt (Client) wird. Leer = RTSTerrain.Main. |
| Chunk Size | 16 KB | Bytes pro Chunk-Nachricht; der Wert wird automatisch verringert, damit er in die maximale Nachrichtengröße des Transports passt. |
| Chunks In Flight | 8 | Unbestätigte Chunks auf dem Weg zu einem Client. Höhere Werte laden bei hohem Ping schneller herunter; niedrigere Werte lassen mehr Bandbreite für das Spiel. |
| Transfer Timeout | 30 s | Sekunden ohne Fortschritt, nach denen eine Übertragung fehlschlägt. |
| Load Timeout | 120 s | Server: Zeit, die ein Client nach dem Download brauchen darf, um seine Szene zu laden und die Karte anzuwenden. Clients senden Keep-Alive-Nachrichten, während sie warten. |
| Disconnect On Failure | an | Server: Trennt Clients, deren Übertragung fehlgeschlagen ist (ohne die Karte können sie nicht spielen). |
| Max Map Size MB | 64 | Client: Größte Karte, die der Client akzeptiert (schützt vor defekten oder feindseligen Servern). |
| Reuse Loaded Map | an | Überspringt den Download, wenn der Client bereits genau die Karte des Servers anzeigt. |
| Use Cache / Max Cached Maps | an / 20 | Speichert heruntergeladene Karten in Application.persistentDataPath/MapCache (die am längsten nicht gespielten werden zuerst entfernt, 0 = unbegrenzt). |
| Log Transfers | an | Protokolliert Beginn und Ende jeder Übertragung. |
Events für Ladebildschirme: ClientHeaderReceived, ClientProgressChanged(float), ClientMapReady, ClientTransferFailed(string); auf dem Server ServerClientReady und ServerClientFailed. SendMapToAll() sendet eine geänderte Karte (zum Beispiel nach einer Kartenabstimmung in einer Lobby) erneut an jeden verbundenen Client.
Nebel des Krieges und Anti-Maphack
Der Server berechnet den Nebel des Krieges jedes Teams. Alle Rebuild Interval Sekunden prüft RTSFogOfWarInterestManagement jede Einheit mit RTSNetworkUnit:
- Eigene und verbündete Einheiten (von Teams, die über
RTSFogOfWarManager.SetAllianceihre Sicht teilen) werden immer gesendet. - Gegnerische Einheiten werden nur gesendet, solange der Nebel des Krieges sie dem Team des Clients zeigt. Linger Time behält eine Einheit noch einen Moment, nachdem sie die Sicht verlassen hat, damit Einheiten am Rand des Sichtbereichs nicht flackern.
- Objekte ohne
RTSNetworkUnit(Spieler, Manager) werden immer gesendet.
Ein verborgener Gegner wird auf dem Client despawnt, und keine seiner Bewegungen wird gesendet, sodass ein manipulierter Client ihn nicht aufdecken kann. Auf Clients verbirgt RTSFogOfWarHideable außerdem Gegner, die sich zwar in Netzwerkreichweite befinden, aber noch nicht sichtbar sind.
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Fog Of War | leer | Serverseitiger Nebel des Krieges. Leer = RTSFogOfWarManager.Active. |
| Rebuild Interval | 0.2 s | Sekunden zwischen den Sichtbarkeitsprüfungen. Halte den Wert bei 0.1–0.3 s: Jedes Mal, wenn eine Einheit sichtbar wird, erhält der Client eine vollständige Spawn-Nachricht. |
| Linger Time | 0.5 s | Sekunden, die eine Einheit beobachtet bleibt, nachdem sie die Sicht eines Teams verlassen hat. |
| Share With Allies | an | Teams mit geteilter Sicht sehen immer die Einheiten der jeweils anderen. |
| Show All Without Fog | an | Solange auf dem Server kein Nebel des Krieges läuft, sieht jede Verbindung jede Einheit. Aus: nur die eigenen Einheiten. |
| Spectators See All | aus | Verbindungen ohne Team (Zuschauer) sehen jede Einheit. |
RTSNetworkFogOfWar bietet Compute All Teams On Server (an, für das Interest Management erforderlich), ein Update-Intervall für den Server (0 = den Wert des Managers beibehalten; niedrigere Werte lassen Einheiten früher erscheinen) und Disable Reveal All Online (an). Eine Sichtbarkeitsprüfung besteht pro Einheit aus einer einzigen Abfrage im Nebelraster, daher kosten Tausende Einheiten deutlich weniger als eine Millisekunde. Der Aufwand des Servers für den Nebel wächst mit der Anzahl der Teams; 2–8 Teams sind günstig.
Scripting mit Mirror
using RTSTerrainStudio.Integrations.Mirror;
using RTSTerrainStudio.Samples;
using UnityEngine;
// Needs Mirror (96 or newer). Sends orders of the local player to the server and shows the map download.
public class MirrorExample : MonoBehaviour
{
[SerializeField] RTSNetworkMapSync mapSync; // on the network manager object
void OnEnable()
{
if (mapSync == null) return;
mapSync.ClientProgressChanged += OnProgress; // 0-1 while the map downloads
mapSync.ClientMapReady += OnMapReady; // the map is applied to the terrain
mapSync.ClientTransferFailed += OnTransferFailed;
}
void OnDisable()
{
if (mapSync == null) return;
mapSync.ClientProgressChanged -= OnProgress;
mapSync.ClientMapReady -= OnMapReady;
mapSync.ClientTransferFailed -= OnTransferFailed;
}
void OnProgress(float progress) => Debug.Log($"Downloading map: {progress:P0}");
void OnMapReady() => Debug.Log("Map ready");
void OnTransferFailed(string reason) => Debug.LogWarning("Map download failed: " + reason);
// For your own UI (e.g. a "Move here" button): the server validates the order (ownership,
// target, unit count, rate limit) before the units move.
public void SendSelectedTo(Vector3 target, bool queue)
{
RTSNetworkPlayer player = RTSNetworkPlayer.Local;
RTSSelectionManager selection = RTSSelectionManager.Main;
if (player == null || selection == null) return;
player.RequestMove(selection.Selection, target, queue);
}
// Host only: after changing the map (map vote, in-game editor), send it to every client again.
public void ResendMap()
{
if (mapSync != null) mapSync.SendMapToAll();
}
}
| Member | Beschreibung |
|---|---|
RTSNetworkManager (RTSNetworkManager.Instance) | |
ServerPlayerJoined(conn, team), ServerConnectionRejected(conn) | Server: Eine Verbindung hat ein Team erhalten (vor der Kartenübertragung) oder wurde abgelehnt, weil alle Plätze belegt sind. |
ServerPlayerSpawned(conn, player), ServerUnitSpawned(conn, unit) | Server: Das Spielerobjekt wurde gespawnt (nachdem seine Karte bereit war); eine Einheit wurde gespawnt, auch durch SpawnUnit. Nutze diese Events, um Spielern ihre Startobjekte zu geben. |
ServerPlayerLeft(conn, team) | Server: Eine Verbindung hat das Spiel verlassen; das Event wird ausgelöst, bevor ihre Objekte zerstört werden. |
ClientJoinedGame, ClientDisconnected, ClientRejected(reason) | Client: Nach dem Laden der Karte dem Spiel beigetreten; die Verbindung wurde beendet; der Server hat diesen Client abgelehnt. |
RTSNetworkUnit SpawnUnit(owner, team, position, yaw, prefab = null) | Server: Spawnt eine Einheit (sie wird an die nächste begehbare Stelle verschoben). owner null = kein Besitzer, zum Beispiel für neutrale Creeps oder Gebäude. Das Prefab muss ein registriertes spawnbares Prefab mit RTSNetworkUnit sein. |
GetUnits(conn), TryGetTeam(conn, out team), IsTeamReserved(team) | Server: die Einheiten einer Verbindung, ihr Team und ob Einheiten eines Spielers, der das Spiel verlassen hat, noch einen Team-Platz belegen. |
RejectConnection(conn, reason) | Server: Sendet den Grund an den Client und trennt ihn nach Ablauf von Reject Disconnect Delay. |
| RTSNetworkUnit und RTSNetworkPlayer | |
static RTSNetworkUnit.Spawned, Despawned, All | Wird auf jedem Rechner ausgelöst, wenn eine Netzwerkeinheit gespawnt (nachdem ihr Team gesetzt wurde) oder despawnt wurde; alle auf diesem Rechner bekannten Einheiten. |
static RTSNetworkPlayer.LocalPlayerStarted, LocalPlayerStopped, Local | Das lokale Spielerobjekt ist erschienen oder verschwunden; der aktuelle lokale Spieler. |
using Mirror;
using RTSTerrainStudio.Integrations.Mirror;
using UnityEngine;
// Server: gives every player an extra starting object (for example a town hall) next to its start location.
// Put it on the network manager object.
public class StartingObjectsExample : MonoBehaviour
{
[SerializeField] GameObject townHallPrefab; // NetworkIdentity + RTSUnit + RTSNetworkUnit, in Registered Spawnable Prefabs
RTSNetworkManager manager;
void Awake() => manager = GetComponent<RTSNetworkManager>();
void OnEnable()
{
if (manager != null) manager.ServerPlayerSpawned += OnPlayerSpawned;
}
void OnDisable()
{
if (manager != null) manager.ServerPlayerSpawned -= OnPlayerSpawned;
}
void OnPlayerSpawned(NetworkConnectionToClient conn, RTSNetworkPlayer player)
{
if (player == null || townHallPrefab == null) return;
// Start location of the player's team (a fallback position when the map has none).
RTSNetworkUtility.GetStartLocation(manager.Terrain, player.Team, manager.MaxPlayers, out Vector3 start, out float yaw);
// Owned by the connection, so the player can select and order it.
manager.SpawnUnit(conn, player.Team, start, yaw, townHallPrefab);
}
}
Einschränkungen und Fehlerbehebung
- Kartenänderungen während eines Spiels (In-Game-Editor auf dem Host) werden nicht live übertragen. Rufe
RTSNetworkMapSync.SendMapToAll()auf, um die Karte erneut zu senden. RTSBuildingPlacementDemoundRTSTreeHarvestDemolaufen lokal und sind nicht vernetzt.RTSDemoGameist ein Bootstrapper für den Einzelspielermodus und gehört nicht in eine Netzwerkszene.- Auf Clients bleibt
RTSUnit.Stateauf Idle. VerwendeRTSUnit.Velocity(zum Beispiel für Laufanimationen) oder füge eine eigene SyncVar hinzu. - Allianzen werden serverseitig im Nebel des Krieges festgelegt. Clients erhalten sie nicht; rufe
SetAllianceauch auf den Clients auf, wenn die Sicht der Verbündeten dort sichtbar sein soll. - Beim additiven Laden von Szenen bleibt die aktuelle Karte erhalten. Server und Clients müssen dasselbe Tileset und dieselben Bibliotheken verwenden (dieselben Projektinhalte).
| Problem | Lösung |
|---|---|
| „The server's fog of war computes only its local team“ | Füge RTSNetworkFogOfWar zum Netzwerkmanager hinzu oder aktiviere Compute All Teams am Fog of War Manager des Servers. |
| Client kommt nie über „Connecting...“ hinaus | Der Server ist nicht erreichbar (Adresse, Firewall, UDP-Port 7777) oder die Serverszene hat keinen RTSNetworkManager. |
| „Could not join the game: The game is full“ | Die Karte hat weniger freie Plätze als Spieler. Füge Startpositionen hinzu, erhöhe den Wert Max Players der Karte oder prüfe Max Players Override. Plätze von Spielern, die das Spiel verlassen haben, bleiben reserviert, solange ihre Einheiten leben. |
| Kompilierfehler wegen fehlender Mirror-Typen nach dem Entfernen von Mirror | Das Define-Symbol MIRROR ist noch gesetzt. Siehe Integrationen entfernen. |
| Einheiten bewegen sich auf dem Client nicht | Das Einheiten-Prefab braucht NetworkTransformReliable, und die Szene braucht einen RTSCommandController. |
| Feindliche Einheiten sind immer sichtbar | RTSFogOfWarInterestManagement muss auf einem aktiven Objekt liegen, und der Fog of War Manager muss auf dem Server laufen. |
| Die Karte wird jedes Mal heruntergeladen | Der Server sendet eine Karte, die nach dem Laden bearbeitet oder generiert wurde. Speichere sie vor dem Hosten in ihrem Asset oder ihrer Datei. |
A* Pathfinding Project
Diese Integration wandelt die Pathing-Karte des Terrains in Grid Graphs von A* um und hält sie aktuell, während du die Karte bearbeitest (im Editor und im Spiel). Außerdem lässt sie die Beispieleinheiten mit A* laufen: entweder entlang von A*-Pfaden mit ihrer eigenen Steuerung oder mit AIPath von A* und lokaler RVO-Kollisionsvermeidung.
TraversalConstraint (neu in 5.4) und die lokale RVO-Kollisionsvermeidung (RVOController, RVOSimulator), die nur in der Pro-Version enthalten sind. Die kostenlose Version verwendet denselben Paketnamen, daher kann Unity die beiden nicht unterscheiden: Füge bei der kostenlosen Version RTS_ASTAR_DISABLED unter hinzu (oder lösche den Ordner der Integration).Schnellstart: die Demoszene
- Installiere A* Pathfinding Project Pro 5.4 oder neuer.
- Führe einmal aus.
- Führe aus. Die Szene wird als
Integrations/AStarPathfinding/Demo/RTS AStar Demo.unitygespeichert. Wenn die Demo-Inhalte keine Karte enthalten, wird danebenRTS AStar Demo Map.rtsmap(Plateaus, Rampen, Seen) generiert. - Drücke Play. Wähle Einheiten mit der linken Maustaste aus und bewege sie per Rechtsklick. Ritter verwenden den Graphen RTS Large, alle anderen Einheiten RTS Ground.
- Wähle das Objekt A* Pathfinding aus und schalte die Einstellung Mode der Komponente A* Path Provider zwischen Agent Movement und Path Only um, um beide Bewegungsarten zu vergleichen.
A* zur eigenen Szene hinzufügen
Wähle dein RTS Terrain aus und führe aus. Dadurch wird ein GameObject A* Pathfinding mit folgenden Komponenten hinzugefügt:
| Komponente | Zweck |
|---|---|
AstarPath | Die Hauptkomponente von A*. Scan on Startup ist ausgeschaltet (der Graph-Builder übernimmt das Scannen), das Pfad-Logging ist auf Fehler beschränkt. |
RTSAstarGraphBuilder (A* Graph Builder) | Erzeugt die Grid Graphs aus der Pathing-Karte und aktualisiert sie bei jeder Kartenänderung. |
RVOSimulator | Lokale Kollisionsvermeidung für Agenten mit einem RVOController. |
RTSAstarPathProvider (A* Path Provider) | Lässt alle Einheiten A* verwenden (registriert als RTSUnitPathProviderRegistry.Default). |
Die manuelle Einrichtung funktioniert genauso: Füge diese Komponenten einem beliebigen GameObject hinzu () und weise dem Graph-Builder das Terrain zu. Alternativ legst du den Builder auf das GameObject des Terrains, wo er das Terrain selbst findet. Schalte bei AstarPath die Option Scan On Startup aus.
Wie die Graphen erstellt werden
- Exaktes Layout. Ein Knoten pro Pathing-Zelle, auf der Karte zentriert und mit der Y-Rotation des Terrains gedreht. Wenn du das Terrain verschiebst oder drehst, werden die Graphen neu erstellt (im Edit Mode, sobald das Terrain 0.2 Sekunden lang stillgestanden hat).
- Keine Physik nötig. Die Kollisions- und Höhentests von A* sind ausgeschaltet. Die Knotenhöhen stammen von der Terrain-Oberfläche (dieselben Werte wie bei
RTSTerrain.GetHeight); Begehbarkeit, Tags und Penalties stammen aus den Pathing-Flags. Die Daten werden von Burst-Jobs berechnet. - Tags (im Builder änderbar): 0 Boden, 1 Flachwasser, 2 Tiefwasser, 3 Klippenkante. Nutze sie in den Einstellungen Tags des Seekers, um pro Einheit bestimmte Bodenarten zu verbieten oder mit Penalties zu belegen.
- Graph-Profile. Ein Grid Graph von A* pro Profil (zum Beispiel einer pro Einheitengröße). Jedes Profil hat einen Graph-Namen, eine Einstellung Traversal (Land, Shallow Water, Deep Water) und einen Unit Radius: Knoten, die näher als dieser Radius an einem Hindernis liegen, werden unbegehbar, sodass große Einheiten nie in Hindernisse ragen. Dazu kommen Penalties pro Bodenart (1000 ≈ ein zusätzlicher Meter pro Knoten), Use Water Surface Height für Graphen, die See-Einheiten nutzen, und eine maximale Steighöhe. Einheiten wählen den kleinsten Graphen, der zu ihrem Radius passt.
- See- und Amphibieneinheiten. Setze die Einstellung Traversal des Profils auf Deep Water (Boote) oder Land | Shallow Water | Deep Water und aktiviere Use Water Surface Height. Gib Booten ein
RTSAstarUnitSetupmit Required Traversal = Deep Water. Im Code erzeugen die Factory-MethodenRTSAstarGraphProfile.Ground()(„RTS Ground“, Radius 0.5 m, Land | Shallow Water),Naval()(„RTS Naval“, Radius 1 m, Deep Water, Höhe der Wasseroberfläche) undAmphibious()(„RTS Amphibious“, Radius 0.75 m, alle Bodenarten, Höhe der Wasseroberfläche) fertige Profile. Ein neues Profil verwendet standardmäßig Land | Shallow Water, eine Flachwasser-Penalty von 600 und eine Klippenkanten-Penalty von 200. - Aktualisierungen. Eine neue Karte, ein verschobenes Terrain oder geänderte Einstellungen lösen eine komplette Neuerstellung aus. Jede Kartenänderung (
RTSTerrain.PathingChanged) schreibt nur die betroffenen Knoten, ihren Freiraumbereich und ihre Verbindungen in einem Aufruf vonAstarPath.AddWorkItemneu. - Sehr große Karten. A* begrenzt Grid Graphs auf 1024 Knoten pro Achse (8192 mit dem Optimierungs-Define
ASTAR_LARGER_GRIDS). Größere Pathing-Karten verwenden automatisch mehrere Pathing-Zellen pro Knoten; Cells Per Node macht dasselbe gezielt, um schnellere Graphen zu erhalten. - Build In Edit Mode zeigt die Graphen als A*-Gizmos an, während du die Karte bearbeitest.
Member von RTSAstarGraphBuilder | Beschreibung |
|---|---|
| Cells Per Node (Einstellung, Standard 1) | Pathing-Zellen pro Graph-Knoten entlang jeder Achse. 1 = exakte Übereinstimmung mit der Pathing-Karte; wird auf sehr großen Karten automatisch erhöht (EffectiveCellsPerNode). |
| Log Rebuilds (Einstellung, standardmäßig aus) | Schreibt bei jeder kompletten Neuerstellung eine Zeile in die Console. |
Profiles, Tags | Die Graph-Profile und Tag-Nummern. Rufe RequestRebuild() auf, nachdem du sie per Code geändert hast. |
RequestRebuild() | Plant eine komplette Neuerstellung für das nächste Update ein (wiederholte Aufrufe kosten kaum etwas). |
bool RebuildNow() | Erstellt alle Graphen sofort neu. Gibt false zurück, wenn es kein aktives AstarPath oder keine Karte gibt. |
UpdateRegion(RectInt pathingRect) | Schreibt die Knoten eines Rechtecks aus Pathing-Zellen neu. Wird automatisch von RTSTerrain.PathingChanged aufgerufen; rufe die Methode selbst auf, wenn du PathingMap.Flags ohne dieses Event geändert hast. |
ProcessPendingChanges() | Wendet ausstehende Aktualisierungen sofort an (für Pfade, die im selben Frame wie eine Kartenänderung angefordert werden). |
GetGraph(index), GetGraph(name), GetProfileIndex(name) | Der A*-GridGraph eines Profils. |
FindProfileFor(radius, traversal), FindGraphFor(radius, traversal) | Das kleinste Profil (bzw. der kleinste Graph), das zu einer Einheit passt; -1 / null, wenn kein Profil diese Fortbewegungsart erlaubt. |
GetStats(index) | RTSAstarGraphStats: Größe, Knotengröße, begehbare Knoten, Zeiten für Neuerstellung und Aktualisierung, isBuilt. |
IsBuilt, IsProfileBuilt(index), bool TryGetProblem(out string problem) | Ob die Graphen die aktuelle Karte enthalten; warum sie nicht erstellt werden können (kein AstarPath, kein Terrain, keine Karte). |
GraphsRebuilt, GraphsUpdated | Events nach einer kompletten Neuerstellung und nach einer inkrementellen Aktualisierung (mit dem Pathing-Rechteck). |
static FindFor(terrain), static All | Der Builder eines Terrains; alle aktivierten Builder. |
Provider-Modi und RVO
Modus (RTSAstarMovementMode) | Verhalten |
|---|---|
| Agent Movement (Standard) | HandlesMovement = true. Bewegungsbefehle setzen das Ziel des AIPath der Einheit und starten eine Suche; Stop und Hold löschen den Pfad, Einheiten mit Hold-Befehl sperren ihren RVOController. Ankunft und Fehlschlag werden an die Einheit zurückgemeldet, sodass eingereihte Befehle (Shift), Patrouillen und Einheiten-Events wie mit dem integrierten Pathfinder funktionieren. Die lokale RVO-Kollisionsvermeidung hält Einheiten in Bewegung auf Abstand zueinander. |
| Path Only | HandlesMovement = false. A* berechnet nur die Wegpunkte (über den Seeker der Einheit, sodass Modifier wie der Funnel Modifier greifen); die Einheit folgt ihnen mit ihrer eigenen Steuerung und Separation. Bei Einheiten ohne Funnel Modifier werden die Pfade begradigt (Smooth Paths). |
| Provider-Einstellung | Standard | Beschreibung |
|---|---|---|
| Mode | Agent Movement | Siehe oben. |
| Graph Builder | leer | Builder, dessen Graphen verwendet werden. Leer = der Builder des Terrains der Einheit. |
| Auto Configure Units | an | Fügt jeder Einheit RTSAstarUnitSetup hinzu und konfiguriert damit einen Seeker, einen FunnelModifier, ein RTSAstarAIPath (ein AIPath, das ohne Physik-Raycasts auf der Terrain-Oberfläche steht) und einen RVOController. Geschwindigkeit, Drehrate, Beschleunigung, Radius und Höhe werden von der RTSUnit übernommen. |
| Fallback To Builtin | an | Verwendet den integrierten Pathfinder, solange kein A*-Graph verfügbar ist (im Edit Mode und vor dem ersten Scan). |
| Smooth Paths | an | Path Only: Begradigt die Pfade von Einheiten ohne Funnel Modifier; Abkürzungen bleiben auf erlaubten Tags und führen nie über teurere Knoten. |
| Stuck Time | 1.5 s | Agent Movement: Sekunden, die ein Agent während einer Bewegung stillstehen darf, bevor er einmal einen neuen Pfad sucht und danach aufgibt. |
Füge RTSAstarUnitSetup selbst zu einem Einheiten-Prefab hinzu, um einen Graphen über seinen Namen zu wählen oder um die lokale Kollisionsvermeidung oder die Pfadglättung pro Einheitentyp auszuschalten. Der Inspector von RTSAstarAIPath zeigt die üblichen Bewegungseinstellungen von A* sowie einen Abschnitt RTS Terrain (Follow Terrain Height, Stay On Water Surface, Terrain).
using RTSTerrainStudio.Integrations.AStar;
using RTSTerrainStudio.Samples;
using UnityEngine;
// Needs the A* Pathfinding Project Pro 5.4+. Put it into an assembly that references
// RTSTerrainStudio.Integrations.AStar (or into Assembly-CSharp, which references it automatically).
public class AstarExample : MonoBehaviour
{
[SerializeField] RTSUnit unit;
void Start()
{
// Configure one unit by hand (Seeker, FunnelModifier, RTSAstarAIPath and RVOController).
RTSAstarUnitSetup setup = RTSAstarUnitSetup.Configure(unit, agentMovement: true);
setup.Agent.maxSpeed *= 1.5f;
// The graph that fits this unit's radius.
RTSAstarGraphBuilder builder = RTSAstarGraphBuilder.FindFor(unit.Terrain);
if (builder != null)
Debug.Log("Unit uses graph " + builder.FindGraphFor(unit.Radius)?.name);
}
// After changing the pathing map from code in the middle of a frame, push the change to A* now
// instead of waiting for the next update.
public static void FlushGraphs(RTSUnit anyUnit)
{
RTSAstarGraphBuilder.FindFor(anyUnit.Terrain)?.ProcessPendingChanges();
}
}
Einschränkungen
- Die Regeln für Grid Graphs von A* (
GridGraph.rules) haben keine Wirkung: Der Builder schreibt Begehbarkeit, Tags, Penalties und Positionen nach jedem Scan selbst. Verwende stattdessen die Tags und Penalties des Builders. - Terrains, die um X oder Z gedreht sind, werden nicht unterstützt (Grid Graphs sind horizontale Ebenen).
- Ein gemalter Pathing-Override Unwalkable blockiert nur Bodeneinheiten. Schiffe werden durch Hindernisse und Klippen blockiert; forme Seewege mit Doodads oder über die Ränder des Tiefwassers. See-Einheiten brauchen den Modus Agent Movement.
- Graph-Profile, die du aus dem Builder entfernst, hinterlassen ihren Graphen in der Komponente
AstarPath; lösche ihn dort, wenn du ihn nicht mehr brauchst. - A* sucht Pfade nur im Play Mode. Im Edit Mode greifen Pfadanfragen auf den integrierten Pathfinder zurück.
Integrationen entfernen
Du musst eine Integration nie entfernen: Ohne ihre Abhängigkeit wird sie nicht kompiliert. Entferne eine Integration, wenn du die Abhängigkeit deinstallierst oder ein kleineres Projekt haben möchtest. Lösche in jedem Fall zuerst den Ordner Demo der Integration, falls du eine Demoszene erstellt hast: Ihre Szenen und Prefabs verwenden die Komponenten der Integration und würden fehlende Skripte anzeigen.
A* Pathfinding Project oder AI Navigation entfernen
- Lösche
Integrations/AStarPathfinding/Demo(bzw.Integrations/UnityNavMesh/Demo), falls vorhanden. - Entferne die Komponenten der Integration aus deinen eigenen Szenen und Prefabs (Graph-Builder, Pfad-Provider und Unit-Setup bzw. NavMesh-Builder und NavMesh-Pfad-Provider).
- Deinstalliere das Paket (A* Pathfinding Project bzw. AI Navigation im Package Manager). Das Versions-Define verschwindet zusammen mit dem Paket, und die Assemblies der Integration werden automatisch übersprungen.
- Falls du für die kostenlose Version von A*
RTS_ASTAR_DISABLEDhinzugefügt hast, kannst du es wieder aus den Scripting Define Symbols entfernen.
Mirror entfernen
Mirror trägt die Scripting Define Symbols MIRROR, MIRROR_89_OR_NEWER, MIRROR_90_OR_NEWER, MIRROR_93_OR_NEWER und MIRROR_96_OR_NEWER in die Player Settings ein; wenn du Mirror löschst, bleiben sie dort erhalten. Solange MIRROR noch definiert ist, kompiliert Unity die Integration weiterhin gegen Mirror-Assemblies, die nicht mehr existieren, und die Kompilierfehler blockieren jedes Skript im Projekt.
- Lösche zuerst
Integrations/Mirror/Demo. - Lösche den Ordner
Mirrorim Unity Editor (Project-Fenster). Die kleine Editor-AssemblyRTSTerrainStudio.Integrations.Mirror.DefineCleanup.Editorerkennt das Löschen, entfernt die Symbole von Mirror aus allen Build-Zielen und protokolliert, was sie entfernt hat. Sie läuft außerdem jedes Mal, wenn der Editor ohne Mirror startet. Über stößt du denselben Vorgang manuell an. - Prüfe für jede Plattform, für die du Builds erstellst: Es darf kein Eintrag
MIRRORoderMIRROR_..._OR_NEWERübrig bleiben. Entferne Reste von Hand, zum Beispiel wenn Mirror bei geschlossenem Editor gelöscht wurde und der Editor danach im Safe Mode gestartet ist. Das Edgegap-Plugin von Mirror fügt außerdemEDGEGAP_PLUGIN_SERVERShinzu; es ist harmlos, du kannst es aber ebenfalls entfernen.
Die Skripte der Integration können im Projekt bleiben: Sie werden erst kompiliert, wenn Mirror wieder installiert ist.
Integrationsordner löschen
Jede Integration ist in sich abgeschlossen. Wenn du Integrations/Mirror, Integrations/AStarPathfinding oder Integrations/UnityNavMesh (im Project-Fenster) löschst, ist die jeweilige Integration vollständig entfernt; nichts anderes im Paket verweist auf diese Ordner. Du kannst auch den gesamten Ordner Integrations löschen.
Integrations/Mirror löschst, während Mirror installiert bleibt, und Mirror später ebenfalls entfernst, musst du die Define Symbols von Mirror von Hand entfernen (die automatische Bereinigung liegt in diesem Ordner).Scripting-Leitfaden
Alles, was du in den Editorfenstern tun kannst, lässt sich auch aus C# heraus erledigen. Dieses Kapitel zeigt, wie du das Terrain abfragst, die Karte per Code änderst (mit und ohne Rückgängigmachen), eigene Werkzeuge schreibst, auf Events reagierst und Karten zur Laufzeit speicherst und lädst. Jedes der folgenden Beispiele kompiliert gegen das Paket im Auslieferungszustand; kopiere es in dein Projekt und passe es an.
Assemblies, Namespaces und Koordinaten
| Assembly | Namespace | Inhalt |
|---|---|---|
RTSTerrainStudio.Runtime | RTSTerrainStudio | Terrain, Kartendaten, Werkzeuge, Pinsel, Vegetation, Wasser, Dekor, Nebel des Krieges, Pathing, Generator, Serialisierung, In-Game-Editor. |
RTSTerrainStudio.Samples | RTSTerrainStudio.Samples | RTS-Kamera, Einheiten, Auswahl, Befehle, Minikarten-UI, Demospiel. |
RTSTerrainStudio.Editor | RTSTerrainStudio.Editor | Editorfenster, Werkzeuge für die Scene View, Inspectors, Importer, Einrichtungsfenster, RTSEditorContext. |
RTSTerrainStudio.Integrations.* | RTSTerrainStudio.Integrations.Mirror / .AStar / .NavMesh | Optionale Integrationen. |
Skripte ohne Assembly Definition (Assembly-CSharp sowie Assembly-CSharp-Editor für Skripte in Ordnern namens Editor) referenzieren diese Assemblies automatisch. Hat dein Code eine eigene .asmdef, füge ihren Referenzen RTSTerrainStudio.Runtime hinzu (und RTSTerrainStudio.Samples oder RTSTerrainStudio.Editor, wenn du sie verwendest), außerdem Unity.Mathematics und Unity.Collections, wenn du Typen wie float3 oder NativeArray verwendest.
Die Karte in drei Rastern
- Zellen: Die Karte besteht aus
Width × HeightZellen mit einer Kantenlänge von jeCellSizeMetern (Standard 2 m). Rechtecke, die du anNotifyChangedübergibst, sind in Zellen angegeben. - Vertices: die Zellecken,
(Width + 1) × (Height + 1); Indexz * VertexCountX + x(MapData.VertexIndex(x, z)). Hier werden Höhen, Klippenstufen, Flags, Klippentypen und Wasser gespeichert. Der Vertex (x, z) liegt bei(x * CellSize, z * CellSize). - Texel: Rasterschichten mit N Texeln pro Zelle: Splat (
SplatResolution), Details (DetailResolution) und Pathing (PathingResolution). Der Mittelpunkt des Texels (x, z) liegt bei(x + 0.5, z + 0.5) * CellSize / N.
Kartenlokale Koordinaten beginnen am ersten Vertex: X/Z in Metern von 0 bis MapData.WorldSize, Y zeigt nach oben. Vom Terrain werden nur Position und Y-Rotation berücksichtigt (die Skalierung wird ignoriert). Zum Umrechnen dienen WorldToLocal, LocalToWorld, WorldToLocalDirection, LocalToWorldDirection, WorldToCell, CellCenterToWorld und VertexToWorld. Die Bodenhöhe eines Vertex ist CliffLevels[i] × CliffLevelHeight + Heights[i]. Rotationen sind in Grad angegeben, im Uhrzeigersinn von oben gesehen (0 = +Z, 90 = +X), wie bei Unitys Y-Rotation.
Das Terrain abfragen
Die Abfrage-API von RTSTerrain folgt exakt der sichtbaren Oberfläche, einschließlich der abgerundeten Klippenkonturen, und braucht keine Collider. Sie funktioniert im Edit Mode und zur Laufzeit.
| Member | Rückgabe |
|---|---|
RTSTerrain.Main, RTSTerrain.All | Das erste aktivierte Terrain / alle aktivierten Terrains. |
RTSTerrain.FindAt(Vector3 world) | Das Terrain, dessen Fläche die Position enthält, oder null. |
RTSTerrain.RaycastAll(Ray, out RTSTerrainHit, maxDistance) | Der nächstgelegene Treffer über alle Terrains hinweg. |
Raycast(Ray, out RTSTerrainHit, maxDistance) | Analytischer Raycast gegen dieses Terrain. RTSTerrainHit enthält point, normal, distance, localPoint, cell und terrain. |
GetHeight(world), SnapToGround(world) | Y-Weltkoordinate des Bodens / die auf den Boden versetzte Position. |
GetNormal(world) | Oberflächennormale in Weltkoordinaten. |
GetCliffLevel(world) | Klippenstufe 0–15 an der Position. |
IsWater(world, out depth), TryGetWaterHeight(world, out waterY) | Ob die Position unter Wasser liegt, ihre Wassertiefe und die Y-Weltkoordinate der Wasseroberfläche. |
GetPathing(world), IsWalkable(world), IsBuildable(world) | PathingFlags (Walkable, Buildable, Flyable, ShallowWater, DeepWater, Cliff, Blocked, Steep). None außerhalb der Karte oder solange das Pathing noch nicht aufgebaut ist. |
Contains(world), WorldBounds | Ob die Position innerhalb der Kartenfläche liegt / die Begrenzungsbox in Weltkoordinaten. |
GetGeometry() | Eine Burst-taugliche MapGeometryView (kartenlokal: SampleHeight, SampleNormal, SampleCliffLevel, SampleWater, Raycast …) für deine eigenen Jobs. |
RTSPathfinder.FindPath(terrain, from, to, options, list) | Ein Pfad vom integrierten Burst-Pathfinder (PathStatus: Success, Partial, NoPath, InvalidStart). |
using System.Collections.Generic;
using RTSTerrainStudio;
using UnityEngine;
// Logs what is under the mouse cursor when you left-click: height, cliff level, water and pathing.
// Works with any number of RTS terrains in the scene and needs no colliders.
public class TerrainQueryExample : MonoBehaviour
{
[SerializeField] Camera viewCamera;
void Update()
{
// RTSInput works with the Input System and with the legacy Input Manager.
if (!RTSInput.GetMouseButtonDown(0)) return;
Camera cam = viewCamera != null ? viewCamera : Camera.main;
if (cam == null) return;
Ray ray = cam.ScreenPointToRay(RTSInput.PointerPosition);
// Analytic ray cast against every enabled RTS terrain (closest hit wins).
if (!RTSTerrain.RaycastAll(ray, out RTSTerrainHit hit)) return;
RTSTerrain terrain = hit.terrain;
Vector3 p = hit.point;
float groundY = terrain.GetHeight(p); // world Y of the ground
Vector3 normal = terrain.GetNormal(p); // world space surface normal
int level = terrain.GetCliffLevel(p); // 0-15
bool walkable = terrain.IsWalkable(p); // ground units can walk here
bool buildable = terrain.IsBuildable(p); // buildings can be placed here
PathingFlags flags = terrain.GetPathing(p); // all pathing flags of the cell
string water = "dry";
if (terrain.IsWater(p, out float depth) && terrain.TryGetWaterHeight(p, out float waterY))
water = $"water, depth {depth:0.00} m, surface at y = {waterY:0.00}";
Debug.Log($"Cell {hit.cell} on '{terrain.name}': ground y = {groundY:0.00}, level {level}, " +
$"slope {Vector3.Angle(normal, Vector3.up):0} deg, {water}, " +
$"walkable {walkable}, buildable {buildable}, flags {flags}");
}
// Keeps an object standing on the ground of whatever terrain is below it.
public static void PlaceOnGround(Transform t)
{
RTSTerrain terrain = RTSTerrain.FindAt(t.position);
if (terrain != null) t.position = terrain.SnapToGround(t.position);
}
// Asks the built-in Burst pathfinder for a path (main thread, no third-party package needed).
public static bool TryFindPath(RTSTerrain terrain, Vector3 from, Vector3 to, List<Vector3> waypoints)
{
// ForUnit sets the clearance for a unit of this radius (large units avoid narrow gaps).
PathRequestOptions options = PathRequestOptions.ForUnit(0.5f);
PathStatus status = RTSPathfinder.FindPath(terrain, from, to, options, waypoints);
// Partial: the goal is unreachable, the path ends at the closest reachable cell.
return status == PathStatus.Success || status == PathStatus.Partial;
}
}
RTSTerrain.RaycastAll als Physics.Raycast: Es ist exakt und funktioniert ohne Collider und auch im Edit Mode. Collider (Collider Mode in den Terrain-Einstellungen) sind trotzdem nützlich für Physikobjekte und für Raycasts, die auch andere Objekte treffen müssen.Die Karte per Code bearbeiten
Alle Karteninhalte liegen in terrain.Map, einer Instanz von MapData. Änderungen folgen immer denselben drei Schritten:
- Ändere die Daten in
terrain.Map(auf dem Hauptthread). - Rufe
terrain.NotifyChanged(layers, cellRect)mit den geändertenMapLayersund dem geänderten Rechteck (in Zellen) auf. Hast du die gesamte Karte geändert, rufe stattdessenNotifyChangedAll(layers)auf. - Das war's. In seinem nächsten
LateUpdate(im Edit Mode: beim nächsten Editor-Update) baut das Terrain nur neu auf, was als geändert markiert ist: Chunks, Splat-Texturen, Wasser, Vegetation, Dekor und die Pathing-Karte. Rufeterrain.ProcessPendingChanges()auf, wenn du die Ergebnisse noch im selben Frame brauchst (häufige Aufrufe kosten wenig).
Daten (MapData) | Raster / Typ | Flag in MapLayers |
|---|---|---|
Heights | Vertices, float: Feinhöhe in Metern über der Stufe | Heights |
CliffLevels | Vertices, byte 0–15 | CliffLevels |
VertexFlags | Vertices, VertexFlags (Ramp, Water, User0, User1) | VertexFlags |
CliffTypes | Vertices, byte 0–3 | CliffTypes |
WaterHeights | Vertices, Wasseroberfläche in kartenlokalen Metern (gilt dort, wo das Flag Water gesetzt ist) | Water |
SplatMaps[0..3] | Splat-Texel, 4 × RGBA32; Schicht i liegt in Splatmap i/4, Kanal i%4; die Gewichtungen ergeben zusammen 255 | Splat |
DetailLayers | Detail-Texel, ein Byte Dichte pro Schicht (DetailLayer.prototypeId, density) | Details |
Trees, Decor | Listen von TreeInstanceData / DecorInstanceData mit Palettenindizes in TreePrototypeIds / DecorPrototypeIds | Trees, Decor |
PathingOverrides | Pathing-Texel, gemalte Overrides | PathingOverrides |
Regions, StartLocations, Markers | Listen | Regions, StartLocations, Markers |
Info, Environment | Karteninfo und Beleuchtung | Info, Environment |
TerrainToolUtility.ClampFineHeight); sorge dafür, dass jedes Splat-Texel in der Summe genau 255 ergibt (verwende SplatRaster und SplatMath); vergib Instanz-IDs für neue Bäume und Dekorobjekte mit map.AllocateInstanceId() und Prototyp-Indizes mit GetOrAddTreePrototype / GetOrAddDecorPrototype. Halte niemals Referenzen auf MapData oder dessen Arrays über das Laden einer Karte oder eine Größenänderung hinweg: SetMap, LoadFromFile und ResizeMap ersetzen das gesamte Objekt und geben das alte frei.Verformen und Texturieren zur Laufzeit: Krater und verseuchter Boden
Ein typischer Einsatz im Gameplay: Eine Explosion reißt einen Krater in den Boden, und ein Gebäude der Untoten breitet „verseuchten Boden“ um sich herum aus. Pathing, Vegetation und Dekorhöhen passen sich automatisch an.
using RTSTerrainStudio;
using UnityEngine;
// Runtime terrain changes from gameplay code: explosion craters and spreading "blight".
// Both edit MapData directly and then call NotifyChanged. The terrain rebuilds only the
// touched chunks (and the pathing map) in its next LateUpdate.
public static class TerrainEffects
{
// Digs a bowl-shaped crater into the fine height around a world position.
public static void Crater(RTSTerrain terrain, Vector3 worldPosition, float radius, float depth)
{
MapData map = terrain != null ? terrain.Map : null;
if (map == null || radius <= 0f) return;
// Work in map-local space: X/Z in metres from the first vertex.
Vector3 local = terrain.WorldToLocal(worldPosition);
// Vertex rectangle covered by the crater (vertices sit at x * CellSize, z * CellSize).
int x0 = Mathf.Max(0, Mathf.FloorToInt((local.x - radius) / map.CellSize));
int z0 = Mathf.Max(0, Mathf.FloorToInt((local.z - radius) / map.CellSize));
int x1 = Mathf.Min(map.Width, Mathf.CeilToInt((local.x + radius) / map.CellSize));
int z1 = Mathf.Min(map.Height, Mathf.CeilToInt((local.z + radius) / map.CellSize));
if (x1 < x0 || z1 < z0) return; // completely outside the map
for (int z = z0; z <= z1; z++)
for (int x = x0; x <= x1; x++)
{
float dx = x * map.CellSize - local.x;
float dz = z * map.CellSize - local.z;
float t = Mathf.Sqrt(dx * dx + dz * dz) / radius;
if (t >= 1f) continue;
// Smooth bowl: deepest in the centre, zero at the rim.
float falloff = 1f - t * t;
int i = map.VertexIndex(x, z);
// Heights is the fine height on top of the cliff level. Clamp it to the allowed range.
map.Heights[i] = TerrainToolUtility.ClampFineHeight(map, map.Heights[i] - depth * falloff * falloff);
}
// Tell the terrain which cells changed. Meshes, colliders, water, vegetation, decor and
// pathing follow automatically.
var vertexRect = new RectInt(x0, z0, x1 - x0 + 1, z1 - z0 + 1);
terrain.NotifyChanged(MapLayers.Heights, BrushUtility.VertexRectToCellRect(map, vertexRect));
}
// Paints a ground layer (for example a "blight" texture) around a position with a soft edge.
// amount is the coverage reached in the centre (0-1).
public static void PaintLayer(RTSTerrain terrain, Vector3 worldPosition, float radius, int layer, float amount = 1f)
{
MapData map = terrain != null ? terrain.Map : null;
if (map == null || radius <= 0f) return;
layer = Mathf.Clamp(layer, 0, MapData.MaxGroundLayers - 1);
Vector3 local = terrain.WorldToLocal(worldPosition);
int res = map.SplatResolution; // splat texels per cell
float texel = map.CellSize / res; // texel size in metres
// Texel rectangle (texel centres sit at (x + 0.5) * texel).
int x0 = Mathf.Max(0, Mathf.FloorToInt((local.x - radius) / texel));
int z0 = Mathf.Max(0, Mathf.FloorToInt((local.z - radius) / texel));
int x1 = Mathf.Min(map.SplatWidth - 1, Mathf.CeilToInt((local.x + radius) / texel));
int z1 = Mathf.Min(map.SplatHeight - 1, Mathf.CeilToInt((local.z + radius) / texel));
if (x1 < x0 || z1 < z0) return;
// SplatRaster gives read/write access to all 16 layer weights of a texel.
var splat = new SplatRaster(map);
for (int z = z0; z <= z1; z++)
for (int x = x0; x <= x1; x++)
{
float dx = (x + 0.5f) * texel - local.x;
float dz = (z + 0.5f) * texel - local.z;
float t = Mathf.Sqrt(dx * dx + dz * dz) / radius;
if (t >= 1f) continue;
int index = splat.Index(x, z);
SplatTexel weights = splat.Read(index);
int target = Mathf.RoundToInt(Mathf.Clamp01(amount * (1f - t)) * SplatMath.Total);
if (weights[layer] >= target) continue; // never remove blight that is already there
// Sets the layer weight and rescales the other layers, so the texel still sums to 255.
SplatMath.SetLayerWeight(ref weights, layer, target, 0);
splat.Write(index, weights);
}
var texelRect = new RectInt(x0, z0, x1 - x0 + 1, z1 - z0 + 1);
terrain.NotifyChanged(MapLayers.Splat, BrushUtility.TexelRectToCellRect(map, texelRect, res));
}
}
TerrainEffects.Crater(RTSTerrain.FindAt(hitPoint), hitPoint, 4f, 1.5f); oder TerrainEffects.PaintLayer(terrain, building.position, 12f, blightLayer);. Für Effekte, die in jedem Frame Tausende von Vertices verändern, verlagere die Schleife in einen Burst-Job, wie es die integrierten Werkzeuge tun.Rückgängig machbare Bearbeitungen: RTSEditSession.RunOperation
Direkte Bearbeitungen lassen sich nicht rückgängig machen, und das ist für das Gameplay auch richtig so. Für Editorwerkzeuge und Funktionen zur Kartenbearbeitung verwendest du eine RTSEditSession: RunOperation(name, layers, action) zeichnet die angegebenen Schichten der gesamten Karte auf, führt deine Aktion aus und fasst alles zu einem Undo-Schritt zusammen. Im Unity Editor verwendest du die gemeinsame Sitzung RTSEditorContext.Session; ihre Schritte sind Teil von Unitys Undo-System, daher funktionieren Ctrl+Z / Ctrl+Y wie gewohnt.
// Put this file into a folder named "Editor".
using RTSTerrainStudio;
using RTSTerrainStudio.Editor;
using UnityEditor;
using UnityEngine;
// Editor menu command that raises a plateau in the middle of the edited map.
// It runs through the shared edit session, so Ctrl+Z / Ctrl+Y undo and redo it in one step.
public static class PlateauMenu
{
[MenuItem("Tools/My Game/Raise Centre Plateau")]
static void RaiseCentrePlateau()
{
RTSTerrain terrain = RTSEditorContext.Terrain; // the terrain selected in the RTS Terrain Studio window
if (terrain == null || terrain.Map == null) return;
// The shared session of the window and the Scene View. It always edits RTSEditorContext.Terrain.
RTSEditSession session = RTSEditorContext.Session;
// RunOperation records the listed layers of the whole map BEFORE the operation runs,
// so inside it you only change data and notify the terrain.
session.RunOperation("Raise Centre Plateau", MapLayers.CliffLevels | MapLayers.Heights, ctx =>
{
MapData map = ctx.Map;
int size = Mathf.Min(map.Width, map.Height) / 4; // plateau size in cells
var vertexRect = new RectInt(map.Width / 2 - size / 2, map.Height / 2 - size / 2, size + 1, size + 1);
for (int z = vertexRect.yMin; z < vertexRect.yMax; z++)
for (int x = vertexRect.xMin; x < vertexRect.xMax; x++)
{
int i = map.VertexIndex(x, z);
// Cliff levels are bytes 0-15. Ground height = level * CliffLevelHeight + fine height.
map.CliffLevels[i] = (byte)Mathf.Min(MapData.MaxCliffLevel, map.CliffLevels[i] + 1);
map.Heights[i] = 0f; // flat top
}
ctx.MarkChanged(MapLayers.CliffLevels | MapLayers.Heights,
BrushUtility.VertexRectToCellRect(map, vertexRect));
});
}
}
In einem laufenden Spiel verwendest du die Sitzung des In-Game-Editors (RTSRuntimeEditor.Session), damit deine Operation in dessen Undo-Verlauf landet, oder du erstellst eine eigene Sitzung:
using RTSTerrainStudio;
using UnityEngine;
// Undoable edits in a running game: use the session of the in-game editor, or your own session.
public class RuntimeUndoExample : MonoBehaviour
{
RTSEditSession ownSession;
public void FloodLowGround(RTSTerrain terrain, float waterLocalY)
{
// Prefer the in-game editor's session, so the edit shares its undo history (Ctrl+Z in the editor UI).
var ingameEditor = FindFirstObjectByType<RTSRuntimeEditor>();
RTSEditSession session = ingameEditor != null ? ingameEditor.Session : null;
if (session == null)
{
// Without the in-game editor: a plain session works as well (call Dispose when done).
if (ownSession == null) ownSession = new RTSEditSession();
ownSession.Terrain = terrain;
session = ownSession;
}
session.RunOperation("Flood Low Ground", MapLayers.Water | MapLayers.VertexFlags, ctx =>
{
MapData map = ctx.Map;
const byte waterFlag = (byte)VertexFlags.Water;
for (int i = 0; i < map.VertexCount; i++)
{
// Ground height of a vertex = cliff level * level height + fine height.
float ground = map.CliffLevels[i] * map.CliffLevelHeight + map.Heights[i];
if (ground >= waterLocalY) continue;
// A vertex is wet when it has the Water flag; WaterHeights holds its surface height.
map.VertexFlags[i] = (byte)(map.VertexFlags[i] | waterFlag);
map.WaterHeights[i] = waterLocalY;
}
// Water changes only need the Water layer: terrain meshes are not rebuilt.
ctx.Terrain.NotifyChangedAll(MapLayers.Water);
});
}
public void UndoLast()
{
ownSession?.Undo();
}
void OnDestroy()
{
ownSession?.Dispose();
}
}
RunOperation auf, sondern für jeden Tupfer ctx.Record und ctx.MarkChanged (siehe nächsten Abschnitt). RunOperation zeichnet ganze Schichten auf; gib auf großen Karten daher besser gezielt die betroffenen Schichten an statt MapLayers.All.Ein eigenes Werkzeug schreiben
Werkzeuge sind gewöhnliche C#-Klassen, die von RTSTool erben. Das Paket findet per Reflection (RTSToolRegistry) jedes nicht abstrakte Werkzeug mit einem öffentlichen parameterlosen Konstruktor. Dein Werkzeug erscheint daher automatisch im Tab seiner Kategorie, sowohl im Fenster von RTS Terrain Studio als auch im In-Game-Editor. Es bekommt eine Einstellungsoberfläche, die aus seinen [ToolSetting]-Feldern erzeugt wird, sowie das Pinsel-Panel, die Symmetrie, Rückgängig/Wiederherstellen, den projizierten Pinselcursor und die Statusleiste. Im Editor bleiben seine Einstellungen zwischen den Sitzungen erhalten.
- Lege an beliebiger Stelle in deinem Projekt eine C#-Datei an (nicht in einem
Editor-Ordner, damit das Werkzeug auch in Builds für den In-Game-Editor vorhanden ist). - Leite von
RTSToolab und überschreibeId(eindeutig, zum Beispiel"height.crater"),DisplayNameundCategory. - Füge für die Optionen, die der Benutzer ändern kann, öffentliche Felder mit
[ToolSetting("Label", ...)]hinzu. - Überschreibe
ApplyDab: Berechne die Pinselgewichte mitBrushUtility.ComputeWeights, rufectx.Record(layers, cells)vor dem Ändern der Daten auf, ändere die Daten und rufectx.MarkChanged(layers, cells)danach auf. - Wechsle zurück zu Unity. Sobald das Skript kompiliert ist, erscheint das Werkzeug in seinem Tab.
- Wenn du das Werkzeug in einem Build mit einem Managed Stripping Level über Minimal auslieferst, markiere die Klasse mit
[UnityEngine.Scripting.Preserve], damit sie beim Code-Stripping erhalten bleibt (siehe Code-Stripping und eigene Werkzeuge).
using RTSTerrainStudio;
using UnityEngine;
// A custom tool: every click digs a crater and paints scorched ground into it.
// No registration is needed. The class is found by reflection and appears in the Height tab
// of the RTS Terrain Studio window and of the in-game editor, with a UI for every [ToolSetting].
public sealed class CraterTool : RTSTool
{
// Unique id. The part before the dot also picks a fallback icon ("height" = height tools).
public override string Id => "height.crater";
public override string DisplayName => "Crater";
public override ToolCategory Category => ToolCategory.Height;
public override string Description => "Digs a crater with a scorched floor at the click position.";
public override string Hint => "Click: dig a crater | Shift+click: raise a mound";
public override int Order => 100; // after the built-in height tools
public override ToolInputMode InputMode => ToolInputMode.Click; // one dab per click
public override bool UsesBrush => true; // keep the brush size / shape panel
public override MapLayers ModifiedLayers => MapLayers.Heights | MapLayers.Splat;
public override Color PreviewColor => new Color(1f, 0.45f, 0.1f, 1f);
// Public fields with [ToolSetting] get a slider, toggle or palette automatically.
[ToolSetting("Depth", Min = 0.1f, Max = 6f, Order = 0, Tooltip = "Depth of the crater centre in metres.")]
public float depth = 1.5f;
[ToolSetting("Paint Floor", Order = 1, Tooltip = "Paint a ground layer into the crater.")]
public bool paintFloor = true;
[ToolSetting("Floor Layer", Kind = ToolSettingKind.GroundLayer, Order = 2, Tooltip = "Ground layer of the crater floor.")]
public int floorLayer = 0;
public override void ApplyDab(RTSToolContext ctx, in BrushDab dab)
{
MapData map = ctx.Map;
// 1. Brush weights (0-1) for every vertex under the brush. Shape, hardness, falloff and
// rotation are already applied; strength is not (use dab.strength). Dispose the result.
using BrushWeights w = BrushUtility.ComputeWeights(map, dab, ctx.BrushMask, BrushGrid.Vertices, 1);
if (w.IsEmpty) return;
// Cells touched by the dab (covers both the vertex and the splat texel grid).
RectInt cells = BrushUtility.GetCellRect(map, dab);
// 2. Record for undo BEFORE changing anything. Record every layer you are going to write.
MapLayers layers = paintFloor ? MapLayers.Heights | MapLayers.Splat : MapLayers.Heights;
ctx.Record(layers, cells);
// 3. Change the data. Shift (dab.invert) raises a mound instead.
float delta = depth * dab.strength * (dab.invert ? 1f : -1f);
for (int z = w.rect.yMin; z < w.rect.yMax; z++)
for (int x = w.rect.xMin; x < w.rect.xMax; x++)
{
float weight = w[x, z];
if (weight <= 0f) continue;
int i = map.VertexIndex(x, z);
map.Heights[i] = TerrainToolUtility.ClampFineHeight(map, map.Heights[i] + delta * weight);
}
if (paintFloor && !dab.invert)
{
// The splat maps use their own texel grid: compute weights for it as well.
int res = map.SplatResolution;
using BrushWeights sw = BrushUtility.ComputeWeights(map, dab, ctx.BrushMask, BrushGrid.Texels, res);
var splat = new SplatRaster(map);
int layer = Mathf.Clamp(floorLayer, 0, MapData.MaxGroundLayers - 1);
for (int z = sw.rect.yMin; z < sw.rect.yMax; z++)
for (int x = sw.rect.xMin; x < sw.rect.xMax; x++)
{
int target = Mathf.RoundToInt(sw[x, z] * SplatMath.Total);
int index = splat.Index(x, z);
SplatTexel t = splat.Read(index);
if (t[layer] >= target) continue;
SplatMath.SetLayerWeight(ref t, layer, target, 0);
splat.Write(index, t);
}
}
// 4. Notify the terrain AFTER the change (the same rectangle you recorded).
ctx.MarkChanged(layers, cells);
}
}
Was du überschreiben kannst
| Member | Standard | Beschreibung |
|---|---|---|
Id, DisplayName, Category | erforderlich | Eindeutige ID, Anzeigename in der Oberfläche und Tab (Height, Cliffs, Paint, Vegetation, Decor, Water, Pathing, Markers). |
Description, Hint | "" | Tooltip und Hilfetext; Bedienhinweis in der Statusleiste. |
IconName | Id | Name des Icons. Bei unbekannten Namen wird ersatzweise das Icon des Gruppenpräfixes der ID (height., paint. …) verwendet, ansonsten ein allgemeines Schraubenschlüssel-Icon. Eigene Icon-Zeichnungen kannst du mit RTSIcons.RegisterIcon hinzufügen. |
Order | 0 | Position innerhalb der Kategorie. |
InputMode | Brush | Brush: Tupfer entlang des Zeigerwegs (im Pinselabstand). Click: ein Tupfer pro Klick. Drag: OnDragBegin / OnDragUpdate / OnDragEnd (Rechtecke, Linien). |
UsesBrush | true bei Brush | Zeigt das Pinsel-Panel an (Größe, Form, Stärke …). |
RepeatRate | 0 | Tupfer pro Sekunde, solange die Maustaste ohne Bewegung gedrückt gehalten wird (für kontinuierliches Anheben oder Absenken). |
ModifiedLayers | None | Nur zur Information: für Overlays und die Statusleiste. |
PreviewColor | hellblau | Farbe des projizierten Pinselcursors. |
Snap | None | Richtet die Pinselmitte an Vertices (Klippenwerkzeuge) oder an Zellmitten aus. |
SupportsPick + OnPick | false | Ctrl+Klick nimmt einen Wert auf (eine Höhe, eine Schicht), statt zu malen. |
OnActivated, OnDeactivated, OnSettingChanged | Lebenszyklus-Callbacks. | |
OnStrokeBegin, OnStrokeEnd, OnStrokeCancelled | Werden zu Beginn und am Ende eines Pinselstrichs aufgerufen; ein abgebrochener Pinselstrich (Esc) wird anschließend rückgängig gemacht. | |
DrawGizmos(ctx, gizmos, hasHover, hoverLocal) | Zusätzliche Vorschauen über IToolGizmos; im Editor mit Handles umgesetzt, im Spiel mit GL-Linien. | |
GetStatus(ctx, hoverLocal) | null | Text für die Statusleiste, zum Beispiel „Level 3 → 4“. |
[ToolSetting] akzeptiert Min/Max (Bereich des Schiebereglers), Order, Tooltip und Kind. Auto wählt das Bedienelement anhand des Feldtyps (Schieberegler für float/int, Schalter für bool, Popup für Enums, Color, string). Spezielle Arten: GroundLayer und CliffType (Tileset-Paletten mit Vorschaubildern), CliffLevel, DetailPrototype / DetailPrototypeList, TreePrototype / TreePrototypeList, DecorPrototype / DecorPrototypeList (Bibliothekspaletten), Player (0–15) und Hidden.
Hidden-Einstellungen werden von den generischen Einstellungsoberflächen nicht angezeigt. Eine Ausnahme: Im Unity Editor zeigt das Editorfenster ein Hidden-Feld, dessen Typ ein UnityEngine.Object ist (eine Asset-Referenz wie AutoPaintTool.profile), trotzdem als Objektfeld an, weil die generischen Oberflächen Asset-Referenzen sonst nicht darstellen können. Der In-Game-Editor zeigt es nicht an. Eine verborgene Einstellung wird trotzdem zusammen mit den anderen Werkzeugeinstellungen gespeichert.
Gut zu wissen
- Symmetrie funktioniert automatisch.
ApplyDabwird für jede Symmetriekopie einmal aufgerufen;dab.symmetryverrät dir, um welche Kopie es sich handelt (mit ihrem Drehwinkelversatz und der Angabe, ob sie gespiegelt ist).dab.rotationist bereits transformiert. - Der Tupfer (
BrushDab) enthältlocalPosition,worldPosition,radius,strength,rotation,invert(Shift gedrückt),isFirstDabunddabIndex. - Pinselgewichte:
BrushGrid.Verticesmit Auflösung 1 für Vertex-Schichten,BrushGrid.Texelsmitmap.SplatResolution,DetailResolutionoderPathingResolutionfür Rasterschichten. Rechtecke rechnest du mitBrushUtility.VertexRectToCellRect,TexelRectToCellRectoderGetCellRect(map, dab)um. Rufe für die Gewichte immer Dispose auf. - Zufallswerte müssen für jede Symmetriekopie eines Tupfers gleich sein.
ctx.Randomerhält bei jedem Pinselstrich einen neuen Seed und liefert bei jedem Aufruf den nächsten Wert. Eine Kopie, die es aufruft, bekommt daher einen anderen Wert als das Original. Ziehe Zufallswerte nur, wenndab.symmetry.index == 0gilt, speichere sie für diesendab.dabIndexzwischen und verwende sie für die anderen Kopien wieder – gespiegelt oder gedreht mitSymmetricScatter.TransformOffsetundSymmetryPoint.TransformYaw. Die öffentliche KlasseSymmetricScatter(GetCandidates,GetSingleSeed) erledigt das für dich. - Harte Auswahl: Für Alles-oder-nichts-Änderungen im Stil klassischer RTS-Spiele leitest du von
CliffTool,WaterTargetTooloderPathingToolab (sie liefern Kategorie, Cursorfarbe und Statustext;WaterTargetToolergänzt außerdem die Einstellungen für Water Height und das Aufnehmen per Ctrl+Klick) oder rufstTerrainToolUtility.BeginHardVertexSelection(ctx, dab, layers, out weights, out forcedIndex, out cells)auf. Betroffen sind Vertices mit einem Gewicht von mindestensTerrainToolUtility.HardSelectionThreshold(0.5) sowie der Vertex unter der Pinselmitte (forcedIndex), und Undo-Daten werden nur für diesen Bereich aufgezeichnet. Rufe Dispose fürweightsauch dann auf, wenn die Methode false zurückgibt. - Performance: Die integrierten Werkzeuge führen ihre Schleifen in Burst-Jobs aus (siehe
RaiseLowerToolundPaintLayerToolinRuntime/Tools). Für kleine Pinsel reichen einfache Schleifen aus. - Eine mit
[HideTool]markierte Klasse wird gar nicht registriert: Sie fehlt in beiden Editoren und insession.Tools, daher findenSetActiveTool,RTSEditorContext.ActivateToolundFindToolsie nicht. Um ein solches Werkzeug (zum Beispiel ein Debug-Werkzeug) aus Code heraus zu verwenden, weise direkt eine Instanz zu:session.ActiveTool = new MyDebugTool();. Setze im Unity Editor zusätzlichRTSEditorContext.EditingEnabled = true. - Die Registry durchsucht nur die Runtime-Assembly des Pakets und die Assemblies, die sie direkt oder über eine andere solche Assembly referenzieren. Ein Werkzeug erbt immer von
RTSTool, daher referenziert seine Assembly die Runtime-Assembly ohnehin (Skripte ohne Assembly Definition liegen inAssembly-CSharp, die sie automatisch referenziert). Die Registry überspringt Assemblies, deren Namen mitSystem,Unity.,UnityEngine,UnityEditor,Mono.,mscorlib,netstandardodernunitbeginnen. Gib deiner eigenen Assembly keinen solchen Namen. - Code-Stripping: Werkzeuge werden per Reflection erzeugt. Bei einem Build mit einem Managed Stripping Level über Minimal erkennt das Code-Stripping deshalb nicht, dass sie verwendet werden. Die Datei
Runtime/link.xmldes Pakets sorgt dafür, dass die integrierten Werkzeuge erhalten bleiben; markiere deine eigenen mit[UnityEngine.Scripting.Preserve]oder trage sie in einelink.xmldeines Projekts ein (siehe Code-Stripping und eigene Werkzeuge). - Aus Code aktivierst du ein Werkzeug mit
session.SetActiveTool("height.crater")oder im Editor mitRTSEditorContext.ActivateTool("height.crater").
Die Sitzung per Code steuern
Pinsel, Symmetrie und Pinselstriche beider Editoren laufen über eine RTSEditSession. Mit derselben API kannst du dein eigenes Frontend bauen, zum Beispiel eine eigene Maloberfläche, ein Tutorial, das für den Spieler malt, oder ein Level-Skript.
| Member | Beschreibung |
|---|---|
BrushSettings Brush | Der gemeinsam genutzte Pinsel: shape, customShape, radius, strength, hardness, useFalloffCurve, falloffCurve, rotation, randomRotation, spacing, jitter. Rufe Brush.MarkDirty() auf, nachdem du die Keys von falloffCurve per Code geändert hast: Die gebackene Pinselmaske erkennt Änderungen an Form, Härte, useFalloffCurve und customShape, aber nicht an den Keys der Kurve. |
SymmetrySettings Symmetry | mode (SymmetryMode) und center (normalisiert, 0.5/0.5 = Kartenmitte). GetPoints(localPos, mapWorldSize, mergeDistance, results) liefert den ursprünglichen Punkt und seine Kopien. |
ScaleRadius(factor), AddStrength(delta) | Entspricht den Tastenkürzeln [ ] und - =, begrenzt auf die gültigen Bereiche. |
bool InvertModifier | Wie das Gedrückthalten von Shift: absenken statt anheben, radieren statt malen. |
BeginStroke(worldPoint), UpdateStroke(worldPoint, deltaTime), EndStroke(), CancelStroke() | Ein Pinselstrich vom Drücken bis zum Loslassen. Rufe UpdateStroke in jedem Frame auf, solange die Maustaste gedrückt ist (auch wenn sich der Zeiger nicht bewegt, damit wiederholt wirkende Werkzeuge weiterarbeiten). EndStroke legt einen Undo-Schritt an; CancelStroke macht den Pinselstrich rückgängig. |
Pick(worldPoint) | Das Aufnehmen eines Werts per Ctrl+Klick mit dem aktiven Werkzeug. |
FindTool(id), FindTool<T>(), GetTools(category), SetActiveTool(id), ActiveTool | Werkzeuge finden und aktivieren. |
ActiveToolChanged, StrokeEnded, UndoRedoPerformed, SelectionChanged | Events für deine Oberfläche: Das aktive Werkzeug wurde gewechselt, ein Pinselstrich wurde abgeschlossen (wird bei abgebrochenen Pinselstrichen nicht ausgelöst), Rückgängig oder Wiederherstellen wurde ausgeführt, die Dekorauswahl hat sich geändert. |
using RTSTerrainStudio;
using UnityEngine;
// Paints with the built-in tools from your own code, for example from a custom UI.
// Call PressAt / DragTo / Release with world positions on the terrain (for example from terrain.Raycast).
public class SessionDriverExample : MonoBehaviour
{
public RTSTerrain terrain;
RTSEditSession session;
void OnEnable()
{
// In a scene with the in-game editor you can use RTSRuntimeEditor.Session instead.
session = new RTSEditSession();
session.Terrain = terrain;
// Brush and symmetry are shared by every tool of this session.
session.Brush.radius = 8f;
session.Brush.strength = 0.6f;
session.Symmetry.mode = SymmetryMode.Rotate2; // 1v1 point symmetry
session.Symmetry.center = new Vector2(0.5f, 0.5f); // map centre
session.SetActiveTool("height.raise");
session.StrokeEnded += () => Debug.Log("Stroke finished (one undo step)");
}
void OnDisable()
{
session?.Dispose();
session = null;
}
public void PressAt(Vector3 worldPoint, bool lower)
{
session.InvertModifier = lower; // like holding Shift
session.BeginStroke(worldPoint);
}
// Call every frame while the button is held.
public void DragTo(Vector3 worldPoint) => session.UpdateStroke(worldPoint, Time.deltaTime);
public void Release() => session.EndStroke();
public void Abort() => session.CancelStroke(); // reverts the running stroke
public void UseLinearFalloff()
{
session.Brush.useFalloffCurve = true;
session.Brush.falloffCurve = AnimationCurve.Linear(0f, 1f, 1f, 0f);
// Curve keys are not detected automatically: rebuild the baked brush mask.
session.Brush.MarkDirty();
}
}
Events
| Event | Signatur | Ausgelöst |
|---|---|---|
MapLoaded | Action<RTSTerrain> | Nachdem eine neue Karte zugewiesen wurde (Laden, neue Karte, Größenänderung, Neuaufbau). Baue hier alles neu auf, was du aus der Karte ableitest. |
MapUnloading | Action<RTSTerrain> | Bevor die aktuelle Karte ersetzt oder freigegeben wird. Verwirf hier deine Referenzen auf sie. |
MapChanged | Action<RTSTerrain, MapChangeEventArgs> | Sofort beim Aufruf von NotifyChanged / NotifyChangedAll, bevor die Meshes neu aufgebaut werden. MapChangeEventArgs enthält Layers, CellRect und IsFullRefresh. Wird während Pinselstrichen häufig ausgelöst: Halte die Handler schlank. |
ChangesProcessed | Action<RTSTerrain, IReadOnlyList<DirtyRegion>> | Nachdem ausstehende Änderungen verarbeitet wurden: Meshes, Texturen und Pathing sind auf dem neuesten Stand. Jede DirtyRegion enthält layers und cellRect. |
PathingChanged | Action<RTSTerrain, RectInt> | Nachdem die Pathing-Karte (teilweise) neu aufgebaut wurde; das Rechteck ist in Pathing-Zellen angegeben. Bleibt auch beim Neuladen der Karte bestehen (anders als PathingMap.Changed). |
TilesetChanged | Action<RTSTerrain> | Die Tileset-Referenz oder der Inhalt des Tilesets hat sich geändert. |
LibrariesChanged | Action<RTSTerrain> | Die Referenz auf eine Vegetations- oder Dekorbibliothek oder deren Inhalt hat sich geändert. |
RTSTerrain.TerrainEnabled / TerrainDisabled | static Action<RTSTerrain> | Ein Terrain wurde aktiviert oder deaktiviert (Änderungen an der Registry). |
RTSRuntimeEditorEvents.PlaytestChanged | static Action<bool> | Der In-Game-Editor hat einen Spieltest gestartet oder beendet. Spawne hier deine Gameplay-Objekte und räume sie wieder auf; die Karte wird danach wiederhergestellt. |
RTSRuntimeEditorEvents.MapOpened | static Action<RTSTerrain> | Der In-Game-Editor hat eine Karte geöffnet. |
RTSEditSession: ActiveToolChanged, TerrainChanged, StrokeEnded, UndoRedoPerformed, SelectionChanged | Bearbeitungszustand einer Sitzung (Editor oder In-Game-Editor). |
using System.Collections.Generic;
using RTSTerrainStudio;
using UnityEngine;
// Listens to all important terrain events. Put it on the RTS Terrain GameObject (or assign the terrain).
public class TerrainEventsExample : MonoBehaviour
{
[SerializeField] RTSTerrain terrain;
void OnEnable()
{
if (terrain == null) terrain = GetComponent<RTSTerrain>();
if (terrain == null) return;
terrain.MapLoaded += OnMapLoaded; // new map assigned (load, new map, resize)
terrain.MapChanged += OnMapChanged; // immediately when NotifyChanged is called
terrain.ChangesProcessed += OnChangesProcessed; // after meshes, textures and pathing are up to date
terrain.PathingChanged += OnPathingChanged; // after the pathing map was rebuilt
terrain.TilesetChanged += OnTilesetChanged; // tileset reference or content changed
// The map may already be loaded before this component was enabled.
if (terrain.Map != null) OnMapLoaded(terrain);
}
void OnDisable()
{
// Always unsubscribe: terrains survive scene changes in the editor and domain reloads.
if (terrain == null) return;
terrain.MapLoaded -= OnMapLoaded;
terrain.MapChanged -= OnMapChanged;
terrain.ChangesProcessed -= OnChangesProcessed;
terrain.PathingChanged -= OnPathingChanged;
terrain.TilesetChanged -= OnTilesetChanged;
}
void OnMapLoaded(RTSTerrain t)
{
// Rebuild everything your game derives from the map, e.g. spawn points from start locations.
Debug.Log($"Map '{t.Map.Info.name}' loaded: {t.Map.Width} x {t.Map.Height} cells, " +
$"{t.Map.StartLocations.Count} start locations.");
}
void OnMapChanged(RTSTerrain t, MapChangeEventArgs e)
{
// Raised often during brush strokes: keep it cheap. Check which layers changed.
if ((e.Layers & (MapLayers.Trees | MapLayers.Decor)) != 0)
Debug.Log($"Objects changed in cells {e.CellRect} (full refresh: {e.IsFullRefresh})");
}
void OnChangesProcessed(RTSTerrain t, IReadOnlyList<DirtyRegion> regions)
{
// Meshes are rebuilt now, so GetHeight and Raycast return the new surface.
foreach (DirtyRegion r in regions)
if ((r.layers & MapLayers.Geometry) != 0)
Debug.Log($"Ground changed in cells {r.cellRect}");
}
void OnPathingChanged(RTSTerrain t, RectInt pathingRect)
{
// The rectangle is in pathing cells (Map.PathingResolution per map cell). Re-plan paths here.
Debug.Log($"Pathing changed in {pathingRect}");
}
void OnTilesetChanged(RTSTerrain t)
{
Debug.Log("Tileset is now " + (t.Tileset != null ? t.Tileset.name : "none"));
}
}
Speichern und Laden zur Laufzeit, eigene Dateiabschnitte
Karten sind .rtsmap-Dateien. Dasselbe Format verwenden der Unity Editor (importiert als RTSMapAsset), der In-Game-Editor und das Karten-Streaming über Mirror. Eine von einem Spieler erstellte Karte öffnet sich daher unverändert in Unity und umgekehrt.
| Member | Beschreibung |
|---|---|
terrain.SaveToFile(path) / terrain.LoadFromFile(path) | Speichern und Laden zur Laufzeit. Beide setzen CurrentFilePath; das Speichern setzt HasUnsavedChanges zurück. |
terrain.MapAsset, terrain.LoadFromAsset() | Verwendet eine Karte, die ins Projekt importiert wurde (Kampagnenkarten, mitgelieferte Gefechtskarten). |
terrain.SetMap(MapData) | Zeigt beliebige MapData an. Das Terrain wird Eigentümer der Daten und gibt die vorherige Karte frei. |
RTSMapSerializer.Serialize(map) / Deserialize(bytes) | Karten als Byte-Daten (Netzwerk, Cloud-Spielstände, Mod-Downloads). Eine deserialisierte Karte gehört dem Aufrufer. |
RTSMapSerializer.SaveToFile, LoadFromFile, Write(map, stream), Read(stream) | Datei- und Stream-Zugriff ohne Terrain. |
RTSMapSerializer.TryReadHeaderFromFile(path, out header) | Liest für Kartenbrowser nur den kleinen Dateikopf (Name, Autor, Beschreibung, Größe, Zellgröße, maximale Spielerzahl, Tileset-ID, Änderungszeitpunkt). |
RTSMapSerializer.DefaultMapsFolder, FileExtension | Der Ordner Maps in StorageRoot (wird vom In-Game-Editor verwendet, sofern dort kein Maps Folder gesetzt ist) und ".rtsmap". |
RTSMapSerializer.StorageRoot | Der Ordner für Dateien, die zwischen Sitzungen erhalten bleiben: Application.persistentDataPath. In Web-Builds ist es ein Ordner im Browserspeicher, der nach dem Firmen- und Produktnamen benannt ist, weil sich persistentDataPath dort mit der Adresse der Seite ändert. |
RTSWebFileSystem.Flush(), DownloadFile(...), IsSupported | Für Web-Builds: geänderte Dateien in den Browserspeicher schreiben, damit sie ein Neuladen der Seite überstehen, und eine Datei vom Browser herunterladen lassen. RTSMapSerializer.SaveToFile und terrain.SaveToFile erledigen das Schreiben in den Browserspeicher selbst; rufe Flush() nach deinen eigenen Schreib- und Löschvorgängen mit System.IO auf. Auf anderen Plattformen tun die Methoden nichts. Siehe Scripting-API für Web-Builds. |
using System.Collections.Generic;
using System.IO;
using RTSTerrainStudio;
using UnityEngine;
// Saving, listing and loading maps at runtime (for example for your own map browser or a save game).
public static class MapFilesExample
{
// The folder the in-game editor uses too: Maps in RTSMapSerializer.StorageRoot.
static string Folder => RTSMapSerializer.DefaultMapsFolder;
public static void Save(RTSTerrain terrain, string fileName)
{
// SaveToFile creates the folder when needed and remembers the path in terrain.CurrentFilePath.
terrain.SaveToFile(Path.Combine(Folder, fileName + RTSMapSerializer.FileExtension));
}
public static void Load(RTSTerrain terrain, string fileName)
{
string path = Path.Combine(Folder, fileName + RTSMapSerializer.FileExtension);
if (File.Exists(path)) terrain.LoadFromFile(path); // raises MapLoaded
}
// Lists all maps with their header (name, size, players) without loading the whole file.
public static List<RTSMapHeader> ListMaps()
{
var result = new List<RTSMapHeader>();
if (!Directory.Exists(Folder)) return result;
foreach (string path in Directory.GetFiles(Folder, "*" + RTSMapSerializer.FileExtension))
if (RTSMapSerializer.TryReadHeaderFromFile(path, out RTSMapHeader header))
result.Add(header);
return result;
}
// Maps can also travel as bytes (network, cloud save, mod download).
public static byte[] ToBytes(RTSTerrain terrain) => RTSMapSerializer.Serialize(terrain.Map);
public static void FromBytes(RTSTerrain terrain, byte[] bytes)
{
// The terrain takes ownership of the new MapData and disposes the previous map.
terrain.SetMap(RTSMapSerializer.Deserialize(bytes));
}
// Loads a .rtsmap that was imported into the project (an RTSMapAsset), e.g. for a campaign.
public static void LoadAsset(RTSTerrain terrain, RTSMapAsset asset)
{
// Assigning a different asset loads it immediately (when the terrain is enabled).
// Assigning the same asset again does nothing, so reload it explicitly.
if (terrain.MapAsset == asset) terrain.LoadFromAsset();
else terrain.MapAsset = asset;
}
}
Dateiaufbau und eigene Dateiabschnitte
"RTSM" 4 bytes magic
int32 format version (RTSMapSerializer.CurrentVersion = 1)
int32 + bytes uncompressed header (RTSMapHeader)
deflate stream sections: [int32 tag][int32 length][payload] ...
(built-in sections first, then the CustomSectionWriter sections)
int32 0 end marker
Beim Lesen werden unbekannte Abschnitte übersprungen. Deshalb kannst du eigene Daten in einer Kartendatei speichern (Trigger, Startressourcen, Questdaten), und ältere Versionen oder andere Werkzeuge lesen die Karte trotzdem. Registriere RTSMapSerializer.CustomSectionWriter, um beim Speichern Abschnitte hinzuzufügen, und RTSMapSerializer.CustomSectionReader, um beim Laden jeden unbekannten Abschnitt zu erhalten. Verwende Tags, die mit 'X' beginnen, damit sie nie mit den eingebauten Abschnitten kollidieren. Tag 0 ist reserviert: Es markiert das Ende der Abschnitte.
Jedes System muss ein eigenes, eindeutiges Tag 'X…' verwenden, denn CustomSectionReader wird für jeden unbekannten Abschnitt aufgerufen. Schreiben zwei Writer dasselbe Tag mit unterschiedlichen Nutzdaten, erhält ein Reader Nutzdaten, die er nicht versteht, und das Laden der Karte schlägt fehl. So verwendet etwa StartingGoldSection im Abschnitt Eigene Dateiabschnitte das Tag XGLD, deshalb nutzt das folgende Beispiel XGMD.
using System.IO;
using System.Runtime.CompilerServices;
using RTSTerrainStudio;
using UnityEngine;
// Stores your own game data (here: starting gold and a victory text) inside the .rtsmap file.
// Older versions of the package and other games simply skip the unknown section.
public static class MapGameData
{
public sealed class Data
{
public int startingGold = 500;
public string victoryText = "";
}
// Section tag "XGMD" as a little-endian FourCC. Start your tags with 'X' so they never
// collide with the built-in sections, and use a tag no other script of your project uses.
const int Tag = 'X' | ('G' << 8) | ('M' << 16) | ('D' << 24);
// Bump this when you change the payload layout, and keep reading the old layouts.
const int PayloadVersion = 1;
// One Data object per MapData instance. The table does not keep maps alive.
static readonly ConditionalWeakTable<MapData, Data> s_Data = new ConditionalWeakTable<MapData, Data>();
// Returns the game data of a map (created with defaults on first access).
public static Data Get(MapData map) => s_Data.GetValue(map, _ => new Data());
// Registers the hooks before any scene loads (play mode and builds) and, in the editor,
// after every script reload, so maps saved from the RTS Terrain Studio window keep the data too.
#if UNITY_EDITOR
[UnityEditor.InitializeOnLoadMethod]
#endif
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
static void Register()
{
// Remove first, so entering play mode without a domain reload never registers twice.
RTSMapSerializer.CustomSectionWriter -= Write;
RTSMapSerializer.CustomSectionWriter += Write;
RTSMapSerializer.CustomSectionReader -= Read;
RTSMapSerializer.CustomSectionReader += Read;
}
// Called while a map is saved: hand every section you want to write to addSection(tag, payload).
static void Write(MapData map, System.Action<int, byte[]> addSection)
{
if (!s_Data.TryGetValue(map, out Data data)) return; // nothing set: write no section
using var stream = new MemoryStream();
using (var w = new BinaryWriter(stream))
{
w.Write(PayloadVersion);
w.Write(data.startingGold);
w.Write(data.victoryText ?? "");
}
addSection(Tag, stream.ToArray());
}
// Called for every section the serializer does not know. Ignore tags that are not yours,
// other scripts may have registered readers too.
static void Read(MapData map, int tag, byte[] payload)
{
if (tag != Tag) return;
using var r = new BinaryReader(new MemoryStream(payload));
int version = r.ReadInt32();
var data = Get(map);
data.startingGold = r.ReadInt32();
if (version >= 1) data.victoryText = r.ReadString();
}
}
MapData-Objekt. Operationen, die ein neues Kartenobjekt erzeugen (Laden, Größenänderung, Clone), übernehmen sie nicht von selbst. Lies sie erneut aus der Datei, oder kopiere sie in deinem eigenen Code, wenn du eine Karte klonst oder ihre Größe änderst.Häufig gestellte Fragen
Alles ist pink (magenta). Was stimmt nicht?
Pink bedeutet, dass ein Shader nicht verwendet werden konnte. RTS Terrain Studio verwendet handgeschriebene Shader ausschließlich für die Universal Render Pipeline.
- Öffne . Die Prüfung Universal Render Pipeline is active muss grün sein; Fix All (oder die Fix-Schaltfläche der Prüfung) weist ein URP-Asset zu.
- Kontrolliere im selben Fenster die Prüfung Package shaders compile. Hat ein Shader Fehler, nennt ihn die Console; importiere dann den Ordner
Shadersdes Pakets neu (Rechtsklick ▸ Reimport). - Wenn nur deine eigenen Prefabs pink sind (Bäume, Felsen, Einheiten), verwenden ihre Materialien Shader der Built-in-Pipeline. Konvertiere sie mit dem Render Pipeline Converter von Unity, oder weise ihnen URP-Materialien zu.
Ich habe ein RTS Terrain hinzugefügt, aber es wird nichts gerendert.
- Wähle das Terrain aus und sieh dir seinen Inspector an. Meldet er, dass keine Karte geladen ist, weise ein Karten-Asset zu, erstelle eine neue Karte (Fenster ▸ Tab Map ▸ New Map) oder verwende den New Map Wizard.
- Stelle sicher, dass die Komponente
RTSTerrainaktiviert ist. Eine deaktivierte Komponente entlädt ihre Karte. - Prüfe die Kamera: Die Chunks liegen auf dem Layer, der in den Terrain-Einstellungen festgelegt ist (Chunk Layer), und dieser Layer muss in der Culling Mask der Kamera enthalten sein. Hole das Terrain in der Scene View mit F ins Bild: Eine Karte mit 128 Zellen zu je 2 m ist 256 m breit.
- Ein Terrain ohne Tileset ist nicht unsichtbar, sondern wird mit neutralen Ersatzfarben gerendert. Weise ein Tileset zu, oder führe aus und verwende das Demo-Tileset.
- Klicke im Terrain-Inspector auf Rebuild Terrain, um alle Chunks, Texturen und die Pathing-Karte neu zu erstellen.
Der Pinsel malt nicht dort, wo sich mein Cursor befindet.
Der Editor verwendet Unitys DPI-korrekten Strahl der Scene View, und der Pinselcursor wird vom Terrain-Shader gezeichnet. Bei einer normalen Einrichtung liegt der Pinselauftrag daher immer genau unter dem Cursor, auch bei einer Anzeigeskalierung von 150 % oder mehr. Falls nicht:
- Das Terrain oder ein übergeordnetes Objekt ist skaliert oder um X oder Z gedreht. Verwendet werden nur die Position und die Y-Rotation. Der Terrain-Inspector warnt vor beidem; setze die Skalierung auf 1 und die X/Z-Rotation auf 0 zurück, und ändere statt einer Skalierung die Zellgröße der Karte.
- Das Terrain verwendet einen Material Override, dessen Shader den Pinselcursor nicht zeichnet. Du malst dann an der richtigen Stelle, siehst aber den Cursor nicht. Verwende den Terrain-Shader des Pakets, oder übernimm dessen Code für den Pinselcursor.
Der Nebel des Krieges ist nicht sichtbar.
- Die Szene braucht genau einen
RTSFogOfWarManager(). - Führe aus und behebe das Problem bei Fog of War renderer feature. Das Renderer Feature verdunkelt alles auf dem Bildschirm. Ohne dieses Feature berücksichtigen nur die paketeigenen Shader (Terrain, Wasser, Vegetation) den Nebel; deine eigenen Einheiten und Props bleiben unverdeckt.
- Aktiviere im Edit Mode die Option Preview In Edit Mode am Manager.
- Reveal All muss ausgeschaltet sein. Start Explored lässt die ganze Karte als erkundet erscheinen (abgedunkelt statt schwarz).
- Deine Einheiten brauchen einen
RTSFogOfWarRevealer, dessen Team das Local Team des Managers ist. Sichtquellen anderer Teams decken für das lokale Team nichts auf; ohne eine passende Sichtquelle bleibt die Karte daher verdeckt (standardmäßig schwarz).
Vegetation (oder Wasser oder das Terrain) fehlt in Builds, im Editor ist aber alles in Ordnung.
- Führe aus und behebe das Problem bei Shaders included in builds. Das Paket listet seine Shader in einem Shader-Referenz-Asset unter
Resources, das vor jedem Player-Build automatisch aktualisiert wird. Hast du es gelöscht oder verschoben, erstellt die Prüfung es neu. - Behebe das Problem bei GPU instancing on instanced prototypes: Instanzierte Bäume, Details und Dekorobjekte brauchen Materialien mit aktiviertem Enable GPU Instancing.
- Stelle sicher, dass die Karte im Build enthalten ist: Karten, die als
RTSMapAsseteinem Terrain in einer Szene des Builds zugewiesen sind, werden automatisch aufgenommen; Kartendateien, die von der Festplatte geladen werden, müssen auf dem Rechner des Spielers vorhanden sein (zum Beispiel inRTSMapSerializer.DefaultMapsFolder). - Shader-Stripping oder ein anderes URP-Asset für die Zielplattform (Quality-Einstellungen) kann Funktionen entfernen. Prüfe, ob das URP-Asset des Builds dieselben Einstellungen hat wie das im Editor.
- In einem Web-Build unter WebGL 2.0 kann ein URP-Asset mit vielen aktivierten Funktionen die Grenze von 16 Texturen pro Draw Call überschreiten, und das Terrain verschwindet. Siehe den Hinweis zu OpenGL ES und WebGL 2.0 unter Hinweise für Mobilgeräte.
- Atmosphärischer Nebel fehlt in Builds: siehe Nebel in Player-Builds.
Wasser sieht schwarz oder grau aus oder hat keine Lichtbrechung.
- Der Wasser-Shader liest die Tiefe und die Farbe der Szene. Aktiviere Depth Texture und Opaque Texture im URP-Asset: ▸ Depth and Opaque Texture enabled ▸ Fix.
- Setze Opaque Downsampling des URP-Assets auf None; Downsampling macht die Lichtbrechung unscharf.
- Prüfe die Kamera: Ihre Overrides für Depth Texture und Opaque Texture dürfen nicht auf Off stehen. Das Wasser richtet sich nach der Einstellung der jeweiligen Kamera, und die Scene View verwendet immer das URP-Asset.
- Prüfe bei einem Web-Build das URP-Asset der Qualitätsstufe, die Web-Builds verwenden (die Prüfung Web quality levels have Depth and Opaque Texture, siehe Web-Prüfungen im Project Validator).
- Die Wasserfarben stammen aus dem Water Profile des Tilesets. Prüfe dort die Farben für Flach- und Tiefwasser.
Wie erreiche ich die beste Performance?
- Burst muss aktiviert sein (). Pinsel, Pathing, Nebel des Krieges, der Generator und die Minikarte laufen als Burst-Jobs.
- Chunk size (Terrain-Einstellungen): 16, 32 (Standard) oder 64 Zellen. Kleinere Chunks werden beim Bearbeiten schneller neu aufgebaut, größere Chunks bedeuten im Spiel weniger Draw Calls.
- Surface Subdivisions: 1 ergibt den klassischen RTS-Look mit den wenigsten Dreiecken, 2–4 ergeben sanftere Hügel.
- Collider Mode None lässt die Mesh-Collider weg, wenn du nur die Abfrage-API verwendest (
Raycast,GetHeight). - Verwende für Bäume und Dekor bevorzugt den Spawn-Modus Instanced; nutze den Modus GameObject nur für Objekte, die dein Gameplay als GameObjects braucht (abbaubare Bäume, Dekor mit Skripten).
- Verringere für schwächere Hardware Detail Density Multiplier und Detail Distance Multiplier am Vegetation Renderer.
- Nebel des Krieges: Resolution Per Cell 1 und ein größeres Update Interval kosten weniger Leistung.
- Führe den Kartenvalidator aus: Seine Performance-Warnungen melden zu viele Bäume, Dekorobjekte, Objekte im GameObject-Modus oder Detailschichten.
Kann ich mehrere Terrains in einer Szene verwenden?
Ja. Jedes RTSTerrain hat seine eigene Karte, sein eigenes Tileset, eigene Bibliotheken und einen eigenen Transform. RTSTerrain.All listet alle auf, RTSTerrain.FindAt(position) findet das Terrain unter einer Position, und RTSTerrain.RaycastAll führt einen Raycast gegen alle aus. Im Editorfenster legst du das bearbeitete Terrain in der Kopfzeile fest (es wird auch übernommen, wenn du ein Terrain in der Szene auswählst); im In-Game-Editor verwendest du Map ▸ Edited terrain. Hinweise:
- Der Undo-Verlauf gehört zum bearbeiteten Terrain: Wechselst du das bearbeitete Terrain, wird er gelöscht.
- Der Manager für den Nebel des Krieges deckt ein Terrain ab (sein Feld Terrain oder
RTSTerrain.Main, wenn es leer ist). - A* braucht pro Terrain einen Graph Builder mit eigenen Graph-Namen; die NavMesh-Integration braucht einen Builder pro Terrain.
Kann ich ein Terrain verschieben oder drehen?
Ja: Position und Y-Rotation werden vollständig unterstützt, auch zur Laufzeit. Alle Abfragen, Pinsel, Vegetation, Dekor, Wasser, Pathing, der Nebel des Krieges und die Integrationen folgen dem Transform. Rotation um X oder Z und Skalierung werden nicht unterstützt (der Inspector warnt davor); um eine Karte zu vergrößern, verwende Resize im Tab Map oder eine größere Zellgröße.
Funktioniert es mit dem Input System, dem alten Input Manager oder mit beiden?
Alle drei Einstellungen von funktionieren. Laufzeitcode liest Eingaben über RTSInput. Diese Klasse verwendet das Paket Input System, wenn es installiert und aktiv ist, und sonst den alten Input Manager. Die Prüfung Input handling von zeigt, was dein Projekt verwendet. Auch in deinem eigenen Gameplay-Code kannst du RTSInput verwenden (PointerPosition, GetMouseButtonDown, GetKey(RTSKey) …), damit er mit jeder dieser Einstellungen läuft. Für Tastenkürzel mit Buchstaben, die dem Tastaturlayout des Spielers folgen sollen, verwende GetKeyDownByLabel(RTSKey); für Symboltasten wie [ oder + verwende GetCharacterDown(char).
Kann ich mein Spiel oder den In-Game-Editor in einem Webbrowser veröffentlichen?
Ja. In Web-Builds (WebGL 2.0) laufen das Terrain, die Vegetation, das Wasser, der Nebel des Krieges, der In-Game-Editor und die Beispiele in Desktop-Browsern mit Maus und Tastatur. Installiere das Vollfenster-Template mit . Die Einstellungen, das Speichern von Karten im Browser und Hinweise zum Hosting findest du unter Web-Builds (WebGL).
Kann ich das Paket in einen anderen Ordner verschieben?
Ja. Nichts hängt von einem festen Pfad ab; das Paket findet seine Dateien relativ zu seinem eigenen Speicherort.
Fehlerbehebung
Arbeite diese Checkliste von oben nach unten ab; die meisten Probleme sind nach den ersten drei Schritten gelöst.
- Lies die Console. Meldungen des Pakets beginnen mit
[RTS Terrain Studio]und sind mit dem Objekt verknüpft, zu dem sie gehören (klicke auf die Meldung, um es auszuwählen). Behebe zuerst Kompilierfehler: Solange sich ein Skript nicht kompilieren lässt, wird nichts anderes aktualisiert. - Validiere das Projekt. prüft URP, Depth Texture und Opaque Texture, das Renderer Feature für den Nebel des Krieges, die in Builds erhaltenen Modi des atmosphärischen Nebels, den Farbraum, Burst/Collections/Mathematics, die Eingabeverarbeitung, die Shader-Kompilierung, die Shader in Builds, die Textur-Arrays der Tilesets, GPU-Instancing, Terrains mit Tilesets und Terrains, die in Kartendateien speichern. Ist Web die aktive Plattform, kommen die Web-Qualitätsstufen und das Web-Template hinzu. Klicke auf Fix All (ein Dialog listet vorher jede Änderung auf) und dann auf Run Again.
- Prüfe den Terrain-Inspector. Ist eine Karte geladen, ein Tileset zugewiesen, die Komponente aktiviert? Gibt es Warnungen zu Skalierung oder Rotation? Klicke auf Rebuild Terrain.
- Validiere die Karte. Tab Map ▸ Validation ▸ Validate Map findet fehlende Prototypen, fehlerhafte Bodentexturierung, unerreichbare Startpositionen und Performance-Probleme.
- Prüfe die Inhalte. Fehlende Texturen, Pflanzen oder Dekorobjekte bedeuten meist, dass ein Tileset oder eine Bibliothek nicht zugewiesen ist oder dass eine Prototyp-ID der Karte in der Bibliothek nicht existiert. Wenn du erneut ausführst, werden die Demo-Assets wiederhergestellt.
- Prüfe die Integrationen. Kompilierfehler in
Integrationsnach dem Entfernen von Mirror bedeuten, dass das SymbolMIRRORnoch definiert ist (Mirror entfernen). Eine Integration, die im Welcome-Fenster als Installed, aber nicht als Active markiert ist, hat eine Version, die ihre Anforderung nicht erfüllt. - Play Mode ohne Domain Reload wird unterstützt. Verhält sich etwas beim zweiten Start des Play Mode anders, aktiviere einmal den Domain Reload, um zu sehen, ob dein eigener statischer Zustand die Ursache ist.
- Kommst du immer noch nicht weiter? Reproduziere das Problem in einer neuen Szene mit einem Terrain, das über erstellt wurde, und mit den Demo-Inhalten. Gib die Unity-Version, die URP-Version, die Console-Ausgabe und die Schritte an, wenn du um Hilfe bittest.
| Symptom | Wahrscheinliche Ursache | Siehe |
|---|---|---|
| Pinke Materialien | URP nicht aktiv, Shader-Fehler, Built-in-Materialien an deinen Prefabs | FAQ |
| Terrain unsichtbar | Keine Karte geladen, Komponente deaktiviert, Chunk-Layer per Culling ausgeblendet | FAQ |
| Pinsel malt neben dem Cursor | Skaliertes Terrain oder X/Z-Rotation | FAQ |
| Kein Nebel des Krieges | Renderer Feature fehlt, keine Sichtquelle des lokalen Teams, Reveal All an | FAQ |
| Schwarzes Wasser | Depth / Opaque Texture aus | FAQ |
| Fehlende Vegetation in Builds | Shader-Referenzen, GPU-Instancing | FAQ |
| Einheiten bewegen sich nicht | Pathing-Karte noch nicht erstellt, Start oder Ziel nicht begehbar, Provider ohne Graph oder NavMesh | Kartenvalidierung, Integrationen |
| Generator platziert weniger Startpositionen | Nicht genug ebene Fläche für Flat Radius / Edge Margin | Generator-Einstellungen |
| Heightmap-Import ist flach oder extrem | Höhenskalierung 0 im Code, falsche RAW-Größe oder Byte-Reihenfolge | Heightmaps |
| Kompilierfehler zu Mirror nach dessen Entfernen | Veraltetes Define MIRROR | Integrationen entfernen |
| Dialog RTS Terrain Studio requires URP | URP 17.3 nicht installiert oder Editor älter als Unity 6.3 | Unity und Render-Pipeline |
| Web-Build: winzige UI, verlorene Karten, Tasten öffnen Browserfunktionen | Template mit fester Größe, Browserspeicher, Tastenkürzel des Browsers | Web-Builds (WebGL) |
Änderungsprotokoll
Version 1.0.0 – Erstveröffentlichung
Erfordert Unity 6.3 LTS (6000.3) und die Universal Render Pipeline 17.3 mit Burst, Collections und Mathematics.
Kartenmodell
- Raster aus Zellen und Eckvertices im Stil klassischer RTS-Spiele, 16 bis 1024 Zellen pro Seite (Vielfache von 16), frei wählbare Zellgröße.
- 16 Klippenstufen mit prozedural erzeugten, abgerundeten Klippenwänden; Feinhöhe über jeder Stufe; Rampen; 4 Klippentypen pro Tileset.
- 16 Bodenschichten pro Tileset in hochauflösenden Splatmaps, höhenbasierte Überblendung, optionales Triplanar-Mapping und Anti-Tiling; beliebige Texturgrößen und -formate.
- Wasser pro Vertex mit Flach- und Tiefwasser.
- Automatisch abgeleitete Pathing-Karte (begehbar, bebaubar, befliegbar, Flach- und Tiefwasser) plus gemalte Overrides.
- Regionen, Startpositionen, Marker, Karteninfo und Umgebungsbeleuchtung werden in der Karte gespeichert.
Bearbeitung
- Ein gemeinsamer Werkzeugsatz für den Unity Editor und den In-Game-Editor: Höhe, Klippen, Malen (weich, kachelweise, Füllen, automatische Texturierung), Vegetation, Dekor, Wasser, Pathing und Marker.
- Pinsel mit Kreis-, Quadrat-, Rauten- und Texturstempelform, Abnahmekurve, Rotation, Abstand und Streuung; symmetrisches Malen (Spiegelung an X/Z, vierfache Spiegelung, zwei- und vierfache Rotation).
- Rückgängigmachen und Wiederherstellen mit Speicherbudget, integriert in das Undo-System von Unity.
- Andockbares Fenster von RTS Terrain Studio, Integration in die Scene View mit projiziertem Pinselcursor und Overlays, eigene Inspectors und Importer für alle Assets, Welcome-Fenster, New Map Wizard und Projektvalidator.
- In-Game-Editor auf Basis von UI Toolkit: Werkzeugleiste, Werkzeug- und Pinsel-Panels, Paletten, Kartenbrowser, Tastenkürzel und Spieltest.
Kartenwerkzeuge
- Prozeduraler Kartengenerator mit Seed, sechs Stilen, symmetrischen Layouts, Rampen, Seen und Inseln, Kartenrändern, automatischer Texturierung, Vegetation, Dekor und Startpositionen; speicherbare Presets.
- Import (drei Modi) und Export von 16-Bit-Heightmaps im PNG- und RAW-Format, Export von Splat-Bildern.
- Minikarten-Baker, Kartenvalidator, Größenänderung mit Anker.
- Binäres Format
.rtsmap, das Editor und Spiel gemeinsam nutzen, mit eigenen Dateiabschnitten.
Laufzeitsysteme
- In Chunks unterteilte Terrain-Meshes, gebaut in Burst-Jobs, optionale Mesh-Collider und eine physikfreie Abfrage-API.
- GPU-instanzierte Vegetation mit Wind und Culling; Bäume als Instanzen oder GameObjects.
- Wasser-Rendering mit Tiefenfarbe und Lichtbrechung; Dekor-Manager für beliebige Prefabs.
- Klassischer Nebel des Krieges wie in RTS-Spielen: Sicht pro Team, Gedächtnis für erkundete Gebiete, von Klippen und Blockern unterbrochene Sichtlinie, geteilte Sicht, Unterstützung der Minikarte.
- Eingebauter A*-Pathfinder in Burst mit Einheitengrößen, die den Freiraum berücksichtigen, und Pfadglättung.
- Beispiele: RTS-Kamera, Auswahl, Bewegungsbefehle mit Formationen, Testeinheiten, Minikarten-UI, Demoszenen und generierte Demo-Inhalte.
Plattformen
- Shader für Shader Model 3.5, daher läuft das Paket auch unter OpenGL ES 3.0 und in Web-Builds (WebGL 2.0).
- Web-Builds: Karten im Browserspeicher, Download Map, Import Map und Drag-and-drop, eine Warnung vor dem Verlassen der Seite bei nicht gespeicherten Änderungen, auf den Browser abgestimmte Tastenkürzel, ein fensterfüllendes Web-Template und Web-Prüfungen im Projektvalidator.
- Die Textur-Arrays der Tilesets werden vor Player-Builds automatisch neu erstellt, passend zur Texturkomprimierung des Builds.
- Ohne URP 17.3 bleibt das Paket inaktiv und erklärt, wie du URP installierst, statt Kompilierfehler anzuzeigen.
Integrationen
- Mirror: Karten-Streaming mit Cache, serverautoritative Einheiten, Nebel des Krieges pro Team, Interest Management auf Basis des Nebels des Krieges.
- A* Pathfinding Project Pro: Grid Graphs aus der Pathing-Karte mit inkrementellen Updates,
AIPath- und RVO-Einheiten. - Unity AI Navigation: NavMesh aus dem Terrain und der Pathing-Karte, inkrementelle Updates, Pfad-Provider und NavMeshAgent-Modus.