So codieren Sie Türen und Schlösser
() translation by (you can also view the original English article)
In Spielen aus verbundenen Räumen wie The Legend of Zelda, The Binding of Isaac oder jeder Art von Roguelike oder sogar Metroidvania-ähnlich spielen Türen eine wesentliche Rolle für die Navigation und den Fortschritt des Spielers.
Türen ermöglichen es dem Spieler, von einem Raum oder einer Ebene in einen anderen zu reisen, und haben somit einen wichtigen Platz in der Navigation und der Verbindung der verschiedenen Räume untereinander sowie bei der Definition der Karte als offene Welt oder Dungeonboden. Sie können auch als vorübergehende Straßensperren fungieren, die der Spieler durch einen bestimmten Mechaniker entsperren muss (z. B. durch Erhalten eines Schlüssels oder Aktivieren eines Schalters).
In diesem Tutorial werde ich verschiedene Sperrmechanismen demonstrieren und Möglichkeiten vorschlagen, sie in Ihren Spielen zu implementieren. Dies sind in keiner Weise die einzigen oder besten Implementierungen. Sie sind praktische Beispiele.
Die interaktiven Demos in diesem Tutorial wurden mit dem HTML5-Game-Maker-Tool Construct 2 erstellt und sollten mit der kostenlosen Version kompatibel sein. (Die CAPX-Dateien sind im Quelldownload verfügbar.) In diesem Lernprogramm sollten Sie jedoch lernen, wie Sie die Logik für Türen und Schlösser in einer beliebigen Engine implementieren. Sobald Sie die Idee hinter der Logik haben, hängt alles von Ihren eigenen Kenntnissen Ihres Codierungswerkzeugs / Ihrer Codierungssprache und der Art und Weise ab, wie Sie es an das Spiel anpassen möchten, das Sie gerade erstellen.
Lassen Sie uns eintauchen!
Der Grundmechaniker
Eine Tür ist im Grunde ein Szenenblock, der nicht passiert werden kann und verhindert, dass der Charakter des Spielers durchgeht, bis er entsperrt wird. Die Tür kann verschiedene Zustände haben: verriegelt oder entriegelt, geschlossen oder offen.
Es muss eine offensichtliche Darstellung des letzteren geben; Der Spieler muss erkennen können, dass es sich bei der Tür tatsächlich um eine Tür handelt und ob sie sich im verriegelten oder entriegelten Zustand befindet.
In den folgenden Demos werden die Türen durch zwei Grafiken dargestellt:


Ich habe auch verschiedene Farben verwendet, um die verschiedenen Materialien darzustellen, aus denen die Türen bestehen könnten - aber um ehrlich zu sein, liegt der grafische Aspekt bei Ihnen, Ihrem Spiel und seinem Universum. Der wichtigste Teil ist, dass die Tür eindeutig als Tür identifizierbar ist und dass klar ist, ob sie den Fortschritt des Spielers blockiert oder sich öffnet und zum Rest des Levels oder der Welt führt.
Wenn die Tür geschlossen oder verschlossen ist, sollte sie ein Festkörperblock sein. Beim Öffnen sollte der Solid State deaktiviert sein, damit Zeichen ihn durchlaufen können. Stellen Sie sicher, dass Sie diesen Status unabhängig von Ihrer Kollisions-Engine ganz einfach im laufenden Betrieb ändern können.
Aus Programmiersicht sollte das Türobjekt eine boolesche Variable is_locked
enthalten oder mit dieser verknüpft sein. Abhängig vom Wert dieser Variablen können Sie festlegen, welches Sprite angezeigt werden soll und ob der Block fest sein soll oder nicht.
Um die Tür zu öffnen, sollte der Charakter selbst eine boolesche Variable has_key
enthalten, wenn der Spieler einen Schlüssel aufgehoben hat: true
, wenn er ihn hat, false
, wenn er ihn nicht hat.
In dieser grundlegenden Mechanik fungiert der Schlüssel als Teil des Inventars des Charakters, und ein Schlüssel öffnet alle Türen. Die Verwendung des Schlüssels an einer Tür verbraucht ihn nicht. Der Schlüssel bleibt im Inventar des Charakters.
Um es zu visualisieren, können wir einfach ein Bild des Schlüssels im HUD anzeigen, um den Spieler wissen zu lassen, dass er einen Schlüssel "besitzt", der die Türen öffnen könnte, sobald der Charakter ihn aufgehoben hat (indem er den Charakter über den Schlüssel im Raum bewegt ).
Betrachten Sie das folgende grundlegende Beispiel:
Klicken Sie auf die Demo, um sie zu fokussieren, steuern Sie den Charakter mit den Pfeiltasten Ihrer Tastatur und führen Sie Aktionen mit der Leertaste aus. (In diesem Beispiel lautet die Aktion "Tür öffnen".)
Wände sind solide Blöcke, durch die der Charakter bei einer Kollision nicht hindurchtreten kann. Geschlossene Türen sind ebenfalls solide.
Um eine Tür zu öffnen, muss sich das Zeichen innerhalb von 64 Pixel von der Tür befinden und einen Schlüssel besitzen (dh die boolesche Variable has_key
, die bestimmt, ob das Zeichen den Schlüssel in seinem Inventar hat, muss true
sein).
Unter diesen Bedingungen ändert sich der Status der entsprechenden Tür, wenn der Spieler die Leertaste drückt. Die locked
boolesche Variable ist auf false
gesetzt und der "feste" Zustand ist deaktiviert.
Im Pseudocode würde dies ungefähr so aussehen:
1 |
Door.Locked = True |
2 |
Door.AnimationFrame = 0 |
3 |
//The animation frame that displays the door as locked
|
4 |
Door.Solid = Enabled |
5 |
//The solid state of the door is enabled
|
6 |
|
7 |
Door.Locked = False |
8 |
Door.AnimationFrame = 1 |
9 |
//The animation frame that displays the door as opened
|
10 |
Door.Solid = Disabled |
11 |
//The solid state of the door is disabled
|
12 |
|
13 |
Keyboard Key "Space" is pressed |
14 |
and Distance(Character,Door) <= 64px |
15 |
and Door.Locked = True |
16 |
and Character.Has_Key = True |
17 |
//The player has a key
|
18 |
Door.Locked = False |
19 |
|
20 |
Keyboard Key "Space" is pressed |
21 |
and Distance(Character,Door) <= 64px |
22 |
and Door.Locked = True |
23 |
and Character.Has_Key = False |
24 |
//The player does not have a key
|
25 |
Text.text = "You don't have a key for that door" |
Erinnerung: Dieser Code repräsentiert keine bestimmte Sprache. Sie sollten es in jeder gewünschten Sprache implementieren können.
Sie können auch feststellen, dass wir prüfen, ob der Spieler nicht über den erwarteten Schlüssel verfügt, und eine Rückmeldung anzeigen, in der erläutert wird, warum die Tür nicht entriegelt wurde. Sie können Schecks wie diese bearbeiten, die am besten zu Ihrem Spiel passen. Beachten Sie jedoch, dass es immer schön ist, Ihrem Spieler Feedback zu geben, dass seine Aktion registriert wurde, und den Grund zu erklären, warum sie nicht abgeschlossen wurde.
Dies ist eine sehr grundlegende Tür- und Schlosslogik und deren Implementierung. Im Rest des Tutorials werden wir uns andere Schließsysteme ansehen, die Varianten dieses Basissystems sind.
Verschiedene Schließsysteme
Wir haben das Basissystem gesehen, bei dem der Schlüssel ein ganzer Teil des Inventars des Charakters ist und ein Schlüssel alle Türen öffnet und wiederverwendet werden kann, um mehrere Türen zu öffnen. Bauen wir darauf auf.
KeyStack-Beispiel
Im nächsten Beispiel hat der Charakter einen Schlüsselstapel in seinem Inventar. Obwohl es mehrere verschiedene Türfarben gibt, ist der Unterschied hier streng grafisch - das Türobjekt ist logisch das gleiche wie im Basisbeispiel, und ein Schlüsseltyp kann jeden von ihnen öffnen. Wenn Sie diesmal jedoch einen Schlüssel zum Öffnen einer Tür verwenden, wird dieser Schlüssel vom Stapel entfernt.
In Bezug auf die Codierung erfolgt diese Änderung hauptsächlich auf der Ebene des Charakters. Anstatt eine boolesche Variable has_key
zu haben, möchten Sie eine numerische Variable haben, die die Anzahl der Schlüssel enthält, die das Zeichen "auf Lager" hat.
Jedes Mal, wenn der Charakter einen Schlüssel aufnimmt, addieren Sie 1
zu dieser Variablen, um den aufsteigenden Stapel darzustellen. Jedes Mal, wenn der Charakter eine Tür öffnet, subtrahieren Sie 1
von dieser Variablen, um die Verwendung eines Schlüssels darzustellen. (Im Land der Videospiele werden Schlüssel zerstört, sobald sie einmal verwendet werden.)
Eine weitere Änderung betrifft das Drücken der Leertaste: Anstatt zu überprüfen, ob eine boolesche Variable has_key
true
ist, möchten wir tatsächlich überprüfen, ob der Wert von KeyStack
größer als Null ist, damit wir nach dem Öffnen der Tür einen Schlüssel verbrauchen können.
Im Pseudocode sieht das ungefähr so aus:
1 |
Doors mechanics = same as in the basic example above. |
2 |
|
3 |
Keyboard Key "Space" is pressed |
4 |
and Character.KeyStack > 0 |
5 |
and Distance(Character, Door) <= 64 |
6 |
and Door.Locked = True |
7 |
Character.KeyStack = Character.KeyStack - 1 |
8 |
Door.Locked = False |
WhichKey Beispiel
In diesem neuen Beispiel betrachten wir ein Szenario, in dem für verschiedene Türtypen unterschiedliche Schlüsseltypen zum Entriegeln erforderlich sind.
Hier, wie im ersten Basisbeispiel, werden Schlüssel Teil des Inventars des Charakters sein. Wir werden wieder boolesche Variablen verwenden, um festzustellen, ob das Zeichen die erforderlichen Schlüssel aufgenommen hat. Und da wir verschiedene Schlüssel haben werden, werden wir auch verschiedene Arten von Türen haben (schwarze Tür, rote Tür, goldene Tür), für die auch ein geeigneter Schlüssel erforderlich ist, damit sie geöffnet werden können (schwarzer Schlüssel, roter Schlüssel, goldener Schlüssel).
Die Türobjekte verwenden unterschiedliche Sprites, um ihr Material anzuzeigen, und enthalten eine numerische Variable namens WhichKey
, die die Art des erwarteten Schlüssels sowie die Art der Grafik angibt, die angezeigt werden soll. Die verschiedenen Schlüsselwerte sind zur besseren Lesbarkeit als konstante Variablen enthalten.
Im Pseudocode:
1 |
CONSTANT BLACK_KEY = 0 |
2 |
CONSTANT RED_KEY = 1 |
3 |
CONSTANT GOLD_KEY = 2 |
4 |
|
5 |
Door mechanics are the same as in the basic example. |
6 |
|
7 |
Keyboard Key "Space" is pressed |
8 |
//The door requires a black key but the character doesn't have one
|
9 |
If Door.Locked = True |
10 |
and Door.WhichKey = BLACK_KEY |
11 |
and Character.Has_Black_Key = False |
12 |
and Distance(Door,Character) <= 64 |
13 |
Text.text="You need a black key for this door" |
14 |
|
15 |
//The door requires a red key but the character doesn't have one
|
16 |
Else If Door.Locked = True |
17 |
and Door.WhichKey = RED_KEY |
18 |
and Character.Has_Red_Key = False |
19 |
and Distance(Door,Character) <= 64 |
20 |
Text.text="You need a red key for this door" |
21 |
|
22 |
//The door requires a gold key but the character doesn't have one
|
23 |
Else If Door.Locked = True |
24 |
and Door.WhichKey = GOLD_KEY |
25 |
and Character.Has_Gold_Key = False |
26 |
and Distance(Door,Character) <= 64 |
27 |
Text.text="You need a red key for this door" |
28 |
|
29 |
//The door requires a black key and the character has one
|
30 |
Else If Door.Locked = True |
31 |
and Door.WhichKey = BLACK_KEY |
32 |
and Character.Has_Black_Key = True |
33 |
and Distance(Door,Character) <= 64 |
34 |
Door.Locked = False |
35 |
|
36 |
//The door requires a red key and the character has one
|
37 |
Else If Door.Locked = True |
38 |
and Door.WhichKey = RED_KEY |
39 |
and Character.Has_Red_Key = True |
40 |
and Distance(Door,Character) <= 64 |
41 |
Door.Locked = False |
42 |
|
43 |
//The door requires a gold key and the character has one
|
44 |
Else If Door.Locked = True |
45 |
and Door.WhichKey = GOLD_KEY |
46 |
and Character.Has_Gold_Key = True |
47 |
and Distance(Door,Character) <= 64 |
48 |
Door.Locked = False |
Dies ist eine Variation des Basisbeispiels, das verschiedene Arten von Schlüsseln und Türen zulässt und zum Öffnen von Türen keine Schlüssel verbraucht. Sobald Sie den Schlüssel haben, ist er Teil Ihres Inventars - Teil der "Statistik" des Charakters.
Beispiel wechseln
Dieses Mal muss der Spieler, anstatt direkt auf Türen zu wirken, einen bestimmten Schalter aktivieren, um eine bestimmte Tür zu öffnen oder zu schließen.
Die Türen hier sind im Wesentlichen das gleiche Objekt wie im Basisbeispiel. Sie könnten unterschiedliche Grafiken anzeigen, aber die Logik des Objekts ist immer noch dieselbe. Es gibt jedoch einen Zusatz: Wir fügen zwei numerische Variablen DoorID
und SwitchID
hinzu, mit denen wir wissen, welcher Schalter an welche Tür gebunden ist.
Schalter sind eine neue Art von Objekten, die ich ausgewählt habe, um sie fest zu machen (aber das müssen Sie nicht). Sie enthalten eine boolesche Variable, Activated
und numerische Variablen DoorID
und SwitchID
, mit denen wir, wie Sie sich vorstellen können, bestimmen, welcher Schalter an welche Tür gebunden ist.
Wenn also ein Schalter ist Activated: True
, ist die "verknüpfte" Tür auf Locked: False
eingestellt. Unsere Aktion mit der Leertaste wird dann neben einem Schalter und nicht neben einer Tür stattfinden. Beachten Sie in diesem Beispiel das Fehlen eines Schlüssels, da die Schalter als Schlüssel fungieren:
Wir könnten einfach einen einfachen Code verwenden, der die Türschalterverbindungen im selben Raum überprüft (da in diesem Beispiel drei Türen und Schalter im selben Raum angezeigt werden), aber später werden wir sehen, dass möglicherweise Schalter auf Türen wirken, die sich im Inneren befinden ein anderer Raum, und so wird ihre Aktion nicht genau in dem Moment stattfinden, in dem der Spieler den Schalter betätigt; Es wird später auftreten, wenn der neue Raum geladen wird.
Aus diesem Grund brauchen wir Ausdauer. Eine Möglichkeit hierfür ist die Verwendung von Arrays, um Daten wie den Status der Switches zu verfolgen (dh ob jeder Switch aktiviert ist oder nicht).
Im Pseudocode:
1 |
CONSTANT SWITCH_DOORID = 0 |
2 |
CONSTANT SWITCH_ACTIVATION = 1 |
3 |
//Those constants will allow us to keep a readable reminder of the array coordinates
|
4 |
<br>//Define some array |
5 |
//The X coordinate of the array will correspond to the SwitchID value
|
6 |
//The Y-0 coordinate will be the DoorID
|
7 |
//The Y-1 coordinate will be the activation state
|
8 |
aSwitch(number of switches,2) |
9 |
//2 is the height (Y) number, often 0-based.
|
10 |
<br>Run some association of the SwitchIDs with DoorIDs |
11 |
|
12 |
Door mechanic is still the same as in the basic example. |
13 |
|
14 |
//Displaying the correct switch graphic according to their activation state
|
15 |
Switch.Activated = True |
16 |
Display the animation frame Switch_ON |
17 |
Switch.Activated = False |
18 |
Display the animation frame Switch_OFF |
19 |
|
20 |
Keyboard Key "Space" is pressed |
21 |
and Distance(Character, Switch) <= 64 |
22 |
Switch.Toggle(Activated) |
23 |
//A function that will set the value to either True or False)
|
24 |
aSwitch(Switch.SwitchID,SWITCH_ACTIVATION) = Switch.Activated |
25 |
//It can depend on your coding language, but the idea is to set the value in the array where X is the SwitchID and where Y is the state of activation of the switch. The value itself is supposed to be the equivalent of the Switch.Activated boolean value.
|
26 |
|
27 |
Door.DoorID = aSwitch(Switch.SwitchID,SWITCH.DOORID) |
28 |
//Allows us to make sure we're applying/selecting the correct door instance
|
29 |
//Now according to the activation value, we lock or unlock the door
|
30 |
aSwitch(Switch.SwitchID,SWITCH.ACTIVATION) = True |
31 |
Door.Locked = False |
32 |
aSwitch(Switch.SwitchID,SWITCH.ACTIVATION) = False |
33 |
Door.Locked = True |
In diesem speziellen Beispiel, in dem sich die Schalter im selben Raum befinden wie die Türen, mit denen sie verbunden sind, ist die Verwendung der Array-Technik übertrieben. Wenn Ihr Spiel so eingerichtet ist, dass jeder Schalter, der auf eine Tür wirkt, im selben Raum positioniert wird, wählen Sie auf jeden Fall die einfachere Methode, entfernen Sie das Array und suchen Sie nach Objekten, die eingeschaltet sind Nur Bildschirm.
Beispiel für einen Plattenschalter
Plattenschalter ähneln Schaltern in dem Sinne, dass sie entweder aktiviert sind oder nicht, und dass wir sie mit Türen verbinden können, um sie zu verriegeln oder zu entriegeln. Der Unterschied besteht darin, wie ein Plattenschalter durch Druck aktiviert wird.
In diesem Beispiel von oben nach unten wird der Plattenschalter immer dann aktiviert, wenn das Zeichen ihn überlappt. Sie können die Leertaste drücken, um einen Stein auf den Plattenschalter fallen zu lassen, und ihn aktiviert lassen, auch wenn der Charakter nicht darauf steht.
Die Implementierung ähnelt dem vorherigen Beispiel mit zwei kleinen Änderungen:
- Sie müssen den Plattenschalter aktivieren, wenn sich ein Charakter oder Stein darauf befindet.
- Sie müssen die Leertaste dazu bringen, einen Stein (aus dem Inventar) auf den Plattenschalter fallen zu lassen.
1 |
//Most of the implementation is the same as the previous example replace the Switch object with PlateSwitch object
|
2 |
|
3 |
//Plate-Switch Mechanic
|
4 |
Character OR Rock is NOT overlapping PlateSwitch |
5 |
PlateSwitch.Activated = False |
6 |
aSwitch(PlateSwitch.SwitchID,SWITCH_ACTIVATION) = PlateSwitch.Activated |
7 |
|
8 |
Door.DoorID = aSwitch(PlateSwitch.SwitchID,SWITCH.DOORID) |
9 |
//Allows us to make sure we're applying/selecting the correct door instance
|
10 |
Door.Locked = True |
11 |
|
12 |
Character OR Rock is overlapping PlateSwitch |
13 |
PlateSwitch.Activated = True |
14 |
aSwitch(PlateSwitch.SwitchID,SWITCH_ACTIVATION) = PlateSwitch.Activated |
15 |
|
16 |
Door.DoorID = aSwitch(PlateSwitch.SwitchID,SWITCH.DOORID) |
17 |
//Allows us to make sure we're applying/selecting the correct door instance
|
18 |
Door.Locked = False |
19 |
|
20 |
Keyboard Key "Space" is pressed |
21 |
And Character is overlapping PlateSwitch |
22 |
Spawn Rock at PlateSwitch.position |
23 |
Mobs Beispiel
Eine andere mögliche Sperrmechanik besteht darin, dass der Spieler alle Feinde (auch als Mobs bezeichnet) in einem Raum oder Bereich loswerden muss, um das Entriegeln der Türen auszulösen.
In diesem Beispiel habe ich einige Bereiche in einem einzelnen Raum erstellt. Jeder Bereich hat eine Tür und mehrere Mobs (obwohl sich diese Feinde nicht bewegen und keinen Schaden verursachen). Jeder Bereich hat seine eigene Farbe.
Die Leertaste lässt den Charakter einige Projektile abfeuern. Drei Projektile töten einen Mob.
Diese Art von Mechanik wird in The Legend of Zelda und The Binding of Isaac verwendet und dreht sich um eine Funktion, die die Anzahl lebender Feinde in dem Raum oder Bereich überprüft. In diesem Beispiel enthält jeder farbige Bereich eine Anzahl der lebenden Mobs, die beim Laden des Raums ausgelöst werden und an die Tür gebunden sind. Der Tod jedes Pöbels subtrahiert 1
von diesem Zähler; Sobald es auf 0
fällt, wird der Status Locked
der Tür in False
geändert.
1 |
//On start of the game
|
2 |
For each Area |
3 |
For each Mob overlapping Area |
4 |
Area.AliveMobs = Area.AliveMobs + 1 |
5 |
<br>Door mechanic is the same as in the basic example<br> |
6 |
Keyboard Key "Space" is pressed |
7 |
Spawn a Projectile from Character's position |
8 |
|
9 |
Projectile collides with Mob
|
10 |
Mob.HP = Mob.HP - 1
|
11 |
Destroy Projectile
|
12 |
|
13 |
Mob.HP <=0 //Mob is dead
|
14 |
and Mob is overlapping Area
|
15 |
Destroy Mob
|
16 |
Area.AliveMobs = Area.AliveMobs - 1
|
17 |
|
18 |
Area.AliveMobs <= 0
|
19 |
and Door is linked to Area //By means of an ID, a pointer or whatever
|
20 |
Door.Locked = False
|
In diesem Beispiel ist ein Area
ein farbiges Sprite mit der zugehörigen numerischen Variablen AliveMobs
, die die Anzahl der Mobs zählt, die den Bereich überlappen. Sobald alle Mobs in einem Gebiet besiegt sind, wird die entsprechende Tür entriegelt (mit derselben Mechanik, die wir seit dem Basisbeispiel gesehen haben).
Navigationsbeispiel
Wie ich in der Einleitung erwähnt habe, können Türen als blockierende Hindernisse fungieren, aber auch verwendet werden, um dem Charakter des Spielers die Navigation von einem Raum zu einem anderen zu ermöglichen.
In diesem Beispiel werden Türen standardmäßig entsperrt, da wir uns mehr für den Navigationsaspekt interessieren.
Die Mechanik hängt stark von dem Spiel ab, das Sie machen, sowie von der Art und Weise, wie Sie mit der Datenstruktur für Ihre Etagen umgehen. Ich werde hier nicht näher auf die Funktionsweise meiner Implementierung eingehen, da sie sehr spezifisch für Construct 2 ist. Sie können sie jedoch in den Quelldateien finden, wenn Sie dies wünschen.
Abschluss
In diesem Artikel haben wir gesehen, wie Türen vorübergehende Hindernisse sind, für die Schlüssel oder Entriegelungsmechanismen wie Schalter, Plattenschalter oder sogar der Tod von Mobs erforderlich sind. Wir haben auch gesehen, wie sie als "Brücken" fungieren können, die die Navigation durch verschiedene Bereiche der Spielwelt ermöglichen.
Zur schnellen Erinnerung hier einige mögliche Schlossmechaniken:
- Ein Schlüssel für alle Türen als Teil des Inventars.
- Verbrauchsschlüssel: Jedes Mal, wenn Sie eine Tür öffnen, wird ein Schlüssel von Ihrem Schlüsselstapel abgezogen.
- Unterschiedliche Türen erfordern unterschiedliche Schlüssel.
- Schalter oder Plattenschalter, bei denen Sie nicht direkt auf die Tür einwirken, um sie zu entriegeln, sondern über ein separates, verbundenes Gerät.
- Wenn Sie alle Mobs eines Gebiets töten, wird automatisch eine Tür geöffnet.
Wenn Sie all diese Mechaniken in einem Spiel mischen, könnten Sie am Ende Folgendes haben:
Hier haben wir eine schöne Auswahl an verschiedenen Tür- und Schlossmechaniken, bei denen der Spieler mehrere Räume durchlaufen muss, um die verschiedenen Türen zu öffnen. Zu Lernzwecken möchten Sie dies möglicherweise in Ihrer eigenen Programmierumgebung reproduzieren und dabei alle vorherigen Implementierungen verwenden, die wir durchlaufen haben.
Ich hoffe, dass Ihnen dieser Artikel gefallen hat und dass er für Sie nützlich war, und ich möchte Sie daran erinnern, dass Sie die Quelle für alle Demos auf Github finden können. Sie können sie in der kostenlosen Version von Construct 2 (Version r164.2 oder höher) öffnen und bearbeiten.
Verweise
- Vorschaubild: Schloss von João Miranda aus dem Noun-Projekt