Essential Concepts
Dieser Leitfaden richtet sich an Anfänger, die die wesentlichen Konzepte erlernen möchten, um mit dem Modding und Scripting in nanos world zu beginnen.
Einführung​
Willkommen bei nanos world! Ein Sandbox-Multiplayer-Spiel, mit dem du deine eigenen Spielmodi erstellen, benutzerdefinierte Assets und Karten importieren und seine Funktionalitäten vollständig mit Lua-Skripten erweitern kannst.
Bevor du dich in Modding und Scripting stürzt, ist es wichtig, die wesentlichen Konzepte zu verstehen, wie alles in nanos world funktioniert. Diese Seite gibt dir eine kurze Erklärung zu allem, was du wissen musst, sowie Links, um mehr über jedes Thema zu erfahren!
Nachdem du diesen Leitfaden gelesen hast, bist du bereit, mit dem Programmieren, Modden und Erstellen von Inhalten zu beginnen!
Asset Packs​
Asset Packs sind der Weg für Modder, benutzerdefinierte Assets wie neue Modelle, Texturen, Sounds und mehr in das Spiel zu importieren. Sie können mit der Unreal Engine erstellt, exportiert und dann als Asset Pack in das Verzeichnis Assets/ des nanos world Servers hinzugefügt werden!
Jedes Asset Pack hat eine Konfigurationsdatei namens Assets.toml. Diese Konfiguration wird verwendet, um alle im Pack enthaltenen Assets, deren Pfade und andere Metadaten wie Typen, Kategorien und Tags aufzulisten. Diese Datei wird vom Spiel genutzt, um die Assets zu laden und sie in Lua-Skripten ĂĽber Entities verfĂĽgbar zu machen.
Through Lua Scripting, some Entities require an Asset as parameter. For example the entity Prop asks for a StaticMesh Reference in the third constructor parameter.
So spawnen wir einen Prop auf diese Weise, unter Verwendung der Asset-Referenz:
-- Verwendung des standardmäßigen 'nanos-world' Asset Packs, das bereits nativ im Spiel enthalten ist
local my_prop = Prop(Vector(), Rotator(), "nanos-world::SM_Cube")
Asset Packs are a powerful tool for extending the game and adding new features, and they are essential for modders who want to create unique and exciting experiences for players. Lies mehr ĂĽber Asset Packs in unserem Assets-Leitfaden:
Assets Guidecore-concepts/assetsPakete​
Pakete sind die zentralen Bausteine von nanos world, da sie Scriptern eine Möglichkeit bieten, benutzerdefinierte Inhalte und Funktionen für das Spiel zu erstellen.
Es gibt verschiedene Arten von Paketen, jede mit einer anderen Fähigkeit, darunter das Ausführen von Lua-Skripten, das Erstellen von Spielmodi, das Definieren von Karten und sogar das Erstellen benutzerdefinierter Ladebildschirme!
Script is the most common type of Package, it allow scripters to run Lua code on both the client and server which can listen to events, interact with game classes, and modify game behavior in various ways.
Pakete können dynamisch geladen/entladen werden, während der Server läuft, mit package reload all!
Each Package is a folder under server's Packages/ directory with a configuration file Package.toml inside, on which we define the type of the package and it's relevant settings.
Packages can also have dependencies on other Packages and Asset Packs, which allows scripters to reuse code and assets across multiple Packages. Dies kann helfen, Duplikate zu reduzieren und die Organisation zu verbessern, insbesondere bei größeren Skripten oder Spielmodi.
Wenn wir möchten, dass ein Paket geladen wird, müssen wir es in der Config.toml unseres Servers definieren. Wir haben einen Abschnitt für jeden Pakettyp (game_mode, loading_screen, map und packages), in dem wir die Namen der Pakete auflisten müssen, die der Server laden soll!
Insgesamt sind Pakete ein wesentliches Konzept im nanos world Modding, und sie bieten eine leistungsstarke und flexible Möglichkeit, benutzerdefinierte Inhalte und Funktionalität für das Spiel zu erstellen. Lies mehr darüber in unserem Pakete-Leitfaden:
Packages Guidecore-concepts/packages/packages-guideSkripting​
Scripting ist eine leistungsstarke Möglichkeit, nanos world zu programmieren und anzupassen. It allows us to listen to game events, spawn, interact, and change entities at will.
Der gesamte Lua-Code wird über Pakete ausgeführt, es ist möglich, Skripte zu haben, die nur auf dem Server, auf dem Client oder auf beiden laufen.
Lernen​
Wenn du neu im Programmieren bist, sind YouTube oder beliebige Kursseiten groĂźartige Orte, um die Grundlagen des Programmierens in verschiedenen Programmiersprachen zu erlernen.
For Lua scripting, the official Lua Manual provides a comprehensive tutorial on the basics of the language.
AuĂźerdem ist unsere Dokumentation eine groĂźartige Ressource, um alles ĂĽber nanos world zu lernen, was verfĂĽgbar ist. You can explore our Game-Modes and Packages category to find real examples!
The 🎓 CORE CONCEPTS section provides guides and great information whilst 👨‍💻 SCRIPTING REFERENCE provides technical details of the usage of our API!
AuĂźerdem ist unser offizieller Discord eine aktive Community, in der du Fragen stellen, Hilfe erhalten und dich mit anderen Scriptern vernetzen kannst!
Ordnerstruktur des Pakets​
Die Ordnerstruktur eines Scripting-Pakets umfasst die Ordner Server/, Client/ und Shared/. Du kannst .lua-Dateien in jedem Ordner ablegen, um Code auf dem Server, dem Client oder auf beiden (Shared) auszufĂĽhren:
my-package/
├── Server/
│ ├── Index.lua
│ └── *.lua
├── Client/
│ ├── Index.lua
│ └── *.lua
├── Shared/
│ ├── Index.lua
│ └── *.lua
└── Package.toml
Nur Pakete vom Typ script, game-mode und map haben diese Struktur, da dies die einzigen sind, die Lua-Skripte ausfĂĽhren.
Das Paket lädt nur die Datei Index.lua jedes Basisordners (Server/, Client/ oder Shared/), diese Datei ist dafür verantwortlich, andere Skriptdateien zu importieren.
Du findest mehrere Leitfäden zum Scripting im Abschnitt 🎓 CORE CONCEPTS / Scripting in der linken Seitenleiste.
Unreal-Engine-Konzepte​
Einige Konzepte mögen verwirrend klingen, wenn du mit der Unreal Engine nicht vertraut bist. Da nanos world darauf aufbaut, übernehmen wir größtenteils ihre eigenen Konzepte und Terminologien.
Vector, Rotator und Einheiten​
In der Unreal Engine wird die Welt in einem 3D-Raum mithilfe eines Koordinatensystems dargestellt. Die Position von Objekten in diesem Raum wird mit Vector dargestellt, im Wesentlichen einer Reihe von drei Zahlen, die die Koordinaten X (Vorwärts), Y (Rechts) und Z (Hoch) darstellen.
Die Ausrichtung von Objekten wird mit Rotator dargestellt, einer Reihe von drei Zahlen, die Pitch (Rotation um die Y-Achse), Yaw (Rotation um die Z-Achse) und Roll (Rotation um die X-Achse) darstellen.
Die Unreal Engine verwendet Zentimeter als Standardmaßeinheit. Wenn du also einen Vector mit den Werten (100, 0, 0) siehst, bedeutet das, dass sich das Objekt 100 Zentimeter (oder 1 Meter) vom Ursprungspunkt entfernt vorwärts befindet.
Actors​
In der Unreal Engine ist ein Actor jedes Objekt, das in der Welt platziert werden kann. Es ist die Basisklasse für alle Objekte, die eine physische Repräsentation in der Spielwelt haben, wie Character, Prop, Weapon und mehr. Sie können in der Regel mit einer location und rotation gespawnt werden.
Players vs. Characters vs. Pawn​
Ein wichtiges Konzept, das es zu verstehen gilt, ist der Unterschied zwischen Player, Character und Base Pawn.
In der Unreal Engine ist ein PlayerController die Entity, die einen verbundenen Spieler auf dem Server repräsentiert, während ein Pawn die physische Repräsentation eines Spielers in der Welt ist, die gesteuert werden kann. Und Character ist eine Art von Pawn, die zusätzliche Funktionen wie Laufen, Springen und mehr besitzt.
Für nanos world haben wir dieses Konzept etwas vereinfacht, wir haben einfach eine Klasse Player, die den Spieler repräsentiert. Und einige Character-Klassen wie Character und CharacterSimple, die die physische Repräsentation des Spielers in der Welt darstellen.
Wenn also ein Player spawnt, bedeutet das, dass sich gerade eine echte Person mit dem Server verbunden hat.
Logging & Debugging​
Du kannst Nachrichten mithilfe der statischen Klasse Console in der Konsole ausgeben, die verschiedene Log-Level wie .Log(), .Debug(), .Warn() und .Error() bietet:
-- Standardmäßige Konsolenausgabe
Console.Log("Normal scripting logs!")
Console.Debug("Debug message that will only appear if log level is >= debug!")
Console.Warn("Warning orange message! Will show the stack trace!")
Console.Error("Error red messages! Will show the stack trace!")
Klassen​
Klassen sind Entities, die nach Belieben gespawnt, mit denen interagiert und die verändert werden können. Ein Beispiel für das Spawnen eines Light und StaticMesh würde so aussehen:
local my_light = Light(Vector(0, 100, 100), Rotator(), Color.RED)
local my_static_mesh = StaticMesh(Vector(0, 0, 100), Rotator(), "nanos-world::SM_Cube_VR_01")
Oder sogar mit den bestehenden Entities interagieren und sie so verändern, wie wir wollen, wie zum Beispiel mit einem bestehenden Prop:
local some_prop = Prop.GetByIndex(1)
some_prop:SetMesh("nanos-world::SM_Pyramid_VR")
some_prop:SetLocation(Vector(100, 100, 100))
local prop_rotation = some_prop:GetRotation()
Lies mehr ĂĽber Klassen im Klassen-Leitfaden:
Classes Guidecore-concepts/scripting/classes-guideStatische Klassen​
Neben den Klassen haben wir auch die statischen Klassen, die einfach Lua-Bibliotheken mit Funktionen sind, die aufgerufen werden können, ohne dass eine Entity gespawnt oder eine Instanz vorhanden sein muss.
Zum Beispiel können wir mithilfe der statischen Klasse Chat eine Chat-Nachricht senden:
Chat.BroadcastMessage("Hello world!")
Oder mit der statischen Klasse Trace auf der Client-Seite einen Raycast durchfĂĽhren:
local start_location = Vector(0, 0, 100)
local end_location = Vector(1000, 0, 100)
local collision_channels = CollisionChannel.WorldStatic
local trace_mode = TraceMode.DrawDebug | TraceMode.TraceComplex
-- FĂĽhrt den Trace durch
local trace_result = Trace.LineSingle(start_location, end_location, collision_channels, trace_mode)
Events​
In nanos world basiert alles in der API auf Events, die ähnlich wie Hooks in anderen Spielen sind.
Zum Beispiel werden Events ausgelöst, wenn ein Player spawnt (Spawn), ein Character in ein Fahrzeug einsteigt (EnterVehicle) oder Schaden erleidet (TakeDamage).
Du kannst diese Events mit Lua-Scripting abhören und entsprechend Aktionen ausführen. Zum Beispiel erfährst du so, wann ein Player dem Server beitritt oder ein Prop gespawnt wird:
-- Abonniert, wenn ein Player dem Server beitritt
Player.Subscribe("Spawn", function(player)
Console.Log("The player %s has joined the server!", player:GetName())
end)
-- Abonniert, wenn ein Prop gespawnt wird
Prop.Subscribe("Spawn", function(prop)
Console.Log("A prop id %d just spawned!", prop:GetID())
end)
Lies mehr ĂĽber Events im Events-Leitfaden:
Events Guidecore-concepts/scripting/events-guideTimer​
Mit unserer statischen Klasse Timer kannst du Timer und Verzögerungen erstellen, um Code auszuführen, nachdem eine bestimmte Zeit vergangen ist. Dies ist nützlich, um zeitgesteuerte Events, Abklingzeiten oder jede Situation zu erstellen, in der du die Ausführung von Code verzögern möchtest.
Unsere Timer-API ähnelt den JavaScript-Timern, du kannst Timer.SetTimeout() verwenden, um eine Funktion einmalig nach einer Verzögerung auszuführen, oder Timer.SetInterval(), um eine Funktion kontinuierlich in einem festgelegten Intervall wiederholt auszuführen.
-- FĂĽhrt eine Funktion alle 10 Sekunden aus
Timer.SetInterval(function()
Console.Log("Triggered each 10 seconds!")
end, 10000)
-- FĂĽhrt eine Funktion nach 1 Minute aus (einmalig)
Timer.SetTimeout(function()
-- Do Something
end, 60000)
Lies mehr ĂĽber Timer auf der Timer-Seite:
Timer Static Classscripting-reference/static-classes/timerBestehende Entities finden​
Neben dem Spawnen einer Entity und dem Speichern eines Variablenverweises darauf oder dem Abrufen ihrer Referenz ĂĽber ein Event, musst du manchmal eine bereits gespawnte Entity abrufen.
Zum Beispiel können wir mit den statischen Methoden .GetAll() oder .GetPairs() alle gespawnten Entities einer Klasse abrufen:
-- Gibt eine Kopie einer Tabelle mit allen gespawnten Characters zurĂĽck
local all_characters = Character.GetAll()
-- Iteriert effizient ĂĽber alle gespawnten Players
for _, player in pairs(Player.GetPairs()) do
Console.Log("Player %s is connected!", player:GetName())
end
Oder wir können mit der Methode .GetByIndex() einen Verweis auf einen bestehenden Prop mit einem bestimmten Index abrufen:
local my_prop = Prop.GetByIndex(10)
Eine andere Möglichkeit ist es, eine Entity anhand ihrer global eindeutigen ID mit den Methoden Server.GetEntityByID() oder Client.GetEntityByID() abzurufen:
local my_character = Character(Vector(), Rotator(), "nanos-world::SK_Male")
local character_id = my_character:GetID()
-- Später...
local my_character = Server.GetEntityByID(character_id)
Einen Player-Character abrufen​
Wie im Abschnitt Players vs Characters vs Pawn erklärt, repräsentiert ein Player einen verbundenen Spieler auf dem Server, während ein Character die physische Repräsentation des Spielers in der Welt ist.
Du kannst den vom Player kontrollierten Character mit der Methode Player:GetControlledCharacter() abrufen.
Den lokalen Player auf der Client-Seite abrufen​
Auf der Client-Seite gibt es einige spezielle Methoden und Events, um den lokalen Player abzurufen: Client.GetLocalPlayer() und SpawnLocalPlayer (Client):
local local_player = Client.GetLocalPlayer()
-- Hinweis: der lokale Player ist erst verfügbar, nachdem das Client-Event "SpawnLocalPlayer" ausgelöst wurde
if (local_player) then
local local_character = local_player:GetControlledCharacter()
-- Ruft den Standort meines Characters ab
Console.Log("My character is at location %s", local_character:GetLocation())
end
Client.Subscribe("SpawnLocalPlayer", function(player)
Console.Log("My Player just spawned in client side! %s", player:GetName())
-- Erst jetzt ist der Zugriff auf Client.GetLocalPlayer() möglich
end)
Netzwerk​
Unsere Client-Server-Architektur wurde unter BerĂĽcksichtigung der Einfachheit und Leichtigkeit von allem, was mit Netzwerk und Synchronisation zu tun hat, entwickelt. So muss sich der Scripter nur um den kreativen Teil kĂĽmmern!
Alle Entities, die auf dem Server gespawnt werden, werden auch automatisch auf dem Client gespawnt und synchronisiert.
Wenn eine Funktion auf einer Entity auf der Serverseite aufgerufen wird, wird diese Funktion auch automatisch auf derselben Entity bei jedem verbundenen Client aufgerufen. So sind alle immer mit allen Änderungen synchronisiert!
Wenn wir zum Beispiel einen Character spawnen und seinen Standort festlegen, wird bei allen Clients ebenfalls der Standort aktualisiert:
-- Spawnt einen Character (auf der Serverseite)
local my_character = Character(Vector(0, 0, 0), Rotator(), "nanos-world::SK_Male")
-- Legt seinen Standort fest
my_character:SetLocation(Vector(100, 100, 100))
Manche Klassen können nur auf dem Server gespawnt werden, andere nur auf dem Client. Dasselbe gilt für einige ihrer Funktionen. Wenn du also eine Entity auf der Client-Seite spawnst, existiert sie nur für diesen Client.
In dieser Dokumentation findest du Authority-Tooltips, die anzeigen, ob eine Funktion auf dem Server , auf dem Client
, auf beiden
oder nur auf der Seite, die sie gespawnt hat,
aufgerufen werden kann!
Kommunikation zwischen den Seiten​
Um Informationen vom Server zum Client oder umgekehrt senden zu können, haben wir in unserer statischen Klasse Events einige spezielle Methoden bereitgestellt, die dies ermöglichen!
-- Abonniert ein Remote Event auf der Client-Seite
Events.SubscribeRemote("MyClientEvent", function(my_text)
Console.Log("Event received from server! " .. my_text)
-- gibt aus "Event received from server! hello nanos world!"
end)
-- Sendet ein Remote Event an alle Players in allen Client-Paketen
Events.BroadcastRemote("MyClientEvent", Reliability.Reliable, "hello nanos world!")
Lies mehr ĂĽber Netzwerk-Events in unserem Leitfaden zur Kommunikation zwischen Paketen:
Communicating Between Packagescore-concepts/scripting/communicating-between-packagesSynchronisierte Werte​
Neben dem Senden von Daten über Events, was eine dynamische Sache ist, haben wir auch die Möglichkeit, benutzerdefinierte Werte an Entities zu binden/festzulegen, oder sogar global im Server/Client.
Zum Beispiel können wir einen Wert auf einer beliebigen Entity festlegen, auf den jedes Paket zugreifen kann:
-- Legt einen Wert 'my_value' auf einem Prop fest
my_prop:SetValue("my_value", 100)
-- Später den Wert erneut abrufen
local my_value = my_prop:GetValue("my_value")
Dadurch wird my_value auf dieser Entity global auf dieser Seite (Client oder Server) festgelegt.
Wir haben aber auch die Möglichkeit, einen synchronisierten Wert auf einer Entity zu senden (wenn wir uns auf der Serverseite befinden)! Dafür fügen wir einfach den Parameter true hinzu, der angibt, dass er ebenfalls an alle Clients gesendet und mit ihnen synchronisiert werden soll!
-- Legt einen synchronisierten Wert 'my_value' auf einem Prop fest
my_prop:SetValue("my_value", 100, true)
Diese Werte sind von jedem Paket aus zugänglich und eine großartige Möglichkeit, benutzerdefinierte Werte zu synchronisieren!
Lies mehr ĂĽber Entity-Werte in unserem Leitfaden zu Entity-Werten:
Entity Valuescore-concepts/scripting/entity-valuesUI​
Die Benutzeroberfläche ist ein wesentlicher Aspekt des Spiels, wir bieten mehrere flexible Möglichkeiten, eine UI zu erstellen!
WebUI​
Der direkteste Weg ist mit WebUI, womit du UIs mithilfe von HTML/CSS/JavaScript erstellen und integrieren kannst, mit absoluter Freiheit, sie so zu gestalten und anzupassen, wie du möchtest.
Es ist möglich, mithilfe von Events zwischen Lua und JavaScript zu kommunizieren!
FĂĽr weitere Informationen sieh dir unser WebUI-Tutorial an:
Basic HUD (HTML)getting-started/tutorials-and-examples/basic-hud-htmlWidget​
Eine weitere neue und fortgeschrittenere Möglichkeit, eine UI zu erstellen, ist die Verwendung von Unreal Widget! Damit hast du vollen Zugriff auf die leistungsstarken Widgets und kannst sogar deine eigenen über die Unreal Engine erstellen und im Spiel importieren, was eine nahtlose Benutzererfahrung ermöglicht!
FĂĽr weitere Informationen sieh dir unsere Widget-Klassenseite an:
Widget Classscripting-reference/classes/widgetKarten​
Karten (oder Levels in Unreal) sind der Einstiegspunkt des Spiels, hier wird die Welt geladen und die Entities werden auf der Client-Seite gespawnt.
Um eine Karte zu konfigurieren, erstellen wir ein Paket vom Typ map, dieser Typ ist wie script, aber seine Package.toml hat zusätzliche Einstellungen zur Konfiguration der Karte, z. B. haben wir map_asset = "my-asset-pack::MyLevel", wodurch wir festlegen müssen, welches Karten-Asset dieses Paket laden wird.
Wir können in der Config.toml unter der Einstellung map = "my-map-package" konfigurieren, welche Karte der Server ausführen wird.
Wir bieten 4 integrierte Karten, die ohne zusätzliches Paket oder Asset-Pakete geladen werden können: default-blank-map, default-empty-map, default-ocean-map und default-testing-map.
Außerdem können wir in der Package.toml der Karte spawn_points und custom_data konfigurieren, auf die per Scripting von jedem Paket aus zugegriffen werden kann:
local spawn_points = Server.GetMapSpawnPoints()
local custom_data = Server.GetMapConfig()
Wie du bemerkt haben könntest, lädst du durch das Laden einer Karte auch ein Paket mit Skripten, sodass du kartenspezifische, benutzerdefinierte Skripte laufen lassen kannst. Dies kann nützlich sein, um benutzerdefiniertes Verhalten wie interaktive Umgebungen oder Türen hinzuzufügen.
Spielmodi​
Spielmodi sind eine Art von Paket, das sich genau wie scripts verhält, mit der Ausnahme, dass wir nur einen Spielmodus gleichzeitig geladen haben können, während wir so viele Skripte laufen lassen können, wie wir möchten.
Sie sollten verwendet werden, um einzigartige und in sich geschlossene Spielerlebnisse zu erschaffen.
Spieler können Server einfach auswählen und starten, indem sie im Hauptmenü im Abschnitt „Neues Spiel" einen Spielmodus auswählen.
Conclusion​
In diesem Leitfaden haben wir einige der wesentlichen Konzepte des nanos world Scriptings und Moddings behandelt. Diese Konzepte sind wichtig zu verstehen, da sie die Grundlage fĂĽr den Aufbau jedes Spielmodus oder Skripts bilden. Wir hoffen, dass du jetzt beginnen kannst, Inhalte zu erstellen und deinen Ideen Leben einzuhauchen!
Wenn du Fragen hast, zögere nicht, sie in unserem Discord zu stellen! Unsere Community ist großartig und sehr freundlich, wir helfen dir gerne weiter!
Jetzt empfehlen wir, mit unserem Schnellstart-Leitfaden zu beginnen, um dein erstes Paket zu erstellen!
Quick Startgetting-started/quick-start