Canvas
Textfelder, Panels, Fortschrittsbalken, Bilder, Kreise am Boden und Routen mit Pfeil aufs Spielbild zeichnen. Die Runtime zeichnet selbst in jedem Frame und ändert den Spielzustand nicht – auch in Mehrspieler-Partien sicher; per Python, HTTP oder direkt über Shared Memory.
Externe Programme können Textfelder, Panels, Fortschrittsbalken, Bilder, Kreise am Boden und Routen am Boden (mit Pfeil) aufs Spielbild zeichnen; die Runtime zeichnet sie in jedem Frame selbst. Ideal für eigene HUDs, Hilfslinien, Hinweise, Lernmarkierungen und Info-Tafeln für Streams.
Canvas oder JASS-Grafikfunktionen?
| Canvas (diese Seite) | JASS-Grafikfunktionen | |
|---|---|---|
| Wer zeichnet | die Runtime selbst | das Spiel selbst (schwebender Text, Effekte, Panels, Porträt-Dialog …) |
| Mehrspieler | sicher: wird nur auf deinem eigenen Bildschirm gezeichnet, erzeugt keine Spielobjekte, ändert keinen Spielzustand | nur Einzelspieler |
| Stil | frei: eigene Schriften (auch Chinesisch), abgerundete Ecken, Halbtransparenz, Rahmen, beliebige Farben, lokale Bilder | nativer Spielstil |
| Folgt Objekten | Einheiten, Weltkoordinaten, Bildschirmpositionen; Kreise am Boden folgen dem Geländeprofil | je nach Funktion |
| Kosten | gemessen 0.2–0.35 ms pro Frame (9 Elemente) | ca. 13 ms pro Aufruf |
Beide Wege lassen sich kombinieren: Effekte im nativen Stil per JASS, eigene Panels, Hilfslinien und Hinweise per Canvas.
Python
c = g.canvas # beim ersten Zugriff installiert die Runtime den Zeichen-Hook (ca. 0.1 s)
c.text("title", "Hallo, das ist das Canvas", screen=(40, 110), color=(255, 220, 80),
bg=(0, 0, 0, 170), border="#C49C40", size=22, bold=True)
c.panel("status", "Begleiter · Lumi", ["Stimmung: fröhlich", "Kills: 12"], screen=(16, 330))
c.bar("hp", 0.62, unit=hero, lift=260, width=100, height=12, color=(80, 220, 80), text="62%") # folgt der Einheit
c.text("tag", "Boss lädt sein Ultimate auf!", unit=boss, lift=320, color=(255, 80, 80), size=22, bold=True)
c.circle("danger", (x, y), 300, color=(255, 60, 60), fill=(255, 60, 60, 60), width=3) # Gefahrenzone am Boden
c.circle("aura", hero, 450, color=(80, 200, 255, 220)) # Kreis, der der Einheit folgt
c.path("route", [(x1, y1), (x2, y2), (x3, y3)], color=(255, 220, 0), width=5, arrow=True)
c.image("icon", "icon.png", screen=(40, 170), width=64, height=64)
c.remove("danger"); c.hide("tag"); c.clear() # clear entfernt nur, was du selbst gezeichnet hast
c.expire("tag", 5) # verschwindet nach 5 s von selbst
with c.batch(): ... # viele Änderungen auf einmal, Shared Memory wird nur einmal geschrieben
c.stats() # drawnFrames steigt = es wird wirklich gezeichnet
Jedes Element wird über einen key identifiziert: Mit demselben key erneut zeichnen heißt aktualisieren.
Klickbar: Gib Textfeldern und Panels clickable=True (die Hover-Farbe legst du mit hover= fest). Bei einem Klick darauf kommt ein ui.click im Event-Stream an, ev.key ist genau dieser key, und das Spiel bekommt diesen Klick nicht mit. Fertige Buttons, Auswahlkarten, Hotkeys und Klicks auf den Boden findest du unter Oberfläche & Eingabe.
Position (eine pro Element):
screen=(x, y): Bildschirmpixel; negative Werte zählen vom rechten / unteren Rand aus;center=Truerichtet an der Mitte aus;frac=(0.5, 0.1): Anteil der Bildschirmgröße;world=(x, y): Weltkoordinaten;unit=Einheit: folgt der Einheit. Text und Balken in der Welt oder an Einheiten sitzen mit der Mitte ihrer Unterkante auf diesem Punkt,lifthebt sie an.
Elemente in der Welt und an Einheiten weichen standardmäßig der Bedienleiste unten und der Tag-/Nacht-Uhr oben aus (over_ui=True legt sie darüber). Farben schreibst du als (r, g, b), (r, g, b, a), "#RRGGBB" oder "#RRGGBBAA".
| Methode | Zeichnet | Wichtige Parameter |
|---|---|---|
text(key, Text, ...) | Textfeld, mehrzeilig mit \n | color, bg Hintergrund (ohne = transparent), border, size, bold, shadow, width (Umbruch bei dieser Breite), radius abgerundete Ecken |
panel(key, Titel, [Zeilen...], ...) | Panel (dunkler halbtransparenter Hintergrund, goldener Rahmen) | wie text |
bar(key, 0..1, ...) | Fortschrittsbalken: HP, Abklingzeit, Zauberbalken | width, height, color, bg, border, text |
image(key, Pfad, ...) | lokales Bild (png / jpg / bmp / gif) | width, height (ohne = Originalgröße) |
circle(key, Einheit oder Punkt, Radius, ...) | Kreis am Boden, folgt dem Gelände | color Linienfarbe, fill Füllung (mit Transparenz), width Linienbreite |
path(key, [Punkte...], ...) | Linienzug am Boden | color, width, arrow Pfeil am Ende; Punkte können Koordinaten oder Einheiten sein |
HTTP (jede Sprache)
Farsight-Backend (lauscht nur lokal):
POST /api/instances/20/canvas
{"set": [
{"key": "banner", "kind": "text", "text": "Canvas per HTTP", "frac": [0.5, 0.12], "center": true,
"color": "#FFDC50", "bg": [0, 0, 0, 180]},
{"key": "hp", "kind": "bar", "value": 0.8, "unit": 596125988, "lift": 260, "text": "80%"},
{"key": "zone", "kind": "circle", "center": 596125988, "radius": 600, "color": [255, 200, 0], "width": 4},
{"key": "route", "kind": "path", "points": [[-4587, -9092], [-5387, -8792]], "color": "#50C8FF"}
],
"remove": ["old"], "clear": false}
GET /api/instances/20/canvas welche Elemente gerade gezeichnet werden + wie viele Frames gezeichnet wurden
kind ist der Name der Python-Methode, die Parameternamen sind identisch; Einheiten gibst du über ihre Adresse addr aus dem Snapshot an.
Direkt in Shared Memory schreiben
Es geht auch ohne Python und Farsight: Einmal den semantischen Befehl canvas_enable (W3P-Opcode 73) senden, dann legt die Runtime den Shared-Memory-Block Local\War3Canvas_<pid> an: 64-Byte-Header + 256 Einträge × 112 Bytes + 64 KB Pool für Text und Punkte. Geschrieben wird per seqlock (Sequenznummer ungerade → Einträge und Pool schreiben → Sequenznummer gerade); die Runtime liest einmal pro Frame, behält bei halb geschriebenen Daten den Stand des vorigen Frames bei und schreibt die Zahl gezeichneter Frames, die Elementzahl und den Ausnahmezähler zurück. Die Python-Referenzimplementierung ist sdk/python/w3canvas.py; die Strukturen sind im Protokoll-Header definiert, siehe W3P-Protokoll.
Mehrere Programme zeichnen gleichzeitig
Mods, Farsight, MCP und Gateway zeichnen unter Umständen gleichzeitig in dieselbe Partie, es gibt aber nur ein Canvas. Die Regel: Jedes Programm fasst nur seine eigenen Elemente an.
- Vor dem Schreiben eine benannte Sperre holen, die vorhandenen Elemente lesen, die der anderen behalten, die eigenen ersetzen und dann zurückschreiben;
- Jedes Element merkt sich, wer es gezeichnet hat (Prozess-ID + laufende Nummer im Prozess). Hat sich das zeichnende Programm beendet, räumt der nächste Schreibvorgang das Element nebenbei weg, und seine Buttons fangen keine Klicks mehr ab;
- Elementnummern kommen aus einem gemeinsamen Zähler, es gibt also keine Kollisionen.
Das Python-SDK macht das bereits so, und auch clear() entfernt nur die eigenen Elemente. Wenn du selbst direkt in Shared Memory schreibst, halte dich daran, sonst überschreibst du die Elemente anderer. Details zum Speicherlayout siehe W3P-Protokoll.
Messwerte und Hinweise
- Messung vom 2026-09-25 (1920×1080, 2× Geschwindigkeit): 9 Elemente 0.27–0.34 ms pro Frame, ca. 63 Frames pro Sekunde, 0 Ausnahmen; 9 Einträge zu schreiben dauert 6 ms; wenn der Held läuft, halten Kreise, Text und HP-Balken, die an Einheiten hängen, mit. Texturen werden nur neu gerendert, wenn sich der Inhalt ändert, nicht bei reinen Positionsänderungen.
- Das Canvas wird nach der Spieloberfläche und vor dem Mauszeiger gezeichnet: Es liegt über den HP-Balken, Einheiten und der Oberfläche des Spiels, der Mauszeiger liegt darüber. Es weicht der Bedienleiste unten und der Tag-/Nacht-Uhr oben aus, aber nicht den Panels der Karte selbst (Rangliste oben rechts, Countdown) – eigene Panels also nicht oben rechts platzieren.
- Außerhalb einer Partie (Hauptmenü, Ergebnisbildschirm) werden Elemente in Weltkoordinaten und an Einheiten nicht gezeichnet, Elemente mit Bildschirmposition schon.
- Kreise am Boden entstehen, indem 64 Punkte auf dem Umfang einzeln auf den Boden projiziert werden; bei unebenem Gelände verformt sich der Kreis mit – das ist richtig so: Er liegt auf dem echten Boden.
- Beim ersten Öffnen werden der Hook installiert und Schriften vorgewärmt, ca. 1 Sekunde; in dieser Zeit werden Textelemente noch nicht gezeichnet, Kreise und Linien schon.
- Tritt beim Zeichnen auch nur eine Ausnahme auf, wird in dieser Sitzung nichts mehr gezeichnet (derselbe Schutz wie bei den Sprechblasen über Einheiten);
faultsinstats()wird dann 1. - Text, Bildpfade und Punkte teilen sich 64 KB, maximal 256 Elemente; Bildpfade müssen lokale Pfade sein, die der Spielprozess lesen kann.
Das Statuspanel des KI-Begleiters wird mit dem Canvas gezeichnet: HP-Balken, was er gerade tut, Stimmung, Kills und Heilungen.