-
Notifications
You must be signed in to change notification settings - Fork 75
The best approach to implement NeoLua in a game engine
What is in your opionion the best practice to use NeoLua for scripting npc's.
Welches wäre deiner Meinung nach der beste Weg, NeoLua für das Skripten von NPC's einzusetzen.
NPC's or common objects in game get created or destroyed very often during runtime. The attached script will be recompiled every time, what will result in a performance hit. This is not necessary, because the objects can grouped into classes where every group has the own set of scripts.
NPC's oder allgemein Objekte in Spielen werden immer wieder erzeugt und zerstört. Das eventuell zugehörige Script wird dabei immer wieder neu kompiliert, was zu Performanceengpässen führen kann. Dies ist auch nicht notwendig, da sich die Objekte in Klassen einteilen lassen und jede Klasse ihr eigenes Set an Scripten erhält.
Every script gets for every scripted part there own LuaTable, which holds the functions and members.
Jedes Script erhält für die geskripten Anteil eine LuaTable, die die entsprechenden funktionen enthält.
local npc = {}
function npc:Hit(obj)
end;
return npc;
The functions can be call with CallMember or you use a dynamic call.
Die Funktionen können per CallMember oder dynamic calls einfach aufgerufen werden.
public class NPC
{
public NPC(LuaGlobal env) // P1
{
Ability = env.DoChunk(scriptfile); // P2
}
public void Hit(object obj) // P3
{
Ability.Hit(obj);
}
public dynamic Ability { get; private set; }
}
What I is problamatic in this approach:
- P1: All scripts get compiled in the global envrionment. This can result in conflicts with the global members, and the application/game will be behave unexpected.
- P2: Every time, the npc is created. The script will be parsed and compiled again.
- P3: The mix the host code with the script code you need ugly proxy code.
Was ist Problematisch bei diesem Ansatz:
- P1: Da alle Scripte innerhalb des globalen Environments kompiliert werden, kann es zu Problemen mit globalen Funktionen/Variablen kommen und zu unvorhergesehenen verhalten führen.
- P2: Jedesmal wenn ein NPC erzeugt wird, wird immer wieder das Skript erneut übersetzt. Das ist Zeit.
- P3: Das Mischen von Host- und Scriptcode bewirkt, dass Proxycode geschrieben werden muss. Das blählt das System auf.
To prevent the recompiling, you need to implement a for scripts, that are used very often, a script cache.
Das Rekompilieren verhintert man, indem man sich für die immer wiederkehrenden Skripte einen Cache baut, in dem die Scripte beim Laden oder auf Anfrage erzeugt werden. Bei Spielen sollte das wohl beim Laden passieren.
public class ScriptCache
{
private Lua lua = new Lua();
public ScriptCache()
{
NPC = lua.CompileChunk(scriptfile, null);
}
public LuaChunk NPC { get; private set; }
}
You can run the script NPC
without recompiling it. The script is now a chunk.
Dieses Skript
NPC
, welches nun als Chunk bezeichnet wird, kann man beliebig oft auf LuaTable's ausführen. Ohne, das es neu Kompiliert werden muss.
public class NPC
{
public NPC(LuaGlobal env, ScriptCache cache)
{
Ability = env.DoChunk(cache.NPC);
}
...
}
The create a better interaction between the C# and lua, it is the best to inherit your NPC or object from LuaTable. This will create a dynamic object, which has also a static interface. The initialization of the Lua-World can be done with the pre-compiled chunk.
Um das Zusammenspiel zwischen C# und Lua zu verbessern. Ist es am besten die Klasse von LuaTable abzuleiten. Dabei erhält man ein dynamisches Objekt, welches ein statisches Interface besitzt. Die Initialisierung der Lua-Welt erfolgt über den Chunk.
This approach needs changes on the lua code.
Dazu muss der Lua-Code geringfügig angepasst werden.
function Hit(obj)
end;
Die C#-Welt sieht wie folgt aus.
public class NPC : LuaTable
{
private LuaGlobal env;
public NPC(LuaGlobal env, ScriptCache cache)
{
this.env = env;
cache.NPC.Run(this, LuaResult.Empty.Values); // bis Version 0.9.10
// > 0.9.10: cache.NPC.Run(this);
}
protected override object OnIndex(object key)
{
return base.OnIndex(key) ?? env.OnIndex(key);
}
}
In this approach it is necessary to connect the globel environment with the npc/object environment, so that you can access the global functions and variables.
Dabei ist es notwendig das globale Environment mit dem Eigenen zu verbinden, sonst können in dem Skript keine globalen Funktionen verwendet werden.
The method Hit is now without any proxy in both worlds useable.
Die Methode Hit lässt sich nun ohne Umwege in beiden Welten verwenden.
dynamic npc = ...;
npc.Hit(bullet);
npc.Hit(bullet);
To exchange members/methods/variables between the two world you can use LuaMember
Desweiteren können Methoden und Eigenschaften zwischen den Welten getauscht werden. LuaMember registriert dabei den Member in der LuaTable.
public class NPC : LuaTable
{
...
[LuaMember("Die")]
public void Die()
{
}
// Gemeinsamer Wert
[LuaMember("Health")}
public int Health { get; set; }
}
In the NPC-script the variables/functions look like globale variables.
Im NPC-Skript sehen die Eigenschaften wie globale Variablen aus.
Health = 100;
The is no difference for the caller.
Wird auf den NPC Zugriffen, verhält sich der Wert wie ein Member. Wie man sieht merkt der Host bzw. das Skript nicht den Unterschied zwischen den beiden Welten.
npc.Health = 100;
npc.Health = 100;