-
Notifications
You must be signed in to change notification settings - Fork 75
The best approach to implement NeoLua in a game engine
Welches wäre deiner Meinung nach der beste Weg, NeoLua für das Skripten von NPC's einzusetzen.
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, ws 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.
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;
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; }
}
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.
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; }
}
Dieses Script 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);
}
...
}
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.
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);
}
}
Dabei ist es notwendig das globale Environment mit dem Eigenen zu verbinden, sonst können in dem Skript keine globalen Funktionen verwendet werden.
Die Methode Hit lässt sich nun ohne Umwege in beiden Welten verwenden.
dynamic npc = ...;
npc.Hit(bullet);
npc.Hit(bullet);
Desweiteren können Methoden und Eigenschaften zwischen den Welten getauscht werden.
public class NPC : LuaTable
{
...
[LuaMember("Die")]
public void Die()
{
}
// Gemeinsamer Wert
[LuaMember("Health")}
public int Health { get; set; }
}
Im NPC-Skript sehen die Eigenschaften wie globale Variablen aus.
Health = 100;
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;
Ich hoffe ich konnte damit einige Anregungen geben.