Passer au contenu principal
Version: latest - a1.169.2 ⚖️

Essential Concepts for Beginners

Ce guide est destiné aux débutants qui souhaitent apprendre les concepts essentiels pour commencer avec le modding et le scripting sur nanos world.

Introduction​

Bienvenue sur nanos world ! Un jeu multijoueur qui vous permet de créer vos propres mode de jeux, importer des ressources personnalisés, des maps et d'étendre complétement ses fonctionnalités avec les scripts Lua.

Avant de plonger dans le modding et le scripting, il est important de comprendre les concepts essentiels de la façon dont tout fonctionne dans nanos world. Cette page vous donnera une brève explication de tout ce que vous devez savoir et des liens pour en savoir plus sur chaque sujet !

Après avoir lu ce Guide, vous serez prêt à commencer la programmation, le modding et la création de contenu !

Packs d'Assets​

Les Packs d'Assets sont le moyen pour les modders d'importer des assets personnalisés dans le jeu, tels que les nouveaux modèles, textures, sons et plus encore. Ils se créent avec Unreal Engine, s'exportent, puis s'ajoutent au dossier Assets/ du serveur nanos world sous forme d'Asset Pack !

Chaque Pack d'Asset a un fichier de configuration appelé Assets.toml. Cette configuration est utilisée pour lister tous les assets inclus dans le pack, leurs chemins, et d'autres métadonnées telles que leurs types, catégories et tags. Ce fichier est utilisé par le jeu pour charger les ressources et les rendre disponibles dans les scripts Lua via les entités.

Par le biais du Lua Scripting, certaines entités nécessitent un Asset en tant que paramètre. Par exemple, l'entité Prop demande un StaticMesh Reference dans le troisième paramètre du constructeur.

De cette façon, nous faisons apparaître un Prop comme celui-ci, en utilisant la référence de l'asset:

my-package/Server/Index.lua
-- Utiliser l'Asset Pack 'nanos-world' par défaut, qui est déjà inclus nativement dans le jeu
local my_prop = Prop(Vector(), Rotator(), "nanos-world::SM_Cube")

Les packs d'Assets sont un outil puissant pour étendre le jeu et ajouter de nouvelles fonctionnalités, et ils sont essentiels pour les modders qui veulent créer une expérience unique et passionnantes pour les joueurs. En savoir plus sur les Packs d'Assets dans notre Guide d'Assets:

Guide d'Assetscore-concepts/assets

Packages​

Les Packages sont les principaux éléments de construction de nanos world, car ils fournissent un moyen aux scripteurs de créer du contenu personnalisé et des fonctionnalités pour le jeu.

Il y a différents types de packages, chaque type avec une capacité différente, y compris l'exécution de scripts Lua, la création de modes de jeu, la définition de maps et également la création d'écrans de chargements personnalisés !

Script est le type de package le plus courant, il permet aux scripteurs d'exécuter du code Lua sur le client et le serveur qui peuvent écouter les événements, interagissez avec les classes du jeu et modifiez le comportement du jeu de différentes manières.

tip

Les paquets peuvent être rechargés pendant que le serveur fonctionne avec la commande console package reload my-package, ou chargés et déchargés par le script avec Server.LoadPackage() et Server.UnloadPackage()! De plus, tous les paquets et toute la VM Lua peuvent être rechargés avec la commande console package reload all.

Chaque Package est un dossier dans le dossier Packages/ du serveur, avec à l'intérieur un fichier de configuration Package.toml où l'on définit le type du package et ses réglages.

Les packages peuvent aussi avoir des dépendances sur d'autres packages et Packs d'Assets, ce qui permet aux scripters de réutiliser du code et des ressources sur plusieurs paquets. Cela peut aider à réduire la duplication et améliorer l'organisation, en particulier pour les scripts plus grands ou les modes de jeu.

tip

Si nous voulons qu'un paquet soit chargé, nous devons le définir dans notre serveur Config.toml, sous la section [game], il y a un paramètre pour chaque type de paquet (game_mode, loading_screen, map et packages), sur laquelle nous devons lister les noms de paquets que nous voulons que le serveur charge !

Dans l'ensemble, les packages sont un concept essentiel dans le modding de nanos world, et ils fournissent un moyen puissant et souple de créer du contenu personnalisé et des fonctionnalités pour le jeu. En savoir plus dans notre Guide des Packages:

Guide des Packagescore-concepts/packages/packages-guide

Scripting​

Le scripting est un moyen puissant de programmer et de personnaliser nanos world. Il nous permet d'écouter les événements du jeu, d'apparaître, d'interagir et de changer les entités à volonté.

Tous les codes Lua sont exécutés par le biais de Packages, il est possible d'avoir des scripts qui s'exécute uniquement sur le serveur, sur le client ou sur les deux.

Apprentissage​

tip

Si vous débutez à la programmation, YouTube ou n'importe quel site Web de Cours sont de bons endroits pour commencer à apprendre les bases du codage dans divers langages de programmation.

Pour le scripting en Lua, le Manuel Lua officiel fournit un tutoriel complet sur les bases du langage.

De plus, notre documentation est une excellente ressource pour apprendre sur tout ce qui est disponible dans le monde nanos. Vous pouvez explorer notre catégorie Modes de jeu et Packages pour trouver des exemple concret !

La section 🎓 CORE CONCEPTS fournit des guides et d'excellentes informations tandis que 👨‍💻 SCRIPTING REFERENCE fournit des détails techniques sur l'utilisation de notre API!

De plus, notre Discord officiel est une communauté active où vous pouvez poser des questions, obtenir de l'aide et échanger avec d'autres développeurs de scripts !

Structure du dossier de paquets (Package)​

La structure d'un Package de scripts comprend les dossiers Server/, Client/ et Shared/. Vous pouvez mettre des fichiers .lua dans chaque dossier pour exécuter du code sur le serveur, le client, ou les deux (partagé):

my-package/
├── Server/
│ ├── Index.lua
│ └── *.lua
├── Client/
│ ├── Index.lua
│ └── *.lua
├── Shared/
│ ├── Index.lua
│ └── *.lua
└── Package.toml
tip

Seuls les paquets de type script, game-mode et map auront cette structure, car ce sont les seuls qui exécutent des scripts Lua.

Le Package ne charge que le fichier Index.lua de chaque dossier de base (Server/, Client/ ou Shared/) ; c'est ce fichier qui importe les autres fichiers de script.

Vous pouvez trouver plusieurs guides pour les scripts dans la section 🎓 CORE CONCEPTS / Scripting dans la barre latérale gauche.

Concepts d'Unreal Engine​

Voici quelques concepts qui peuvent paraître complexes si vous ne connaissez pas bien Unreal Engine. Étant donné que nanos world s'appuie sur cette base, nous reprenons pour l'essentiel ses concepts et sa terminologie.

Vector, Rotator et Units​

Dans Unreal Engine, le monde est représenté dans un espace 3D en utilisant un système de coordonnées. La position des objets dans cet espace est représentée à l'aide de Vector, qui correspondent essentiellement à un ensemble de trois nombres représentant les coordonnées X (avant), Y (droite) et Z (haut).

L'orientation des objets est représentée à l'aide de Rotator, qui correspondent à un ensemble de trois nombres représentant le Pitch (rotation autour de l'axe Y), le Yaw (rotation autour de l'axe Z) et le Roll (rotation autour de l'axe X).

Unreal Engine utilise les centimètres comme unité de mesure par défaut ; ainsi, lorsqu'un vecteur présente les valeurs (100, 0, 0), cela signifie que l'objet se trouve à 100 centimètres (soit 1 mètre) en avant du point d'origine.

Acteurs​

Dans Unreal Engine, un Actor (Acteur) est n'importe quel objet qui peut être placé dans le monde. C'est la classe de base pour tous les objets qui ont une représentation physique dans le monde du jeu, comme Character, Prop, Weapon et plus. On peut généralement les faire apparaître avec une location et une rotation.

Players vs Characters vs Pawn​

Une notion importante à comprendre est la différence entre Player, Character et Base Pawn.

Dans Unreal Engine, un PlayerController est l'entité qui représente un joueur connecté sur le serveur, alors qu'un Pawn est la représentation physique d'un joueur dans le monde, qui peut être contrôlée. Et Character est un type de « Pawn » doté de fonctions supplémentaires, telles que la marche, le saut, etc.

Pour Nanos World, nous avons quelque peu simplifié ce concept, et nous disposons simplement d'une classe Player qui représente le joueur. Et quelques classes de personnages, telles que Character et CharacterSimple, qui correspondent à la représentation physique du joueur dans l'univers du jeu.

Ainsi, lorsqu'un Player apparaît, cela signifie qu'une personne réelle vient de se connecter au serveur.

Journal et débogage​

Vous pouvez afficher des messages sur la console à l'aide de la classe statique Console, qui propose différents niveaux de journalisation, tels que .Log(), .Debug(), .Warn() et .Error() :

my-package/Shared/Index.lua
-- Sortie standard de la console
Console.Log("Logs de script normaux !")
Console.Debug("Message de débogage qui n'apparaîtra que si le niveau de journalisation (logs) est >= debug !")
Console.Warn("Message d'avertissement orange ! Affichera la trace de la pile !")
Console.Error("Messages d'erreur rouges ! Affichera la trace de la pile !")

Classes​

Les classes sont des entités que vous pouvez créer, interagir et modifier à votre guise. Voici un exemple illustrant la création d'un Light et d'un StaticMesh sur le serveur (afin qu'ils soient automatiquement synchronisés pour tous les joueurs) :

my-package/Server/Index.lua
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")

Ou même interagir avec celles qui existent déjà et les modifier à notre guise, par exemple avec un élément Prop existant :

my-package/Server/Index.lua
local some_prop = Prop.GetByIndex(1)

-- GetByIndex returns nil if there is no Prop at that index
if (some_prop) then
some_prop:SetMesh("nanos-world::SM_Pyramid_VR")
some_prop:SetLocation(Vector(100, 100, 100))

local prop_rotation = some_prop:GetRotation()
end
attention

Le code dans Shared/ s'exécute à la fois sur le serveur et sur chaque client. Si vous créez une entité à cet endroit, le serveur en crée une (synchronisée pour tous les joueurs) et chaque client crée sa propre copie locale. Créer des entités en réseau uniquement dans le répertoire Server/.

En savoir plus sur les classes dans le Guide des classes :

Guide des classescore-concepts/scripting/classes-guide

Classes statiques​

Outre les Classes, nous avons aussi les Classes statiques, qui ne sont que des bibliothèques Lua avec des fonctions qui peuvent être appelées sans avoir à faire apparaître une entité ou avoir une instance.

Par exemple, nous pouvons envoyer un message dans le chat à l'aide de la classe statique Chat :

my-package/Server/Index.lua
Chat.BroadcastMessage("Bonjour tout le monde !")

Ou en lançant un raycast avec la classe statique Trace côté client :

my-package/Client/Index.lua
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

-- Do the trace
local trace_result = Trace.LineSingle(start_location, end_location, collision_channels, trace_mode)

Événements​

Dans nanos world, toute l'API repose sur des événements, l'équivalent des hooks dans d'autres jeux.

Par exemple, des événements se déclenchent quand un Player apparaît (Spawn), quand un Character monte dans un véhicule (EnterVehicle) ou quand il subit des dégâts (TakeDamage).

Vous pouvez écouter ces événements en Lua et réagir en conséquence. Par exemple, voici comment savoir qu'un Player rejoint le serveur ou qu'un Prop apparaît :

my-package/Shared/Index.lua
-- Subscribes when a Player joins the server
Player.Subscribe("Spawn", function(player)
Console.Log("The player %s has joined the server!", player:GetName())
end)

-- Subscribes when a Prop is spawned
Prop.Subscribe("Spawn", function(prop)
Console.Log("A prop id %d just spawned!", prop:GetID())
end)

Pour en savoir plus sur les événements, consultez le Guide des événements :

Guide des événementscore-concepts/scripting/events-guide

Timers​

Avec notre classe statique Timer, vous pouvez créer des timers et des délais pour exécuter du code après un certain temps. C'est utile pour créer des événements chronométrés, des temps de recharge, ou dès que vous voulez retarder l'exécution d'un code.

Notre API de timers ressemble aux timers de JavaScript : utilisez Timer.SetTimeout() pour exécuter une fonction une fois après un délai, ou Timer.SetInterval() pour l'exécuter en boucle à un intervalle donné.

my-package/Shared/Index.lua
-- Runs a function every 10 seconds
Timer.SetInterval(function()
Console.Log("Triggered each 10 seconds!")
end, 10000)

-- Runs a function after 1 minute (once)
Timer.SetTimeout(function()
-- Do Something
end, 60000)

Pour en savoir plus sur les timers, consultez la page Timer :

Classe statique Timerscripting-reference/static-classes/timer

Retrouver des entités existantes​

En plus de faire apparaître une entité et de garder une référence dans une variable, ou de récupérer sa référence via un événement, il faut parfois retrouver une entité déjà apparue.

Par exemple, on peut récupérer toutes les entités apparues d'une classe avec leurs méthodes statiques .GetAll() ou .GetPairs() :

my-package/Shared/Index.lua
-- Returns a copy of a table of all Characters spawned
local all_characters = Character.GetAll()
my-package/Shared/Index.lua
-- Efficiently iterates over all Players spawned
for _, player in pairs(Player.GetPairs()) do
Console.Log("Player %s is connected!", player:GetName())
end

On peut aussi récupérer la référence d'un Prop existant à partir de son index avec la méthode .GetByIndex() :

my-package/Shared/Index.lua
local my_prop = Prop.GetByIndex(10)

On peut encore récupérer une entité par son ID unique global avec les méthodes Server.GetEntityByID() ou Client.GetEntityByID() :

my-package/Server/Index.lua
local my_character = Character(Vector(), Rotator(), "nanos-world::SK_Male")
local character_id = my_character:GetID()

-- Later on...
local my_character = Server.GetEntityByID(character_id)

Récupérer le Character d'un Player​

Comme expliqué dans la section Players vs Characters vs Pawn, un Player représente un joueur connecté au serveur, tandis qu'un Character est sa représentation physique dans le monde.

Vous pouvez récupérer le Character contrôlé par le Player avec la méthode Player:GetControlledCharacter().

Récupérer le Player local côté client​

Côté client, des méthodes et événements spéciaux permettent de récupérer le Player local : Client.GetLocalPlayer() et SpawnLocalPlayer (Client) :

my-package/Client/Index.lua
local local_player = Client.GetLocalPlayer()

-- Note: the local Player is only available after the Client "SpawnLocalPlayer" event is triggered
if (local_player) then
local local_character = local_player:GetControlledCharacter()

-- The Player may not be possessing any Character at the moment
if (local_character) then
Console.Log("My character is at location %s", local_character:GetLocation())
end
end
my-package/Client/Index.lua
Client.Subscribe("SpawnLocalPlayer", function(player)
Console.Log("My Player just spawned in client side! %s", player:GetName())
-- Only now it's possible to access Client.GetLocalPlayer()
end)

Réseau​

Notre architecture client-serveur a été pensée pour rendre tout ce qui touche au réseau et à la synchronisation simple et facile. Le scripteur n'a plus qu'à se concentrer sur la partie créative !

Toutes les entités apparues sur le serveur apparaissent et se synchronisent aussi automatiquement sur le client.

Quand on appelle une fonction sur une entité côté serveur, elle est aussi automatiquement appelée sur la même entité chez chaque client connecté. Tout le monde reste ainsi synchronisé avec tous les changements !

Par exemple, si on fait apparaître un Character et qu'on définit sa position, tous les clients verront aussi sa position mise à jour :

my-package/Server/Index.lua
-- Spawns a Character (on server side)
local my_character = Character(Vector(0, 0, 0), Rotator(), "nanos-world::SK_Male")

-- Sets it's location
my_character:SetLocation(Vector(100, 100, 100))

Certaines classes ne peuvent apparaître que sur le serveur, d'autres que sur le client. C'est pareil pour certaines de leurs fonctions. Ainsi, si vous faites apparaître une entité côté client, elle n'existera que pour ce client.

tip

Dans cette documentation, des infobulles Authority indiquent si une fonction peut être appelée sur le serveur , sur le client , des deux côtés ou seulement du côté qui l'a fait apparaître !

Communication entre les côtés​

Pour envoyer des informations du serveur au client ou inversement, notre classe statique Events expose des méthodes spéciales !

my-package/Client/Index.lua
-- Subscribes to a Remote Event on client side
Events.SubscribeRemote("MyClientEvent", function(my_text)
Console.Log("Event received from server! " .. my_text)
-- outputs "Event received from server! hello nanos world!"
end)
my-package/Server/Index.lua
-- Sends a remote Event to all Players in all Client Packages
Events.BroadcastRemote("MyClientEvent", Reliability.Reliable, "hello nanos world!")

Pour en savoir plus sur les événements réseau, consultez notre guide Communiquer entre Packages :

Communiquer entre Packagescore-concepts/scripting/communicating-between-packages

Valeurs synchronisées​

En plus d'envoyer des données via les événements, qui sont dynamiques, on peut aussi associer des valeurs personnalisées à des entités, ou même globalement au Server/Client.

Par exemple, on peut définir une valeur sur n'importe quelle entité, accessible depuis n'importe quel Package :

-- Sets a 'my_value' value to a Prop
my_prop:SetValue("my_value", 100)

-- Later on, get the value again
local my_value = my_prop:GetValue("my_value")

my_value est alors défini sur cette entité, globalement, de ce côté (client ou serveur).

Mais on peut aussi envoyer une valeur synchronisée sur une entité (si on est côté serveur) ! Il suffit d'ajouter le paramètre true pour qu'elle soit aussi envoyée et synchronisée à tous les clients !

my-package/Server/Index.lua
-- Sets a synchronized 'my_value' value to a Prop
my_prop:SetValue("my_value", 100, true)

Ces valeurs sont accessibles par n'importe quel Package et sont un excellent moyen de synchroniser des valeurs personnalisées !

Pour en savoir plus, consultez notre guide Entity Values :

Entity Valuescore-concepts/scripting/entity-values

Interface​

L'interface utilisateur est un aspect essentiel du jeu, et nous proposons plusieurs façons flexibles de la créer !

WebUI​

La plus directe est la WebUI, qui permet de créer et d'intégrer des interfaces en HTML/CSS/JavaScript, avec une liberté totale de création et de personnalisation.

Lua et JavaScript peuvent communiquer grâce aux événements !

Pour plus d'informations, consultez notre tutoriel WebUI :

HUD basique (HTML)getting-started/tutorials-and-examples/basic-hud-html

Widget​

Une autre façon, plus récente et plus avancée, de créer une interface est d'utiliser les Widget d'Unreal ! Ils vous donnent accès à tous les puissants widgets, et vous pouvez même créer les vôtres dans Unreal Engine puis les importer en jeu, pour une expérience utilisateur fluide !

Pour plus d'informations, consultez la page de la classe Widget :

Classe Widgetscripting-reference/classes/widget

Maps​

Les maps (ou Levels dans Unreal) sont le point d'entrée du jeu : c'est là que le monde est chargé et que les entités apparaissent côté client.

Pour configurer une map, on crée un Package de type map. Ce type est comme script, mais son Package.toml a une section [map] avec des réglages supplémentaires, comme map_asset, qui définit quel Map Asset ce Package charge :

Packages/my-map/Package.toml
[meta]
title = "My Map"
author = "Me"
version = "1.0.0"

[map]
# the Map Asset to load, in the format [ASSET_PACK]::[ASSET_KEY]
map_asset = "my-asset-pack::MyLevel"
# asset packs this map needs
assets_requirements = [ "my-asset-pack" ]
# where players can spawn
spawn_points = [
{ location = "Vector(0, 0, 100)", rotation = "Rotator(0, 0, 0)" },
]

On choisit la map du serveur dans le Config.toml, avec le réglage map = "my-map" de la section [game].

tip

Nous fournissons 3 maps intégrées, chargeables sans aucun Package ni Asset Pack supplémentaire : default-blank-map, default-empty-map et default-ocean-map. Nous recommandons aussi default-playground-map (l'ancienne default-testing-map reste disponible), toutes deux sur le Vault et téléchargées automatiquement quand elles sont utilisées.

Dans le Package.toml de la map, on peut aussi configurer spawn_points et custom_data, accessibles par script depuis n'importe quel Package :

my-package/Server/Index.lua
local spawn_points = Server.GetMapSpawnPoints()
local custom_data = Server.GetMapConfig()

Vous l'avez remarqué : charger une map charge aussi un Package avec des scripts, ce qui permet d'avoir des scripts propres à chaque map. Pratique pour ajouter des comportements spécifiques, comme un décor interactif ou des portes.

Modes de jeu​

Les game-modes sont un type de Package qui se comporte exactement comme les scripts, sauf qu'on ne peut charger qu'un seul game-mode à la fois, alors qu'on peut faire tourner autant de scripts qu'on veut.

Ils servent à créer des expériences de jeu uniques et autonomes.

Les joueurs peuvent facilement lancer un serveur en choisissant un game-mode dans la section New Game du menu principal.

Conclusion​

Dans ce guide, nous avons vu quelques concepts essentiels du scripting et du modding dans nanos world. Ces concepts sont importants, car ils sont à la base de tout game-mode ou script. Nous espérons que vous pouvez maintenant commencer à créer du contenu et donner vie à vos idées !

Si vous avez des questions, n'hésitez pas à les poser sur notre Discord ! Notre communauté est géniale et très accueillante, nous serons ravis de vous aider !

Nous vous conseillons maintenant de commencer par notre guide de démarrage rapide pour créer votre premier package !

Démarrage rapidegetting-started/quick-start